Webserver+3d print

Dependents:   Nucleo

Revision:
0:8918a71cdbe9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cyclone_tcp/http/http_server_misc.c	Sat Feb 04 18:15:49 2017 +0000
@@ -0,0 +1,1070 @@
+/**
+ * @file http_server_misc.c
+ * @brief HTTP server (miscellaneous functions)
+ *
+ * @section License
+ *
+ * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved.
+ *
+ * This file is part of CycloneTCP Open.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * @author Oryx Embedded SARL (www.oryx-embedded.com)
+ * @version 1.7.6
+ **/
+
+//Switch to the appropriate trace level
+#define TRACE_LEVEL HTTP_TRACE_LEVEL
+
+//Dependencies
+#include <stdlib.h>
+#include <limits.h>
+#include "core/net.h"
+#include "http/http_server.h"
+#include "http/http_server_auth.h"
+#include "http/http_server_misc.h"
+#include "http/mime.h"
+#include "str.h"
+#include "path.h"
+#include "debug.h"
+
+//Check TCP/IP stack configuration
+#if (HTTP_SERVER_SUPPORT == ENABLED)
+
+
+/**
+ * @brief HTTP status codes
+ **/
+
+static const HttpStatusCodeDesc statusCodeList[] =
+{
+   //Success
+   {200, "OK"},
+   {201, "Created"},
+   {202, "Accepted"},
+   {204, "No Content"},
+   //Redirection
+   {301, "Moved Permanently"},
+   {302, "Found"},
+   {304, "Not Modified"},
+   //Client error
+   {400, "Bad Request"},
+   {401, "Unauthorized"},
+   {403, "Forbidden"},
+   {404, "Not Found"},
+   //Server error
+   {500, "Internal Server Error"},
+   {501, "Not Implemented"},
+   {502, "Bad Gateway"},
+   {503, "Service Unavailable"}
+};
+
+
+/**
+ * @brief Read HTTP request header and parse its contents
+ * @param[in] connection Structure representing an HTTP connection
+ * @return Error code
+ **/
+
+error_t httpReadRequestHeader(HttpConnection *connection)
+{
+   error_t error;
+   size_t length;
+
+   //Set the maximum time the server will wait for an HTTP
+   //request before closing the connection
+   error = socketSetTimeout(connection->socket, HTTP_SERVER_IDLE_TIMEOUT);
+   //Any error to report?
+   if(error)
+      return error;
+
+   //Read the first line of the request
+   error = httpReceive(connection, connection->buffer,
+      HTTP_SERVER_BUFFER_SIZE - 1, &length, SOCKET_FLAG_BREAK_CRLF);
+   //Unable to read any data?
+   if(error)
+      return error;
+
+   //Revert to default timeout
+   error = socketSetTimeout(connection->socket, HTTP_SERVER_TIMEOUT);
+   //Any error to report?
+   if(error)
+      return error;
+
+   //Properly terminate the string with a NULL character
+   connection->buffer[length] = '\0';
+   //Debug message
+   TRACE_INFO("%s", connection->buffer);
+
+   //Parse the Request-Line
+   error = httpParseRequestLine(connection, connection->buffer);
+   //Any error to report?
+   if(error)
+      return error;
+
+   //Default value for properties
+   connection->request.chunkedEncoding = FALSE;
+   connection->request.contentLength = 0;
+#if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
+   connection->request.upgradeWebSocket = FALSE;
+   connection->request.connectionUpgrade = FALSE;
+   strcpy(connection->request.clientKey, "");
+#endif
+
+   //HTTP 0.9 does not support Full-Request
+   if(connection->request.version >= HTTP_VERSION_1_0)
+   {
+      //Local variables
+      char_t firstChar;
+      char_t *separator;
+      char_t *name;
+      char_t *value;
+
+      //This variable is used to decode header fields that span multiple lines
+      firstChar = '\0';
+
+      //Parse the header fields of the HTTP request
+      while(1)
+      {
+         //Decode multiple-line header field
+         error = httpReadHeaderField(connection, connection->buffer,
+            HTTP_SERVER_BUFFER_SIZE, &firstChar);
+         //Any error to report?
+         if(error)
+            return error;
+
+         //Debug message
+         TRACE_DEBUG("%s", connection->buffer);
+
+         //An empty line indicates the end of the header fields
+         if(!strcmp(connection->buffer, "\r\n"))
+            break;
+
+         //Check whether a separator is present
+         separator = strchr(connection->buffer, ':');
+
+         //Separator found?
+         if(separator != NULL)
+         {
+            //Split the line
+            *separator = '\0';
+
+            //Trim whitespace characters
+            name = strTrimWhitespace(connection->buffer);
+            value = strTrimWhitespace(separator + 1);
+
+            //Parse HTTP header field
+            httpParseHeaderField(connection, name, value);
+         }
+      }
+   }
+
+   //Prepare to read the HTTP request body
+   if(connection->request.chunkedEncoding)
+   {
+      connection->request.byteCount = 0;
+      connection->request.firstChunk = TRUE;
+      connection->request.lastChunk = FALSE;
+   }
+   else
+   {
+      connection->request.byteCount = connection->request.contentLength;
+   }
+
+   //The request header has been successfully parsed
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Parse Request-Line
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[in] requestLine Pointer to the string that holds the Request-Line
+ * @return Error code
+ **/
+
+error_t httpParseRequestLine(HttpConnection *connection, char_t *requestLine)
+{
+   error_t error;
+   char_t *token;
+   char_t *p;
+   char_t *s;
+
+   //The Request-Line begins with a method token
+   token = strtok_r(requestLine, " \r\n", &p);
+   //Unable to retrieve the method?
+   if(token == NULL)
+      return ERROR_INVALID_REQUEST;
+
+   //The Method token indicates the method to be performed on the
+   //resource identified by the Request-URI
+   error = strSafeCopy(connection->request.method, token, HTTP_SERVER_METHOD_MAX_LEN);
+   //Any error to report?
+   if(error)
+      return ERROR_INVALID_REQUEST;
+
+   //The Request-URI is following the method token
+   token = strtok_r(NULL, " \r\n", &p);
+   //Unable to retrieve the Request-URI?
+   if(token == NULL)
+      return ERROR_INVALID_REQUEST;
+
+   //Check whether a query string is present
+   s = strchr(token, '?');
+
+   //Query string found?
+   if(s != NULL)
+   {
+      //Split the string
+      *s = '\0';
+
+      //Save the Request-URI
+      error = httpDecodePercentEncodedString(token,
+         connection->request.uri, HTTP_SERVER_URI_MAX_LEN);
+      //Any error to report?
+      if(error)
+         return ERROR_INVALID_REQUEST;
+
+      //Check the length of the query string
+      if(strlen(s + 1) > HTTP_SERVER_QUERY_STRING_MAX_LEN)
+         return ERROR_INVALID_REQUEST;
+
+      //Save the query string
+      strcpy(connection->request.queryString, s + 1);
+   }
+   else
+   {
+      //Save the Request-URI
+      error = httpDecodePercentEncodedString(token,
+         connection->request.uri, HTTP_SERVER_URI_MAX_LEN);
+      //Any error to report?
+      if(error)
+         return ERROR_INVALID_REQUEST;
+
+      //No query string
+      connection->request.queryString[0] = '\0';
+   }
+
+   //Redirect to the default home page if necessary
+   if(!strcasecmp(connection->request.uri, "/"))
+      strcpy(connection->request.uri, connection->settings->defaultDocument);
+
+   //Clean the resulting path
+   pathCanonicalize(connection->request.uri);
+
+   //The protocol version is following the Request-URI
+   token = strtok_r(NULL, " \r\n", &p);
+
+   //HTTP version 0.9?
+   if(token == NULL)
+   {
+      //Save version number
+      connection->request.version = HTTP_VERSION_0_9;
+      //Persistent connections are not supported
+      connection->request.keepAlive = FALSE;
+   }
+   //HTTP version 1.0?
+   else if(!strcasecmp(token, "HTTP/1.0"))
+   {
+      //Save version number
+      connection->request.version = HTTP_VERSION_1_0;
+      //By default connections are not persistent
+      connection->request.keepAlive = FALSE;
+   }
+   //HTTP version 1.1?
+   else if(!strcasecmp(token, "HTTP/1.1"))
+   {
+      //Save version number
+      connection->request.version = HTTP_VERSION_1_1;
+      //HTTP 1.1 makes persistent connections the default
+      connection->request.keepAlive = TRUE;
+   }
+   //HTTP version not supported?
+   else
+   {
+      //Report an error
+      return ERROR_INVALID_REQUEST;
+   }
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Read multiple-line header field
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[out] buffer Buffer where to store the header field
+ * @param[in] size Size of the buffer, in bytes
+ * @param[in,out] firstChar Leading character of the header line
+ * @return Error code
+ **/
+
+error_t httpReadHeaderField(HttpConnection *connection,
+   char_t *buffer, size_t size, char_t *firstChar)
+{
+   error_t error;
+   size_t n;
+   size_t length;
+
+   //This is the actual length of the header field
+   length = 0;
+
+   //The process of moving from a multiple-line representation of a header
+   //field to its single line representation is called unfolding
+   do
+   {
+      //Check the length of the header field
+      if((length + 1) >= size)
+      {
+         //Report an error
+         error = ERROR_INVALID_REQUEST;
+         //Exit immediately
+         break;
+      }
+
+      //NULL character found?
+      if(*firstChar == '\0')
+      {
+         //Prepare to decode the first header field
+         length = 0;
+      }
+      //LWSP character found?
+      else if(*firstChar == ' ' || *firstChar == '\t')
+      {
+         //Unfolding is accomplished by regarding CRLF immediately
+         //followed by a LWSP as equivalent to the LWSP character
+         buffer[length] = *firstChar;
+         //The current header field spans multiple lines
+         length++;
+      }
+      //Any other character?
+      else
+      {
+         //Restore the very first character of the header field
+         buffer[0] = *firstChar;
+         //Prepare to decode a new header field
+         length = 1;
+      }
+
+      //Read data until a CLRF character is encountered
+      error = httpReceive(connection, buffer + length,
+         size - 1 - length, &n, SOCKET_FLAG_BREAK_CRLF);
+      //Any error to report?
+      if(error)
+         break;
+
+      //Update the length of the header field
+      length += n;
+      //Properly terminate the string with a NULL character
+      buffer[length] = '\0';
+
+      //An empty line indicates the end of the header fields
+      if(!strcmp(buffer, "\r\n"))
+         break;
+
+      //Read the next character to detect if the CRLF is immediately
+      //followed by a LWSP character
+      error = httpReceive(connection, firstChar,
+         sizeof(char_t), &n, SOCKET_FLAG_WAIT_ALL);
+      //Any error to report?
+      if(error)
+         break;
+
+      //LWSP character found?
+      if(*firstChar == ' ' || *firstChar == '\t')
+      {
+         //CRLF immediately followed by LWSP as equivalent to the LWSP character
+         if(length >= 2)
+         {
+            if(buffer[length - 2] == '\r' || buffer[length - 1] == '\n')
+            {
+               //Remove trailing CRLF sequence
+               length -= 2;
+               //Properly terminate the string with a NULL character
+               buffer[length] = '\0';
+            }
+         }
+      }
+
+      //A header field may span multiple lines...
+   } while(*firstChar == ' ' || *firstChar == '\t');
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Parse HTTP header field
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[in] name Name of the header field
+ * @param[in] value Value of the header field
+ * @return Error code
+ **/
+
+void httpParseHeaderField(HttpConnection *connection,
+   const char_t *name, char_t *value)
+{
+   //Host header field?
+   if(!strcasecmp(name, "Host"))
+   {
+      //Save host name
+      strSafeCopy(connection->request.host, value,
+         HTTP_SERVER_HOST_MAX_LEN);
+   }
+   //Connection header field?
+   else if(!strcasecmp(name, "Connection"))
+   {
+      //Parse Connection header field
+      httpParseConnectionField(connection, value);
+   }
+   //Transfer-Encoding header field?
+   else if(!strcasecmp(name, "Transfer-Encoding"))
+   {
+      //Check whether chunked encoding is used
+      if(!strcasecmp(value, "chunked"))
+         connection->request.chunkedEncoding = TRUE;
+   }
+   //Content-Type field header?
+   else if(!strcasecmp(name, "Content-Type"))
+   {
+      //Parse Content-Type header field
+      httpParseContentTypeField(connection, value);
+   }
+   //Content-Length header field?
+   else if(!strcasecmp(name, "Content-Length"))
+   {
+      //Get the length of the body data
+      connection->request.contentLength = atoi(value);
+   }
+   //Authorization header field?
+   else if(!strcasecmp(name, "Authorization"))
+   {
+      //Parse Authorization header field
+      httpParseAuthorizationField(connection, value);
+   }
+#if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
+   //Upgrade header field?
+   else if(!strcasecmp(name, "Upgrade"))
+   {
+      //WebSocket support?
+      if(!strcasecmp(value, "websocket"))
+         connection->request.upgradeWebSocket = TRUE;
+   }
+   //Sec-WebSocket-Key header field?
+   else if(!strcasecmp(name, "Sec-WebSocket-Key"))
+   {
+      //Save the contents of the Sec-WebSocket-Key header field
+      strSafeCopy(connection->request.clientKey, value,
+         WEB_SOCKET_CLIENT_KEY_SIZE + 1);
+   }
+#endif
+}
+
+
+/**
+ * @brief Parse Connection header field
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[in] value Content-Type field value
+ **/
+
+void httpParseConnectionField(HttpConnection *connection,
+   char_t *value)
+{
+   char_t *p;
+   char_t *token;
+
+   //Get the first value of the list
+   token = strtok_r(value, ",", &p);
+
+   //Parse the comma-separated list
+   while(token != NULL)
+   {
+      //Trim whitespace characters
+      value = strTrimWhitespace(token);
+
+      //Check current value
+      if(!strcasecmp(value, "keep-alive"))
+      {
+         //The connection is persistent
+         connection->request.keepAlive = TRUE;
+      }
+      else if(!strcasecmp(value, "close"))
+      {
+         //The connection will be closed after completion of the response
+         connection->request.keepAlive = FALSE;
+      }
+#if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED)
+      else if(!strcasecmp(value, "upgrade"))
+      {
+         //Upgrade the connection
+         connection->request.connectionUpgrade = TRUE;
+      }
+#endif
+
+      //Get next value
+      token = strtok_r(NULL, ",", &p);
+   }
+}
+
+
+/**
+ * @brief Parse Content-Type header field
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[in] value Content-Type field value
+ **/
+
+void httpParseContentTypeField(HttpConnection *connection,
+   char_t *value)
+{
+#if (HTTP_SERVER_MULTIPART_TYPE_SUPPORT == ENABLED)
+   size_t n;
+   char_t *p;
+   char_t *token;
+
+   //Retrieve type
+   token = strtok_r(value, "/", &p);
+   //Any parsing error?
+   if(token == NULL)
+      return;
+
+   //The boundary parameter makes sense only for the multipart content-type
+   if(!strcasecmp(token, "multipart"))
+   {
+      //Skip subtype
+      token = strtok_r(NULL, ";", &p);
+      //Any parsing error?
+      if(token == NULL)
+         return;
+
+      //Retrieve parameter name
+      token = strtok_r(NULL, "=", &p);
+      //Any parsing error?
+      if(token == NULL)
+         return;
+
+      //Trim whitespace characters
+      token = strTrimWhitespace(token);
+
+      //Check parameter name
+      if(!strcasecmp(token, "boundary"))
+      {
+         //Retrieve parameter value
+         token = strtok_r(NULL, ";", &p);
+         //Any parsing error?
+         if(token == NULL)
+            return;
+
+         //Trim whitespace characters
+         token = strTrimWhitespace(token);
+         //Get the length of the boundary string
+         n = strlen(token);
+
+         //Check the length of the boundary string
+         if(n < HTTP_SERVER_BOUNDARY_MAX_LEN)
+         {
+            //Copy the boundary string
+            strncpy(connection->request.boundary, token, n);
+            //Properly terminate the string
+            connection->request.boundary[n] = '\0';
+
+            //Save the length of the boundary string
+            connection->request.boundaryLength = n;
+         }
+      }
+   }
+#endif
+}
+
+
+/**
+ * @brief Read chunk-size field from the input stream
+ * @param[in] connection Structure representing an HTTP connection
+ **/
+
+error_t httpReadChunkSize(HttpConnection *connection)
+{
+   error_t error;
+   size_t n;
+   char_t *end;
+   char_t s[8];
+
+   //First chunk to be received?
+   if(connection->request.firstChunk)
+   {
+      //Clear the flag
+      connection->request.firstChunk = FALSE;
+   }
+   else
+   {
+      //Read the CRLF that follows the previous chunk-data field
+      error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
+      //Any error to report?
+      if(error)
+         return error;
+
+      //Properly terminate the string with a NULL character
+      s[n] = '\0';
+
+      //The chunk data must be terminated by CRLF
+      if(strcmp(s, "\r\n"))
+         return ERROR_WRONG_ENCODING;
+   }
+
+   //Read the chunk-size field
+   error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
+   //Any error to report?
+   if(error)
+      return error;
+
+   //Properly terminate the string with a NULL character
+   s[n] = '\0';
+   //Remove extra whitespaces
+   strRemoveTrailingSpace(s);
+
+   //Retrieve the size of the chunk
+   connection->request.byteCount = strtoul(s, &end, 16);
+
+   //No valid conversion could be performed?
+   if(end == s || *end != '\0')
+      return ERROR_WRONG_ENCODING;
+
+   //Any chunk whose size is zero terminates the data transfer
+   if(!connection->request.byteCount)
+   {
+      //The end of the HTTP request body has been reached
+      connection->request.lastChunk = TRUE;
+
+      //Skip the trailer
+      while(1)
+      {
+         //Read a complete line
+         error = httpReceive(connection, s, sizeof(s) - 1, &n, SOCKET_FLAG_BREAK_CRLF);
+         //Unable to read any data?
+         if(error)
+            return error;
+
+         //Properly terminate the string with a NULL character
+         s[n] = '\0';
+
+         //The trailer is terminated by an empty line
+         if(!strcmp(s, "\r\n"))
+            break;
+      }
+   }
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Initialize response header
+ * @param[in] connection Structure representing an HTTP connection
+ **/
+
+void httpInitResponseHeader(HttpConnection *connection)
+{
+   //Default HTTP header fields
+   connection->response.version = connection->request.version;
+   connection->response.statusCode = 200;
+   connection->response.noCache = FALSE;
+   connection->response.maxAge = 0;
+   connection->response.location = NULL;
+   connection->response.contentType = mimeGetType(connection->request.uri);
+   connection->response.chunkedEncoding = TRUE;
+
+#if (HTTP_SERVER_PERSISTENT_CONN_SUPPORT == ENABLED)
+   //Persistent connections are accepted
+   connection->response.keepAlive = connection->request.keepAlive;
+#else
+   //Connections are not persistent by default
+   connection->response.keepAlive = FALSE;
+#endif
+}
+
+
+/**
+ * @brief Format HTTP response header
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[out] buffer Pointer to the buffer where to format the HTTP header
+ * @return Error code
+ **/
+
+error_t httpFormatResponseHeader(HttpConnection *connection, char_t *buffer)
+{
+   uint_t i;
+   char_t *p;
+
+   //HTTP version 0.9?
+   if(connection->response.version == HTTP_VERSION_0_9)
+   {
+      //Enforce default parameters
+      connection->response.keepAlive = FALSE;
+      connection->response.chunkedEncoding = FALSE;
+      //The size of the response body is not limited
+      connection->response.byteCount = UINT_MAX;
+      //We are done since HTTP 0.9 does not support Full-Response format
+      return NO_ERROR;
+   }
+
+   //When generating dynamic web pages with HTTP 1.0, the only way to
+   //signal the end of the body is to close the connection
+   if(connection->response.version == HTTP_VERSION_1_0 &&
+      connection->response.chunkedEncoding)
+   {
+      //Make the connection non persistent
+      connection->response.keepAlive = FALSE;
+      connection->response.chunkedEncoding = FALSE;
+      //The size of the response body is not limited
+      connection->response.byteCount = UINT_MAX;
+   }
+   else
+   {
+      //Limit the size of the response body
+      connection->response.byteCount = connection->response.contentLength;
+   }
+
+   //Point to the beginning of the buffer
+   p = buffer;
+
+   //The first line of a response message is the Status-Line, consisting
+   //of the protocol version followed by a numeric status code and its
+   //associated textual phrase
+   p += sprintf(p, "HTTP/%u.%u %u ", MSB(connection->response.version),
+      LSB(connection->response.version), connection->response.statusCode);
+
+   //Retrieve the Reason-Phrase that corresponds to the Status-Code
+   for(i = 0; i < arraysize(statusCodeList); i++)
+   {
+      //Check the status code
+      if(statusCodeList[i].value == connection->response.statusCode)
+      {
+         //Append the textual phrase to the Status-Line
+         p += sprintf(p, statusCodeList[i].message);
+         //Break the loop and continue processing
+         break;
+      }
+   }
+
+   //Properly terminate the Status-Line
+   p += sprintf(p, "\r\n");
+   //The Server response-header field contains information about the
+   //software used by the origin server to handle the request
+   p += sprintf(p, "Server: Oryx Embedded HTTP Server\r\n");
+
+   //Valid location?
+   if(connection->response.location != NULL)
+   {
+      //Set Location field
+      p += sprintf(p, "Location: %s\r\n", connection->response.location);
+   }
+
+   //Persistent connection?
+   if(connection->response.keepAlive)
+   {
+      //Set Connection field
+      p += sprintf(p, "Connection: keep-alive\r\n");
+
+      //Set Keep-Alive field
+      p += sprintf(p, "Keep-Alive: timeout=%u, max=%u\r\n",
+         HTTP_SERVER_IDLE_TIMEOUT / 1000, HTTP_SERVER_MAX_REQUESTS);
+   }
+   else
+   {
+      //Set Connection field
+      p += sprintf(p, "Connection: close\r\n");
+   }
+
+   //Specify the caching policy
+   if(connection->response.noCache)
+   {
+      //Set Pragma field
+      p += sprintf(p, "Pragma: no-cache\r\n");
+      //Set Cache-Control field
+      p += sprintf(p, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
+      p += sprintf(p, "Cache-Control: max-age=0, post-check=0, pre-check=0\r\n");
+   }
+   else if(connection->response.maxAge != 0)
+   {
+      //Set Cache-Control field
+      p += sprintf(p, "Cache-Control: max-age=%u\r\n", connection->response.maxAge);
+   }
+
+#if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
+   //Check whether authentication is required
+   if(connection->response.auth.mode != HTTP_AUTH_MODE_NONE)
+   {
+      //Add WWW-Authenticate header field
+      p += httpAddAuthenticateField(connection, p);
+   }
+#endif
+
+   //Valid content type?
+   if(connection->response.contentType != NULL)
+   {
+      //Content type
+      p += sprintf(p, "Content-Type: %s\r\n", connection->response.contentType);
+   }
+
+   //Use chunked encoding transfer?
+   if(connection->response.chunkedEncoding)
+   {
+      //Set Transfer-Encoding field
+      p += sprintf(p, "Transfer-Encoding: chunked\r\n");
+   }
+   //Persistent connection?
+   else if(connection->response.keepAlive)
+   {
+      //Set Content-Length field
+      p += sprintf(p, "Content-Length: %" PRIuSIZE "\r\n", connection->response.contentLength);
+   }
+
+   //Terminate the header with an empty line
+   p += sprintf(p, "\r\n");
+
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Send data to the client
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[in] data Pointer to a buffer containing the data to be transmitted
+ * @param[in] length Number of bytes to be transmitted
+ * @param[in] flags Set of flags that influences the behavior of this function
+ **/
+
+error_t httpSend(HttpConnection *connection,
+   const void *data, size_t length, uint_t flags)
+{
+#if (NET_RTOS_SUPPORT == ENABLED)
+      error_t error;
+
+#if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
+   //Check whether a secure connection is being used
+   if(connection->tlsContext != NULL)
+   {
+      //Use SSL/TLS to transmit data to the client
+      error = tlsWrite(connection->tlsContext, data, length, NULL, flags);
+   }
+   else
+#endif
+   {
+      //Transmit data to the client
+      error = socketSend(connection->socket, data, length, NULL, flags);
+   }
+
+   //Return status code
+   return error;
+#else
+   //Prevent buffer overflow
+   if((connection->bufferLen + length) > HTTP_SERVER_BUFFER_SIZE)
+      return ERROR_BUFFER_OVERFLOW;
+
+   //Copy user data
+   memcpy(connection->buffer + connection->bufferLen, data, length);
+   //Adjust the length of the buffer
+   connection->bufferLen += length;
+
+   //Successful processing
+   return NO_ERROR;
+#endif
+}
+
+
+/**
+ * @brief Receive data from the client
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[out] data Buffer into which received data will be placed
+ * @param[in] size Maximum number of bytes that can be received
+ * @param[out] received Actual number of bytes that have been received
+ * @param[in] flags Set of flags that influences the behavior of this function
+ * @return Error code
+ **/
+
+error_t httpReceive(HttpConnection *connection,
+   void *data, size_t size, size_t *received, uint_t flags)
+{
+   error_t error;
+
+#if (HTTP_SERVER_TLS_SUPPORT == ENABLED)
+   //Check whether a secure connection is being used
+   if(connection->tlsContext != NULL)
+   {
+      //Use SSL/TLS to receive data from the client
+      error = tlsRead(connection->tlsContext, data, size, received, flags);
+   }
+   else
+#endif
+   {
+      //Receive data from the client
+      error = socketReceive(connection->socket, data, size, received, flags);
+   }
+
+   //Return status code
+   return error;
+}
+
+
+/**
+ * @brief Retrieve the full pathname to the specified resource
+ * @param[in] connection Structure representing an HTTP connection
+ * @param[in] relative String containing the relative path to the resource
+ * @param[out] absolute Resulting string containing the absolute path
+ * @param[in] maxLen Maximum acceptable path length
+ **/
+
+void httpGetAbsolutePath(HttpConnection *connection,
+   const char_t *relative, char_t *absolute, size_t maxLen)
+{
+   //Copy the root directory
+   strcpy(absolute, connection->settings->rootDirectory);
+
+   //Append the specified path
+   pathCombine(absolute, relative, maxLen);
+
+   //Clean the resulting path
+   pathCanonicalize(absolute);
+}
+
+
+/**
+ * @brief Compare filename extension
+ * @param[in] filename Filename whose extension is to be checked
+ * @param[in] extension String defining the extension to be checked
+ * @return TRUE is the filename matches the given extension, else FALSE
+ **/
+
+bool_t httpCompExtension(const char_t *filename, const char_t *extension)
+{
+   uint_t n;
+   uint_t m;
+
+   //Get the length of the specified filename
+   n = strlen(filename);
+   //Get the length of the extension
+   m = strlen(extension);
+
+   //Check the length of the filename
+   if(n < m)
+      return FALSE;
+
+   //Compare extensions
+   if(!strncasecmp(filename + n - m, extension, m))
+      return TRUE;
+   else
+      return FALSE;
+}
+
+
+/**
+ * @brief Decode a percent-encoded string
+ * @param[in] input NULL-terminated string to be decoded
+ * @param[out] output NULL-terminated string resulting from the decoding process
+ * @param[in] outputSize Size of the output buffer in bytes
+ * @return Error code
+ **/
+
+error_t httpDecodePercentEncodedString(const char_t *input,
+   char_t *output, size_t outputSize)
+{
+   size_t i;
+   char_t buffer[3];
+
+   //Check parameters
+   if(input == NULL || output == NULL)
+      return ERROR_INVALID_PARAMETER;
+
+   //Decode the percent-encoded string
+   for(i = 0; *input != '\0' && i < outputSize; i++)
+   {
+      //Check current character
+      if(*input == '+')
+      {
+         //Replace '+' characters with spaces
+         output[i] = ' ';
+         //Advance data pointer
+         input++;
+      }
+      else if(input[0] == '%' && input[1] != '\0' && input[2] != '\0')
+      {
+         //Process percent-encoded characters
+         buffer[0] = input[1];
+         buffer[1] = input[2];
+         buffer[2] = '\0';
+         //String to integer conversion
+         output[i] = (uint8_t) strtoul(buffer, NULL, 16);
+         //Advance data pointer
+         input += 3;
+      }
+      else
+      {
+         //Copy any other characters
+         output[i] = *input;
+         //Advance data pointer
+         input++;
+      }
+   }
+
+   //Check whether the output buffer runs out of space
+   if(i >= outputSize)
+      return ERROR_FAILURE;
+
+   //Properly terminate the resulting string
+   output[i] = '\0';
+   //Successful processing
+   return NO_ERROR;
+}
+
+
+/**
+ * @brief Convert byte array to hex string
+ * @param[in] input Point to the byte array
+ * @param[in] inputLength Length of the byte array
+ * @param[out] output NULL-terminated string resulting from the conversion
+ * @return Error code
+ **/
+
+void httpConvertArrayToHexString(const uint8_t *input,
+   size_t inputLength, char_t *output)
+{
+   size_t i;
+
+   //Hex conversion table
+   static const char_t hexDigit[] =
+   {
+      '0', '1', '2', '3', '4', '5', '6', '7',
+      '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+   };
+
+   //Process byte array
+   for(i = 0; i < inputLength; i++)
+   {
+      //Convert upper nibble
+      output[i * 2] = hexDigit[(input[i] >> 4) & 0x0F];
+      //Then convert lower nibble
+      output[i * 2 + 1] = hexDigit[input[i] & 0x0F];
+   }
+
+   //Properly terminate the string with a NULL character
+   output[i * 2] = '\0';
+}
+
+#endif
+