/************************************************************************** * * Copyright (c) 2000-2003 Intel Corporation * 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. * **************************************************************************/ /****************************************************************************** * Purpose: This file defines the Web Server and has functions to carry out * operations of the Web Server. ******************************************************************************/ #include "config.h" #include "webserver.h" #include "FileInfo.h" #include "httpparser.h" #include "httpreadwrite.h" #include "ithread.h" #include "membuffer.h" #include "ssdplib.h" #include "statcodes.h" #include "strintmap.h" #include "unixutil.h" #include "upnp.h" #include "upnpapi.h" #include "util.h" #include "VirtualDir.h" #include #include #include #ifdef UPNP_USE_BCBPP /* Do not #include */ /* Do not #include */ #else #include #include #endif /* !UPNP_USE_BCBPP */ #ifdef WIN32 /* Do not #include */ #else #include #endif /* Response Types */ enum resp_type { RESP_FILEDOC, RESP_XMLDOC, RESP_HEADERS, RESP_WEBDOC, RESP_POST }; // mapping of file extension to content-type of document struct document_type_t { const char *file_ext; const char *content_type; const char *content_subtype; }; struct xml_alias_t { membuffer name; // name of DOC from root; e.g.: /foo/bar/mydesc.xml membuffer doc; // the XML document contents time_t last_modified; int *ct; }; static const char *gMediaTypes[] = { NULL, // 0 "audio", // 1 "video", // 2 "image", // 3 "application", // 4 "text" // 5 }; /* 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_MEDIA_TYPES 69 #define NUM_HTTP_HEADER_NAMES 33 // 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" "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 ************************************************************************/ static struct document_type_t gMediaTypeList[NUM_MEDIA_TYPES]; membuffer gDocumentRootDir; // a local dir which serves as webserver root static struct xml_alias_t gAliasDoc; // XML document static ithread_mutex_t gWebMutex; extern str_int_entry Http_Header_Names[NUM_HTTP_HEADER_NAMES]; /************************************************************************ * Function: has_xml_content_type * * Parameters: * none * * Description: decodes list and stores it in gMediaTypeList * * Returns: * void ************************************************************************/ static UPNP_INLINE void media_list_init( void ) { int i; const char *s = gEncodedMediaTypes; struct document_type_t *doc_type; for( i = 0; *s != '\0'; i++ ) { doc_type = &gMediaTypeList[i]; doc_type->file_ext = s; s += strlen( s ) + 1; // point to type doc_type->content_type = gMediaTypes[( int )*s]; // set cont-type s++; // point to subtype doc_type->content_subtype = s; s += strlen( s ) + 1; // next entry } assert( i == NUM_MEDIA_TYPES ); } /************************************************************************ * Function: has_xml_content_type * * Parameters: * IN const char* extension ; * OUT const char** con_type, * OUT const char** con_subtype * * Description: Based on the extension, returns the content type and * content subtype * * Returns: * 0 on success; * -1 on error ************************************************************************/ static UPNP_INLINE int search_extension( IN const char *extension, OUT const char **con_type, OUT const char **con_subtype ) { int top, mid, bot; int cmp; top = 0; bot = NUM_MEDIA_TYPES - 1; while( top <= bot ) { mid = ( top + bot ) / 2; cmp = strcasecmp( extension, gMediaTypeList[mid].file_ext ); if( cmp > 0 ) { top = mid + 1; // look below mid } else if( cmp < 0 ) { bot = mid - 1; // look above mid } else // cmp == 0 { *con_type = gMediaTypeList[mid].content_type; *con_subtype = gMediaTypeList[mid].content_subtype; return 0; } } return -1; } /************************************************************************ * Function: get_content_type * * Parameters: * IN const char* filename, * OUT UpnpFileInfo *fileInfo * * Description: Based on the extension, sets the content type of fileInfo * based on type and content subtype. If content type and sub type * are not found, unknown types are used * * Returns: * 0 - On Sucess * UPNP_E_OUTOF_MEMORY - on memory allocation failures ************************************************************************/ UPNP_INLINE int get_content_type( IN const char *filename, OUT UpnpFileInfo *fileInfo) { const char *extension; const char *type; const char *subtype; xboolean ctype_found = FALSE; char *temp = NULL; int length = 0; UpnpFileInfo_set_ContentType(fileInfo, NULL); // get ext extension = strrchr( filename, '.' ); if (extension != NULL) { if( search_extension( extension + 1, &type, &subtype ) == 0 ) { ctype_found = TRUE; } } if (!ctype_found) { // unknown content type type = gMediaTypes[APPLICATION_INDEX]; subtype = "octet-stream"; } length = strlen( type ) + strlen( "/" ) + strlen( subtype ) + 1; temp = ( char * )malloc( length ); if (!temp) { return UPNP_E_OUTOF_MEMORY; } sprintf( temp, "%s/%s", type, subtype ); UpnpFileInfo_set_ContentType(fileInfo, temp); free(temp); if (!UpnpFileInfo_get_ContentType(fileInfo)) { return UPNP_E_OUTOF_MEMORY; } return 0; } /************************************************************************ * Function: glob_alias_init * * Parameters: * none * * Description: Initialize the global XML document. Allocate buffers * for the XML document * * Returns: * void ************************************************************************/ static UPNP_INLINE void glob_alias_init( void ) { struct xml_alias_t *alias = &gAliasDoc; membuffer_init( &alias->doc ); membuffer_init( &alias->name ); alias->ct = NULL; alias->last_modified = 0; } /************************************************************************ * Function: is_valid_alias * * Parameters: * IN const struct xml_alias_t* alias ; XML alias object * * Description: Check for the validity of the XML object buffer * * Returns: * BOOLEAN ************************************************************************/ static UPNP_INLINE xboolean is_valid_alias( IN const struct xml_alias_t *alias ) { return alias->doc.buf != NULL; } /************************************************************************ * Function: alias_grab * * Parameters: * OUT struct xml_alias_t* alias ; XML alias object * * Description: Copy the contents of the global XML document into the * local OUT parameter * * Returns: * void ************************************************************************/ static void alias_grab( OUT struct xml_alias_t *alias ) { ithread_mutex_lock( &gWebMutex ); assert( is_valid_alias( &gAliasDoc ) ); memcpy( alias, &gAliasDoc, sizeof( struct xml_alias_t ) ); *alias->ct = *alias->ct + 1; ithread_mutex_unlock( &gWebMutex ); } /************************************************************************ * Function: alias_release * * Parameters: * IN struct xml_alias_t* alias ; XML alias object * * Description: Release the XML document referred to by the IN parameter * Free the allocated buffers associated with this object * * Returns: * void ************************************************************************/ static void alias_release( IN struct xml_alias_t *alias ) { ithread_mutex_lock( &gWebMutex ); // ignore invalid alias if( !is_valid_alias( alias ) ) { ithread_mutex_unlock( &gWebMutex ); return; } assert( alias->ct > 0 ); *alias->ct = *alias->ct - 1; if( *alias->ct <= 0 ) { membuffer_destroy( &alias->doc ); membuffer_destroy( &alias->name ); free( alias->ct ); } ithread_mutex_unlock( &gWebMutex ); } /************************************************************************ * Function: web_server_set_alias * * Parameters: * alias_name: webserver name of alias; created by caller and freed by * caller (doesn't even have to be malloc()d .) * alias_content: the xml doc; this is allocated by the caller; and * freed by the web server * alias_content_length: length of alias body in bytes * last_modified: time when the contents of alias were last * changed (local time) * * Description: Replaces current alias with the given alias. To remove * the current alias, set alias_name to NULL. * * Returns: * 0 - OK * UPNP_E_OUTOF_MEMORY: note: alias_content is not freed here ************************************************************************/ int web_server_set_alias( IN const char *alias_name, IN const char *alias_content, IN size_t alias_content_length, IN time_t last_modified ) { int ret_code; struct xml_alias_t alias; alias_release( &gAliasDoc ); if( alias_name == NULL ) { // 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 ithread_mutex_lock( &gWebMutex ); gAliasDoc = alias; ithread_mutex_unlock( &gWebMutex ); return 0; } while( FALSE ); // 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() { int ret = 0; if (bWebServerState == WEB_SERVER_DISABLED) { // decode media list media_list_init(); membuffer_init(&gDocumentRootDir); glob_alias_init(); pVirtualDirList = NULL; // Initialize callbacks virtualDirCallback.get_info = NULL; virtualDirCallback.open = NULL; virtualDirCallback.read = NULL; virtualDirCallback.write = NULL; virtualDirCallback.seek = NULL; virtualDirCallback.close = NULL; if (ithread_mutex_init(&gWebMutex, NULL) == -1) { ret = UPNP_E_OUTOF_MEMORY; } else { bWebServerState = WEB_SERVER_ENABLED; } } return ret; } /************************************************************************ * Function: web_server_destroy * * Parameters: * none * * Description: Release memory allocated for the global web server root * directory and the global XML document * Resets the flag bWebServerState to WEB_SERVER_DISABLED * * Returns: * void ************************************************************************/ void web_server_destroy( void ) { int ret; if( bWebServerState == WEB_SERVER_ENABLED ) { membuffer_destroy( &gDocumentRootDir ); alias_release( &gAliasDoc ); ithread_mutex_lock( &gWebMutex ); memset( &gAliasDoc, 0, sizeof( struct xml_alias_t ) ); ithread_mutex_unlock( &gWebMutex ); ret = ithread_mutex_destroy( &gWebMutex ); assert( ret == 0 ); bWebServerState = WEB_SERVER_DISABLED; } } /************************************************************************ * Function: get_file_info * * Parameters: * IN const char *filename; Filename having the description document * OUT UpnpFileInfo *info; 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. * * Description: Release memory allocated for the global web server root * directory and the global XML document * Resets the flag bWebServerState to WEB_SERVER_DISABLED * * Returns: * int ************************************************************************/ static int get_file_info( IN const char *filename, OUT UpnpFileInfo *info) { int code; struct stat s; FILE *fp; int rc = 0; UpnpFileInfo_set_ContentType(info, NULL); code = stat(filename, &s); if (code == -1) { return -1; } if (S_ISDIR(s.st_mode)) { UpnpFileInfo_set_IsDirectory(info, TRUE); } else if (S_ISREG(s.st_mode)) { UpnpFileInfo_set_IsDirectory(info, FALSE); } else { return -1; } // check readable fp = fopen(filename, "r"); UpnpFileInfo_set_IsReadable(info, fp != NULL); if (fp) { fclose(fp); } UpnpFileInfo_set_FileLength(info, s.st_size); UpnpFileInfo_set_LastModified(info, &s.st_mtime); rc = get_content_type(filename, info); UpnpPrintf( UPNP_INFO, HTTP, __FILE__, __LINE__, "file info: %s, length: %lld, last_mod=%s readable=%d\n", filename, (long long)UpnpFileInfo_get_FileLength(info), asctime(gmtime(UpnpFileInfo_get_LastModified(info))), UpnpFileInfo_get_IsReadable(info)); return rc; } /************************************************************************ * Function: web_server_set_root_dir * * Parameters: * IN const char* root_dir ; String having the root directory for the * document * * Description: Assign the path specfied by the IN const char* root_dir * parameter to the global Document root directory. Also check for * path names ending in '/' * * Returns: * int ************************************************************************/ int web_server_set_root_dir( IN const char *root_dir ) { int index; int ret; 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; } /************************************************************************ * Function: get_alias * * Parameters: * IN const char *request_file; request file passed in to be compared with * OUT struct xml_alias_t* alias ; xml alias object which has a file name * stored * OUT UpnpFileInfo *info; File information object which will be * filled up if the file comparison succeeds * * Description: Compare the files names between the one on the XML alias * the one passed in as the input parameter. If equal extract file * information * * Returns: * TRUE - On Success * FALSE if request is not an alias ************************************************************************/ static UPNP_INLINE xboolean get_alias( IN const char *request_file, OUT struct xml_alias_t *alias, OUT UpnpFileInfo *info) { int cmp = strcmp(alias->name.buf, request_file); if (cmp == 0) { // fill up info UpnpFileInfo_set_FileLength(info, alias->doc.length); UpnpFileInfo_set_IsDirectory(info, FALSE); UpnpFileInfo_set_IsReadable(info, TRUE); UpnpFileInfo_set_LastModified(info, &alias->last_modified); } return cmp == 0; } /************************************************************************ * Function: isFileInVirtualDir * * Parameters: * IN char *filePath ; directory path to be tested for virtual directory * * Description: Compares filePath with paths from the list of virtual * directory lists * * Returns: * BOOLEAN ************************************************************************/ int isFileInVirtualDir( IN char *filePath ) { virtualDirList *pCurVirtualDir; int webDirLen; pCurVirtualDir = pVirtualDirList; while( pCurVirtualDir != NULL ) { webDirLen = strlen( pCurVirtualDir->dirName ); if( pCurVirtualDir->dirName[webDirLen - 1] == '/' ) { if( strncmp( pCurVirtualDir->dirName, filePath, webDirLen ) == 0 ) return TRUE; } else { if( ( strncmp( pCurVirtualDir->dirName, filePath, webDirLen ) == 0 ) && ( filePath[webDirLen] == '/' || filePath[webDirLen] == '\0' || filePath[webDirLen] == '?' ) ) return TRUE; } pCurVirtualDir = pCurVirtualDir->next; } return FALSE; } /************************************************************************ * Function: ToUpperCase * * Parameters: * INOUT char * Str ; Input string to be converted * * Description: Converts input string to upper case * * Returns: * int ************************************************************************/ int ToUpperCase( char *Str ) { int i; for( i = 0; i < ( int )strlen( Str ); i++ ) Str[i] = toupper( Str[i] ); return 1; } /************************************************************************ * Function: StrStr * * Parameters: * IN char * S1 ; Input string * IN char * S2 ; Input sub-string * * Description: Finds a substring from a string * * Returns: * char * ptr - pointer to the first occurence of S2 in S1 ************************************************************************/ char * StrStr( char *S1, char *S2 ) { char *Str1, *Str2; char *Ptr, *Ret; int Pos; Str1 = ( char * )malloc( strlen( S1 ) + 2 ); if(!Str1) return NULL; Str2 = ( char * )malloc( strlen( S2 ) + 2 ); if (!Str2) { free(Str1); return NULL; } strcpy( Str1, S1 ); strcpy( Str2, S2 ); ToUpperCase( Str1 ); ToUpperCase( Str2 ); Ptr = strstr( Str1, Str2 ); if( Ptr == NULL ) return NULL; Pos = Ptr - Str1; Ret = S1 + Pos; free( Str1 ); free( Str2 ); return Ret; } /************************************************************************ * Function: StrTok * * Parameters: * IN char ** Src ; String containing the token * IN char * del ; Set of delimiter characters * * Description: Finds next token in a string * * Returns: * char * ptr - pointer to the first occurence of S2 in S1 ************************************************************************/ char * StrTok( char **Src, char *Del ) { char *TmpPtr, *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; } /************************************************************************ * Function: GetNextRange * * Parameters: * IN char ** SrcRangeStr ; string containing the token / range * OUT int * FirstByte ; gets the first byte of the token * OUT int * LastByte ; gets the last byte of the token * * Description: Returns a range of integers from a string * * Returns: int ; * always returns 1; ************************************************************************/ int GetNextRange( char **SrcRangeStr, off_t *FirstByte, 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; } /************************************************************************ * Function: CreateHTTPRangeResponseHeader * * Parameters: * char * ByteRangeSpecifier ; String containing the range * long FileLength ; Length of the file * OUT struct SendInstruction * Instr ; SendInstruction object * where the range operations will be stored * * Description: Fills in the Offset, read size and contents to send out * as an HTTP Range Response * * Returns: * HTTP_BAD_REQUEST * UPNP_E_OUTOF_MEMORY * HTTP_REQUEST_RANGE_NOT_SATISFIABLE * HTTP_OK ************************************************************************/ int CreateHTTPRangeResponseHeader( char *ByteRangeSpecifier, off_t FileLength, OUT struct SendInstruction *Instr ) { off_t FirstByte, LastByte; char *RangeInput, *Ptr; Instr->IsRangeActive = 1; Instr->ReadSendSize = FileLength; if( !ByteRangeSpecifier ) return HTTP_BAD_REQUEST; RangeInput = malloc( strlen( ByteRangeSpecifier ) + 1 ); if( !RangeInput ) return UPNP_E_OUTOF_MEMORY; strcpy( RangeInput, ByteRangeSpecifier ); //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 ) { free( RangeInput ); return HTTP_REQUEST_RANGE_NOT_SATISFIABLE; } 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; sprintf( Instr->RangeHeader, "CONTENT-RANGE: bytes %"PRId64"-%"PRId64"/%"PRId64"\r\n", (int64_t)FirstByte, (int64_t)LastByte, (int64_t)FileLength ); //Data between two range. } else if( FirstByte >= 0 && LastByte == -1 && FirstByte < FileLength ) { Instr->RangeOffset = FirstByte; Instr->ReadSendSize = FileLength - FirstByte; sprintf( Instr->RangeHeader, "CONTENT-RANGE: bytes %"PRId64"-%"PRId64"/%"PRId64"\r\n", (int64_t)FirstByte, (int64_t)(FileLength - 1), (int64_t)FileLength ); } else if( FirstByte == -1 && LastByte > 0 ) { if( LastByte >= FileLength ) { Instr->RangeOffset = 0; Instr->ReadSendSize = FileLength; sprintf( 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; sprintf( Instr->RangeHeader, "CONTENT-RANGE: bytes %"PRId64"-%"PRId64"/%"PRId64"\r\n", (int64_t)(FileLength - LastByte + 1), (int64_t)FileLength, (int64_t)FileLength ); } } else { free( RangeInput ); return HTTP_REQUEST_RANGE_NOT_SATISFIABLE; } } else { free( RangeInput ); return HTTP_REQUEST_RANGE_NOT_SATISFIABLE; } free( RangeInput ); return HTTP_OK; } /************************************************************************ * Function: CheckOtherHTTPHeaders * * Parameters: * IN http_message_t * Req ; HTTP Request message * OUT struct SendInstruction * RespInstr ; Send Instruction object to * data for the response * int FileSize ; Size of the file containing the request document * * Description: Get header id from the request parameter and take * appropriate action based on the ids. * as an HTTP Range Response * * Returns: * HTTP_BAD_REQUEST * UPNP_E_OUTOF_MEMORY * HTTP_REQUEST_RANGE_NOT_SATISFIABLE * HTTP_OK ************************************************************************/ int CheckOtherHTTPHeaders( IN http_message_t * Req, OUT struct SendInstruction *RespInstr, off_t FileSize ) { http_header_t *header; ListNode *node; //NNS: dlist_node* node; int index, RetCode = HTTP_OK; char *TmpBuf; TmpBuf = ( char * )malloc( LINE_SIZE ); if( !TmpBuf ) return UPNP_E_OUTOF_MEMORY; 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, FALSE ); if( header->value.length >= LINE_SIZE ) { free( TmpBuf ); TmpBuf = ( char * )malloc( header->value.length + 1 ); if( !TmpBuf ) return UPNP_E_OUTOF_MEMORY; } 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" ) ) { if( StrStr( TmpBuf, "trailers" ) != NULL ) { //means client will accept trailer RespInstr->IsTrailers = 1; } } } break; case HDR_CONTENT_LENGTH: { RespInstr->RecvWriteSize = atoi( TmpBuf ); break; } case HDR_RANGE: if( ( RetCode = CreateHTTPRangeResponseHeader( TmpBuf, FileSize, RespInstr ) ) != HTTP_OK ) { free( TmpBuf ); return RetCode; } 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_ACCEPT_LANGUAGE://return 1; case HDR_USER_AGENT: break;//return 1; */ //Header check for encoding /* case HDR_ACCEPT_RANGE: //Server capability. case HDR_CONTENT_RANGE://Response. 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: //Response */ break; } node = ListNext( &Req->headers, node ); } free( TmpBuf ); return RetCode; } /************************************************************************ * Function: process_request * * Parameters: * IN http_message_t *req ; HTTP Request message * OUT enum resp_type *rtype ; Tpye of response * OUT membuffer *headers ; * OUT membuffer *filename ; Get filename from request document * OUT struct xml_alias_t *alias ; Xml alias document from the * request document, * OUT struct SendInstruction * RespInstr ; Send Instruction object * where the response is set up. * * Description: Processes the request and returns the result in the OUT * parameters * * Returns: * HTTP_BAD_REQUEST * UPNP_E_OUTOF_MEMORY * HTTP_REQUEST_RANGE_NOT_SATISFIABLE * HTTP_OK ************************************************************************/ static int process_request( IN http_message_t * req, OUT enum resp_type *rtype, OUT membuffer * headers, OUT membuffer * filename, OUT struct xml_alias_t *alias, OUT struct SendInstruction *RespInstr ) { int code; int err_code; char *request_doc; UpnpFileInfo *finfo; xboolean using_alias; xboolean using_virtual_dir; uri_type *url; char *temp_str; int resp_major; int resp_minor; xboolean alias_grabbed; size_t dummy; const char *extra_headers; print_http_headers( req ); url = &req->uri; assert( req->method == HTTPMETHOD_GET || req->method == HTTPMETHOD_HEAD || req->method == HTTPMETHOD_POST || req->method == HTTPMETHOD_SIMPLEGET ); // init request_doc = NULL; finfo = UpnpFileInfo_new(); alias_grabbed = FALSE; err_code = HTTP_INTERNAL_SERVER_ERROR; // default error using_virtual_dir = FALSE; using_alias = FALSE; 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( request_doc ) ) { using_virtual_dir = TRUE; 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(alias); alias_grabbed = TRUE; using_alias = get_alias(request_doc, alias, finfo); if (using_alias == TRUE) { UpnpFileInfo_set_ContentType(finfo, "text/xml"); if (UpnpFileInfo_get_ContentType(finfo) == NULL) { goto error_handler; } } } } if (using_virtual_dir) { if (req->method != HTTPMETHOD_POST) { // get file info if (virtualDirCallback.get_info(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 (virtualDirCallback.get_info(filename->buf, finfo) != 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(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(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. err_code = CheckOtherHTTPHeaders(req, RespInstr, UpnpFileInfo_get_FileLength(finfo)); if ( err_code != HTTP_OK ) { goto error_handler; } if( req->method == HTTPMETHOD_POST ) { *rtype = RESP_POST; err_code = UPNP_E_SUCCESS; goto error_handler; } extra_headers = UpnpFileInfo_get_ExtraHeaders(finfo); if (!extra_headers) { extra_headers = ""; } if( RespInstr->IsRangeActive && RespInstr->IsChunkActive ) { // Content-Range: bytes 222-3333/4000 HTTP_PARTIAL_CONTENT // Transfer-Encoding: chunked if (http_MakeMessage( headers, resp_major, resp_minor, "R" "T" "GKD" "s" "tcS" "Xc" "sCc", HTTP_PARTIAL_CONTENT, // status code UpnpFileInfo_get_ContentType(finfo), // content type RespInstr, // range info "LAST-MODIFIED: ", UpnpFileInfo_get_LastModified(finfo), X_USER_AGENT, extra_headers) != 0 ) { goto error_handler; } } else if( RespInstr->IsRangeActive && !RespInstr->IsChunkActive ) { // Content-Range: bytes 222-3333/4000 HTTP_PARTIAL_CONTENT // Transfer-Encoding: chunked if (http_MakeMessage( headers, resp_major, resp_minor, "R" "N" "T" "GD" "s" "tcS" "Xc" "sCc", HTTP_PARTIAL_CONTENT, // status code RespInstr->ReadSendSize, // content length UpnpFileInfo_get_ContentType(finfo), // content type RespInstr, // range info "LAST-MODIFIED: ", UpnpFileInfo_get_LastModified(finfo), X_USER_AGENT, extra_headers) != 0 ) { goto error_handler; } } else if( !RespInstr->IsRangeActive && RespInstr->IsChunkActive ) { // Content-Range: bytes 222-3333/4000 HTTP_PARTIAL_CONTENT // Transfer-Encoding: chunked if (http_MakeMessage( headers, resp_major, resp_minor, "RK" "TD" "s" "tcS" "Xc" "sCc", HTTP_OK, // status code UpnpFileInfo_get_ContentType(finfo), // content type "LAST-MODIFIED: ", UpnpFileInfo_get_LastModified(finfo), X_USER_AGENT, extra_headers) != 0 ) { goto error_handler; } } else { // !RespInstr->IsRangeActive && !RespInstr->IsChunkActive if (RespInstr->ReadSendSize >= 0) { // Content-Range: bytes 222-3333/4000 HTTP_PARTIAL_CONTENT // Transfer-Encoding: chunked if (http_MakeMessage( headers, resp_major, resp_minor, "R" "N" "TD" "s" "tcS" "Xc" "sCc", HTTP_OK, // status code RespInstr->ReadSendSize, // content length UpnpFileInfo_get_ContentType(finfo), // content type "LAST-MODIFIED: ", UpnpFileInfo_get_LastModified(finfo), X_USER_AGENT, extra_headers) != 0 ) { goto error_handler; } } else { // Content-Range: bytes 222-3333/4000 HTTP_PARTIAL_CONTENT // Transfer-Encoding: chunked if (http_MakeMessage( headers, resp_major, resp_minor, "R" "TD" "s" "tcS" "b" "Xc" "sCc", HTTP_OK, // status code UpnpFileInfo_get_ContentType(finfo), // content type "LAST-MODIFIED: ", UpnpFileInfo_get_LastModified(finfo), X_USER_AGENT, extra_headers) != 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 = UPNP_E_SUCCESS; error_handler: free(request_doc); UpnpFileInfo_delete(finfo); if (err_code != UPNP_E_SUCCESS && alias_grabbed) { alias_release(alias); } return err_code; } /************************************************************************ * Function: http_RecvPostMessage * * Parameters: * http_parser_t* parser ; HTTP Parser object * IN SOCKINFO *info ; Socket Information object * char * filename ; File where received data is copied to * struct SendInstruction * Instr ; Send Instruction object which gives * information whether the file is a virtual file or not. * * Description: Receives the HTTP post message * * Returns: * HTTP_INTERNAL_SERVER_ERROR * HTTP_UNAUTHORIZED * HTTP_REQUEST_RANGE_NOT_SATISFIABLE * HTTP_OK ************************************************************************/ int http_RecvPostMessage( http_parser_t *parser, IN SOCKINFO *info, char *filename, struct SendInstruction *Instr ) { unsigned int Data_Buf_Size = 1024; char Buf[1024]; int Timeout = 0; long Num_Write = 0; FILE *Fp; parse_status_t status = PARSE_OK; xboolean ok_on_close = FALSE; unsigned int entity_offset = 0; int num_read = 0; int ret_code = 0; if( Instr && Instr->IsVirtualFile ) { Fp = (virtualDirCallback.open)( filename, UPNP_WRITE ); if( Fp == NULL ) { return HTTP_INTERNAL_SERVER_ERROR; } } else { Fp = fopen( filename, "wb" ); if( Fp == NULL ) { return HTTP_UNAUTHORIZED; } } parser->position = POS_ENTITY; do { //first parse what has already been gotten if( parser->position != POS_COMPLETE ) { status = parser_parse_entity( parser ); } if( status == PARSE_INCOMPLETE_ENTITY ) { // read until close ok_on_close = TRUE; } else if( ( status != PARSE_SUCCESS ) && ( status != PARSE_CONTINUE_1 ) && ( status != PARSE_INCOMPLETE ) ) { //error fclose( Fp ); return HTTP_BAD_REQUEST; } //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 ret_code = membuffer_append( &parser->msg.msg, Buf, num_read ); if( ret_code != 0 ) { // set failure status parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR; return HTTP_INTERNAL_SERVER_ERROR; } status = parser_parse_entity( parser ); if( status == PARSE_INCOMPLETE_ENTITY ) { // read until close ok_on_close = TRUE; } else if( ( status != PARSE_SUCCESS ) && ( status != PARSE_CONTINUE_1 ) && ( status != PARSE_INCOMPLETE ) ) { return HTTP_BAD_REQUEST; } } else if( num_read == 0 ) { if( ok_on_close ) { UpnpPrintf( UPNP_INFO, HTTP, __FILE__, __LINE__, "<<< (RECVD) <<<\n%s\n-----------------\n", parser->msg.msg.buf ); print_http_headers( &parser->msg ); parser->position = POS_COMPLETE; } else { // partial msg parser->http_error_code = HTTP_BAD_REQUEST; // or response return HTTP_BAD_REQUEST; } } else { return num_read; } } 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->IsVirtualFile ) { Num_Write = virtualDirCallback.write( Fp, Buf, Data_Buf_Size ); if( Num_Write < 0 ) { virtualDirCallback.close( Fp ); return HTTP_INTERNAL_SERVER_ERROR; } } else { Num_Write = fwrite( Buf, 1, Data_Buf_Size, Fp ); if( Num_Write < 0 ) { fclose( Fp ); return HTTP_INTERNAL_SERVER_ERROR; } } } while( ( parser->position != POS_COMPLETE ) || ( entity_offset != parser->msg.entity.length ) ); if( Instr->IsVirtualFile ) { virtualDirCallback.close( Fp ); } else { fclose( Fp ); } /* while(TotalByteReceived < Instr->RecvWriteSize && (NumReceived = sock_read(info,Buf, Data_Buf_Size,&Timeout) ) > 0 ) { TotalByteReceived = TotalByteReceived + NumReceived; Num_Write = virtualDirCallback.write(Fp, Buf, NumReceived); if (ferror(Fp)) { virtualDirCallback.close(Fp); return HTTP_INTERNAL_SERVER_ERROR; } } if(TotalByteReceived < Instr->RecvWriteSize) { return HTTP_INTERNAL_SERVER_ERROR; } virtualDirCallback.close(Fp); } */ return HTTP_OK; } /************************************************************************ * Function: web_server_callback * * Parameters: * IN http_parser_t *parser ; HTTP Parser Object * INOUT http_message_t* req ; HTTP Message request * IN SOCKINFO *info ; Socket information object * * Description: main entry point into web server; * handles HTTP GET and HEAD requests * * Returns: * void ************************************************************************/ void web_server_callback( IN http_parser_t * parser, INOUT http_message_t * req, IN SOCKINFO * info ) { int ret; int timeout = 0; enum resp_type rtype = 0; membuffer headers; membuffer filename; struct xml_alias_t xmldoc; struct SendInstruction RespInstr; //Initialize instruction header. RespInstr.IsVirtualFile = 0; RespInstr.IsChunkActive = 0; RespInstr.IsRangeActive = 0; RespInstr.IsTrailers = 0; // init membuffer_init( &headers ); membuffer_init( &filename ); //Process request should create the different kind of header depending on the //the type of request. ret = process_request( req, &rtype, &headers, &filename, &xmldoc, &RespInstr ); if( ret != UPNP_E_SUCCESS ) { // send error code http_SendStatusResponse( info, ret, req->major_version, req->minor_version ); } else { // // send response switch ( rtype ) { case RESP_FILEDOC: // send file, I = further instruction to send data. http_SendMessage( info, &timeout, "Ibf", &RespInstr, headers.buf, headers.length, filename.buf ); break; case RESP_XMLDOC: // send xmldoc , I = further instruction to send data. http_SendMessage( info, &timeout, "Ibb", &RespInstr, headers.buf, headers.length, xmldoc.doc.buf, xmldoc.doc.length ); alias_release( &xmldoc ); break; case RESP_WEBDOC: //, I = further instruction to send data. /* http_SendVirtualDirDoc( info, &timeout, "Ibf",&RespInstr, headers.buf, headers.length, filename.buf ); */ http_SendMessage( info, &timeout, "Ibf", &RespInstr, headers.buf, headers.length, filename.buf ); break; case RESP_HEADERS: // headers only http_SendMessage( info, &timeout, "b", headers.buf, headers.length ); break; case RESP_POST: // headers only ret = http_RecvPostMessage( parser, info, filename.buf, &RespInstr ); //Send response. http_MakeMessage( &headers, 1, 1, "RTDSXcCc", ret, "text/html", X_USER_AGENT ); http_SendMessage( info, &timeout, "b", headers.buf, headers.length ); break; default: assert( 0 ); } } UpnpPrintf( UPNP_INFO, HTTP, __FILE__, __LINE__, "webserver: request processed...\n" ); membuffer_destroy( &headers ); membuffer_destroy( &filename ); }