http_server_misc.c
Go to the documentation of this file.
1 /**
2  * @file http_server_misc.c
3  * @brief HTTP server (miscellaneous functions)
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2026 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.6.2
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_server.h"
38 #include "http/http_server_auth.h"
39 #include "http/http_server_misc.h"
40 #include "http/mime.h"
41 #include "str.h"
42 #include "path.h"
43 #include "debug.h"
44 
45 //Check TCP/IP stack configuration
46 #if (HTTP_SERVER_SUPPORT == ENABLED)
47 
48 
49 /**
50  * @brief HTTP status codes
51  **/
52 
53 static const HttpStatusCodeDesc statusCodeList[] =
54 {
55  //Success
56  {200, "OK"},
57  {201, "Created"},
58  {202, "Accepted"},
59  {204, "No Content"},
60  //Redirection
61  {301, "Moved Permanently"},
62  {302, "Found"},
63  {304, "Not Modified"},
64  //Client error
65  {400, "Bad Request"},
66  {401, "Unauthorized"},
67  {403, "Forbidden"},
68  {404, "Not Found"},
69  //Server error
70  {500, "Internal Server Error"},
71  {501, "Not Implemented"},
72  {502, "Bad Gateway"},
73  {503, "Service Unavailable"}
74 };
75 
76 
77 /**
78  * @brief Read HTTP request header and parse its contents
79  * @param[in] connection Structure representing an HTTP connection
80  * @return Error code
81  **/
82 
84 {
85  error_t error;
86  size_t length;
87 
88  //Set the maximum time the server will wait for an HTTP
89  //request before closing the connection
90  error = socketSetTimeout(connection->socket, HTTP_SERVER_IDLE_TIMEOUT);
91  //Any error to report?
92  if(error)
93  return error;
94 
95  //Read the first line of the request
96  error = httpReceive(connection, connection->buffer,
98  //Unable to read any data?
99  if(error)
100  return error;
101 
102  //Revert to default timeout
103  error = socketSetTimeout(connection->socket, HTTP_SERVER_TIMEOUT);
104  //Any error to report?
105  if(error)
106  return error;
107 
108  //Properly terminate the string with a NULL character
109  connection->buffer[length] = '\0';
110  //Debug message
111  TRACE_INFO("%s", connection->buffer);
112 
113  //Parse the Request-Line
114  error = httpParseRequestLine(connection, connection->buffer);
115  //Any error to report?
116  if(error)
117  return error;
118 
119  //Default value for properties
120  connection->request.chunkedEncoding = FALSE;
121  connection->request.contentLength = 0;
122 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
123  connection->request.upgradeWebSocket = FALSE;
124  connection->request.connectionUpgrade = FALSE;
125  osStrcpy(connection->request.clientKey, "");
126 #endif
127 
128  //HTTP 0.9 does not support Full-Request
129  if(connection->request.version >= HTTP_VERSION_1_0)
130  {
131  //Local variables
132  char_t firstChar;
133  char_t *separator;
134  char_t *name;
135  char_t *value;
136 
137  //This variable is used to decode header fields that span multiple lines
138  firstChar = '\0';
139 
140  //Parse the header fields of the HTTP request
141  while(1)
142  {
143  //Decode multiple-line header field
144  error = httpReadHeaderField(connection, connection->buffer,
145  HTTP_SERVER_BUFFER_SIZE, &firstChar);
146  //Any error to report?
147  if(error)
148  return error;
149 
150  //Debug message
151  TRACE_DEBUG("%s", connection->buffer);
152 
153  //An empty line indicates the end of the header fields
154  if(osStrcmp(connection->buffer, "\r\n") == 0)
155  break;
156 
157  //Check whether a separator is present
158  separator = osStrchr(connection->buffer, ':');
159 
160  //Separator found?
161  if(separator != NULL)
162  {
163  //Split the line
164  *separator = '\0';
165 
166  //Trim whitespace characters
167  name = strTrimWhitespace(connection->buffer);
168  value = strTrimWhitespace(separator + 1);
169 
170  //Parse HTTP header field
171  httpParseHeaderField(connection, name, value);
172  }
173  }
174  }
175 
176  //Prepare to read the HTTP request body
177  if(connection->request.chunkedEncoding)
178  {
179  connection->request.byteCount = 0;
180  connection->request.firstChunk = TRUE;
181  connection->request.lastChunk = FALSE;
182  }
183  else
184  {
185  connection->request.byteCount = connection->request.contentLength;
186  }
187 
188  //The request header has been successfully parsed
189  return NO_ERROR;
190 }
191 
192 
193 /**
194  * @brief Parse Request-Line
195  * @param[in] connection Structure representing an HTTP connection
196  * @param[in] requestLine Pointer to the string that holds the Request-Line
197  * @return Error code
198  **/
199 
201 {
202  error_t error;
203  char_t *token;
204  char_t *p;
205  char_t *s;
206 
207  //The Request-Line begins with a method token
208  token = osStrtok_r(requestLine, " \r\n", &p);
209  //Unable to retrieve the method?
210  if(token == NULL)
211  return ERROR_INVALID_REQUEST;
212 
213  //The Method token indicates the method to be performed on the
214  //resource identified by the Request-URI
215  error = strSafeCopy(connection->request.method, token,
217  //Any error to report?
218  if(error)
219  return ERROR_INVALID_REQUEST;
220 
221  //The Request-URI is following the method token
222  token = osStrtok_r(NULL, " \r\n", &p);
223  //Unable to retrieve the Request-URI?
224  if(token == NULL)
225  return ERROR_INVALID_REQUEST;
226 
227  //Check whether a query string is present
228  s = osStrchr(token, '?');
229 
230  //Query string found?
231  if(s != NULL)
232  {
233  //Split the string
234  *s = '\0';
235 
236  //Save the Request-URI
238  connection->request.uri, HTTP_SERVER_URI_MAX_LEN);
239  //Any error to report?
240  if(error)
241  return ERROR_INVALID_REQUEST;
242 
243  //Check the length of the query string
245  return ERROR_INVALID_REQUEST;
246 
247  //Save the query string
248  osStrcpy(connection->request.queryString, s + 1);
249  }
250  else
251  {
252  //Save the Request-URI
254  connection->request.uri, HTTP_SERVER_URI_MAX_LEN);
255  //Any error to report?
256  if(error)
257  return ERROR_INVALID_REQUEST;
258 
259  //No query string
260  connection->request.queryString[0] = '\0';
261  }
262 
263  //Redirect to the default home page if necessary
264  if(osStrcasecmp(connection->request.uri, "/") == 0)
265  osStrcpy(connection->request.uri, connection->settings->defaultDocument);
266 
267  //Clean the resulting path
268  pathCanonicalize(connection->request.uri);
269 
270  //The protocol version is following the Request-URI
271  token = osStrtok_r(NULL, " \r\n", &p);
272 
273  //HTTP version 0.9?
274  if(token == NULL)
275  {
276  //Save version number
277  connection->request.version = HTTP_VERSION_0_9;
278  //Persistent connections are not supported
279  connection->request.keepAlive = FALSE;
280  }
281  //HTTP version 1.0?
282  else if(osStrcasecmp(token, "HTTP/1.0") == 0)
283  {
284  //Save version number
285  connection->request.version = HTTP_VERSION_1_0;
286  //By default connections are not persistent
287  connection->request.keepAlive = FALSE;
288  }
289  //HTTP version 1.1?
290  else if(osStrcasecmp(token, "HTTP/1.1") == 0)
291  {
292  //Save version number
293  connection->request.version = HTTP_VERSION_1_1;
294  //HTTP 1.1 makes persistent connections the default
295  connection->request.keepAlive = TRUE;
296  }
297  //HTTP version not supported?
298  else
299  {
300  //Report an error
301  return ERROR_INVALID_REQUEST;
302  }
303 
304  //Successful processing
305  return NO_ERROR;
306 }
307 
308 
309 /**
310  * @brief Read multiple-line header field
311  * @param[in] connection Structure representing an HTTP connection
312  * @param[out] buffer Buffer where to store the header field
313  * @param[in] size Size of the buffer, in bytes
314  * @param[in,out] firstChar Leading character of the header line
315  * @return Error code
316  **/
317 
319  char_t *buffer, size_t size, char_t *firstChar)
320 {
321  error_t error;
322  size_t n;
323  size_t length;
324 
325  //This is the actual length of the header field
326  length = 0;
327 
328  //The process of moving from a multiple-line representation of a header
329  //field to its single line representation is called unfolding
330  do
331  {
332  //Check the length of the header field
333  if((length + 1) >= size)
334  {
335  //Report an error
336  error = ERROR_INVALID_REQUEST;
337  //Exit immediately
338  break;
339  }
340 
341  //NULL character found?
342  if(*firstChar == '\0')
343  {
344  //Prepare to decode the first header field
345  length = 0;
346  }
347  //LWSP character found?
348  else if(*firstChar == ' ' || *firstChar == '\t')
349  {
350  //Unfolding is accomplished by regarding CRLF immediately
351  //followed by a LWSP as equivalent to the LWSP character
352  buffer[length] = *firstChar;
353  //The current header field spans multiple lines
354  length++;
355  }
356  //Any other character?
357  else
358  {
359  //Restore the very first character of the header field
360  buffer[0] = *firstChar;
361  //Prepare to decode a new header field
362  length = 1;
363  }
364 
365  //Read data until a CLRF character is encountered
366  error = httpReceive(connection, buffer + length,
367  size - 1 - length, &n, SOCKET_FLAG_BREAK_CRLF);
368  //Any error to report?
369  if(error)
370  break;
371 
372  //Update the length of the header field
373  length += n;
374  //Properly terminate the string with a NULL character
375  buffer[length] = '\0';
376 
377  //An empty line indicates the end of the header fields
378  if(osStrcmp(buffer, "\r\n") == 0)
379  break;
380 
381  //Read the next character to detect if the CRLF is immediately
382  //followed by a LWSP character
383  error = httpReceive(connection, firstChar,
384  sizeof(char_t), &n, SOCKET_FLAG_WAIT_ALL);
385  //Any error to report?
386  if(error)
387  break;
388 
389  //LWSP character found?
390  if(*firstChar == ' ' || *firstChar == '\t')
391  {
392  //CRLF immediately followed by LWSP as equivalent to the LWSP character
393  if(length >= 2)
394  {
395  if(buffer[length - 2] == '\r' || buffer[length - 1] == '\n')
396  {
397  //Remove trailing CRLF sequence
398  length -= 2;
399  //Properly terminate the string with a NULL character
400  buffer[length] = '\0';
401  }
402  }
403  }
404 
405  //A header field may span multiple lines...
406  } while(*firstChar == ' ' || *firstChar == '\t');
407 
408  //Return status code
409  return error;
410 }
411 
412 
413 /**
414  * @brief Parse HTTP header field
415  * @param[in] connection Structure representing an HTTP connection
416  * @param[in] name Name of the header field
417  * @param[in] value Value of the header field
418  **/
419 
421  const char_t *name, char_t *value)
422 {
423  //Host header field?
424  if(osStrcasecmp(name, "Host") == 0)
425  {
426  //Save host name
427  strSafeCopy(connection->request.host, value,
429  }
430  //Connection header field?
431  else if(osStrcasecmp(name, "Connection") == 0)
432  {
433  //Parse Connection header field
434  httpParseConnectionField(connection, value);
435  }
436  //Transfer-Encoding header field?
437  else if(osStrcasecmp(name, "Transfer-Encoding") == 0)
438  {
439  //Check whether chunked encoding is used
440  if(osStrcasecmp(value, "chunked") == 0)
441  connection->request.chunkedEncoding = TRUE;
442  }
443  //Content-Type field header?
444  else if(osStrcasecmp(name, "Content-Type") == 0)
445  {
446  //Parse Content-Type header field
447  httpParseContentTypeField(connection, value);
448  }
449  //Content-Length header field?
450  else if(osStrcasecmp(name, "Content-Length") == 0)
451  {
452  //Get the length of the body data
453  connection->request.contentLength = atoi(value);
454  }
455  //Accept-Encoding field header?
456  else if(osStrcasecmp(name, "Accept-Encoding") == 0)
457  {
458  //Parse Content-Type header field
459  httpParseAcceptEncodingField(connection, value);
460  }
461  //Authorization header field?
462  else if(osStrcasecmp(name, "Authorization") == 0)
463  {
464  //Parse Authorization header field
465  httpParseAuthorizationField(connection, value);
466  }
467 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
468  //Upgrade header field?
469  else if(osStrcasecmp(name, "Upgrade") == 0)
470  {
471  //WebSocket support?
472  if(osStrcasecmp(value, "websocket") == 0)
473  connection->request.upgradeWebSocket = TRUE;
474  }
475  //Sec-WebSocket-Key header field?
476  else if(osStrcasecmp(name, "Sec-WebSocket-Key") == 0)
477  {
478  //Save the contents of the Sec-WebSocket-Key header field
479  strSafeCopy(connection->request.clientKey, value,
481  }
482 #endif
483 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
484  //Cookie header field?
485  else if(osStrcasecmp(name, "Cookie") == 0)
486  {
487  //Parse Cookie header field
488  httpParseCookieField(connection, value);
489  }
490 #endif
491 
492 #if defined(HTTP_PARSE_REQUEST_HEADER_FIELD_HOOK)
493  //Parse custom header fields
494  HTTP_PARSE_REQUEST_HEADER_FIELD_HOOK(connection, name, value);
495 #endif
496 }
497 
498 
499 /**
500  * @brief Parse Connection header field
501  * @param[in] connection Structure representing an HTTP connection
502  * @param[in] value Connection field value
503  **/
504 
506  char_t *value)
507 {
508  char_t *p;
509  char_t *token;
510 
511  //Get the first value of the list
512  token = osStrtok_r(value, ",", &p);
513 
514  //Parse the comma-separated list
515  while(token != NULL)
516  {
517  //Trim whitespace characters
519 
520  //Check current value
521  if(osStrcasecmp(value, "keep-alive") == 0)
522  {
523  //The connection is persistent
524  connection->request.keepAlive = TRUE;
525  }
526  else if(osStrcasecmp(value, "close") == 0)
527  {
528  //The connection will be closed after completion of the response
529  connection->request.keepAlive = FALSE;
530  }
531 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
532  else if(osStrcasecmp(value, "upgrade") == 0)
533  {
534  //Upgrade the connection
535  connection->request.connectionUpgrade = TRUE;
536  }
537 #endif
538 
539  //Get next value
540  token = osStrtok_r(NULL, ",", &p);
541  }
542 }
543 
544 
545 /**
546  * @brief Parse Content-Type header field
547  * @param[in] connection Structure representing an HTTP connection
548  * @param[in] value Content-Type field value
549  **/
550 
552  char_t *value)
553 {
554 #if (HTTP_SERVER_MULTIPART_TYPE_SUPPORT == ENABLED)
555  size_t n;
556  char_t *p;
557  char_t *token;
558 #endif
559 
560 #if (HTTP_SERVER_CONTENT_TYPE_MAX_LEN > 0)
561  //Save the contents of the Content-Type header field
562  strSafeCopy(connection->request.contentType, value,
564 #endif
565 
566 #if (HTTP_SERVER_MULTIPART_TYPE_SUPPORT == ENABLED)
567  //Retrieve type
568  token = osStrtok_r(value, "/", &p);
569  //Any parsing error?
570  if(token == NULL)
571  return;
572 
573  //The boundary parameter makes sense only for the multipart content-type
574  if(osStrcasecmp(token, "multipart") == 0)
575  {
576  //Skip subtype
577  token = osStrtok_r(NULL, ";", &p);
578  //Any parsing error?
579  if(token == NULL)
580  return;
581 
582  //Retrieve parameter name
583  token = osStrtok_r(NULL, "=", &p);
584  //Any parsing error?
585  if(token == NULL)
586  return;
587 
588  //Trim whitespace characters
590 
591  //Check parameter name
592  if(osStrcasecmp(token, "boundary") == 0)
593  {
594  //Retrieve parameter value
595  token = osStrtok_r(NULL, ";", &p);
596  //Any parsing error?
597  if(token == NULL)
598  return;
599 
600  //Trim whitespace characters
602  //Get the length of the boundary string
603  n = osStrlen(token);
604 
605  //Check the length of the boundary string
607  {
608  //Copy the boundary string
609  osStrncpy(connection->request.boundary, token, n);
610  //Properly terminate the string
611  connection->request.boundary[n] = '\0';
612 
613  //Save the length of the boundary string
614  connection->request.boundaryLength = n;
615  }
616  }
617  }
618 #endif
619 }
620 
621 
622 /**
623  * @brief Parse Accept-Encoding header field
624  * @param[in] connection Structure representing an HTTP connection
625  * @param[in] value Accept-Encoding field value
626  **/
627 
629  char_t *value)
630 {
631 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
632  char_t *p;
633  char_t *token;
634 
635  //Get the first value of the list
636  token = osStrtok_r(value, ",", &p);
637 
638  //Parse the comma-separated list
639  while(token != NULL)
640  {
641  //Trim whitespace characters
643 
644  //Check current value
645  if(osStrcasecmp(value, "gzip") == 0)
646  {
647  //gzip compression is supported
648  connection->request.acceptGzipEncoding = TRUE;
649  }
650 
651  //Get next value
652  token = osStrtok_r(NULL, ",", &p);
653  }
654 #endif
655 }
656 
657 
658 /**
659  * @brief Parse Cookie header field
660  * @param[in] connection Structure representing an HTTP connection
661  * @param[in] value Accept-Encoding field value
662  **/
663 
665 {
666 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
667  //Save the value of the header field
668  strSafeCopy(connection->request.cookie, value,
670 #endif
671 }
672 
673 
674 /**
675  * @brief Read chunk-size field from the input stream
676  * @param[in] connection Structure representing an HTTP connection
677  **/
678 
680 {
681  error_t error;
682  size_t n;
683  char_t *end;
684  char_t s[8];
685 
686  //First chunk to be received?
687  if(connection->request.firstChunk)
688  {
689  //Clear the flag
690  connection->request.firstChunk = FALSE;
691  }
692  else
693  {
694  //Read the CRLF that follows the previous chunk-data field
695  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
696  //Any error to report?
697  if(error)
698  return error;
699 
700  //Properly terminate the string with a NULL character
701  s[n] = '\0';
702 
703  //The chunk data must be terminated by CRLF
704  if(osStrcmp(s, "\r\n") != 0)
705  return ERROR_WRONG_ENCODING;
706  }
707 
708  //Read the chunk-size field
709  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
710  //Any error to report?
711  if(error)
712  return error;
713 
714  //Properly terminate the string with a NULL character
715  s[n] = '\0';
716  //Remove extra whitespaces
718 
719  //Retrieve the size of the chunk
720  connection->request.byteCount = osStrtoul(s, &end, 16);
721 
722  //No valid conversion could be performed?
723  if(end == s || *end != '\0')
724  return ERROR_WRONG_ENCODING;
725 
726  //Any chunk whose size is zero terminates the data transfer
727  if(!connection->request.byteCount)
728  {
729  //The end of the HTTP request body has been reached
730  connection->request.lastChunk = TRUE;
731 
732  //Skip the trailer
733  while(1)
734  {
735  //Read a complete line
736  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
737  //Unable to read any data?
738  if(error)
739  return error;
740 
741  //Properly terminate the string with a NULL character
742  s[n] = '\0';
743 
744  //The trailer is terminated by an empty line
745  if(osStrcmp(s, "\r\n") == 0)
746  break;
747  }
748  }
749 
750  //Successful processing
751  return NO_ERROR;
752 }
753 
754 
755 /**
756  * @brief Initialize response header
757  * @param[in] connection Structure representing an HTTP connection
758  **/
759 
761 {
762  //Default HTTP header fields
763  connection->response.version = connection->request.version;
764  connection->response.statusCode = 200;
765  connection->response.noCache = FALSE;
766  connection->response.maxAge = 0;
767  connection->response.location = NULL;
768  connection->response.contentType = mimeGetType(connection->request.uri);
769  connection->response.chunkedEncoding = TRUE;
770 
771 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
772  //Do not use gzip encoding
773  connection->response.gzipEncoding = FALSE;
774 #endif
775 
776 #if (HTTP_SERVER_PERSISTENT_CONN_SUPPORT == ENABLED)
777  //Persistent connections are accepted
778  connection->response.keepAlive = connection->request.keepAlive;
779 #else
780  //Connections are not persistent by default
781  connection->response.keepAlive = FALSE;
782 #endif
783 }
784 
785 
786 /**
787  * @brief Format HTTP response header
788  * @param[in] connection Structure representing an HTTP connection
789  * @param[out] buffer Pointer to the buffer where to format the HTTP header
790  * @return Error code
791  **/
792 
794 {
795  uint_t i;
796  char_t *p;
797 
798  //HTTP version 0.9?
799  if(connection->response.version == HTTP_VERSION_0_9)
800  {
801  //Enforce default parameters
802  connection->response.keepAlive = FALSE;
803  connection->response.chunkedEncoding = FALSE;
804  //The size of the response body is not limited
805  connection->response.byteCount = UINT_MAX;
806  //We are done since HTTP 0.9 does not support Full-Response format
807  return NO_ERROR;
808  }
809 
810  //When generating dynamic web pages with HTTP 1.0, the only way to
811  //signal the end of the body is to close the connection
812  if(connection->response.version == HTTP_VERSION_1_0 &&
813  connection->response.chunkedEncoding)
814  {
815  //Make the connection non persistent
816  connection->response.keepAlive = FALSE;
817  connection->response.chunkedEncoding = FALSE;
818  //The size of the response body is not limited
819  connection->response.byteCount = UINT_MAX;
820  }
821  else
822  {
823  //Limit the size of the response body
824  connection->response.byteCount = connection->response.contentLength;
825  }
826 
827  //Point to the beginning of the buffer
828  p = buffer;
829 
830  //The first line of a response message is the Status-Line, consisting
831  //of the protocol version followed by a numeric status code and its
832  //associated textual phrase
833  p += osSprintf(p, "HTTP/%u.%u %u ", MSB(connection->response.version),
834  LSB(connection->response.version), connection->response.statusCode);
835 
836  //Retrieve the Reason-Phrase that corresponds to the Status-Code
837  for(i = 0; i < arraysize(statusCodeList); i++)
838  {
839  //Check the status code
840  if(statusCodeList[i].value == connection->response.statusCode)
841  {
842  //Append the textual phrase to the Status-Line
843  p += osSprintf(p, "%s", statusCodeList[i].message);
844  //Break the loop and continue processing
845  break;
846  }
847  }
848 
849  //Properly terminate the Status-Line
850  p += osSprintf(p, "\r\n");
851 
852  //Valid location?
853  if(connection->response.location != NULL)
854  {
855  //Set Location field
856  p += osSprintf(p, "Location: %s\r\n", connection->response.location);
857  }
858 
859  //Persistent connection?
860  if(connection->response.keepAlive)
861  {
862  //Set Connection field
863  p += osSprintf(p, "Connection: keep-alive\r\n");
864 
865  //Set Keep-Alive field
866  p += osSprintf(p, "Keep-Alive: timeout=%u, max=%u\r\n",
868  }
869  else
870  {
871  //Set Connection field
872  p += osSprintf(p, "Connection: close\r\n");
873  }
874 
875  //Specify the caching policy
876  if(connection->response.noCache)
877  {
878  //Set Pragma field
879  p += osSprintf(p, "Pragma: no-cache\r\n");
880  //Set Cache-Control field
881  p += osSprintf(p, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
882  p += osSprintf(p, "Cache-Control: max-age=0, post-check=0, pre-check=0\r\n");
883  }
884  else if(connection->response.maxAge != 0)
885  {
886  //Set Cache-Control field
887  p += osSprintf(p, "Cache-Control: max-age=%u\r\n", connection->response.maxAge);
888  }
889 
890 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED && HTTP_SERVER_HSTS_SUPPORT == ENABLED)
891  //TLS-secured connection?
892  if(connection->serverContext->settings.tlsInitCallback != NULL)
893  {
894  //Set Strict-Transport-Security field
895  p += osSprintf(p, "Strict-Transport-Security: max-age=31536000\r\n");
896  }
897 #endif
898 
899 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
900  //Check whether authentication is required
901  if(connection->response.auth.mode != HTTP_AUTH_MODE_NONE)
902  {
903  //Add WWW-Authenticate header field
904  p += httpAddAuthenticateField(connection, p);
905  }
906 #endif
907 
908 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
909  //Valid cookie
910  if(connection->response.setCookie[0] != '\0')
911  {
912  //Add Set-Cookie header field
913  p += osSprintf(p, "Set-Cookie: %s\r\n", connection->response.setCookie);
914  }
915 #endif
916 
917  //Valid content type?
918  if(connection->response.contentType != NULL)
919  {
920  //Content type
921  p += osSprintf(p, "Content-Type: %s\r\n", connection->response.contentType);
922  }
923 
924 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
925  //Use gzip encoding?
926  if(connection->response.gzipEncoding)
927  {
928  //Set Transfer-Encoding field
929  p += osSprintf(p, "Content-Encoding: gzip\r\n");
930  }
931 #endif
932 
933  //Use chunked encoding transfer?
934  if(connection->response.chunkedEncoding)
935  {
936  //Set Transfer-Encoding field
937  p += osSprintf(p, "Transfer-Encoding: chunked\r\n");
938  }
939  //Persistent connection?
940  else if(connection->response.keepAlive)
941  {
942  //Set Content-Length field
943  p += osSprintf(p, "Content-Length: %" PRIuSIZE "\r\n", connection->response.contentLength);
944  }
945 
946 #if defined(HTTP_FORMAT_RESPONSE_HEADER_HOOK)
947  //Format custom header fields
948  HTTP_FORMAT_RESPONSE_HEADER_HOOK(connection, p);
949 #endif
950 
951  //Terminate the header with an empty line
952  osStrcat(p, "\r\n");
953 
954  //Successful processing
955  return NO_ERROR;
956 }
957 
958 
959 /**
960  * @brief Send data to the client
961  * @param[in] connection Structure representing an HTTP connection
962  * @param[in] data Pointer to a buffer containing the data to be transmitted
963  * @param[in] length Number of bytes to be transmitted
964  * @param[in] flags Set of flags that influences the behavior of this function
965  **/
966 
968  const void *data, size_t length, uint_t flags)
969 {
970 #if (NET_RTOS_SUPPORT == ENABLED)
971  error_t error;
972 
973 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
974  //Check whether a secure connection is being used
975  if(connection->tlsContext != NULL)
976  {
977  //Use TLS to transmit data to the client
978  error = tlsWrite(connection->tlsContext, data, length, NULL, flags);
979  }
980  else
981 #endif
982  {
983  //Transmit data to the client
984  error = socketSend(connection->socket, data, length, NULL, flags);
985  }
986 
987  //Return status code
988  return error;
989 #else
990  //Prevent buffer overflow
991  if((connection->bufferLen + length) > HTTP_SERVER_BUFFER_SIZE)
992  return ERROR_BUFFER_OVERFLOW;
993 
994  //Copy user data
995  osMemcpy(connection->buffer + connection->bufferLen, data, length);
996  //Adjust the length of the buffer
997  connection->bufferLen += length;
998 
999  //Successful processing
1000  return NO_ERROR;
1001 #endif
1002 }
1003 
1004 
1005 /**
1006  * @brief Receive data from the client
1007  * @param[in] connection Structure representing an HTTP connection
1008  * @param[out] data Buffer into which received data will be placed
1009  * @param[in] size Maximum number of bytes that can be received
1010  * @param[out] received Actual number of bytes that have been received
1011  * @param[in] flags Set of flags that influences the behavior of this function
1012  * @return Error code
1013  **/
1014 
1016  void *data, size_t size, size_t *received, uint_t flags)
1017 {
1018 #if (NET_RTOS_SUPPORT == ENABLED)
1019  error_t error;
1020 
1021 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
1022  //Check whether a secure connection is being used
1023  if(connection->tlsContext != NULL)
1024  {
1025  //Use TLS to receive data from the client
1026  error = tlsRead(connection->tlsContext, data, size, received, flags);
1027  }
1028  else
1029 #endif
1030  {
1031  //Receive data from the client
1032  error = socketReceive(connection->socket, data, size, received, flags);
1033  }
1034 
1035  //Return status code
1036  return error;
1037 #else
1038  error_t error;
1039  char_t c;
1040  size_t i;
1041  size_t n;
1042 
1043  //Number of data bytes that are pending in the receive buffer
1044  n = connection->bufferLen - connection->bufferPos;
1045 
1046  //Any data to be copied?
1047  if(n > 0)
1048  {
1049  //Limit the number of bytes to read at a time
1050  n = MIN(n, size);
1051 
1052  //The HTTP_FLAG_BREAK_CHAR flag causes the function to stop reading
1053  //data as soon as the specified break character is encountered
1054  if((flags & HTTP_FLAG_BREAK_CHAR) != 0)
1055  {
1056  //Retrieve the break character code
1057  c = LSB(flags);
1058 
1059  //Search for the specified break character
1060  for(i = 0; i < n && connection->buffer[connection->bufferPos + i] != c; i++)
1061  {
1062  }
1063 
1064  //Adjust the number of data to read
1065  n = MIN(n, i + 1);
1066  }
1067 
1068  //Copy data to user buffer
1069  osMemcpy(data, connection->buffer + connection->bufferPos, n);
1070 
1071  //Advance current position
1072  connection->bufferPos += n;
1073  //Total number of data that have been read
1074  *received = n;
1075 
1076  //Successful processing
1077  error = NO_ERROR;
1078  }
1079  else
1080  {
1081  //No more data available...
1082  error = ERROR_END_OF_STREAM;
1083  }
1084 
1085  //Return status code
1086  return error;
1087 #endif
1088 }
1089 
1090 
1091 /**
1092  * @brief Retrieve the full pathname to the specified resource
1093  * @param[in] connection Structure representing an HTTP connection
1094  * @param[in] relative String containing the relative path to the resource
1095  * @param[out] absolute Resulting string containing the absolute path
1096  * @param[in] maxLen Maximum acceptable path length
1097  **/
1098 
1100  const char_t *relative, char_t *absolute, size_t maxLen)
1101 {
1102  //Copy the root directory
1103  osStrcpy(absolute, connection->settings->rootDirectory);
1104 
1105  //Append the specified path
1106  pathCombine(absolute, relative, maxLen);
1107 
1108  //Clean the resulting path
1109  pathCanonicalize(absolute);
1110 }
1111 
1112 
1113 /**
1114  * @brief Compare filename extension
1115  * @param[in] filename Filename whose extension is to be checked
1116  * @param[in] extension String defining the extension to be checked
1117  * @return TRUE is the filename matches the given extension, else FALSE
1118  **/
1119 
1120 bool_t httpCompExtension(const char_t *filename, const char_t *extension)
1121 {
1122  uint_t n;
1123  uint_t m;
1124 
1125  //Get the length of the specified filename
1126  n = osStrlen(filename);
1127  //Get the length of the extension
1128  m = osStrlen(extension);
1129 
1130  //Check the length of the filename
1131  if(n < m)
1132  return FALSE;
1133 
1134  //Compare extensions
1135  if(osStrncasecmp(filename + n - m, extension, m) == 0)
1136  {
1137  return TRUE;
1138  }
1139  else
1140  {
1141  return FALSE;
1142  }
1143 }
1144 
1145 
1146 /**
1147  * @brief Decode a percent-encoded string
1148  * @param[in] input NULL-terminated string to be decoded
1149  * @param[out] output NULL-terminated string resulting from the decoding process
1150  * @param[in] outputSize Size of the output buffer in bytes
1151  * @return Error code
1152  **/
1153 
1155  char_t *output, size_t outputSize)
1156 {
1157  size_t i;
1158  char_t buffer[3];
1159 
1160  //Check parameters
1161  if(input == NULL || output == NULL)
1162  return ERROR_INVALID_PARAMETER;
1163 
1164  //Decode the percent-encoded string
1165  for(i = 0; *input != '\0' && i < outputSize; i++)
1166  {
1167  //Check current character
1168  if(*input == '+')
1169  {
1170  //Replace '+' characters with spaces
1171  output[i] = ' ';
1172  //Advance data pointer
1173  input++;
1174  }
1175  else if(input[0] == '%' && input[1] != '\0' && input[2] != '\0')
1176  {
1177  //Process percent-encoded characters
1178  buffer[0] = input[1];
1179  buffer[1] = input[2];
1180  buffer[2] = '\0';
1181  //String to integer conversion
1182  output[i] = (uint8_t) osStrtoul(buffer, NULL, 16);
1183  //Advance data pointer
1184  input += 3;
1185  }
1186  else
1187  {
1188  //Copy any other characters
1189  output[i] = *input;
1190  //Advance data pointer
1191  input++;
1192  }
1193  }
1194 
1195  //Check whether the output buffer runs out of space
1196  if(i >= outputSize)
1197  return ERROR_FAILURE;
1198 
1199  //Properly terminate the resulting string
1200  output[i] = '\0';
1201  //Successful processing
1202  return NO_ERROR;
1203 }
1204 
1205 
1206 /**
1207  * @brief Convert byte array to hex string
1208  * @param[in] input Point to the byte array
1209  * @param[in] inputLen Length of the byte array
1210  * @param[out] output NULL-terminated string resulting from the conversion
1211  **/
1212 
1213 void httpConvertArrayToHexString(const uint8_t *input,
1214  size_t inputLen, char_t *output)
1215 {
1216  size_t i;
1217 
1218  //Hex conversion table
1219  static const char_t hexDigit[16] =
1220  {
1221  '0', '1', '2', '3', '4', '5', '6', '7',
1222  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
1223  };
1224 
1225  //Process byte array
1226  for(i = 0; i < inputLen; i++)
1227  {
1228  //Convert upper nibble
1229  output[i * 2] = hexDigit[(input[i] >> 4) & 0x0F];
1230  //Then convert lower nibble
1231  output[i * 2 + 1] = hexDigit[input[i] & 0x0F];
1232  }
1233 
1234  //Properly terminate the string with a NULL character
1235  output[i * 2] = '\0';
1236 }
1237 
1238 #endif
error_t socketSend(Socket *socket, const void *data, size_t length, size_t *written, uint_t flags)
Send data to a connected socket.
Definition: socket.c:1514
Path manipulation helper functions.
#define osStrchr(s, c)
Definition: os_port.h:198
String manipulation helper functions.
error_t httpFormatResponseHeader(HttpConnection *connection, char_t *buffer)
Format HTTP response header.
int bool_t
Definition: compiler_port.h:63
@ SOCKET_FLAG_WAIT_ALL
Definition: socket.h:138
#define HTTP_SERVER_COOKIE_MAX_LEN
Definition: http_server.h:286
HTTP server (HyperText Transfer Protocol)
#define WEB_SOCKET_CLIENT_KEY_SIZE
Definition: web_socket.h:171
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:143
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 HTTP_SERVER_CONTENT_TYPE_MAX_LEN
Definition: http_server.h:230
void strRemoveTrailingSpace(char_t *s)
Removes all trailing whitespace from a string.
Definition: str.c:119
#define HTTP_SERVER_TIMEOUT
Definition: http_server.h:145
uint8_t message[]
Definition: chap.h:154
#define TRUE
Definition: os_port.h:50
uint8_t data[]
Definition: ethernet.h:224
#define HTTP_SERVER_HOST_MAX_LEN
Definition: http_server.h:216
@ HTTP_VERSION_0_9
Definition: http_common.h:61
char_t name[]
#define osStrcmp(s1, s2)
Definition: os_port.h:174
#define osStrlen(s)
Definition: os_port.h:168
void httpParseConnectionField(HttpConnection *connection, char_t *value)
Parse Connection header field.
#define HTTP_SERVER_URI_MAX_LEN
Definition: http_server.h:202
@ ERROR_END_OF_STREAM
Definition: error.h:211
error_t httpDecodePercentEncodedString(const char_t *input, char_t *output, size_t outputSize)
Decode a percent-encoded string.
#define HTTP_SERVER_MAX_REQUESTS
Definition: http_server.h:167
void pathCanonicalize(char_t *path)
Simplify a path.
Definition: path.c:162
@ HTTP_VERSION_1_0
Definition: http_common.h:62
#define FALSE
Definition: os_port.h:46
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
void httpParseContentTypeField(HttpConnection *connection, char_t *value)
Parse Content-Type header field.
#define osStrncasecmp(s1, s2, length)
Definition: os_port.h:192
#define osMemcpy(dest, src, length)
Definition: os_port.h:144
@ HTTP_FLAG_BREAK_CHAR
Definition: http_common.h:98
error_t
Error codes.
Definition: error.h:43
#define osSprintf(dest,...)
Definition: os_port.h:234
error_t socketReceive(Socket *socket, void *data, size_t size, size_t *received, uint_t flags)
Receive data from a connected socket.
Definition: socket.c:1724
#define HttpConnection
Definition: http_server.h:349
#define osStrtok_r(s, delim, last)
Definition: os_port.h:228
HTTP status code.
Definition: http_server.h:449
@ ERROR_FAILURE
Generic error code.
Definition: error.h:45
void httpParseAuthorizationField(HttpConnection *connection, char_t *value)
Parse Authorization header field.
error_t httpReceive(HttpConnection *connection, void *data, size_t size, size_t *received, uint_t flags)
Receive data from the client.
const char_t * mimeGetType(const char_t *filename)
Get the MIME type from a given extension.
Definition: mime.c:113
#define HTTP_SERVER_IDLE_TIMEOUT
Definition: http_server.h:153
HTTP server (miscellaneous functions)
#define osStrcasecmp(s1, s2)
Definition: os_port.h:186
char_t filename[]
Definition: tftp_common.h:93
void httpParseCookieField(HttpConnection *connection, char_t *value)
Parse Cookie header field.
#define MSB(x)
Definition: os_port.h:59
#define TRACE_INFO(...)
Definition: debug.h:105
uint8_t length
Definition: tcp.h:375
#define LSB(x)
Definition: os_port.h:55
error_t tlsRead(TlsContext *context, void *data, size_t size, size_t *received, uint_t flags)
Receive application data from a the remote host using TLS.
Definition: tls.c:2280
error_t httpSend(HttpConnection *connection, const void *data, size_t length, uint_t flags)
Send data to the client.
#define MIN(a, b)
Definition: os_port.h:63
size_t httpAddAuthenticateField(HttpConnection *connection, char_t *output)
Format WWW-Authenticate header field.
#define osStrtoul(s, endptr, base)
Definition: os_port.h:258
#define TRACE_DEBUG(...)
Definition: debug.h:119
HTTP authentication.
char char_t
Definition: compiler_port.h:55
#define osStrcat(s1, s2)
Definition: os_port.h:222
error_t strSafeCopy(char_t *dest, const char_t *src, size_t destSize)
Copy string.
Definition: str.c:172
uint8_t m
Definition: ndp.h:304
uint8_t n
@ HTTP_VERSION_1_1
Definition: http_common.h:63
#define HTTP_SERVER_QUERY_STRING_MAX_LEN
Definition: http_server.h:209
void httpGetAbsolutePath(HttpConnection *connection, const char_t *relative, char_t *absolute, size_t maxLen)
Retrieve the full pathname to the specified resource.
void httpConvertArrayToHexString(const uint8_t *input, size_t inputLen, char_t *output)
Convert byte array to hex string.
#define HTTP_SERVER_METHOD_MAX_LEN
Definition: http_server.h:195
#define osStrncpy(s1, s2, length)
Definition: os_port.h:216
uint8_t value[]
Definition: tcp.h:376
bool_t httpCompExtension(const char_t *filename, const char_t *extension)
Compare filename extension.
error_t tlsWrite(TlsContext *context, const void *data, size_t length, size_t *written, uint_t flags)
Send application data to the remote host using TLS.
Definition: tls.c:2139
error_t httpReadRequestHeader(HttpConnection *connection)
Read HTTP request header and parse its contents.
uint8_t s
Definition: igmp_common.h:234
@ ERROR_WRONG_ENCODING
Definition: error.h:122
#define HTTP_SERVER_BUFFER_SIZE
Definition: http_server.h:174
void httpParseHeaderField(HttpConnection *connection, const char_t *name, char_t *value)
Parse HTTP header field.
MIME (Multipurpose Internet Mail Extensions)
error_t httpReadHeaderField(HttpConnection *connection, char_t *buffer, size_t size, char_t *firstChar)
Read multiple-line header field.
uint8_t flags
Definition: tcp.h:358
error_t httpReadChunkSize(HttpConnection *connection)
Read chunk-size field from the input stream.
error_t httpParseRequestLine(HttpConnection *connection, char_t *requestLine)
Parse Request-Line.
#define PRIuSIZE
unsigned int uint_t
Definition: compiler_port.h:57
TCP/IP stack core.
@ HTTP_AUTH_MODE_NONE
Definition: http_common.h:73
#define osStrcpy(s1, s2)
Definition: os_port.h:210
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:169
@ SOCKET_FLAG_BREAK_CRLF
Definition: socket.h:141
void httpParseAcceptEncodingField(HttpConnection *connection, char_t *value)
Parse Accept-Encoding header field.
@ ERROR_INVALID_REQUEST
Definition: error.h:65
#define HTTP_SERVER_BOUNDARY_MAX_LEN
Definition: http_server.h:279
@ NO_ERROR
Success.
Definition: error.h:44
uint8_t c
Definition: ndp.h:514
Debugging facilities.
uint8_t token[]
Definition: coap_common.h:181
#define arraysize(a)
Definition: os_port.h:71
void pathCombine(char_t *path, const char_t *more, size_t maxLen)
Concatenate two paths.
Definition: path.c:414
void httpInitResponseHeader(HttpConnection *connection)
Initialize response header.