Webserver+3d print
Diff: cyclone_tcp/http/ssi.c
- Revision:
- 0:8918a71cdbe9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cyclone_tcp/http/ssi.c Sat Feb 04 18:15:49 2017 +0000 @@ -0,0 +1,867 @@ +/** + * @file ssi.c + * @brief SSI (Server Side Includes) + * + * @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. + * + * @section Description + * + * Server Side Includes (SSI) is a simple interpreted server-side scripting + * language used to generate dynamic content to web pages + * + * @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 "core/net.h" +#include "http/http_server.h" +#include "http/http_server_misc.h" +#include "http/mime.h" +#include "http/ssi.h" +#include "str.h" +#include "debug.h" + +//File system support? +#if (HTTP_SERVER_FS_SUPPORT == ENABLED) + #include "fs_port.h" +#else + #include "resource_manager.h" +#endif + +//Check TCP/IP stack configuration +#if (HTTP_SERVER_SUPPORT == ENABLED && HTTP_SERVER_SSI_SUPPORT == ENABLED) + + +/** + * @brief Execute SSI script + * @param[in] connection Structure representing an HTTP connection + * @param[in] uri NULL-terminated string containing the file to process + * @param[in] level Current level of recursion + * @return Error code + **/ + +error_t ssiExecuteScript(HttpConnection *connection, const char_t *uri, uint_t level) +{ + error_t error; + size_t length; + +#if (HTTP_SERVER_FS_SUPPORT == ENABLED) + bool_t more; + uint_t pos; + uint_t n; + char_t *buffer; + FsFile *file; +#else + uint_t i; + uint_t j; + char_t *data; +#endif + + //Recursion limit exceeded? + if(level >= HTTP_SERVER_SSI_MAX_RECURSION) + return NO_ERROR; + + //Retrieve the full pathname + httpGetAbsolutePath(connection, uri, + connection->buffer, HTTP_SERVER_BUFFER_SIZE); + +#if (HTTP_SERVER_FS_SUPPORT == ENABLED) + //Open the file for reading + file = fsOpenFile(connection->buffer, FS_FILE_MODE_READ); + //Failed to open the file? + if(file == NULL) + return ERROR_NOT_FOUND; + + //Allocate a memory buffer + buffer = osAllocMem(HTTP_SERVER_BUFFER_SIZE); + //Failed to allocate memory? + if(buffer == NULL) + { + //Close the file + fsCloseFile(file); + //Report an error + return ERROR_OUT_OF_MEMORY; + } +#else + //Get the resource data associated with the URI + error = resGetData(connection->buffer, (uint8_t **) &data, &length); + //The specified URI cannot be found? + if(error) + return error; +#endif + + //Send the HTTP response header before executing the script + if(!level) + { + //Format HTTP response header + connection->response.statusCode = 200; + connection->response.contentType = mimeGetType(uri); + connection->response.chunkedEncoding = TRUE; + + //Send the header to the client + error = httpWriteHeader(connection); + //Any error to report? + if(error) + { +#if (HTTP_SERVER_FS_SUPPORT == ENABLED) + //Close the file + fsCloseFile(file); + //Release memory buffer + osFreeMem(buffer); +#endif + //Return status code + return error; + } + } + +#if (HTTP_SERVER_FS_SUPPORT == ENABLED) + //Point to the beginning of the buffer + pos = 0; + length = 0; + + //This flag indicates whether data should be read + more = TRUE; + + //Parse the specified file + while(1) + { + //Read more data if needed + if(more) + { + //Check whether the current position is aligned on 32-bit boundaries + n = 4 - ((pos + length) % 4); + + //Maintain proper alignment + if(n != 4) + { + memmove(buffer + pos + n, buffer + pos, length); + pos += n; + } + + //Read data from the specified file + error = fsReadFile(file, buffer + pos + length, + HTTP_SERVER_BUFFER_SIZE - (pos + length), &n); + + //End of input stream? + if(error) + { + //Purge data buffer + error = httpWriteStream(connection, buffer + pos, length); + //Exit immediately + break; + } + + //Adjust the length of the buffer + length += n; + //Clear flag + more = FALSE; + } + + //Search for any SSI tags + error = ssiSearchTag(buffer + pos, length, "<!--#", 5, &n); + + //Full match? + if(error == NO_ERROR) + { + //Send the part of the file that precedes the tag + error = httpWriteStream(connection, buffer + pos, n); + //Failed to send data? + if(error) + break; + + //Advance data pointer + pos += n; + length -= n; + + //Search for the comment terminator + error = ssiSearchTag(buffer + pos + 5, length - 5, "-->", 3, &n); + + //Full match? + if(error == NO_ERROR) + { + //Advance data pointer over the opening identifier + pos += 5; + length -= 5; + + //Process SSI directive + error = ssiProcessCommand(connection, buffer + pos, n, uri, level); + //Any error to report? + if(error) + break; + + //Advance data pointer over the SSI tag + pos += n + 3; + length -= n + 3; + } + //No match or partial match? + else + { + if(pos > 0) + { + //Move the remaining bytes to the start of the buffer + memmove(buffer, buffer + pos, length); + //Rewind to the beginning of the buffer + pos = 0; + //More data are needed + more = TRUE; + } + else + { + //Send data to the client + error = httpWriteStream(connection, buffer + pos, length); + //Any error to report? + if(error) + break; + + //Rewind to the beginning of the buffer + pos = 0; + length = 0; + //More data are needed + more = TRUE; + } + } + } + //Partial match? + else if(error == ERROR_PARTIAL_MATCH) + { + //Send the part of the file that precedes the tag + error = httpWriteStream(connection, buffer + pos, n); + //Failed to send data? + if(error) + break; + + //Advance data pointer + pos += n; + length -= n; + + //Move the remaining bytes to the start of the buffer + memmove(buffer, buffer + pos, length); + //Rewind to the beginning of the buffer + pos = 0; + //More data are needed + more = TRUE; + } + //No match? + else + { + //Send data to the client + error = httpWriteStream(connection, buffer + pos, length); + //Any error to report? + if(error) + break; + + //Rewind to the beginning of the buffer + pos = 0; + length = 0; + //More data are needed + more = TRUE; + } + } + + //Close the file + fsCloseFile(file); + //Release memory buffer + osFreeMem(buffer); + + //Properly close the output stream + if(!level && error == NO_ERROR) + error = httpCloseStream(connection); +#else + //Parse the specified file + while(length > 0) + { + //Search for any SSI tags + error = ssiSearchTag(data, length, "<!--#", 5, &i); + + //Opening identifier found? + if(!error) + { + //Search for the comment terminator + error = ssiSearchTag(data + i + 5, length - i - 5, "-->", 3, &j); + } + + //Check whether a valid SSI tag has been found? + if(!error) + { + //Send the part of the file that precedes the tag + error = httpWriteStream(connection, data, i); + //Failed to send data? + if(error) + return error; + + //Advance data pointer over the opening identifier + data += i + 5; + length -= i + 5; + + //Process SSI directive + error = ssiProcessCommand(connection, data, j, uri, level); + //Any error to report? + if(error) + return error; + + //Advance data pointer over the SSI tag + data += j + 3; + length -= j + 3; + } + else + { + //Send the rest of the file + error = httpWriteStream(connection, data, length); + //Failed to send data? + if(error) + return error; + + //Advance data pointer + data += length; + length = 0; + } + } + + //Properly close the output stream + if(!level) + error = httpCloseStream(connection); +#endif + + //Return status code + return error; +} + + +/** + * @brief Process SSI directive + * @param[in] connection Structure representing an HTTP connection + * @param[in] tag Pointer to the SSI tag + * @param[in] length Total length of the SSI tag + * @param[in] uri NULL-terminated string containing the file being processed + * @param[in] level Current level of recursion + * @return Error code + **/ + +error_t ssiProcessCommand(HttpConnection *connection, + const char_t *tag, size_t length, const char_t *uri, uint_t level) +{ + error_t error; + + //Include command found? + if(length > 7 && !strncasecmp(tag, "include", 7)) + { + //Process SSI include directive + error = ssiProcessIncludeCommand(connection, tag, length, uri, level); + } + //Echo command found? + else if(length > 4 && !strncasecmp(tag, "echo", 4)) + { + //Process SSI echo directive + error = ssiProcessEchoCommand(connection, tag, length); + } + //Exec command found? + else if(length > 4 && !strncasecmp(tag, "exec", 4)) + { + //Process SSI exec directive + error = ssiProcessExecCommand(connection, tag, length); + } + //Unknown command? + else + { + //The server is unable to decode the SSI tag + error = ERROR_INVALID_TAG; + } + + //Invalid SSI directive? + if(error == ERROR_INVALID_TAG) + { + //Report a warning to the user + error = httpWriteStream(connection, "Warning: Invalid SSI Tag", 24); + } + + //Return status code + return error; +} + + +/** + * @brief Process SSI include directive + * + * This include directive allows the content of one document to be included + * in another. The file parameter defines the included file as relative to + * the document path. The virtual parameter defines the included file as + * relative to the document root + * + * @param[in] connection Structure representing an HTTP connection + * @param[in] tag Pointer to the SSI tag + * @param[in] length Total length of the SSI tag + * @param[in] uri NULL-terminated string containing the file being processed + * @param[in] level Current level of recursion + * @return Error code + **/ + +error_t ssiProcessIncludeCommand(HttpConnection *connection, + const char_t *tag, size_t length, const char_t *uri, uint_t level) +{ + error_t error; + char_t *separator; + char_t *attribute; + char_t *value; + char_t *path; + char_t *p; + + //Discard invalid SSI directives + if(length < 7 || length >= HTTP_SERVER_BUFFER_SIZE) + return ERROR_INVALID_TAG; + + //Skip the SSI include command (7 bytes) + memcpy(connection->buffer, tag + 7, length - 7); + //Ensure the resulting string is NULL-terminated + connection->buffer[length - 7] = '\0'; + + //Check whether a separator is present + separator = strchr(connection->buffer, '='); + //Separator not found? + if(!separator) + return ERROR_INVALID_TAG; + + //Split the tag + *separator = '\0'; + + //Get attribute name and value + attribute = strTrimWhitespace(connection->buffer); + value = strTrimWhitespace(separator + 1); + + //Remove leading simple or double quote + if(value[0] == '\'' || value[0] == '\"') + value++; + + //Get the length of the attribute value + length = strlen(value); + + //Remove trailing simple or double quote + if(length > 0) + { + if(value[length - 1] == '\'' || value[length - 1] == '\"') + value[length - 1] = '\0'; + } + + //Check the length of the filename + if(strlen(value) > HTTP_SERVER_URI_MAX_LEN) + return ERROR_INVALID_TAG; + + //The file parameter defines the included file as relative to the document path + if(!strcasecmp(attribute, "file")) + { + //Allocate a buffer to hold the path to the file to be included + path = osAllocMem(strlen(uri) + strlen(value) + 1); + //Failed to allocate memory? + if(path == NULL) + return ERROR_OUT_OF_MEMORY; + + //Copy the path identifying the script file being processed + strcpy(path, uri); + //Search for the last slash character + p = strrchr(path, '/'); + + //Remove the filename from the path if applicable + if(p) + strcpy(p + 1, value); + else + strcpy(path, value); + } + //The virtual parameter defines the included file as relative to the document root + else if(!strcasecmp(attribute, "virtual")) + { + //Copy the absolute path + path = strDuplicate(value); + //Failed to duplicate the string? + if(path == NULL) + return ERROR_OUT_OF_MEMORY; + } + //Unknown parameter... + else + { + //Report an error + return ERROR_INVALID_TAG; + } + + //Use server-side scripting to dynamically generate HTML code? + if(httpCompExtension(value, ".stm") || + httpCompExtension(value, ".shtm") || + httpCompExtension(value, ".shtml")) + { + //SSI processing (Server Side Includes) + error = ssiExecuteScript(connection, path, level + 1); + } + else + { +#if (HTTP_SERVER_FS_SUPPORT == ENABLED) + FsFile *file; + + //Retrieve the full pathname + httpGetAbsolutePath(connection, path, + connection->buffer, HTTP_SERVER_BUFFER_SIZE); + + //Open the file for reading + file = fsOpenFile(connection->buffer, FS_FILE_MODE_READ); + + //Successful operation? + if(file) + { + //Send the contents of the requested file + while(1) + { + //Read data from the specified file + error = fsReadFile(file, connection->buffer, HTTP_SERVER_BUFFER_SIZE, &length); + //End of input stream? + if(error) + break; + + //Send data to the client + error = httpWriteStream(connection, connection->buffer, length); + //Any error to report? + if(error) + break; + } + + //Close the file + fsCloseFile(file); + + //Successful file transfer? + if(error == ERROR_END_OF_FILE) + error = NO_ERROR; + } + else + { + //The specified URI cannot be found + error = ERROR_NOT_FOUND; + } +#else + uint8_t *data; + + //Retrieve the full pathname + httpGetAbsolutePath(connection, path, + connection->buffer, HTTP_SERVER_BUFFER_SIZE); + + //Get the resource data associated with the file + error = resGetData(connection->buffer, &data, &length); + + //Send the contents of the requested file + if(!error) + error = httpWriteStream(connection, data, length); +#endif + } + + //Cannot found the specified resource? + if(error == ERROR_NOT_FOUND) + error = ERROR_INVALID_TAG; + + //Release previously allocated memory + osFreeMem(path); + //return status code + return error; +} + + +/** + * @brief Process SSI echo directive + * + * This echo directive displays the contents of a specified + * HTTP environment variable + * + * @param[in] connection Structure representing an HTTP connection + * @param[in] tag Pointer to the SSI tag + * @param[in] length Total length of the SSI tag + * @return Error code + **/ + +error_t ssiProcessEchoCommand(HttpConnection *connection, const char_t *tag, size_t length) +{ + error_t error; + char_t *separator; + char_t *attribute; + char_t *value; + + //Discard invalid SSI directives + if(length < 4 || length >= HTTP_SERVER_BUFFER_SIZE) + return ERROR_INVALID_TAG; + + //Skip the SSI echo command (4 bytes) + memcpy(connection->buffer, tag + 4, length - 4); + //Ensure the resulting string is NULL-terminated + connection->buffer[length - 4] = '\0'; + + //Check whether a separator is present + separator = strchr(connection->buffer, '='); + //Separator not found? + if(!separator) + return ERROR_INVALID_TAG; + + //Split the tag + *separator = '\0'; + + //Get attribute name and value + attribute = strTrimWhitespace(connection->buffer); + value = strTrimWhitespace(separator + 1); + + //Remove leading simple or double quote + if(value[0] == '\'' || value[0] == '\"') + value++; + + //Get the length of the attribute value + length = strlen(value); + + //Remove trailing simple or double quote + if(length > 0) + { + if(value[length - 1] == '\'' || value[length - 1] == '\"') + value[length - 1] = '\0'; + } + + //Enforce attribute name + if(strcasecmp(attribute, "var")) + return ERROR_INVALID_TAG; + + //Remote address? + if(!strcasecmp(value, "REMOTE_ADDR")) + { + //The IP address of the host making this request + ipAddrToString(&connection->socket->remoteIpAddr, connection->buffer); + } + //Remote port? + else if(!strcasecmp(value, "REMOTE_PORT")) + { + //The port number used by the remote host when making this request + sprintf(connection->buffer, "%" PRIu16, connection->socket->remotePort); + } + //Server address? + else if(!strcasecmp(value, "SERVER_ADDR")) + { + //The IP address of the server for this URL + ipAddrToString(&connection->socket->localIpAddr, connection->buffer); + } + //Server port? + else if(!strcasecmp(value, "SERVER_PORT")) + { + //The port number on this server to which this request was directed + sprintf(connection->buffer, "%" PRIu16, connection->socket->localPort); + } + //Request method? + else if(!strcasecmp(value, "REQUEST_METHOD")) + { + //The method used for this HTTP request + strcpy(connection->buffer, connection->request.method); + } + //Document root? + else if(!strcasecmp(value, "DOCUMENT_ROOT")) + { + //The root directory + strcpy(connection->buffer, connection->settings->rootDirectory); + } + //Document URI? + else if(!strcasecmp(value, "DOCUMENT_URI")) + { + //The URI for this request relative to the root directory + strcpy(connection->buffer, connection->request.uri); + } + //Document name? + else if(!strcasecmp(value, "DOCUMENT_NAME")) + { + //The full physical path and filename of the document requested + httpGetAbsolutePath(connection, connection->request.uri, + connection->buffer, HTTP_SERVER_BUFFER_SIZE); + } + //Query string? + else if(!strcasecmp(value, "QUERY_STRING")) + { + //The information following the "?" in the URL for this request + strcpy(connection->buffer, connection->request.queryString); + } + //User name? + else if(!strcasecmp(value, "AUTH_USER")) + { +#if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED) + //The username provided by the user to the server + strcpy(connection->buffer, connection->request.auth.user); +#else + //Basic access authentication is not supported + connection->buffer[0] = '\0'; +#endif + } + //GMT time? + else if(!strcasecmp(value, "DATE_GMT")) + { + //The current date and time in Greenwich Mean Time + connection->buffer[0] = '\0'; + } + //Local time? + else if(!strcasecmp(value, "DATE_LOCAL")) + { + //The current date and time in the local timezone + connection->buffer[0] = '\0'; + } + //Unknown variable? + else + { + //Report an error + return ERROR_INVALID_TAG; + } + + //Get the length of the resulting string + length = strlen(connection->buffer); + + //Send the contents of the specified environment variable + error = httpWriteStream(connection, connection->buffer, length); + //Failed to send data? + if(error) + return error; + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Process SSI exec directive + * + * This exec directive executes a program, script, or shell command on + * the server. The cmd parameter specifies a server-side command. The + * cgi parameter specifies the path to a CGI script + * + * @param[in] connection Structure representing an HTTP connection + * @param[in] tag Pointer to the SSI tag + * @param[in] length Total length of the SSI tag + * @return Error code + **/ + +error_t ssiProcessExecCommand(HttpConnection *connection, const char_t *tag, size_t length) +{ + char_t *separator; + char_t *attribute; + char_t *value; + + //First, check whether CGI is supported by the server + if(connection->settings->cgiCallback == NULL) + return ERROR_INVALID_TAG; + + //Discard invalid SSI directives + if(length < 4 || length >= HTTP_SERVER_BUFFER_SIZE) + return ERROR_INVALID_TAG; + + //Skip the SSI exec command (4 bytes) + memcpy(connection->buffer, tag + 4, length - 4); + //Ensure the resulting string is NULL-terminated + connection->buffer[length - 4] = '\0'; + + //Check whether a separator is present + separator = strchr(connection->buffer, '='); + //Separator not found? + if(!separator) + return ERROR_INVALID_TAG; + + //Split the tag + *separator = '\0'; + + //Get attribute name and value + attribute = strTrimWhitespace(connection->buffer); + value = strTrimWhitespace(separator + 1); + + //Remove leading simple or double quote + if(value[0] == '\'' || value[0] == '\"') + value++; + + //Get the length of the attribute value + length = strlen(value); + + //Remove trailing simple or double quote + if(length > 0) + { + if(value[length - 1] == '\'' || value[length - 1] == '\"') + value[length - 1] = '\0'; + } + + //Enforce attribute name + if(strcasecmp(attribute, "cgi") && strcasecmp(attribute, "cmd") && strcasecmp(attribute, "cmd_argument")) + return ERROR_INVALID_TAG; + //Check the length of the CGI parameter + if(strlen(value) > HTTP_SERVER_CGI_PARAM_MAX_LEN) + return ERROR_INVALID_TAG; + + //The scratch buffer may be altered by the user-defined callback. + //So the CGI parameter must be copied prior to function invocation + strcpy(connection->cgiParam, value); + + //Invoke user-defined callback + return connection->settings->cgiCallback(connection, connection->cgiParam); +} + + +/** + * @brief Search a string for a given tag + * @param[in] s String to search + * @param[in] sLen Length of the string to search + * @param[in] tag String containing the tag to search for + * @param[in] tagLen Length of the tag + * @param[out] pos The index of the first occurrence of the tag in the string, + * @retval NO_ERROR if the specified tag has been found + * @retval ERROR_PARTIAL_MATCH if a partial match occurs + * @retval ERROR_NO_MATCH if the tag does not appear in the string + **/ + +error_t ssiSearchTag(const char_t *s, size_t sLen, const char_t *tag, size_t tagLen, uint_t *pos) +{ + uint_t i; + uint_t j; + + //Parse the input string + for(i = 0; i <= sLen; i++) + { + //Compare current substring with the given tag + for(j = 0; (i + j) < sLen && j < tagLen; j++) + { + if(s[i + j] != tag[j]) + break; + } + + //Check whether a full match occurred + if(j == tagLen) + { + //Save the position of the first character + *pos = i; + //The specified tag has been found + return NO_ERROR; + } + //Check whether a partial match occurred + else if((i + j) == sLen && j > 0) + { + //Save the position of the first character + *pos = i; + //The beginning of the tag matches the end of the string + return ERROR_PARTIAL_MATCH; + } + } + + //The tag does not appear in the string + return ERROR_NO_MATCH; +} + +#endif +