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-2024 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.4.4
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 
492 
493 /**
494  * @brief Parse Connection header field
495  * @param[in] connection Structure representing an HTTP connection
496  * @param[in] value Connection field value
497  **/
498 
500  char_t *value)
501 {
502  char_t *p;
503  char_t *token;
504 
505  //Get the first value of the list
506  token = osStrtok_r(value, ",", &p);
507 
508  //Parse the comma-separated list
509  while(token != NULL)
510  {
511  //Trim whitespace characters
513 
514  //Check current value
515  if(osStrcasecmp(value, "keep-alive") == 0)
516  {
517  //The connection is persistent
518  connection->request.keepAlive = TRUE;
519  }
520  else if(osStrcasecmp(value, "close") == 0)
521  {
522  //The connection will be closed after completion of the response
523  connection->request.keepAlive = FALSE;
524  }
525 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
526  else if(osStrcasecmp(value, "upgrade") == 0)
527  {
528  //Upgrade the connection
529  connection->request.connectionUpgrade = TRUE;
530  }
531 #endif
532 
533  //Get next value
534  token = osStrtok_r(NULL, ",", &p);
535  }
536 }
537 
538 
539 /**
540  * @brief Parse Content-Type header field
541  * @param[in] connection Structure representing an HTTP connection
542  * @param[in] value Content-Type field value
543  **/
544 
546  char_t *value)
547 {
548 #if (HTTP_SERVER_MULTIPART_TYPE_SUPPORT == ENABLED)
549  size_t n;
550  char_t *p;
551  char_t *token;
552 
553  //Retrieve type
554  token = osStrtok_r(value, "/", &p);
555  //Any parsing error?
556  if(token == NULL)
557  return;
558 
559  //The boundary parameter makes sense only for the multipart content-type
560  if(osStrcasecmp(token, "multipart") == 0)
561  {
562  //Skip subtype
563  token = osStrtok_r(NULL, ";", &p);
564  //Any parsing error?
565  if(token == NULL)
566  return;
567 
568  //Retrieve parameter name
569  token = osStrtok_r(NULL, "=", &p);
570  //Any parsing error?
571  if(token == NULL)
572  return;
573 
574  //Trim whitespace characters
576 
577  //Check parameter name
578  if(osStrcasecmp(token, "boundary") == 0)
579  {
580  //Retrieve parameter value
581  token = osStrtok_r(NULL, ";", &p);
582  //Any parsing error?
583  if(token == NULL)
584  return;
585 
586  //Trim whitespace characters
588  //Get the length of the boundary string
589  n = osStrlen(token);
590 
591  //Check the length of the boundary string
593  {
594  //Copy the boundary string
595  osStrncpy(connection->request.boundary, token, n);
596  //Properly terminate the string
597  connection->request.boundary[n] = '\0';
598 
599  //Save the length of the boundary string
600  connection->request.boundaryLength = n;
601  }
602  }
603  }
604 #endif
605 }
606 
607 
608 /**
609  * @brief Parse Accept-Encoding header field
610  * @param[in] connection Structure representing an HTTP connection
611  * @param[in] value Accept-Encoding field value
612  **/
613 
615  char_t *value)
616 {
617 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
618  char_t *p;
619  char_t *token;
620 
621  //Get the first value of the list
622  token = osStrtok_r(value, ",", &p);
623 
624  //Parse the comma-separated list
625  while(token != NULL)
626  {
627  //Trim whitespace characters
629 
630  //Check current value
631  if(osStrcasecmp(value, "gzip") == 0)
632  {
633  //gzip compression is supported
634  connection->request.acceptGzipEncoding = TRUE;
635  }
636 
637  //Get next value
638  token = osStrtok_r(NULL, ",", &p);
639  }
640 #endif
641 }
642 
643 
644 /**
645  * @brief Parse Cookie header field
646  * @param[in] connection Structure representing an HTTP connection
647  * @param[in] value Accept-Encoding field value
648  **/
649 
651 {
652 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
653  //Save the value of the header field
654  strSafeCopy(connection->request.cookie, value, HTTP_SERVER_COOKIE_MAX_LEN);
655 #endif
656 }
657 
658 
659 /**
660  * @brief Read chunk-size field from the input stream
661  * @param[in] connection Structure representing an HTTP connection
662  **/
663 
665 {
666  error_t error;
667  size_t n;
668  char_t *end;
669  char_t s[8];
670 
671  //First chunk to be received?
672  if(connection->request.firstChunk)
673  {
674  //Clear the flag
675  connection->request.firstChunk = FALSE;
676  }
677  else
678  {
679  //Read the CRLF that follows the previous chunk-data field
680  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
681  //Any error to report?
682  if(error)
683  return error;
684 
685  //Properly terminate the string with a NULL character
686  s[n] = '\0';
687 
688  //The chunk data must be terminated by CRLF
689  if(osStrcmp(s, "\r\n") != 0)
690  return ERROR_WRONG_ENCODING;
691  }
692 
693  //Read the chunk-size field
694  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
695  //Any error to report?
696  if(error)
697  return error;
698 
699  //Properly terminate the string with a NULL character
700  s[n] = '\0';
701  //Remove extra whitespaces
703 
704  //Retrieve the size of the chunk
705  connection->request.byteCount = osStrtoul(s, &end, 16);
706 
707  //No valid conversion could be performed?
708  if(end == s || *end != '\0')
709  return ERROR_WRONG_ENCODING;
710 
711  //Any chunk whose size is zero terminates the data transfer
712  if(!connection->request.byteCount)
713  {
714  //The end of the HTTP request body has been reached
715  connection->request.lastChunk = TRUE;
716 
717  //Skip the trailer
718  while(1)
719  {
720  //Read a complete line
721  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
722  //Unable to read any data?
723  if(error)
724  return error;
725 
726  //Properly terminate the string with a NULL character
727  s[n] = '\0';
728 
729  //The trailer is terminated by an empty line
730  if(osStrcmp(s, "\r\n") == 0)
731  break;
732  }
733  }
734 
735  //Successful processing
736  return NO_ERROR;
737 }
738 
739 
740 /**
741  * @brief Initialize response header
742  * @param[in] connection Structure representing an HTTP connection
743  **/
744 
746 {
747  //Default HTTP header fields
748  connection->response.version = connection->request.version;
749  connection->response.statusCode = 200;
750  connection->response.noCache = FALSE;
751  connection->response.maxAge = 0;
752  connection->response.location = NULL;
753  connection->response.contentType = mimeGetType(connection->request.uri);
754  connection->response.chunkedEncoding = TRUE;
755 
756 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
757  //Do not use gzip encoding
758  connection->response.gzipEncoding = FALSE;
759 #endif
760 
761 #if (HTTP_SERVER_PERSISTENT_CONN_SUPPORT == ENABLED)
762  //Persistent connections are accepted
763  connection->response.keepAlive = connection->request.keepAlive;
764 #else
765  //Connections are not persistent by default
766  connection->response.keepAlive = FALSE;
767 #endif
768 }
769 
770 
771 /**
772  * @brief Format HTTP response header
773  * @param[in] connection Structure representing an HTTP connection
774  * @param[out] buffer Pointer to the buffer where to format the HTTP header
775  * @return Error code
776  **/
777 
779 {
780  uint_t i;
781  char_t *p;
782 
783  //HTTP version 0.9?
784  if(connection->response.version == HTTP_VERSION_0_9)
785  {
786  //Enforce default parameters
787  connection->response.keepAlive = FALSE;
788  connection->response.chunkedEncoding = FALSE;
789  //The size of the response body is not limited
790  connection->response.byteCount = UINT_MAX;
791  //We are done since HTTP 0.9 does not support Full-Response format
792  return NO_ERROR;
793  }
794 
795  //When generating dynamic web pages with HTTP 1.0, the only way to
796  //signal the end of the body is to close the connection
797  if(connection->response.version == HTTP_VERSION_1_0 &&
798  connection->response.chunkedEncoding)
799  {
800  //Make the connection non persistent
801  connection->response.keepAlive = FALSE;
802  connection->response.chunkedEncoding = FALSE;
803  //The size of the response body is not limited
804  connection->response.byteCount = UINT_MAX;
805  }
806  else
807  {
808  //Limit the size of the response body
809  connection->response.byteCount = connection->response.contentLength;
810  }
811 
812  //Point to the beginning of the buffer
813  p = buffer;
814 
815  //The first line of a response message is the Status-Line, consisting
816  //of the protocol version followed by a numeric status code and its
817  //associated textual phrase
818  p += osSprintf(p, "HTTP/%u.%u %u ", MSB(connection->response.version),
819  LSB(connection->response.version), connection->response.statusCode);
820 
821  //Retrieve the Reason-Phrase that corresponds to the Status-Code
822  for(i = 0; i < arraysize(statusCodeList); i++)
823  {
824  //Check the status code
825  if(statusCodeList[i].value == connection->response.statusCode)
826  {
827  //Append the textual phrase to the Status-Line
828  p += osSprintf(p, "%s", statusCodeList[i].message);
829  //Break the loop and continue processing
830  break;
831  }
832  }
833 
834  //Properly terminate the Status-Line
835  p += osSprintf(p, "\r\n");
836 
837  //Valid location?
838  if(connection->response.location != NULL)
839  {
840  //Set Location field
841  p += osSprintf(p, "Location: %s\r\n", connection->response.location);
842  }
843 
844  //Persistent connection?
845  if(connection->response.keepAlive)
846  {
847  //Set Connection field
848  p += osSprintf(p, "Connection: keep-alive\r\n");
849 
850  //Set Keep-Alive field
851  p += osSprintf(p, "Keep-Alive: timeout=%u, max=%u\r\n",
853  }
854  else
855  {
856  //Set Connection field
857  p += osSprintf(p, "Connection: close\r\n");
858  }
859 
860  //Specify the caching policy
861  if(connection->response.noCache)
862  {
863  //Set Pragma field
864  p += osSprintf(p, "Pragma: no-cache\r\n");
865  //Set Cache-Control field
866  p += osSprintf(p, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
867  p += osSprintf(p, "Cache-Control: max-age=0, post-check=0, pre-check=0\r\n");
868  }
869  else if(connection->response.maxAge != 0)
870  {
871  //Set Cache-Control field
872  p += osSprintf(p, "Cache-Control: max-age=%u\r\n", connection->response.maxAge);
873  }
874 
875 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED && HTTP_SERVER_HSTS_SUPPORT == ENABLED)
876  //TLS-secured connection?
877  if(connection->serverContext->settings.tlsInitCallback != NULL)
878  {
879  //Set Strict-Transport-Security field
880  p += osSprintf(p, "Strict-Transport-Security: max-age=31536000\r\n");
881  }
882 #endif
883 
884 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
885  //Check whether authentication is required
886  if(connection->response.auth.mode != HTTP_AUTH_MODE_NONE)
887  {
888  //Add WWW-Authenticate header field
889  p += httpAddAuthenticateField(connection, p);
890  }
891 #endif
892 
893 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
894  //Valid cookie
895  if(connection->response.setCookie[0] != '\0')
896  {
897  //Add Set-Cookie header field
898  p += osSprintf(p, "Set-Cookie: %s\r\n", connection->response.setCookie);
899  }
900 #endif
901 
902  //Valid content type?
903  if(connection->response.contentType != NULL)
904  {
905  //Content type
906  p += osSprintf(p, "Content-Type: %s\r\n", connection->response.contentType);
907  }
908 
909 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
910  //Use gzip encoding?
911  if(connection->response.gzipEncoding)
912  {
913  //Set Transfer-Encoding field
914  p += osSprintf(p, "Content-Encoding: gzip\r\n");
915  }
916 #endif
917 
918  //Use chunked encoding transfer?
919  if(connection->response.chunkedEncoding)
920  {
921  //Set Transfer-Encoding field
922  p += osSprintf(p, "Transfer-Encoding: chunked\r\n");
923  }
924  //Persistent connection?
925  else if(connection->response.keepAlive)
926  {
927  //Set Content-Length field
928  p += osSprintf(p, "Content-Length: %" PRIuSIZE "\r\n", connection->response.contentLength);
929  }
930 
931  //Terminate the header with an empty line
932  p += osSprintf(p, "\r\n");
933 
934  //Successful processing
935  return NO_ERROR;
936 }
937 
938 
939 /**
940  * @brief Send data to the client
941  * @param[in] connection Structure representing an HTTP connection
942  * @param[in] data Pointer to a buffer containing the data to be transmitted
943  * @param[in] length Number of bytes to be transmitted
944  * @param[in] flags Set of flags that influences the behavior of this function
945  **/
946 
948  const void *data, size_t length, uint_t flags)
949 {
950 #if (NET_RTOS_SUPPORT == ENABLED)
951  error_t error;
952 
953 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
954  //Check whether a secure connection is being used
955  if(connection->tlsContext != NULL)
956  {
957  //Use TLS to transmit data to the client
958  error = tlsWrite(connection->tlsContext, data, length, NULL, flags);
959  }
960  else
961 #endif
962  {
963  //Transmit data to the client
964  error = socketSend(connection->socket, data, length, NULL, flags);
965  }
966 
967  //Return status code
968  return error;
969 #else
970  //Prevent buffer overflow
971  if((connection->bufferLen + length) > HTTP_SERVER_BUFFER_SIZE)
972  return ERROR_BUFFER_OVERFLOW;
973 
974  //Copy user data
975  osMemcpy(connection->buffer + connection->bufferLen, data, length);
976  //Adjust the length of the buffer
977  connection->bufferLen += length;
978 
979  //Successful processing
980  return NO_ERROR;
981 #endif
982 }
983 
984 
985 /**
986  * @brief Receive data from the client
987  * @param[in] connection Structure representing an HTTP connection
988  * @param[out] data Buffer into which received data will be placed
989  * @param[in] size Maximum number of bytes that can be received
990  * @param[out] received Actual number of bytes that have been received
991  * @param[in] flags Set of flags that influences the behavior of this function
992  * @return Error code
993  **/
994 
996  void *data, size_t size, size_t *received, uint_t flags)
997 {
998 #if (NET_RTOS_SUPPORT == ENABLED)
999  error_t error;
1000 
1001 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
1002  //Check whether a secure connection is being used
1003  if(connection->tlsContext != NULL)
1004  {
1005  //Use TLS to receive data from the client
1006  error = tlsRead(connection->tlsContext, data, size, received, flags);
1007  }
1008  else
1009 #endif
1010  {
1011  //Receive data from the client
1012  error = socketReceive(connection->socket, data, size, received, flags);
1013  }
1014 
1015  //Return status code
1016  return error;
1017 #else
1018  error_t error;
1019  char_t c;
1020  size_t i;
1021  size_t n;
1022 
1023  //Number of data bytes that are pending in the receive buffer
1024  n = connection->bufferLen - connection->bufferPos;
1025 
1026  //Any data to be copied?
1027  if(n > 0)
1028  {
1029  //Limit the number of bytes to read at a time
1030  n = MIN(n, size);
1031 
1032  //The HTTP_FLAG_BREAK_CHAR flag causes the function to stop reading
1033  //data as soon as the specified break character is encountered
1034  if((flags & HTTP_FLAG_BREAK_CHAR) != 0)
1035  {
1036  //Retrieve the break character code
1037  c = LSB(flags);
1038 
1039  //Search for the specified break character
1040  for(i = 0; i < n && connection->buffer[connection->bufferPos + i] != c; i++)
1041  {
1042  }
1043 
1044  //Adjust the number of data to read
1045  n = MIN(n, i + 1);
1046  }
1047 
1048  //Copy data to user buffer
1049  osMemcpy(data, connection->buffer + connection->bufferPos, n);
1050 
1051  //Advance current position
1052  connection->bufferPos += n;
1053  //Total number of data that have been read
1054  *received = n;
1055 
1056  //Successful processing
1057  error = NO_ERROR;
1058  }
1059  else
1060  {
1061  //No more data available...
1062  error = ERROR_END_OF_STREAM;
1063  }
1064 
1065  //Return status code
1066  return error;
1067 #endif
1068 }
1069 
1070 
1071 /**
1072  * @brief Retrieve the full pathname to the specified resource
1073  * @param[in] connection Structure representing an HTTP connection
1074  * @param[in] relative String containing the relative path to the resource
1075  * @param[out] absolute Resulting string containing the absolute path
1076  * @param[in] maxLen Maximum acceptable path length
1077  **/
1078 
1080  const char_t *relative, char_t *absolute, size_t maxLen)
1081 {
1082  //Copy the root directory
1083  osStrcpy(absolute, connection->settings->rootDirectory);
1084 
1085  //Append the specified path
1086  pathCombine(absolute, relative, maxLen);
1087 
1088  //Clean the resulting path
1089  pathCanonicalize(absolute);
1090 }
1091 
1092 
1093 /**
1094  * @brief Compare filename extension
1095  * @param[in] filename Filename whose extension is to be checked
1096  * @param[in] extension String defining the extension to be checked
1097  * @return TRUE is the filename matches the given extension, else FALSE
1098  **/
1099 
1100 bool_t httpCompExtension(const char_t *filename, const char_t *extension)
1101 {
1102  uint_t n;
1103  uint_t m;
1104 
1105  //Get the length of the specified filename
1106  n = osStrlen(filename);
1107  //Get the length of the extension
1108  m = osStrlen(extension);
1109 
1110  //Check the length of the filename
1111  if(n < m)
1112  return FALSE;
1113 
1114  //Compare extensions
1115  if(osStrncasecmp(filename + n - m, extension, m) == 0)
1116  {
1117  return TRUE;
1118  }
1119  else
1120  {
1121  return FALSE;
1122  }
1123 }
1124 
1125 
1126 /**
1127  * @brief Decode a percent-encoded string
1128  * @param[in] input NULL-terminated string to be decoded
1129  * @param[out] output NULL-terminated string resulting from the decoding process
1130  * @param[in] outputSize Size of the output buffer in bytes
1131  * @return Error code
1132  **/
1133 
1135  char_t *output, size_t outputSize)
1136 {
1137  size_t i;
1138  char_t buffer[3];
1139 
1140  //Check parameters
1141  if(input == NULL || output == NULL)
1142  return ERROR_INVALID_PARAMETER;
1143 
1144  //Decode the percent-encoded string
1145  for(i = 0; *input != '\0' && i < outputSize; i++)
1146  {
1147  //Check current character
1148  if(*input == '+')
1149  {
1150  //Replace '+' characters with spaces
1151  output[i] = ' ';
1152  //Advance data pointer
1153  input++;
1154  }
1155  else if(input[0] == '%' && input[1] != '\0' && input[2] != '\0')
1156  {
1157  //Process percent-encoded characters
1158  buffer[0] = input[1];
1159  buffer[1] = input[2];
1160  buffer[2] = '\0';
1161  //String to integer conversion
1162  output[i] = (uint8_t) osStrtoul(buffer, NULL, 16);
1163  //Advance data pointer
1164  input += 3;
1165  }
1166  else
1167  {
1168  //Copy any other characters
1169  output[i] = *input;
1170  //Advance data pointer
1171  input++;
1172  }
1173  }
1174 
1175  //Check whether the output buffer runs out of space
1176  if(i >= outputSize)
1177  return ERROR_FAILURE;
1178 
1179  //Properly terminate the resulting string
1180  output[i] = '\0';
1181  //Successful processing
1182  return NO_ERROR;
1183 }
1184 
1185 
1186 /**
1187  * @brief Convert byte array to hex string
1188  * @param[in] input Point to the byte array
1189  * @param[in] inputLen Length of the byte array
1190  * @param[out] output NULL-terminated string resulting from the conversion
1191  **/
1192 
1193 void httpConvertArrayToHexString(const uint8_t *input,
1194  size_t inputLen, char_t *output)
1195 {
1196  size_t i;
1197 
1198  //Hex conversion table
1199  static const char_t hexDigit[16] =
1200  {
1201  '0', '1', '2', '3', '4', '5', '6', '7',
1202  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
1203  };
1204 
1205  //Process byte array
1206  for(i = 0; i < inputLen; i++)
1207  {
1208  //Convert upper nibble
1209  output[i * 2] = hexDigit[(input[i] >> 4) & 0x0F];
1210  //Then convert lower nibble
1211  output[i * 2 + 1] = hexDigit[input[i] & 0x0F];
1212  }
1213 
1214  //Properly terminate the string with a NULL character
1215  output[i * 2] = '\0';
1216 }
1217 
1218 #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:1486
Path manipulation helper functions.
#define osStrchr(s, c)
Definition: os_port.h:195
String manipulation helper functions.
error_t httpFormatResponseHeader(HttpConnection *connection, char_t *buffer)
Format HTTP response header.
int bool_t
Definition: compiler_port.h:53
@ SOCKET_FLAG_WAIT_ALL
Definition: socket.h:138
#define HTTP_SERVER_COOKIE_MAX_LEN
Definition: http_server.h:279
HTTP server (HyperText Transfer Protocol)
#define WEB_SOCKET_CLIENT_KEY_SIZE
Definition: web_socket.h:171
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:142
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
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:171
#define osStrlen(s)
Definition: os_port.h:165
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:210
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:189
#define osMemcpy(dest, src, length)
Definition: os_port.h:141
@ HTTP_FLAG_BREAK_CHAR
Definition: http_common.h:98
error_t
Error codes.
Definition: error.h:43
#define osSprintf(dest,...)
Definition: os_port.h:231
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:1692
#define HttpConnection
Definition: http_server.h:332
#define osStrtok_r(s, delim, last)
Definition: os_port.h:225
HTTP status code.
Definition: http_server.h:432
@ 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:183
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:95
uint8_t length
Definition: tcp.h:368
#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:1989
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:255
uint8_t flags
Definition: tcp.h:351
#define TRACE_DEBUG(...)
Definition: debug.h:107
HTTP authentication.
char char_t
Definition: compiler_port.h:48
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:213
uint8_t value[]
Definition: tcp.h:369
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:1854
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:50
TCP/IP stack core.
@ HTTP_AUTH_MODE_NONE
Definition: http_common.h:73
#define osStrcpy(s1, s2)
Definition: os_port.h:207
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:272
@ 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.