Webserver+3d print

Dependents:   Nucleo

cyclone_tcp/http/http_server_misc.c

Committer:
Sergunb
Date:
2017-02-04
Revision:
0:8918a71cdbe9

File content as of revision 0:8918a71cdbe9:

/**
 * @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