Webserver+3d print
Diff: cyclone_tcp/http/http_server_misc.c
- 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 +