/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2000-2003 Intel Corporation // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither name of Intel Corporation nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /////////////////////////////////////////////////////////////////////////// /************************************************************************ * Purpose: This file contains functions for scanner and parser for http * messages. ************************************************************************/ #define _GNU_SOURCE // For strcasestr() in string.h #include "config.h" #include #include #include #include #include #include "strintmap.h" #include "httpparser.h" #include "statcodes.h" #include "unixutil.h" // entity positions #define NUM_HTTP_METHODS 9 static str_int_entry Http_Method_Table[NUM_HTTP_METHODS] = { {"GET", HTTPMETHOD_GET}, {"HEAD", HTTPMETHOD_HEAD}, {"M-POST", HTTPMETHOD_MPOST}, {"M-SEARCH", HTTPMETHOD_MSEARCH}, {"NOTIFY", HTTPMETHOD_NOTIFY}, {"POST", HTTPMETHOD_POST}, {"SUBSCRIBE", HTTPMETHOD_SUBSCRIBE}, {"UNSUBSCRIBE", HTTPMETHOD_UNSUBSCRIBE}, {"POST", SOAPMETHOD_POST}, }; #define NUM_HTTP_HEADER_NAMES 33 str_int_entry Http_Header_Names[NUM_HTTP_HEADER_NAMES] = { {"ACCEPT", HDR_ACCEPT}, {"ACCEPT-CHARSET", HDR_ACCEPT_CHARSET}, {"ACCEPT-ENCODING", HDR_ACCEPT_ENCODING}, {"ACCEPT-LANGUAGE", HDR_ACCEPT_LANGUAGE}, {"ACCEPT-RANGES", HDR_ACCEPT_RANGE}, {"CACHE-CONTROL", HDR_CACHE_CONTROL}, {"CALLBACK", HDR_CALLBACK}, {"CONTENT-ENCODING", HDR_CONTENT_ENCODING}, {"CONTENT-LANGUAGE", HDR_CONTENT_LANGUAGE}, {"CONTENT-LENGTH", HDR_CONTENT_LENGTH}, {"CONTENT-LOCATION", HDR_CONTENT_LOCATION}, {"CONTENT-RANGE", HDR_CONTENT_RANGE}, {"CONTENT-TYPE", HDR_CONTENT_TYPE}, {"DATE", HDR_DATE}, {"EXT", HDR_EXT}, {"HOST", HDR_HOST}, {"IF-RANGE", HDR_IF_RANGE}, {"LOCATION", HDR_LOCATION}, {"MAN", HDR_MAN}, {"MX", HDR_MX}, {"NT", HDR_NT}, {"NTS", HDR_NTS}, {"RANGE", HDR_RANGE}, {"SEQ", HDR_SEQ}, {"SERVER", HDR_SERVER}, {"SID", HDR_SID}, {"SOAPACTION", HDR_SOAPACTION}, {"ST", HDR_ST}, {"TE", HDR_TE}, {"TIMEOUT", HDR_TIMEOUT}, {"TRANSFER-ENCODING", HDR_TRANSFER_ENCODING}, {"USER-AGENT", HDR_USER_AGENT}, {"USN", HDR_USN} }; /***********************************************************************/ /************* scanner **************/ /***********************************************************************/ #define TOKCHAR_CR 0xD #define TOKCHAR_LF 0xA /************************************************************************ * Function : scanner_init * * Parameters : * OUT scanner_t* scanner ; Scanner Object to be initialized * IN membuffer* bufptr ; Buffer to be copied * * Description : Intialize scanner * * Return : void ; * * Note : ************************************************************************/ static XINLINE void scanner_init( OUT scanner_t * scanner, IN membuffer * bufptr ) { scanner->cursor = 0; scanner->msg = bufptr; scanner->entire_msg_loaded = FALSE; } /************************************************************************ * Function : is_separator_char * * Parameters : * IN char c ; character to be tested against used separator values * * Description : Finds the separator character. * * Return : xboolean ; * * Note : ************************************************************************/ static XINLINE xboolean is_separator_char( IN char c ) { return strchr( " \t()<>@,;:\\\"/[]?={}", c ) != NULL; } /************************************************************************ * Function : is_identifier_char * * Parameters : * IN char c ; character to be tested for separator values * * Description : Calls the function to indentify separator character * * Return : xboolean ; * * Note : ************************************************************************/ static XINLINE xboolean is_identifier_char( IN char 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 * * Return : xboolean ; * * Note : ************************************************************************/ static XINLINE xboolean is_control_char( IN char c ) { return ( ( c >= 0 && c <= 31 ) || ( c == 127 ) ); } /************************************************************************ * Function : is_qdtext_char * * Parameters : * IN char cc ; character to be tested for CR/LF * * Description : Checks to see if the passed in value is CR/LF * * Return : xboolean ; * * Note : ************************************************************************/ static XINLINE xboolean is_qdtext_char( IN char cc ) { unsigned char c = ( unsigned char )cc; // we don't check for this; it's checked in get_token() assert( c != '"' ); if( ( c >= 32 && c != 127 ) || ( c == TOKCHAR_CR || c == TOKCHAR_LF || c == '\t' ) ) { return TRUE; } else { return FALSE; } } /************************************************************************ * 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 char c; token_type_t token_type; xboolean 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( 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 = cursor - token->buf; } else if( c == ' ' || c == '\t' ) { token->buf = cursor++; token_type = TT_WHITESPACE; while( *cursor == ' ' || *cursor == '\t' ) { cursor++; } if( !scanner->entire_msg_loaded && cursor == null_terminator ) { // possibly more chars return PARSE_INCOMPLETE; } token->length = cursor - 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 = 1; } else { // got CRLF token->length = 2; token_type = TT_CRLF; cursor++; } } else if( c == TOKCHAR_LF ) // accept \n as CRLF { token->buf = cursor++; token->length = 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++; //if ( !(c > 0 && c <= 127) ) if( c == 0 ) { return PARSE_FAILURE; } } // 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 = cursor - 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 = 1; } else if( is_control_char( c ) ) { // scan ctrl char token->buf = cursor++; token_type = TT_CTRL; token->length = 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 XINLINE char * scanner_get_str( IN scanner_t * scanner ) { return scanner->msg->buf + scanner->cursor; } /************************************************************************ * Function : scanner_pushback * * Parameters : * INOUT scanner_t* scanner ; Scanner Object * IN size_t pushback_bytes ; Bytes to be moved back * * Description : Move back by a certain number of bytes. * This is used to put back one or more tokens back into the input * * Return : void ; * * Note : ************************************************************************/ static XINLINE void scanner_pushback( INOUT scanner_t * scanner, IN size_t pushback_bytes ) { scanner->cursor -= pushback_bytes; } /***********************************************************************/ /************* end of scanner **************/ /***********************************************************************/ /***********************************************************************/ /************* parser **************/ /***********************************************************************/ /***********************************************************************/ /************* http_message_t **************/ /***********************************************************************/ /************************************************************************ * 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 = 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; } /***********************************************************************/ /************* http_parser_t **************/ /***********************************************************************/ /************************************************************************ * Function : skip_blank_lines * * Parameters : * INOUT scanner_t* scanner ; Scanner Object * * Description : skips blank lines at the start of a msg. * * Return : int ; * * Note : ************************************************************************/ static XINLINE int 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_OK && ( tok_type == TT_WHITESPACE || tok_type == TT_CRLF ) ); if( status == PARSE_OK ) { // pushback a non-whitespace token scanner->cursor -= token.length; //scanner_pushback( scanner, 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 XINLINE int skip_lws( INOUT scanner_t * scanner ) { memptr token; token_type_t tok_type; parse_status_t status; size_t save_pos; xboolean matched; do { save_pos = scanner->cursor; matched = FALSE; // get CRLF or WS status = scanner_get_token( scanner, &token, &tok_type ); if( status == PARSE_OK ) { if( tok_type == TT_CRLF ) { // get WS status = scanner_get_token( scanner, &token, &tok_type ); } if( status == PARSE_OK && tok_type == 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_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 : XINLINE parse_status_t ; * PARSE_OK * PARSE_NO_MATCH * PARSE_FAILURE * PARSE_INCOMPLETE * * Note : ************************************************************************/ static XINLINE 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; xboolean done = FALSE; size_t save_cursor; save_cursor = scanner->cursor; str->length = 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_OK && tok_type != TT_WHITESPACE && tok_type != TT_CRLF ) { // append non-ws token str->length += token.length; } else { done = TRUE; } } if( status == PARSE_OK ) { // last token was WS; push it back in scanner->cursor -= token.length; } // tolerate 'incomplete' msg if( status == PARSE_OK || ( status == PARSE_INCOMPLETE && scanner->entire_msg_loaded ) ) { if( str->length == 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 XINLINE 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; xboolean done = FALSE; xboolean saw_crlf = FALSE; size_t pos_at_crlf = 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 = 0; while( !done ) { status = scanner_get_token( scanner, &token, &tok_type ); if( status == PARSE_OK ) { if( !saw_crlf ) { if( tok_type == 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 == 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_OK ) { // trim whitespace on right side of value while( raw_value->length > 0 ) { // get last char c = raw_value->buf[raw_value->length - 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 XINLINE int 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_OK ) { if( tok_type == TT_IDENTIFIER ) { errno = 0; num = strtol( token.buf, &end_ptr, base ); if( ( num < 0 ) // all and only those chars in token should be used for num || ( end_ptr != token.buf + token.length ) || ( ( num == LONG_MIN || num == LONG_MAX ) && ( errno == ERANGE ) ) ) { status = PARSE_NO_MATCH; } *value = num; // save result } else { status = PARSE_NO_MATCH; // token must be an identifier } } if( status != 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 XINLINE int 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_OK && tok_type != TT_CRLF ); if( status == 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: skip_to_end_of_header * * Parameters: * INOUT scanner_t* scanner ; Scanner Object * * Description: Skip to end of header * * Returns: * PARSE_OK * PARSE_FAILURE * PARSE_INCOMPLETE ************************************************************************/ static XINLINE int skip_to_end_of_header( INOUT scanner_t * scanner ) { memptr dummy_raw_value; parse_status_t status; status = match_raw_value( scanner, &dummy_raw_value ); return status; } /************************************************************************ * Function: match_char * * Parameters: * INOUT scanner_t* scanner ; Scanner Object * IN char c ; Character to be compared with * IN xboolean 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 XINLINE parse_status_t match_char( INOUT scanner_t * scanner, IN char c, IN xboolean 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 int 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; xboolean 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++ ) != 0 ) && ( status == 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_OK && tok_type != TT_IDENTIFIER ) { // not an identifier status = PARSE_NO_MATCH; } break; case 'c': // crlf status = scanner_get_token( scanner, &token, &tok_type ); if( status == PARSE_OK && tok_type != 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_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_OK && tok_type != 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_OK && tok_type != 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 = 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: assert( 0 ); // unknown option } } else { switch ( c ) { case ' ': // LWS* status = skip_lws( scanner ); break; case '\t': // Whitespace status = scanner_get_token( scanner, &token, &tok_type ); if( status == PARSE_OK && tok_type != TT_WHITESPACE ) { // not whitespace token status = PARSE_NO_MATCH; } break; default: // match characters { status = match_char( scanner, c, case_sensitive ); } } } } if( status != PARSE_OK ) { // on error, restore original scanner pos scanner->cursor = save_pos; } return status; } /************************************************************************ * Function: match * * Parameters: * INOUT scanner_t* scanner ; Scanner Object * IN const char* fmt; Pattern format * ... * * Description: matches a variable parameter list and takes necessary * actions based on the data type specified. * * Returns: * PARSE_OK * PARSE_NO_MATCH * PARSE_INCOMPLETE ************************************************************************/ static int match( INOUT scanner_t * scanner, IN const char *fmt, ... ) { int 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 ************************************************************************/ int matchstr( IN char *str, IN size_t slen, IN const char *fmt, ... ) { int 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 XINLINE void parser_init( OUT http_parser_t * parser ) { memset( parser, 0, sizeof( http_parser_t ) ); parser->http_error_code = HTTP_BAD_REQUEST; // err msg by default parser->ent_position = ENTREAD_DETERMINE_READ_METHOD; parser->valid_ssdp_notify_hack = FALSE; httpmsg_init( &parser->msg ); scanner_init( &parser->scanner, &parser->msg.msg ); } /************************************************************************ * Function: parser_parse_requestline * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: Get HTTP Method, URL location and version information. * * Returns: * PARSE_OK * PARSE_SUCCESS * PARSE_FAILURE ************************************************************************/ static parse_status_t parser_parse_requestline( INOUT http_parser_t * parser ) { parse_status_t status; http_message_t *hmsg = &parser->msg; memptr method_str; memptr version_str; int index; char save_char; int num_scanned; memptr url_str; assert( parser->position == POS_REQUEST_LINE ); status = skip_blank_lines( &parser->scanner ); if( status != PARSE_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_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_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; } // 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 || hmsg->major_version < 0 || hmsg->minor_version < 0 ) { // error; bad http version 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; } hmsg->method = Http_Method_Table[index].id; parser->position = POS_HEADERS; // move to headers return PARSE_OK; } /************************************************************************ * Function: parser_parse_responseline * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: Get HTTP Method, URL location and version information. * * Returns: * PARSE_OK * PARSE_SUCCESS * PARSE_FAILURE ************************************************************************/ parse_status_t parser_parse_responseline( INOUT http_parser_t * parser ) { parse_status_t status; http_message_t *hmsg = &parser->msg; memptr line; char save_char; int num_scanned; int i; char *p; assert( parser->position == POS_RESPONSE_LINE ); status = skip_blank_lines( &parser->scanner ); if( status != 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_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 if( membuffer_assign( &hmsg->status_msg, p, line.length - ( p - line.buf ) ) != 0 ) { // out of mem parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR; return PARSE_FAILURE; } parser->position = POS_HEADERS; // move to headers return PARSE_OK; } /************************************************************************ * Function: parser_parse_headers * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: Get HTTP Method, URL location and version information. * * Returns: * PARSE_OK * PARSE_SUCCESS * PARSE_FAILURE ************************************************************************/ parse_status_t parser_parse_headers( INOUT http_parser_t * parser ) { parse_status_t status; memptr token; memptr hdr_value; token_type_t tok_type; scanner_t *scanner = &parser->scanner; size_t save_pos; http_header_t *header; int header_id; int ret = 0; int index; http_header_t *orig_header; char save_char; int ret2; assert( parser->position == 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_OK ) { return status; } if( tok_type == TT_CRLF ) { // end of headers if( ( parser->msg.is_request ) && ( parser->msg.method == 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; } // // not end; read header // if( tok_type != TT_IDENTIFIER ) { return PARSE_FAILURE; // didn't see header name } status = match( scanner, " : %R%c", &hdr_value ); if( status != 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 == 0 ) { hdr_value.buf = "\0"; hdr_value.length = 1; } // save in header in buffers if( membuffer_assign ( &header->name_buf, token.buf, token.length ) != 0 || membuffer_assign( &header->value, hdr_value.buf, hdr_value.length ) != 0 ) { // not enuf mem free (header); parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR; return PARSE_FAILURE; } header->name.buf = header->name_buf.buf; header->name.length = header->name_buf.length; header->name_id = header_id; ListAddTail( &parser->msg.headers, header ); //NNS: ret = dlist_append( &parser->msg.headers, header ); /** TODO: remove that? */ if( ret == UPNP_E_OUTOF_MEMORY ) { parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR; return PARSE_FAILURE; } /** end of remove that? */ } else if( hdr_value.length > 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 } //////////////////////////////////////////////////////////////////////// #ifdef HIGHLY_UNLIKELY // ************** static parse_status_t parser_parse_headers_old( 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, ret3; assert( parser->position == 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_OK ) { return status; } if( tok_type == TT_CRLF ) { // end of headers parser->position = POS_ENTITY; // read entity next return PARSE_OK; } // // not end; read header // if( tok_type != TT_IDENTIFIER ) { return PARSE_FAILURE; // didn't see header name } status = match( scanner, " : %R%c", &hdr_value ); if( status != 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 ) { 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->multi_hdr_buf ); header->name = token; header->value = hdr_value; header->name_id = header_id; ret = dlist_append( &parser->msg.headers, header ); if( ret == UPNP_E_OUTOF_MEMORY ) { parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR; return PARSE_FAILURE; } } else if( hdr_value.length > 0 ) { // // append value to existing header // if( orig_header->multi_hdr_buf.buf == NULL ) { // store in buffer ret = membuffer_append( &orig_header->multi_hdr_buf, orig_header->value.buf, orig_header->value.length ); } // append space ret2 = membuffer_append( &orig_header->multi_hdr_buf, ", ", 2 ); // append continuation of header value ret3 = membuffer_append( &orig_header->multi_hdr_buf, hdr_value.buf, hdr_value.length ); if( ret == UPNP_E_OUTOF_MEMORY || ret2 == UPNP_E_OUTOF_MEMORY || ret3 == UPNP_E_OUTOF_MEMORY ) { // not enuf mem parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR; return PARSE_FAILURE; } // header value points to allocated buf orig_header->value.buf = orig_header->multi_hdr_buf.buf; orig_header->value.length = orig_header->multi_hdr_buf.length; } } // end while } #endif // ****************************** /************************************************************************ * Function: parser_parse_entity_using_clen * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: reads entity using content-length * * Returns: * PARSE_INCOMPLETE * PARSE_FAILURE -- entity length > content-length value * PARSE_SUCCESS ************************************************************************/ static XINLINE 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 //entity_length = parser->msg.msg.length - parser->entity_start_position; parser->msg.entity.length = parser->msg.msg.length - parser->entity_start_position; 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] = '\0'; } // save entity length parser->msg.entity.length = parser->content_length; // save entity start ptr; (the very last thing to do) parser->msg.entity.buf = parser->msg.msg.buf + parser->entity_start_position; // done reading entity parser->position = POS_COMPLETE; return PARSE_SUCCESS; } } /************************************************************************ * Function: parser_parse_chunky_body * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: Read data in the chunks * * Returns: * PARSE_INCOMPLETE * PARSE_FAILURE -- entity length > content-length value * PARSE_SUCCESS ************************************************************************/ static XINLINE 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( ( int )( 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_OK ) { parser->scanner.cursor -= parser->chunk_size; //move back //parser->scanner.cursor = save_pos; return status; } membuffer_delete( &parser->msg.msg, save_pos, ( parser->scanner.cursor - save_pos ) ); parser->scanner.cursor = save_pos; parser->msg.entity.length += parser->chunk_size; //update temp parser->ent_position = ENTREAD_USING_CHUNKED; return PARSE_CONTINUE_1; } else { return PARSE_INCOMPLETE; // need more data for chunk } } /************************************************************************ * Function: parser_parse_chunky_headers * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: Read headers at the end of the chunked entity * * Returns: * PARSE_INCOMPLETE * PARSE_FAILURE -- entity length > content-length value * PARSE_SUCCESS ************************************************************************/ static XINLINE 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_OK ) { // finally, done with the whole msg parser->position = POS_COMPLETE; // save entity start ptr as the very last thing to do parser->msg.entity.buf = parser->msg.msg.buf + parser->entity_start_position; membuffer_delete( &parser->msg.msg, save_pos, ( parser->scanner.cursor - save_pos ) ); parser->scanner.cursor = save_pos; return PARSE_SUCCESS; } else { return status; } } /************************************************************************ * Function: parser_parse_chunky_entity * * Parameters: * INOUT http_parser_t* parser - HTTP Parser Object * * Description: Read headers at the end of the chunked entity * * Returns: * PARSE_INCOMPLETE * PARSE_FAILURE -- entity length > content-length value * PARSE_SUCCESS * PARSE_CONTINUE_1 ************************************************************************/ static XINLINE 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_OK ) { scanner->cursor = save_pos; DBGONLY( 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 == 0 ) { // done reading entity; determine length of entity parser->msg.entity.length = parser->scanner.cursor - parser->entity_start_position; // read entity headers parser->ent_position = ENTREAD_CHUNKY_HEADERS; } else { // read chunk body parser->ent_position = ENTREAD_CHUNKY_BODY; } return PARSE_CONTINUE_1; // continue to reading body } /************************************************************************ * Function: parser_parse_entity_until_close * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: Read headers at the end of the chunked entity * * Returns: * PARSE_INCOMPLETE_ENTITY ************************************************************************/ static XINLINE 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; // update pointer parser->msg.entity.buf = parser->msg.msg.buf + parser->entity_start_position; parser->scanner.cursor = cursor; return PARSE_INCOMPLETE_ENTITY; // add anything } /************************************************************************ * Function: parser_get_entity_read_method * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: Determines method to read entity * * Returns: * PARSE_OK * PARSE_FAILURE * PARSE_COMPLETE -- no more reading to do ************************************************************************/ XINLINE 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 = 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 == HTTPMETHOD_HEAD || hmsg->request_method == HTTPMETHOD_MSEARCH || hmsg->request_method == HTTPMETHOD_SUBSCRIBE || hmsg->request_method == HTTPMETHOD_UNSUBSCRIBE || hmsg->request_method == 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; DBGONLY( 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 = raw_to_int( &hdr_value, 10 ); if( parser->content_length < 0 ) { // bad content-length return PARSE_FAILURE; } 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 == HTTPMETHOD_NOTIFY ) { parser->valid_ssdp_notify_hack = TRUE; } parser->http_error_code = HTTP_LENGTH_REQUIRED; return PARSE_FAILURE; } parser->ent_position = ENTREAD_UNTIL_CLOSE; return PARSE_CONTINUE_1; } /************************************************************************ * Function: parser_parse_entity * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: Determines method to read entity * * Returns: * PARSE_OK * PARSE_FAILURE * PARSE_COMPLETE -- no more reading to do ************************************************************************/ XINLINE parse_status_t parser_parse_entity( INOUT http_parser_t * parser ) { parse_status_t status = PARSE_OK; assert( parser->position == POS_ENTITY ); do { switch ( parser->ent_position ) { case ENTREAD_USING_CLEN: status = parser_parse_entity_using_clen( parser ); break; case ENTREAD_USING_CHUNKED: status = parser_parse_chunky_entity( parser ); break; case ENTREAD_CHUNKY_BODY: status = parser_parse_chunky_body( parser ); break; case ENTREAD_CHUNKY_HEADERS: status = parser_parse_chunky_headers( parser ); break; case ENTREAD_UNTIL_CLOSE: status = parser_parse_entity_until_close( parser ); break; case ENTREAD_DETERMINE_READ_METHOD: status = parser_get_entity_read_method( parser ); break; default: assert( 0 ); } } while( status == PARSE_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->position = POS_RESPONSE_LINE; } /************************************************************************ * Function: parser_parse * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser object * * Description: The parser function. Depending on the position of the * parser object the actual parsing function is invoked * * Returns: * void ************************************************************************/ parse_status_t parser_parse( INOUT http_parser_t * parser ) { parse_status_t status; //takes an http_parser_t with memory already allocated //in the message assert( parser != NULL ); do { switch ( parser->position ) { case POS_ENTITY: status = parser_parse_entity( parser ); break; case POS_HEADERS: status = parser_parse_headers( parser ); break; case POS_REQUEST_LINE: status = parser_parse_requestline( parser ); break; case POS_RESPONSE_LINE: status = parser_parse_responseline( parser ); break; default: { status = PARSE_FAILURE; assert( 0 ); } } } while( status == PARSE_OK ); return status; } /************************************************************************ * Function: parser_append * * Parameters: * INOUT http_parser_t* parser ; HTTP Parser Object * IN const char* buf ; buffer to be appended to the parser * buffer * IN size_t buf_length ; Size of the buffer * * Description: The parser function. Depending on the position of the * parser object the actual parsing function is invoked * * Returns: * void ************************************************************************/ parse_status_t parser_append( INOUT http_parser_t * parser, IN const char *buf, IN size_t buf_length ) { int ret_code; assert( parser != NULL ); assert( buf != NULL ); // append data to buffer ret_code = membuffer_append( &parser->msg.msg, buf, buf_length ); if( ret_code != 0 ) { // set failure status parser->http_error_code = HTTP_INTERNAL_SERVER_ERROR; return PARSE_FAILURE; } return parser_parse( parser ); } /************************************************************************ ********** end of 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 == 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 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 * * 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; c = raw_value->buf[raw_value->length]; // save raw_value->buf[raw_value->length] = 0; // null-terminate // Use strcasestr because the string may not always be exact case ptr = strcasestr( raw_value->buf, str ); raw_value->buf[raw_value->length] = c; // restore if( ptr == 0 ) { return -1; } return ptr - raw_value->buf; // return index } /************************************************************************ * 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; } /************************************************************************ * Function: print_http_headers * * Parameters: * http_message_t* hmsg ; HTTP Message object * * Description: * * Returns: * void ************************************************************************/ 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, // 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 ); } }