- initialize local zvals used as HashTable containers
[m6w6/ext-http] / http_headers_api.c
1 /*
2 +----------------------------------------------------------------------+
3 | PECL :: http |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.0 of the PHP license, that |
6 | is bundled with this package in the file LICENSE, and is available |
7 | through the world-wide-web at http://www.php.net/license/3_0.txt. |
8 | If you did not receive a copy of the PHP license and are unable to |
9 | obtain it through the world-wide-web, please send a note to |
10 | license@php.net so we can mail you a copy immediately. |
11 +----------------------------------------------------------------------+
12 | Copyright (c) 2004-2005 Michael Wallner <mike@php.net> |
13 +----------------------------------------------------------------------+
14 */
15
16 /* $Id$ */
17
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21 #include "php.h"
22
23 #include "ext/standard/php_string.h"
24 #include "ext/standard/url.h"
25
26 #include "php_http.h"
27 #include "php_http_std_defs.h"
28 #include "php_http_api.h"
29 #include "php_http_headers_api.h"
30 #include "php_http_info_api.h"
31
32 #include <ctype.h>
33
34 ZEND_EXTERN_MODULE_GLOBALS(http);
35
36 #ifndef HTTP_DBG_NEG
37 # define HTTP_DBG_NEG 0
38 #endif
39
40 /* {{{ static int http_sort_q(const void *, const void *) */
41 static int http_sort_q(const void *a, const void *b TSRMLS_DC)
42 {
43 Bucket *f, *s;
44 zval result, *first, *second;
45
46 f = *((Bucket **) a);
47 s = *((Bucket **) b);
48
49 first = *((zval **) f->pData);
50 second= *((zval **) s->pData);
51
52 if (numeric_compare_function(&result, first, second TSRMLS_CC) != SUCCESS) {
53 return 0;
54 }
55 return (Z_LVAL(result) > 0 ? -1 : (Z_LVAL(result) < 0 ? 1 : 0));
56 }
57 /* }}} */
58
59 /* {{{ char *http_negotiate_language_func */
60 char *_http_negotiate_language_func(const char *test, double *quality, HashTable *supported TSRMLS_DC)
61 {
62 zval **value;
63 const char *dash_test;
64
65 FOREACH_HASH_VAL(supported, value) {
66 #if HTTP_DBG_NEG
67 fprintf(stderr, "strcasecmp('%s', '%s')\n", Z_STRVAL_PP(value), test);
68 #endif
69 if (!strcasecmp(Z_STRVAL_PP(value), test)) {
70 return Z_STRVAL_PP(value);
71 }
72 }
73
74 /* no distinct match found, so try primaries */
75 if (dash_test = strchr(test, '-')) {
76 FOREACH_HASH_VAL(supported, value) {
77 int len = dash_test - test;
78 #if HTTP_DBG_NEG
79 fprintf(stderr, "strncascmp('%s', '%s', %d)\n", Z_STRVAL_PP(value), test, len);
80 #endif
81 if ( (!strncasecmp(Z_STRVAL_PP(value), test, len)) &&
82 ( (Z_STRVAL_PP(value)[len] == '\0') ||
83 (Z_STRVAL_PP(value)[len] == '-'))) {
84 *quality /= 2;
85 return Z_STRVAL_PP(value);
86 }
87 }
88 }
89
90 return NULL;
91 }
92 /* }}} */
93
94 /* {{{ char *http_negotiate_charset_func */
95 char *_http_negotiate_charset_func(const char *test, double *quality, HashTable *supported TSRMLS_DC)
96 {
97 zval **value;
98
99 FOREACH_HASH_VAL(supported, value) {
100 #if HTTP_DBG_NEG
101 fprintf(stderr, "strcasecmp('%s', '%s')\n", Z_STRVAL_PP(value), test);
102 #endif
103 if (!strcasecmp(Z_STRVAL_PP(value), test)) {
104 return Z_STRVAL_PP(value);
105 }
106 }
107
108 return NULL;
109 }
110 /* }}} */
111
112 /* {{{ HashTable *http_negotiate_q(const char *, HashTable *, negotiate_func_t) */
113 PHP_HTTP_API HashTable *_http_negotiate_q(const char *header, HashTable *supported, negotiate_func_t neg TSRMLS_DC)
114 {
115 zval *accept;
116 HashTable *result = NULL;
117
118 #if HTTP_DBG_NEG
119 fprintf(stderr, "Reading header %s: ", header);
120 #endif
121 HTTP_GSC(accept, header, NULL);
122 #if HTTP_DBG_NEG
123 fprintf(stderr, "%s\n", Z_STRVAL_P(accept));
124 #endif
125
126 if (Z_STRLEN_P(accept)) {
127 zval ex_arr, ex_del;
128
129 INIT_PZVAL(&ex_del);
130 INIT_PZVAL(&ex_arr);
131 ZVAL_STRINGL(&ex_del, ",", 1, 0);
132 array_init(&ex_arr);
133
134 php_explode(&ex_del, accept, &ex_arr, -1);
135
136 if (zend_hash_num_elements(Z_ARRVAL(ex_arr)) > 0) {
137 int i = 0;
138 zval **entry, array;
139
140 INIT_PZVAL(&array);
141 array_init(&array);
142
143 FOREACH_HASH_VAL(Z_ARRVAL(ex_arr), entry) {
144 double quality;
145 char *selected, *identifier;
146 const char *separator;
147
148 #if HTTP_DBG_NEG
149 fprintf(stderr, "Checking %s\n", Z_STRVAL_PP(entry));
150 #endif
151
152 if (separator = strchr(Z_STRVAL_PP(entry), ';')) {
153 const char *ptr = separator;
154
155 while (*++ptr && !isdigit(*ptr));
156
157 quality = strtod(ptr, NULL);
158 identifier = estrndup(Z_STRVAL_PP(entry), separator - Z_STRVAL_PP(entry));
159 } else {
160 quality = 1000.0 - i++;
161 identifier = estrndup(Z_STRVAL_PP(entry), Z_STRLEN_PP(entry));
162 }
163
164 if (selected = neg(identifier, &quality, supported TSRMLS_CC)) {
165 /* don't overwrite previously set with higher quality */
166 if (!zend_hash_exists(Z_ARRVAL(array), selected, strlen(selected) + 1)) {
167 add_assoc_double(&array, selected, quality);
168 }
169 }
170
171 efree(identifier);
172 }
173
174 result = Z_ARRVAL(array);
175 zend_hash_sort(result, zend_qsort, http_sort_q, 0 TSRMLS_CC);
176 }
177
178 zval_dtor(&ex_arr);
179 }
180
181 return result;
182 }
183 /* }}} */
184
185 /* {{{ http_range_status http_get_request_ranges(HashTable *ranges, size_t) */
186 PHP_HTTP_API http_range_status _http_get_request_ranges(HashTable *ranges, size_t length TSRMLS_DC)
187 {
188 zval *zrange;
189 char *range, c;
190 long begin = -1, end = -1, *ptr;
191
192 HTTP_GSC(zrange, "HTTP_RANGE", RANGE_NO);
193 range = Z_STRVAL_P(zrange);
194
195 if (strncmp(range, "bytes=", sizeof("bytes=") - 1)) {
196 return RANGE_NO;
197 }
198
199 ptr = &begin;
200 range += sizeof("bytes=") - 1;
201
202 do {
203 switch (c = *(range++))
204 {
205 case '0':
206 /* allow 000... - shall we? */
207 if (*ptr != -10) {
208 *ptr *= 10;
209 }
210 break;
211
212 case '1': case '2': case '3':
213 case '4': case '5': case '6':
214 case '7': case '8': case '9':
215 /*
216 * If the value of the pointer is already set (non-negative)
217 * then multiply its value by ten and add the current value,
218 * else initialise the pointers value with the current value
219 * --
220 * This let us recognize empty fields when validating the
221 * ranges, i.e. a "-10" for begin and "12345" for the end
222 * was the following range request: "Range: bytes=0-12345";
223 * While a "-1" for begin and "12345" for the end would
224 * have been: "Range: bytes=-12345".
225 */
226 if (*ptr > 0) {
227 *ptr *= 10;
228 *ptr += c - '0';
229 } else {
230 *ptr = c - '0';
231 }
232 break;
233
234 case '-':
235 ptr = &end;
236 break;
237
238 case ' ':
239 /* IE - ignore for now */
240 break;
241
242 case 0:
243 case ',':
244
245 if (length) {
246 /* validate ranges */
247 switch (begin)
248 {
249 /* "0-12345" */
250 case -10:
251 /* "0-" */
252 if (end == -1) {
253 return RANGE_NO;
254 }
255 /* "0-0" or overflow */
256 if (end == -10 || length <= (size_t) end) {
257 return RANGE_ERR;
258 }
259 begin = 0;
260 break;
261
262 /* "-12345" */
263 case -1:
264 /* "-", "-0" or overflow */
265 if (end == -1 || end == -10 || length <= (size_t) end) {
266 return RANGE_ERR;
267 }
268 begin = length - end;
269 end = length - 1;
270 break;
271
272 /* "12345-(xxx)" */
273 default:
274 switch (end)
275 {
276 /* "12345-0" */
277 case -10:
278 return RANGE_ERR;
279 break;
280
281 /* "12345-" */
282 case -1:
283 if (length <= (size_t) begin) {
284 return RANGE_ERR;
285 }
286 end = length - 1;
287 break;
288
289 /* "12345-67890" */
290 default:
291 if ( (length <= (size_t) begin) ||
292 (length <= (size_t) end) ||
293 (end < begin)) {
294 return RANGE_ERR;
295 }
296 break;
297 }
298 break;
299 }
300 }
301 {
302 zval *zentry;
303 MAKE_STD_ZVAL(zentry);
304 array_init(zentry);
305 add_index_long(zentry, 0, begin);
306 add_index_long(zentry, 1, end);
307 zend_hash_next_index_insert(ranges, &zentry, sizeof(zval *), NULL);
308
309 begin = -1;
310 end = -1;
311 ptr = &begin;
312 }
313 break;
314
315 default:
316 return RANGE_NO;
317 break;
318 }
319 } while (c != 0);
320
321 return RANGE_OK;
322 }
323 /* }}} */
324
325 /* {{{ STATUS http_parse_headers(char *, HashTable *, zend_bool) */
326 PHP_HTTP_API STATUS _http_parse_headers_ex(const char *header, HashTable *headers, zend_bool prettify,
327 http_info_callback callback_func, void **callback_data TSRMLS_DC)
328 {
329 const char *colon = NULL, *line = NULL, *begin = header;
330 const char *body = http_locate_body(header);
331 size_t header_len;
332 zval array;
333
334 INIT_PZVAL(&array);
335 Z_TYPE_P(array) = IS_ARRAY;
336 Z_ARRVAL(array) = headers;
337
338 if (body) {
339 header_len = body - header;
340 } else {
341 header_len = strlen(header) + 1;
342 }
343 line = header;
344
345 while (header_len >= (size_t) (line - begin)) {
346 int value_len = 0;
347 /* note: valgrind may choke on that -- should be safe though */
348 switch (*line++)
349 {
350 case ':':
351 if (!colon) {
352 colon = line - 1;
353 }
354 break;
355
356 case 0:
357 --value_len; /* we don't have CR so value length is one char less */
358 case '\n':
359 if ((!(*line - 1)) || ((*line != ' ') && (*line != '\t'))) {
360 http_info i;
361
362 /* response/request line */
363 if (SUCCESS == http_info_parse(header, &i)) {
364 callback_func(callback_data, &headers, &i TSRMLS_CC);
365 http_info_dtor(&i);
366 Z_ARRVAL(array) = headers;
367 } else
368
369 /* "header: value" pair */
370 if (colon) {
371
372 /* skip empty key */
373 if (header != colon) {
374 zval **previous = NULL;
375 char *value;
376 int keylen = colon - header;
377 char *key = estrndup(header, keylen);
378
379 if (prettify) {
380 key = pretty_key(key, keylen, 1, 1);
381 }
382
383 value_len += line - colon - 1;
384
385 /* skip leading ws */
386 while (isspace(*(++colon))) --value_len;
387 /* skip trailing ws */
388 while (isspace(colon[value_len - 1])) --value_len;
389
390 if (value_len > 0) {
391 value = estrndup(colon, value_len);
392 } else {
393 value = estrdup("");
394 value_len = 0;
395 }
396
397 /* if we already have got such a header make an array of those */
398 if (SUCCESS == zend_hash_find(headers, key, keylen + 1, (void **) &previous)) {
399 /* convert to array */
400 if (Z_TYPE_PP(previous) != IS_ARRAY) {
401 convert_to_array(*previous);
402 }
403 add_next_index_stringl(*previous, value, value_len, 0);
404 } else {
405 add_assoc_stringl(&array, key, value, value_len, 0);
406 }
407 efree(key);
408 }
409 }
410 colon = NULL;
411 value_len = 0;
412 header += line - header;
413 }
414 break;
415 }
416 }
417 return SUCCESS;
418 }
419 /* }}} */
420
421 /* {{{ void http_get_request_headers_ex(HashTable *, zend_bool) */
422 PHP_HTTP_API void _http_get_request_headers_ex(HashTable *headers, zend_bool prettify TSRMLS_DC)
423 {
424 char *key = NULL;
425 ulong idx = 0;
426 zval array, **hsv;
427
428 Z_ARRVAL(array) = headers;
429
430 if (SUCCESS == zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &hsv)) {
431 FOREACH_KEY(*hsv, key, idx) {
432 if (key && !strncmp(key, "HTTP_", 5)) {
433 zval **header;
434
435 key += 5;
436 if (prettify) {
437 key = pretty_key(key, strlen(key), 1, 1);
438 }
439
440 zend_hash_get_current_data(Z_ARRVAL_PP(hsv), (void **) &header);
441 add_assoc_stringl(&array, key, Z_STRVAL_PP(header), Z_STRLEN_PP(header), 1);
442 key = NULL;
443 }
444 }
445 }
446 }
447 /* }}} */
448
449 /* {{{ zend_bool http_match_request_header(char *, char *) */
450 PHP_HTTP_API zend_bool _http_match_request_header_ex(const char *header, const char *value, zend_bool match_case TSRMLS_DC)
451 {
452 char *name, *key = NULL;
453 ulong idx;
454 zend_bool result = 0;
455 HashTable headers;
456
457 name = pretty_key(estrdup(header), strlen(header), 1, 1);
458 zend_hash_init(&headers, 0, NULL, ZVAL_PTR_DTOR, 0);
459 http_get_request_headers_ex(&headers, 1);
460
461 FOREACH_HASH_KEY(&headers, key, idx) {
462 if (key && (!strcmp(key, name))) {
463 zval **data;
464
465 if (SUCCESS == zend_hash_get_current_data(&headers, (void **) &data)) {
466 result = (match_case ? strcmp(Z_STRVAL_PP(data), value) : strcasecmp(Z_STRVAL_PP(data), value)) ? 0 : 1;
467 }
468 break;
469 }
470 }
471
472 zend_hash_destroy(&headers);
473 efree(name);
474
475 return result;
476 }
477 /* }}} */
478
479
480 /*
481 * Local variables:
482 * tab-width: 4
483 * c-basic-offset: 4
484 * End:
485 * vim600: noet sw=4 ts=4 fdm=marker
486 * vim<600: noet sw=4 ts=4
487 */
488