/*******************************************************************************
 *
 * 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" /* IWYU pragma: keep */

#include "httpparser.h"
#include "statcodes.h"
#include "strintmap.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 : Initialize scanner
 *
 * Return : void ;
 *
 * Note :
 ************************************************************************/
static UPNP_INLINE void scanner_init(scanner_t *scanner, membuffer *bufptr)
{
	scanner->cursor = (size_t)0;
	scanner->msg = bufptr;
	scanner->entire_msg_loaded = 0;
}

/************************************************************************
 * 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(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(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(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(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 enough chars to get a token
 *	PARSE_FAILURE			-- bad msg format
 *
 * Note :
 ************************************************************************/
static parse_status_t scanner_get_token(
	scanner_t *scanner, memptr *token, 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 enough 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 = 0;
		while (cursor < null_terminator) {
			c = *cursor++;
			if (c == '"') {
				got_end_quote = 1;
				break;
			} else if (c == '\\') {
				if (cursor < null_terminator) {
					/* c = *cursor++; */
					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(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(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(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->url_buf);
		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(
	http_message_t *msg, 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(
	http_message_t *msg, int header_name_id, 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 enough chars to get a token
 *	PARSE_FAILURE			-- bad msg format
 ************************************************************************/
static UPNP_INLINE parse_status_t skip_blank_lines(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(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 = 0;

		/* 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 = 1;
			} 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(
	scanner_t *scanner, memptr *str)
{
	memptr token;
	token_type_t tok_type;
	parse_status_t status;
	int done = 0;
	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 = 1;
		}
	}

	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(
	scanner_t *scanner, memptr *raw_value)
{
	memptr token;
	token_type_t tok_type;
	parse_status_t status;
	int done = 0;
	int saw_crlf = 0;
	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 = 1;

					/* 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 = 0;
					raw_value->length += token.length;
				} else {
					/* non-ws means value ended just before
					 * CRLF */
					done = 1;

					/* 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 = 1;
		}
	}

	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(
	scanner_t *scanner, int base, 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(
	scanner_t *scanner, 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(
	scanner_t *scanner, char c, 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 positive 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(scanner_t *scanner, 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 = 1;
	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 = 1;
				break;
			case 'i': /* ignore case */
				case_sensitive = 0;
				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(scanner_t *scanner, 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(char *str, size_t slen, 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 = 1;

	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(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 = 0;

	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_request_line(http_parser_t *parser)
{
	parse_status_t status;
	http_message_t *h_msg = &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,
			1);

		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;
		}

		h_msg->method = HTTPMETHOD_SIMPLEGET;

		/* remove excessive leading slashes, keep one slash */
		while (url_str.length >= 2 && url_str.buf[0] == '/' &&
			url_str.buf[1] == '/') {
			url_str.buf++;
			url_str.length--;
		}
		/* store url */
		h_msg->url_buf = str_alloc(url_str.buf, url_str.length);
		if (h_msg->url_buf == NULL) {
			/* out of mem */
			parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR;
			return PARSE_FAILURE;
		}
		if (parse_uri(h_msg->url_buf, url_str.length, &h_msg->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;
	}
	/* remove excessive leading slashes, keep one slash */
	while (url_str.length >= 2 && url_str.buf[0] == '/' &&
		url_str.buf[1] == '/') {
		url_str.buf++;
		url_str.length--;
	}
	/* store url */
	h_msg->url_buf = str_alloc(url_str.buf, url_str.length);
	if (h_msg->url_buf == NULL) {
		/* out of mem */
		parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR;
		return PARSE_FAILURE;
	}
	if (parse_uri(h_msg->url_buf, url_str.length, &h_msg->uri) !=
		HTTP_SUCCESS) {
		return PARSE_FAILURE;
	}

	index = map_str_to_int(method_str.buf,
		method_str.length,
		Http_Method_Table,
		NUM_HTTP_METHODS,
		1);
	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 */
#ifdef _WIN32
	num_scanned = sscanf_s(version_str.buf,
#else
	num_scanned = sscanf(version_str.buf,
#endif
		"%d . %d",
		&h_msg->major_version,
		&h_msg->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 */
		h_msg->major_version < 0 ||
		(h_msg->major_version == 1 && h_msg->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;
	}

	h_msg->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(http_parser_t *parser)
{
	parse_status_t status;
	http_message_t *h_msg = &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", */
	/*  &h_msg->major_version, &h_msg->minor_version, */
	/*  &h_msg->status_code, &h_msg->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 */
#ifdef _WIN32
	num_scanned = sscanf_s(line.buf,
#else
	num_scanned = sscanf(line.buf,
#endif
		"%d . %d %d",
		&h_msg->major_version,
		&h_msg->minor_version,
		&h_msg->status_code);
	line.buf[line.length] = save_char; /* restore */
	if (num_scanned != 3 || h_msg->major_version < 0 ||
		h_msg->minor_version < 0 || h_msg->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(&h_msg->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(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 (1) {
		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,
			0);
		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 enough memory */
				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(
	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(
	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(
	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(
	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(
	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
 ************************************************************************/
parse_status_t parser_get_entity_read_method(http_parser_t *parser)
{
	http_message_t *h_msg = &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 (h_msg->is_request) {
		switch (h_msg->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 = h_msg->status_code;

		if (response_code == 204 || response_code == 304 ||
			(response_code >= 100 && response_code <= 199) ||
			h_msg->request_method ==
				(http_method_t)HTTPMETHOD_HEAD ||
			h_msg->request_method ==
				(http_method_t)HTTPMETHOD_MSEARCH ||
			h_msg->request_method ==
				(http_method_t)HTTPMETHOD_SUBSCRIBE ||
			h_msg->request_method ==
				(http_method_t)HTTPMETHOD_UNSUBSCRIBE ||
			h_msg->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(h_msg, 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(h_msg, 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 (h_msg->is_request) {
		/* set hack flag for NOTIFY methods; if set to 1 this is */
		/*  a valid SSDP notify msg */
		if (h_msg->method == (http_method_t)HTTPMETHOD_NOTIFY) {
			parser->valid_ssdp_notify_hack = 1;
		}

		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
 ************************************************************************/
parse_status_t parser_parse_entity(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(http_parser_t *parser)
{
	parser_init(parser);
	parser->msg.is_request = 1;
	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(http_parser_t *parser, http_method_t request_method)
{
	parser_init(parser);
	parser->msg.is_request = 0;
	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(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_request_line(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(
	http_parser_t *parser, const char *buf, 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(memptr *raw_value, 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 containing 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(memptr *raw_value, 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(http_method_t method)
{
	int index;

	index = map_int_to_str(
		(int)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 *h_msg)
{
	ListNode *node;
	/* NNS:	 dlist_node *node; */
	http_header_t *header;

	/* print start line */
	if (h_msg->is_request) {
		UpnpPrintf(UPNP_ALL,
			HTTP,
			__FILE__,
			__LINE__,
			"method = %d, version = %d.%d, url = %.*s\n",
			h_msg->method,
			h_msg->major_version,
			h_msg->minor_version,
			(int)h_msg->uri.pathquery.size,
			h_msg->uri.pathquery.buff);
	} else {
		UpnpPrintf(UPNP_ALL,
			HTTP,
			__FILE__,
			__LINE__,
			"resp status = %d, version = %d.%d, status msg = "
			"%.*s\n",
			h_msg->status_code,
			h_msg->major_version,
			h_msg->minor_version,
			(int)h_msg->status_msg.length,
			h_msg->status_msg.buf);
	}

	/* print headers */
	node = ListHead(&h_msg->headers);
	/* NNS: node = dlist_first_node( &h_msg->headers ); */
	while (node != NULL) {
		header = (http_header_t *)node->item;
		/* NNS: header = (http_header_t *)node->data; */
		UpnpPrintf(UPNP_ALL,
			HTTP,
			__FILE__,
			__LINE__,
			"hdr name: %.*s, value: %.*s\n",
			(int)header->name.length,
			header->name.buf,
			(int)header->value.length,
			header->value.buf);

		node = ListNext(&h_msg->headers, node);
		/* NNS: node = dlist_next( &h_msg->headers, node ); */
	}
}
#endif /* DEBUG */
