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