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