Webserver+3d print

Dependents:   Nucleo

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
+