
it seems to me that there is still something wrong: 1) the new is_qdtext_char() is incorrect. There is a trap if char is implemented as signed char. Suppose that c is '\xFF', it will be -1 when converted to an int. By definition, c should be qdtext: qdtext = <any TEXT except <">> TEXT = <any OCTET except CTLs, but including LWS> OCTET = <any 8-bit sequence of data> 2) the character after '\\' could be either part of a quoted-pair (together with '\\'), or a normal qdtext, since '\\' itself can be treated as a qdtext. This is equivalent to saying that the character after '\\' in a quoted string could be ANY octet. A patch based on the above two observations is attached. Peng
2207 lines
65 KiB
C
2207 lines
65 KiB
C
/*******************************************************************************
|
|
*
|
|
* Copyright (c) 2000-2003 Intel Corporation
|
|
* All rights reserved.
|
|
* Copyright (c) 2012 France Telecom All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* - Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* - Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* - Neither name of Intel Corporation nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
******************************************************************************/
|
|
|
|
/*
|
|
* \file
|
|
*
|
|
* \brief Contains functions for scanner and parser for http messages.
|
|
*/
|
|
|
|
#define _GNU_SOURCE /* For strcasestr() in string.h */
|
|
|
|
#include "config.h"
|
|
|
|
#include "strintmap.h"
|
|
#include "httpparser.h"
|
|
#include "statcodes.h"
|
|
#include "unixutil.h"
|
|
#include "upnpdebug.h"
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
/* entity positions */
|
|
|
|
#define NUM_HTTP_METHODS 9
|
|
static str_int_entry Http_Method_Table[NUM_HTTP_METHODS] = {
|
|
{"GET", HTTPMETHOD_GET},
|
|
{"HEAD", HTTPMETHOD_HEAD},
|
|
{"M-POST", HTTPMETHOD_MPOST},
|
|
{"M-SEARCH", HTTPMETHOD_MSEARCH},
|
|
{"NOTIFY", HTTPMETHOD_NOTIFY},
|
|
{"POST", HTTPMETHOD_POST},
|
|
{"SUBSCRIBE", HTTPMETHOD_SUBSCRIBE},
|
|
{"UNSUBSCRIBE", HTTPMETHOD_UNSUBSCRIBE},
|
|
{"POST", SOAPMETHOD_POST},
|
|
};
|
|
|
|
#define NUM_HTTP_HEADER_NAMES 33
|
|
str_int_entry Http_Header_Names[NUM_HTTP_HEADER_NAMES] = {
|
|
{"ACCEPT", HDR_ACCEPT},
|
|
{"ACCEPT-CHARSET", HDR_ACCEPT_CHARSET},
|
|
{"ACCEPT-ENCODING", HDR_ACCEPT_ENCODING},
|
|
{"ACCEPT-LANGUAGE", HDR_ACCEPT_LANGUAGE},
|
|
{"ACCEPT-RANGES", HDR_ACCEPT_RANGE},
|
|
{"CACHE-CONTROL", HDR_CACHE_CONTROL},
|
|
{"CALLBACK", HDR_CALLBACK},
|
|
{"CONTENT-ENCODING", HDR_CONTENT_ENCODING},
|
|
{"CONTENT-LANGUAGE", HDR_CONTENT_LANGUAGE},
|
|
{"CONTENT-LENGTH", HDR_CONTENT_LENGTH},
|
|
{"CONTENT-LOCATION", HDR_CONTENT_LOCATION},
|
|
{"CONTENT-RANGE", HDR_CONTENT_RANGE},
|
|
{"CONTENT-TYPE", HDR_CONTENT_TYPE},
|
|
{"DATE", HDR_DATE},
|
|
{"EXT", HDR_EXT},
|
|
{"HOST", HDR_HOST},
|
|
{"IF-RANGE", HDR_IF_RANGE},
|
|
{"LOCATION", HDR_LOCATION},
|
|
{"MAN", HDR_MAN},
|
|
{"MX", HDR_MX},
|
|
{"NT", HDR_NT},
|
|
{"NTS", HDR_NTS},
|
|
{"RANGE", HDR_RANGE},
|
|
{"SEQ", HDR_SEQ},
|
|
{"SERVER", HDR_SERVER},
|
|
{"SID", HDR_SID},
|
|
{"SOAPACTION", HDR_SOAPACTION},
|
|
{"ST", HDR_ST},
|
|
{"TE", HDR_TE},
|
|
{"TIMEOUT", HDR_TIMEOUT},
|
|
{"TRANSFER-ENCODING", HDR_TRANSFER_ENCODING},
|
|
{"USER-AGENT", HDR_USER_AGENT},
|
|
{"USN", HDR_USN},
|
|
};
|
|
|
|
/***********************************************************************/
|
|
/************* scanner *************/
|
|
/***********************************************************************/
|
|
|
|
#define TOKCHAR_CR 0xD
|
|
#define TOKCHAR_LF 0xA
|
|
|
|
/************************************************************************
|
|
* Function : scanner_init
|
|
*
|
|
* Parameters :
|
|
* OUT scanner_t* scanner ; Scanner Object to be initialized
|
|
* IN membuffer* bufptr ; Buffer to be copied
|
|
*
|
|
* Description : Intialize scanner
|
|
*
|
|
* Return : void ;
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static UPNP_INLINE void scanner_init(OUT scanner_t *scanner, IN membuffer *bufptr)
|
|
{
|
|
scanner->cursor = (size_t)0;
|
|
scanner->msg = bufptr;
|
|
scanner->entire_msg_loaded = FALSE;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : is_separator_char
|
|
*
|
|
* Parameters :
|
|
* IN char c ; character to be tested against used separator values
|
|
*
|
|
* Description : Finds the separator character.
|
|
*
|
|
************************************************************************/
|
|
static UPNP_INLINE int is_separator_char(IN int c)
|
|
{
|
|
return strchr(" \t()<>@,;:\\\"/[]?={}", c) != 0;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : is_identifier_char
|
|
*
|
|
* Parameters :
|
|
* IN char c ; character to be tested for separator values
|
|
*
|
|
* Description : Calls the function to indentify separator character
|
|
*
|
|
************************************************************************/
|
|
static UPNP_INLINE int is_identifier_char(IN int c)
|
|
{
|
|
return c >= 32 && c <= 126 && !is_separator_char(c);
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : is_control_char
|
|
*
|
|
* Parameters :
|
|
* IN char c ; character to be tested for a control character
|
|
*
|
|
* Description : Determines if the passed value is a control character
|
|
*
|
|
************************************************************************/
|
|
static UPNP_INLINE int is_control_char(IN int c)
|
|
{
|
|
return (c >= 0 && c <= 31) || c == 127;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : is_qdtext_char
|
|
*
|
|
* Parameters :
|
|
* IN char cc ; character to be tested for CR/LF
|
|
*
|
|
* Description : Checks to see if the passed in value is CR/LF
|
|
*
|
|
************************************************************************/
|
|
static UPNP_INLINE int is_qdtext_char(IN int c)
|
|
{
|
|
/* we don't check for this; it's checked in get_token() */
|
|
assert( c != '"' );
|
|
|
|
return
|
|
(c >= 32 && c != 127) ||
|
|
c < 0 ||
|
|
c == TOKCHAR_CR ||
|
|
c == TOKCHAR_LF ||
|
|
c == '\t';
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : scanner_get_token
|
|
*
|
|
* Parameters :
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
* OUT memptr* token ; Token
|
|
* OUT token_type_t* tok_type ; Type of token
|
|
*
|
|
* Description : reads next token from the input stream
|
|
* note: 0 and is used as a marker, and will not be valid in a quote
|
|
*
|
|
* Return : parse_status_t ;
|
|
* PARSE_OK
|
|
* PARSE_INCOMPLETE -- not enuf chars to get a token
|
|
* PARSE_FAILURE -- bad msg format
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static parse_status_t scanner_get_token(
|
|
INOUT scanner_t *scanner,
|
|
OUT memptr *token,
|
|
OUT token_type_t *tok_type)
|
|
{
|
|
char *cursor;
|
|
char *null_terminator; /* point to null-terminator in buffer */
|
|
int c;
|
|
token_type_t token_type;
|
|
int got_end_quote;
|
|
|
|
assert(scanner);
|
|
assert(token);
|
|
assert(tok_type);
|
|
|
|
/* point to next char in buffer */
|
|
cursor = scanner->msg->buf + scanner->cursor;
|
|
null_terminator = scanner->msg->buf + scanner->msg->length;
|
|
/* not enough chars in input to parse */
|
|
if (cursor == null_terminator)
|
|
return PARSE_INCOMPLETE;
|
|
c = *cursor;
|
|
if (is_identifier_char(c)) {
|
|
/* scan identifier */
|
|
token->buf = cursor++;
|
|
token_type = TT_IDENTIFIER;
|
|
while (cursor < null_terminator && is_identifier_char(*cursor))
|
|
cursor++;
|
|
if (!scanner->entire_msg_loaded && cursor == null_terminator)
|
|
/* possibly more valid chars */
|
|
return PARSE_INCOMPLETE;
|
|
/* calc token length */
|
|
token->length = (size_t)cursor - (size_t)token->buf;
|
|
} else if (c == ' ' || c == '\t') {
|
|
token->buf = cursor++;
|
|
token_type = TT_WHITESPACE;
|
|
while (cursor < null_terminator && (*cursor == ' ' || *cursor == '\t'))
|
|
cursor++;
|
|
if (!scanner->entire_msg_loaded && cursor == null_terminator)
|
|
/* possibly more chars */
|
|
return PARSE_INCOMPLETE;
|
|
token->length = (size_t)cursor - (size_t)token->buf;
|
|
} else if (c == TOKCHAR_CR) {
|
|
/* scan CRLF */
|
|
token->buf = cursor++;
|
|
if (cursor == null_terminator)
|
|
/* not enuf info to determine CRLF */
|
|
return PARSE_INCOMPLETE;
|
|
if (*cursor != TOKCHAR_LF) {
|
|
/* couldn't match CRLF; match as CR */
|
|
token_type = TT_CTRL; /* ctrl char */
|
|
token->length = (size_t)1;
|
|
} else {
|
|
/* got CRLF */
|
|
token->length = (size_t)2;
|
|
token_type = TT_CRLF;
|
|
cursor++;
|
|
}
|
|
} else if (c == TOKCHAR_LF) { /* accept \n as CRLF */
|
|
token->buf = cursor++;
|
|
token->length = (size_t)1;
|
|
token_type = TT_CRLF;
|
|
} else if (c == '"') {
|
|
/* quoted text */
|
|
token->buf = cursor++;
|
|
token_type = TT_QUOTEDSTRING;
|
|
got_end_quote = FALSE;
|
|
while (cursor < null_terminator) {
|
|
c = *cursor++;
|
|
if (c == '"') {
|
|
got_end_quote = TRUE;
|
|
break;
|
|
} else if (c == '\\') {
|
|
if (cursor < null_terminator) {
|
|
c = *cursor++;
|
|
/* the char after '\\' could be ANY octet */
|
|
}
|
|
/* else, while loop handles incomplete buf */
|
|
} else if (is_qdtext_char(c)) {
|
|
/* just accept char */
|
|
} else
|
|
/* bad quoted text */
|
|
return PARSE_FAILURE;
|
|
}
|
|
if (got_end_quote)
|
|
token->length = (size_t)cursor - (size_t)token->buf;
|
|
else { /* incomplete */
|
|
|
|
assert(cursor == null_terminator);
|
|
return PARSE_INCOMPLETE;
|
|
}
|
|
} else if (is_separator_char(c)) {
|
|
/* scan separator */
|
|
token->buf = cursor++;
|
|
token_type = TT_SEPARATOR;
|
|
token->length = (size_t)1;
|
|
} else if (is_control_char(c)) {
|
|
/* scan ctrl char */
|
|
token->buf = cursor++;
|
|
token_type = TT_CTRL;
|
|
token->length = (size_t)1;
|
|
} else
|
|
return PARSE_FAILURE;
|
|
|
|
scanner->cursor += token->length; /* move to next token */
|
|
*tok_type = token_type;
|
|
return PARSE_OK;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : scanner_get_str
|
|
*
|
|
* Parameters :
|
|
* IN scanner_t* scanner ; Scanner Object
|
|
*
|
|
* Description : returns ptr to next char in string
|
|
*
|
|
* Return : char* ;
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static UPNP_INLINE char *scanner_get_str(IN scanner_t * scanner)
|
|
{
|
|
return scanner->msg->buf + scanner->cursor;
|
|
}
|
|
|
|
|
|
/************************************************************************
|
|
* Function : httpmsg_compare
|
|
*
|
|
* Parameters :
|
|
* void* param1 ;
|
|
* void* param2 ;
|
|
*
|
|
* Description : Compares name id in the http headers.
|
|
*
|
|
* Return : int ;
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static int httpmsg_compare(void *param1, void *param2)
|
|
{
|
|
assert( param1 != NULL );
|
|
assert( param2 != NULL );
|
|
|
|
return
|
|
((http_header_t *)param1)->name_id ==
|
|
((http_header_t *)param2)->name_id;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : httpheader_free
|
|
*
|
|
* Parameters :
|
|
* void *msg ;
|
|
*
|
|
* Description : Free memory allocated for the http header
|
|
*
|
|
* Return : void ;
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static void httpheader_free(void *msg)
|
|
{
|
|
http_header_t *hdr = ( http_header_t * ) msg;
|
|
|
|
membuffer_destroy( &hdr->name_buf );
|
|
membuffer_destroy( &hdr->value );
|
|
free( hdr );
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : httpmsg_init
|
|
*
|
|
* Parameters :
|
|
* INOUT http_message_t* msg ; HTTP Message Object
|
|
*
|
|
* Description : Initialize and allocate memory for http message
|
|
*
|
|
* Return : void ;
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
void httpmsg_init(INOUT http_message_t *msg)
|
|
{
|
|
msg->initialized = 1;
|
|
msg->entity.buf = NULL;
|
|
msg->entity.length = ( size_t ) 0;
|
|
ListInit( &msg->headers, httpmsg_compare, httpheader_free );
|
|
membuffer_init( &msg->msg );
|
|
membuffer_init( &msg->status_msg );
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : httpmsg_destroy
|
|
*
|
|
* Parameters :
|
|
* INOUT http_message_t* msg ; HTTP Message Object
|
|
*
|
|
* Description : Free memory allocated for the http message
|
|
*
|
|
* Return : void ;
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
void httpmsg_destroy( INOUT http_message_t * msg )
|
|
{
|
|
assert( msg != NULL );
|
|
|
|
if( msg->initialized == 1 ) {
|
|
ListDestroy( &msg->headers, 1 );
|
|
membuffer_destroy( &msg->msg );
|
|
membuffer_destroy( &msg->status_msg );
|
|
free( msg->urlbuf );
|
|
msg->initialized = 0;
|
|
}
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : httpmsg_find_hdr_str
|
|
*
|
|
* Parameters :
|
|
* IN http_message_t* msg ; HTTP Message Object
|
|
* IN const char* header_name ; Header name to be compared with
|
|
*
|
|
* Description : Compares the header name with the header names stored
|
|
* in the linked list of messages
|
|
*
|
|
* Return : http_header_t* - Pointer to a header on success;
|
|
* NULL on failure
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
http_header_t *httpmsg_find_hdr_str(
|
|
IN http_message_t *msg,
|
|
IN const char *header_name)
|
|
{
|
|
http_header_t *header;
|
|
|
|
ListNode *node;
|
|
|
|
node = ListHead( &msg->headers );
|
|
while( node != NULL ) {
|
|
|
|
header = ( http_header_t * ) node->item;
|
|
|
|
if( memptr_cmp_nocase( &header->name, header_name ) == 0 ) {
|
|
return header;
|
|
}
|
|
|
|
node = ListNext( &msg->headers, node );
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : httpmsg_find_hdr
|
|
*
|
|
* Parameters :
|
|
* IN http_message_t* msg ; HTTP Message Object
|
|
* IN int header_name_id ; Header Name ID to be compared with
|
|
* OUT memptr* value ; Buffer to get the ouput to.
|
|
*
|
|
* Description : Finds header from a list, with the given 'name_id'.
|
|
*
|
|
* Return : http_header_t* - Pointer to a header on success;
|
|
* NULL on failure
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
http_header_t *httpmsg_find_hdr(
|
|
IN http_message_t *msg,
|
|
IN int header_name_id,
|
|
OUT memptr *value)
|
|
{
|
|
http_header_t header; /* temp header for searching */
|
|
ListNode *node;
|
|
http_header_t *data;
|
|
|
|
header.name_id = header_name_id;
|
|
node = ListFind( &msg->headers, NULL, &header );
|
|
if( node == NULL ) {
|
|
return NULL;
|
|
}
|
|
data = ( http_header_t * ) node->item;
|
|
if( value != NULL ) {
|
|
value->buf = data->value.buf;
|
|
value->length = data->value.length;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : skip_blank_lines
|
|
*
|
|
* Parameters :
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
*
|
|
* Description : skips blank lines at the start of a msg.
|
|
*
|
|
* Return : parse_status_t ;
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t skip_blank_lines(INOUT scanner_t *scanner)
|
|
{
|
|
memptr token;
|
|
token_type_t tok_type;
|
|
parse_status_t status;
|
|
|
|
/* skip ws, crlf */
|
|
do {
|
|
status = scanner_get_token(scanner, &token, &tok_type);
|
|
} while (status == (parse_status_t)PARSE_OK &&
|
|
(tok_type == (token_type_t)TT_WHITESPACE ||
|
|
tok_type == (token_type_t)TT_CRLF));
|
|
if (status == (parse_status_t)PARSE_OK) {
|
|
/* pushback a non-whitespace token */
|
|
scanner->cursor -= token.length;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : skip_lws
|
|
*
|
|
* Parameters :
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
*
|
|
* Description : skip linear whitespace.
|
|
*
|
|
* Return : int ;
|
|
* PARSE_OK: (LWS)* removed from input
|
|
* PARSE_FAILURE: bad input
|
|
* PARSE_INCOMPLETE: incomplete input
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t skip_lws(INOUT scanner_t *scanner)
|
|
{
|
|
memptr token;
|
|
token_type_t tok_type;
|
|
parse_status_t status;
|
|
size_t save_pos;
|
|
int matched;
|
|
|
|
do {
|
|
save_pos = scanner->cursor;
|
|
matched = FALSE;
|
|
|
|
/* get CRLF or WS */
|
|
status = scanner_get_token( scanner, &token, &tok_type );
|
|
if( status == ( parse_status_t ) PARSE_OK ) {
|
|
if( tok_type == ( token_type_t ) TT_CRLF ) {
|
|
/* get WS */
|
|
status = scanner_get_token( scanner, &token, &tok_type );
|
|
}
|
|
|
|
if( status == ( parse_status_t ) PARSE_OK &&
|
|
tok_type == ( token_type_t ) TT_WHITESPACE ) {
|
|
matched = TRUE;
|
|
} else {
|
|
/* did not match LWS; pushback token(s) */
|
|
scanner->cursor = save_pos;
|
|
}
|
|
}
|
|
} while( matched );
|
|
|
|
/* if entire msg is loaded, ignore an 'incomplete' warning */
|
|
if( status == ( parse_status_t ) PARSE_INCOMPLETE &&
|
|
scanner->entire_msg_loaded ) {
|
|
status = PARSE_OK;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : match_non_ws_string
|
|
*
|
|
* Parameters :
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
* OUT memptr* str ; Buffer to get the scanner buffer contents.
|
|
*
|
|
* Description : Match a string without whitespace or CRLF (%S)
|
|
*
|
|
* Return : UPNP_INLINE parse_status_t ;
|
|
* PARSE_OK
|
|
* PARSE_NO_MATCH
|
|
* PARSE_FAILURE
|
|
* PARSE_INCOMPLETE
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t match_non_ws_string(
|
|
INOUT scanner_t *scanner,
|
|
OUT memptr *str)
|
|
{
|
|
memptr token;
|
|
token_type_t tok_type;
|
|
parse_status_t status;
|
|
int done = FALSE;
|
|
size_t save_cursor;
|
|
|
|
save_cursor = scanner->cursor;
|
|
|
|
str->length = ( size_t ) 0;
|
|
str->buf = scanner_get_str( scanner ); /* point to next char in input */
|
|
|
|
while( !done ) {
|
|
status = scanner_get_token( scanner, &token, &tok_type );
|
|
if( status == ( parse_status_t ) PARSE_OK &&
|
|
tok_type != ( token_type_t ) TT_WHITESPACE &&
|
|
tok_type != ( token_type_t ) TT_CRLF ) {
|
|
/* append non-ws token */
|
|
str->length += token.length;
|
|
} else {
|
|
done = TRUE;
|
|
}
|
|
}
|
|
|
|
if( status == ( parse_status_t ) PARSE_OK ) {
|
|
/* last token was WS; push it back in */
|
|
scanner->cursor -= token.length;
|
|
}
|
|
/* tolerate 'incomplete' msg */
|
|
if( status == ( parse_status_t ) PARSE_OK ||
|
|
( status == ( parse_status_t ) PARSE_INCOMPLETE &&
|
|
scanner->entire_msg_loaded )
|
|
) {
|
|
if( str->length == ( size_t ) 0 ) {
|
|
/* no strings found */
|
|
return PARSE_NO_MATCH;
|
|
} else {
|
|
return PARSE_OK;
|
|
}
|
|
} else {
|
|
/* error -- pushback tokens */
|
|
scanner->cursor = save_cursor;
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function : match_raw_value
|
|
*
|
|
* Parameters :
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
* OUT memptr* raw_value ; Buffer to get the scanner buffer
|
|
* contents
|
|
*
|
|
* Description : Matches a raw value in a the input; value's length
|
|
* can be 0 or more. Whitespace after value is trimmed. On success,
|
|
* scanner points the CRLF that ended the value
|
|
*
|
|
* Return : parse_status_t ;
|
|
* PARSE_OK
|
|
* PARSE_INCOMPLETE
|
|
* PARSE_FAILURE
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t match_raw_value(
|
|
INOUT scanner_t * scanner,
|
|
OUT memptr *raw_value)
|
|
{
|
|
memptr token;
|
|
token_type_t tok_type;
|
|
parse_status_t status;
|
|
int done = FALSE;
|
|
int saw_crlf = FALSE;
|
|
size_t pos_at_crlf = ( size_t ) 0;
|
|
size_t save_pos;
|
|
char c;
|
|
|
|
save_pos = scanner->cursor;
|
|
|
|
/* value points to start of input */
|
|
raw_value->buf = scanner_get_str( scanner );
|
|
raw_value->length = ( size_t ) 0;
|
|
|
|
while( !done ) {
|
|
status = scanner_get_token( scanner, &token, &tok_type );
|
|
if( status == ( parse_status_t ) PARSE_OK ) {
|
|
if( !saw_crlf ) {
|
|
if( tok_type == ( token_type_t ) TT_CRLF ) {
|
|
/* CRLF could end value */
|
|
saw_crlf = TRUE;
|
|
|
|
/* save input position at start of CRLF */
|
|
pos_at_crlf = scanner->cursor - token.length;
|
|
}
|
|
/* keep appending value */
|
|
raw_value->length += token.length;
|
|
} else /* already seen CRLF */
|
|
{
|
|
if( tok_type == ( token_type_t ) TT_WHITESPACE ) {
|
|
/* start again; forget CRLF */
|
|
saw_crlf = FALSE;
|
|
raw_value->length += token.length;
|
|
} else {
|
|
/* non-ws means value ended just before CRLF */
|
|
done = TRUE;
|
|
|
|
/* point to the crlf which ended the value */
|
|
scanner->cursor = pos_at_crlf;
|
|
}
|
|
}
|
|
} else {
|
|
/* some kind of error; restore scanner position */
|
|
scanner->cursor = save_pos;
|
|
done = TRUE;
|
|
}
|
|
}
|
|
|
|
if( status == ( parse_status_t ) PARSE_OK ) {
|
|
/* trim whitespace on right side of value */
|
|
while( raw_value->length > ( size_t ) 0 ) {
|
|
/* get last char */
|
|
c = raw_value->buf[raw_value->length - ( size_t ) 1];
|
|
|
|
if( c != ' ' && c != '\t' &&
|
|
c != TOKCHAR_CR && c != TOKCHAR_LF ) {
|
|
/* done; no more whitespace */
|
|
break;
|
|
}
|
|
/* remove whitespace */
|
|
raw_value->length--;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: match_int
|
|
*
|
|
* Parameters:
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
* IN int base : Base of number in the string;
|
|
* valid values: 10 or 16
|
|
* OUT int* value ; Number stored here
|
|
*
|
|
* Description: Matches an unsigned integer value in the input. The
|
|
* integer is returned in 'value'. Except for PARSE_OK result, the
|
|
* scanner's cursor is moved back to its original position on error.
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_NO_MATCH -- got different kind of token
|
|
* PARSE_FAILURE -- bad input
|
|
* PARSE_INCOMPLETE
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t match_int(
|
|
INOUT scanner_t *scanner,
|
|
IN int base,
|
|
OUT int *value)
|
|
{
|
|
memptr token;
|
|
token_type_t tok_type;
|
|
parse_status_t status;
|
|
long num;
|
|
char *end_ptr;
|
|
size_t save_pos;
|
|
|
|
save_pos = scanner->cursor;
|
|
status = scanner_get_token(scanner, &token, &tok_type);
|
|
if (status == (parse_status_t)PARSE_OK) {
|
|
if (tok_type == (token_type_t)TT_IDENTIFIER) {
|
|
errno = 0;
|
|
num = strtol(token.buf, &end_ptr, base);
|
|
/* all and only those chars in token should be used for num */
|
|
if (num < 0 || end_ptr != token.buf + token.length ||
|
|
((num == LONG_MIN || num == LONG_MAX) && (errno == ERANGE))) {
|
|
status = PARSE_NO_MATCH;
|
|
}
|
|
/* save result */
|
|
*value = (int)num;
|
|
} else {
|
|
/* token must be an identifier */
|
|
status = PARSE_NO_MATCH;
|
|
}
|
|
}
|
|
if (status != (parse_status_t)PARSE_OK) {
|
|
/* restore scanner position for bad values */
|
|
scanner->cursor = save_pos;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: read_until_crlf
|
|
*
|
|
* Parameters:
|
|
* INOUT scanner_t* scanner ;Scanner Object
|
|
* OUT memptr* str ; Buffer to copy scanner buffer contents to
|
|
*
|
|
* Description: Reads data until end of line; the crlf at the end of
|
|
* line is not consumed. On error, scanner is not restored. On
|
|
* success, 'str' points to a string that runs until eol
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_FAILURE
|
|
* PARSE_INCOMPLETE
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t
|
|
read_until_crlf( INOUT scanner_t * scanner,
|
|
OUT memptr * str )
|
|
{
|
|
memptr token;
|
|
token_type_t tok_type;
|
|
parse_status_t status;
|
|
size_t start_pos;
|
|
|
|
start_pos = scanner->cursor;
|
|
str->buf = scanner_get_str( scanner );
|
|
|
|
/* read until we hit a crlf */
|
|
do {
|
|
status = scanner_get_token( scanner, &token, &tok_type );
|
|
} while( status == ( parse_status_t ) PARSE_OK &&
|
|
tok_type != ( token_type_t ) TT_CRLF );
|
|
|
|
if( status == ( parse_status_t ) PARSE_OK ) {
|
|
/* pushback crlf in stream */
|
|
scanner->cursor -= token.length;
|
|
|
|
/* str should include all strings except crlf at the end */
|
|
str->length = scanner->cursor - start_pos;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: match_char
|
|
*
|
|
* Parameters:
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
* IN char c ; Character to be compared with
|
|
* IN int case_sensitive; Flag indicating whether
|
|
* comparison should be case sensitive
|
|
*
|
|
* Description: Compares a character to the next char in the scanner;
|
|
* on error, scanner chars are not restored
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_NO_MATCH
|
|
* PARSE_INCOMPLETE
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t
|
|
match_char( INOUT scanner_t * scanner,
|
|
IN char c,
|
|
IN int case_sensitive )
|
|
{
|
|
char scan_char;
|
|
|
|
if( scanner->cursor >= scanner->msg->length ) {
|
|
return PARSE_INCOMPLETE;
|
|
}
|
|
/* read next char from scanner */
|
|
scan_char = scanner->msg->buf[scanner->cursor++];
|
|
|
|
if( case_sensitive ) {
|
|
return c == scan_char ? PARSE_OK : PARSE_NO_MATCH;
|
|
} else {
|
|
return tolower( c ) == tolower( scan_char ) ?
|
|
PARSE_OK : PARSE_NO_MATCH;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
* args for ...
|
|
* %d, int * (31-bit positive integer)
|
|
* %x, int * (31-bit postive number encoded as hex)
|
|
* %s, memptr* (simple identifier)
|
|
* %q, memptr* (quoted string)
|
|
* %S, memptr* (non-whitespace string)
|
|
* %R, memptr* (raw value)
|
|
* %U, uri_type* (url)
|
|
* %L, memptr* (string until end of line)
|
|
* %P, int * (current index of the string being scanned)
|
|
*
|
|
* no args for
|
|
* ' ' LWS*
|
|
* \t whitespace
|
|
* "%%" matches '%'
|
|
* "% " matches ' '
|
|
* %c matches CRLF
|
|
* %i ignore case in literal matching
|
|
* %n case-sensitive matching in literals
|
|
* %w optional whitespace; (similar to '\t',
|
|
* except whitespace is optional)
|
|
* %0 (zero) match null-terminator char '\0'
|
|
* (can only be used as last char in fmt)
|
|
* use only in matchstr(), not match()
|
|
* other chars match literally
|
|
*
|
|
* returns:
|
|
* PARSE_OK
|
|
* PARSE_INCOMPLETE
|
|
* PARSE_FAILURE -- bad input
|
|
* PARSE_NO_MATCH -- input does not match pattern
|
|
*/
|
|
|
|
/************************************************************************
|
|
* Function : vfmatch
|
|
*
|
|
* Parameters :
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
* IN const char* fmt ; Pattern Format
|
|
* va_list argp ; List of variable arguments
|
|
*
|
|
* Description : Extracts variable parameters depending on the passed
|
|
* in format parameter. Parses data also based on the passed in
|
|
* format parameter.
|
|
*
|
|
* Return : int ;
|
|
* PARSE_OK
|
|
* PARSE_INCOMPLETE
|
|
* PARSE_FAILURE - bad input
|
|
* PARSE_NO_MATCH - input does not match pattern
|
|
*
|
|
* Note :
|
|
************************************************************************/
|
|
static parse_status_t vfmatch(
|
|
INOUT scanner_t *scanner,
|
|
IN const char *fmt,
|
|
va_list argp)
|
|
{
|
|
char c;
|
|
const char *fmt_ptr = fmt;
|
|
parse_status_t status;
|
|
memptr *str_ptr;
|
|
memptr temp_str;
|
|
int *int_ptr;
|
|
uri_type *uri_ptr;
|
|
size_t save_pos;
|
|
int stat;
|
|
int case_sensitive = TRUE;
|
|
memptr token;
|
|
token_type_t tok_type;
|
|
int base;
|
|
|
|
assert( scanner != NULL );
|
|
assert( fmt != NULL );
|
|
|
|
/* save scanner pos; to aid error recovery */
|
|
save_pos = scanner->cursor;
|
|
|
|
status = PARSE_OK;
|
|
while( ( c = *fmt_ptr++ ) &&
|
|
( status == ( parse_status_t ) PARSE_OK ) ) {
|
|
if( c == '%' ) {
|
|
c = *fmt_ptr++;
|
|
switch ( c ) {
|
|
case 'R': /* raw value */
|
|
str_ptr = va_arg( argp, memptr * );
|
|
assert( str_ptr != NULL );
|
|
status = match_raw_value( scanner, str_ptr );
|
|
break;
|
|
case 's': /* simple identifier */
|
|
str_ptr = va_arg( argp, memptr * );
|
|
assert( str_ptr != NULL );
|
|
status = scanner_get_token( scanner, str_ptr,
|
|
&tok_type );
|
|
if( status == ( parse_status_t ) PARSE_OK &&
|
|
tok_type != ( token_type_t ) TT_IDENTIFIER ) {
|
|
/* not an identifier */
|
|
status = PARSE_NO_MATCH;
|
|
}
|
|
break;
|
|
case 'c': /* crlf */
|
|
status = scanner_get_token( scanner,
|
|
&token, &tok_type );
|
|
if( status == ( parse_status_t ) PARSE_OK &&
|
|
tok_type != ( token_type_t ) TT_CRLF ) {
|
|
/* not CRLF token */
|
|
status = PARSE_NO_MATCH;
|
|
}
|
|
break;
|
|
case 'd': /* integer */
|
|
case 'x': /* hex number */
|
|
int_ptr = va_arg(argp, int *);
|
|
assert(int_ptr != NULL);
|
|
base = c == 'd' ? 10 : 16;
|
|
status = match_int(scanner, base, int_ptr);
|
|
break;
|
|
case 'S': /* non-whitespace string */
|
|
case 'U': /* uri */
|
|
if( c == 'S' ) {
|
|
str_ptr = va_arg( argp, memptr * );
|
|
} else {
|
|
str_ptr = &temp_str;
|
|
}
|
|
assert( str_ptr != NULL );
|
|
status = match_non_ws_string( scanner, str_ptr );
|
|
if( c == 'U' && status == ( parse_status_t ) PARSE_OK ) {
|
|
uri_ptr = va_arg( argp, uri_type * );
|
|
assert( uri_ptr != NULL );
|
|
stat = parse_uri( str_ptr->buf, str_ptr->length,
|
|
uri_ptr );
|
|
if( stat != HTTP_SUCCESS ) {
|
|
status = PARSE_NO_MATCH;
|
|
}
|
|
}
|
|
break;
|
|
case 'L': /* string till eol */
|
|
str_ptr = va_arg( argp, memptr * );
|
|
assert( str_ptr != NULL );
|
|
status = read_until_crlf( scanner, str_ptr );
|
|
break;
|
|
case ' ': /* match space */
|
|
case '%': /* match percentage symbol */
|
|
status = match_char( scanner, c, case_sensitive );
|
|
break;
|
|
case 'n': /* case-sensitive match */
|
|
case_sensitive = TRUE;
|
|
break;
|
|
case 'i': /* ignore case */
|
|
case_sensitive = FALSE;
|
|
break;
|
|
case 'q': /* quoted string */
|
|
str_ptr = ( memptr * ) va_arg( argp, memptr * );
|
|
status =
|
|
scanner_get_token( scanner, str_ptr, &tok_type );
|
|
if( status == ( parse_status_t ) PARSE_OK &&
|
|
tok_type != ( token_type_t ) TT_QUOTEDSTRING ) {
|
|
status = PARSE_NO_MATCH; /* not a quoted string */
|
|
}
|
|
break;
|
|
case 'w':
|
|
/* optional whitespace */
|
|
status = scanner_get_token( scanner,
|
|
&token, &tok_type );
|
|
if( status == ( parse_status_t ) PARSE_OK &&
|
|
tok_type != ( token_type_t ) TT_WHITESPACE ) {
|
|
/* restore non-whitespace token */
|
|
scanner->cursor -= token.length;
|
|
}
|
|
break;
|
|
case 'P':
|
|
/* current pos of scanner */
|
|
int_ptr = va_arg( argp, int * );
|
|
assert( int_ptr != NULL );
|
|
*int_ptr = (int)scanner->cursor;
|
|
break;
|
|
/* valid only in matchstr() */
|
|
case '0':
|
|
/* end of msg? */
|
|
/* check that we are 1 beyond last char */
|
|
if( scanner->cursor == scanner->msg->length &&
|
|
scanner->msg->buf[scanner->cursor] == '\0' ) {
|
|
status = PARSE_OK;
|
|
} else {
|
|
status = PARSE_NO_MATCH;
|
|
}
|
|
break;
|
|
default:
|
|
/* unknown option */
|
|
assert( 0 );
|
|
}
|
|
} else {
|
|
switch ( c ) {
|
|
case ' ': /* LWS* */
|
|
status = skip_lws( scanner );
|
|
break;
|
|
case '\t': /* Whitespace */
|
|
status = scanner_get_token( scanner,
|
|
&token, &tok_type );
|
|
if( status == ( parse_status_t ) PARSE_OK &&
|
|
tok_type != (token_type_t) TT_WHITESPACE ) {
|
|
/* not whitespace token */
|
|
status = PARSE_NO_MATCH;
|
|
}
|
|
break;
|
|
default: /* match characters */
|
|
{
|
|
status = match_char( scanner, c, case_sensitive );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( status != ( parse_status_t ) PARSE_OK ) {
|
|
/* on error, restore original scanner pos */
|
|
scanner->cursor = save_pos;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: match
|
|
*
|
|
* Parameters:
|
|
* INOUT scanner_t* scanner ; Scanner Object
|
|
* IN const char* fmt; Pattern format
|
|
* ...
|
|
*
|
|
* Description: matches a variable parameter list and takes necessary
|
|
* actions based on the data type specified.
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_NO_MATCH
|
|
* PARSE_INCOMPLETE
|
|
************************************************************************/
|
|
static parse_status_t match(
|
|
INOUT scanner_t *scanner,
|
|
IN const char *fmt,
|
|
...)
|
|
{
|
|
parse_status_t ret_code;
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
ret_code = vfmatch(scanner, fmt, args);
|
|
va_end(args);
|
|
|
|
return ret_code;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: matchstr
|
|
*
|
|
* Parameters:
|
|
* IN char *str ; String to be matched
|
|
* IN size_t slen ; Length of the string
|
|
* IN const char* fmt ; Pattern format
|
|
* ...
|
|
*
|
|
* Description: Matches a variable parameter list with a string
|
|
* and takes actions based on the data type specified.
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_NO_MATCH -- failure to match pattern 'fmt'
|
|
* PARSE_FAILURE -- 'str' is bad input
|
|
************************************************************************/
|
|
parse_status_t
|
|
matchstr( IN char *str,
|
|
IN size_t slen,
|
|
IN const char *fmt,
|
|
... )
|
|
{
|
|
parse_status_t ret_code;
|
|
char save_char;
|
|
scanner_t scanner;
|
|
membuffer buf;
|
|
va_list arg_list;
|
|
|
|
/* null terminate str */
|
|
save_char = str[slen];
|
|
str[slen] = '\0';
|
|
|
|
membuffer_init( &buf );
|
|
|
|
/* under no circumstances should this buffer be modifed because its memory */
|
|
/* might have not come from malloc() */
|
|
membuffer_attach( &buf, str, slen );
|
|
|
|
scanner_init( &scanner, &buf );
|
|
scanner.entire_msg_loaded = TRUE;
|
|
|
|
va_start( arg_list, fmt );
|
|
ret_code = vfmatch( &scanner, fmt, arg_list );
|
|
va_end( arg_list );
|
|
|
|
/* restore str */
|
|
str[slen] = save_char;
|
|
|
|
/* don't destroy buf */
|
|
|
|
return ret_code;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_init
|
|
*
|
|
* Parameters:
|
|
* OUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Initializes the parser object.
|
|
*
|
|
* Returns:
|
|
* void
|
|
************************************************************************/
|
|
static UPNP_INLINE void
|
|
parser_init( OUT http_parser_t * parser )
|
|
{
|
|
memset( parser, 0, sizeof( http_parser_t ) );
|
|
|
|
parser->http_error_code = HTTP_BAD_REQUEST; /* err msg by default */
|
|
parser->ent_position = ENTREAD_DETERMINE_READ_METHOD;
|
|
parser->valid_ssdp_notify_hack = FALSE;
|
|
|
|
httpmsg_init( &parser->msg );
|
|
scanner_init( &parser->scanner, &parser->msg.msg );
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_requestline
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Get HTTP Method, URL location and version information.
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_SUCCESS
|
|
* PARSE_FAILURE
|
|
************************************************************************/
|
|
static parse_status_t
|
|
parser_parse_requestline( INOUT http_parser_t * parser )
|
|
{
|
|
parse_status_t status;
|
|
http_message_t *hmsg = &parser->msg;
|
|
memptr method_str;
|
|
memptr version_str;
|
|
int index;
|
|
char save_char;
|
|
int num_scanned;
|
|
memptr url_str;
|
|
|
|
assert( parser->position == POS_REQUEST_LINE );
|
|
|
|
status = skip_blank_lines( &parser->scanner );
|
|
if( status != ( parse_status_t ) PARSE_OK ) {
|
|
return status;
|
|
}
|
|
/*simple get http 0.9 as described in http 1.0 spec */
|
|
|
|
status =
|
|
match( &parser->scanner, "%s\t%S%w%c", &method_str, &url_str );
|
|
|
|
if( status == ( parse_status_t ) PARSE_OK ) {
|
|
|
|
index =
|
|
map_str_to_int( method_str.buf, method_str.length,
|
|
Http_Method_Table, NUM_HTTP_METHODS, TRUE );
|
|
|
|
if( index < 0 ) {
|
|
/* error; method not found */
|
|
parser->http_error_code = HTTP_NOT_IMPLEMENTED;
|
|
return PARSE_FAILURE;
|
|
}
|
|
|
|
if( Http_Method_Table[index].id != HTTPMETHOD_GET ) {
|
|
parser->http_error_code = HTTP_BAD_REQUEST;
|
|
return PARSE_FAILURE;
|
|
}
|
|
|
|
hmsg->method = HTTPMETHOD_SIMPLEGET;
|
|
|
|
/* store url */
|
|
hmsg->urlbuf = str_alloc( url_str.buf, url_str.length );
|
|
if( hmsg->urlbuf == NULL ) {
|
|
/* out of mem */
|
|
parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR;
|
|
return PARSE_FAILURE;
|
|
}
|
|
if( parse_uri( hmsg->urlbuf, url_str.length, &hmsg->uri ) !=
|
|
HTTP_SUCCESS ) {
|
|
return PARSE_FAILURE;
|
|
}
|
|
|
|
parser->position = POS_COMPLETE; /* move to headers */
|
|
|
|
return PARSE_SUCCESS;
|
|
}
|
|
|
|
status = match( &parser->scanner,
|
|
"%s\t%S\t%ihttp%w/%w%L%c", &method_str, &url_str,
|
|
&version_str );
|
|
if( status != ( parse_status_t ) PARSE_OK ) {
|
|
return status;
|
|
}
|
|
/* store url */
|
|
hmsg->urlbuf = str_alloc( url_str.buf, url_str.length );
|
|
if( hmsg->urlbuf == NULL ) {
|
|
/* out of mem */
|
|
parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR;
|
|
return PARSE_FAILURE;
|
|
}
|
|
if( parse_uri( hmsg->urlbuf, url_str.length, &hmsg->uri ) !=
|
|
HTTP_SUCCESS ) {
|
|
return PARSE_FAILURE;
|
|
}
|
|
|
|
index =
|
|
map_str_to_int( method_str.buf, method_str.length,
|
|
Http_Method_Table, NUM_HTTP_METHODS, TRUE );
|
|
if( index < 0 ) {
|
|
/* error; method not found */
|
|
parser->http_error_code = HTTP_NOT_IMPLEMENTED;
|
|
return PARSE_FAILURE;
|
|
}
|
|
|
|
/* scan version */
|
|
save_char = version_str.buf[version_str.length];
|
|
version_str.buf[version_str.length] = '\0'; /* null-terminate */
|
|
num_scanned = sscanf( version_str.buf, "%d . %d",
|
|
&hmsg->major_version, &hmsg->minor_version );
|
|
version_str.buf[version_str.length] = save_char; /* restore */
|
|
if (num_scanned != 2 ||
|
|
/* HTTP version equals to 1.0 should fail for MSEARCH as required by the
|
|
* UPnP certification tool */
|
|
hmsg->major_version < 0 ||
|
|
( hmsg->major_version == 1 && hmsg->minor_version < 1 &&
|
|
Http_Method_Table[index].id == HTTPMETHOD_MSEARCH )) {
|
|
parser->http_error_code = HTTP_HTTP_VERSION_NOT_SUPPORTED;
|
|
/* error; bad http version */
|
|
return PARSE_FAILURE;
|
|
}
|
|
|
|
hmsg->method = ( http_method_t ) Http_Method_Table[index].id;
|
|
parser->position = POS_HEADERS; /* move to headers */
|
|
|
|
return PARSE_OK;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_responseline
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Get HTTP Method, URL location and version information.
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_SUCCESS
|
|
* PARSE_FAILURE
|
|
************************************************************************/
|
|
parse_status_t parser_parse_responseline(INOUT http_parser_t *parser)
|
|
{
|
|
parse_status_t status;
|
|
http_message_t *hmsg = &parser->msg;
|
|
memptr line;
|
|
char save_char;
|
|
int num_scanned;
|
|
int i;
|
|
size_t n;
|
|
char *p;
|
|
|
|
assert(parser->position == POS_RESPONSE_LINE);
|
|
|
|
status = skip_blank_lines(&parser->scanner);
|
|
if (status != ( parse_status_t) PARSE_OK)
|
|
return status;
|
|
/* response line */
|
|
/*status = match( &parser->scanner, "%ihttp%w/%w%d\t.\t%d\t%d\t%L%c", */
|
|
/* &hmsg->major_version, &hmsg->minor_version, */
|
|
/* &hmsg->status_code, &hmsg->status_msg ); */
|
|
status = match(&parser->scanner, "%ihttp%w/%w%L%c", &line);
|
|
if (status != ( parse_status_t ) PARSE_OK)
|
|
return status;
|
|
save_char = line.buf[line.length];
|
|
line.buf[line.length] = '\0'; /* null-terminate */
|
|
/* scan http version and ret code */
|
|
num_scanned = sscanf(line.buf, "%d . %d %d",
|
|
&hmsg->major_version, &hmsg->minor_version,
|
|
&hmsg->status_code);
|
|
line.buf[line.length] = save_char; /* restore */
|
|
if (num_scanned != 3 ||
|
|
hmsg->major_version < 0 || hmsg->minor_version < 0 ||
|
|
hmsg->status_code < 0)
|
|
/* bad response line */
|
|
return PARSE_FAILURE;
|
|
/* point to status msg */
|
|
p = line.buf;
|
|
/* skip 3 ints */
|
|
for (i = 0; i < 3; i++) {
|
|
/* go to start of num */
|
|
while (!isdigit(*p))
|
|
p++;
|
|
/* skip int */
|
|
while (isdigit(*p))
|
|
p++;
|
|
}
|
|
/* whitespace must exist after status code */
|
|
if (*p != ' ' && *p != '\t')
|
|
return PARSE_FAILURE;
|
|
/* skip whitespace */
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
/* now, p is at start of status msg */
|
|
n = line.length - ((size_t)p - (size_t)line.buf);
|
|
if (membuffer_assign(&hmsg->status_msg, p, n) != 0) {
|
|
/* out of mem */
|
|
parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR;
|
|
return PARSE_FAILURE;
|
|
}
|
|
parser->position = POS_HEADERS; /* move to headers */
|
|
|
|
return PARSE_OK;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_headers
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Get HTTP Method, URL location and version information.
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_SUCCESS
|
|
* PARSE_FAILURE
|
|
************************************************************************/
|
|
parse_status_t parser_parse_headers(INOUT http_parser_t *parser)
|
|
{
|
|
parse_status_t status;
|
|
memptr token;
|
|
memptr hdr_value;
|
|
token_type_t tok_type;
|
|
scanner_t *scanner = &parser->scanner;
|
|
size_t save_pos;
|
|
http_header_t *header;
|
|
int header_id;
|
|
int ret = 0;
|
|
int index;
|
|
http_header_t *orig_header;
|
|
char save_char;
|
|
int ret2;
|
|
|
|
assert(parser->position == (parser_pos_t)POS_HEADERS ||
|
|
parser->ent_position == ENTREAD_CHUNKY_HEADERS);
|
|
|
|
while (TRUE) {
|
|
save_pos = scanner->cursor;
|
|
/* check end of headers */
|
|
status = scanner_get_token(scanner, &token, &tok_type);
|
|
if (status != (parse_status_t)PARSE_OK) {
|
|
return status;
|
|
}
|
|
switch (tok_type) {
|
|
case TT_CRLF:
|
|
/* end of headers */
|
|
if ((parser->msg.is_request)
|
|
&& (parser->msg.method == (http_method_t)HTTPMETHOD_POST)) {
|
|
parser->position = POS_COMPLETE; /*post entity parsing */
|
|
/*is handled separately */
|
|
return PARSE_SUCCESS;
|
|
}
|
|
parser->position = POS_ENTITY; /* read entity next */
|
|
return PARSE_OK;
|
|
case TT_IDENTIFIER:
|
|
/* not end; read header */
|
|
break;
|
|
default:
|
|
return PARSE_FAILURE; /* didn't see header name */
|
|
}
|
|
status = match(scanner, " : %R%c", &hdr_value);
|
|
if (status != (parse_status_t)PARSE_OK) {
|
|
/* pushback tokens; useful only on INCOMPLETE error */
|
|
scanner->cursor = save_pos;
|
|
return status;
|
|
}
|
|
/* add header */
|
|
/* find header */
|
|
index =
|
|
map_str_to_int(token.buf, token.length, Http_Header_Names,
|
|
NUM_HTTP_HEADER_NAMES, FALSE);
|
|
if (index != -1) {
|
|
/*Check if it is a soap header */
|
|
if (Http_Header_Names[index].id == HDR_SOAPACTION) {
|
|
parser->msg.method = SOAPMETHOD_POST;
|
|
}
|
|
header_id = Http_Header_Names[index].id;
|
|
orig_header =
|
|
httpmsg_find_hdr(&parser->msg, header_id, NULL);
|
|
} else {
|
|
header_id = HDR_UNKNOWN;
|
|
save_char = token.buf[token.length];
|
|
token.buf[token.length] = '\0';
|
|
orig_header =
|
|
httpmsg_find_hdr_str(&parser->msg, token.buf);
|
|
token.buf[token.length] = save_char; /* restore */
|
|
}
|
|
if (orig_header == NULL) {
|
|
/* add new header */
|
|
header =
|
|
(http_header_t *) malloc(sizeof(http_header_t));
|
|
if (header == NULL) {
|
|
parser->http_error_code =
|
|
HTTP_INTERNAL_SERVER_ERROR;
|
|
return PARSE_FAILURE;
|
|
}
|
|
membuffer_init(&header->name_buf);
|
|
membuffer_init(&header->value);
|
|
/* value can be 0 length */
|
|
if (hdr_value.length == (size_t)0) {
|
|
/* FIXME: Is this a bug? buf is not const. */
|
|
hdr_value.buf = "\0";
|
|
hdr_value.length = (size_t)1;
|
|
}
|
|
/* save in header in buffers */
|
|
if (membuffer_assign(&header->name_buf, token.buf, token.length) ||
|
|
membuffer_assign(&header->value, hdr_value.buf, hdr_value.length)) {
|
|
/* not enough mem */
|
|
free(header);
|
|
parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR;
|
|
return PARSE_FAILURE;
|
|
}
|
|
header->name.buf = header->name_buf.buf;
|
|
header->name.length = header->name_buf.length;
|
|
header->name_id = header_id;
|
|
ListAddTail(&parser->msg.headers, header);
|
|
/*NNS: ret = dlist_append( &parser->msg.headers, header ); */
|
|
/** TODO: remove that? Yes as ret is not set anymore
|
|
if (ret == UPNP_E_OUTOF_MEMORY) {
|
|
parser->http_error_code =
|
|
HTTP_INTERNAL_SERVER_ERROR;
|
|
return PARSE_FAILURE;
|
|
}
|
|
end of remove that? */
|
|
} else if (hdr_value.length > (size_t)0) {
|
|
/* append value to existing header */
|
|
/* append space */
|
|
ret = membuffer_append_str(&orig_header->value, ", ");
|
|
/* append continuation of header value */
|
|
ret2 = membuffer_append(&orig_header->value,
|
|
hdr_value.buf,
|
|
hdr_value.length);
|
|
if (ret == UPNP_E_OUTOF_MEMORY
|
|
|| ret2 == UPNP_E_OUTOF_MEMORY) {
|
|
/* not enuf mem */
|
|
parser->http_error_code =
|
|
HTTP_INTERNAL_SERVER_ERROR;
|
|
return PARSE_FAILURE;
|
|
}
|
|
}
|
|
} /* end while */
|
|
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_entity_using_clen
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: reads entity using content-length
|
|
*
|
|
* Returns:
|
|
* PARSE_INCOMPLETE
|
|
* PARSE_FAILURE -- entity length > content-length value
|
|
* PARSE_SUCCESS
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t
|
|
parser_parse_entity_using_clen( INOUT http_parser_t * parser )
|
|
{
|
|
/*int entity_length; */
|
|
|
|
assert( parser->ent_position == ENTREAD_USING_CLEN );
|
|
|
|
/* determine entity (i.e. body) length so far */
|
|
parser->msg.entity.length =
|
|
parser->msg.msg.length - parser->entity_start_position +
|
|
parser->msg.amount_discarded;
|
|
|
|
if( parser->msg.entity.length < parser->content_length ) {
|
|
/* more data to be read */
|
|
return PARSE_INCOMPLETE;
|
|
} else {
|
|
if( parser->msg.entity.length > parser->content_length ) {
|
|
/* silently discard extra data */
|
|
parser->msg.msg.buf[parser->entity_start_position +
|
|
parser->content_length -
|
|
parser->msg.amount_discarded] = '\0';
|
|
}
|
|
/* save entity length */
|
|
parser->msg.entity.length = parser->content_length;
|
|
|
|
/* save entity start ptr; (the very last thing to do) */
|
|
parser->msg.entity.buf = parser->msg.msg.buf +
|
|
parser->entity_start_position;
|
|
|
|
/* done reading entity */
|
|
parser->position = POS_COMPLETE;
|
|
return PARSE_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_chunky_body
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Read data in the chunks
|
|
*
|
|
* Returns:
|
|
* PARSE_INCOMPLETE
|
|
* PARSE_FAILURE -- entity length > content-length value
|
|
* PARSE_SUCCESS
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t parser_parse_chunky_body(
|
|
INOUT http_parser_t *parser)
|
|
{
|
|
parse_status_t status;
|
|
size_t save_pos;
|
|
|
|
/* if 'chunk_size' of bytes have been read; read next chunk */
|
|
if ((parser->msg.msg.length - parser->scanner.cursor) >= parser->chunk_size) {
|
|
/* move to next chunk */
|
|
parser->scanner.cursor += parser->chunk_size;
|
|
save_pos = parser->scanner.cursor;
|
|
/* discard CRLF */
|
|
status = match(&parser->scanner, "%c");
|
|
if (status != (parse_status_t)PARSE_OK) {
|
|
/*move back */
|
|
parser->scanner.cursor -= parser->chunk_size;
|
|
/*parser->scanner.cursor = save_pos; */
|
|
return status;
|
|
}
|
|
membuffer_delete(&parser->msg.msg, save_pos,
|
|
(parser->scanner.cursor - save_pos));
|
|
parser->scanner.cursor = save_pos;
|
|
/*update temp */
|
|
parser->msg.entity.length += parser->chunk_size;
|
|
parser->ent_position = ENTREAD_USING_CHUNKED;
|
|
return PARSE_CONTINUE_1;
|
|
} else
|
|
/* need more data for chunk */
|
|
return PARSE_INCOMPLETE;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_chunky_headers
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Read headers at the end of the chunked entity
|
|
*
|
|
* Returns:
|
|
* PARSE_INCOMPLETE
|
|
* PARSE_FAILURE -- entity length > content-length value
|
|
* PARSE_SUCCESS
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t
|
|
parser_parse_chunky_headers( INOUT http_parser_t * parser )
|
|
{
|
|
parse_status_t status;
|
|
size_t save_pos;
|
|
|
|
save_pos = parser->scanner.cursor;
|
|
status = parser_parse_headers( parser );
|
|
if( status == ( parse_status_t ) PARSE_OK ) {
|
|
/* finally, done with the whole msg */
|
|
parser->position = POS_COMPLETE;
|
|
|
|
membuffer_delete( &parser->msg.msg, save_pos,
|
|
( parser->scanner.cursor - save_pos ) );
|
|
parser->scanner.cursor = save_pos;
|
|
|
|
/* save entity start ptr as the very last thing to do */
|
|
parser->msg.entity.buf = parser->msg.msg.buf +
|
|
parser->entity_start_position;
|
|
|
|
return PARSE_SUCCESS;
|
|
} else {
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_chunky_entity
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser - HTTP Parser Object
|
|
*
|
|
* Description: Read headers at the end of the chunked entity
|
|
*
|
|
* Returns:
|
|
* PARSE_INCOMPLETE
|
|
* PARSE_FAILURE -- entity length > content-length value
|
|
* PARSE_SUCCESS
|
|
* PARSE_CONTINUE_1
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t
|
|
parser_parse_chunky_entity( INOUT http_parser_t * parser )
|
|
{
|
|
scanner_t *scanner = &parser->scanner;
|
|
parse_status_t status;
|
|
size_t save_pos;
|
|
memptr dummy;
|
|
|
|
assert( parser->ent_position == ENTREAD_USING_CHUNKED );
|
|
|
|
save_pos = scanner->cursor;
|
|
|
|
/* get size of chunk, discard extension, discard CRLF */
|
|
status = match( scanner, "%x%L%c", &parser->chunk_size, &dummy );
|
|
if( status != ( parse_status_t ) PARSE_OK ) {
|
|
scanner->cursor = save_pos;
|
|
UpnpPrintf( UPNP_INFO, HTTP, __FILE__, __LINE__,
|
|
"CHUNK COULD NOT BE PARSED\n" );
|
|
return status;
|
|
}
|
|
/* remove chunk info just matched; just retain data */
|
|
membuffer_delete( &parser->msg.msg, save_pos,
|
|
( scanner->cursor - save_pos ) );
|
|
scanner->cursor = save_pos; /* adjust scanner too */
|
|
|
|
if( parser->chunk_size == (size_t)0 ) {
|
|
/* done reading entity; determine length of entity */
|
|
parser->msg.entity.length = parser->scanner.cursor -
|
|
parser->entity_start_position + parser->msg.amount_discarded;
|
|
|
|
/* read entity headers */
|
|
parser->ent_position = ENTREAD_CHUNKY_HEADERS;
|
|
} else {
|
|
/* read chunk body */
|
|
parser->ent_position = ENTREAD_CHUNKY_BODY;
|
|
}
|
|
|
|
return PARSE_CONTINUE_1; /* continue to reading body */
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_entity_until_close
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Read headers at the end of the chunked entity
|
|
*
|
|
* Returns:
|
|
* PARSE_INCOMPLETE_ENTITY
|
|
************************************************************************/
|
|
static UPNP_INLINE parse_status_t
|
|
parser_parse_entity_until_close( INOUT http_parser_t * parser )
|
|
{
|
|
size_t cursor;
|
|
|
|
assert( parser->ent_position == ENTREAD_UNTIL_CLOSE );
|
|
|
|
/* eat any and all data */
|
|
cursor = parser->msg.msg.length;
|
|
|
|
/* update entity length */
|
|
parser->msg.entity.length = cursor - parser->entity_start_position +
|
|
parser->msg.amount_discarded;
|
|
|
|
/* update pointer */
|
|
parser->msg.entity.buf =
|
|
parser->msg.msg.buf + parser->entity_start_position;
|
|
|
|
parser->scanner.cursor = cursor;
|
|
|
|
return PARSE_INCOMPLETE_ENTITY; /* add anything */
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_get_entity_read_method
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Determines method to read entity
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_FAILURE
|
|
* PARSE_COMPLETE -- no more reading to do
|
|
************************************************************************/
|
|
UPNP_INLINE parse_status_t
|
|
parser_get_entity_read_method( INOUT http_parser_t * parser )
|
|
{
|
|
http_message_t *hmsg = &parser->msg;
|
|
int response_code;
|
|
memptr hdr_value;
|
|
|
|
assert( parser->ent_position == ENTREAD_DETERMINE_READ_METHOD );
|
|
|
|
/* entity points to start of msg body */
|
|
parser->msg.entity.buf = scanner_get_str( &parser->scanner );
|
|
parser->msg.entity.length = ( size_t ) 0;
|
|
|
|
/* remember start of body */
|
|
parser->entity_start_position = parser->scanner.cursor;
|
|
|
|
/* std http rules for determining content length */
|
|
|
|
/* * no body for 1xx, 204, 304 and HEAD, GET, */
|
|
/* SUBSCRIBE, UNSUBSCRIBE */
|
|
if( hmsg->is_request ) {
|
|
switch ( hmsg->method ) {
|
|
case HTTPMETHOD_HEAD:
|
|
case HTTPMETHOD_GET:
|
|
/*case HTTPMETHOD_POST: */
|
|
case HTTPMETHOD_SUBSCRIBE:
|
|
case HTTPMETHOD_UNSUBSCRIBE:
|
|
case HTTPMETHOD_MSEARCH:
|
|
/* no body; mark as done */
|
|
parser->position = POS_COMPLETE;
|
|
return PARSE_SUCCESS;
|
|
break;
|
|
|
|
default:
|
|
; /* do nothing */
|
|
}
|
|
} else /* response */
|
|
{
|
|
response_code = hmsg->status_code;
|
|
|
|
if( response_code == 204 ||
|
|
response_code == 304 ||
|
|
( response_code >= 100 && response_code <= 199 ) ||
|
|
hmsg->request_method == ( http_method_t ) HTTPMETHOD_HEAD ||
|
|
hmsg->request_method == ( http_method_t ) HTTPMETHOD_MSEARCH ||
|
|
hmsg->request_method == ( http_method_t ) HTTPMETHOD_SUBSCRIBE ||
|
|
hmsg->request_method == ( http_method_t ) HTTPMETHOD_UNSUBSCRIBE ||
|
|
hmsg->request_method == ( http_method_t ) HTTPMETHOD_NOTIFY ) {
|
|
parser->position = POS_COMPLETE;
|
|
return PARSE_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/* * transfer-encoding -- used to indicate chunked data */
|
|
if( httpmsg_find_hdr( hmsg, HDR_TRANSFER_ENCODING, &hdr_value ) ) {
|
|
if( raw_find_str( &hdr_value, "chunked" ) >= 0 ) {
|
|
/* read method to use chunked transfer encoding */
|
|
parser->ent_position = ENTREAD_USING_CHUNKED;
|
|
UpnpPrintf( UPNP_INFO, HTTP, __FILE__, __LINE__,
|
|
"Found Chunked Encoding ....\n" );
|
|
|
|
return PARSE_CONTINUE_1;
|
|
}
|
|
}
|
|
/* * use content length */
|
|
if( httpmsg_find_hdr( hmsg, HDR_CONTENT_LENGTH, &hdr_value ) ) {
|
|
parser->content_length = (unsigned int)raw_to_int(&hdr_value, 10);
|
|
parser->ent_position = ENTREAD_USING_CLEN;
|
|
return PARSE_CONTINUE_1;
|
|
}
|
|
/* * multi-part/byteranges not supported (yet) */
|
|
|
|
/* * read until connection is closed */
|
|
if( hmsg->is_request ) {
|
|
/* set hack flag for NOTIFY methods; if set to true this is */
|
|
/* a valid SSDP notify msg */
|
|
if( hmsg->method == ( http_method_t ) HTTPMETHOD_NOTIFY ) {
|
|
parser->valid_ssdp_notify_hack = TRUE;
|
|
}
|
|
|
|
parser->http_error_code = HTTP_LENGTH_REQUIRED;
|
|
return PARSE_FAILURE;
|
|
}
|
|
|
|
parser->ent_position = ENTREAD_UNTIL_CLOSE;
|
|
return PARSE_CONTINUE_1;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse_entity
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Determines method to read entity
|
|
*
|
|
* Returns:
|
|
* PARSE_OK
|
|
* PARSE_FAILURE
|
|
* PARSE_COMPLETE -- no more reading to do
|
|
************************************************************************/
|
|
UPNP_INLINE parse_status_t
|
|
parser_parse_entity( INOUT http_parser_t * parser )
|
|
{
|
|
parse_status_t status = PARSE_OK;
|
|
|
|
assert( parser->position == POS_ENTITY );
|
|
|
|
do {
|
|
switch ( parser->ent_position ) {
|
|
case ENTREAD_USING_CLEN:
|
|
status = parser_parse_entity_using_clen( parser );
|
|
break;
|
|
|
|
case ENTREAD_USING_CHUNKED:
|
|
status = parser_parse_chunky_entity( parser );
|
|
break;
|
|
|
|
case ENTREAD_CHUNKY_BODY:
|
|
status = parser_parse_chunky_body( parser );
|
|
break;
|
|
|
|
case ENTREAD_CHUNKY_HEADERS:
|
|
status = parser_parse_chunky_headers( parser );
|
|
break;
|
|
|
|
case ENTREAD_UNTIL_CLOSE:
|
|
status = parser_parse_entity_until_close( parser );
|
|
break;
|
|
|
|
case ENTREAD_DETERMINE_READ_METHOD:
|
|
status = parser_get_entity_read_method( parser );
|
|
break;
|
|
|
|
default:
|
|
assert( 0 );
|
|
}
|
|
|
|
} while( status == ( parse_status_t ) PARSE_CONTINUE_1 );
|
|
|
|
return status;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_request_init
|
|
*
|
|
* Parameters:
|
|
* OUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: Initializes parser object for a request
|
|
*
|
|
* Returns:
|
|
* void
|
|
************************************************************************/
|
|
void
|
|
parser_request_init( OUT http_parser_t * parser )
|
|
{
|
|
parser_init( parser );
|
|
parser->msg.is_request = TRUE;
|
|
parser->position = POS_REQUEST_LINE;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_response_init
|
|
*
|
|
* Parameters:
|
|
* OUT http_parser_t* parser ; HTTP Parser object
|
|
* IN http_method_t request_method ; Request method
|
|
*
|
|
* Description: Initializes parser object for a response
|
|
*
|
|
* Returns:
|
|
* void
|
|
************************************************************************/
|
|
void
|
|
parser_response_init( OUT http_parser_t * parser,
|
|
IN http_method_t request_method )
|
|
{
|
|
parser_init( parser );
|
|
parser->msg.is_request = FALSE;
|
|
parser->msg.request_method = request_method;
|
|
parser->msg.amount_discarded = (size_t)0;
|
|
parser->position = POS_RESPONSE_LINE;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_parse
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser object
|
|
*
|
|
* Description: The parser function. Depending on the position of the
|
|
* parser object the actual parsing function is invoked
|
|
*
|
|
* Returns:
|
|
* void
|
|
************************************************************************/
|
|
parse_status_t
|
|
parser_parse( INOUT http_parser_t * parser )
|
|
{
|
|
parse_status_t status;
|
|
|
|
/*takes an http_parser_t with memory already allocated */
|
|
/*in the message */
|
|
assert( parser != NULL );
|
|
|
|
do {
|
|
switch ( parser->position ) {
|
|
case POS_ENTITY:
|
|
status = parser_parse_entity( parser );
|
|
|
|
break;
|
|
|
|
case POS_HEADERS:
|
|
status = parser_parse_headers( parser );
|
|
|
|
break;
|
|
|
|
case POS_REQUEST_LINE:
|
|
status = parser_parse_requestline( parser );
|
|
|
|
break;
|
|
|
|
case POS_RESPONSE_LINE:
|
|
status = parser_parse_responseline( parser );
|
|
|
|
break;
|
|
|
|
default:
|
|
{
|
|
status = PARSE_FAILURE;
|
|
assert( 0 );
|
|
}
|
|
}
|
|
|
|
} while( status == ( parse_status_t ) PARSE_OK );
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: parser_append
|
|
*
|
|
* Parameters:
|
|
* INOUT http_parser_t* parser ; HTTP Parser Object
|
|
* IN const char* buf ; buffer to be appended to the parser
|
|
* buffer
|
|
* IN size_t buf_length ; Size of the buffer
|
|
*
|
|
* Description: The parser function. Depending on the position of the
|
|
* parser object the actual parsing function is invoked
|
|
*
|
|
* Returns:
|
|
* void
|
|
************************************************************************/
|
|
parse_status_t
|
|
parser_append( INOUT http_parser_t * parser,
|
|
IN const char *buf,
|
|
IN size_t buf_length )
|
|
{
|
|
int ret_code;
|
|
|
|
assert( parser != NULL );
|
|
assert( buf != NULL );
|
|
|
|
/* append data to buffer */
|
|
ret_code = membuffer_append( &parser->msg.msg, buf, buf_length );
|
|
if( ret_code != 0 ) {
|
|
/* set failure status */
|
|
parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR;
|
|
return PARSE_FAILURE;
|
|
}
|
|
|
|
return parser_parse( parser );
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: raw_to_int
|
|
*
|
|
* Parameters:
|
|
* IN memptr* raw_value ; Buffer to be converted
|
|
* IN int base ; Base to use for conversion
|
|
*
|
|
* Description: Converts raw character data to long-integer value
|
|
*
|
|
* Returns:
|
|
* int
|
|
************************************************************************/
|
|
int raw_to_int(IN memptr *raw_value, IN int base)
|
|
{
|
|
long num;
|
|
char *end_ptr;
|
|
|
|
if (raw_value->length == (size_t)0)
|
|
return -1;
|
|
errno = 0;
|
|
num = strtol(raw_value->buf, &end_ptr, base);
|
|
if ((num < 0)
|
|
/* all and only those chars in token should be used for num */
|
|
|| (end_ptr != raw_value->buf + raw_value->length)
|
|
|| ((num == LONG_MIN || num == LONG_MAX)
|
|
&& (errno == ERANGE))
|
|
) {
|
|
return -1;
|
|
}
|
|
return (int)num;
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: raw_find_str
|
|
*
|
|
* Parameters:
|
|
* IN memptr* raw_value ; Buffer containg the string
|
|
* IN const char* str ; Substring to be found
|
|
*
|
|
* Description: Find a substring from raw character string buffer
|
|
*
|
|
* Side effects: raw_value is transformed to lowercase.
|
|
*
|
|
* Returns:
|
|
* int - index at which the substring is found.
|
|
************************************************************************/
|
|
int raw_find_str(IN memptr *raw_value, IN const char *str)
|
|
{
|
|
char c;
|
|
char *ptr;
|
|
int i = 0;
|
|
|
|
/* save */
|
|
c = raw_value->buf[raw_value->length];
|
|
|
|
/* Make it lowercase */
|
|
for (i = 0; raw_value->buf[i]; ++i) {
|
|
raw_value->buf[i] = (char)tolower(raw_value->buf[i]);
|
|
}
|
|
|
|
/* null-terminate */
|
|
raw_value->buf[raw_value->length] = 0;
|
|
|
|
/* Find the substring position */
|
|
ptr = strstr( raw_value->buf, str );
|
|
|
|
/* restore the "length" byte */
|
|
raw_value->buf[raw_value->length] = c;
|
|
|
|
if( ptr == 0 ) {
|
|
return -1;
|
|
}
|
|
|
|
/* return index */
|
|
return (int)(ptr - raw_value->buf);
|
|
}
|
|
|
|
/************************************************************************
|
|
* Function: method_to_str
|
|
*
|
|
* Parameters:
|
|
* IN http_method_t method ; HTTP method
|
|
*
|
|
* Description: A wrapper function that maps a method id to a method
|
|
* nameConverts a http_method id stored in the HTTP Method
|
|
*
|
|
* Returns:
|
|
* const char* ptr - Ptr to the HTTP Method
|
|
************************************************************************/
|
|
const char *method_to_str(IN http_method_t method)
|
|
{
|
|
int index;
|
|
|
|
index = map_int_to_str( method, Http_Method_Table, NUM_HTTP_METHODS );
|
|
|
|
assert( index != -1 );
|
|
|
|
return index == -1 ? NULL : Http_Method_Table[index].name;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
void print_http_headers(http_message_t *hmsg)
|
|
{
|
|
ListNode *node;
|
|
/* NNS: dlist_node *node; */
|
|
http_header_t *header;
|
|
|
|
/* print start line */
|
|
if( hmsg->is_request ) {
|
|
printf( "method = %d, version = %d.%d, url = %.*s\n",
|
|
hmsg->method, hmsg->major_version, hmsg->minor_version,
|
|
(int)hmsg->uri.pathquery.size, hmsg->uri.pathquery.buff);
|
|
} else {
|
|
printf( "resp status = %d, version = %d.%d, status msg = %.*s\n",
|
|
hmsg->status_code, hmsg->major_version, hmsg->minor_version,
|
|
(int)hmsg->status_msg.length, hmsg->status_msg.buf);
|
|
}
|
|
|
|
/* print headers */
|
|
node = ListHead( &hmsg->headers );
|
|
/* NNS: node = dlist_first_node( &hmsg->headers ); */
|
|
while( node != NULL ) {
|
|
header = ( http_header_t * ) node->item;
|
|
/* NNS: header = (http_header_t *)node->data; */
|
|
printf( "hdr name: %.*s, value: %.*s\n",
|
|
(int)header->name.length, header->name.buf,
|
|
(int)header->value.length, header->value.buf );
|
|
|
|
node = ListNext( &hmsg->headers, node );
|
|
/* NNS: node = dlist_next( &hmsg->headers, node ); */
|
|
}
|
|
}
|
|
#endif /* DEBUG */
|
|
|