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