libupnp/upnp/src/genlib/net/http/httpparser.c
Peng f10730f616 Patch to fix behaviou when char is signed
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
2013-08-16 14:16:47 -03:00

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 */