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

#ifndef _GNU_SOURCE
#define _GNU_SOURCE	/* For strcasestr() in string.h */
#endif

#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 11
static str_int_entry Http_Method_Table[NUM_HTTP_METHODS] = {
	{"DELETE", HTTPMETHOD_DELETE},
	{"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},
	{"PUT", HTTPMETHOD_PUT}
};

#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 :	Determines if the passed value is a separator
*
************************************************************************/
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 :	Determines if the passed value is permissible in token
*
************************************************************************/
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 :	Determines if the passed value is permissible in qdtext
*
************************************************************************/
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 ;
*	PARSE_OK
*	PARSE_INCOMPLETE		-- not enuf chars to get a token
*	PARSE_FAILURE			-- bad msg format
************************************************************************/
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
*   PARSE_FAILURE		- bad input
************************************************************************/
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_INCOMPLETE
************************************************************************/
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
*	PARSE_INCOMPLETE
*	PARSE_NO_MATCH
************************************************************************/
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 version information, status code and status msg.
*
* Returns:
*	PARSE_OK
*	PARSE_FAILURE
*	PARSE_INCOMPLETE
*	PARSE_NO_MATCH
************************************************************************/
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: Read HTTP header fields.
*
* Returns:
*	PARSE_OK
*	PARSE_SUCCESS
*	PARSE_FAILURE
*	PARSE_INCOMPLETE
*	PARSE_NO_MATCH
************************************************************************/
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;
	static char zero = 0;

	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) {
			/* pushback tokens; useful only on INCOMPLETE error */
			scanner->cursor = save_pos;
			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) {
				hdr_value.buf = &zero;
				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 */
				membuffer_destroy(&header->value);
				membuffer_destroy(&header->name_buf);
				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;
			if (!ListAddTail(&parser->msg.headers, header)) {
				membuffer_destroy(&header->value);
				membuffer_destroy(&header->name_buf);
				free(header);
				parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR;
				return PARSE_FAILURE;
			}
		} 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_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_CONTINUE_1
*	 PARSE_INCOMPLETE
*	 PARSE_FAILURE
*	 PARSE_NO_MATCH
************************************************************************/
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_NO_MATCH
*	 PARSE_INCOMPLETE
*	 PARSE_FAILURE
*	 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 entity using chunked transfer encoding
*
* Returns:
*	 PARSE_INCOMPLETE
*	 PARSE_FAILURE
*	 PARSE_NO_MATCH
*	 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: Keep reading entity until the connection is closed.
*
* 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_CONTINUE_1
* 	 PARSE_FAILURE
*	 PARSE_SUCCESS	-- 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: Read HTTP entity body
*
* Returns:
* 	 PARSE_FAILURE
* 	 PARSE_NO_MATCH
* 	 PARSE_INCOMPLETE
* 	 PARSE_INCOMPLETE_ENTITY
*	 PARSE_SUCCESS	-- no more reading to do
************************************************************************/
UPNP_INLINE parse_status_t
parser_parse_entity( INOUT http_parser_t * parser )
{
    parse_status_t status;

    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:
		status = PARSE_FAILURE;
		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:
*	PARSE_SUCCESS
*	PARSE_FAILURE
*	PARSE_INCOMPLETE
*	PARSE_INCOMPLETE_ENTITY
*	PARSE_NO_MATCH
************************************************************************/
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: Append date to HTTP parser, and do the parsing.
*
* Returns:
*	PARSE_SUCCESS
*	PARSE_FAILURE
*	PARSE_INCOMPLETE
*	PARSE_INCOMPLETE_ENTITY
*	PARSE_NO_MATCH
************************************************************************/
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 */

