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-2019 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 1.9.6
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL HTTP_TRACE_LEVEL
33 
34 //Dependencies
35 #include <stdlib.h>
36 #include <limits.h>
37 #include "core/net.h"
38 #include "http/http_server.h"
39 #include "http/http_server_auth.h"
40 #include "http/http_server_misc.h"
41 #include "http/mime.h"
42 #include "str.h"
43 #include "path.h"
44 #include "debug.h"
45 
46 //Check TCP/IP stack configuration
47 #if (HTTP_SERVER_SUPPORT == ENABLED)
48 
49 
50 /**
51  * @brief HTTP status codes
52  **/
53 
54 static const HttpStatusCodeDesc statusCodeList[] =
55 {
56  //Success
57  {200, "OK"},
58  {201, "Created"},
59  {202, "Accepted"},
60  {204, "No Content"},
61  //Redirection
62  {301, "Moved Permanently"},
63  {302, "Found"},
64  {304, "Not Modified"},
65  //Client error
66  {400, "Bad Request"},
67  {401, "Unauthorized"},
68  {403, "Forbidden"},
69  {404, "Not Found"},
70  //Server error
71  {500, "Internal Server Error"},
72  {501, "Not Implemented"},
73  {502, "Bad Gateway"},
74  {503, "Service Unavailable"}
75 };
76 
77 
78 /**
79  * @brief Read HTTP request header and parse its contents
80  * @param[in] connection Structure representing an HTTP connection
81  * @return Error code
82  **/
83 
85 {
86  error_t error;
87  size_t length;
88 
89  //Set the maximum time the server will wait for an HTTP
90  //request before closing the connection
91  error = socketSetTimeout(connection->socket, HTTP_SERVER_IDLE_TIMEOUT);
92  //Any error to report?
93  if(error)
94  return error;
95 
96  //Read the first line of the request
97  error = httpReceive(connection, connection->buffer,
99  //Unable to read any data?
100  if(error)
101  return error;
102 
103  //Revert to default timeout
104  error = socketSetTimeout(connection->socket, HTTP_SERVER_TIMEOUT);
105  //Any error to report?
106  if(error)
107  return error;
108 
109  //Properly terminate the string with a NULL character
110  connection->buffer[length] = '\0';
111  //Debug message
112  TRACE_INFO("%s", connection->buffer);
113 
114  //Parse the Request-Line
115  error = httpParseRequestLine(connection, connection->buffer);
116  //Any error to report?
117  if(error)
118  return error;
119 
120  //Default value for properties
121  connection->request.chunkedEncoding = FALSE;
122  connection->request.contentLength = 0;
123 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
124  connection->request.upgradeWebSocket = FALSE;
125  connection->request.connectionUpgrade = FALSE;
126  strcpy(connection->request.clientKey, "");
127 #endif
128 
129  //HTTP 0.9 does not support Full-Request
130  if(connection->request.version >= HTTP_VERSION_1_0)
131  {
132  //Local variables
133  char_t firstChar;
134  char_t *separator;
135  char_t *name;
136  char_t *value;
137 
138  //This variable is used to decode header fields that span multiple lines
139  firstChar = '\0';
140 
141  //Parse the header fields of the HTTP request
142  while(1)
143  {
144  //Decode multiple-line header field
145  error = httpReadHeaderField(connection, connection->buffer,
146  HTTP_SERVER_BUFFER_SIZE, &firstChar);
147  //Any error to report?
148  if(error)
149  return error;
150 
151  //Debug message
152  TRACE_DEBUG("%s", connection->buffer);
153 
154  //An empty line indicates the end of the header fields
155  if(!strcmp(connection->buffer, "\r\n"))
156  break;
157 
158  //Check whether a separator is present
159  separator = strchr(connection->buffer, ':');
160 
161  //Separator found?
162  if(separator != NULL)
163  {
164  //Split the line
165  *separator = '\0';
166 
167  //Trim whitespace characters
168  name = strTrimWhitespace(connection->buffer);
169  value = strTrimWhitespace(separator + 1);
170 
171  //Parse HTTP header field
172  httpParseHeaderField(connection, name, value);
173  }
174  }
175  }
176 
177  //Prepare to read the HTTP request body
178  if(connection->request.chunkedEncoding)
179  {
180  connection->request.byteCount = 0;
181  connection->request.firstChunk = TRUE;
182  connection->request.lastChunk = FALSE;
183  }
184  else
185  {
186  connection->request.byteCount = connection->request.contentLength;
187  }
188 
189  //The request header has been successfully parsed
190  return NO_ERROR;
191 }
192 
193 
194 /**
195  * @brief Parse Request-Line
196  * @param[in] connection Structure representing an HTTP connection
197  * @param[in] requestLine Pointer to the string that holds the Request-Line
198  * @return Error code
199  **/
200 
202 {
203  error_t error;
204  char_t *token;
205  char_t *p;
206  char_t *s;
207 
208  //The Request-Line begins with a method token
209  token = strtok_r(requestLine, " \r\n", &p);
210  //Unable to retrieve the method?
211  if(token == NULL)
212  return ERROR_INVALID_REQUEST;
213 
214  //The Method token indicates the method to be performed on the
215  //resource identified by the Request-URI
216  error = strSafeCopy(connection->request.method, token, HTTP_SERVER_METHOD_MAX_LEN);
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 = strtok_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 = strchr(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
244  if(strlen(s + 1) > HTTP_SERVER_QUERY_STRING_MAX_LEN)
245  return ERROR_INVALID_REQUEST;
246 
247  //Save the query string
248  strcpy(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(!strcasecmp(connection->request.uri, "/"))
265  strcpy(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 = strtok_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(!strcasecmp(token, "HTTP/1.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(!strcasecmp(token, "HTTP/1.1"))
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(!strcmp(buffer, "\r\n"))
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  * @return Error code
419  **/
420 
422  const char_t *name, char_t *value)
423 {
424  //Host header field?
425  if(!strcasecmp(name, "Host"))
426  {
427  //Save host name
428  strSafeCopy(connection->request.host, value,
430  }
431  //Connection header field?
432  else if(!strcasecmp(name, "Connection"))
433  {
434  //Parse Connection header field
435  httpParseConnectionField(connection, value);
436  }
437  //Transfer-Encoding header field?
438  else if(!strcasecmp(name, "Transfer-Encoding"))
439  {
440  //Check whether chunked encoding is used
441  if(!strcasecmp(value, "chunked"))
442  connection->request.chunkedEncoding = TRUE;
443  }
444  //Content-Type field header?
445  else if(!strcasecmp(name, "Content-Type"))
446  {
447  //Parse Content-Type header field
448  httpParseContentTypeField(connection, value);
449  }
450  //Content-Length header field?
451  else if(!strcasecmp(name, "Content-Length"))
452  {
453  //Get the length of the body data
454  connection->request.contentLength = atoi(value);
455  }
456  //Accept-Encoding field header?
457  else if(!strcasecmp(name, "Accept-Encoding"))
458  {
459  //Parse Content-Type header field
460  httpParseAcceptEncodingField(connection, value);
461  }
462  //Authorization header field?
463  else if(!strcasecmp(name, "Authorization"))
464  {
465  //Parse Authorization header field
466  httpParseAuthorizationField(connection, value);
467  }
468 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
469  //Upgrade header field?
470  else if(!strcasecmp(name, "Upgrade"))
471  {
472  //WebSocket support?
473  if(!strcasecmp(value, "websocket"))
474  connection->request.upgradeWebSocket = TRUE;
475  }
476  //Sec-WebSocket-Key header field?
477  else if(!strcasecmp(name, "Sec-WebSocket-Key"))
478  {
479  //Save the contents of the Sec-WebSocket-Key header field
480  strSafeCopy(connection->request.clientKey, value,
482  }
483 #endif
484 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
485  //Cookie header field?
486  else if(!strcasecmp(name, "Cookie"))
487  {
488  //Parse Cookie header field
489  httpParseCookieField(connection, value);
490  }
491 #endif
492 }
493 
494 
495 /**
496  * @brief Parse Connection header field
497  * @param[in] connection Structure representing an HTTP connection
498  * @param[in] value Connection field value
499  **/
500 
502  char_t *value)
503 {
504  char_t *p;
505  char_t *token;
506 
507  //Get the first value of the list
508  token = strtok_r(value, ",", &p);
509 
510  //Parse the comma-separated list
511  while(token != NULL)
512  {
513  //Trim whitespace characters
515 
516  //Check current value
517  if(!strcasecmp(value, "keep-alive"))
518  {
519  //The connection is persistent
520  connection->request.keepAlive = TRUE;
521  }
522  else if(!strcasecmp(value, "close"))
523  {
524  //The connection will be closed after completion of the response
525  connection->request.keepAlive = FALSE;
526  }
527 #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
528  else if(!strcasecmp(value, "upgrade"))
529  {
530  //Upgrade the connection
531  connection->request.connectionUpgrade = TRUE;
532  }
533 #endif
534 
535  //Get next value
536  token = strtok_r(NULL, ",", &p);
537  }
538 }
539 
540 
541 /**
542  * @brief Parse Content-Type header field
543  * @param[in] connection Structure representing an HTTP connection
544  * @param[in] value Content-Type field value
545  **/
546 
548  char_t *value)
549 {
550 #if (HTTP_SERVER_MULTIPART_TYPE_SUPPORT == ENABLED)
551  size_t n;
552  char_t *p;
553  char_t *token;
554 
555  //Retrieve type
556  token = strtok_r(value, "/", &p);
557  //Any parsing error?
558  if(token == NULL)
559  return;
560 
561  //The boundary parameter makes sense only for the multipart content-type
562  if(!strcasecmp(token, "multipart"))
563  {
564  //Skip subtype
565  token = strtok_r(NULL, ";", &p);
566  //Any parsing error?
567  if(token == NULL)
568  return;
569 
570  //Retrieve parameter name
571  token = strtok_r(NULL, "=", &p);
572  //Any parsing error?
573  if(token == NULL)
574  return;
575 
576  //Trim whitespace characters
578 
579  //Check parameter name
580  if(!strcasecmp(token, "boundary"))
581  {
582  //Retrieve parameter value
583  token = strtok_r(NULL, ";", &p);
584  //Any parsing error?
585  if(token == NULL)
586  return;
587 
588  //Trim whitespace characters
590  //Get the length of the boundary string
591  n = strlen(token);
592 
593  //Check the length of the boundary string
595  {
596  //Copy the boundary string
597  strncpy(connection->request.boundary, token, n);
598  //Properly terminate the string
599  connection->request.boundary[n] = '\0';
600 
601  //Save the length of the boundary string
602  connection->request.boundaryLength = n;
603  }
604  }
605  }
606 #endif
607 }
608 
609 
610 /**
611  * @brief Parse Accept-Encoding header field
612  * @param[in] connection Structure representing an HTTP connection
613  * @param[in] value Accept-Encoding field value
614  **/
615 
617  char_t *value)
618 {
619 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
620  char_t *p;
621  char_t *token;
622 
623  //Get the first value of the list
624  token = strtok_r(value, ",", &p);
625 
626  //Parse the comma-separated list
627  while(token != NULL)
628  {
629  //Trim whitespace characters
631 
632  //Check current value
633  if(!strcasecmp(value, "gzip"))
634  {
635  //gzip compression is supported
636  connection->request.acceptGzipEncoding = TRUE;
637  }
638 
639  //Get next value
640  token = strtok_r(NULL, ",", &p);
641  }
642 #endif
643 }
644 
645 
646 /**
647  * @brief Parse Cookie header field
648  * @param[in] connection Structure representing an HTTP connection
649  * @param[in] value Accept-Encoding field value
650  **/
651 
653 {
654 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
655  //Save the value of the header field
656  strSafeCopy(connection->request.cookie, value, HTTP_SERVER_COOKIE_MAX_LEN);
657 #endif
658 }
659 
660 
661 /**
662  * @brief Read chunk-size field from the input stream
663  * @param[in] connection Structure representing an HTTP connection
664  **/
665 
667 {
668  error_t error;
669  size_t n;
670  char_t *end;
671  char_t s[8];
672 
673  //First chunk to be received?
674  if(connection->request.firstChunk)
675  {
676  //Clear the flag
677  connection->request.firstChunk = FALSE;
678  }
679  else
680  {
681  //Read the CRLF that follows the previous chunk-data field
682  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
683  //Any error to report?
684  if(error)
685  return error;
686 
687  //Properly terminate the string with a NULL character
688  s[n] = '\0';
689 
690  //The chunk data must be terminated by CRLF
691  if(strcmp(s, "\r\n"))
692  return ERROR_WRONG_ENCODING;
693  }
694 
695  //Read the chunk-size field
696  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
697  //Any error to report?
698  if(error)
699  return error;
700 
701  //Properly terminate the string with a NULL character
702  s[n] = '\0';
703  //Remove extra whitespaces
705 
706  //Retrieve the size of the chunk
707  connection->request.byteCount = strtoul(s, &end, 16);
708 
709  //No valid conversion could be performed?
710  if(end == s || *end != '\0')
711  return ERROR_WRONG_ENCODING;
712 
713  //Any chunk whose size is zero terminates the data transfer
714  if(!connection->request.byteCount)
715  {
716  //The end of the HTTP request body has been reached
717  connection->request.lastChunk = TRUE;
718 
719  //Skip the trailer
720  while(1)
721  {
722  //Read a complete line
723  error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
724  //Unable to read any data?
725  if(error)
726  return error;
727 
728  //Properly terminate the string with a NULL character
729  s[n] = '\0';
730 
731  //The trailer is terminated by an empty line
732  if(!strcmp(s, "\r\n"))
733  break;
734  }
735  }
736 
737  //Successful processing
738  return NO_ERROR;
739 }
740 
741 
742 /**
743  * @brief Initialize response header
744  * @param[in] connection Structure representing an HTTP connection
745  **/
746 
748 {
749  //Default HTTP header fields
750  connection->response.version = connection->request.version;
751  connection->response.statusCode = 200;
752  connection->response.noCache = FALSE;
753  connection->response.maxAge = 0;
754  connection->response.location = NULL;
755  connection->response.contentType = mimeGetType(connection->request.uri);
756  connection->response.chunkedEncoding = TRUE;
757 
758 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
759  //Do not use gzip encoding
760  connection->response.gzipEncoding = FALSE;
761 #endif
762 
763 #if (HTTP_SERVER_PERSISTENT_CONN_SUPPORT == ENABLED)
764  //Persistent connections are accepted
765  connection->response.keepAlive = connection->request.keepAlive;
766 #else
767  //Connections are not persistent by default
768  connection->response.keepAlive = FALSE;
769 #endif
770 }
771 
772 
773 /**
774  * @brief Format HTTP response header
775  * @param[in] connection Structure representing an HTTP connection
776  * @param[out] buffer Pointer to the buffer where to format the HTTP header
777  * @return Error code
778  **/
779 
781 {
782  uint_t i;
783  char_t *p;
784 
785  //HTTP version 0.9?
786  if(connection->response.version == HTTP_VERSION_0_9)
787  {
788  //Enforce default parameters
789  connection->response.keepAlive = FALSE;
790  connection->response.chunkedEncoding = FALSE;
791  //The size of the response body is not limited
792  connection->response.byteCount = UINT_MAX;
793  //We are done since HTTP 0.9 does not support Full-Response format
794  return NO_ERROR;
795  }
796 
797  //When generating dynamic web pages with HTTP 1.0, the only way to
798  //signal the end of the body is to close the connection
799  if(connection->response.version == HTTP_VERSION_1_0 &&
800  connection->response.chunkedEncoding)
801  {
802  //Make the connection non persistent
803  connection->response.keepAlive = FALSE;
804  connection->response.chunkedEncoding = FALSE;
805  //The size of the response body is not limited
806  connection->response.byteCount = UINT_MAX;
807  }
808  else
809  {
810  //Limit the size of the response body
811  connection->response.byteCount = connection->response.contentLength;
812  }
813 
814  //Point to the beginning of the buffer
815  p = buffer;
816 
817  //The first line of a response message is the Status-Line, consisting
818  //of the protocol version followed by a numeric status code and its
819  //associated textual phrase
820  p += sprintf(p, "HTTP/%u.%u %u ", MSB(connection->response.version),
821  LSB(connection->response.version), connection->response.statusCode);
822 
823  //Retrieve the Reason-Phrase that corresponds to the Status-Code
824  for(i = 0; i < arraysize(statusCodeList); i++)
825  {
826  //Check the status code
827  if(statusCodeList[i].value == connection->response.statusCode)
828  {
829  //Append the textual phrase to the Status-Line
830  p += sprintf(p, statusCodeList[i].message);
831  //Break the loop and continue processing
832  break;
833  }
834  }
835 
836  //Properly terminate the Status-Line
837  p += sprintf(p, "\r\n");
838 
839  //Valid location?
840  if(connection->response.location != NULL)
841  {
842  //Set Location field
843  p += sprintf(p, "Location: %s\r\n", connection->response.location);
844  }
845 
846  //Persistent connection?
847  if(connection->response.keepAlive)
848  {
849  //Set Connection field
850  p += sprintf(p, "Connection: keep-alive\r\n");
851 
852  //Set Keep-Alive field
853  p += sprintf(p, "Keep-Alive: timeout=%u, max=%u\r\n",
855  }
856  else
857  {
858  //Set Connection field
859  p += sprintf(p, "Connection: close\r\n");
860  }
861 
862  //Specify the caching policy
863  if(connection->response.noCache)
864  {
865  //Set Pragma field
866  p += sprintf(p, "Pragma: no-cache\r\n");
867  //Set Cache-Control field
868  p += sprintf(p, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
869  p += sprintf(p, "Cache-Control: max-age=0, post-check=0, pre-check=0\r\n");
870  }
871  else if(connection->response.maxAge != 0)
872  {
873  //Set Cache-Control field
874  p += sprintf(p, "Cache-Control: max-age=%u\r\n", connection->response.maxAge);
875  }
876 
877 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
878  //Check whether authentication is required
879  if(connection->response.auth.mode != HTTP_AUTH_MODE_NONE)
880  {
881  //Add WWW-Authenticate header field
882  p += httpAddAuthenticateField(connection, p);
883  }
884 #endif
885 
886 #if (HTTP_SERVER_COOKIE_SUPPORT == ENABLED)
887  //Valid cookie
888  if(connection->response.setCookie[0] != '\0')
889  {
890  //Add Set-Cookie header field
891  p += sprintf(p, "Set-Cookie: %s\r\n", connection->response.setCookie);
892  }
893 #endif
894 
895  //Valid content type?
896  if(connection->response.contentType != NULL)
897  {
898  //Content type
899  p += sprintf(p, "Content-Type: %s\r\n", connection->response.contentType);
900  }
901 
902 #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED)
903  //Use gzip encoding?
904  if(connection->response.gzipEncoding)
905  {
906  //Set Transfer-Encoding field
907  p += sprintf(p, "Content-Encoding: gzip\r\n");
908  }
909 #endif
910 
911  //Use chunked encoding transfer?
912  if(connection->response.chunkedEncoding)
913  {
914  //Set Transfer-Encoding field
915  p += sprintf(p, "Transfer-Encoding: chunked\r\n");
916  }
917  //Persistent connection?
918  else if(connection->response.keepAlive)
919  {
920  //Set Content-Length field
921  p += sprintf(p, "Content-Length: %" PRIuSIZE "\r\n", connection->response.contentLength);
922  }
923 
924  //Terminate the header with an empty line
925  p += sprintf(p, "\r\n");
926 
927  //Successful processing
928  return NO_ERROR;
929 }
930 
931 
932 /**
933  * @brief Send data to the client
934  * @param[in] connection Structure representing an HTTP connection
935  * @param[in] data Pointer to a buffer containing the data to be transmitted
936  * @param[in] length Number of bytes to be transmitted
937  * @param[in] flags Set of flags that influences the behavior of this function
938  **/
939 
941  const void *data, size_t length, uint_t flags)
942 {
943 #if (NET_RTOS_SUPPORT == ENABLED)
944  error_t error;
945 
946 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
947  //Check whether a secure connection is being used
948  if(connection->tlsContext != NULL)
949  {
950  //Use TLS to transmit data to the client
951  error = tlsWrite(connection->tlsContext, data, length, NULL, flags);
952  }
953  else
954 #endif
955  {
956  //Transmit data to the client
957  error = socketSend(connection->socket, data, length, NULL, flags);
958  }
959 
960  //Return status code
961  return error;
962 #else
963  //Prevent buffer overflow
964  if((connection->bufferLen + length) > HTTP_SERVER_BUFFER_SIZE)
965  return ERROR_BUFFER_OVERFLOW;
966 
967  //Copy user data
968  memcpy(connection->buffer + connection->bufferLen, data, length);
969  //Adjust the length of the buffer
970  connection->bufferLen += length;
971 
972  //Successful processing
973  return NO_ERROR;
974 #endif
975 }
976 
977 
978 /**
979  * @brief Receive data from the client
980  * @param[in] connection Structure representing an HTTP connection
981  * @param[out] data Buffer into which received data will be placed
982  * @param[in] size Maximum number of bytes that can be received
983  * @param[out] received Actual number of bytes that have been received
984  * @param[in] flags Set of flags that influences the behavior of this function
985  * @return Error code
986  **/
987 
989  void *data, size_t size, size_t *received, uint_t flags)
990 {
991 #if (NET_RTOS_SUPPORT == ENABLED)
992  error_t error;
993 
994 #if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
995  //Check whether a secure connection is being used
996  if(connection->tlsContext != NULL)
997  {
998  //Use TLS to receive data from the client
999  error = tlsRead(connection->tlsContext, data, size, received, flags);
1000  }
1001  else
1002 #endif
1003  {
1004  //Receive data from the client
1005  error = socketReceive(connection->socket, data, size, received, flags);
1006  }
1007 
1008  //Return status code
1009  return error;
1010 #else
1011  error_t error;
1012  char_t c;
1013  size_t i;
1014  size_t n;
1015 
1016  //Number of data bytes that are pending in the receive buffer
1017  n = connection->bufferLen - connection->bufferPos;
1018 
1019  //Any data to be copied?
1020  if(n > 0)
1021  {
1022  //Limit the number of bytes to read at a time
1023  n = MIN(n, size);
1024 
1025  //The HTTP_FLAG_BREAK_CHAR flag causes the function to stop reading
1026  //data as soon as the specified break character is encountered
1028  {
1029  //Retrieve the break character code
1030  c = LSB(flags);
1031 
1032  //Search for the specified break character
1033  for(i = 0; i < n && connection->buffer[connection->bufferPos + i] != c; i++);
1034 
1035  //Adjust the number of data to read
1036  n = MIN(n, i + 1);
1037  }
1038 
1039  //Copy data to user buffer
1040  memcpy(data, connection->buffer + connection->bufferPos, n);
1041 
1042  //Advance current position
1043  connection->bufferPos += n;
1044  //Total number of data that have been read
1045  *received = n;
1046 
1047  //Successful processing
1048  error = NO_ERROR;
1049  }
1050  else
1051  {
1052  //No more data available...
1053  error = ERROR_END_OF_STREAM;
1054  }
1055 
1056  //Return status code
1057  return error;
1058 #endif
1059 }
1060 
1061 
1062 /**
1063  * @brief Retrieve the full pathname to the specified resource
1064  * @param[in] connection Structure representing an HTTP connection
1065  * @param[in] relative String containing the relative path to the resource
1066  * @param[out] absolute Resulting string containing the absolute path
1067  * @param[in] maxLen Maximum acceptable path length
1068  **/
1069 
1071  const char_t *relative, char_t *absolute, size_t maxLen)
1072 {
1073  //Copy the root directory
1074  strcpy(absolute, connection->settings->rootDirectory);
1075 
1076  //Append the specified path
1077  pathCombine(absolute, relative, maxLen);
1078 
1079  //Clean the resulting path
1080  pathCanonicalize(absolute);
1081 }
1082 
1083 
1084 /**
1085  * @brief Compare filename extension
1086  * @param[in] filename Filename whose extension is to be checked
1087  * @param[in] extension String defining the extension to be checked
1088  * @return TRUE is the filename matches the given extension, else FALSE
1089  **/
1090 
1091 bool_t httpCompExtension(const char_t *filename, const char_t *extension)
1092 {
1093  uint_t n;
1094  uint_t m;
1095 
1096  //Get the length of the specified filename
1097  n = strlen(filename);
1098  //Get the length of the extension
1099  m = strlen(extension);
1100 
1101  //Check the length of the filename
1102  if(n < m)
1103  return FALSE;
1104 
1105  //Compare extensions
1106  if(!strncasecmp(filename + n - m, extension, m))
1107  return TRUE;
1108  else
1109  return FALSE;
1110 }
1111 
1112 
1113 /**
1114  * @brief Decode a percent-encoded string
1115  * @param[in] input NULL-terminated string to be decoded
1116  * @param[out] output NULL-terminated string resulting from the decoding process
1117  * @param[in] outputSize Size of the output buffer in bytes
1118  * @return Error code
1119  **/
1120 
1122  char_t *output, size_t outputSize)
1123 {
1124  size_t i;
1125  char_t buffer[3];
1126 
1127  //Check parameters
1128  if(input == NULL || output == NULL)
1129  return ERROR_INVALID_PARAMETER;
1130 
1131  //Decode the percent-encoded string
1132  for(i = 0; *input != '\0' && i < outputSize; i++)
1133  {
1134  //Check current character
1135  if(*input == '+')
1136  {
1137  //Replace '+' characters with spaces
1138  output[i] = ' ';
1139  //Advance data pointer
1140  input++;
1141  }
1142  else if(input[0] == '%' && input[1] != '\0' && input[2] != '\0')
1143  {
1144  //Process percent-encoded characters
1145  buffer[0] = input[1];
1146  buffer[1] = input[2];
1147  buffer[2] = '\0';
1148  //String to integer conversion
1149  output[i] = (uint8_t) strtoul(buffer, NULL, 16);
1150  //Advance data pointer
1151  input += 3;
1152  }
1153  else
1154  {
1155  //Copy any other characters
1156  output[i] = *input;
1157  //Advance data pointer
1158  input++;
1159  }
1160  }
1161 
1162  //Check whether the output buffer runs out of space
1163  if(i >= outputSize)
1164  return ERROR_FAILURE;
1165 
1166  //Properly terminate the resulting string
1167  output[i] = '\0';
1168  //Successful processing
1169  return NO_ERROR;
1170 }
1171 
1172 
1173 /**
1174  * @brief Convert byte array to hex string
1175  * @param[in] input Point to the byte array
1176  * @param[in] inputLen Length of the byte array
1177  * @param[out] output NULL-terminated string resulting from the conversion
1178  * @return Error code
1179  **/
1180 
1181 void httpConvertArrayToHexString(const uint8_t *input,
1182  size_t inputLen, char_t *output)
1183 {
1184  size_t i;
1185 
1186  //Hex conversion table
1187  static const char_t hexDigit[16] =
1188  {
1189  '0', '1', '2', '3', '4', '5', '6', '7',
1190  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
1191  };
1192 
1193  //Process byte array
1194  for(i = 0; i < inputLen; i++)
1195  {
1196  //Convert upper nibble
1197  output[i * 2] = hexDigit[(input[i] >> 4) & 0x0F];
1198  //Then convert lower nibble
1199  output[i * 2 + 1] = hexDigit[input[i] & 0x0F];
1200  }
1201 
1202  //Properly terminate the string with a NULL character
1203  output[i * 2] = '\0';
1204 }
1205 
1206 #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:514
Path manipulation helper functions.
uint8_t length
Definition: dtls_misc.h:149
String manipulation helper functions.
error_t httpFormatResponseHeader(HttpConnection *connection, char_t *buffer)
Format HTTP response header.
int bool_t
Definition: compiler_port.h:49
@ SOCKET_FLAG_WAIT_ALL
Definition: socket.h:120
#define HTTP_SERVER_COOKIE_MAX_LEN
Definition: http_server.h:265
HTTP server (HyperText Transfer Protocol)
#define WEB_SOCKET_CLIENT_KEY_SIZE
Definition: web_socket.h:171
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:140
uint8_t p
Definition: ndp.h:298
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:117
#define HTTP_SERVER_TIMEOUT
Definition: http_server.h:131
#define TRUE
Definition: os_port.h:50
#define strncasecmp
#define HTTP_SERVER_HOST_MAX_LEN
Definition: http_server.h:202
@ HTTP_VERSION_0_9
Definition: http_common.h:61
char_t name[]
#define strtok_r(str, delim, p)
void httpParseConnectionField(HttpConnection *connection, char_t *value)
Parse Connection header field.
#define HTTP_SERVER_URI_MAX_LEN
Definition: http_server.h:188
@ ERROR_END_OF_STREAM
Definition: error.h:208
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:153
void pathCanonicalize(char_t *path)
Simplify a path.
Definition: path.c:112
@ 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.
@ HTTP_FLAG_BREAK_CHAR
Definition: http_common.h:98
char_t filename[]
Definition: tftp_common.h:91
error_t
Error codes.
Definition: error.h:42
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:609
#define HttpConnection
Definition: http_server.h:318
HTTP status code.
Definition: http_server.h:417
@ 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:139
HTTP server (miscellaneous functions)
void httpParseCookieField(HttpConnection *connection, char_t *value)
Parse Cookie header field.
#define MSB(x)
Definition: os_port.h:58
#define TRACE_INFO(...)
Definition: debug.h:94
#define LSB(x)
Definition: os_port.h:54
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:1798
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:62
size_t httpAddAuthenticateField(HttpConnection *connection, char_t *output)
Format WWW-Authenticate header field.
uint8_t flags
Definition: tcp.h:314
#define TRACE_DEBUG(...)
Definition: debug.h:106
HTTP authentication.
char char_t
Definition: compiler_port.h:43
error_t strSafeCopy(char_t *dest, const char_t *src, size_t destSize)
Copy string.
Definition: str.c:167
uint8_t m
Definition: ndp.h:302
uint8_t n
@ HTTP_VERSION_1_1
Definition: http_common.h:63
#define HTTP_SERVER_QUERY_STRING_MAX_LEN
Definition: http_server.h:195
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:181
#define strcasecmp
uint8_t s
uint8_t message[]
Definition: chap.h:152
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:1663
error_t httpReadRequestHeader(HttpConnection *connection)
Read HTTP request header and parse its contents.
@ ERROR_WRONG_ENCODING
Definition: error.h:120
#define HTTP_SERVER_BUFFER_SIZE
Definition: http_server.h:160
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.
uint8_t value[]
Definition: dtls_misc.h:150
#define PRIuSIZE
Definition: compiler_port.h:78
unsigned int uint_t
Definition: compiler_port.h:45
TCP/IP stack core.
uint8_t data[]
Definition: dtls_misc.h:176
@ HTTP_AUTH_MODE_NONE
Definition: http_common.h:73
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:219
@ SOCKET_FLAG_BREAK_CRLF
Definition: socket.h:123
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:258
@ NO_ERROR
Success.
Definition: error.h:44
uint8_t c
Definition: ndp.h:513
Debugging facilities.
uint8_t token[]
Definition: coap_common.h:183
#define arraysize(a)
Definition: os_port.h:70
void pathCombine(char_t *path, const char_t *more, size_t maxLen)
Concatenate two paths.
Definition: path.c:332
void httpInitResponseHeader(HttpConnection *connection)
Initialize response header.