* compatibility with older libcurl versions, take 2
[m6w6/ext-http] / http_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 #define _WINSOCKAPI_
19 #define ZEND_INCLUDE_FULL_WINDOWS_HEADERS
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <ctype.h>
26
27 #include "php.h"
28 #include "php_version.h"
29 #include "php_streams.h"
30 #include "snprintf.h"
31 #include "ext/standard/md5.h"
32 #include "ext/standard/url.h"
33 #include "ext/standard/base64.h"
34 #include "ext/standard/php_string.h"
35 #include "ext/standard/php_smart_str.h"
36 #include "ext/standard/php_lcg.h"
37
38 #include "SAPI.h"
39
40 #ifdef ZEND_ENGINE_2
41 # include "ext/standard/php_http.h"
42 #else
43 #include "http_build_query.c"
44 #endif
45
46 #include "php_http.h"
47 #include "php_http_api.h"
48
49 #ifdef HTTP_HAVE_CURL
50
51 # ifdef PHP_WIN32
52 # include <winsock2.h>
53 # include <sys/types.h>
54 # endif
55
56 # include <curl/curl.h>
57 # include <curl/easy.h>
58
59 #endif
60
61 #if !defined(CURLINFO_RESONSE_CODE) && defined(CURLINFO_HTTP_CODE)
62 #define CURLINFO_RESONSE_CODE CURLINFO_HTTP_CODE
63 #endif
64
65 ZEND_DECLARE_MODULE_GLOBALS(http)
66
67 /* {{{ day/month names */
68 static const char *days[] = {
69 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
70 };
71 static const char *wkdays[] = {
72 "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
73 };
74 static const char *weekdays[] = {
75 "Monday", "Tuesday", "Wednesday",
76 "Thursday", "Friday", "Saturday", "Sunday"
77 };
78 static const char *months[] = {
79 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
80 "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"
81 };
82 enum assume_next {
83 DATE_MDAY,
84 DATE_YEAR,
85 DATE_TIME
86 };
87 static const struct time_zone {
88 const char *name;
89 const int offset;
90 } time_zones[] = {
91 {"GMT", 0}, /* Greenwich Mean */
92 {"UTC", 0}, /* Universal (Coordinated) */
93 {"WET", 0}, /* Western European */
94 {"BST", 0}, /* British Summer */
95 {"WAT", 60}, /* West Africa */
96 {"AST", 240}, /* Atlantic Standard */
97 {"ADT", 240}, /* Atlantic Daylight */
98 {"EST", 300}, /* Eastern Standard */
99 {"EDT", 300}, /* Eastern Daylight */
100 {"CST", 360}, /* Central Standard */
101 {"CDT", 360}, /* Central Daylight */
102 {"MST", 420}, /* Mountain Standard */
103 {"MDT", 420}, /* Mountain Daylight */
104 {"PST", 480}, /* Pacific Standard */
105 {"PDT", 480}, /* Pacific Daylight */
106 {"YST", 540}, /* Yukon Standard */
107 {"YDT", 540}, /* Yukon Daylight */
108 {"HST", 600}, /* Hawaii Standard */
109 {"HDT", 600}, /* Hawaii Daylight */
110 {"CAT", 600}, /* Central Alaska */
111 {"AHST", 600}, /* Alaska-Hawaii Standard */
112 {"NT", 660}, /* Nome */
113 {"IDLW", 720}, /* International Date Line West */
114 {"CET", -60}, /* Central European */
115 {"MET", -60}, /* Middle European */
116 {"MEWT", -60}, /* Middle European Winter */
117 {"MEST", -120}, /* Middle European Summer */
118 {"CEST", -120}, /* Central European Summer */
119 {"MESZ", -60}, /* Middle European Summer */
120 {"FWT", -60}, /* French Winter */
121 {"FST", -60}, /* French Summer */
122 {"EET", -120}, /* Eastern Europe, USSR Zone 1 */
123 {"WAST", -420}, /* West Australian Standard */
124 {"WADT", -420}, /* West Australian Daylight */
125 {"CCT", -480}, /* China Coast, USSR Zone 7 */
126 {"JST", -540}, /* Japan Standard, USSR Zone 8 */
127 {"EAST", -600}, /* Eastern Australian Standard */
128 {"EADT", -600}, /* Eastern Australian Daylight */
129 {"GST", -600}, /* Guam Standard, USSR Zone 9 */
130 {"NZT", -720}, /* New Zealand */
131 {"NZST", -720}, /* New Zealand Standard */
132 {"NZDT", -720}, /* New Zealand Daylight */
133 {"IDLE", -720}, /* International Date Line East */
134 };
135 /* }}} */
136
137 /* {{{ internals */
138
139 static int http_sort_q(const void *a, const void *b TSRMLS_DC);
140 #define http_send_chunk(d, b, e, m) _http_send_chunk((d), (b), (e), (m) TSRMLS_CC)
141 static STATUS _http_send_chunk(const void *data, const size_t begin, const size_t end, const http_send_mode mode TSRMLS_DC);
142
143 static int check_day(char *day, size_t len);
144 static int check_month(char *month);
145 static int check_tzone(char *tzone);
146
147 static char *pretty_key(char *key, int key_len, int uctitle, int xhyphen);
148
149 static int http_ob_stack_get(php_ob_buffer *, php_ob_buffer **);
150
151 /* {{{ HAVE_CURL */
152 #ifdef HTTP_HAVE_CURL
153 #define http_curl_initbuf(m) _http_curl_initbuf((m) TSRMLS_CC)
154 static inline void _http_curl_initbuf(http_curlbuf_member member TSRMLS_DC);
155 #define http_curl_freebuf(m) _http_curl_freebuf((m) TSRMLS_CC)
156 static inline void _http_curl_freebuf(http_curlbuf_member member TSRMLS_DC);
157 #define http_curl_sizebuf(m, l) _http_curl_sizebuf((m), (l) TSRMLS_CC)
158 static inline void _http_curl_sizebuf(http_curlbuf_member member, size_t len TSRMLS_DC);
159 #define http_curl_movebuf(m, d, l) _http_curl_movebuf((m), (d), (l) TSRMLS_CC)
160 static inline void _http_curl_movebuf(http_curlbuf_member member, char **data, size_t *data_len TSRMLS_DC);
161 #define http_curl_copybuf(m, d, l) _http_curl_copybuf((m), (d), (l) TSRMLS_CC)
162 static inline void _http_curl_copybuf(http_curlbuf_member member, char **data, size_t *data_len TSRMLS_DC);
163 #define http_curl_setopts(c, u, o) _http_curl_setopts((c), (u), (o) TSRMLS_CC)
164 static inline void _http_curl_setopts(CURL *ch, const char *url, HashTable *options TSRMLS_DC);
165
166 #define http_curl_getopt(o, k) _http_curl_getopt((o), (k) TSRMLS_CC, 0)
167 #define http_curl_getopt1(o, k, t1) _http_curl_getopt((o), (k) TSRMLS_CC, 1, (t1))
168 #define http_curl_getopt2(o, k, t1, t2) _http_curl_getopt((o), (k) TSRMLS_CC, 2, (t1), (t2))
169 static inline zval *_http_curl_getopt(HashTable *options, char *key TSRMLS_DC, int checks, ...);
170
171 static size_t http_curl_body_callback(char *, size_t, size_t, void *);
172 static size_t http_curl_hdrs_callback(char *, size_t, size_t, void *);
173
174 #define http_curl_getinfo(c, h) _http_curl_getinfo((c), (h) TSRMLS_CC)
175 static inline void _http_curl_getinfo(CURL *ch, HashTable *info TSRMLS_DC);
176 #define http_curl_getinfo_ex(c, i, a) _http_curl_getinfo_ex((c), (i), (a) TSRMLS_CC)
177 static inline void _http_curl_getinfo_ex(CURL *ch, CURLINFO i, zval *array TSRMLS_DC);
178 #define http_curl_getinfoname(i) _http_curl_getinfoname((i) TSRMLS_CC)
179 static inline char *_http_curl_getinfoname(CURLINFO i TSRMLS_DC);
180
181 #endif
182 /* }}} HAVE_CURL */
183
184 /* {{{ static int http_sort_q(const void *, const void *) */
185 static int http_sort_q(const void *a, const void *b TSRMLS_DC)
186 {
187 Bucket *f, *s;
188 zval result, *first, *second;
189
190 f = *((Bucket **) a);
191 s = *((Bucket **) b);
192
193 first = *((zval **) f->pData);
194 second= *((zval **) s->pData);
195
196 if (numeric_compare_function(&result, first, second TSRMLS_CC) != SUCCESS) {
197 return 0;
198 }
199 return (Z_LVAL(result) > 0 ? -1 : (Z_LVAL(result) < 0 ? 1 : 0));
200 }
201 /* }}} */
202
203 /* {{{ static STATUS http_send_chunk(const void *, size_t, size_t,
204 http_send_mode) */
205 static STATUS _http_send_chunk(const void *data, const size_t begin,
206 const size_t end, const http_send_mode mode TSRMLS_DC)
207 {
208 char *buf;
209 size_t read = 0;
210 long len = end - begin;
211 php_stream *s;
212
213 switch (mode)
214 {
215 case SEND_RSRC:
216 s = (php_stream *) data;
217 if (php_stream_seek(s, begin, SEEK_SET)) {
218 return FAILURE;
219 }
220 buf = (char *) ecalloc(1, HTTP_BUF_SIZE);
221 /* read into buf and write out */
222 while ((len -= HTTP_BUF_SIZE) >= 0) {
223 if (!(read = php_stream_read(s, buf, HTTP_BUF_SIZE))) {
224 efree(buf);
225 return FAILURE;
226 }
227 if (read - php_body_write(buf, read TSRMLS_CC)) {
228 efree(buf);
229 return FAILURE;
230 }
231 }
232
233 /* read & write left over */
234 if (len) {
235 if (read = php_stream_read(s, buf, HTTP_BUF_SIZE + len)) {
236 if (read - php_body_write(buf, read TSRMLS_CC)) {
237 efree(buf);
238 return FAILURE;
239 }
240 } else {
241 efree(buf);
242 return FAILURE;
243 }
244 }
245 efree(buf);
246 return SUCCESS;
247 break;
248
249 case SEND_DATA:
250 return len == php_body_write(((char *)data) + begin, len TSRMLS_CC)
251 ? SUCCESS : FAILURE;
252 break;
253
254 default:
255 return FAILURE;
256 break;
257 }
258 }
259 /* }}} */
260
261 /* {{{ HAVE_CURL */
262 #ifdef HTTP_HAVE_CURL
263
264 /* {{{ static inline void http_curl_initbuf(http_curlbuf_member) */
265 static inline void _http_curl_initbuf(http_curlbuf_member member TSRMLS_DC)
266 {
267 http_curl_freebuf(member);
268
269 if (member & CURLBUF_HDRS) {
270 HTTP_G(curlbuf).hdrs.data = emalloc(HTTP_CURLBUF_HDRSSIZE);
271 HTTP_G(curlbuf).hdrs.free = HTTP_CURLBUF_HDRSSIZE;
272 }
273 if (member & CURLBUF_BODY) {
274 HTTP_G(curlbuf).body.data = emalloc(HTTP_CURLBUF_BODYSIZE);
275 HTTP_G(curlbuf).body.free = HTTP_CURLBUF_BODYSIZE;
276 }
277 }
278 /* }}} */
279
280 /* {{{ static inline void http_curl_freebuf(http_curlbuf_member) */
281 static inline void _http_curl_freebuf(http_curlbuf_member member TSRMLS_DC)
282 {
283 if (member & CURLBUF_HDRS) {
284 if (HTTP_G(curlbuf).hdrs.data) {
285 efree(HTTP_G(curlbuf).hdrs.data);
286 HTTP_G(curlbuf).hdrs.data = NULL;
287 }
288 HTTP_G(curlbuf).hdrs.used = 0;
289 HTTP_G(curlbuf).hdrs.free = 0;
290 }
291 if (member & CURLBUF_BODY) {
292 if (HTTP_G(curlbuf).body.data) {
293 efree(HTTP_G(curlbuf).body.data);
294 HTTP_G(curlbuf).body.data = NULL;
295 }
296 HTTP_G(curlbuf).body.used = 0;
297 HTTP_G(curlbuf).body.free = 0;
298 }
299 }
300 /* }}} */
301
302 /* {{{ static inline void http_curl_copybuf(http_curlbuf_member, char **,
303 size_t *) */
304 static inline void _http_curl_copybuf(http_curlbuf_member member, char **data,
305 size_t *data_len TSRMLS_DC)
306 {
307 *data = NULL;
308 *data_len = 0;
309
310 if ((member & CURLBUF_HDRS) && HTTP_G(curlbuf).hdrs.used) {
311 if ((member & CURLBUF_BODY) && HTTP_G(curlbuf).body.used) {
312 *data = emalloc(HTTP_G(curlbuf).hdrs.used + HTTP_G(curlbuf).body.used + 1);
313 } else {
314 *data = emalloc(HTTP_G(curlbuf).hdrs.used + 1);
315 }
316 memcpy(*data, HTTP_G(curlbuf).hdrs.data, HTTP_G(curlbuf).hdrs.used);
317 *data_len = HTTP_G(curlbuf).hdrs.used;
318 }
319
320 if ((member & CURLBUF_BODY) && HTTP_G(curlbuf).body.used) {
321 if (*data) {
322 memcpy((*data) + HTTP_G(curlbuf).hdrs.used,
323 HTTP_G(curlbuf).body.data, HTTP_G(curlbuf).body.used);
324 *data_len = HTTP_G(curlbuf).hdrs.used + HTTP_G(curlbuf).body.used;
325 } else {
326 emalloc(HTTP_G(curlbuf).body.used + 1);
327 memcpy(*data, HTTP_G(curlbuf).body.data, HTTP_G(curlbuf).body.used);
328 *data_len = HTTP_G(curlbuf).body.used;
329 }
330 }
331 if (*data) {
332 (*data)[*data_len] = 0;
333 } else {
334 *data = "";
335 }
336 }
337 /* }}} */
338
339 /* {{{ static inline void http_curl_movebuf(http_curlbuf_member, char **,
340 size_t *) */
341 static inline void _http_curl_movebuf(http_curlbuf_member member, char **data,
342 size_t *data_len TSRMLS_DC)
343 {
344 http_curl_copybuf(member, data, data_len);
345 http_curl_freebuf(member);
346 }
347 /* }}} */
348
349 /* {{{ static size_t http_curl_body_callback(char *, size_t, size_t, void *) */
350 static size_t http_curl_body_callback(char *buf, size_t len, size_t n, void *s)
351 {
352 TSRMLS_FETCH();
353
354 if ((len *= n) > HTTP_G(curlbuf).body.free) {
355 size_t bsize = HTTP_CURLBUF_BODYSIZE;
356 while (bsize < len) {
357 bsize *= 2;
358 }
359 HTTP_G(curlbuf).body.data = erealloc(HTTP_G(curlbuf).body.data,
360 HTTP_G(curlbuf).body.used + bsize);
361 HTTP_G(curlbuf).body.free += bsize;
362 }
363
364 memcpy(HTTP_G(curlbuf).body.data + HTTP_G(curlbuf).body.used, buf, len);
365 HTTP_G(curlbuf).body.free -= len;
366 HTTP_G(curlbuf).body.used += len;
367
368 return len;
369 }
370 /* }}} */
371
372 /* {{{ static size_t http_curl_hdrs_callback(char*, size_t, size_t, void *) */
373 static size_t http_curl_hdrs_callback(char *buf, size_t len, size_t n, void *s)
374 {
375 TSRMLS_FETCH();
376
377 /* discard previous headers */
378 if ((HTTP_G(curlbuf).hdrs.used) && (!strncmp(buf, "HTTP/1.", strlen("HTTP/1.")))) {
379 http_curl_initbuf(CURLBUF_HDRS);
380 }
381
382 if ((len *= n) > HTTP_G(curlbuf).hdrs.free) {
383 size_t bsize = HTTP_CURLBUF_HDRSSIZE;
384 while (bsize < len) {
385 bsize *= 2;
386 }
387 HTTP_G(curlbuf).hdrs.data = erealloc(HTTP_G(curlbuf).hdrs.data,
388 HTTP_G(curlbuf).hdrs.used + bsize);
389 HTTP_G(curlbuf).hdrs.free += bsize;
390 }
391
392 memcpy(HTTP_G(curlbuf).hdrs.data + HTTP_G(curlbuf).hdrs.used, buf, len);
393 HTTP_G(curlbuf).hdrs.free -= len;
394 HTTP_G(curlbuf).hdrs.used += len;
395
396 return len;
397 }
398 /* }}} */
399
400 /* {{{ static inline zval *http_curl_getopt(HashTable *, char *, int, ...) */
401 static inline zval *_http_curl_getopt(HashTable *options, char *key TSRMLS_DC, int checks, ...)
402 {
403 zval **zoption;
404 va_list types;
405 int i;
406
407 if (SUCCESS != zend_hash_find(options, key, strlen(key) + 1, (void **) &zoption)) {
408 return NULL;
409 }
410 if (checks < 1) {
411 return *zoption;
412 }
413
414 va_start(types, checks);
415 for (i = 0; i < checks; ++i) {
416 if ((va_arg(types, int)) == (Z_TYPE_PP(zoption))) {
417 va_end(types);
418 return *zoption;
419 }
420 }
421 va_end(types);
422 return NULL;
423 }
424 /* }}} */
425
426 /* {{{ static inline void http_curl_setopts(CURL *, char *, HashTable *) */
427 static inline void _http_curl_setopts(CURL *ch, const char *url, HashTable *options TSRMLS_DC)
428 {
429 zval *zoption;
430
431 /* standard options */
432 curl_easy_setopt(ch, CURLOPT_URL, url);
433 curl_easy_setopt(ch, CURLOPT_HEADER, 0);
434 curl_easy_setopt(ch, CURLOPT_NOPROGRESS, 1);
435 curl_easy_setopt(ch, CURLOPT_AUTOREFERER, 1);
436 curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, http_curl_body_callback);
437 curl_easy_setopt(ch, CURLOPT_HEADERFUNCTION, http_curl_hdrs_callback);
438 #ifdef ZTS
439 curl_easy_setopt(ch, CURLOPT_NOSIGNAL, 1);
440 #endif
441
442 if ((!options) || (1 > zend_hash_num_elements(options))) {
443 return;
444 }
445
446 /* redirects, defaults to 0 */
447 if (zoption = http_curl_getopt1(options, "redirect", IS_LONG)) {
448 curl_easy_setopt(ch, CURLOPT_FOLLOWLOCATION, Z_LVAL_P(zoption) ? 1 : 0);
449 curl_easy_setopt(ch, CURLOPT_MAXREDIRS, Z_LVAL_P(zoption));
450 if (zoption = http_curl_getopt2(options, "unrestrictedauth", IS_LONG, IS_BOOL)) {
451 curl_easy_setopt(ch, CURLOPT_UNRESTRICTED_AUTH, Z_LVAL_P(zoption));
452 }
453 } else {
454 curl_easy_setopt(ch, CURLOPT_FOLLOWLOCATION, 0);
455 }
456
457 /* proxy */
458 if (zoption = http_curl_getopt1(options, "proxyhost", IS_STRING)) {
459 curl_easy_setopt(ch, CURLOPT_PROXY, Z_STRVAL_P(zoption));
460 /* port */
461 if (zoption = http_curl_getopt1(options, "proxyport", IS_LONG)) {
462 curl_easy_setopt(ch, CURLOPT_PROXYPORT, Z_LVAL_P(zoption));
463 }
464 /* user:pass */
465 if (zoption = http_curl_getopt1(options, "proxyauth", IS_STRING)) {
466 curl_easy_setopt(ch, CURLOPT_PROXYUSERPWD, Z_STRVAL_P(zoption));
467 }
468 #if LIBCURL_VERSION_NUM > 0x070a06
469 /* auth method */
470 if (zoption = http_curl_getopt1(options, "proxyauthtype", IS_LONG)) {
471 curl_easy_setopt(ch, CURLOPT_PROXYAUTH, Z_LVAL_P(zoption));
472 }
473 #endif
474 }
475
476 /* auth */
477 if (zoption = http_curl_getopt1(options, "httpauth", IS_STRING)) {
478 curl_easy_setopt(ch, CURLOPT_USERPWD, Z_STRVAL_P(zoption));
479 }
480 #if LIBCURL_VERSION_NUM > 0x070a05
481 if (zoption = http_curl_getopt1(options, "httpauthtype", IS_LONG)) {
482 curl_easy_setopt(ch, CURLOPT_HTTPAUTH, Z_LVAL_P(zoption));
483 }
484 #endif
485
486 /* compress, enabled by default (empty string enables deflate and gzip) */
487 if (zoption = http_curl_getopt2(options, "compress", IS_LONG, IS_BOOL)) {
488 if (Z_LVAL_P(zoption)) {
489 curl_easy_setopt(ch, CURLOPT_ENCODING, "");
490 }
491 } else {
492 curl_easy_setopt(ch, CURLOPT_ENCODING, "");
493 }
494
495 /* another port */
496 if (zoption = http_curl_getopt1(options, "port", IS_LONG)) {
497 curl_easy_setopt(ch, CURLOPT_PORT, Z_LVAL_P(zoption));
498 }
499
500 /* referer */
501 if (zoption = http_curl_getopt1(options, "referer", IS_STRING)) {
502 curl_easy_setopt(ch, CURLOPT_REFERER, Z_STRVAL_P(zoption));
503 }
504
505 /* useragent, default "PECL::HTTP/version (PHP/version)" */
506 if (zoption = http_curl_getopt1(options, "useragent", IS_STRING)) {
507 curl_easy_setopt(ch, CURLOPT_USERAGENT, Z_STRVAL_P(zoption));
508 } else {
509 curl_easy_setopt(ch, CURLOPT_USERAGENT,
510 "PECL::HTTP/" PHP_EXT_HTTP_VERSION " (PHP/" PHP_VERSION ")");
511 }
512
513 /* cookies, array('name' => 'value') */
514 if (zoption = http_curl_getopt1(options, "cookies", IS_ARRAY)) {
515 char *cookie_key;
516 zval **cookie_val;
517 int key_type;
518 smart_str qstr = {0};
519
520 zend_hash_internal_pointer_reset(Z_ARRVAL_P(zoption));
521 while (HASH_KEY_NON_EXISTANT != (key_type = zend_hash_get_current_key_type(Z_ARRVAL_P(zoption)))) {
522 if (key_type == HASH_KEY_IS_STRING) {
523 zend_hash_get_current_key(Z_ARRVAL_P(zoption), &cookie_key, NULL, 0);
524 zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void **) &cookie_val);
525 smart_str_appends(&qstr, cookie_key);
526 smart_str_appendl(&qstr, "=", 1);
527 smart_str_appendl(&qstr, Z_STRVAL_PP(cookie_val), Z_STRLEN_PP(cookie_val));
528 smart_str_appendl(&qstr, "; ", 2);
529 zend_hash_move_forward(Z_ARRVAL_P(zoption));
530 }
531 }
532 smart_str_0(&qstr);
533
534 if (qstr.c) {
535 curl_easy_setopt(ch, CURLOPT_COOKIE, qstr.c);
536 }
537 }
538
539 /* cookiestore */
540 if (zoption = http_curl_getopt1(options, "cookiestore", IS_STRING)) {
541 curl_easy_setopt(ch, CURLOPT_COOKIEFILE, Z_STRVAL_P(zoption));
542 curl_easy_setopt(ch, CURLOPT_COOKIEJAR, Z_STRVAL_P(zoption));
543 }
544
545 /* additional headers, array('name' => 'value') */
546 if (zoption = http_curl_getopt1(options, "headers", IS_ARRAY)) {
547 int key_type;
548 char *header_key, header[1024] = {0};
549 zval **header_val;
550 struct curl_slist *headers = NULL;
551
552 zend_hash_internal_pointer_reset(Z_ARRVAL_P(zoption));
553 while (HASH_KEY_NON_EXISTANT != (key_type = zend_hash_get_current_key_type(Z_ARRVAL_P(zoption)))) {
554 if (key_type == HASH_KEY_IS_STRING) {
555 zend_hash_get_current_key(Z_ARRVAL_P(zoption), &header_key, NULL, 0);
556 zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void **) &header_val);
557 snprintf(header, 1023, "%s: %s", header_key, Z_STRVAL_PP(header_val));
558 headers = curl_slist_append(headers, header);
559 zend_hash_move_forward(Z_ARRVAL_P(zoption));
560 }
561 }
562 if (headers) {
563 curl_easy_setopt(ch, CURLOPT_HTTPHEADER, headers);
564 }
565 }
566 }
567 /* }}} */
568
569 /* {{{ static inline char *http_curl_getinfoname(CURLINFO) */
570 static inline char *_http_curl_getinfoname(CURLINFO i TSRMLS_DC)
571 {
572 #define CASE(I) case CURLINFO_ ##I : { static char I[] = #I; return pretty_key(I, sizeof(#I)-1, 0, 0); }
573 switch (i)
574 {
575 /* CURLINFO_EFFECTIVE_URL = CURLINFO_STRING +1, */
576 CASE(EFFECTIVE_URL);
577 /* CURLINFO_RESPONSE_CODE = CURLINFO_LONG +2, */
578 #if LIBCURL_VERSION_NUM > 0x070a06
579 CASE(RESPONSE_CODE);
580 #else
581 CASE(HTTP_CODE);
582 #endif
583 /* CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE +3, */
584 CASE(TOTAL_TIME);
585 /* CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE +4, */
586 CASE(NAMELOOKUP_TIME);
587 /* CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE +5, */
588 CASE(CONNECT_TIME);
589 /* CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE +6, */
590 CASE(PRETRANSFER_TIME);
591 /* CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE +7, */
592 CASE(SIZE_UPLOAD);
593 /* CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE +8, */
594 CASE(SIZE_DOWNLOAD);
595 /* CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE +9, */
596 CASE(SPEED_DOWNLOAD);
597 /* CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE +10, */
598 CASE(SPEED_UPLOAD);
599 /* CURLINFO_HEADER_SIZE = CURLINFO_LONG +11, */
600 CASE(HEADER_SIZE);
601 /* CURLINFO_REQUEST_SIZE = CURLINFO_LONG +12, */
602 CASE(REQUEST_SIZE);
603 /* CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG +13, */
604 CASE(SSL_VERIFYRESULT);
605 /* CURLINFO_FILETIME = CURLINFO_LONG +14, */
606 CASE(FILETIME);
607 /* CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE +15, */
608 CASE(CONTENT_LENGTH_DOWNLOAD);
609 /* CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE +16, */
610 CASE(CONTENT_LENGTH_UPLOAD);
611 /* CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE +17, */
612 CASE(STARTTRANSFER_TIME);
613 /* CURLINFO_CONTENT_TYPE = CURLINFO_STRING +18, */
614 CASE(CONTENT_TYPE);
615 /* CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE +19, */
616 CASE(REDIRECT_TIME);
617 /* CURLINFO_REDIRECT_COUNT = CURLINFO_LONG +20, */
618 CASE(REDIRECT_COUNT);
619 /* CURLINFO_PRIVATE = CURLINFO_STRING +21, * (mike) /
620 CASE(PRIVATE);
621 /* CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG +22, */
622 CASE(HTTP_CONNECTCODE);
623 #if LIBCURL_VERSION_NUM > 0x070a07
624 /* CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG +23, */
625 CASE(HTTPAUTH_AVAIL);
626 /* CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG +24, */
627 CASE(PROXYAUTH_AVAIL);
628 #endif
629 }
630 #undef CASE
631 return NULL;
632 }
633 /* }}} */
634
635 /* {{{ static inline void http_curl_getinfo_ex(CURL, CURLINFO, zval *) */
636 static inline void _http_curl_getinfo_ex(CURL *ch, CURLINFO i, zval *array TSRMLS_DC)
637 {
638 char *key;
639 if (key = http_curl_getinfoname(i)) {
640 switch (i & ~CURLINFO_MASK)
641 {
642 case CURLINFO_STRING:
643 {
644 char *c;
645 if (CURLE_OK == curl_easy_getinfo(ch, i, &c)) {
646 add_assoc_string(array, key, c ? c : "", 1);
647 }
648 }
649 break;
650
651 case CURLINFO_DOUBLE:
652 {
653 double d;
654 if (CURLE_OK == curl_easy_getinfo(ch, i, &d)) {
655 add_assoc_double(array, key, d);
656 }
657 }
658 break;
659
660 case CURLINFO_LONG:
661 {
662 long l;
663 if (CURLE_OK == curl_easy_getinfo(ch, i, &l)) {
664 add_assoc_long(array, key, l);
665 }
666 }
667 break;
668 }
669 }
670 }
671 /* }}} */
672
673 /* {{{ static inline http_curl_getinfo(CURL, HashTable *) */
674 static inline void _http_curl_getinfo(CURL *ch, HashTable *info TSRMLS_DC)
675 {
676 zval array;
677 Z_ARRVAL(array) = info;
678
679 #define INFO(I) http_curl_getinfo_ex(ch, CURLINFO_ ##I , &array)
680 /* CURLINFO_EFFECTIVE_URL = CURLINFO_STRING +1, */
681 INFO(EFFECTIVE_URL);
682 #if LIBCURL_VERSION_NUM > 0x070a06
683 /* CURLINFO_RESPONSE_CODE = CURLINFO_LONG +2, */
684 INFO(RESPONSE_CODE);
685 #else
686 INFO(HTTP_CODE);
687 #endif
688 /* CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE +3, */
689 INFO(TOTAL_TIME);
690 /* CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE +4, */
691 INFO(NAMELOOKUP_TIME);
692 /* CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE +5, */
693 INFO(CONNECT_TIME);
694 /* CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE +6, */
695 INFO(PRETRANSFER_TIME);
696 /* CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE +7, */
697 INFO(SIZE_UPLOAD);
698 /* CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE +8, */
699 INFO(SIZE_DOWNLOAD);
700 /* CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE +9, */
701 INFO(SPEED_DOWNLOAD);
702 /* CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE +10, */
703 INFO(SPEED_UPLOAD);
704 /* CURLINFO_HEADER_SIZE = CURLINFO_LONG +11, */
705 INFO(HEADER_SIZE);
706 /* CURLINFO_REQUEST_SIZE = CURLINFO_LONG +12, */
707 INFO(REQUEST_SIZE);
708 /* CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG +13, */
709 INFO(SSL_VERIFYRESULT);
710 /* CURLINFO_FILETIME = CURLINFO_LONG +14, */
711 INFO(FILETIME);
712 /* CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE +15, */
713 INFO(CONTENT_LENGTH_DOWNLOAD);
714 /* CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE +16, */
715 INFO(CONTENT_LENGTH_UPLOAD);
716 /* CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE +17, */
717 INFO(STARTTRANSFER_TIME);
718 /* CURLINFO_CONTENT_TYPE = CURLINFO_STRING +18, */
719 INFO(CONTENT_TYPE);
720 /* CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE +19, */
721 INFO(REDIRECT_TIME);
722 /* CURLINFO_REDIRECT_COUNT = CURLINFO_LONG +20, */
723 INFO(REDIRECT_COUNT);
724 /* CURLINFO_PRIVATE = CURLINFO_STRING +21, */
725 INFO(PRIVATE);
726 /* CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG +22, */
727 INFO(HTTP_CONNECTCODE);
728 #if LIBCURL_VERSION_NUM > 0x070a07
729 /* CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG +23, */
730 INFO(HTTPAUTH_AVAIL);
731 /* CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG +24, */
732 INFO(PROXYAUTH_AVAIL);
733 #endif
734 #undef INFO
735 }
736 /* }}} */
737
738 #endif
739 /* }}} HAVE_CURL */
740
741 /* {{{ Day/Month/TZ checks for http_parse_date()
742 Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
743 static int check_day(char *day, size_t len)
744 {
745 int i;
746 const char * const *check = (len > 3) ? &weekdays[0] : &wkdays[0];
747 for (i = 0; i < 7; i++) {
748 if (!strcmp(day, check[0])) {
749 return i;
750 }
751 check++;
752 }
753 return -1;
754 }
755
756 static int check_month(char *month)
757 {
758 int i;
759 const char * const *check = &months[0];
760 for (i = 0; i < 12; i++) {
761 if (!strcmp(month, check[0])) {
762 return i;
763 }
764 check++;
765 }
766 return -1;
767 }
768
769 /* return the time zone offset between GMT and the input one, in number
770 of seconds or -1 if the timezone wasn't found/legal */
771
772 static int check_tzone(char *tzone)
773 {
774 int i;
775 const struct time_zone *check = time_zones;
776 for (i = 0; i < sizeof(time_zones) / sizeof(time_zones[0]); i++) {
777 if (!strcmp(tzone, check->name)) {
778 return check->offset * 60;
779 }
780 check++;
781 }
782 return -1;
783 }
784 /* }}} */
785
786 /* static char *pretty_key(char *, int, int, int) */
787 static char *pretty_key(char *key, int key_len, int uctitle, int xhyphen)
788 {
789 if (key && key_len) {
790 int i, wasalpha;
791 if (wasalpha = isalpha(key[0])) {
792 key[0] = uctitle ? toupper(key[0]) : tolower(key[0]);
793 }
794 for (i = 1; i < key_len; i++) {
795 if (isalpha(key[i])) {
796 key[i] = ((!wasalpha) && uctitle) ? toupper(key[i]) : tolower(key[i]);
797 wasalpha = 1;
798 } else {
799 if (xhyphen && (key[i] == '_')) {
800 key[i] = '-';
801 }
802 wasalpha = 0;
803 }
804 }
805 }
806 return key;
807 }
808 /* }}} */
809
810 /* {{{ static STATUS http_ob_stack_get(php_ob_buffer *, php_ob_buffer **) */
811 static STATUS http_ob_stack_get(php_ob_buffer *o, php_ob_buffer **s)
812 {
813 static int i = 0;
814 php_ob_buffer *b = emalloc(sizeof(php_ob_buffer));
815 b->handler_name = estrdup(o->handler_name);
816 b->buffer = estrndup(o->buffer, o->text_length);
817 b->text_length = o->text_length;
818 b->chunk_size = o->chunk_size;
819 b->erase = o->erase;
820 s[i++] = b;
821 return SUCCESS;
822 }
823 /* }}} */
824
825 /* }}} internals */
826
827 /* {{{ public API */
828
829 /* {{{ char *http_date(time_t) */
830 PHP_HTTP_API char *_http_date(time_t t TSRMLS_DC)
831 {
832 struct tm *gmtime, tmbuf;
833 char *date = ecalloc(31, 1);
834
835 gmtime = php_gmtime_r(&t, &tmbuf);
836 snprintf(date, 30,
837 "%s, %02d %s %04d %02d:%02d:%02d GMT",
838 days[gmtime->tm_wday], gmtime->tm_mday,
839 months[gmtime->tm_mon], gmtime->tm_year + 1900,
840 gmtime->tm_hour, gmtime->tm_min, gmtime->tm_sec
841 );
842 return date;
843 }
844 /* }}} */
845
846 /* {{{ time_t http_parse_date(char *)
847 Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
848 PHP_HTTP_API time_t _http_parse_date(const char *date)
849 {
850 time_t t = 0;
851 int tz_offset = -1, year = -1, month = -1, monthday = -1, weekday = -1,
852 hours = -1, minutes = -1, seconds = -1;
853 struct tm tm;
854 enum assume_next dignext = DATE_MDAY;
855 const char *indate = date;
856
857 int found = 0, part = 0; /* max 6 parts */
858
859 while (*date && (part < 6)) {
860 int found = 0;
861
862 while (*date && !isalnum(*date)) {
863 date++;
864 }
865
866 if (isalpha(*date)) {
867 /* a name coming up */
868 char buf[32] = "";
869 size_t len;
870 sscanf(date, "%31[A-Za-z]", buf);
871 len = strlen(buf);
872
873 if (weekday == -1) {
874 weekday = check_day(buf, len);
875 if (weekday != -1) {
876 found = 1;
877 }
878 }
879
880 if (!found && (month == -1)) {
881 month = check_month(buf);
882 if (month != -1) {
883 found = 1;
884 }
885 }
886
887 if (!found && (tz_offset == -1)) {
888 /* this just must be a time zone string */
889 tz_offset = check_tzone(buf);
890 if (tz_offset != -1) {
891 found = 1;
892 }
893 }
894
895 if (!found) {
896 return -1; /* bad string */
897 }
898 date += len;
899 }
900 else if (isdigit(*date)) {
901 /* a digit */
902 int val;
903 char *end;
904 if((seconds == -1) && (3 == sscanf(date, "%02d:%02d:%02d", &hours, &minutes, &seconds))) {
905 /* time stamp! */
906 date += 8;
907 found = 1;
908 }
909 else {
910 val = (int) strtol(date, &end, 10);
911
912 if ((tz_offset == -1) && ((end - date) == 4) && (val < 1300) && (indate < date) && ((date[-1] == '+' || date[-1] == '-'))) {
913 /* four digits and a value less than 1300 and it is preceeded with
914 a plus or minus. This is a time zone indication. */
915 found = 1;
916 tz_offset = (val / 100 * 60 + val % 100) * 60;
917
918 /* the + and - prefix indicates the local time compared to GMT,
919 this we need ther reversed math to get what we want */
920 tz_offset = date[-1] == '+' ? -tz_offset : tz_offset;
921 }
922
923 if (((end - date) == 8) && (year == -1) && (month == -1) && (monthday == -1)) {
924 /* 8 digits, no year, month or day yet. This is YYYYMMDD */
925 found = 1;
926 year = val / 10000;
927 month = (val % 10000) / 100 - 1; /* month is 0 - 11 */
928 monthday = val % 100;
929 }
930
931 if (!found && (dignext == DATE_MDAY) && (monthday == -1)) {
932 if ((val > 0) && (val < 32)) {
933 monthday = val;
934 found = 1;
935 }
936 dignext = DATE_YEAR;
937 }
938
939 if (!found && (dignext == DATE_YEAR) && (year == -1)) {
940 year = val;
941 found = 1;
942 if (year < 1900) {
943 year += year > 70 ? 1900 : 2000;
944 }
945 if(monthday == -1) {
946 dignext = DATE_MDAY;
947 }
948 }
949
950 if (!found) {
951 return -1;
952 }
953
954 date = end;
955 }
956 }
957
958 part++;
959 }
960
961 if (-1 == seconds) {
962 seconds = minutes = hours = 0; /* no time, make it zero */
963 }
964
965 if ((-1 == monthday) || (-1 == month) || (-1 == year)) {
966 /* lacks vital info, fail */
967 return -1;
968 }
969
970 if (sizeof(time_t) < 5) {
971 /* 32 bit time_t can only hold dates to the beginning of 2038 */
972 if (year > 2037) {
973 return 0x7fffffff;
974 }
975 }
976
977 tm.tm_sec = seconds;
978 tm.tm_min = minutes;
979 tm.tm_hour = hours;
980 tm.tm_mday = monthday;
981 tm.tm_mon = month;
982 tm.tm_year = year - 1900;
983 tm.tm_wday = 0;
984 tm.tm_yday = 0;
985 tm.tm_isdst = 0;
986
987 t = mktime(&tm);
988
989 /* time zone adjust */
990 {
991 struct tm *gmt, keeptime2;
992 long delta;
993 time_t t2;
994
995 if(!(gmt = php_gmtime_r(&t, &keeptime2))) {
996 return -1; /* illegal date/time */
997 }
998
999 t2 = mktime(gmt);
1000
1001 /* Add the time zone diff (between the given timezone and GMT) and the
1002 diff between the local time zone and GMT. */
1003 delta = (tz_offset != -1 ? tz_offset : 0) + (t - t2);
1004
1005 if((delta > 0) && (t + delta < t)) {
1006 return -1; /* time_t overflow */
1007 }
1008
1009 t += delta;
1010 }
1011
1012 return t;
1013 }
1014 /* }}} */
1015
1016 /* {{{ inline char *http_etag(void *, size_t, http_send_mode) */
1017 PHP_HTTP_API inline char *_http_etag(const void *data_ptr, const size_t data_len,
1018 const http_send_mode data_mode TSRMLS_DC)
1019 {
1020 char ssb_buf[128] = {0};
1021 unsigned char digest[16];
1022 PHP_MD5_CTX ctx;
1023 char *new_etag = ecalloc(33, 1);
1024
1025 PHP_MD5Init(&ctx);
1026
1027 switch (data_mode)
1028 {
1029 case SEND_DATA:
1030 PHP_MD5Update(&ctx, data_ptr, data_len);
1031 break;
1032
1033 case SEND_RSRC:
1034 if (!HTTP_G(ssb).sb.st_ino) {
1035 if (php_stream_stat((php_stream *) data_ptr, &HTTP_G(ssb))) {
1036 return NULL;
1037 }
1038 }
1039 snprintf(ssb_buf, 127, "%ld=%ld=%ld",
1040 HTTP_G(ssb).sb.st_mtime,
1041 HTTP_G(ssb).sb.st_ino,
1042 HTTP_G(ssb).sb.st_size
1043 );
1044 PHP_MD5Update(&ctx, ssb_buf, strlen(ssb_buf));
1045 break;
1046
1047 default:
1048 efree(new_etag);
1049 return NULL;
1050 break;
1051 }
1052
1053 PHP_MD5Final(digest, &ctx);
1054 make_digest(new_etag, digest);
1055
1056 return new_etag;
1057 }
1058 /* }}} */
1059
1060 /* {{{ inline http_lmod(void *, http_send_mode) */
1061 PHP_HTTP_API inline time_t _http_lmod(const void *data_ptr, const http_send_mode data_mode TSRMLS_DC)
1062 {
1063 switch (data_mode)
1064 {
1065 case SEND_DATA:
1066 {
1067 return time(NULL);
1068 }
1069
1070 case SEND_RSRC:
1071 {
1072 if (!HTTP_G(ssb).sb.st_mtime) {
1073 if (php_stream_stat((php_stream *) data_ptr, &HTTP_G(ssb))) {
1074 return 0;
1075 }
1076 }
1077 return HTTP_G(ssb).sb.st_mtime;
1078 }
1079
1080 default:
1081 {
1082 if (!HTTP_G(ssb).sb.st_mtime) {
1083 if(php_stream_stat_path(Z_STRVAL_P((zval *) data_ptr), &HTTP_G(ssb))) {
1084 return 0;
1085 }
1086 }
1087 return HTTP_G(ssb).sb.st_mtime;
1088 }
1089 }
1090 }
1091 /* }}} */
1092
1093 /* {{{inline int http_is_range_request(void) */
1094 PHP_HTTP_API inline int _http_is_range_request(TSRMLS_D)
1095 {
1096 return zend_hash_exists(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]),
1097 "HTTP_RANGE", strlen("HTTP_RANGE") + 1);
1098 }
1099 /* }}} */
1100
1101 /* {{{ inline STATUS http_send_status(int) */
1102 PHP_HTTP_API inline STATUS _http_send_status(const int status TSRMLS_DC)
1103 {
1104 int s = status;
1105 return sapi_header_op(SAPI_HEADER_SET_STATUS, (void *) s TSRMLS_CC);
1106 }
1107 /* }}} */
1108
1109 /* {{{ inline STATUS http_send_header(char *) */
1110 PHP_HTTP_API inline STATUS _http_send_header(const char *header TSRMLS_DC)
1111 {
1112 return http_send_status_header(0, header);
1113 }
1114 /* }}} */
1115
1116 /* {{{ inline STATUS http_send_status_header(int, char *) */
1117 PHP_HTTP_API inline STATUS _http_send_status_header(const int status, const char *header TSRMLS_DC)
1118 {
1119 sapi_header_line h = {(char *) header, strlen(header), status};
1120 return sapi_header_op(SAPI_HEADER_REPLACE, &h TSRMLS_CC);
1121 }
1122 /* }}} */
1123
1124 /* {{{ inline zval *http_get_server_var(char *) */
1125 PHP_HTTP_API inline zval *_http_get_server_var(const char *key TSRMLS_DC)
1126 {
1127 zval **var;
1128 if (SUCCESS == zend_hash_find(
1129 HTTP_SERVER_VARS,
1130 (char *) key, strlen(key) + 1, (void **) &var)) {
1131 return *var;
1132 }
1133 return NULL;
1134 }
1135 /* }}} */
1136
1137 /* {{{ void http_ob_etaghandler(char *, uint, char **, uint *, int) */
1138 PHP_HTTP_API void _http_ob_etaghandler(char *output, uint output_len,
1139 char **handled_output, uint *handled_output_len, int mode TSRMLS_DC)
1140 {
1141 char etag[33] = { 0 };
1142 unsigned char digest[16];
1143
1144 if (mode & PHP_OUTPUT_HANDLER_START) {
1145 PHP_MD5Init(&HTTP_G(etag_md5));
1146 }
1147
1148 PHP_MD5Update(&HTTP_G(etag_md5), output, output_len);
1149
1150 if (mode & PHP_OUTPUT_HANDLER_END) {
1151 PHP_MD5Final(digest, &HTTP_G(etag_md5));
1152
1153 /* just do that if desired */
1154 if (HTTP_G(etag_started)) {
1155 make_digest(etag, digest);
1156
1157 if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1158 http_send_status(304);
1159 } else {
1160 http_send_etag(etag, 32);
1161 }
1162 }
1163 }
1164
1165 *handled_output_len = output_len;
1166 *handled_output = estrndup(output, output_len);
1167 }
1168 /* }}} */
1169
1170 /* {{{ STATUS http_start_ob_handler(php_output_handler_func_t, char *, uint, zend_bool) */
1171 PHP_HTTP_API STATUS _http_start_ob_handler(php_output_handler_func_t handler_func,
1172 char *handler_name, uint chunk_size, zend_bool erase TSRMLS_DC)
1173 {
1174 php_ob_buffer **stack;
1175 int count, i;
1176
1177 if (count = OG(ob_nesting_level)) {
1178 stack = ecalloc(sizeof(php_ob_buffer *), count);
1179
1180 if (count > 1) {
1181 zend_stack_apply_with_argument(&OG(ob_buffers), ZEND_STACK_APPLY_BOTTOMUP,
1182 (int (*)(void *elem, void *)) http_ob_stack_get, stack);
1183 }
1184
1185 if (count > 0) {
1186 http_ob_stack_get(&OG(active_ob_buffer), stack);
1187 }
1188
1189 while (OG(ob_nesting_level)) {
1190 php_end_ob_buffer(0, 0 TSRMLS_CC);
1191 }
1192 }
1193
1194 php_ob_set_internal_handler(handler_func, chunk_size, handler_name, erase TSRMLS_CC);
1195
1196 for (i = 0; i < count; i++) {
1197 php_ob_buffer *s = stack[i];
1198 if (strcmp(s->handler_name, "default output handler")) {
1199 php_start_ob_buffer_named(s->handler_name, s->chunk_size, s->erase TSRMLS_CC);
1200 }
1201 php_body_write(s->buffer, s->text_length TSRMLS_CC);
1202 efree(s->handler_name);
1203 efree(s->buffer);
1204 efree(s);
1205 }
1206 if (count) {
1207 efree(stack);
1208 }
1209
1210 return SUCCESS;
1211 }
1212 /* }}} */
1213
1214 /* {{{ int http_modified_match(char *, int) */
1215 PHP_HTTP_API int _http_modified_match(const char *entry, const time_t t TSRMLS_DC)
1216 {
1217 int retval;
1218 zval *zmodified;
1219 char *modified, *chr_ptr;
1220
1221 HTTP_GSC(zmodified, entry, 0);
1222
1223 modified = estrndup(Z_STRVAL_P(zmodified), Z_STRLEN_P(zmodified));
1224 if (chr_ptr = strrchr(modified, ';')) {
1225 chr_ptr = 0;
1226 }
1227 retval = (t <= http_parse_date(modified));
1228 efree(modified);
1229 return retval;
1230 }
1231 /* }}} */
1232
1233 /* {{{ int http_etag_match(char *, char *) */
1234 PHP_HTTP_API int _http_etag_match(const char *entry, const char *etag TSRMLS_DC)
1235 {
1236 zval *zetag;
1237 char *quoted_etag;
1238 STATUS result;
1239
1240 HTTP_GSC(zetag, entry, 0);
1241
1242 if (NULL != strchr(Z_STRVAL_P(zetag), '*')) {
1243 return 1;
1244 }
1245
1246 quoted_etag = (char *) emalloc(strlen(etag) + 3);
1247 sprintf(quoted_etag, "\"%s\"", etag);
1248
1249 if (!strchr(Z_STRVAL_P(zetag), ',')) {
1250 result = !strcmp(Z_STRVAL_P(zetag), quoted_etag);
1251 } else {
1252 result = (NULL != strstr(Z_STRVAL_P(zetag), quoted_etag));
1253 }
1254 efree(quoted_etag);
1255 return result;
1256 }
1257 /* }}} */
1258
1259 /* {{{ STATUS http_send_last_modified(int) */
1260 PHP_HTTP_API STATUS _http_send_last_modified(const time_t t TSRMLS_DC)
1261 {
1262 char modified[96] = "Last-Modified: ", *date;
1263 date = http_date(t);
1264 strcat(modified, date);
1265 efree(date);
1266
1267 /* remember */
1268 HTTP_G(lmod) = t;
1269
1270 return http_send_header(modified);
1271 }
1272 /* }}} */
1273
1274 /* {{{ static STATUS http_send_etag(char *, int) */
1275 PHP_HTTP_API STATUS _http_send_etag(const char *etag,
1276 const int etag_len TSRMLS_DC)
1277 {
1278 STATUS status;
1279 char *etag_header;
1280
1281 if (!etag_len){
1282 php_error_docref(NULL TSRMLS_CC,E_ERROR,
1283 "Attempt to send empty ETag (previous: %s)\n", HTTP_G(etag));
1284 return FAILURE;
1285 }
1286
1287 /* remember */
1288 if (HTTP_G(etag)) {
1289 efree(HTTP_G(etag));
1290 }
1291 HTTP_G(etag) = estrdup(etag);
1292
1293 etag_header = ecalloc(sizeof("ETag: \"\"") + etag_len, 1);
1294 sprintf(etag_header, "ETag: \"%s\"", etag);
1295 if (SUCCESS != (status = http_send_header(etag_header))) {
1296 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't send '%s' header", etag_header);
1297 }
1298 efree(etag_header);
1299 return status;
1300 }
1301 /* }}} */
1302
1303 /* {{{ STATUS http_send_cache_control(char *, size_t) */
1304 PHP_HTTP_API STATUS _http_send_cache_control(const char *cache_control,
1305 const size_t cc_len TSRMLS_DC)
1306 {
1307 STATUS status;
1308 char *cc_header = ecalloc(sizeof("Cache-Control: ") + cc_len, 1);
1309
1310 sprintf(cc_header, "Cache-Control: %s", cache_control);
1311 if (SUCCESS != (status = http_send_header(cc_header))) {
1312 php_error_docref(NULL TSRMLS_CC, E_NOTICE,
1313 "Could not send '%s' header", cc_header);
1314 }
1315 efree(cc_header);
1316 return status;
1317 }
1318 /* }}} */
1319
1320 /* {{{ STATUS http_send_content_type(char *, size_t) */
1321 PHP_HTTP_API STATUS _http_send_content_type(const char *content_type,
1322 const size_t ct_len TSRMLS_DC)
1323 {
1324 STATUS status;
1325 char *ct_header;
1326
1327 if (!strchr(content_type, '/')) {
1328 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1329 "Content-Type '%s' doesn't seem to consist of a primary and a secondary part",
1330 content_type);
1331 return FAILURE;
1332 }
1333
1334 /* remember for multiple ranges */
1335 if (HTTP_G(ctype)) {
1336 efree(HTTP_G(ctype));
1337 }
1338 HTTP_G(ctype) = estrndup(content_type, ct_len);
1339
1340 ct_header = ecalloc(sizeof("Content-Type: ") + ct_len, 1);
1341 sprintf(ct_header, "Content-Type: %s", content_type);
1342
1343 if (SUCCESS != (status = http_send_header(ct_header))) {
1344 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1345 "Couldn't send '%s' header", ct_header);
1346 }
1347 efree(ct_header);
1348 return status;
1349 }
1350 /* }}} */
1351
1352 /* {{{ STATUS http_send_content_disposition(char *, size_t, zend_bool) */
1353 PHP_HTTP_API STATUS _http_send_content_disposition(const char *filename,
1354 const size_t f_len, const int send_inline TSRMLS_DC)
1355 {
1356 STATUS status;
1357 char *cd_header;
1358
1359 if (send_inline) {
1360 cd_header = ecalloc(sizeof("Content-Disposition: inline; filename=\"\"") + f_len, 1);
1361 sprintf(cd_header, "Content-Disposition: inline; filename=\"%s\"", filename);
1362 } else {
1363 cd_header = ecalloc(sizeof("Content-Disposition: attachment; filename=\"\"") + f_len, 1);
1364 sprintf(cd_header, "Content-Disposition: attachment; filename=\"%s\"", filename);
1365 }
1366
1367 if (SUCCESS != (status = http_send_header(cd_header))) {
1368 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't send '%s' header", cd_header);
1369 }
1370 efree(cd_header);
1371 return status;
1372 }
1373 /* }}} */
1374
1375 /* {{{ STATUS http_cache_last_modified(time_t, time_t, char *, size_t) */
1376 PHP_HTTP_API STATUS _http_cache_last_modified(const time_t last_modified,
1377 const time_t send_modified, const char *cache_control, const size_t cc_len TSRMLS_DC)
1378 {
1379 if (cc_len) {
1380 http_send_cache_control(cache_control, cc_len);
1381 }
1382
1383 if (http_modified_match("HTTP_IF_MODIFIED_SINCE", last_modified)) {
1384 if (SUCCESS == http_send_status(304)) {
1385 zend_bailout();
1386 } else {
1387 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified");
1388 return FAILURE;
1389 }
1390 }
1391 return http_send_last_modified(send_modified);
1392 }
1393 /* }}} */
1394
1395 /* {{{ STATUS http_cache_etag(char *, size_t, char *, size_t) */
1396 PHP_HTTP_API STATUS _http_cache_etag(const char *etag, const size_t etag_len,
1397 const char *cache_control, const size_t cc_len TSRMLS_DC)
1398 {
1399 if (cc_len) {
1400 http_send_cache_control(cache_control, cc_len);
1401 }
1402
1403 if (etag_len) {
1404 http_send_etag(etag, etag_len);
1405 if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1406 if (SUCCESS == http_send_status(304)) {
1407 zend_bailout();
1408 } else {
1409 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified");
1410 return FAILURE;
1411 }
1412 }
1413 }
1414
1415 /* if no etag is given and we didn't already start ob_etaghandler -- start it */
1416 if (!HTTP_G(etag_started)) {
1417 if (SUCCESS == http_start_ob_handler(_http_ob_etaghandler, "ob_etaghandler", 4096, 1)) {
1418 HTTP_G(etag_started) = 1;
1419 return SUCCESS;
1420 } else {
1421 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not start ob_etaghandler");
1422 return FAILURE;
1423 }
1424 }
1425 return SUCCESS;
1426 }
1427 /* }}} */
1428
1429 /* {{{ char *http_absolute_uri(char *, char *) */
1430 PHP_HTTP_API char *_http_absolute_uri(const char *url,
1431 const char *proto TSRMLS_DC)
1432 {
1433 char URI[HTTP_URI_MAXLEN + 1], *PTR, *proto_ptr, *host, *path;
1434 zval *zhost;
1435
1436 if (!url || !strlen(url)) {
1437 if (!SG(request_info).request_uri) {
1438 return NULL;
1439 }
1440 url = SG(request_info).request_uri;
1441 }
1442 /* Mess around with already absolute URIs */
1443 else if (proto_ptr = strstr(url, "://")) {
1444 if (!proto || !strncmp(url, proto, strlen(proto))) {
1445 return estrdup(url);
1446 } else {
1447 snprintf(URI, HTTP_URI_MAXLEN, "%s%s", proto, proto_ptr + 3);
1448 return estrdup(URI);
1449 }
1450 }
1451
1452 /* protocol defaults to http */
1453 if (!proto || !strlen(proto)) {
1454 proto = "http";
1455 }
1456
1457 /* get host name */
1458 if ( (zhost = http_get_server_var("HTTP_HOST")) ||
1459 (zhost = http_get_server_var("SERVER_NAME"))) {
1460 host = Z_STRVAL_P(zhost);
1461 } else {
1462 host = "localhost";
1463 }
1464
1465
1466 /* glue together */
1467 if (url[0] == '/') {
1468 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s%s", proto, host, url);
1469 } else if (SG(request_info).request_uri) {
1470 path = estrdup(SG(request_info).request_uri);
1471 php_dirname(path, strlen(path));
1472 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s%s/%s", proto, host, path, url);
1473 efree(path);
1474 } else {
1475 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s/%s", proto, host, url);
1476 }
1477
1478 /* strip everything after a new line */
1479 PTR = URI;
1480 while (*PTR != 0) {
1481 if (*PTR == '\n' || *PTR == '\r') {
1482 *PTR = 0;
1483 break;
1484 }
1485 PTR++;
1486 }
1487
1488 return estrdup(URI);
1489 }
1490 /* }}} */
1491
1492 /* {{{ char *http_negotiate_q(char *, zval *, char *, hash_entry_type) */
1493 PHP_HTTP_API char *_http_negotiate_q(const char *entry, const zval *supported,
1494 const char *def TSRMLS_DC)
1495 {
1496 zval *zaccept, *zarray, *zdelim, **zentry, *zentries, **zsupp;
1497 char *q_ptr, *result;
1498 int i, c;
1499 double qual;
1500
1501 HTTP_GSC(zaccept, entry, estrdup(def));
1502
1503 MAKE_STD_ZVAL(zarray);
1504 array_init(zarray);
1505
1506 MAKE_STD_ZVAL(zdelim);
1507 ZVAL_STRING(zdelim, ",", 0);
1508 php_explode(zdelim, zaccept, zarray, -1);
1509 efree(zdelim);
1510
1511 MAKE_STD_ZVAL(zentries);
1512 array_init(zentries);
1513
1514 c = zend_hash_num_elements(Z_ARRVAL_P(zarray));
1515 for (i = 0; i < c; i++, zend_hash_move_forward(Z_ARRVAL_P(zarray))) {
1516
1517 if (SUCCESS != zend_hash_get_current_data(
1518 Z_ARRVAL_P(zarray), (void **) &zentry)) {
1519 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1520 "Cannot parse %s header: %s", entry, Z_STRVAL_P(zaccept));
1521 break;
1522 }
1523
1524 /* check for qualifier */
1525 if (NULL != (q_ptr = strrchr(Z_STRVAL_PP(zentry), ';'))) {
1526 qual = strtod(q_ptr + 3, NULL);
1527 } else {
1528 qual = 1000.0 - i;
1529 }
1530
1531 /* walk through the supported array */
1532 for ( zend_hash_internal_pointer_reset(Z_ARRVAL_P(supported));
1533 SUCCESS == zend_hash_get_current_data(
1534 Z_ARRVAL_P(supported), (void **) &zsupp);
1535 zend_hash_move_forward(Z_ARRVAL_P(supported))) {
1536 if (!strcasecmp(Z_STRVAL_PP(zsupp), Z_STRVAL_PP(zentry))) {
1537 add_assoc_double(zentries, Z_STRVAL_PP(zsupp), qual);
1538 break;
1539 }
1540 }
1541 }
1542
1543 zval_dtor(zarray);
1544 efree(zarray);
1545
1546 zend_hash_internal_pointer_reset(Z_ARRVAL_P(zentries));
1547
1548 if ( (SUCCESS != zend_hash_sort(Z_ARRVAL_P(zentries), zend_qsort,
1549 http_sort_q, 0 TSRMLS_CC)) ||
1550 (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key(
1551 Z_ARRVAL_P(zentries), &result, 0, 1))) {
1552 result = estrdup(def);
1553 }
1554
1555 zval_dtor(zentries);
1556 efree(zentries);
1557
1558 return result;
1559 }
1560 /* }}} */
1561
1562 /* {{{ http_range_status http_get_request_ranges(zval *zranges, size_t) */
1563 PHP_HTTP_API http_range_status _http_get_request_ranges(zval *zranges,
1564 const size_t length TSRMLS_DC)
1565 {
1566 zval *zrange;
1567 char *range, c;
1568 long begin = -1, end = -1, *ptr;
1569
1570 HTTP_GSC(zrange, "HTTP_RANGE", RANGE_NO);
1571 range = Z_STRVAL_P(zrange);
1572
1573 if (strncmp(range, "bytes=", strlen("bytes="))) {
1574 php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Range header misses bytes=");
1575 return RANGE_NO;
1576 }
1577
1578 ptr = &begin;
1579 range += strlen("bytes=");
1580
1581 do {
1582 switch (c = *(range++))
1583 {
1584 case '0':
1585 *ptr *= 10;
1586 break;
1587
1588 case '1': case '2': case '3':
1589 case '4': case '5': case '6':
1590 case '7': case '8': case '9':
1591 /*
1592 * If the value of the pointer is already set (non-negative)
1593 * then multiply its value by ten and add the current value,
1594 * else initialise the pointers value with the current value
1595 * --
1596 * This let us recognize empty fields when validating the
1597 * ranges, i.e. a "-10" for begin and "12345" for the end
1598 * was the following range request: "Range: bytes=0-12345";
1599 * While a "-1" for begin and "12345" for the end would
1600 * have been: "Range: bytes=-12345".
1601 */
1602 if (*ptr > 0) {
1603 *ptr *= 10;
1604 *ptr += c - '0';
1605 } else {
1606 *ptr = c - '0';
1607 }
1608 break;
1609
1610 case '-':
1611 ptr = &end;
1612 break;
1613
1614 case ' ':
1615 /* IE - ignore for now */
1616 break;
1617
1618 case 0:
1619 case ',':
1620
1621 if (length) {
1622 /* validate ranges */
1623 switch (begin)
1624 {
1625 /* "0-12345" */
1626 case -10:
1627 if ((length - end) < 1) {
1628 return RANGE_ERR;
1629 }
1630 begin = 0;
1631 break;
1632
1633 /* "-12345" */
1634 case -1:
1635 if ((length - end) < 1) {
1636 return RANGE_ERR;
1637 }
1638 begin = length - end;
1639 end = length;
1640 break;
1641
1642 /* "12345-(xxx)" */
1643 default:
1644 switch (end)
1645 {
1646 /* "12345-" */
1647 case -1:
1648 if ((length - begin) < 1) {
1649 return RANGE_ERR;
1650 }
1651 end = length - 1;
1652 break;
1653
1654 /* "12345-67890" */
1655 default:
1656 if ( ((length - begin) < 1) ||
1657 ((length - end) < 1) ||
1658 ((begin - end) >= 0)) {
1659 return RANGE_ERR;
1660 }
1661 break;
1662 }
1663 break;
1664 }
1665 }
1666 {
1667 zval *zentry;
1668 MAKE_STD_ZVAL(zentry);
1669 array_init(zentry);
1670 add_index_long(zentry, 0, begin);
1671 add_index_long(zentry, 1, end);
1672 add_next_index_zval(zranges, zentry);
1673
1674 begin = -1;
1675 end = -1;
1676 ptr = &begin;
1677 }
1678 break;
1679
1680 default:
1681 return RANGE_NO;
1682 break;
1683 }
1684 } while (c != 0);
1685
1686 return RANGE_OK;
1687 }
1688 /* }}} */
1689
1690 /* {{{ STATUS http_send_ranges(zval *, void *, size_t, http_send_mode) */
1691 PHP_HTTP_API STATUS _http_send_ranges(zval *zranges, const void *data, const size_t size, const http_send_mode mode TSRMLS_DC)
1692 {
1693 int c;
1694 long **begin, **end;
1695 zval **zrange;
1696
1697 /* Send HTTP 206 Partial Content */
1698 http_send_status(206);
1699
1700 /* single range */
1701 if ((c = zend_hash_num_elements(Z_ARRVAL_P(zranges))) == 1) {
1702 char range_header[256] = {0};
1703
1704 zend_hash_index_find(Z_ARRVAL_P(zranges), 0, (void **) &zrange);
1705 zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin);
1706 zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end);
1707
1708 /* send content range header */
1709 snprintf(range_header, 255, "Content-Range: bytes %d-%d/%d", **begin, **end, size);
1710 http_send_header(range_header);
1711
1712 /* send requested chunk */
1713 return http_send_chunk(data, **begin, **end + 1, mode);
1714 }
1715
1716 /* multi range */
1717 else {
1718 int i;
1719 char bound[23] = {0}, preface[1024] = {0},
1720 multi_header[68] = "Content-Type: multipart/byteranges; boundary=";
1721
1722 snprintf(bound, 22, "--%d%0.9f", time(NULL), php_combined_lcg(TSRMLS_C));
1723 strncat(multi_header, bound + 2, 21);
1724 http_send_header(multi_header);
1725
1726 /* send each requested chunk */
1727 for ( i = 0, zend_hash_internal_pointer_reset(Z_ARRVAL_P(zranges));
1728 i < c;
1729 i++, zend_hash_move_forward(Z_ARRVAL_P(zranges))) {
1730 if ( HASH_KEY_NON_EXISTANT == zend_hash_get_current_data(
1731 Z_ARRVAL_P(zranges), (void **) &zrange) ||
1732 SUCCESS != zend_hash_index_find(
1733 Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
1734 SUCCESS != zend_hash_index_find(
1735 Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
1736 break;
1737 }
1738
1739 snprintf(preface, 1023,
1740 HTTP_CRLF "%s"
1741 HTTP_CRLF "Content-Type: %s"
1742 HTTP_CRLF "Content-Range: bytes %ld-%ld/%ld"
1743 HTTP_CRLF
1744 HTTP_CRLF,
1745
1746 bound,
1747 HTTP_G(ctype) ? HTTP_G(ctype) : "application/x-octetstream",
1748 **begin,
1749 **end,
1750 size
1751 );
1752
1753 php_body_write(preface, strlen(preface) TSRMLS_CC);
1754 http_send_chunk(data, **begin, **end + 1, mode);
1755 }
1756
1757 /* write boundary once more */
1758 php_body_write(HTTP_CRLF, 2 TSRMLS_CC);
1759 php_body_write(bound, strlen(bound) TSRMLS_CC);
1760
1761 return SUCCESS;
1762 }
1763 }
1764 /* }}} */
1765
1766 /* {{{ STATUS http_send(void *, sizezo_t, http_send_mode) */
1767 PHP_HTTP_API STATUS _http_send(const void *data_ptr, const size_t data_size,
1768 const http_send_mode data_mode TSRMLS_DC)
1769 {
1770 int is_range_request = http_is_range_request();
1771
1772 if (!data_ptr) {
1773 return FAILURE;
1774 }
1775
1776 /* etag handling */
1777 if (HTTP_G(etag_started)) {
1778 char *etag;
1779 /* interrupt */
1780 HTTP_G(etag_started) = 0;
1781 /* never ever use the output to compute the ETag if http_send() is used */
1782 php_end_ob_buffer(0, 0 TSRMLS_CC);
1783 if (!(etag = http_etag(data_ptr, data_size, data_mode))) {
1784 return FAILURE;
1785 }
1786
1787 /* send 304 Not Modified if etag matches */
1788 if ((!is_range_request) && http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1789 efree(etag);
1790 return http_send_status(304);
1791 }
1792
1793 http_send_etag(etag, 32);
1794 efree(etag);
1795 }
1796
1797 /* send 304 Not Modified if last-modified matches*/
1798 if ((!is_range_request) && http_modified_match("HTTP_IF_MODIFIED_SINCE", HTTP_G(lmod))) {
1799 return http_send_status(304);
1800 }
1801
1802 if (is_range_request) {
1803
1804 /* only send ranges if entity hasn't changed */
1805 if (
1806 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_MATCH", 13)) ||
1807 http_etag_match("HTTP_IF_MATCH", HTTP_G(etag)))
1808 &&
1809 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_UNMODIFIED_SINCE", 25)) ||
1810 http_modified_match("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(lmod)))
1811 ) {
1812
1813 STATUS result = FAILURE;
1814 zval *zranges = NULL;
1815 MAKE_STD_ZVAL(zranges);
1816 array_init(zranges);
1817
1818 switch (http_get_request_ranges(zranges, data_size))
1819 {
1820 case RANGE_NO:
1821 zval_dtor(zranges);
1822 efree(zranges);
1823 /* go ahead and send all */
1824 break;
1825
1826 case RANGE_OK:
1827 result = http_send_ranges(zranges, data_ptr, data_size, data_mode);
1828 zval_dtor(zranges);
1829 efree(zranges);
1830 return result;
1831 break;
1832
1833 case RANGE_ERR:
1834 zval_dtor(zranges);
1835 efree(zranges);
1836 http_send_status(416);
1837 return FAILURE;
1838 break;
1839
1840 default:
1841 return FAILURE;
1842 break;
1843 }
1844 }
1845 }
1846 /* send all */
1847 return http_send_chunk(data_ptr, 0, data_size, data_mode);
1848 }
1849 /* }}} */
1850
1851 /* {{{ STATUS http_send_data(zval *) */
1852 PHP_HTTP_API STATUS _http_send_data(const zval *zdata TSRMLS_DC)
1853 {
1854 if (!Z_STRLEN_P(zdata)) {
1855 return SUCCESS;
1856 }
1857 if (!Z_STRVAL_P(zdata)) {
1858 return FAILURE;
1859 }
1860
1861 return http_send(Z_STRVAL_P(zdata), Z_STRLEN_P(zdata), SEND_DATA);
1862 }
1863 /* }}} */
1864
1865 /* {{{ STATUS http_send_stream(php_stream *) */
1866 PHP_HTTP_API STATUS _http_send_stream(const php_stream *file TSRMLS_DC)
1867 {
1868 if (php_stream_stat((php_stream *) file, &HTTP_G(ssb))) {
1869 return FAILURE;
1870 }
1871
1872 return http_send(file, HTTP_G(ssb).sb.st_size, SEND_RSRC);
1873 }
1874 /* }}} */
1875
1876 /* {{{ STATUS http_send_file(zval *) */
1877 PHP_HTTP_API STATUS _http_send_file(const zval *zfile TSRMLS_DC)
1878 {
1879 php_stream *file;
1880 STATUS ret;
1881
1882 if (!Z_STRLEN_P(zfile)) {
1883 return FAILURE;
1884 }
1885
1886 if (!(file = php_stream_open_wrapper(Z_STRVAL_P(zfile), "rb",
1887 REPORT_ERRORS|ENFORCE_SAFE_MODE, NULL))) {
1888 return FAILURE;
1889 }
1890
1891 ret = http_send_stream(file);
1892 php_stream_close(file);
1893 return ret;
1894 }
1895 /* }}} */
1896
1897 /* {{{ proto STATUS http_chunked_decode(char *, size_t, char **, size_t *) */
1898 PHP_HTTP_API STATUS _http_chunked_decode(const char *encoded,
1899 const size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
1900 {
1901 const char *e_ptr;
1902 char *d_ptr;
1903
1904 *decoded_len = 0;
1905 *decoded = (char *) ecalloc(encoded_len, 1);
1906 d_ptr = *decoded;
1907 e_ptr = encoded;
1908
1909 while (((e_ptr - encoded) - encoded_len) > 0) {
1910 char hex_len[9] = {0};
1911 size_t chunk_len = 0;
1912 int i = 0;
1913
1914 /* read in chunk size */
1915 while (isxdigit(*e_ptr)) {
1916 if (i == 9) {
1917 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1918 "Chunk size is too long: 0x%s...", hex_len);
1919 efree(*decoded);
1920 return FAILURE;
1921 }
1922 hex_len[i++] = *e_ptr++;
1923 }
1924
1925 /* reached the end */
1926 if (!strcmp(hex_len, "0")) {
1927 break;
1928 }
1929
1930 /* new line */
1931 if (strncmp(e_ptr, HTTP_CRLF, 2)) {
1932 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1933 "Invalid character (expected 0x0D 0x0A; got: %x %x)",
1934 *e_ptr, *(e_ptr + 1));
1935 efree(*decoded);
1936 return FAILURE;
1937 }
1938
1939 /* hex to long */
1940 {
1941 char *error = NULL;
1942 chunk_len = strtol(hex_len, &error, 16);
1943 if (error == hex_len) {
1944 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1945 "Invalid chunk size string: '%s'", hex_len);
1946 efree(*decoded);
1947 return FAILURE;
1948 }
1949 }
1950
1951 memcpy(d_ptr, e_ptr += 2, chunk_len);
1952 d_ptr += chunk_len;
1953 e_ptr += chunk_len + 2;
1954 *decoded_len += chunk_len;
1955 }
1956
1957 return SUCCESS;
1958 }
1959 /* }}} */
1960
1961 /* {{{ proto STATUS http_split_response_ex(char *, size_t, zval *, zval *) */
1962 PHP_HTTP_API STATUS _http_split_response_ex( char *response,
1963 size_t response_len, zval *zheaders, zval *zbody TSRMLS_DC)
1964 {
1965 char *body = NULL;
1966 char *header = response;
1967
1968 while ((response - header + 4) < response_len) {
1969 if ( (*response++ == '\r') &&
1970 (*response++ == '\n') &&
1971 (*response++ == '\r') &&
1972 (*response++ == '\n')) {
1973 body = response;
1974 break;
1975 }
1976 }
1977
1978 if (body && (response_len - (body - header))) {
1979 ZVAL_STRINGL(zbody, body, response_len - (body - header) - 1, 1);
1980 } else {
1981 Z_TYPE_P(zbody) = IS_NULL;
1982 }
1983
1984 return http_parse_headers(header, body ? body - header : response_len, zheaders);
1985 }
1986 /* }}} */
1987
1988 /* {{{ STATUS http_parse_headers(char *, long, zval *) */
1989 PHP_HTTP_API STATUS _http_parse_headers(char *header, int header_len, zval *array TSRMLS_DC)
1990 {
1991 char *colon = NULL, *line = NULL, *begin = header;
1992
1993 if (header_len < 2) {
1994 return FAILURE;
1995 }
1996
1997 /* status code */
1998 if (!strncmp(header, "HTTP/1.", 7)) {
1999 char *end = strstr(header, HTTP_CRLF);
2000 add_assoc_stringl(array, "Status",
2001 header + strlen("HTTP/1.x "),
2002 end - (header + strlen("HTTP/1.x ")), 1);
2003 header = end + 2;
2004 }
2005
2006 line = header;
2007
2008 while (header_len >= (line - begin)) {
2009 int value_len = 0;
2010
2011 switch (*line++)
2012 {
2013 case 0:
2014 --value_len; /* we don't have CR so value length is one char less */
2015 case '\n':
2016 if (colon && ((!(*line - 1)) || ((*line != ' ') && (*line != '\t')))) {
2017
2018 /* skip empty key */
2019 if (header != colon) {
2020 char *key = estrndup(header, colon - header);
2021 value_len += line - colon - 1;
2022
2023 /* skip leading ws */
2024 while (isspace(*(++colon))) --value_len;
2025 /* skip trailing ws */
2026 while (isspace(colon[value_len - 1])) --value_len;
2027
2028 if (value_len < 1) {
2029 /* hm, empty header? */
2030 add_assoc_stringl(array, key, "", 0, 1);
2031 } else {
2032 add_assoc_stringl(array, key, colon, value_len, 1);
2033 }
2034 efree(key);
2035 }
2036
2037 colon = NULL;
2038 value_len = 0;
2039 header += line - header;
2040 }
2041 break;
2042
2043 case ':':
2044 if (!colon) {
2045 colon = line - 1;
2046 }
2047 break;
2048 }
2049 }
2050 return SUCCESS;
2051 }
2052 /* }}} */
2053
2054 /* {{{ void http_get_request_headers(zval *) */
2055 PHP_HTTP_API void _http_get_request_headers(zval *array TSRMLS_DC)
2056 {
2057 char *key;
2058
2059 for ( zend_hash_internal_pointer_reset(HTTP_SERVER_VARS);
2060 zend_hash_get_current_key(HTTP_SERVER_VARS, &key, NULL, 0) != HASH_KEY_NON_EXISTANT;
2061 zend_hash_move_forward(HTTP_SERVER_VARS)) {
2062 if (!strncmp(key, "HTTP_", 5)) {
2063 zval **header;
2064 zend_hash_get_current_data(HTTP_SERVER_VARS, (void **) &header);
2065 add_assoc_stringl(array, pretty_key(key + 5, strlen(key) - 5, 1, 1), Z_STRVAL_PP(header), Z_STRLEN_PP(header), 1);
2066 }
2067 }
2068 }
2069 /* }}} */
2070
2071 /* {{{ HAVE_CURL */
2072 #ifdef HTTP_HAVE_CURL
2073
2074 /* {{{ STATUS http_get(char *, HashTable *, HashTable *, char **, size_t *) */
2075 PHP_HTTP_API STATUS _http_get(const char *URL, HashTable *options,
2076 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2077 {
2078 STATUS rs;
2079 CURL *ch = curl_easy_init();
2080
2081 if (!ch) {
2082 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2083 return FAILURE;
2084 }
2085
2086 rs = http_get_ex(ch, URL, options, info, data, data_len);
2087 curl_easy_cleanup(ch);
2088 return rs;
2089 }
2090 /* }}} */
2091
2092 /* {{{ STATUS http_get_ex(CURL *, char *, HashTable *, HashTable *, char **, size_t *) */
2093 PHP_HTTP_API STATUS _http_get_ex(CURL *ch, const char *URL, HashTable *options,
2094 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2095 {
2096 http_curl_initbuf(CURLBUF_EVRY);
2097 http_curl_setopts(ch, URL, options);
2098 curl_easy_setopt(ch, CURLOPT_NOBODY, 0);
2099 curl_easy_setopt(ch, CURLOPT_POST, 0);
2100
2101 if (CURLE_OK != curl_easy_perform(ch)) {
2102 http_curl_freebuf(CURLBUF_EVRY);
2103 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2104 return FAILURE;
2105 }
2106 if (info) {
2107 http_curl_getinfo(ch, info);
2108 }
2109 http_curl_movebuf(CURLBUF_EVRY, data, data_len);
2110 return SUCCESS;
2111 }
2112
2113 /* {{{ STATUS http_head(char *, HashTable *, HashTable *, char **data, size_t *) */
2114 PHP_HTTP_API STATUS _http_head(const char *URL, HashTable *options,
2115 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2116 {
2117 STATUS rs;
2118 CURL *ch = curl_easy_init();
2119
2120 if (!ch) {
2121 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2122 return FAILURE;
2123 }
2124
2125 rs = http_head_ex(ch, URL, options, info, data, data_len);
2126 curl_easy_cleanup(ch);
2127 return rs;
2128 }
2129 /* }}} */
2130
2131 /* {{{ STATUS http_head_ex(CURL *, char *, HashTable *, HashTable *, char **data, size_t *) */
2132 PHP_HTTP_API STATUS _http_head_ex(CURL *ch, const char *URL, HashTable *options,
2133 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2134 {
2135 http_curl_initbuf(CURLBUF_HDRS);
2136 http_curl_setopts(ch, URL, options);
2137 curl_easy_setopt(ch, CURLOPT_NOBODY, 1);
2138 curl_easy_setopt(ch, CURLOPT_POST, 0);
2139
2140 if (CURLE_OK != curl_easy_perform(ch)) {
2141 http_curl_freebuf(CURLBUF_HDRS);
2142 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2143 return FAILURE;
2144 }
2145 if (info) {
2146 http_curl_getinfo(ch, info);
2147 }
2148 http_curl_movebuf(CURLBUF_HDRS, data, data_len);
2149 return SUCCESS;
2150 }
2151
2152 /* {{{ STATUS http_post_data(char *, char *, size_t, HashTable *, HashTable *, char **, size_t *) */
2153 PHP_HTTP_API STATUS _http_post_data(const char *URL, char *postdata,
2154 size_t postdata_len, HashTable *options, HashTable *info, char **data,
2155 size_t *data_len TSRMLS_DC)
2156 {
2157 STATUS rs;
2158 CURL *ch = curl_easy_init();
2159
2160 if (!ch) {
2161 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2162 return FAILURE;
2163 }
2164 rs = http_post_data_ex(ch, URL, postdata, postdata_len, options, info, data, data_len);
2165 curl_easy_cleanup(ch);
2166 return rs;
2167 }
2168 /* }}} */
2169
2170 /* {{{ STATUS http_post_data_ex(CURL *, char *, char *, size_t, HashTable *, HashTable *, char **, size_t *) */
2171 PHP_HTTP_API STATUS _http_post_data_ex(CURL *ch, const char *URL, char *postdata,
2172 size_t postdata_len, HashTable *options, HashTable *info, char **data,
2173 size_t *data_len TSRMLS_DC)
2174 {
2175 http_curl_initbuf(CURLBUF_EVRY);
2176 http_curl_setopts(ch, URL, options);
2177 curl_easy_setopt(ch, CURLOPT_POST, 1);
2178 curl_easy_setopt(ch, CURLOPT_POSTFIELDS, postdata);
2179 curl_easy_setopt(ch, CURLOPT_POSTFIELDSIZE, postdata_len);
2180
2181 if (CURLE_OK != curl_easy_perform(ch)) {
2182 http_curl_freebuf(CURLBUF_EVRY);
2183 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2184 return FAILURE;
2185 }
2186 if (info) {
2187 http_curl_getinfo(ch, info);
2188 }
2189 http_curl_movebuf(CURLBUF_EVRY, data, data_len);
2190 return SUCCESS;
2191 }
2192 /* }}} */
2193
2194 /* {{{ STATUS http_post_array(char *, HashTable *, HashTable *, HashTable *, char **, size_t *) */
2195 PHP_HTTP_API STATUS _http_post_array(const char *URL, HashTable *postarray,
2196 HashTable *options, HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2197 {
2198 smart_str qstr = {0};
2199 STATUS status;
2200
2201 if (php_url_encode_hash_ex(postarray, &qstr, NULL,0,NULL,0,NULL,0,NULL TSRMLS_CC) != SUCCESS) {
2202 if (qstr.c) {
2203 efree(qstr.c);
2204 }
2205 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not encode post data");
2206 return FAILURE;
2207 }
2208 smart_str_0(&qstr);
2209
2210 status = http_post_data(URL, qstr.c, qstr.len, options, info, data, data_len);
2211 if (qstr.c) {
2212 efree(qstr.c);
2213 }
2214 return status;
2215 }
2216 /* }}} */
2217
2218 #endif
2219 /* }}} HAVE_CURL */
2220
2221 /* {{{ STATUS http_auth_header(char *, char*) */
2222 PHP_HTTP_API STATUS _http_auth_header(const char *type, const char *realm TSRMLS_DC)
2223 {
2224 char realm_header[1024] = {0};
2225 snprintf(realm_header, 1023, "WWW-Authenticate: %s realm=\"%s\"", type, realm);
2226 return http_send_status_header(401, realm_header);
2227 }
2228 /* }}} */
2229
2230 /* {{{ STATUS http_auth_credentials(char **, char **) */
2231 PHP_HTTP_API STATUS _http_auth_credentials(char **user, char **pass TSRMLS_DC)
2232 {
2233 if (strncmp(sapi_module.name, "isapi", 5)) {
2234 zval *zuser, *zpass;
2235
2236 HTTP_GSC(zuser, "PHP_AUTH_USER", FAILURE);
2237 HTTP_GSC(zpass, "PHP_AUTH_PW", FAILURE);
2238
2239 *user = estrndup(Z_STRVAL_P(zuser), Z_STRLEN_P(zuser));
2240 *pass = estrndup(Z_STRVAL_P(zpass), Z_STRLEN_P(zpass));
2241
2242 return SUCCESS;
2243 } else {
2244 zval *zauth = NULL;
2245 HTTP_GSC(zauth, "HTTP_AUTHORIZATION", FAILURE);
2246 {
2247 char *decoded, *colon;
2248 int decoded_len;
2249 decoded = php_base64_decode(Z_STRVAL_P(zauth), Z_STRLEN_P(zauth),
2250 &decoded_len);
2251
2252 if (colon = strchr(decoded + 6, ':')) {
2253 *user = estrndup(decoded + 6, colon - decoded - 6);
2254 *pass = estrndup(colon + 1, decoded + decoded_len - colon - 6 - 1);
2255
2256 return SUCCESS;
2257 } else {
2258 return FAILURE;
2259 }
2260 }
2261 }
2262 }
2263 /* }}} */
2264
2265 /* }}} public API */
2266
2267 /*
2268 * Local variables:
2269 * tab-width: 4
2270 * c-basic-offset: 4
2271 * End:
2272 * vim600: noet sw=4 ts=4 fdm=marker
2273 * vim<600: noet sw=4 ts=4
2274 */