X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=blobdiff_plain;f=php_http_header_parser.c;h=2fbcb9394dbae1f5cd8761468ae4ff7799f988f4;hp=da02ff5f765237b76ba79d477be637c588691f7c;hb=refs%2Fheads%2Fv2.4.x;hpb=629c7b270b047582160c87b7c688c2b942a75d60 diff --git a/php_http_header_parser.c b/php_http_header_parser.c index da02ff5..2fbcb93 100644 --- a/php_http_header_parser.c +++ b/php_http_header_parser.c @@ -96,7 +96,26 @@ void php_http_header_parser_free(php_http_header_parser_t **parser) } } -STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg) +/* NOTE: 'str' has to be null terminated */ +static void php_http_header_parser_error(size_t valid_len, char *str, size_t len, const char *eol_str TSRMLS_DC) +{ + int escaped_len; + char *escaped_str; + + escaped_str = php_addcslashes(str, len, &escaped_len, 0, ZEND_STRL("\x0..\x1F\x7F..\xFF") TSRMLS_CC); + + if (valid_len != len && (!eol_str || (str+valid_len) != eol_str)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected character '\\%03o' at pos %zu of '%.*s'", str[valid_len], valid_len, escaped_len, escaped_str); + } else if (eol_str) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected end of line at pos %zu of '%.*s'", eol_str - str, escaped_len, escaped_str); + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected end of input at pos %zu of '%.*s'", len, escaped_len, escaped_str); + } + + efree(escaped_str); +} + +php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg) { TSRMLS_FETCH_FROM_CTX(parser->ts); @@ -108,6 +127,7 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b #endif switch (php_http_header_parser_state_pop(parser)) { case PHP_HTTP_HEADER_PARSER_STATE_FAILURE: + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers"); return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE); case PHP_HTTP_HEADER_PARSER_STATE_START: { @@ -140,12 +160,24 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE); } else if ((colon = memchr(buffer->data, ':', buffer->used)) && (!eol_str || eol_str > colon)) { /* header: string */ - parser->_key.str = estrndup(buffer->data, parser->_key.len = colon - buffer->data); + size_t valid_len; + + parser->_key.len = colon - buffer->data; + parser->_key.str = estrndup(buffer->data, parser->_key.len); + + valid_len = strspn(parser->_key.str, PHP_HTTP_HEADER_NAME_CHARS); + if (valid_len != parser->_key.len) { + php_http_header_parser_error(valid_len, parser->_key.str, parser->_key.len, eol_str TSRMLS_CC); + PTR_SET(parser->_key.str, NULL); + return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE); + } while (PHP_HTTP_IS_CTYPE(space, *++colon) && *colon != '\n' && *colon != '\r'); php_http_buffer_cut(buffer, 0, colon - buffer->data); php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE); - } else if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) { - /* neither reqeust/response line nor header: string */ + } else if (eol_str || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) { + /* neither reqeust/response line nor 'header:' string, or injected new line or NUL etc. */ + php_http_buffer_fix(buffer); + php_http_header_parser_error(strspn(buffer->data, PHP_HTTP_HEADER_NAME_CHARS), buffer->data, buffer->used, eol_str TSRMLS_CC); return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE); } else { /* keep feeding */ @@ -214,6 +246,17 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE: if (parser->_key.str && parser->_val.str) { zval array, **exist; + size_t valid_len = strlen(parser->_val.str); + + /* check for truncation */ + if (valid_len != parser->_val.len) { + php_http_header_parser_error(valid_len, parser->_val.str, parser->_val.len, NULL TSRMLS_CC); + + PTR_SET(parser->_key.str, NULL); + PTR_SET(parser->_val.str, NULL); + + return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE); + } if (!headers && callback_func) { callback_func(callback_arg, &headers, NULL TSRMLS_CC); @@ -244,6 +287,55 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b return php_http_header_parser_state_is(parser); } +php_http_header_parser_state_t php_http_header_parser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buf, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg) +{ + php_http_header_parser_state_t state = PHP_HTTP_HEADER_PARSER_STATE_START; + TSRMLS_FETCH_FROM_CTX(parser->ts); + + if (!buf->data) { + php_http_buffer_resize_ex(buf, 0x1000, 1, 0); + } + while (1) { + size_t justread = 0; +#if DBG_PARSER + const char *states[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"}; + fprintf(stderr, "#SHP: %s (f:%u)\n", states[state], flags); +#endif + /* resize if needed */ + if (buf->free < 0x1000) { + php_http_buffer_resize_ex(buf, 0x1000, 1, 0); + } + switch (state) { + case PHP_HTTP_HEADER_PARSER_STATE_FAILURE: + case PHP_HTTP_HEADER_PARSER_STATE_DONE: + return state; + + default: + /* read line */ + php_stream_get_line(s, buf->data + buf->used, buf->free, &justread); + /* if we fail reading a whole line, try a single char */ + if (!justread) { + int c = php_stream_getc(s); + + if (c != EOF) { + char s[1] = {c}; + justread = php_http_buffer_append(buf, s, 1); + } + } + php_http_buffer_account(buf, justread); + } + + if (justread) { + state = php_http_header_parser_parse(parser, buf, flags, headers, callback_func, callback_arg); + } else if (php_stream_eof(s)) { + return php_http_header_parser_parse(parser, buf, flags | PHP_HTTP_HEADER_PARSER_CLEANUP, headers, callback_func, callback_arg); + } else { + return state; + } + } + + return PHP_HTTP_HEADER_PARSER_STATE_DONE; +} zend_class_entry *php_http_header_parser_class_entry; static zend_object_handlers php_http_header_parser_object_handlers; @@ -327,10 +419,37 @@ static PHP_METHOD(HttpHeaderParser, parse) RETVAL_LONG(php_http_header_parser_parse(parser_obj->parser, parser_obj->buffer, flags, Z_ARRVAL_P(zmsg), NULL, NULL)); } +ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_stream, 0, 0, 3) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_ARRAY_INFO(1, headers, 1) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpHeaderParser, stream) +{ + php_http_header_parser_object_t *parser_obj; + zend_error_handling zeh; + zval *zmsg, *zstream; + php_stream *s; + long flags; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlz", &zstream, &flags, &zmsg), invalid_arg, return); + + zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh TSRMLS_CC); + php_stream_from_zval(s, &zstream); + zend_restore_error_handling(&zeh TSRMLS_CC); + + if (Z_TYPE_P(zmsg) != IS_ARRAY) { + zval_dtor(zmsg); + array_init(zmsg); + } + parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC); + RETVAL_LONG(php_http_header_parser_parse_stream(parser_obj->parser, parser_obj->buffer, s, flags, Z_ARRVAL_P(zmsg), NULL, NULL)); +} static zend_function_entry php_http_header_parser_methods[] = { PHP_ME(HttpHeaderParser, getState, ai_HttpHeaderParser_getState, ZEND_ACC_PUBLIC) PHP_ME(HttpHeaderParser, parse, ai_HttpHeaderParser_parse, ZEND_ACC_PUBLIC) + PHP_ME(HttpHeaderParser, stream, ai_HttpHeaderParser_stream, ZEND_ACC_PUBLIC) {NULL, NULL, NULL} };