
git-svn-id: https://pupnp.svn.sourceforge.net/svnroot/pupnp/trunk@384 119443c7-1b9e-41f8-b6fc-b9c35fce742c
1778 lines
53 KiB
C
1778 lines
53 KiB
C
/**************************************************************************
|
|
*
|
|
* 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 <assert.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
|
|
|
|
#ifdef UPNP_USE_BCBPP
|
|
/* Do not #include <inttypes.h> */
|
|
/* Do not #include <stdint.h> */
|
|
#else
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#endif /* !UPNP_USE_BCBPP */
|
|
|
|
|
|
#ifdef WIN32
|
|
/* Do not #include <unistd.h> */
|
|
#else
|
|
#include <unistd.h>
|
|
#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 );
|
|
}
|
|
|