text: /**************************************************************************
   *
   * Copyright (c) 2000-2003 Intel Corporation
   * All rights reserved.
   * Copyright (c) 2012 France Telecom All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * - Redistributions of source code must retain the above copyright notice,
   * this list of conditions and the following disclaimer.
   * - Redistributions in binary form must reproduce the above copyright notice,
   * this list of conditions and the following disclaimer in the documentation
   * and/or other materials provided with the distribution.
   * - Neither name of Intel Corporation nor the names of its contributors
   * may be used to endorse or promote products derived from this software
   * without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   **************************************************************************/
  
  /*!
   * \file
   *
   * \brief Defines the Web Server and has functions to carry out
   * operations of the Web Server.
   */
  
  #include "config.h"
  
  #if EXCLUDE_WEB_SERVER == 0
  
  #include "UpnpFileInfo.h"
  #include "UpnpHttpHeaders.h"
  #include "UpnpIntTypes.h"
  #include "UpnpLib.h"
  #include "UpnpStdInt.h"
  #include "VirtualDir.h"
  #include "document_type.h"
  #include "httpparser.h"
  #include "httpreadwrite.h"
  #include "membuffer.h"
  #include "pthread.h"
  #include "ssdplib.h"
  #include "statcodes.h"
  #include "strintmap.h"
  #include "unixutil.h"
  #include "upnp.h"
  #include "upnpapi.h"
  #include "webserver.h"
  #include "winutil.h"
  #include "xml_alias.h"
  
  #include <assert.h>
  #include <fcntl.h>
  #include <sys/stat.h>
  
  #ifdef _WIN32
  #if defined(_MSC_VER) && _MSC_VER < 1900
  #define snprintf _snprintf
  #endif
  #endif /* _WIN32 */
  
  /*!
   * Response Types.
   */
  enum resp_type
  {
          RESP_FILEDOC,
          RESP_XMLDOC,
          RESP_HEADERS,
          RESP_WEBDOC,
          RESP_POST
  };
  
  static const char *gMediaTypes[] = {
          /*! 0. */
          NULL,
          /*! 1. */
          "audio",
          /*! 2. */
          "video",
          /*! 3. */
          "image",
          /*! 4. */
          "application",
          /*! 5. */
          "text"};
  
  /*
   * Defines.
   */
  
  /* index into 'gMediaTypes' */
  #define AUDIO_STR "\1"
  #define VIDEO_STR "\2"
  #define IMAGE_STR "\3"
  #define APPLICATION_STR "\4"
  #define TEXT_STR "\5"
  
  /* int index */
  #define APPLICATION_INDEX 4
  #define TEXT_INDEX 5
  
  /* general */
  #define NUM_HTTP_HEADER_NAMES 33
  
  #define ASCTIME_R_BUFFER_SIZE 26
  #ifdef _WIN32
  static char *web_server_asctime_r(const struct tm *tm, char *buf)
  {
          if (tm == NULL || buf == NULL)
                  return NULL;
  
          asctime_s(buf, ASCTIME_R_BUFFER_SIZE, tm);
          return buf;
  }
  #else /* _WIN32 */
  #define web_server_asctime_r asctime_r
  #endif /* _WIN32 */
  
  /* sorted by file extension; must have 'NUM_MEDIA_TYPES' extensions */
  static const char *gEncodedMediaTypes =
          "aif\0" AUDIO_STR "aiff\0"
          "aifc\0" AUDIO_STR "aiff\0"
          "aiff\0" AUDIO_STR "aiff\0"
          "asf\0" VIDEO_STR "x-ms-asf\0"
          "asx\0" VIDEO_STR "x-ms-asf\0"
          "au\0" AUDIO_STR "basic\0"
          "avi\0" VIDEO_STR "msvideo\0"
          "bmp\0" IMAGE_STR "bmp\0"
          "css\0" TEXT_STR "css\0"
          "dcr\0" APPLICATION_STR "x-director\0"
          "dib\0" IMAGE_STR "bmp\0"
          "dir\0" APPLICATION_STR "x-director\0"
          "dxr\0" APPLICATION_STR "x-director\0"
          "gif\0" IMAGE_STR "gif\0"
          "hta\0" TEXT_STR "hta\0"
          "htm\0" TEXT_STR "html\0"
          "html\0" TEXT_STR "html\0"
          "jar\0" APPLICATION_STR "java-archive\0"
          "jfif\0" IMAGE_STR "pjpeg\0"
          "jpe\0" IMAGE_STR "jpeg\0"
          "jpeg\0" IMAGE_STR "jpeg\0"
          "jpg\0" IMAGE_STR "jpeg\0"
          "js\0" APPLICATION_STR "x-javascript\0"
          "kar\0" AUDIO_STR "midi\0"
          "m3u\0" AUDIO_STR "mpegurl\0"
          "mid\0" AUDIO_STR "midi\0"
          "midi\0" AUDIO_STR "midi\0"
          "mov\0" VIDEO_STR "quicktime\0"
          "mp2v\0" VIDEO_STR "x-mpeg2\0"
          "mp3\0" AUDIO_STR "mpeg\0"
          "mpe\0" VIDEO_STR "mpeg\0"
          "mpeg\0" VIDEO_STR "mpeg\0"
          "mpg\0" VIDEO_STR "mpeg\0"
          "mpv\0" VIDEO_STR "mpeg\0"
          "mpv2\0" VIDEO_STR "x-mpeg2\0"
          "pdf\0" APPLICATION_STR "pdf\0"
          "pjp\0" IMAGE_STR "jpeg\0"
          "pjpeg\0" IMAGE_STR "jpeg\0"
          "plg\0" TEXT_STR "html\0"
          "pls\0" AUDIO_STR "scpls\0"
          "png\0" IMAGE_STR "png\0"
          "qt\0" VIDEO_STR "quicktime\0"
          "ram\0" AUDIO_STR "x-pn-realaudio\0"
          "rmi\0" AUDIO_STR "mid\0"
          "rmm\0" AUDIO_STR "x-pn-realaudio\0"
          "rtf\0" APPLICATION_STR "rtf\0"
          "shtml\0" TEXT_STR "html\0"
          "smf\0" AUDIO_STR "midi\0"
          "snd\0" AUDIO_STR "basic\0"
          "spl\0" APPLICATION_STR "futuresplash\0"
          "ssm\0" APPLICATION_STR "streamingmedia\0"
          "swf\0" APPLICATION_STR "x-shockwave-flash\0"
          "tar\0" APPLICATION_STR "tar\0"
          "tcl\0" APPLICATION_STR "x-tcl\0"
          "text\0" TEXT_STR "plain\0"
          "tif\0" IMAGE_STR "tiff\0"
          "tiff\0" IMAGE_STR "tiff\0"
          "txt\0" TEXT_STR "plain\0"
          "ulw\0" AUDIO_STR "basic\0"
          "wav\0" AUDIO_STR "wav\0"
          "wax\0" AUDIO_STR "x-ms-wax\0"
          "wm\0" VIDEO_STR "x-ms-wm\0"
          "wma\0" AUDIO_STR "x-ms-wma\0"
          "wmv\0" VIDEO_STR "x-ms-wmv\0"
          "wvx\0" VIDEO_STR "x-ms-wvx\0"
          "xbm\0" IMAGE_STR "x-xbitmap\0"
          "xml\0" TEXT_STR "xml\0"
          "xsl\0" TEXT_STR "xml\0"
          "z\0" APPLICATION_STR "x-compress\0"
          "zip\0" APPLICATION_STR "zip\0"
          "\0";
  /* *** end *** */
  
  /*!
   * module variables - Globals, static and externs.
   */
  
  /*! XML document. */
  extern const str_int_entry Http_Header_Names[NUM_HTTP_HEADER_NAMES];
  
  /*!
   * \brief Decodes list and stores it in gMediaTypeList.
   */
  static UPNP_INLINE void media_list_init(UpnpLib *p)
  {
          int i;
          const char *s = gEncodedMediaTypes;
          document_type_t *gMediaTypeArray =
                  UpnpLib_getnc_gMediaTypeArray(p)->doc;
          document_type_t *doc_type;
  
          for (i = 0; *s != '\0'; i++) {
                  doc_type = gMediaTypeArray + i;
                  doc_type->file_ext = s;
                  /* point to type. */
                  s += strlen(s) + 1;
                  doc_type->content_type = gMediaTypes[(int)*s];
                  /* point to subtype. */
                  s++;
                  doc_type->content_subtype = s;
                  /* next entry. */
                  s += strlen(s) + 1;
          }
          assert(i == NUM_MEDIA_TYPES);
  }
  
  /*!
   * \brief Based on the extension, returns the content type and content
   * subtype.
   *
   * \return
   * \li \c 0 on success
   * \li \c -1 on error
   */
  static UPNP_INLINE int search_extension(
          /*! Library handle. */
          UpnpLib *p,
          /*! [in] . */
          const char *extension,
          /*! [out] . */
          const char **con_type,
          /*! [out] . */
          const char **con_subtype)
  {
          int top, mid, bot;
          int cmp;
          document_type_t *gMediaTypeArray =
                  UpnpLib_getnc_gMediaTypeArray(p)->doc;
  
          top = 0;
          bot = NUM_MEDIA_TYPES - 1;
  
          while (top <= bot) {
                  mid = (top + bot) / 2;
                  cmp = strcasecmp(extension, gMediaTypeArray[mid].file_ext);
                  if (cmp > 0) {
                          /* look below mid. */
                          top = mid + 1;
                  } else if (cmp < 0) {
                          /* look above mid. */
                          bot = mid - 1;
                  } else {
                          /* cmp == 0 */
                          *con_type = gMediaTypeArray[mid].content_type;
                          *con_subtype = gMediaTypeArray[mid].content_subtype;
                          return 0;
                  }
          }
  
          return -1;
  }
  
  /*!
   * \brief Based on the extension, clones an XML string based on type and
   * content subtype. If content type and sub type are not found, unknown
   * types are used.
   *
   * \return
   * \li \c 0 on success.
   * \li \c UPNP_E_OUTOF_MEMORY - on memory allocation failures.
   */
  static UPNP_INLINE int get_content_type(
          /*! Library handle. */
          UpnpLib *p,
          /*! [in] . */
          const char *filename,
          /*! [out] . */
          UpnpFileInfo *fileInfo)
  {
          const char *extension;
          const char *type;
          const char *subtype;
          int ctype_found = 0;
          char *temp = NULL;
          size_t length = 0;
          int rc = 0;
  
          UpnpFileInfo_set_ContentType(fileInfo, NULL);
          /* get ext */
          extension = strrchr(filename, '.');
          if (extension != NULL)
                  if (search_extension(p, extension + 1, &type, &subtype) == 0)
                          ctype_found = 1;
          if (!ctype_found) {
                  /* unknown content type */
                  type = gMediaTypes[APPLICATION_INDEX];
                  subtype = "octet-stream";
          }
          length = strlen(type) + strlen("/") + strlen(subtype) + 1;
          temp = malloc(length);
          if (!temp)
                  return UPNP_E_OUTOF_MEMORY;
          rc = snprintf(temp, length, "%s/%s", type, subtype);
          if (rc < 0 || (unsigned int)rc >= length) {
                  free(temp);
                  return UPNP_E_OUTOF_MEMORY;
          }
          UpnpFileInfo_set_ContentType(fileInfo, temp);
          free(temp);
          if (!UpnpFileInfo_get_ContentType(fileInfo))
                  return UPNP_E_OUTOF_MEMORY;
  
          return 0;
  }
  
  /*!
   * \brief Initialize the global XML document. Allocate buffers for the XML
   * document.
   */
  static UPNP_INLINE void glob_alias_init(UpnpLib *p)
  {
          xml_alias_t *alias = UpnpLib_getnc_gAliasDoc(p);
  
          membuffer_init(&alias->doc);
          membuffer_init(&alias->name);
          alias->ct = NULL;
          alias->last_modified = 0;
  }
  
  /*!
   * \brief Check for the validity of the XML object buffer.
   *
   * \return int.
   */
  static UPNP_INLINE int is_valid_alias(
          /*! [in] XML alias object. */
          const xml_alias_t *alias)
  {
          return alias->doc.buf != NULL;
  }
  
  /*!
   * \brief Copy the contents of the global XML document into the local output
   * parameter.
   */
  static void alias_grab(
          /*! Library handle. */
          UpnpLib *p,
          /*! [out] XML alias object. */
          xml_alias_t *alias)
  {
          xml_alias_t *gAliasDoc = UpnpLib_getnc_gAliasDoc(p);
  
          pthread_mutex_lock(UpnpLib_getnc_gWebMutex(p));
          assert(is_valid_alias(gAliasDoc));
          memcpy(alias, gAliasDoc, sizeof(xml_alias_t));
          *alias->ct = *alias->ct + 1;
          pthread_mutex_unlock(UpnpLib_getnc_gWebMutex(p));
  }
  
  /*!
   * \brief Release the XML document referred to by the input parameter. Free
   * the allocated buffers associated with this object.
   */
  static void alias_release(
          /*! Library handle. */
          UpnpLib *p,
          /*! [in] XML alias object. */
          xml_alias_t *alias)
  {
          pthread_mutex_lock(UpnpLib_getnc_gWebMutex(p));
          /* ignore invalid alias */
          if (!is_valid_alias(alias)) {
                  pthread_mutex_unlock(UpnpLib_getnc_gWebMutex(p));
                  return;
          }
          assert(*alias->ct > 0);
          *alias->ct -= 1;
          if (*alias->ct <= 0) {
                  membuffer_destroy(&alias->doc);
                  membuffer_destroy(&alias->name);
                  free(alias->ct);
          }
          pthread_mutex_unlock(UpnpLib_getnc_gWebMutex(p));
  }
  
  int web_server_set_alias(UpnpLib *p,
          const char *alias_name,
          const char *alias_content,
          size_t alias_content_length,
          time_t last_modified)
  {
          int ret_code;
          xml_alias_t alias;
          xml_alias_t *gAliasDoc = UpnpLib_getnc_gAliasDoc(p);
  
          alias_release(p, gAliasDoc);
          if (!alias_name) {
                  /* don't serve aliased doc anymore */
                  return 0;
          }
          assert(alias_content != NULL);
          membuffer_init(&alias.doc);
          membuffer_init(&alias.name);
          alias.ct = NULL;
          do {
                  /* insert leading /, if missing */
                  if (*alias_name != '/')
                          if (membuffer_assign_str(&alias.name, "/") != 0)
                                  break; /* error; out of mem */
                  ret_code = membuffer_append_str(&alias.name, alias_name);
                  if (ret_code != 0)
                          break; /* error */
                  if ((alias.ct = (int *)malloc(sizeof(int))) == NULL)
                          break; /* error */
                  *alias.ct = 1;
                  membuffer_attach(&alias.doc,
                          (char *)alias_content,
                          alias_content_length);
                  alias.last_modified = last_modified;
                  /* save in module var */
                  pthread_mutex_lock(UpnpLib_getnc_gWebMutex(p));
                  *gAliasDoc = alias;
                  pthread_mutex_unlock(UpnpLib_getnc_gWebMutex(p));
  
                  return 0;
          } while (0);
          /* error handler */
          /* free temp alias */
          membuffer_destroy(&alias.name);
          membuffer_destroy(&alias.doc);
          free(alias.ct);
  
          return UPNP_E_OUTOF_MEMORY;
  }
  
  int web_server_init(UpnpLib *p)
  {
          int ret = UPNP_E_SUCCESS;
          struct VirtualDirCallbacks *vdcb = UpnpLib_getnc_virtualDirCallback(p);
  
          if (UpnpLib_get_bWebServerState(p) == WEB_SERVER_DISABLED) {
                  /* decode media list */
                  media_list_init(p);
                  membuffer_init(UpnpLib_getnc_gDocumentRootDir(p));
                  glob_alias_init(p);
                  UpnpLib_set_pVirtualDirList(p, NULL);
  
                  /* Initialize callbacks */
                  vdcb->get_info = NULL;
                  vdcb->open = NULL;
                  vdcb->read = NULL;
                  vdcb->write = NULL;
                  vdcb->seek = NULL;
                  vdcb->close = NULL;
  
                  if (pthread_mutex_init(UpnpLib_getnc_gWebMutex(p), NULL) ==
                          -1) {
                          ret = UPNP_E_OUTOF_MEMORY;
                  } else {
                          UpnpLib_set_bWebServerState(p, WEB_SERVER_ENABLED);
                  }
          }
  
          return ret;
  }
  
  void web_server_destroy(UpnpLib *p)
  {
          if (UpnpLib_get_bWebServerState(p) == WEB_SERVER_ENABLED) {
                  membuffer *gDocumentRootDir = UpnpLib_getnc_gDocumentRootDir(p);
                  xml_alias_t *gAliasDoc = UpnpLib_getnc_gAliasDoc(p);
  
                  membuffer_destroy(gDocumentRootDir);
                  alias_release(p, gAliasDoc);
  
                  pthread_mutex_lock(UpnpLib_getnc_gWebMutex(p));
                  memset(gAliasDoc, 0, sizeof(xml_alias_t));
                  pthread_mutex_unlock(UpnpLib_getnc_gWebMutex(p));
  
                  pthread_mutex_destroy(UpnpLib_getnc_gWebMutex(p));
                  UpnpLib_set_bWebServerState(p, WEB_SERVER_DISABLED);
          }
  }
  
  /*!
   * \brief Release memory allocated for the global web server root directory
   * and the global XML document. Resets the flag bWebServerState to
   * WEB_SERVER_DISABLED.
   *
   * \return Integer.
   */
  static int get_file_info(
          /*! Library handle. */
          UpnpLib *p,
          /*! [in] Filename having the description document. */
          const char *filename,
          /*! [out] File information object having file attributes such as
           * filelength, when was the file last modified, whether a file or a
           * directory and whether the file or directory is readable. */
          UpnpFileInfo *info)
  {
          int code;
          struct stat s;
          FILE *fp;
          int fd;
          int rc = 0;
          time_t aux_LastModified;
          struct tm date;
          char buffer[ASCTIME_R_BUFFER_SIZE];
  
          UpnpFileInfo_set_ContentType(info, NULL);
          fp = fopen(filename, "r");
          /* check readable */
          UpnpFileInfo_set_IsReadable(info, fp != NULL);
          if (!fp) {
                  rc = -1;
                  goto exit_function;
          }
          fd = fileno(fp);
          if (fd == -1) {
                  rc = -1;
                  goto exit_function;
          }
          code = fstat(fd, &s);
          if (code == -1) {
                  rc = -1;
                  goto exit_function;
          }
          fclose(fp);
          if (S_ISDIR(s.st_mode)) {
                  UpnpFileInfo_set_IsDirectory(info, 1);
          } else if (S_ISREG(s.st_mode)) {
                  UpnpFileInfo_set_IsDirectory(info, 0);
          } else {
                  rc = -1;
                  goto exit_function;
          }
          UpnpFileInfo_set_FileLength(info, s.st_size);
          UpnpFileInfo_set_LastModified(info, s.st_mtime);
          rc = get_content_type(p, filename, info);
          aux_LastModified = UpnpFileInfo_get_LastModified(info);
          UpnpPrintf(UpnpLib_get_Log(p),
                  UPNP_INFO,
                  HTTP,
                  __FILE__,
                  __LINE__,
                  "file info: %s, length: %" PRId64 ", last_mod=%s readable=%d\n",
                  filename,
                  (int64_t)UpnpFileInfo_get_FileLength(info),
                  web_server_asctime_r(
                          http_gmtime_r(&aux_LastModified, &date), buffer),
                  UpnpFileInfo_get_IsReadable(info));
  
  exit_function:
          if (fp) {
                  fclose(fp);
          }
          return rc;
  }
  
  int web_server_set_root_dir(UpnpLib *p, const char *root_dir)
  {
          size_t index;
          int ret;
          membuffer *gDocumentRootDir = UpnpLib_getnc_gDocumentRootDir(p);
  
          ret = membuffer_assign_str(gDocumentRootDir, root_dir);
          if (ret != 0)
                  return ret;
          /* remove trailing '/', if any */
          if (gDocumentRootDir->length > 0) {
                  index = gDocumentRootDir->length - 1; /* last char */
                  if (gDocumentRootDir->buf[index] == '/')
                          membuffer_delete(gDocumentRootDir, index, 1);
          }
  
          return 0;
  }
  
  /*!
   * \brief Compare the files names between the one on the XML alias the
   * one passed in as the input parameter. If equal extract file
   * information.
   *
   * \return
   * \li \c 1 - On Success
   * \li \c 0 if request is not an alias
   */
  static UPNP_INLINE int get_alias(
          /*! [in] request file passed in to be compared with. */
          const char *request_file,
          /*! [out] xml alias object which has a file name stored. */
          xml_alias_t *alias,
          /*! [out] File information object which will be filled up if the
           * file comparison succeeds. */
          UpnpFileInfo *info)
  {
          int cmp = strcmp(alias->name.buf, request_file);
          if (cmp == 0) {
                  UpnpFileInfo_set_FileLength(info, (off_t)alias->doc.length);
                  UpnpFileInfo_set_IsDirectory(info, 0);
                  UpnpFileInfo_set_IsReadable(info, 1);
                  UpnpFileInfo_set_LastModified(info, alias->last_modified);
          }
  
          return cmp == 0;
  }
  
  /*!
   * \brief Compares filePath with paths from the list of virtual
   * directory lists.
   *
   * \return int.
   */
  static int isFileInVirtualDir(
          /*! Library handle. */
          UpnpLib *p,
          /*! [in] Directory path to be tested for virtual directory. */
          char *filePath,
          /*! [out] The cookie registered with this virtual directory, if
           * matched.
           */
          const void **cookie)
  {
          virtualDirList *pCurVirtualDir;
          virtualDirList *pVirtualDirList = UpnpLib_get_pVirtualDirList(p);
          size_t webDirLen;
  
          pCurVirtualDir = pVirtualDirList;
          while (pCurVirtualDir) {
                  webDirLen = strlen(pCurVirtualDir->dirName);
                  if (webDirLen) {
                          if (pCurVirtualDir->dirName[webDirLen - 1] == '/') {
                                  if (strncmp(pCurVirtualDir->dirName,
                                              filePath,
                                              webDirLen) == 0) {
                                          if (cookie != NULL)
                                                  *cookie =
                                                          pCurVirtualDir->cookie;
                                          return 1;
                                  }
                          } else {
                                  if (strncmp(pCurVirtualDir->dirName,
                                              filePath,
                                              webDirLen) == 0 &&
                                          (filePath[webDirLen] == '/' ||
                                                  filePath[webDirLen] == '\0' ||
                                                  filePath[webDirLen] == '?')) {
                                          if (cookie != NULL)
                                                  *cookie =
                                                          pCurVirtualDir->cookie;
                                          return 1;
                                  }
                          }
                  }
                  pCurVirtualDir = pCurVirtualDir->next;
          }
  
          return 0;
  }
  
  /*!
   * \brief Converts input string to upper case.
   */
  static void ToUpperCase(
          /*! Input string to be converted. */
          char *s)
  {
          while (*s) {
                  *s = (char)toupper(*s);
                  ++s;
          }
  }
  
  /*!
   * \brief Finds a substring from a string in a case insensitive way.
   *
   * \return A pointer to the first occurence of s2 in s1.
   */
  static char *StrStr(
          /*! Input string. */
          char *s1,
          /*! Input sub-string. */
          const char *s2)
  {
          char *Str1;
          char *Str2;
          const char *Ptr;
          char *ret = NULL;
  
          Str1 = strdup(s1);
          if (!Str1)
                  goto error1;
          Str2 = strdup(s2);
          if (!Str2)
                  goto error2;
          ToUpperCase(Str1);
          ToUpperCase(Str2);
          Ptr = strstr(Str1, Str2);
          if (!Ptr)
                  ret = NULL;
          else
                  ret = s1 + (Ptr - Str1);
  
          free(Str2);
  error2:
          free(Str1);
  error1:
          return ret;
  }
  
  /*!
   * \brief Finds next token in a string.
   *
   * \return Pointer to the next token.
   */
  static char *StrTok(
          /*! String containing the token. */
          char **Src,
          /*! Set of delimiter characters. */
          const char *Del)
  {
          char *TmpPtr;
          char *RetPtr;
  
          if (*Src != NULL) {
                  RetPtr = *Src;
                  TmpPtr = strstr(*Src, Del);
                  if (TmpPtr != NULL) {
                          *TmpPtr = '\0';
                          *Src = TmpPtr + strlen(Del);
                  } else
                          *Src = NULL;
  
                  return RetPtr;
          }
  
          return NULL;
  }
  
  /*!
   * \brief Returns a range of integers from a string.
   *
   * \return Always returns 1.
   */
  static int GetNextRange(
          /*! string containing the token / range. */
          char **SrcRangeStr,
          /*! gets the first byte of the token. */
          off_t *FirstByte,
          /*! gets the last byte of the token. */
          off_t *LastByte)
  {
          char *Ptr;
          char *Tok;
          int i;
          int64_t F = -1;
          int64_t L = -1;
          int Is_Suffix_byte_Range = 1;
  
          if (*SrcRangeStr == NULL)
                  return -1;
          Tok = StrTok(SrcRangeStr, ",");
          if ((Ptr = strstr(Tok, "-")) == NULL)
                  return -1;
          *Ptr = ' ';
          sscanf(Tok, "%" SCNd64 "%" SCNd64, &F, &L);
          if (F == -1 || L == -1) {
                  *Ptr = '-';
                  for (i = 0; i < (int)strlen(Tok); i++) {
                          if (Tok[i] == '-') {
                                  break;
                          } else if (isdigit(Tok[i])) {
                                  Is_Suffix_byte_Range = 0;
                                  break;
                          }
                  }
                  if (Is_Suffix_byte_Range) {
                          *FirstByte = (off_t)L;
                          *LastByte = (off_t)F;
                          return 1;
                  }
          }
          *FirstByte = (off_t)F;
          *LastByte = (off_t)L;
  
          return 1;
  }
  
  /*!
   * \brief Fills in the Offset, read size and contents to send out as an
   * HTTP Range Response.
   *
   * \return
   * \li \c HTTP_BAD_REQUEST
   * \li \c HTTP_INTERNAL_SERVER_ERROR
   * \li \c HTTP_REQUEST_RANGE_NOT_SATISFIABLE
   * \li \c HTTP_OK
   */
  static int CreateHTTPRangeResponseHeader(
          /*! String containing the range. */
          char *ByteRangeSpecifier,
          /*! Length of the file. */
          off_t FileLength,
          /*! [out] SendInstruction object where the range operations will
             be stored. */
          struct SendInstruction *Instr)
  {
          off_t FirstByte, LastByte;
          char *RangeInput;
          char *Ptr;
          int rc = 0;
  
          Instr->IsRangeActive = 1;
          Instr->ReadSendSize = FileLength;
          if (!ByteRangeSpecifier)
                  return HTTP_BAD_REQUEST;
          RangeInput = strdup(ByteRangeSpecifier);
          if (!RangeInput)
                  return HTTP_INTERNAL_SERVER_ERROR;
          /* CONTENT-RANGE: bytes 222-3333/4000  HTTP_PARTIAL_CONTENT */
          if (StrStr(RangeInput, "bytes") == NULL ||
                  (Ptr = StrStr(RangeInput, "=")) == NULL) {
                  free(RangeInput);
                  Instr->IsRangeActive = 0;
                  return HTTP_BAD_REQUEST;
          }
          /* Jump = */
          Ptr = Ptr + 1;
          if (FileLength < 0) {
                  int ret = HTTP_REQUEST_RANGE_NOT_SATISFIABLE;
                  if ((*Ptr == '0') && (*(Ptr + 1) == '-') &&
                          (*(Ptr + 2) == '\0')) {
                          Instr->IsRangeActive = 0;
                          ret = HTTP_OK;
                  }
                  free(RangeInput);
                  return ret;
          }
          if (GetNextRange(&Ptr, &FirstByte, &LastByte) != -1) {
                  if (FileLength < FirstByte) {
                          free(RangeInput);
                          return HTTP_REQUEST_RANGE_NOT_SATISFIABLE;
                  }
                  if (FirstByte >= 0 && LastByte >= 0 && LastByte >= FirstByte) {
                          if (LastByte >= FileLength)
                                  LastByte = FileLength - 1;
                          Instr->RangeOffset = FirstByte;
                          Instr->ReadSendSize = LastByte - FirstByte + 1;
                          /* Data between two range. */
                          rc = snprintf(Instr->RangeHeader,
                                  sizeof(Instr->RangeHeader),
                                  "CONTENT-RANGE: bytes %" PRId64 "-%" PRId64
                                  "/%" PRId64 "\r\n",
                                  (int64_t)FirstByte,
                                  (int64_t)LastByte,
                                  (int64_t)FileLength);
                          if (rc < 0 ||
                                  (unsigned int)rc >=
                                          sizeof(Instr->RangeHeader)) {
                                  free(RangeInput);
                                  return HTTP_INTERNAL_SERVER_ERROR;
                          }
                  } else if (FirstByte >= 0 && LastByte == -1 &&
                          FirstByte < FileLength) {
                          Instr->RangeOffset = FirstByte;
                          Instr->ReadSendSize = FileLength - FirstByte;
                          rc = snprintf(Instr->RangeHeader,
                                  sizeof(Instr->RangeHeader),
                                  "CONTENT-RANGE: bytes %" PRId64 "-%" PRId64
                                  "/%" PRId64 "\r\n",
                                  (int64_t)FirstByte,
                                  (int64_t)(FileLength - 1),
                                  (int64_t)FileLength);
                          if (rc < 0 ||
                                  (unsigned int)rc >=
                                          sizeof(Instr->RangeHeader)) {
                                  free(RangeInput);
                                  return HTTP_INTERNAL_SERVER_ERROR;
                          }
                  } else if (FirstByte == -1 && LastByte > 0) {
                          if (LastByte >= FileLength) {
                                  Instr->RangeOffset = 0;
                                  Instr->ReadSendSize = FileLength;
                                  rc = snprintf(Instr->RangeHeader,
                                          sizeof(Instr->RangeHeader),
                                          "CONTENT-RANGE: bytes "
                                          "0-%" PRId64 "/%" PRId64 "\r\n",
                                          (int64_t)(FileLength - 1),
                                          (int64_t)FileLength);
                          } else {
                                  Instr->RangeOffset = FileLength - LastByte;
                                  Instr->ReadSendSize = LastByte;
                                  rc = snprintf(Instr->RangeHeader,
                                          sizeof(Instr->RangeHeader),
                                          "CONTENT-RANGE: bytes %" PRId64
                                          "-%" PRId64 "/%" PRId64 "\r\n",
                                          (int64_t)(FileLength - LastByte),
                                          (int64_t)FileLength - 1,
                                          (int64_t)FileLength);
                          }
                          if (rc < 0 ||
                                  (unsigned int)rc >=
                                          sizeof(Instr->RangeHeader)) {
                                  free(RangeInput);
                                  return HTTP_INTERNAL_SERVER_ERROR;
                          }
                  } else {
                          free(RangeInput);
                          return HTTP_REQUEST_RANGE_NOT_SATISFIABLE;
                  }
          } else {
                  free(RangeInput);
                  return HTTP_REQUEST_RANGE_NOT_SATISFIABLE;
          }
  
          free(RangeInput);
          return HTTP_OK;
  }
  
  /*!
   * \brief Get header id from the request parameter and take appropriate
   * action based on the ids as an HTTP Range Response.
   *
   * \return
   * \li \c HTTP_BAD_REQUEST
   * \li \c HTTP_INTERNAL_SERVER_ERROR
   * \li \c HTTP_REQUEST_RANGE_NOT_SATISFIABLE
   * \li \c HTTP_OK
   */
  static int CheckOtherHTTPHeaders(
          /*! [in] HTTP Request message. */
          http_message_t *Req,
          /*! [out] Send Instruction object to data for the response. */
          struct SendInstruction *RespInstr,
          /*! Size of the file containing the request document. */
          off_t FileSize)
  {
          http_header_t *header;
          ListNode *node;
          /*NNS: dlist_node* node; */
          int index, RetCode = HTTP_OK;
          char *TmpBuf;
          size_t TmpBufSize = LINE_SIZE;
  
          TmpBuf = (char *)malloc(TmpBufSize);
          if (!TmpBuf)
                  return HTTP_INTERNAL_SERVER_ERROR;
          node = ListHead(&Req->headers);
          while (node != NULL) {
                  header = (http_header_t *)node->item;
                  /* find header type. */
                  index = map_str_to_int((const char *)header->name.buf,
                          header->name.length,
                          Http_Header_Names,
                          NUM_HTTP_HEADER_NAMES,
                          0);
                  if (header->value.length >= TmpBufSize) {
                          free(TmpBuf);
                          TmpBufSize = header->value.length + 1;
                          TmpBuf = (char *)malloc(TmpBufSize);
                          if (!TmpBuf)
                                  return HTTP_INTERNAL_SERVER_ERROR;
                  }
                  memcpy(TmpBuf, header->value.buf, header->value.length);
                  TmpBuf[header->value.length] = '\0';
                  if (index >= 0) {
                          switch (Http_Header_Names[index].id) {
                          case HDR_TE: {
                                  /* Request */
                                  RespInstr->IsChunkActive = 1;
  
                                  if (strlen(TmpBuf) > strlen("gzip")) {
                                          /* means client will accept
                                           * trailer. */
                                          if (StrStr(TmpBuf, "trailers") !=
                                                  NULL) {
                                                  RespInstr->IsTrailers = 1;
                                          }
                                  }
                                  break;
                          }
                          case HDR_CONTENT_LENGTH:
                                  RespInstr->RecvWriteSize = atoi(TmpBuf);
                                  break;
                          case HDR_RANGE:
                                  RetCode = CreateHTTPRangeResponseHeader(
                                          TmpBuf, FileSize, RespInstr);
                                  if (RetCode != HTTP_OK) {
                                          free(TmpBuf);
                                          return RetCode;
                                  }
                                  break;
                          case HDR_ACCEPT_LANGUAGE:
                                  if (header->value.length + 1 >
                                          sizeof(RespInstr->AcceptLanguageHeader)) {
                                          size_t length =
                                                  sizeof(RespInstr->AcceptLanguageHeader) -
                                                  1;
                                          memcpy(RespInstr->AcceptLanguageHeader,
                                                  TmpBuf,
                                                  length);
                                          RespInstr
                                                  ->AcceptLanguageHeader[length] =
                                                  '\0';
                                  } else {
                                          memcpy(RespInstr->AcceptLanguageHeader,
                                                  TmpBuf,
                                                  header->value.length + 1);
                                  }
                                  break;
                          default:
                                  /*
                                     TODO
                                   */
                                  /*
                                     header.value is the value.
                                   */
                                  /*
                                     case HDR_CONTENT_TYPE: return 1;
                                     case HDR_CONTENT_LANGUAGE:return 1;
                                     case HDR_LOCATION: return 1;
                                     case HDR_CONTENT_LOCATION:return 1;
                                     case HDR_ACCEPT: return 1;
                                     case HDR_ACCEPT_CHARSET: return 1;
                                     case HDR_USER_AGENT: return 1;
                                   */
  
                                  /*Header check for encoding */
                                  /*
                                     case HDR_ACCEPT_RANGE:
                                     case HDR_CONTENT_RANGE:
                                     case HDR_IF_RANGE:
                                   */
  
                                  /*Header check for encoding */
                                  /*
                                     case HDR_ACCEPT_ENCODING:
                                     if(StrStr(TmpBuf, "identity"))
                                     {
                                     break;
                                     }
                                     else return -1;
                                     case HDR_CONTENT_ENCODING:
                                     case HDR_TRANSFER_ENCODING:
                                   */
                                  break;
                          }
                  }
                  node = ListNext(&Req->headers, node);
          }
          free(TmpBuf);
  
          return RetCode;
  }
  
  /*!
   * \brief Processes the request and returns the result in the output
   * parameters.
   *
   * \return
   * \li \c HTTP_BAD_REQUEST
   * \li \c HTTP_INTERNAL_SERVER_ERROR
   * \li \c HTTP_REQUEST_RANGE_NOT_SATISFIABLE
   * \li \c HTTP_FORBIDDEN
   * \li \c HTTP_NOT_FOUND
   * \li \c HTTP_NOT_ACCEPTABLE
   * \li \c HTTP_OK
   */
  static int process_request(
          /*! Library handle. */
          UpnpLib *p,
          /*! [in] Socket info. */
          SOCKINFO *info,
          /*! [in] HTTP Request message. */
          http_message_t *req,
          /*! [out] Tpye of response. */
          enum resp_type *rtype,
          /*! [out] Headers. */
          membuffer *headers,
          /*! [out] Get filename from request document. */
          membuffer *filename,
          /*! [out] Xml alias document from the request document. */
          xml_alias_t *alias,
          /*! [out] Send Instruction object where the response is set up.
           */
          struct SendInstruction *RespInstr)
  {
          int code;
          int err_code;
  
          char *request_doc;
          UpnpFileInfo *finfo;
          time_t aux_LastModified;
          int using_alias;
          int using_virtual_dir;
          uri_type *url;
          const char *temp_str;
          int resp_major;
          int resp_minor;
          int alias_grabbed;
          size_t dummy;
          memptr hdr_value;
          xml_alias_t *gAliasDoc = UpnpLib_getnc_gAliasDoc(p);
          struct VirtualDirCallbacks *virtualDirCallback =
                  UpnpLib_getnc_virtualDirCallback(p);
          membuffer *gDocumentRootDir = UpnpLib_getnc_gDocumentRootDir(p);
  
          print_http_headers(p, req);
          url = &req->uri;
          assert(req->method == HTTPMETHOD_GET ||
                  req->method == HTTPMETHOD_HEAD ||
                  req->method == HTTPMETHOD_POST ||
                  req->method == HTTPMETHOD_SIMPLEGET);
          /* init */
          memset(&finfo, 0, sizeof(finfo));
          request_doc = NULL;
          finfo = UpnpFileInfo_new();
          alias_grabbed = 0;
          err_code = HTTP_INTERNAL_SERVER_ERROR; /* default error */
          using_virtual_dir = 0;
          using_alias = 0;
  
          http_CalcResponseVersion(req->major_version,
                  req->minor_version,
                  &resp_major,
                  &resp_minor);
          /* */
          /* remove dots */
          /* */
          request_doc = malloc(url->pathquery.size + 1);
          if (request_doc == NULL) {
                  goto error_handler; /* out of mem */
          }
          memcpy(request_doc, url->pathquery.buff, url->pathquery.size);
          request_doc[url->pathquery.size] = '\0';
          dummy = url->pathquery.size;
          remove_escaped_chars(request_doc, &dummy);
          code = remove_dots(request_doc, url->pathquery.size);
          if (code != 0) {
                  err_code = HTTP_FORBIDDEN;
                  goto error_handler;
          }
          if (*request_doc != '/') {
                  /* no slash */
                  err_code = HTTP_BAD_REQUEST;
                  goto error_handler;
          }
          if (isFileInVirtualDir(p, request_doc, &RespInstr->Cookie)) {
                  using_virtual_dir = 1;
                  RespInstr->IsVirtualFile = 1;
                  if (membuffer_assign_str(filename, request_doc) != 0) {
                          goto error_handler;
                  }
          } else {
                  /* try using alias */
                  if (is_valid_alias(gAliasDoc)) {
                          alias_grab(p, alias);
                          alias_grabbed = 1;
                          using_alias = get_alias(request_doc, alias, finfo);
                          if (using_alias == 1) {
                                  UpnpFileInfo_set_ContentType(
                                          finfo, "text/xml; charset=\"utf-8\"");
                                  if (UpnpFileInfo_get_ContentType(finfo) ==
                                          NULL) {
                                          goto error_handler;
                                  }
                          }
                  }
          }
          if (using_virtual_dir) {
                  if (req->method != HTTPMETHOD_POST) {
                          if ((code = httpmsg_list_headers(req,
                                       (UpnpListHead *)
                                               UpnpFileInfo_get_HttpHeadersList(
                                                       finfo))) != HTTP_OK) {
                                  err_code = code;
                                  goto error_handler;
                          }
  
                          UpnpFileInfo_set_CtrlPtIPAddr(
                                  finfo, &info->foreign_sockaddr);
  
                          if (httpmsg_find_hdr(req, HDR_USER_AGENT, &hdr_value) !=
                                  NULL) {
                                  UpnpFileInfo_strncpy_Os(
                                          finfo, hdr_value.buf, hdr_value.length);
                          }
  
                          /* get file info */
                          if (virtualDirCallback->get_info(filename->buf,
                                      finfo,
                                      RespInstr->Cookie,
                                      &RespInstr->RequestCookie) != 0) {
                                  err_code = HTTP_NOT_FOUND;
                                  goto error_handler;
                          }
                          /* try index.html if req is a dir */
                          if (UpnpFileInfo_get_IsDirectory(finfo)) {
                                  if (filename->buf[filename->length - 1] ==
                                          '/') {
                                          temp_str = "index.html";
                                  } else {
                                          temp_str = "/index.html";
                                  }
                                  if (membuffer_append_str(filename, temp_str) !=
                                          0) {
                                          goto error_handler;
                                  }
                                  /* get info */
                                  if (virtualDirCallback->get_info(filename->buf,
                                              finfo,
                                              RespInstr->Cookie,
                                              &RespInstr->RequestCookie) !=
                                                  UPNP_E_SUCCESS ||
                                          UpnpFileInfo_get_IsDirectory(finfo)) {
                                          err_code = HTTP_NOT_FOUND;
                                          goto error_handler;
                                  }
                          }
                          /* not readable */
                          if (!UpnpFileInfo_get_IsReadable(finfo)) {
                                  err_code = HTTP_FORBIDDEN;
                                  goto error_handler;
                          }
                          /* finally, get content type */
                          /* if ( get_content_type(filename->buf,
                           * &content_type)
                           * != 0 ) */
                          /*{ */
                          /*  goto error_handler; */
                          /* } */
                  }
          } else if (!using_alias) {
                  if (gDocumentRootDir->length == 0) {
                          goto error_handler;
                  }
                  /* */
                  /* get file name */
                  /* */
  
                  /* filename str */
                  if (membuffer_assign_str(filename, gDocumentRootDir->buf) !=
                                  0 ||
                          membuffer_append_str(filename, request_doc) != 0) {
                          goto error_handler; /* out of mem */
                  }
                  /* remove trailing slashes */
                  while (filename->length > 0 &&
                          filename->buf[filename->length - 1] == '/') {
                          membuffer_delete(filename, filename->length - 1, 1);
                  }
                  if (req->method != HTTPMETHOD_POST) {
                          /* get info on file */
                          if (get_file_info(p, filename->buf, finfo) != 0) {
                                  err_code = HTTP_NOT_FOUND;
                                  goto error_handler;
                          }
                          /* try index.html if req is a dir */
                          if (UpnpFileInfo_get_IsDirectory(finfo)) {
                                  if (filename->buf[filename->length - 1] ==
                                          '/') {
                                          temp_str = "index.html";
                                  } else {
                                          temp_str = "/index.html";
                                  }
                                  if (membuffer_append_str(filename, temp_str) !=
                                          0) {
                                          goto error_handler;
                                  }
                                  /* get info */
                                  if (get_file_info(p, filename->buf, finfo) !=
                                                  0 ||
                                          UpnpFileInfo_get_IsDirectory(finfo)) {
                                          err_code = HTTP_NOT_FOUND;
                                          goto error_handler;
                                  }
                          }
                          /* not readable */
                          if (!UpnpFileInfo_get_IsReadable(finfo)) {
                                  err_code = HTTP_FORBIDDEN;
                                  goto error_handler;
                          }
                  }
                  /* finally, get content type */
                  /*      if ( get_content_type(filename->buf,
                   * &content_type) != 0
                   * ) */
                  /*      { */
                  /*          goto error_handler; */
                  /*      } */
          }
          RespInstr->ReadSendSize = UpnpFileInfo_get_FileLength(finfo);
          /* Check other header field. */
          code = CheckOtherHTTPHeaders(
                  req, RespInstr, UpnpFileInfo_get_FileLength(finfo));
          if (code != HTTP_OK) {
                  err_code = code;
                  goto error_handler;
          }
          if (req->method == HTTPMETHOD_POST) {
                  *rtype = RESP_POST;
                  err_code = HTTP_OK;
                  goto error_handler;
          }
  
          /* Check if chunked encoding should be used. */
          if (using_virtual_dir &&
                  UpnpFileInfo_get_FileLength(finfo) == UPNP_USING_CHUNKED) {
                  /* Chunked encoding is only supported by HTTP 1.1
                   * clients */
                  if (resp_major == 1 && resp_minor == 1) {
                          RespInstr->IsChunkActive = 1;
                  } else {
                          /* The virtual callback indicates that we should
                           * use chunked encoding however the client
                           * doesn't support it. Return with an internal
                           * server error. */
                          err_code = HTTP_NOT_ACCEPTABLE;
                          goto error_handler;
                  }
          }
  
          aux_LastModified = UpnpFileInfo_get_LastModified(finfo);
          if (RespInstr->IsRangeActive && RespInstr->IsChunkActive) {
                  /* Content-Range: bytes 222-3333/4000
                   * HTTP_PARTIAL_CONTENT */
                  /* Transfer-Encoding: chunked */
                  if (http_MakeMessage(p,
                              headers,
                              resp_major,
                              resp_minor,
                              "R"
                              "T"
                              "GKLD"
                              "s"
                              "tcS"
                              "Xc"
                              "ECc",
                              HTTP_PARTIAL_CONTENT, /* status code */
                              UpnpFileInfo_get_ContentType(
                                      finfo), /* content type */
                              RespInstr, /* range info */
                              RespInstr, /* language info */
                              "LAST-MODIFIED: ",
                              &aux_LastModified,
                              X_USER_AGENT,
                              UpnpFileInfo_get_HttpHeadersList(finfo)) != 0) {
                          goto error_handler;
                  }
          } else if (RespInstr->IsRangeActive && !RespInstr->IsChunkActive) {
                  /* Content-Range: bytes 222-3333/4000
                   * HTTP_PARTIAL_CONTENT */
                  if (http_MakeMessage(p,
                              headers,
                              resp_major,
                              resp_minor,
                              "R"
                              "N"
                              "T"
                              "GLD"
                              "s"
                              "tcS"
                              "Xc"
                              "ECc",
                              HTTP_PARTIAL_CONTENT, /* status code */
                              RespInstr->ReadSendSize, /* content length */
                              UpnpFileInfo_get_ContentType(
                                      finfo), /* content type */
                              RespInstr, /* range info */
                              RespInstr, /* language info */
                              "LAST-MODIFIED: ",
                              &aux_LastModified,
                              X_USER_AGENT,
                              UpnpFileInfo_get_HttpHeadersList(finfo)) != 0) {
                          goto error_handler;
                  }
          } else if (!RespInstr->IsRangeActive && RespInstr->IsChunkActive) {
                  /* Transfer-Encoding: chunked */
                  if (http_MakeMessage(p,
                              headers,
                              resp_major,
                              resp_minor,
                              "RK"
                              "TLD"
                              "s"
                              "tcS"
                              "Xc"
                              "ECc",
                              HTTP_OK, /* status code */
                              UpnpFileInfo_get_ContentType(
                                      finfo), /* content type */
                              RespInstr, /* language info */
                              "LAST-MODIFIED: ",
                              &aux_LastModified,
                              X_USER_AGENT,
                              UpnpFileInfo_get_HttpHeadersList(finfo)) != 0) {
                          goto error_handler;
                  }
          } else {
                  /* !RespInstr->IsRangeActive &&
                   * !RespInstr->IsChunkActive */
                  if (RespInstr->ReadSendSize >= 0) {
                          if (http_MakeMessage(p,
                                      headers,
                                      resp_major,
                                      resp_minor,
                                      "R"
                                      "N"
                                      "TLD"
                                      "s"
                                      "tcS"
                                      "Xc"
                                      "ECc",
                                      HTTP_OK, /* status code */
                                      RespInstr->ReadSendSize, /* content
                                                                  length */
                                      UpnpFileInfo_get_ContentType(
                                              finfo), /* content type */
                                      RespInstr, /* language info */
                                      "LAST-MODIFIED: ",
                                      &aux_LastModified,
                                      X_USER_AGENT,
                                      UpnpFileInfo_get_HttpHeadersList(finfo)) !=
                                  0) {
                                  goto error_handler;
                          }
                  } else {
                          if (http_MakeMessage(p,
                                      headers,
                                      resp_major,
                                      resp_minor,
                                      "R"
                                      "TLD"
                                      "s"
                                      "tcS"
                                      "Xc"
                                      "ECc",
                                      HTTP_OK, /* status code */
                                      UpnpFileInfo_get_ContentType(
                                              finfo), /* content type */
                                      RespInstr, /* language info */
                                      "LAST-MODIFIED: ",
                                      &aux_LastModified,
                                      X_USER_AGENT,
                                      UpnpFileInfo_get_HttpHeadersList(finfo)) !=
                                  0) {
                                  goto error_handler;
                          }
                  }
          }
          if (req->method == HTTPMETHOD_HEAD) {
                  *rtype = RESP_HEADERS;
          } else if (using_alias) {
                  /* GET xml */
                  *rtype = RESP_XMLDOC;
          } else if (using_virtual_dir) {
                  *rtype = RESP_WEBDOC;
          } else {
                  /* GET filename */
                  *rtype = RESP_FILEDOC;
          }
          /* simple get http 0.9 as specified in http 1.0 */
          /* don't send headers */
          if (req->method == HTTPMETHOD_SIMPLEGET) {
                  membuffer_destroy(headers);
          }
          err_code = HTTP_OK;
  
  error_handler:
          free(request_doc);
          free_http_headers_list(
                  (UpnpListHead *)UpnpFileInfo_get_HttpHeadersList(finfo));
          UpnpFileInfo_delete(finfo);
          if (err_code != HTTP_OK && alias_grabbed) {
                  alias_release(p, alias);
          }
  
          return err_code;
  }
  
  /*!
   * \brief Receives the HTTP post message.
   *
   * \return
   * \li \c HTTP_INTERNAL_SERVER_ERROR
   * \li \c HTTP_UNAUTHORIZED
   * \li \c HTTP_BAD_REQUEST
   * \li \c HTTP_SERVICE_UNAVAILABLE
   * \li \c HTTP_OK
   */
  static int http_RecvPostMessage(
          /*! Library handle. */
          UpnpLib *p,
          /*! HTTP Parser object. */
          http_parser_t *parser,
          /*! [in] Socket Information object. */
          SOCKINFO *info,
          /*! File where received data is copied to. */
          char *filename,
          /*! Send Instruction object which gives information whether the
           * file is a virtual file or not. */
          struct SendInstruction *Instr)
  {
          size_t Data_Buf_Size = 1024;
          char Buf[1024];
          int Timeout = -1;
          FILE *Fp;
          parse_status_t status = PARSE_OK;
          int ok_on_close = 0;
          size_t entity_offset = 0;
          int num_read = 0;
          int ret_code = HTTP_OK;
          const struct VirtualDirCallbacks *virtualDirCallback =
                  UpnpLib_get_virtualDirCallback(p);
  
          if (Instr && Instr->IsVirtualFile) {
                  Fp = (virtualDirCallback->open)(filename,
                          UPNP_WRITE,
                          Instr->Cookie,
                          Instr->RequestCookie);
                  if (Fp == NULL)
                          return HTTP_INTERNAL_SERVER_ERROR;
          } else {
                  return HTTP_NOT_IMPLEMENTED;
          }
          parser->position = POS_ENTITY;
          do {
                  /* first parse what has already been gotten */
                  if (parser->position != POS_COMPLETE)
                          status = parser_parse_entity(p, parser);
                  if (status == PARSE_INCOMPLETE_ENTITY) {
                          /* read until close */
                          ok_on_close = 1;
                  } else if ((status != PARSE_SUCCESS) &&
                          (status != PARSE_CONTINUE_1) &&
                          (status != PARSE_INCOMPLETE)) {
                          /* error */
                          ret_code = HTTP_BAD_REQUEST;
                          goto ExitFunction;
                  }
                  /* read more if necessary entity */
                  while (entity_offset + Data_Buf_Size >
                                  parser->msg.entity.length &&
                          parser->position != POS_COMPLETE) {
                          num_read = sock_read(info, Buf, sizeof(Buf), &Timeout);
                          if (num_read > 0) {
                                  /* append data to buffer */
                                  if (membuffer_append(&parser->msg.msg,
                                              Buf,
                                              (size_t)num_read) != 0) {
                                          /* set failure status */
                                          parser->http_error_code =
                                                  HTTP_INTERNAL_SERVER_ERROR;
                                          ret_code = HTTP_INTERNAL_SERVER_ERROR;
                                          goto ExitFunction;
                                  }
                                  status = parser_parse_entity(p, parser);
                                  if (status == PARSE_INCOMPLETE_ENTITY) {
                                          /* read until close */
                                          ok_on_close = 1;
                                  } else if ((status != PARSE_SUCCESS) &&
                                          (status != PARSE_CONTINUE_1) &&
                                          (status != PARSE_INCOMPLETE)) {
                                          ret_code = HTTP_BAD_REQUEST;
                                          goto ExitFunction;
                                  }
                          } else if (num_read == 0) {
                                  if (ok_on_close) {
                                          UpnpPrintf(UpnpLib_get_Log(p),
                                                  UPNP_INFO,
                                                  HTTP,
                                                  __FILE__,
                                                  __LINE__,
                                                  "<<< (RECVD) "
                                                  "<<<\n%s\n-------------"
                                                  "----\n",
                                                  parser->msg.msg.buf);
                                          print_http_headers(p, &parser->msg);
                                          parser->position = POS_COMPLETE;
                                  } else {
                                          /* partial msg or response */
                                          parser->http_error_code =
                                                  HTTP_BAD_REQUEST;
                                          ret_code = HTTP_BAD_REQUEST;
                                          goto ExitFunction;
                                  }
                          } else {
                                  ret_code = HTTP_SERVICE_UNAVAILABLE;
                                  goto ExitFunction;
                          }
                  }
                  if ((entity_offset + Data_Buf_Size) >
                          parser->msg.entity.length) {
                          Data_Buf_Size =
                                  parser->msg.entity.length - entity_offset;
                  }
                  memcpy(Buf,
                          &parser->msg.msg.buf[parser->entity_start_position +
                                  entity_offset],
                          Data_Buf_Size);
                  entity_offset += Data_Buf_Size;
                  if (Instr && Instr->IsVirtualFile) {
                          int n = virtualDirCallback->write(Fp,
                                  Buf,
                                  Data_Buf_Size,
                                  Instr->Cookie,
                                  Instr->RequestCookie);
                          if (n < 0) {
                                  ret_code = HTTP_INTERNAL_SERVER_ERROR;
                                  goto ExitFunction;
                          }
                  } else {
                          size_t n = fwrite(Buf, 1, Data_Buf_Size, Fp);
                          if (n != Data_Buf_Size) {
                                  ret_code = HTTP_INTERNAL_SERVER_ERROR;
                                  goto ExitFunction;
                          }
                  }
          } while (parser->position != POS_COMPLETE ||
                  entity_offset != parser->msg.entity.length);
  ExitFunction:
          if (Instr && Instr->IsVirtualFile) {
                  virtualDirCallback->close(
                          Fp, Instr->Cookie, Instr->RequestCookie);
          } else {
                  fclose(Fp);
          }
  
          return ret_code;
  }
  
  void web_server_callback(UpnpLib *p,
          http_parser_t *parser,
          /* INOUT */ http_message_t *req,
          SOCKINFO *info)
  {
          int ret;
          int timeout = -1;
          enum resp_type rtype = 0;
          membuffer headers;
          membuffer filename;
          xml_alias_t xmldoc;
          struct SendInstruction RespInstr;
  
          /* init */
          memset(&RespInstr, 0, sizeof(RespInstr));
          membuffer_init(&headers);
          membuffer_init(&filename);
  
          /* Process request should create the different kind of header
           * depending on the type of request. */
          ret = process_request(
                  p, info, req, &rtype, &headers, &filename, &xmldoc, &RespInstr);
          if (ret != HTTP_OK) {
                  /* send error code */
                  http_SendStatusResponse(
                          p, info, ret, req->major_version, req->minor_version);
          } else {
                  /* send response */
                  switch (rtype) {
                  case RESP_FILEDOC:
                          http_SendMessage(p,
                                  info,
                                  &timeout,
                                  "Ibf",
                                  &RespInstr,
                                  headers.buf,
                                  headers.length,
                                  filename.buf);
                          break;
                  case RESP_XMLDOC:
                          http_SendMessage(p,
                                  info,
                                  &timeout,
                                  "Ibb",
                                  &RespInstr,
                                  headers.buf,
                                  headers.length,
                                  xmldoc.doc.buf,
                                  xmldoc.doc.length);
                          alias_release(p, &xmldoc);
                          break;
                  case RESP_WEBDOC:
                          /*http_SendVirtualDirDoc(info, &timeout, "Ibf",
                                  &RespInstr,
                                  headers.buf, headers.length,
                                  filename.buf);*/
                          http_SendMessage(p,
                                  info,
                                  &timeout,
                                  "Ibf",
                                  &RespInstr,
                                  headers.buf,
                                  headers.length,
                                  filename.buf);
                          break;
                  case RESP_HEADERS:
                          /* headers only */
                          http_SendMessage(p,
                                  info,
                                  &timeout,
                                  "b",
                                  headers.buf,
                                  headers.length);
                          break;
                  case RESP_POST:
                          /* headers only */
                          ret = http_RecvPostMessage(
                                  p, parser, info, filename.buf, &RespInstr);
                          /* Send response. */
                          http_MakeMessage(p,
                                  &headers,
                                  1,
                                  1,
                                  "RTLSXcCc",
                                  ret,
                                  "text/html",
                                  &RespInstr,
                                  X_USER_AGENT);
                          http_SendMessage(p,
                                  info,
                                  &timeout,
                                  "b",
                                  headers.buf,
                                  headers.length);
                          break;
                  default:
                          UpnpPrintf(UpnpLib_get_Log(p),
                                  UPNP_INFO,
                                  HTTP,
                                  __FILE__,
                                  __LINE__,
                                  "webserver: Invalid response type "
                                  "received.\n");
                          assert(0);
                  }
          }
          UpnpPrintf(UpnpLib_get_Log(p),
                  UPNP_INFO,
                  HTTP,
                  __FILE__,
                  __LINE__,
                  "webserver: request processed...\n");
          membuffer_destroy(&headers);
          membuffer_destroy(&filename);
  }
  #endif /* EXCLUDE_WEB_SERVER */
  