Merge branch 'master' of git.php.net:/pecl/http/pecl_http
[m6w6/ext-http] / 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 typedef struct php_http_header_parser_state_spec {
16 php_http_header_parser_state_t state;
17 unsigned need_data:1;
18 } php_http_header_parser_state_spec_t;
19
20 static const php_http_header_parser_state_spec_t php_http_header_parser_states[] = {
21 {PHP_HTTP_HEADER_PARSER_STATE_START, 1},
22 {PHP_HTTP_HEADER_PARSER_STATE_KEY, 1},
23 {PHP_HTTP_HEADER_PARSER_STATE_VALUE, 1},
24 {PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX, 1},
25 {PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE, 0},
26 {PHP_HTTP_HEADER_PARSER_STATE_DONE, 0}
27 };
28
29 php_http_header_parser_t *php_http_header_parser_init(php_http_header_parser_t *parser TSRMLS_DC)
30 {
31 if (!parser) {
32 parser = emalloc(sizeof(*parser));
33 }
34 memset(parser, 0, sizeof(*parser));
35
36 TSRMLS_SET_CTX(parser->ts);
37
38 return parser;
39 }
40
41 php_http_header_parser_state_t php_http_header_parser_state_push(php_http_header_parser_t *parser, unsigned argc, ...)
42 {
43 va_list va_args;
44 unsigned i;
45 php_http_header_parser_state_t state = 0;
46
47 /* short circuit */
48 ZEND_PTR_STACK_RESIZE_IF_NEEDED((&parser->stack), argc);
49
50 va_start(va_args, argc);
51 for (i = 0; i < argc; ++i) {
52 state = va_arg(va_args, php_http_header_parser_state_t);
53 zend_ptr_stack_push(&parser->stack, (void *) state);
54 }
55 va_end(va_args);
56
57 return state;
58 }
59
60 php_http_header_parser_state_t php_http_header_parser_state_is(php_http_header_parser_t *parser)
61 {
62 php_http_header_parser_state_t state;
63
64 if (parser->stack.top) {
65 return (php_http_header_parser_state_t) zend_ptr_stack_top(&parser->stack);
66 }
67
68 return PHP_HTTP_HEADER_PARSER_STATE_START;
69 }
70
71 php_http_header_parser_state_t php_http_header_parser_state_pop(php_http_header_parser_t *parser)
72 {
73 if (parser->stack.top) {
74 return (php_http_header_parser_state_t) zend_ptr_stack_pop(&parser->stack);
75 }
76
77 return PHP_HTTP_HEADER_PARSER_STATE_START;
78 }
79
80 void php_http_header_parser_dtor(php_http_header_parser_t *parser)
81 {
82 zend_ptr_stack_destroy(&parser->stack);
83 php_http_info_dtor(&parser->info);
84 STR_FREE(parser->_key.str);
85 STR_FREE(parser->_val.str);
86 }
87
88 void php_http_header_parser_free(php_http_header_parser_t **parser)
89 {
90 if (*parser) {
91 php_http_header_parser_dtor(*parser);
92 efree(*parser);
93 *parser = NULL;
94 }
95 }
96
97 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)
98 {
99 TSRMLS_FETCH_FROM_CTX(parser->ts);
100
101 while (buffer->used || !php_http_header_parser_states[php_http_header_parser_state_is(parser)].need_data) {
102 #if 0
103 const char *state[] = {"START", "KEY", "VALUE", "HEADER_DONE", "DONE"};
104 fprintf(stderr, "#HP: %s (avail:%zu, num:%d)\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);
105 _dpf(0, buffer->data, buffer->used);
106 #endif
107 switch (php_http_header_parser_state_pop(parser)) {
108 case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
109 return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
110
111 case PHP_HTTP_HEADER_PARSER_STATE_START: {
112 char *ptr = buffer->data;
113
114 while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
115 ++ptr;
116 }
117
118 php_http_buffer_cut(buffer, 0, ptr - buffer->data);
119 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY);
120 break;
121 }
122
123 case PHP_HTTP_HEADER_PARSER_STATE_KEY: {
124 const char *colon, *eol_str = NULL;
125 int eol_len = 0;
126
127 if (buffer->data == (eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) {
128 /* end of headers */
129 php_http_buffer_cut(buffer, 0, eol_len);
130 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_DONE);
131 } else if (php_http_info_parse(&parser->info, php_http_buffer_fix(buffer)->data TSRMLS_CC)) {
132 /* new message starting with request/response line */
133 if (callback_func) {
134 callback_func(callback_arg, &headers, &parser->info TSRMLS_CC);
135 }
136 php_http_info_dtor(&parser->info);
137 php_http_buffer_cut(buffer, 0, eol_str + eol_len - buffer->data);
138 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
139 } else if ((colon = memchr(buffer->data, ':', buffer->used)) && (!eol_str || eol_str > colon)) {
140 /* header: string */
141 parser->_key.str = estrndup(buffer->data, parser->_key.len = colon - buffer->data);
142 while (PHP_HTTP_IS_CTYPE(space, *++colon) && *colon != '\n' && *colon != '\r');
143 php_http_buffer_cut(buffer, 0, colon - buffer->data);
144 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
145 } else {
146 /* neither reqeust/response line nor header: string */
147 return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
148 }
149 break;
150 }
151
152 case PHP_HTTP_HEADER_PARSER_STATE_VALUE: {
153 const char *eol_str;
154 int eol_len;
155
156 #define SET_ADD_VAL(slen, eol_len) \
157 do { \
158 const char *ptr = buffer->data; \
159 size_t len = slen; \
160 \
161 while (len > 0 && PHP_HTTP_IS_CTYPE(space, *ptr)) { \
162 ++ptr; \
163 --len; \
164 } \
165 while (len > 0 && PHP_HTTP_IS_CTYPE(space, ptr[len - 1])) { \
166 --len; \
167 } \
168 \
169 if (len > 0) { \
170 if (parser->_val.str) { \
171 parser->_val.str = erealloc(parser->_val.str, parser->_val.len + len + 2); \
172 parser->_val.str[parser->_val.len++] = ' '; \
173 memcpy(&parser->_val.str[parser->_val.len], ptr, len); \
174 parser->_val.len += len; \
175 parser->_val.str[parser->_val.len] = '\0'; \
176 } else { \
177 parser->_val.len = len; \
178 parser->_val.str = estrndup(ptr, len); \
179 } \
180 } \
181 php_http_buffer_cut(buffer, 0, slen + eol_len); \
182 } while (0)
183
184 if ((eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) {
185 SET_ADD_VAL(eol_str - buffer->data, eol_len);
186
187 if (buffer->used) {
188 if (*buffer->data != '\t' && *buffer->data != ' ') {
189 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
190 break;
191 } else {
192 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
193 break;
194 }
195 }
196 }
197
198 if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) {
199 if (buffer->used) {
200 SET_ADD_VAL(buffer->used, 0);
201 }
202 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
203 } else {
204 return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
205 }
206 break;
207 }
208
209 case PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX:
210 if (*buffer->data == ' ' || *buffer->data == '\t') {
211 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
212 } else {
213 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
214 }
215 break;
216
217 case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE:
218 if (parser->_key.str && parser->_val.str) {
219 zval array, **exist;
220
221 if (!headers && callback_func) {
222 callback_func(callback_arg, &headers, NULL TSRMLS_CC);
223 }
224
225 INIT_PZVAL_ARRAY(&array, headers);
226 php_http_pretty_key(parser->_key.str, parser->_key.len, 1, 1);
227 if (SUCCESS == zend_symtable_find(headers, parser->_key.str, parser->_key.len + 1, (void *) &exist)) {
228 convert_to_array(*exist);
229 add_next_index_stringl(*exist, parser->_val.str, parser->_val.len, 0);
230 } else {
231 add_assoc_stringl_ex(&array, parser->_key.str, parser->_key.len + 1, parser->_val.str, parser->_val.len, 0);
232 }
233 parser->_val.str = NULL;
234 }
235
236 STR_SET(parser->_key.str, NULL);
237 STR_SET(parser->_val.str, NULL);
238
239 php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY);
240 break;
241
242 case PHP_HTTP_HEADER_PARSER_STATE_DONE:
243 return PHP_HTTP_HEADER_PARSER_STATE_DONE;
244 }
245 }
246
247 return php_http_header_parser_state_is(parser);
248 }
249
250 /*
251 * Local variables:
252 * tab-width: 4
253 * c-basic-offset: 4
254 * End:
255 * vim600: noet sw=4 ts=4 fdm=marker
256 * vim<600: noet sw=4 ts=4
257 */
258