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-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_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, HTTP_SERVER_METHOD_MAX_LEN);
216  //Any error to report?
217  if(error)
218  return ERROR_INVALID_REQUEST;
219 
220  //The Request-URI is following the method token
221  token = osStrtok_r(NULL, " \r\n", &p);
222  //Unable to retrieve the Request-URI?
223  if(token == NULL)
224  return ERROR_INVALID_REQUEST;
225 
226  //Check whether a query string is present
227  s = osStrchr(token, '?');
228 
229  //Query string found?
230  if(s != NULL)
231  {
232  //Split the string
233  *s = '\0';
234 
235  //Save the Request-URI
237  connection->request.uri, HTTP_SERVER_URI_MAX_LEN);
238  //Any error to report?
239  if(error)
240  return ERROR_INVALID_REQUEST;
241 
242  //Check the length of the query string
244  return ERROR_INVALID_REQUEST;
245 
246  //Save the query string
247  osStrcpy(connection->request.queryString, s + 1);
248  }
249  else
250  {
251  //Save the Request-URI
253  connection->request.uri, HTTP_SERVER_URI_MAX_LEN);
254  //Any error to report?
255  if(error)
256  return ERROR_INVALID_REQUEST;
257 
258  //No query string
259  connection->request.queryString[0] = '\0';
260  }
261 
262  //Redirect to the default home page if necessary
263  if(osStrcasecmp(connection->request.uri, "/") == 0)
264  osStrcpy(connection->request.uri, connection->settings->defaultDocument);
265 
266  //Clean the resulting path
267  pathCanonicalize(connection->request.uri);
268 
269  //The protocol version is following the Request-URI
270  token = osStrtok_r(NULL, " \r\n", &p);
271 
272  //HTTP version 0.9?
273  if(token == NULL)
274  {
275  //Save version number
276  connection->request.version = HTTP_VERSION_0_9;
277  //Persistent connections are not supported
278  connection->request.keepAlive = FALSE;
279  }
280  //HTTP version 1.0?
281  else if(osStrcasecmp(token, "HTTP/1.0") == 0)
282  {
283  //Save version number
284  connection->request.version = HTTP_VERSION_1_0;
285  //By default connections are not persistent
286  connection->request.keepAlive = FALSE;
287  }
288  //HTTP version 1.1?
289  else if(osStrcasecmp(token, "HTTP/1.1") == 0)
290  {
291  //Save version number
292  connection->request.version = HTTP_VERSION_1_1;
293  //HTTP 1.1 makes persistent connections the default
294  connection->request.keepAlive = TRUE;
295  }
296  //HTTP version not supported?
297  else
298  {
299  //Report an error
300  return ERROR_INVALID_REQUEST;
301  }
302 
303  //Successful processing
304  return NO_ERROR;
305 }
306 
307 
308 /**
309  * @brief Read multiple-line header field
310  * @param[in] connection Structure representing an HTTP connection
311  * @param[out] buffer Buffer where to store the header field
312  * @param[in] size Size of the buffer, in bytes
313  * @param[in,out] firstChar Leading character of the header line
314  * @return Error code
315  **/
316 
318  char_t *buffer, size_t size, char_t *firstChar)
319 {
320  error_t error;
321  size_t n;
322  size_t length;
323 
324  //This is the actual length of the header field
325  length = 0;
326 
327  //The process of moving from a multiple-line representation of a header
328  //field to its single line representation is called unfolding
329  do
330  {
331  //Check the length of the header field
332  if((length + 1) >= size)
333  {
334  //Report an error
335  error = ERROR_INVALID_REQUEST;
336  //Exit immediately
337  break;
338  }
339 
340  //NULL character found?
341  if(*firstChar == '\0')
342  {
343  //Prepare to decode the first header field
344  length = 0;
345  }
346  //LWSP character found?
347  else if(*firstChar == ' ' || *firstChar == '\t')
348  {
349  //Unfolding is accomplished by regarding CRLF immediately
350  //followed by a LWSP as equivalent to the LWSP character
351  buffer[length] = *firstChar;
352  //The current header field spans multiple lines
353  length++;
354  }
355  //Any other character?
356  else
357  {
358  //Restore the very first character of the header field
359  buffer[0] = *firstChar;
360  //Prepare to decode a new header field
361  length = 1;
362  }
363 
364  //Read data until a CLRF character is encountered
365  error = httpReceive(connection, buffer + length,
366  size - 1 - length, &n, SOCKET_FLAG_BREAK_CRLF);
367  //Any error to report?
368  if(error)
369  break;
370 
371  //Update the length of the header field
372  length += n;
373  //Properly terminate the string with a NULL character
374  buffer[length] = '\0';
375 
376  //An empty line indicates the end of the header fields
377  if(osStrcmp(buffer, "\r\n") == 0)
378  break;
379 
380  //Read the next character to detect if the CRLF is immediately
381  //followed by a LWSP character
382  error = httpReceive(connection, firstChar,
383  sizeof(char_t), &n, SOCKET_FLAG_WAIT_ALL);
384  //Any error to report?
385  if(error)
386  break;
387 
388  //LWSP character found?
389  if(*firstChar == ' ' || *firstChar == '\t')
390  {
391  //CRLF immediately followed by LWSP as equivalent to the LWSP character
392  if(length >= 2)
393  {
394  if(buffer[length - 2] == '\r' || buffer[length - 1] == '\n')
395  {
396  //Remove trailing CRLF sequence
397  length -= 2;
398  //Properly terminate the string with a NULL character
399  buffer[length] = '\0';
400  }
401  }
402  }
403 
404  //A header field may span multiple lines...
405  } while(*firstChar == ' ' || *firstChar == '\t');
406 
407  //Return status code
408  return error;
409 }
410 
411 
412 /**
413  * @brief Parse HTTP header field
414  * @param[in] connection Structure representing an HTTP connection
415  * @param[in] name Name of the header field
416  * @param[in] value Value of the header field
417  **/
418 
420  const char_t *name, char_t *value)
421 {
422  //Host header field?
423  if(osStrcasecmp(name, "Host") == 0)
424  {
425  //Save host name
426  strSafeCopy(connection->request.host, value,
428  }
429  //Connection header field?
430  else if(osStrcasecmp(name, "Connection") == 0)
431  {
432  //Parse Connection header field
433  httpParseConnectionField(connection, value);
434  }
435  //Transfer-Encoding header field?
436  else if(osStrcasecmp(name, "Transfer-Encoding") == 0)
437  {
438  //Check whether chunked encoding is used
439  if(osStrcasecmp(value, "chunked") == 0)
440  connection->request.chunkedEncoding = TRUE;
441  }
442  //Content-Type field header?
443  else if(osStrcasecmp(name, "Content-Type") == 0)
444  {
445  //Parse Content-Type header field
446  httpParseContentTypeField(connection, value);
447  }
448  //Content-Length header field?
449  else if(osStrcasecmp(name, "Content-Length") == 0)
450  {
451  //Get the length of the body data
452  connection->request.contentLength = atoi(value);
453  }
454  //Accept-Encoding field header?
455  else if(osStrcasecmp(name, "Accept-Encoding") == 0)
456  {
457  //Parse Content-Type header field
458  httpParseAcceptEncodingField(connection, value);
459  }
460  //Authorization header field?
461  else if(osStrcasecmp(name, "Authorization") == 0)
462  {
463  //Parse Authorization header field
464  httpParseAuthorizationField(connection, value);
465  }
466 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
467  //Upgrade header field?
468  else if(osStrcasecmp(name, "Upgrade") == 0)
469  {
470  //WebSocket support?
471  if(osStrcasecmp(value, "websocket") == 0)
472  connection->request.upgradeWebSocket = TRUE;
473  }
474  //Sec-WebSocket-Key header field?
475  else if(osStrcasecmp(name, "Sec-WebSocket-Key") == 0)
476  {
477  //Save the contents of the Sec-WebSocket-Key header field
478  strSafeCopy(connection->request.clientKey, value,
480  }
481 #endif
482 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
483  //Cookie header field?
484  else if(osStrcasecmp(name, "Cookie") == 0)
485  {
486  //Parse Cookie header field
487  httpParseCookieField(connection, value);
488  }
489 #endif
490 
491 #if defined(HTTP_PARSE_REQUEST_HEADER_FIELD_HOOK)
492  //Parse custom header fields
493  HTTP_PARSE_REQUEST_HEADER_FIELD_HOOK(connection, name, value);
494 #endif
495 }
496 
497 
498 /**
499  * @brief Parse Connection header field
500  * @param[in] connection Structure representing an HTTP connection
501  * @param[in] value Connection field value
502  **/
503 
505  char_t *value)
506 {
507  char_t *p;
508  char_t *token;
509 
510  //Get the first value of the list
511  token = osStrtok_r(value, ",", &p);
512 
513  //Parse the comma-separated list
514  while(token != NULL)
515  {
516  //Trim whitespace characters
518 
519  //Check current value
520  if(osStrcasecmp(value, "keep-alive") == 0)
521  {
522  //The connection is persistent
523  connection->request.keepAlive = TRUE;
524  }
525  else if(osStrcasecmp(value, "close") == 0)
526  {
527  //The connection will be closed after completion of the response
528  connection->request.keepAlive = FALSE;
529  }
530 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
531  else if(osStrcasecmp(value, "upgrade") == 0)
532  {
533  //Upgrade the connection
534  connection->request.connectionUpgrade = TRUE;
535  }
536 #endif
537 
538  //Get next value
539  token = osStrtok_r(NULL, ",", &p);
540  }
541 }
542 
543 
544 /**
545  * @brief Parse Content-Type header field
546  * @param[in] connection Structure representing an HTTP connection
547  * @param[in] value Content-Type field value
548  **/
549 
551  char_t *value)
552 {
553 #if (HTTP_SERVER_MULTIPART_TYPE_SUPPORT == ENABLED)
554  size_t n;
555  char_t *p;
556  char_t *token;
557 #endif
558 
559 #if (HTTP_SERVER_CONTENT_TYPE_MAX_LEN > 0)
560  //Save the contents of the Content-Type header field
561  strSafeCopy(connection->request.contentType, value,
563 #endif
564 
565 #if (HTTP_SERVER_MULTIPART_TYPE_SUPPORT == ENABLED)
566  //Retrieve type
567  token = osStrtok_r(value, "/", &p);
568  //Any parsing error?
569  if(token == NULL)
570  return;
571 
572  //The boundary parameter makes sense only for the multipart content-type
573  if(osStrcasecmp(token, "multipart") == 0)
574  {
575  //Skip subtype
576  token = osStrtok_r(NULL, ";", &p);
577  //Any parsing error?
578  if(token == NULL)
579  return;
580 
581  //Retrieve parameter name
582  token = osStrtok_r(NULL, "=", &p);
583  //Any parsing error?
584  if(token == NULL)
585  return;
586 
587  //Trim whitespace characters
589 
590  //Check parameter name
591  if(osStrcasecmp(token, "boundary") == 0)
592  {
593  //Retrieve parameter value
594  token = osStrtok_r(NULL, ";", &p);
595  //Any parsing error?
596  if(token == NULL)
597  return;
598 
599  //Trim whitespace characters
601  //Get the length of the boundary string
602  n = osStrlen(token);
603 
604  //Check the length of the boundary string
606  {
607  //Copy the boundary string
608  osStrncpy(connection->request.boundary, token, n);
609  //Properly terminate the string
610  connection->request.boundary[n] = '\0';
611 
612  //Save the length of the boundary string
613  connection->request.boundaryLength = n;
614  }
615  }
616  }
617 #endif
618 }
619 
620 
621 /**
622  * @brief Parse Accept-Encoding header field
623  * @param[in] connection Structure representing an HTTP connection
624  * @param[in] value Accept-Encoding field value
625  **/
626 
628  char_t *value)
629 {
630 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
631  char_t *p;
632  char_t *token;
633 
634  //Get the first value of the list
635  token = osStrtok_r(value, ",", &p);
636 
637  //Parse the comma-separated list
638  while(token != NULL)
639  {
640  //Trim whitespace characters
642 
643  //Check current value
644  if(osStrcasecmp(value, "gzip") == 0)
645  {
646  //gzip compression is supported
647  connection->request.acceptGzipEncoding = TRUE;
648  }
649 
650  //Get next value
651  token = osStrtok_r(NULL, ",", &p);
652  }
653 #endif
654 }
655 
656 
657 /**
658  * @brief Parse Cookie header field
659  * @param[in] connection Structure representing an HTTP connection
660  * @param[in] value Accept-Encoding field value
661  **/
662 
664 {
665 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
666  //Save the value of the header field
667  strSafeCopy(connection->request.cookie, value, HTTP_SERVER_COOKIE_MAX_LEN);
668 #endif
669 }
670 
671 
672 /**
673  * @brief Read chunk-size field from the input stream
674  * @param[in] connection Structure representing an HTTP connection
675  **/
676 
678 {
679  error_t error;
680  size_t n;
681  char_t *end;
682  char_t s[8];
683 
684  //First chunk to be received?
685  if(connection->request.firstChunk)
686  {
687  //Clear the flag
688  connection->request.firstChunk = FALSE;
689  }
690  else
691  {
692  //Read the CRLF that follows the previous chunk-data field
693  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
694  //Any error to report?
695  if(error)
696  return error;
697 
698  //Properly terminate the string with a NULL character
699  s[n] = '\0';
700 
701  //The chunk data must be terminated by CRLF
702  if(osStrcmp(s, "\r\n") != 0)
703  return ERROR_WRONG_ENCODING;
704  }
705 
706  //Read the chunk-size field
707  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
708  //Any error to report?
709  if(error)
710  return error;
711 
712  //Properly terminate the string with a NULL character
713  s[n] = '\0';
714  //Remove extra whitespaces
716 
717  //Retrieve the size of the chunk
718  connection->request.byteCount = osStrtoul(s, &end, 16);
719 
720  //No valid conversion could be performed?
721  if(end == s || *end != '\0')
722  return ERROR_WRONG_ENCODING;
723 
724  //Any chunk whose size is zero terminates the data transfer
725  if(!connection->request.byteCount)
726  {
727  //The end of the HTTP request body has been reached
728  connection->request.lastChunk = TRUE;
729 
730  //Skip the trailer
731  while(1)
732  {
733  //Read a complete line
734  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
735  //Unable to read any data?
736  if(error)
737  return error;
738 
739  //Properly terminate the string with a NULL character
740  s[n] = '\0';
741 
742  //The trailer is terminated by an empty line
743  if(osStrcmp(s, "\r\n") == 0)
744  break;
745  }
746  }
747 
748  //Successful processing
749  return NO_ERROR;
750 }
751 
752 
753 /**
754  * @brief Initialize response header
755  * @param[in] connection Structure representing an HTTP connection
756  **/
757 
759 {
760  //Default HTTP header fields
761  connection->response.version = connection->request.version;
762  connection->response.statusCode = 200;
763  connection->response.noCache = FALSE;
764  connection->response.maxAge = 0;
765  connection->response.location = NULL;
766  connection->response.contentType = mimeGetType(connection->request.uri);
767  connection->response.chunkedEncoding = TRUE;
768 
769 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
770  //Do not use gzip encoding
771  connection->response.gzipEncoding = FALSE;
772 #endif
773 
774 #if (HTTP_SERVER_PERSISTENT_CONN_SUPPORT == ENABLED)
775  //Persistent connections are accepted
776  connection->response.keepAlive = connection->request.keepAlive;
777 #else
778  //Connections are not persistent by default
779  connection->response.keepAlive = FALSE;
780 #endif
781 }
782 
783 
784 /**
785  * @brief Format HTTP response header
786  * @param[in] connection Structure representing an HTTP connection
787  * @param[out] buffer Pointer to the buffer where to format the HTTP header
788  * @return Error code
789  **/
790 
792 {
793  uint_t i;
794  char_t *p;
795 
796  //HTTP version 0.9?
797  if(connection->response.version == HTTP_VERSION_0_9)
798  {
799  //Enforce default parameters
800  connection->response.keepAlive = FALSE;
801  connection->response.chunkedEncoding = FALSE;
802  //The size of the response body is not limited
803  connection->response.byteCount = UINT_MAX;
804  //We are done since HTTP 0.9 does not support Full-Response format
805  return NO_ERROR;
806  }
807 
808  //When generating dynamic web pages with HTTP 1.0, the only way to
809  //signal the end of the body is to close the connection
810  if(connection->response.version == HTTP_VERSION_1_0 &&
811  connection->response.chunkedEncoding)
812  {
813  //Make the connection non persistent
814  connection->response.keepAlive = FALSE;
815  connection->response.chunkedEncoding = FALSE;
816  //The size of the response body is not limited
817  connection->response.byteCount = UINT_MAX;
818  }
819  else
820  {
821  //Limit the size of the response body
822  connection->response.byteCount = connection->response.contentLength;
823  }
824 
825  //Point to the beginning of the buffer
826  p = buffer;
827 
828  //The first line of a response message is the Status-Line, consisting
829  //of the protocol version followed by a numeric status code and its
830  //associated textual phrase
831  p += osSprintf(p, "HTTP/%u.%u %u ", MSB(connection->response.version),
832  LSB(connection->response.version), connection->response.statusCode);
833 
834  //Retrieve the Reason-Phrase that corresponds to the Status-Code
835  for(i = 0; i < arraysize(statusCodeList); i++)
836  {
837  //Check the status code
838  if(statusCodeList[i].value == connection->response.statusCode)
839  {
840  //Append the textual phrase to the Status-Line
841  p += osSprintf(p, "%s", statusCodeList[i].message);
842  //Break the loop and continue processing
843  break;
844  }
845  }
846 
847  //Properly terminate the Status-Line
848  p += osSprintf(p, "\r\n");
849 
850  //Valid location?
851  if(connection->response.location != NULL)
852  {
853  //Set Location field
854  p += osSprintf(p, "Location: %s\r\n", connection->response.location);
855  }
856 
857  //Persistent connection?
858  if(connection->response.keepAlive)
859  {
860  //Set Connection field
861  p += osSprintf(p, "Connection: keep-alive\r\n");
862 
863  //Set Keep-Alive field
864  p += osSprintf(p, "Keep-Alive: timeout=%u, max=%u\r\n",
866  }
867  else
868  {
869  //Set Connection field
870  p += osSprintf(p, "Connection: close\r\n");
871  }
872 
873  //Specify the caching policy
874  if(connection->response.noCache)
875  {
876  //Set Pragma field
877  p += osSprintf(p, "Pragma: no-cache\r\n");
878  //Set Cache-Control field
879  p += osSprintf(p, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
880  p += osSprintf(p, "Cache-Control: max-age=0, post-check=0, pre-check=0\r\n");
881  }
882  else if(connection->response.maxAge != 0)
883  {
884  //Set Cache-Control field
885  p += osSprintf(p, "Cache-Control: max-age=%u\r\n", connection->response.maxAge);
886  }
887 
888 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED && HTTP_SERVER_HSTS_SUPPORT == ENABLED)
889  //TLS-secured connection?
890  if(connection->serverContext->settings.tlsInitCallback != NULL)
891  {
892  //Set Strict-Transport-Security field
893  p += osSprintf(p, "Strict-Transport-Security: max-age=31536000\r\n");
894  }
895 #endif
896 
897 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
898  //Check whether authentication is required
899  if(connection->response.auth.mode != HTTP_AUTH_MODE_NONE)
900  {
901  //Add WWW-Authenticate header field
902  p += httpAddAuthenticateField(connection, p);
903  }
904 #endif
905 
906 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
907  //Valid cookie
908  if(connection->response.setCookie[0] != '\0')
909  {
910  //Add Set-Cookie header field
911  p += osSprintf(p, "Set-Cookie: %s\r\n", connection->response.setCookie);
912  }
913 #endif
914 
915  //Valid content type?
916  if(connection->response.contentType != NULL)
917  {
918  //Content type
919  p += osSprintf(p, "Content-Type: %s\r\n", connection->response.contentType);
920  }
921 
922 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
923  //Use gzip encoding?
924  if(connection->response.gzipEncoding)
925  {
926  //Set Transfer-Encoding field
927  p += osSprintf(p, "Content-Encoding: gzip\r\n");
928  }
929 #endif
930 
931  //Use chunked encoding transfer?
932  if(connection->response.chunkedEncoding)
933  {
934  //Set Transfer-Encoding field
935  p += osSprintf(p, "Transfer-Encoding: chunked\r\n");
936  }
937  //Persistent connection?
938  else if(connection->response.keepAlive)
939  {
940  //Set Content-Length field
941  p += osSprintf(p, "Content-Length: %" PRIuSIZE "\r\n", connection->response.contentLength);
942  }
943 
944 #if defined(HTTP_FORMAT_RESPONSE_HEADER_HOOK)
945  //Format custom header fields
946  HTTP_FORMAT_RESPONSE_HEADER_HOOK(connection, p);
947 #endif
948 
949  //Terminate the header with an empty line
950  osStrcat(p, "\r\n");
951 
952  //Successful processing
953  return NO_ERROR;
954 }
955 
956 
957 /**
958  * @brief Send data to the client
959  * @param[in] connection Structure representing an HTTP connection
960  * @param[in] data Pointer to a buffer containing the data to be transmitted
961  * @param[in] length Number of bytes to be transmitted
962  * @param[in] flags Set of flags that influences the behavior of this function
963  **/
964 
966  const void *data, size_t length, uint_t flags)
967 {
968 #if (NET_RTOS_SUPPORT == ENABLED)
969  error_t error;
970 
971 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
972  //Check whether a secure connection is being used
973  if(connection->tlsContext != NULL)
974  {
975  //Use TLS to transmit data to the client
976  error = tlsWrite(connection->tlsContext, data, length, NULL, flags);
977  }
978  else
979 #endif
980  {
981  //Transmit data to the client
982  error = socketSend(connection->socket, data, length, NULL, flags);
983  }
984 
985  //Return status code
986  return error;
987 #else
988  //Prevent buffer overflow
989  if((connection->bufferLen + length) > HTTP_SERVER_BUFFER_SIZE)
990  return ERROR_BUFFER_OVERFLOW;
991 
992  //Copy user data
993  osMemcpy(connection->buffer + connection->bufferLen, data, length);
994  //Adjust the length of the buffer
995  connection->bufferLen += length;
996 
997  //Successful processing
998  return NO_ERROR;
999 #endif
1000 }
1001 
1002 
1003 /**
1004  * @brief Receive data from the client
1005  * @param[in] connection Structure representing an HTTP connection
1006  * @param[out] data Buffer into which received data will be placed
1007  * @param[in] size Maximum number of bytes that can be received
1008  * @param[out] received Actual number of bytes that have been received
1009  * @param[in] flags Set of flags that influences the behavior of this function
1010  * @return Error code
1011  **/
1012 
1014  void *data, size_t size, size_t *received, uint_t flags)
1015 {
1016 #if (NET_RTOS_SUPPORT == ENABLED)
1017  error_t error;
1018 
1019 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
1020  //Check whether a secure connection is being used
1021  if(connection->tlsContext != NULL)
1022  {
1023  //Use TLS to receive data from the client
1024  error = tlsRead(connection->tlsContext, data, size, received, flags);
1025  }
1026  else
1027 #endif
1028  {
1029  //Receive data from the client
1030  error = socketReceive(connection->socket, data, size, received, flags);
1031  }
1032 
1033  //Return status code
1034  return error;
1035 #else
1036  error_t error;
1037  char_t c;
1038  size_t i;
1039  size_t n;
1040 
1041  //Number of data bytes that are pending in the receive buffer
1042  n = connection->bufferLen - connection->bufferPos;
1043 
1044  //Any data to be copied?
1045  if(n > 0)
1046  {
1047  //Limit the number of bytes to read at a time
1048  n = MIN(n, size);
1049 
1050  //The HTTP_FLAG_BREAK_CHAR flag causes the function to stop reading
1051  //data as soon as the specified break character is encountered
1052  if((flags & HTTP_FLAG_BREAK_CHAR) != 0)
1053  {
1054  //Retrieve the break character code
1055  c = LSB(flags);
1056 
1057  //Search for the specified break character
1058  for(i = 0; i < n && connection->buffer[connection->bufferPos + i] != c; i++)
1059  {
1060  }
1061 
1062  //Adjust the number of data to read
1063  n = MIN(n, i + 1);
1064  }
1065 
1066  //Copy data to user buffer
1067  osMemcpy(data, connection->buffer + connection->bufferPos, n);
1068 
1069  //Advance current position
1070  connection->bufferPos += n;
1071  //Total number of data that have been read
1072  *received = n;
1073 
1074  //Successful processing
1075  error = NO_ERROR;
1076  }
1077  else
1078  {
1079  //No more data available...
1080  error = ERROR_END_OF_STREAM;
1081  }
1082 
1083  //Return status code
1084  return error;
1085 #endif
1086 }
1087 
1088 
1089 /**
1090  * @brief Retrieve the full pathname to the specified resource
1091  * @param[in] connection Structure representing an HTTP connection
1092  * @param[in] relative String containing the relative path to the resource
1093  * @param[out] absolute Resulting string containing the absolute path
1094  * @param[in] maxLen Maximum acceptable path length
1095  **/
1096 
1098  const char_t *relative, char_t *absolute, size_t maxLen)
1099 {
1100  //Copy the root directory
1101  osStrcpy(absolute, connection->settings->rootDirectory);
1102 
1103  //Append the specified path
1104  pathCombine(absolute, relative, maxLen);
1105 
1106  //Clean the resulting path
1107  pathCanonicalize(absolute);
1108 }
1109 
1110 
1111 /**
1112  * @brief Compare filename extension
1113  * @param[in] filename Filename whose extension is to be checked
1114  * @param[in] extension String defining the extension to be checked
1115  * @return TRUE is the filename matches the given extension, else FALSE
1116  **/
1117 
1118 bool_t httpCompExtension(const char_t *filename, const char_t *extension)
1119 {
1120  uint_t n;
1121  uint_t m;
1122 
1123  //Get the length of the specified filename
1124  n = osStrlen(filename);
1125  //Get the length of the extension
1126  m = osStrlen(extension);
1127 
1128  //Check the length of the filename
1129  if(n < m)
1130  return FALSE;
1131 
1132  //Compare extensions
1133  if(osStrncasecmp(filename + n - m, extension, m) == 0)
1134  {
1135  return TRUE;
1136  }
1137  else
1138  {
1139  return FALSE;
1140  }
1141 }
1142 
1143 
1144 /**
1145  * @brief Decode a percent-encoded string
1146  * @param[in] input NULL-terminated string to be decoded
1147  * @param[out] output NULL-terminated string resulting from the decoding process
1148  * @param[in] outputSize Size of the output buffer in bytes
1149  * @return Error code
1150  **/
1151 
1153  char_t *output, size_t outputSize)
1154 {
1155  size_t i;
1156  char_t buffer[3];
1157 
1158  //Check parameters
1159  if(input == NULL || output == NULL)
1160  return ERROR_INVALID_PARAMETER;
1161 
1162  //Decode the percent-encoded string
1163  for(i = 0; *input != '\0' && i < outputSize; i++)
1164  {
1165  //Check current character
1166  if(*input == '+')
1167  {
1168  //Replace '+' characters with spaces
1169  output[i] = ' ';
1170  //Advance data pointer
1171  input++;
1172  }
1173  else if(input[0] == '%' && input[1] != '\0' && input[2] != '\0')
1174  {
1175  //Process percent-encoded characters
1176  buffer[0] = input[1];
1177  buffer[1] = input[2];
1178  buffer[2] = '\0';
1179  //String to integer conversion
1180  output[i] = (uint8_t) osStrtoul(buffer, NULL, 16);
1181  //Advance data pointer
1182  input += 3;
1183  }
1184  else
1185  {
1186  //Copy any other characters
1187  output[i] = *input;
1188  //Advance data pointer
1189  input++;
1190  }
1191  }
1192 
1193  //Check whether the output buffer runs out of space
1194  if(i >= outputSize)
1195  return ERROR_FAILURE;
1196 
1197  //Properly terminate the resulting string
1198  output[i] = '\0';
1199  //Successful processing
1200  return NO_ERROR;
1201 }
1202 
1203 
1204 /**
1205  * @brief Convert byte array to hex string
1206  * @param[in] input Point to the byte array
1207  * @param[in] inputLen Length of the byte array
1208  * @param[out] output NULL-terminated string resulting from the conversion
1209  **/
1210 
1211 void httpConvertArrayToHexString(const uint8_t *input,
1212  size_t inputLen, char_t *output)
1213 {
1214  size_t i;
1215 
1216  //Hex conversion table
1217  static const char_t hexDigit[16] =
1218  {
1219  '0', '1', '2', '3', '4', '5', '6', '7',
1220  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
1221  };
1222 
1223  //Process byte array
1224  for(i = 0; i < inputLen; i++)
1225  {
1226  //Convert upper nibble
1227  output[i * 2] = hexDigit[(input[i] >> 4) & 0x0F];
1228  //Then convert lower nibble
1229  output[i * 2 + 1] = hexDigit[input[i] & 0x0F];
1230  }
1231 
1232  //Properly terminate the string with a NULL character
1233  output[i * 2] = '\0';
1234 }
1235 
1236 #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:1491
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:61
@ 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:222
#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:158
@ 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:1697
#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:2105
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
uint8_t flags
Definition: tcp.h:358
#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:1970
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.
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:148
@ 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:394
void httpInitResponseHeader(HttpConnection *connection)
Initialize response header.