http_client_misc.c
Go to the documentation of this file.
1 /**
2  * @file http_client_misc.c
3  * @brief Helper functions for HTTP client
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2024 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.4.0
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL HTTP_TRACE_LEVEL
33 
34 //Dependencies
35 #include <limits.h>
36 #include <stdlib.h>
37 #include "core/net.h"
38 #include "http/http_client.h"
39 #include "http/http_client_auth.h"
41 #include "http/http_client_misc.h"
42 #include "str.h"
43 #include "debug.h"
44 
45 //Check TCP/IP stack configuration
46 #if (HTTP_CLIENT_SUPPORT == ENABLED)
47 
48 
49 /**
50  * @brief Update HTTP client state
51  * @param[in] context Pointer to the HTTP client context
52  * @param[in] newState New state to switch to
53  **/
54 
56  HttpClientState newState)
57 {
58  //Update HTTP connection state
59  context->state = newState;
60 
61  //Save current time
62  context->timestamp = osGetSystemTime();
63 }
64 
65 
66 /**
67  * @brief Update HTTP request state
68  * @param[in] context Pointer to the HTTP client context
69  * @param[in] newState New state to switch to
70  **/
71 
73  HttpRequestState newState)
74 {
75  //Update HTTP request state
76  context->requestState = newState;
77 
78  //Save current time
79  context->timestamp = osGetSystemTime();
80 }
81 
82 
83 /**
84  * @brief Format default HTTP request header
85  * @param[in] context Pointer to the HTTP client context
86  * @return Error code
87  **/
88 
90 {
91  size_t n;
92  const char_t *version;
93 
94  //Check HTTP version
95  if(context->version == HTTP_VERSION_1_0)
96  {
97  //Select protocol version 1.0
98  version = "HTTP/1.0";
99 
100  //Under HTTP/1.0, connections are not considered persistent unless a
101  //keepalive header is included
102  context->keepAlive = FALSE;
103 
104  //a persistent connection with an HTTP/1.0 client cannot make use of
105  //the chunked transfer-coding, and therefore must use a Content-Length
106  //for marking the ending boundary of each message (refer to RFC 2068,
107  //section 19.7.1)
108  context->chunkedEncoding = FALSE;
109  }
110  else if(context->version == HTTP_VERSION_1_1)
111  {
112  //Select protocol version 1.1
113  version = "HTTP/1.1";
114 
115  //In HTTP/1.1, all connections are considered persistent unless declared
116  //otherwise
117  context->keepAlive = TRUE;
118 
119  //The chunked encoding modifies the body of a message in order to transfer
120  //it as a series of chunks, each with its own size indicator
121  context->chunkedEncoding = FALSE;
122  }
123  else
124  {
125  //Unknown HTTP version
126  return ERROR_INVALID_VERSION;
127  }
128 
129  //Set default HTTP request method
130  osStrcpy(context->method, "GET");
131 
132  //The Request-Line begins with a method token, followed by the Request-URI
133  //and the protocol version, and ending with CRLF
134  n = osSprintf(context->buffer, "GET / %s\r\n", version);
135 
136  //Terminate the HTTP request header with a CRLF sequence
137  n += osSprintf(context->buffer + n, "\r\n");
138 
139  //Set the length of the request header
140  context->bufferLen = n;
141  context->bufferPos = 0;
142 
143  //Successful processing
144  return NO_ERROR;
145 }
146 
147 
148 /**
149  * @brief Format chunk-size field
150  * @param[in] context Pointer to the HTTP client context
151  * @param[in] length Size of the chunk
152  * @return Error code
153  **/
154 
156 {
157  size_t n = 0;
158 
159  //The chunked encoding modifies the body in order to transfer it as a
160  //series of chunks, each with its own size indicator
161  context->bodyLen = length;
162  context->bodyPos = 0;
163 
164  //Check whether the chunk is the first one or not
165  if(context->requestState == HTTP_REQ_STATE_SEND_CHUNK_DATA)
166  {
167  //Terminate the data of the previous chunk with a CRLF sequence
168  n += osSprintf(context->buffer + n, "\r\n");
169  }
170 
171  //The chunk-size field is a string of hex digits indicating the size of the
172  //chunk
173  n += osSprintf(context->buffer + n, "%" PRIXSIZE "\r\n", length);
174 
175  //Check whether the chunk is the last one
176  if(length == 0)
177  {
178  //The trailer allows the sender to include additional HTTP header fields
179  //at the end of the message. The trailer is terminated with a CRLF
180  n += osSprintf(context->buffer + n, "\r\n");
181  }
182 
183  //Set the length of the chunk-size field
184  context->bufferLen = n;
185  context->bufferPos = 0;
186 
187  //The chunked encoding is ended by any chunk whose size is zero
188  if(length == 0)
189  {
190  //The last chunk is followed by an optional trailer
192  }
193  else
194  {
195  //Send the chunk-size field
197  }
198 
199  //Successful processing
200  return NO_ERROR;
201 }
202 
203 
204 /**
205  * @brief Parse HTTP status line
206  * @param[in] context Pointer to the HTTP client context
207  * @param[in] line Pointer to the status line
208  * @param[in] length Length of the status line
209  * @return Error code
210  **/
211 
213  size_t length)
214 {
215  error_t error;
216  char_t *p;
217  char_t *token;
218 
219  //Properly terminate the string with a NULL character
220  line[length] = '\0';
221 
222  //Debug message
223  TRACE_DEBUG("HTTP response header:\r\n%s\r\n", line);
224 
225  //The string must contains visible characters only
226  error = httpCheckCharset(line, length, HTTP_CHARSET_TEXT);
227  //Any error to report?
228  if(error)
229  return error;
230 
231  //Parse HTTP-Version field
232  token = osStrtok_r(line, " ", &p);
233  //Any parsing error?
234  if(token == NULL)
235  return ERROR_INVALID_SYNTAX;
236 
237  //Check protocol version
238  if(!osStrcasecmp(token, "HTTP/1.0"))
239  {
240  //Under HTTP/1.0, connections are not considered persistent unless a
241  //keepalive header is included
242  context->keepAlive = FALSE;
243  }
244  else if(!osStrcasecmp(token, "HTTP/1.1"))
245  {
246  //In HTTP/1.1, all connections are considered persistent unless declared
247  //otherwise
248  if(context->version != HTTP_VERSION_1_1)
249  context->keepAlive = FALSE;
250  }
251  else
252  {
253  //Unknown HTTP version
254  return ERROR_INVALID_VERSION;
255  }
256 
257  //Parse Status-Code field
258  token = osStrtok_r(NULL, " ", &p);
259  //Any parsing error?
260  if(token == NULL)
261  return ERROR_INVALID_SYNTAX;
262 
263  //The Status-Code element is a 3-digit integer
264  context->statusCode = osStrtoul(token, &p, 10);
265  //Any parsing error?
266  if(*p != '\0')
267  return ERROR_INVALID_SYNTAX;
268 
269  //The chunked encoding modifies the body of a message in order to transfer
270  //it as a series of chunks, each with its own size indicator
271  context->chunkedEncoding = FALSE;
272 
273  //For response message without a declared message body length, the message
274  //body length is determined by the number of octets received prior to the
275  //server closing the connection
276  context->bodyLen = UINT_MAX;
277 
278  //Flush receive buffer
279  context->bufferLen = 0;
280  context->bufferPos = 0;
281  context->bodyPos = 0;
282 
283  //Successful processing
284  return NO_ERROR;
285 }
286 
287 
288 /**
289  * @brief Parse HTTP response header field
290  * @param[in] context Pointer to the HTTP client context
291  * @param[in] line Pointer to the header field
292  * @param[in] length Length of the header field
293  * @return Error code
294  **/
295 
297  size_t length)
298 {
299  error_t error;
300  char_t *name;
301  size_t nameLen;
302  char_t *value;
303  size_t valueLen;
304  char_t *separator;
305 
306  //Properly terminate the string with a NULL character
307  line[length] = '\0';
308 
309  //Debug message
310  TRACE_DEBUG("%s\r\n", line);
311 
312  //The string must contains visible characters only
313  error = httpCheckCharset(line, length, HTTP_CHARSET_TEXT);
314  //Any error to report?
315  if(error)
316  return error;
317 
318  //Header field values can be folded onto multiple lines if the continuation
319  //line begins with a space or horizontal tab
320  if(line[0] == ' ' || line[0] == '\t')
321  {
322  //A continuation line cannot immediately follows the Status-Line
323  if(context->bufferPos == 0)
324  return ERROR_INVALID_SYNTAX;
325 
326  //Remove optional leading and trailing whitespace
327  value = strTrimWhitespace(line);
328  //Retrieve the length of the resulting string
329  valueLen = osStrlen(value);
330 
331  //Sanity check
332  if(valueLen > 0)
333  {
334  //The folding LWS is replaced with a single SP before interpretation
335  //of the TEXT value (refer to RFC 2616, section 2.2)
336  context->buffer[context->bufferPos - 1] = ' ';
337 
338  //Save field value
339  osMemmove(context->buffer + context->bufferPos, value, valueLen + 1);
340 
341  //Update the size of the hash table
342  context->bufferLen = context->bufferPos + valueLen + 1;
343  }
344  }
345  else
346  {
347  //Each header field consists of a case-insensitive field name followed
348  //by a colon, optional leading whitespace, the field value, and optional
349  //trailing whitespace (refer to RFC 7230, section 3.2)
350  separator = osStrchr(line, ':');
351 
352  //Any parsing error?
353  if(separator == NULL)
354  return ERROR_INVALID_SYNTAX;
355 
356  //Split the line
357  *separator = '\0';
358 
359  //Remove optional leading and trailing whitespace
360  name = strTrimWhitespace(line);
361  value = strTrimWhitespace(separator + 1);
362 
363  //Retrieve the length of the resulting strings
364  nameLen = osStrlen(name);
365  valueLen = osStrlen(value);
366 
367  //The field name cannot be empty
368  if(nameLen == 0)
369  return ERROR_INVALID_SYNTAX;
370 
371  //Check header field name
372  if(!osStrcasecmp(name, "Connection"))
373  {
374  //Parse Connection header field
376  }
377  else if(!osStrcasecmp(name, "Transfer-Encoding"))
378  {
379  //Parse Transfer-Encoding header field
381  }
382  else if(!osStrcasecmp(name, "Content-Length"))
383  {
384  //Parse Content-Length header field
386  }
387 #if (HTTP_CLIENT_AUTH_SUPPORT == ENABLED)
388  //WWW-Authenticate header field found?
389  else if(!osStrcasecmp(name, "WWW-Authenticate"))
390  {
391  //Parse WWW-Authenticate header field
393  }
394 #endif
395  else
396  {
397  //Discard unknown header fields
398  }
399 
400  //Save each header field into a hash table by field name
401  osMemmove(context->buffer + context->bufferPos, name, nameLen + 1);
402 
403  //Save field value
404  osMemmove(context->buffer + context->bufferPos + nameLen + 1, value,
405  valueLen + 1);
406 
407  //Update the size of the hash table
408  context->bufferLen = context->bufferPos + nameLen + valueLen + 2;
409  }
410 
411  //Decode the next header field
412  context->bufferPos = context->bufferLen;
413 
414  //Successful processing
415  return NO_ERROR;
416 }
417 
418 
419 /**
420  * @brief Parse Connection header field
421  * @param[in] context Pointer to the HTTP client context
422  * @param[in] value NULL-terminated string that contains the field value
423  * @return Error code
424  **/
425 
427  const char_t *value)
428 {
429  size_t n;
430 
431  //Parse the comma-separated list
432  while(value[0] != '\0')
433  {
434  //Get the length of the current token
435  n = strcspn(value, ", \t");
436 
437  //Check current value
438  if(n == 10 && !osStrncasecmp(value, "keep-alive", 10))
439  {
440  //Check HTTP request state
441  if(context->requestState == HTTP_REQ_STATE_FORMAT_HEADER)
442  {
443  //Make the connection persistent
444  context->keepAlive = TRUE;
445  }
446  }
447  else if(n == 5 && !osStrncasecmp(value, "close", 5))
448  {
449  //The connection will be closed after completion of the response
450  context->keepAlive = FALSE;
451  }
452  else
453  {
454  //Just for sanity
455  }
456 
457  //Advance the pointer over the separator
458  if(value[n] != '\0')
459  n++;
460 
461  //Point to the next token
462  value += n;
463  }
464 
465  //Successful processing
466  return NO_ERROR;
467 }
468 
469 
470 /**
471  * @brief Parse Transfer-Encoding header field
472  * @param[in] context Pointer to the HTTP client context
473  * @param[in] value NULL-terminated string that contains the field value
474  * @return Error code
475  **/
476 
478  const char_t *value)
479 {
480  //Check the value of the header field
481  if(!osStrcasecmp(value, "chunked"))
482  {
483  //If a Transfer-Encoding header field is present and the chunked
484  //transfer coding is the final encoding, the message body length
485  //is determined by reading and decoding the chunked data until the
486  //transfer coding indicates the data is complete
487  context->chunkedEncoding = TRUE;
488 
489  //If a message is received with both a Transfer-Encoding and a
490  //Content-Length header field, the Transfer-Encoding overrides
491  //the Content-Length (refer to RFC 7230, section 3.3.3)
492  context->bodyLen = 0;
493  }
494 
495  //Successful processing
496  return NO_ERROR;
497 }
498 
499 
500 /**
501  * @brief Parse Content-Length header field
502  * @param[in] context Pointer to the HTTP client context
503  * @param[in] value NULL-terminated string that contains the field value
504  * @return Error code
505  **/
506 
508  const char_t *value)
509 {
510  char_t *p;
511 
512  //If a valid Content-Length header field is present without
513  //Transfer-Encoding, its decimal value defines the expected message
514  //body length in octets (refer to RFC 7230, section 3.3.3)
515  if(!context->chunkedEncoding)
516  {
517  //Retrieve the length of the body
518  context->bodyLen = osStrtoul(value, &p, 10);
519 
520  //Any parsing error?
521  if(*p != '\0')
522  return ERROR_INVALID_SYNTAX;
523  }
524 
525  //Successful processing
526  return NO_ERROR;
527 }
528 
529 
530 /**
531  * @brief Parse chunk-size field
532  * @param[in] context Pointer to the HTTP client context
533  * @param[in] line Pointer to the chunk-size field
534  * @param[in] length Length of the chunk-size field
535  * @return Error code
536  **/
537 
539  size_t length)
540 {
541  char_t *p;
542 
543  //Properly terminate the string with a NULL character
544  line[length] = '\0';
545 
546  //Remove leading and trailing whitespace
547  line = strTrimWhitespace(line);
548 
549  //The chunk-size field is a string of hex digits indicating the size
550  //of the chunk
551  context->bodyLen = osStrtoul(line, &p, 16);
552 
553  //Any parsing error?
554  if(*p != '\0')
555  return ERROR_INVALID_SYNTAX;
556 
557  //Flush receive buffer
558  context->bufferLen = 0;
559  context->bufferPos = 0;
560  context->bodyPos = 0;
561 
562  //The chunked encoding is ended by any chunk whose size is zero
563  if(context->bodyLen == 0)
564  {
565  //The last chunk is followed by an optional trailer
567  }
568  else
569  {
570  //Receive chunk data
572  }
573 
574  //Successful processing
575  return NO_ERROR;
576 }
577 
578 
579 /**
580  * @brief Determine whether a timeout error has occurred
581  * @param[in] context Pointer to the HTTP client context
582  * @return Error code
583  **/
584 
586 {
587 #if (NET_RTOS_SUPPORT == DISABLED)
588  error_t error;
589  systime_t time;
590 
591  //Get current time
592  time = osGetSystemTime();
593 
594  //Check whether the timeout has elapsed
595  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
596  {
597  //Report a timeout error
598  error = ERROR_TIMEOUT;
599  }
600  else
601  {
602  //The operation would block
603  error = ERROR_WOULD_BLOCK;
604  }
605 
606  //Return status code
607  return error;
608 #else
609  //Report a timeout error
610  return ERROR_TIMEOUT;
611 #endif
612 }
613 
614 #endif
uint8_t token[]
Definition: coap_common.h:181
uint8_t version
Definition: coap_common.h:177
#define PRIXSIZE
char char_t
Definition: compiler_port.h:48
Debugging facilities.
#define TRACE_DEBUG(...)
Definition: debug.h:107
uint8_t n
uint32_t time
error_t
Error codes.
Definition: error.h:43
@ ERROR_WOULD_BLOCK
Definition: error.h:96
@ ERROR_TIMEOUT
Definition: error.h:95
@ ERROR_INVALID_SYNTAX
Definition: error.h:68
@ NO_ERROR
Success.
Definition: error.h:44
@ ERROR_INVALID_VERSION
Definition: error.h:118
HTTP client (HyperText Transfer Protocol)
#define HttpClientContext
Definition: http_client.h:198
HttpClientState
HTTP client states.
Definition: http_client.h:211
error_t httpClientParseWwwAuthenticateField(HttpClientContext *context, const char_t *value)
Parse WWW-Authenticate header field.
HTTP authentication.
error_t httpClientParseHeaderField(HttpClientContext *context, char_t *line, size_t length)
Parse HTTP response header field.
error_t httpClientParseContentLengthField(HttpClientContext *context, const char_t *value)
Parse Content-Length header field.
error_t httpClientFormatRequestHeader(HttpClientContext *context)
Format default HTTP request header.
error_t httpClientFormatChunkSize(HttpClientContext *context, size_t length)
Format chunk-size field.
error_t httpClientParseConnectionField(HttpClientContext *context, const char_t *value)
Parse Connection header field.
void httpClientChangeState(HttpClientContext *context, HttpClientState newState)
Update HTTP client state.
error_t httpClientParseStatusLine(HttpClientContext *context, char_t *line, size_t length)
Parse HTTP status line.
void httpClientChangeRequestState(HttpClientContext *context, HttpRequestState newState)
Update HTTP request state.
error_t httpClientParseChunkSize(HttpClientContext *context, char_t *line, size_t length)
Parse chunk-size field.
error_t httpClientParseTransferEncodingField(HttpClientContext *context, const char_t *value)
Parse Transfer-Encoding header field.
error_t httpClientCheckTimeout(HttpClientContext *context)
Determine whether a timeout error has occurred.
Helper functions for HTTP client.
Transport protocol abstraction layer.
error_t httpCheckCharset(const char_t *s, size_t length, uint_t charset)
Check whether a string contains valid characters.
Definition: http_common.c:49
HttpRequestState
HTTP request states.
Definition: http_common.h:110
@ HTTP_REQ_STATE_SEND_CHUNK_SIZE
Definition: http_common.h:116
@ HTTP_REQ_STATE_FORMAT_HEADER
Definition: http_common.h:112
@ HTTP_REQ_STATE_FORMAT_TRAILER
Definition: http_common.h:118
@ HTTP_REQ_STATE_RECEIVE_TRAILER
Definition: http_common.h:127
@ HTTP_REQ_STATE_RECEIVE_CHUNK_DATA
Definition: http_common.h:125
@ HTTP_REQ_STATE_SEND_CHUNK_DATA
Definition: http_common.h:117
@ HTTP_CHARSET_TEXT
Definition: http_common.h:147
@ HTTP_VERSION_1_0
Definition: http_common.h:62
@ HTTP_VERSION_1_1
Definition: http_common.h:63
uint8_t p
Definition: ndp.h:300
TCP/IP stack core.
#define osMemmove(dest, src, length)
Definition: os_port.h:147
#define timeCompare(t1, t2)
Definition: os_port.h:40
#define osStrcasecmp(s1, s2)
Definition: os_port.h:183
#define osStrchr(s, c)
Definition: os_port.h:195
#define osStrlen(s)
Definition: os_port.h:165
#define osSprintf(dest,...)
Definition: os_port.h:231
#define TRUE
Definition: os_port.h:50
#define FALSE
Definition: os_port.h:46
#define osStrcpy(s1, s2)
Definition: os_port.h:207
#define osStrtoul(s, endptr, base)
Definition: os_port.h:249
#define osStrncasecmp(s1, s2, length)
Definition: os_port.h:189
#define osStrtok_r(s, delim, last)
Definition: os_port.h:225
systime_t osGetSystemTime(void)
Retrieve system time.
uint32_t systime_t
System time.
char_t name[]
char_t * strTrimWhitespace(char_t *s)
Removes all leading and trailing whitespace from a string.
Definition: str.c:78
String manipulation helper functions.
uint8_t length
Definition: tcp.h:368
uint8_t value[]
Definition: tcp.h:369