fix old and add new http\Client\Curl\Versions constants
[m6w6/ext-http] / src / php_http_header_parser.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
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 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14
15 #ifndef DBG_PARSER
16 # define DBG_PARSER 0
17 #endif
18
19 typedef struct php_http_header_parser_state_spec {
20 php_http_header_parser_state_t state;
21 unsigned need_data:1;
22 } php_http_header_parser_state_spec_t;
23
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}
31 };
32
33 php_http_header_parser_t *php_http_header_parser_init(php_http_header_parser_t *parser)
34 {
35 if (!parser) {
36 parser = emalloc(sizeof(*parser));
37 }
38 memset(parser, 0, sizeof(*parser));
39
40 return parser;
41 }
42
43 static inline php_http_header_parser_state_t
44 php_http_header_parser_state_push(php_http_header_parser_t *parser, php_http_header_parser_state_t state)
45 {
46 zend_ptr_stack_push(&(parser)->stack, (void *) (state));
47 return state;
48 }
49
50 #define php_http_header_parser_state_ex(parser) ((parser)->stack.top \
51 ? (php_http_header_parser_state_t) (parser)->stack.elements[(parser)->stack.top - 1] \
52 : PHP_HTTP_HEADER_PARSER_STATE_START)
53
54 php_http_header_parser_state_t php_http_header_parser_state_is(php_http_header_parser_t *parser)
55 {
56 return php_http_header_parser_state_ex(parser);
57 }
58
59 #define php_http_header_parser_state_pop(parser) ((parser)->stack.top \
60 ? (php_http_header_parser_state_t) zend_ptr_stack_pop(&(parser)->stack) \
61 : PHP_HTTP_HEADER_PARSER_STATE_START)
62
63 void php_http_header_parser_dtor(php_http_header_parser_t *parser)
64 {
65 zend_ptr_stack_destroy(&parser->stack);
66 php_http_info_dtor(&parser->info);
67 PTR_FREE(parser->_key.str);
68 PTR_FREE(parser->_val.str);
69 }
70
71 void php_http_header_parser_free(php_http_header_parser_t **parser)
72 {
73 if (*parser) {
74 php_http_header_parser_dtor(*parser);
75 efree(*parser);
76 *parser = NULL;
77 }
78 }
79
80 /* NOTE: 'str' has to be null terminated */
81 static void php_http_header_parser_error(size_t valid_len, char *str, size_t len, const char *eol_str )
82 {
83 zend_string *escaped_str, *zstr_str = zend_string_init(str, len, 0);
84
85 #if PHP_VERSION_ID < 70300
86 escaped_str = php_addcslashes(zstr_str, 1, ZEND_STRL("\x0..\x1F\x7F..\xFF"));
87 #else
88 escaped_str = php_addcslashes(zstr_str, ZEND_STRL("\x0..\x1F\x7F..\xFF"));
89 zend_string_release_ex(zstr_str, 0);
90 #endif
91
92 if (valid_len != len && (!eol_str || (str+valid_len) != eol_str)) {
93 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);
94 } else if (eol_str) {
95 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);
96 } else {
97 php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected end of input at pos %zu of '%s'", len, escaped_str->val);
98 }
99
100 efree(escaped_str);
101 }
102
103 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)
104 {
105 while (buffer->used || !php_http_header_parser_states[php_http_header_parser_state_ex(parser)].need_data) {
106 #if DBG_PARSER
107 const char *state[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
108 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);
109 _dpf(0, buffer->data, buffer->used);
110 #endif
111 switch (php_http_header_parser_state_pop(parser)) {
112 case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
113 php_error_docref(NULL, E_WARNING, "Failed to parse headers");
114 return php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
115
116 case PHP_HTTP_HEADER_PARSER_STATE_START: {
117 char *ptr = buffer->data;
118
119 while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
120 ++ptr;
121 }
122
123 php_http_buffer_cut(buffer, 0, ptr - buffer->data);
124 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_KEY);
125 break;
126 }
127
128 case PHP_HTTP_HEADER_PARSER_STATE_KEY: {
129 const char *colon, *eol_str = NULL;
130 int eol_len = 0;
131
132 /* fix buffer here, so eol_str pointer doesn't become obsolete afterwards */
133 php_http_buffer_fix(buffer);
134
135 if (buffer->data == (eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) {
136 /* end of headers */
137 php_http_buffer_cut(buffer, 0, eol_len);
138 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_DONE);
139 } else if (php_http_info_parse(&parser->info, buffer->data)) {
140 /* new message starting with request/response line */
141 if (callback_func) {
142 callback_func(callback_arg, &headers, &parser->info);
143 }
144 php_http_info_dtor(&parser->info);
145 php_http_buffer_cut(buffer, 0, eol_str + eol_len - buffer->data);
146 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
147 } else if ((colon = memchr(buffer->data, ':', buffer->used)) && (!eol_str || eol_str > colon)) {
148 /* header: string */
149 size_t valid_len;
150
151 parser->_key.len = colon - buffer->data;
152 parser->_key.str = estrndup(buffer->data, parser->_key.len);
153
154 valid_len = strspn(parser->_key.str, PHP_HTTP_HEADER_NAME_CHARS);
155 if (valid_len != parser->_key.len) {
156 php_http_header_parser_error(valid_len, parser->_key.str, parser->_key.len, eol_str);
157 PTR_SET(parser->_key.str, NULL);
158 return php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
159 }
160 while (PHP_HTTP_IS_CTYPE(space, *++colon) && *colon != '\n' && *colon != '\r');
161 php_http_buffer_cut(buffer, 0, colon - buffer->data);
162 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
163 } else if (eol_str || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) {
164 /* neither reqeust/response line nor 'header:' string, or injected new line or NUL etc. */
165 php_http_header_parser_error(strspn(buffer->data, PHP_HTTP_HEADER_NAME_CHARS), buffer->data, buffer->used, eol_str);
166 return php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
167 } else {
168 /* keep feeding */
169 return php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_KEY);
170 }
171 break;
172 }
173
174 case PHP_HTTP_HEADER_PARSER_STATE_VALUE: {
175 const char *eol_str;
176 int eol_len;
177
178 #define SET_ADD_VAL(slen, eol_len) \
179 do { \
180 const char *ptr = buffer->data; \
181 size_t len = slen; \
182 \
183 while (len > 0 && PHP_HTTP_IS_CTYPE(space, *ptr)) { \
184 ++ptr; \
185 --len; \
186 } \
187 while (len > 0 && PHP_HTTP_IS_CTYPE(space, ptr[len - 1])) { \
188 --len; \
189 } \
190 \
191 if (len > 0) { \
192 if (parser->_val.str) { \
193 parser->_val.str = erealloc(parser->_val.str, parser->_val.len + len + 2); \
194 parser->_val.str[parser->_val.len++] = ' '; \
195 memcpy(&parser->_val.str[parser->_val.len], ptr, len); \
196 parser->_val.len += len; \
197 parser->_val.str[parser->_val.len] = '\0'; \
198 } else { \
199 parser->_val.len = len; \
200 parser->_val.str = estrndup(ptr, len); \
201 } \
202 } \
203 php_http_buffer_cut(buffer, 0, slen + eol_len); \
204 } while (0)
205
206 if ((eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) {
207 SET_ADD_VAL(eol_str - buffer->data, eol_len);
208 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
209 } else if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) {
210 if (buffer->used) {
211 SET_ADD_VAL(buffer->used, 0);
212 }
213 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
214 } else {
215 return php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
216 }
217 break;
218 }
219
220 case PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX:
221 if (buffer->used && (*buffer->data == ' ' || *buffer->data == '\t')) {
222 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
223 } else if (buffer->used || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) {
224 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
225 } else {
226 /* keep feeding */
227 return php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
228 }
229 break;
230
231 case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE:
232 if (parser->_key.str && parser->_val.str) {
233 zval tmp, *exist;
234 size_t valid_len = strlen(parser->_val.str);
235
236 /* check for truncation */
237 if (valid_len != parser->_val.len) {
238 php_http_header_parser_error(valid_len, parser->_val.str, parser->_val.len, NULL);
239
240 PTR_SET(parser->_key.str, NULL);
241 PTR_SET(parser->_val.str, NULL);
242
243 return php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
244 }
245
246 if (!headers && callback_func) {
247 callback_func(callback_arg, &headers, NULL);
248 }
249
250 php_http_pretty_key(parser->_key.str, parser->_key.len, 1, 1);
251 if ((exist = zend_symtable_str_find(headers, parser->_key.str, parser->_key.len))) {
252 convert_to_array(exist);
253 add_next_index_str(exist, php_http_cs2zs(parser->_val.str, parser->_val.len));
254 } else {
255 ZVAL_STR(&tmp, php_http_cs2zs(parser->_val.str, parser->_val.len));
256 zend_symtable_str_update(headers, parser->_key.str, parser->_key.len, &tmp);
257 }
258 parser->_val.str = NULL;
259 }
260
261 PTR_SET(parser->_key.str, NULL);
262 PTR_SET(parser->_val.str, NULL);
263
264 php_http_header_parser_state_push(parser, PHP_HTTP_HEADER_PARSER_STATE_KEY);
265 break;
266
267 case PHP_HTTP_HEADER_PARSER_STATE_DONE:
268 return PHP_HTTP_HEADER_PARSER_STATE_DONE;
269 }
270 }
271
272 return php_http_header_parser_state_is(parser);
273 }
274
275 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)
276 {
277 php_http_header_parser_state_t state = PHP_HTTP_HEADER_PARSER_STATE_START;
278
279 if (!buf->data) {
280 php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
281 }
282 while (1) {
283 size_t justread = 0;
284 #if DBG_PARSER
285 const char *states[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
286 fprintf(stderr, "#SHP: %s (f:%u)\n", states[state], flags);
287 #endif
288 /* resize if needed */
289 if (buf->free < 0x1000) {
290 php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
291 }
292 switch (state) {
293 case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
294 case PHP_HTTP_HEADER_PARSER_STATE_DONE:
295 return state;
296
297 default:
298 /* read line */
299 php_stream_get_line(s, buf->data + buf->used, buf->free, &justread);
300 /* if we fail reading a whole line, try a single char */
301 if (!justread) {
302 int c = php_stream_getc(s);
303
304 if (c != EOF) {
305 char s[1] = {c};
306 justread = php_http_buffer_append(buf, s, 1);
307 }
308 }
309 php_http_buffer_account(buf, justread);
310 }
311
312 if (justread) {
313 state = php_http_header_parser_parse(parser, buf, flags, headers, callback_func, callback_arg);
314 } else if (php_stream_eof(s)) {
315 return php_http_header_parser_parse(parser, buf, flags | PHP_HTTP_HEADER_PARSER_CLEANUP, headers, callback_func, callback_arg);
316 } else {
317 return state;
318 }
319 }
320
321 return PHP_HTTP_HEADER_PARSER_STATE_DONE;
322 }
323
324 static zend_class_entry *php_http_header_parser_class_entry;
325 zend_class_entry *php_http_get_header_parser_class_entry(void)
326 {
327 return php_http_header_parser_class_entry;
328 }
329 static zend_object_handlers php_http_header_parser_object_handlers;
330
331 zend_object *php_http_header_parser_object_new(zend_class_entry *ce)
332 {
333 return &php_http_header_parser_object_new_ex(ce, NULL)->zo;
334 }
335
336 php_http_header_parser_object_t *php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser)
337 {
338 php_http_header_parser_object_t *o;
339
340 o = ecalloc(1, sizeof(php_http_header_parser_object_t) + zend_object_properties_size(ce));
341 zend_object_std_init(&o->zo, ce);
342 object_properties_init(&o->zo, ce);
343
344 if (parser) {
345 o->parser = parser;
346 } else {
347 o->parser = php_http_header_parser_init(NULL);
348 }
349 o->buffer = php_http_buffer_new();
350
351 o->zo.handlers = &php_http_header_parser_object_handlers;
352
353 return o;
354 }
355
356 void php_http_header_parser_object_free(zend_object *object)
357 {
358 php_http_header_parser_object_t *o = PHP_HTTP_OBJ(object, NULL);
359
360 if (o->parser) {
361 php_http_header_parser_free(&o->parser);
362 }
363 if (o->buffer) {
364 php_http_buffer_free(&o->buffer);
365 }
366 zend_object_std_dtor(object);
367 }
368
369 ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_getState, 0, 0, 0)
370 ZEND_END_ARG_INFO();
371 static PHP_METHOD(HttpHeaderParser, getState)
372 {
373 php_http_header_parser_object_t *parser_obj = PHP_HTTP_OBJ(NULL, getThis());
374
375 zend_parse_parameters_none();
376 /* always return the real state */
377 RETVAL_LONG(php_http_header_parser_state_is(parser_obj->parser));
378 }
379
380 ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_parse, 0, 0, 3)
381 ZEND_ARG_INFO(0, data)
382 ZEND_ARG_INFO(0, flags)
383 ZEND_ARG_ARRAY_INFO(1, headers, 1)
384 ZEND_END_ARG_INFO();
385 static PHP_METHOD(HttpHeaderParser, parse)
386 {
387 php_http_header_parser_object_t *parser_obj;
388 zval *zmsg;
389 char *data_str;
390 size_t data_len;
391 zend_long flags;
392
393 php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &data_str, &data_len, &flags, &zmsg), invalid_arg, return);
394
395 ZVAL_DEREF(zmsg);
396 if (Z_TYPE_P(zmsg) != IS_ARRAY) {
397 zval_dtor(zmsg);
398 array_init(zmsg);
399 }
400 parser_obj = PHP_HTTP_OBJ(NULL, getThis());
401 php_http_buffer_append(parser_obj->buffer, data_str, data_len);
402 RETVAL_LONG(php_http_header_parser_parse(parser_obj->parser, parser_obj->buffer, flags, Z_ARRVAL_P(zmsg), NULL, NULL));
403 }
404
405 ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_stream, 0, 0, 3)
406 ZEND_ARG_INFO(0, stream)
407 ZEND_ARG_INFO(0, flags)
408 ZEND_ARG_ARRAY_INFO(1, headers, 1)
409 ZEND_END_ARG_INFO();
410 static PHP_METHOD(HttpHeaderParser, stream)
411 {
412 php_http_header_parser_object_t *parser_obj;
413 zend_error_handling zeh;
414 zval *zmsg, *zstream;
415 php_stream *s;
416 zend_long flags;
417
418 php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "rlz", &zstream, &flags, &zmsg), invalid_arg, return);
419
420 zend_replace_error_handling(EH_THROW, php_http_get_exception_unexpected_val_class_entry(), &zeh);
421 php_stream_from_zval(s, zstream);
422 zend_restore_error_handling(&zeh);
423
424 ZVAL_DEREF(zmsg);
425 if (Z_TYPE_P(zmsg) != IS_ARRAY) {
426 zval_dtor(zmsg);
427 array_init(zmsg);
428 }
429 parser_obj = PHP_HTTP_OBJ(NULL, getThis());
430 RETVAL_LONG(php_http_header_parser_parse_stream(parser_obj->parser, parser_obj->buffer, s, flags, Z_ARRVAL_P(zmsg), NULL, NULL));
431 }
432
433 static zend_function_entry php_http_header_parser_methods[] = {
434 PHP_ME(HttpHeaderParser, getState, ai_HttpHeaderParser_getState, ZEND_ACC_PUBLIC)
435 PHP_ME(HttpHeaderParser, parse, ai_HttpHeaderParser_parse, ZEND_ACC_PUBLIC)
436 PHP_ME(HttpHeaderParser, stream, ai_HttpHeaderParser_stream, ZEND_ACC_PUBLIC)
437 {0}
438 };
439
440 PHP_MINIT_FUNCTION(http_header_parser)
441 {
442 zend_class_entry ce;
443
444 INIT_NS_CLASS_ENTRY(ce, "http\\Header", "Parser", php_http_header_parser_methods);
445 php_http_header_parser_class_entry = zend_register_internal_class(&ce);
446 memcpy(&php_http_header_parser_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
447 php_http_header_parser_class_entry->create_object = php_http_header_parser_object_new;
448 php_http_header_parser_object_handlers.offset = XtOffsetOf(php_http_header_parser_object_t, zo);
449 php_http_header_parser_object_handlers.clone_obj = NULL;
450 php_http_header_parser_object_handlers.free_obj = php_http_header_parser_object_free;
451
452 zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_HEADER_PARSER_CLEANUP);
453
454 zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
455 zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_HEADER_PARSER_STATE_START);
456 zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_KEY"), PHP_HTTP_HEADER_PARSER_STATE_KEY);
457 zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE"), PHP_HTTP_HEADER_PARSER_STATE_VALUE);
458 zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE_EX"), PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
459 zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
460 zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_HEADER_PARSER_STATE_DONE);
461
462 return SUCCESS;
463 }
464
465 /*
466 * Local variables:
467 * tab-width: 4
468 * c-basic-offset: 4
469 * End:
470 * vim600: noet sw=4 ts=4 fdm=marker
471 * vim<600: noet sw=4 ts=4
472 */
473