2 +--------------------------------------------------------------------+
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2014, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
13 #include "php_http_api.h"
19 typedef struct php_http_header_parser_state_spec
{
20 php_http_header_parser_state_t state
;
22 } php_http_header_parser_state_spec_t
;
24 static const php_http_header_parser_state_spec_t php_http_header_parser_states
[] = {
25 {PHP_HTTP_HEADER_PARSER_STATE_START
, 1},
26 {PHP_HTTP_HEADER_PARSER_STATE_KEY
, 1},
27 {PHP_HTTP_HEADER_PARSER_STATE_VALUE
, 1},
28 {PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX
, 0},
29 {PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE
, 0},
30 {PHP_HTTP_HEADER_PARSER_STATE_DONE
, 0}
33 php_http_header_parser_t
*php_http_header_parser_init(php_http_header_parser_t
*parser
)
36 parser
= emalloc(sizeof(*parser
));
38 memset(parser
, 0, sizeof(*parser
));
43 php_http_header_parser_state_t
php_http_header_parser_state_push(php_http_header_parser_t
*parser
, unsigned argc
, ...)
47 php_http_header_parser_state_t state
= 0;
50 ZEND_PTR_STACK_RESIZE_IF_NEEDED((&parser
->stack
), argc
);
52 va_start(va_args
, argc
);
53 for (i
= 0; i
< argc
; ++i
) {
54 state
= va_arg(va_args
, php_http_header_parser_state_t
);
55 zend_ptr_stack_push(&parser
->stack
, (void *) state
);
62 php_http_header_parser_state_t
php_http_header_parser_state_is(php_http_header_parser_t
*parser
)
64 if (parser
->stack
.top
) {
65 return (php_http_header_parser_state_t
) parser
->stack
.elements
[parser
->stack
.top
- 1];
68 return PHP_HTTP_HEADER_PARSER_STATE_START
;
71 php_http_header_parser_state_t
php_http_header_parser_state_pop(php_http_header_parser_t
*parser
)
73 if (parser
->stack
.top
) {
74 return (php_http_header_parser_state_t
) zend_ptr_stack_pop(&parser
->stack
);
77 return PHP_HTTP_HEADER_PARSER_STATE_START
;
80 void php_http_header_parser_dtor(php_http_header_parser_t
*parser
)
82 zend_ptr_stack_destroy(&parser
->stack
);
83 php_http_info_dtor(&parser
->info
);
84 PTR_FREE(parser
->_key
.str
);
85 PTR_FREE(parser
->_val
.str
);
88 void php_http_header_parser_free(php_http_header_parser_t
**parser
)
91 php_http_header_parser_dtor(*parser
);
97 /* NOTE: 'str' has to be null terminated */
98 static void php_http_header_parser_error(size_t valid_len
, char *str
, size_t len
, const char *eol_str
)
100 zend_string
*escaped_str
= zend_string_init(str
, len
, 0);
102 escaped_str
= php_addcslashes(escaped_str
, 1, ZEND_STRL("\x0..\x1F\x7F..\xFF"));
104 if (valid_len
!= len
&& (!eol_str
|| (str
+valid_len
) != eol_str
)) {
105 php_error_docref(NULL
, E_WARNING
, "Failed to parse headers: unexpected character '\\%03o' at pos %zu of '%s'", str
[valid_len
], valid_len
, escaped_str
->val
);
106 } else if (eol_str
) {
107 php_error_docref(NULL
, E_WARNING
, "Failed to parse headers: unexpected end of line at pos %zu of '%s'", eol_str
- str
, escaped_str
->val
);
109 php_error_docref(NULL
, E_WARNING
, "Failed to parse headers: unexpected end of input at pos %zu of '%s'", len
, escaped_str
->val
);
115 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
)
117 while (buffer
->used
|| !php_http_header_parser_states
[php_http_header_parser_state_is(parser
)].need_data
) {
119 const char *state
[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
120 fprintf(stderr
, "#HP: %s (avail:%zu, num:%d cleanup:%u)\n", php_http_header_parser_state_is(parser
) < 0 ? "FAILURE" : state
[php_http_header_parser_state_is(parser
)], buffer
->used
, headers
?zend_hash_num_elements(headers
):0, flags
);
121 _dpf(0, buffer
->data
, buffer
->used
);
123 switch (php_http_header_parser_state_pop(parser
)) {
124 case PHP_HTTP_HEADER_PARSER_STATE_FAILURE
:
125 php_error_docref(NULL
, E_WARNING
, "Failed to parse headers");
126 return php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE
);
128 case PHP_HTTP_HEADER_PARSER_STATE_START
: {
129 char *ptr
= buffer
->data
;
131 while (ptr
- buffer
->data
< buffer
->used
&& PHP_HTTP_IS_CTYPE(space
, *ptr
)) {
135 php_http_buffer_cut(buffer
, 0, ptr
- buffer
->data
);
136 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY
);
140 case PHP_HTTP_HEADER_PARSER_STATE_KEY
: {
141 const char *colon
, *eol_str
= NULL
;
144 /* fix buffer here, so eol_str pointer doesn't become obsolete afterwards */
145 php_http_buffer_fix(buffer
);
147 if (buffer
->data
== (eol_str
= php_http_locate_bin_eol(buffer
->data
, buffer
->used
, &eol_len
))) {
149 php_http_buffer_cut(buffer
, 0, eol_len
);
150 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_DONE
);
151 } else if (php_http_info_parse(&parser
->info
, buffer
->data
)) {
152 /* new message starting with request/response line */
154 callback_func(callback_arg
, &headers
, &parser
->info
);
156 php_http_info_dtor(&parser
->info
);
157 php_http_buffer_cut(buffer
, 0, eol_str
+ eol_len
- buffer
->data
);
158 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE
);
159 } else if ((colon
= memchr(buffer
->data
, ':', buffer
->used
)) && (!eol_str
|| eol_str
> colon
)) {
163 parser
->_key
.len
= colon
- buffer
->data
;
164 parser
->_key
.str
= estrndup(buffer
->data
, parser
->_key
.len
);
166 valid_len
= strspn(parser
->_key
.str
, PHP_HTTP_HEADER_NAME_CHARS
);
167 if (valid_len
!= parser
->_key
.len
) {
168 php_http_header_parser_error(valid_len
, parser
->_key
.str
, parser
->_key
.len
, eol_str
);
169 PTR_SET(parser
->_key
.str
, NULL
);
170 return php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE
);
172 while (PHP_HTTP_IS_CTYPE(space
, *++colon
) && *colon
!= '\n' && *colon
!= '\r');
173 php_http_buffer_cut(buffer
, 0, colon
- buffer
->data
);
174 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE
);
175 } else if (eol_str
|| (flags
& PHP_HTTP_HEADER_PARSER_CLEANUP
)) {
176 /* neither reqeust/response line nor 'header:' string, or injected new line or NUL etc. */
177 php_http_header_parser_error(strspn(buffer
->data
, PHP_HTTP_HEADER_NAME_CHARS
), buffer
->data
, buffer
->used
, eol_str
);
178 return php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE
);
181 return php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY
);
186 case PHP_HTTP_HEADER_PARSER_STATE_VALUE
: {
190 #define SET_ADD_VAL(slen, eol_len) \
192 const char *ptr = buffer->data; \
195 while (len > 0 && PHP_HTTP_IS_CTYPE(space, *ptr)) { \
199 while (len > 0 && PHP_HTTP_IS_CTYPE(space, ptr[len - 1])) { \
204 if (parser->_val.str) { \
205 parser->_val.str = erealloc(parser->_val.str, parser->_val.len + len + 2); \
206 parser->_val.str[parser->_val.len++] = ' '; \
207 memcpy(&parser->_val.str[parser->_val.len], ptr, len); \
208 parser->_val.len += len; \
209 parser->_val.str[parser->_val.len] = '\0'; \
211 parser->_val.len = len; \
212 parser->_val.str = estrndup(ptr, len); \
215 php_http_buffer_cut(buffer, 0, slen + eol_len); \
218 if ((eol_str
= php_http_locate_bin_eol(buffer
->data
, buffer
->used
, &eol_len
))) {
219 SET_ADD_VAL(eol_str
- buffer
->data
, eol_len
);
220 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX
);
221 } else if (flags
& PHP_HTTP_HEADER_PARSER_CLEANUP
) {
223 SET_ADD_VAL(buffer
->used
, 0);
225 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE
);
227 return php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE
);
232 case PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX
:
233 if (buffer
->used
&& (*buffer
->data
== ' ' || *buffer
->data
== '\t')) {
234 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE
);
235 } else if (buffer
->used
|| (flags
& PHP_HTTP_HEADER_PARSER_CLEANUP
)) {
236 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE
);
239 return php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX
);
243 case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE
:
244 if (parser
->_key
.str
&& parser
->_val
.str
) {
246 size_t valid_len
= strlen(parser
->_val
.str
);
248 /* check for truncation */
249 if (valid_len
!= parser
->_val
.len
) {
250 php_http_header_parser_error(valid_len
, parser
->_val
.str
, parser
->_val
.len
, NULL
);
252 PTR_SET(parser
->_key
.str
, NULL
);
253 PTR_SET(parser
->_val
.str
, NULL
);
255 return php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE
);
258 if (!headers
&& callback_func
) {
259 callback_func(callback_arg
, &headers
, NULL
);
262 php_http_pretty_key(parser
->_key
.str
, parser
->_key
.len
, 1, 1);
263 if ((exist
= zend_symtable_str_find(headers
, parser
->_key
.str
, parser
->_key
.len
))) {
264 convert_to_array(exist
);
265 add_next_index_str(exist
, php_http_cs2zs(parser
->_val
.str
, parser
->_val
.len
));
267 ZVAL_STR(&tmp
, php_http_cs2zs(parser
->_val
.str
, parser
->_val
.len
));
268 zend_symtable_str_update(headers
, parser
->_key
.str
, parser
->_key
.len
, &tmp
);
270 parser
->_val
.str
= NULL
;
273 PTR_SET(parser
->_key
.str
, NULL
);
274 PTR_SET(parser
->_val
.str
, NULL
);
276 php_http_header_parser_state_push(parser
, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY
);
279 case PHP_HTTP_HEADER_PARSER_STATE_DONE
:
280 return PHP_HTTP_HEADER_PARSER_STATE_DONE
;
284 return php_http_header_parser_state_is(parser
);
287 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
)
289 php_http_header_parser_state_t state
= PHP_HTTP_HEADER_PARSER_STATE_START
;
292 php_http_buffer_resize_ex(buf
, 0x1000, 1, 0);
297 const char *states
[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
298 fprintf(stderr
, "#SHP: %s (f:%u)\n", states
[state
], flags
);
300 /* resize if needed */
301 if (buf
->free
< 0x1000) {
302 php_http_buffer_resize_ex(buf
, 0x1000, 1, 0);
305 case PHP_HTTP_HEADER_PARSER_STATE_FAILURE
:
306 case PHP_HTTP_HEADER_PARSER_STATE_DONE
:
311 php_stream_get_line(s
, buf
->data
+ buf
->used
, buf
->free
, &justread
);
312 /* if we fail reading a whole line, try a single char */
314 int c
= php_stream_getc(s
);
318 justread
= php_http_buffer_append(buf
, s
, 1);
321 php_http_buffer_account(buf
, justread
);
325 state
= php_http_header_parser_parse(parser
, buf
, flags
, headers
, callback_func
, callback_arg
);
326 } else if (php_stream_eof(s
)) {
327 return php_http_header_parser_parse(parser
, buf
, flags
| PHP_HTTP_HEADER_PARSER_CLEANUP
, headers
, callback_func
, callback_arg
);
333 return PHP_HTTP_HEADER_PARSER_STATE_DONE
;
336 static zend_class_entry
*php_http_header_parser_class_entry
;
337 zend_class_entry
*php_http_get_header_parser_class_entry(void)
339 return php_http_header_parser_class_entry
;
341 static zend_object_handlers php_http_header_parser_object_handlers
;
343 zend_object
*php_http_header_parser_object_new(zend_class_entry
*ce
)
345 return &php_http_header_parser_object_new_ex(ce
, NULL
)->zo
;
348 php_http_header_parser_object_t
*php_http_header_parser_object_new_ex(zend_class_entry
*ce
, php_http_header_parser_t
*parser
)
350 php_http_header_parser_object_t
*o
;
352 o
= ecalloc(1, sizeof(php_http_header_parser_object_t
) + zend_object_properties_size(ce
));
353 zend_object_std_init(&o
->zo
, ce
);
354 object_properties_init(&o
->zo
, ce
);
359 o
->parser
= php_http_header_parser_init(NULL
);
361 o
->buffer
= php_http_buffer_new();
363 o
->zo
.handlers
= &php_http_header_parser_object_handlers
;
368 void php_http_header_parser_object_free(zend_object
*object
)
370 php_http_header_parser_object_t
*o
= PHP_HTTP_OBJ(object
, NULL
);
373 php_http_header_parser_free(&o
->parser
);
376 php_http_buffer_free(&o
->buffer
);
378 zend_object_std_dtor(object
);
381 ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_getState
, 0, 0, 0)
383 static PHP_METHOD(HttpHeaderParser
, getState
)
385 php_http_header_parser_object_t
*parser_obj
= PHP_HTTP_OBJ(NULL
, getThis());
387 zend_parse_parameters_none();
388 /* always return the real state */
389 RETVAL_LONG(php_http_header_parser_state_is(parser_obj
->parser
));
392 ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_parse
, 0, 0, 3)
393 ZEND_ARG_INFO(0, data
)
394 ZEND_ARG_INFO(0, flags
)
395 ZEND_ARG_ARRAY_INFO(1, headers
, 1)
397 static PHP_METHOD(HttpHeaderParser
, parse
)
399 php_http_header_parser_object_t
*parser_obj
;
405 php_http_expect(SUCCESS
== zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &data_str
, &data_len
, &flags
, &zmsg
), invalid_arg
, return);
408 if (Z_TYPE_P(zmsg
) != IS_ARRAY
) {
412 parser_obj
= PHP_HTTP_OBJ(NULL
, getThis());
413 php_http_buffer_append(parser_obj
->buffer
, data_str
, data_len
);
414 RETVAL_LONG(php_http_header_parser_parse(parser_obj
->parser
, parser_obj
->buffer
, flags
, Z_ARRVAL_P(zmsg
), NULL
, NULL
));
417 ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_stream
, 0, 0, 3)
418 ZEND_ARG_INFO(0, stream
)
419 ZEND_ARG_INFO(0, flags
)
420 ZEND_ARG_ARRAY_INFO(1, headers
, 1)
422 static PHP_METHOD(HttpHeaderParser
, stream
)
424 php_http_header_parser_object_t
*parser_obj
;
425 zend_error_handling zeh
;
426 zval
*zmsg
, *zstream
;
430 php_http_expect(SUCCESS
== zend_parse_parameters(ZEND_NUM_ARGS(), "rlz", &zstream
, &flags
, &zmsg
), invalid_arg
, return);
432 zend_replace_error_handling(EH_THROW
, php_http_get_exception_unexpected_val_class_entry(), &zeh
);
433 php_stream_from_zval(s
, zstream
);
434 zend_restore_error_handling(&zeh
);
437 if (Z_TYPE_P(zmsg
) != IS_ARRAY
) {
441 parser_obj
= PHP_HTTP_OBJ(NULL
, getThis());
442 RETVAL_LONG(php_http_header_parser_parse_stream(parser_obj
->parser
, parser_obj
->buffer
, s
, flags
, Z_ARRVAL_P(zmsg
), NULL
, NULL
));
445 static zend_function_entry php_http_header_parser_methods
[] = {
446 PHP_ME(HttpHeaderParser
, getState
, ai_HttpHeaderParser_getState
, ZEND_ACC_PUBLIC
)
447 PHP_ME(HttpHeaderParser
, parse
, ai_HttpHeaderParser_parse
, ZEND_ACC_PUBLIC
)
448 PHP_ME(HttpHeaderParser
, stream
, ai_HttpHeaderParser_stream
, ZEND_ACC_PUBLIC
)
452 PHP_MINIT_FUNCTION(http_header_parser
)
456 INIT_NS_CLASS_ENTRY(ce
, "http\\Header", "Parser", php_http_header_parser_methods
);
457 php_http_header_parser_class_entry
= zend_register_internal_class(&ce
);
458 memcpy(&php_http_header_parser_object_handlers
, zend_get_std_object_handlers(), sizeof(zend_object_handlers
));
459 php_http_header_parser_class_entry
->create_object
= php_http_header_parser_object_new
;
460 php_http_header_parser_object_handlers
.offset
= XtOffsetOf(php_http_header_parser_object_t
, zo
);
461 php_http_header_parser_object_handlers
.clone_obj
= NULL
;
462 php_http_header_parser_object_handlers
.free_obj
= php_http_header_parser_object_free
;
464 zend_declare_class_constant_long(php_http_header_parser_class_entry
, ZEND_STRL("CLEANUP"), PHP_HTTP_HEADER_PARSER_CLEANUP
);
466 zend_declare_class_constant_long(php_http_header_parser_class_entry
, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_HEADER_PARSER_STATE_FAILURE
);
467 zend_declare_class_constant_long(php_http_header_parser_class_entry
, ZEND_STRL("STATE_START"), PHP_HTTP_HEADER_PARSER_STATE_START
);
468 zend_declare_class_constant_long(php_http_header_parser_class_entry
, ZEND_STRL("STATE_KEY"), PHP_HTTP_HEADER_PARSER_STATE_KEY
);
469 zend_declare_class_constant_long(php_http_header_parser_class_entry
, ZEND_STRL("STATE_VALUE"), PHP_HTTP_HEADER_PARSER_STATE_VALUE
);
470 zend_declare_class_constant_long(php_http_header_parser_class_entry
, ZEND_STRL("STATE_VALUE_EX"), PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX
);
471 zend_declare_class_constant_long(php_http_header_parser_class_entry
, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE
);
472 zend_declare_class_constant_long(php_http_header_parser_class_entry
, ZEND_STRL("STATE_DONE"), PHP_HTTP_HEADER_PARSER_STATE_DONE
);
482 * vim600: noet sw=4 ts=4 fdm=marker
483 * vim<600: noet sw=4 ts=4