bb83e28bde4a77c70af6d601c39966ff8a3ab48e
[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 #if defined(ZTS) && (LIBCURL_VERSION_NUM >= 0x070a00)
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 /* FIXXXME: mem-leak */
537 }
538 }
539
540 /* cookiestore */
541 if (zoption = http_curl_getopt1(options, "cookiestore", IS_STRING)) {
542 curl_easy_setopt(ch, CURLOPT_COOKIEFILE, Z_STRVAL_P(zoption));
543 curl_easy_setopt(ch, CURLOPT_COOKIEJAR, Z_STRVAL_P(zoption));
544 }
545
546 /* additional headers, array('name' => 'value') */
547 if (zoption = http_curl_getopt1(options, "headers", IS_ARRAY)) {
548 int key_type;
549 char *header_key, header[1024] = {0};
550 zval **header_val;
551 struct curl_slist *headers = NULL;
552
553 zend_hash_internal_pointer_reset(Z_ARRVAL_P(zoption));
554 while (HASH_KEY_NON_EXISTANT != (key_type = zend_hash_get_current_key_type(Z_ARRVAL_P(zoption)))) {
555 if (key_type == HASH_KEY_IS_STRING) {
556 zend_hash_get_current_key(Z_ARRVAL_P(zoption), &header_key, NULL, 0);
557 zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void **) &header_val);
558 snprintf(header, 1023, "%s: %s", header_key, Z_STRVAL_PP(header_val));
559 headers = curl_slist_append(headers, header);
560 zend_hash_move_forward(Z_ARRVAL_P(zoption));
561 }
562 }
563 if (headers) {
564 curl_easy_setopt(ch, CURLOPT_HTTPHEADER, headers);
565 }
566 }
567 }
568 /* }}} */
569
570 /* {{{ static inline char *http_curl_getinfoname(CURLINFO) */
571 static inline char *_http_curl_getinfoname(CURLINFO i TSRMLS_DC)
572 {
573 #define CASE(I) case CURLINFO_ ##I : { static char I[] = #I; return pretty_key(I, sizeof(#I)-1, 0, 0); }
574 switch (i)
575 {
576 /* CURLINFO_EFFECTIVE_URL = CURLINFO_STRING +1, */
577 CASE(EFFECTIVE_URL);
578 /* CURLINFO_RESPONSE_CODE = CURLINFO_LONG +2, */
579 #if LIBCURL_VERSION_NUM > 0x070a06
580 CASE(RESPONSE_CODE);
581 #else
582 CASE(HTTP_CODE);
583 #endif
584 /* CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE +3, */
585 CASE(TOTAL_TIME);
586 /* CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE +4, */
587 CASE(NAMELOOKUP_TIME);
588 /* CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE +5, */
589 CASE(CONNECT_TIME);
590 /* CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE +6, */
591 CASE(PRETRANSFER_TIME);
592 /* CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE +7, */
593 CASE(SIZE_UPLOAD);
594 /* CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE +8, */
595 CASE(SIZE_DOWNLOAD);
596 /* CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE +9, */
597 CASE(SPEED_DOWNLOAD);
598 /* CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE +10, */
599 CASE(SPEED_UPLOAD);
600 /* CURLINFO_HEADER_SIZE = CURLINFO_LONG +11, */
601 CASE(HEADER_SIZE);
602 /* CURLINFO_REQUEST_SIZE = CURLINFO_LONG +12, */
603 CASE(REQUEST_SIZE);
604 /* CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG +13, */
605 CASE(SSL_VERIFYRESULT);
606 /* CURLINFO_FILETIME = CURLINFO_LONG +14, */
607 CASE(FILETIME);
608 /* CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE +15, */
609 CASE(CONTENT_LENGTH_DOWNLOAD);
610 /* CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE +16, */
611 CASE(CONTENT_LENGTH_UPLOAD);
612 /* CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE +17, */
613 CASE(STARTTRANSFER_TIME);
614 /* CURLINFO_CONTENT_TYPE = CURLINFO_STRING +18, */
615 CASE(CONTENT_TYPE);
616 /* CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE +19, */
617 CASE(REDIRECT_TIME);
618 /* CURLINFO_REDIRECT_COUNT = CURLINFO_LONG +20, */
619 CASE(REDIRECT_COUNT);
620 /* CURLINFO_PRIVATE = CURLINFO_STRING +21, * (mike) /
621 CASE(PRIVATE);
622 /* CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG +22, */
623 CASE(HTTP_CONNECTCODE);
624 #if LIBCURL_VERSION_NUM > 0x070a07
625 /* CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG +23, */
626 CASE(HTTPAUTH_AVAIL);
627 /* CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG +24, */
628 CASE(PROXYAUTH_AVAIL);
629 #endif
630 }
631 #undef CASE
632 return NULL;
633 }
634 /* }}} */
635
636 /* {{{ static inline void http_curl_getinfo_ex(CURL, CURLINFO, zval *) */
637 static inline void _http_curl_getinfo_ex(CURL *ch, CURLINFO i, zval *array TSRMLS_DC)
638 {
639 char *key;
640 if (key = http_curl_getinfoname(i)) {
641 switch (i & ~CURLINFO_MASK)
642 {
643 case CURLINFO_STRING:
644 {
645 char *c;
646 if (CURLE_OK == curl_easy_getinfo(ch, i, &c)) {
647 add_assoc_string(array, key, c ? c : "", 1);
648 }
649 }
650 break;
651
652 case CURLINFO_DOUBLE:
653 {
654 double d;
655 if (CURLE_OK == curl_easy_getinfo(ch, i, &d)) {
656 add_assoc_double(array, key, d);
657 }
658 }
659 break;
660
661 case CURLINFO_LONG:
662 {
663 long l;
664 if (CURLE_OK == curl_easy_getinfo(ch, i, &l)) {
665 add_assoc_long(array, key, l);
666 }
667 }
668 break;
669 }
670 }
671 }
672 /* }}} */
673
674 /* {{{ static inline http_curl_getinfo(CURL, HashTable *) */
675 static inline void _http_curl_getinfo(CURL *ch, HashTable *info TSRMLS_DC)
676 {
677 zval array;
678 Z_ARRVAL(array) = info;
679
680 #define INFO(I) http_curl_getinfo_ex(ch, CURLINFO_ ##I , &array)
681 /* CURLINFO_EFFECTIVE_URL = CURLINFO_STRING +1, */
682 INFO(EFFECTIVE_URL);
683 #if LIBCURL_VERSION_NUM > 0x070a06
684 /* CURLINFO_RESPONSE_CODE = CURLINFO_LONG +2, */
685 INFO(RESPONSE_CODE);
686 #else
687 INFO(HTTP_CODE);
688 #endif
689 /* CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE +3, */
690 INFO(TOTAL_TIME);
691 /* CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE +4, */
692 INFO(NAMELOOKUP_TIME);
693 /* CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE +5, */
694 INFO(CONNECT_TIME);
695 /* CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE +6, */
696 INFO(PRETRANSFER_TIME);
697 /* CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE +7, */
698 INFO(SIZE_UPLOAD);
699 /* CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE +8, */
700 INFO(SIZE_DOWNLOAD);
701 /* CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE +9, */
702 INFO(SPEED_DOWNLOAD);
703 /* CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE +10, */
704 INFO(SPEED_UPLOAD);
705 /* CURLINFO_HEADER_SIZE = CURLINFO_LONG +11, */
706 INFO(HEADER_SIZE);
707 /* CURLINFO_REQUEST_SIZE = CURLINFO_LONG +12, */
708 INFO(REQUEST_SIZE);
709 /* CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG +13, */
710 INFO(SSL_VERIFYRESULT);
711 /* CURLINFO_FILETIME = CURLINFO_LONG +14, */
712 INFO(FILETIME);
713 /* CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE +15, */
714 INFO(CONTENT_LENGTH_DOWNLOAD);
715 /* CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE +16, */
716 INFO(CONTENT_LENGTH_UPLOAD);
717 /* CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE +17, */
718 INFO(STARTTRANSFER_TIME);
719 /* CURLINFO_CONTENT_TYPE = CURLINFO_STRING +18, */
720 INFO(CONTENT_TYPE);
721 /* CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE +19, */
722 INFO(REDIRECT_TIME);
723 /* CURLINFO_REDIRECT_COUNT = CURLINFO_LONG +20, */
724 INFO(REDIRECT_COUNT);
725 /* CURLINFO_PRIVATE = CURLINFO_STRING +21, */
726 INFO(PRIVATE);
727 /* CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG +22, */
728 INFO(HTTP_CONNECTCODE);
729 #if LIBCURL_VERSION_NUM > 0x070a07
730 /* CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG +23, */
731 INFO(HTTPAUTH_AVAIL);
732 /* CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG +24, */
733 INFO(PROXYAUTH_AVAIL);
734 #endif
735 #undef INFO
736 }
737 /* }}} */
738
739 #endif
740 /* }}} HAVE_CURL */
741
742 /* {{{ Day/Month/TZ checks for http_parse_date()
743 Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
744 static int check_day(char *day, size_t len)
745 {
746 int i;
747 const char * const *check = (len > 3) ? &weekdays[0] : &wkdays[0];
748 for (i = 0; i < 7; i++) {
749 if (!strcmp(day, check[0])) {
750 return i;
751 }
752 check++;
753 }
754 return -1;
755 }
756
757 static int check_month(char *month)
758 {
759 int i;
760 const char * const *check = &months[0];
761 for (i = 0; i < 12; i++) {
762 if (!strcmp(month, check[0])) {
763 return i;
764 }
765 check++;
766 }
767 return -1;
768 }
769
770 /* return the time zone offset between GMT and the input one, in number
771 of seconds or -1 if the timezone wasn't found/legal */
772
773 static int check_tzone(char *tzone)
774 {
775 int i;
776 const struct time_zone *check = time_zones;
777 for (i = 0; i < sizeof(time_zones) / sizeof(time_zones[0]); i++) {
778 if (!strcmp(tzone, check->name)) {
779 return check->offset * 60;
780 }
781 check++;
782 }
783 return -1;
784 }
785 /* }}} */
786
787 /* static char *pretty_key(char *, int, int, int) */
788 static char *pretty_key(char *key, int key_len, int uctitle, int xhyphen)
789 {
790 if (key && key_len) {
791 int i, wasalpha;
792 if (wasalpha = isalpha(key[0])) {
793 key[0] = uctitle ? toupper(key[0]) : tolower(key[0]);
794 }
795 for (i = 1; i < key_len; i++) {
796 if (isalpha(key[i])) {
797 key[i] = ((!wasalpha) && uctitle) ? toupper(key[i]) : tolower(key[i]);
798 wasalpha = 1;
799 } else {
800 if (xhyphen && (key[i] == '_')) {
801 key[i] = '-';
802 }
803 wasalpha = 0;
804 }
805 }
806 }
807 return key;
808 }
809 /* }}} */
810
811 /* {{{ static STATUS http_ob_stack_get(php_ob_buffer *, php_ob_buffer **) */
812 static STATUS http_ob_stack_get(php_ob_buffer *o, php_ob_buffer **s)
813 {
814 static int i = 0;
815 php_ob_buffer *b = emalloc(sizeof(php_ob_buffer));
816 b->handler_name = estrdup(o->handler_name);
817 b->buffer = estrndup(o->buffer, o->text_length);
818 b->text_length = o->text_length;
819 b->chunk_size = o->chunk_size;
820 b->erase = o->erase;
821 s[i++] = b;
822 return SUCCESS;
823 }
824 /* }}} */
825
826 /* }}} internals */
827
828 /* {{{ public API */
829
830 /* {{{ char *http_date(time_t) */
831 PHP_HTTP_API char *_http_date(time_t t TSRMLS_DC)
832 {
833 struct tm *gmtime, tmbuf;
834 char *date = ecalloc(1, 31);
835
836 gmtime = php_gmtime_r(&t, &tmbuf);
837 snprintf(date, 30,
838 "%s, %02d %s %04d %02d:%02d:%02d GMT",
839 days[gmtime->tm_wday], gmtime->tm_mday,
840 months[gmtime->tm_mon], gmtime->tm_year + 1900,
841 gmtime->tm_hour, gmtime->tm_min, gmtime->tm_sec
842 );
843 return date;
844 }
845 /* }}} */
846
847 /* {{{ time_t http_parse_date(char *)
848 Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
849 PHP_HTTP_API time_t _http_parse_date(const char *date)
850 {
851 time_t t = 0;
852 int tz_offset = -1, year = -1, month = -1, monthday = -1, weekday = -1,
853 hours = -1, minutes = -1, seconds = -1;
854 struct tm tm;
855 enum assume_next dignext = DATE_MDAY;
856 const char *indate = date;
857
858 int found = 0, part = 0; /* max 6 parts */
859
860 while (*date && (part < 6)) {
861 int found = 0;
862
863 while (*date && !isalnum(*date)) {
864 date++;
865 }
866
867 if (isalpha(*date)) {
868 /* a name coming up */
869 char buf[32] = "";
870 size_t len;
871 sscanf(date, "%31[A-Za-z]", buf);
872 len = strlen(buf);
873
874 if (weekday == -1) {
875 weekday = check_day(buf, len);
876 if (weekday != -1) {
877 found = 1;
878 }
879 }
880
881 if (!found && (month == -1)) {
882 month = check_month(buf);
883 if (month != -1) {
884 found = 1;
885 }
886 }
887
888 if (!found && (tz_offset == -1)) {
889 /* this just must be a time zone string */
890 tz_offset = check_tzone(buf);
891 if (tz_offset != -1) {
892 found = 1;
893 }
894 }
895
896 if (!found) {
897 return -1; /* bad string */
898 }
899 date += len;
900 }
901 else if (isdigit(*date)) {
902 /* a digit */
903 int val;
904 char *end;
905 if((seconds == -1) && (3 == sscanf(date, "%02d:%02d:%02d", &hours, &minutes, &seconds))) {
906 /* time stamp! */
907 date += 8;
908 found = 1;
909 }
910 else {
911 val = (int) strtol(date, &end, 10);
912
913 if ((tz_offset == -1) && ((end - date) == 4) && (val < 1300) && (indate < date) && ((date[-1] == '+' || date[-1] == '-'))) {
914 /* four digits and a value less than 1300 and it is preceeded with
915 a plus or minus. This is a time zone indication. */
916 found = 1;
917 tz_offset = (val / 100 * 60 + val % 100) * 60;
918
919 /* the + and - prefix indicates the local time compared to GMT,
920 this we need ther reversed math to get what we want */
921 tz_offset = date[-1] == '+' ? -tz_offset : tz_offset;
922 }
923
924 if (((end - date) == 8) && (year == -1) && (month == -1) && (monthday == -1)) {
925 /* 8 digits, no year, month or day yet. This is YYYYMMDD */
926 found = 1;
927 year = val / 10000;
928 month = (val % 10000) / 100 - 1; /* month is 0 - 11 */
929 monthday = val % 100;
930 }
931
932 if (!found && (dignext == DATE_MDAY) && (monthday == -1)) {
933 if ((val > 0) && (val < 32)) {
934 monthday = val;
935 found = 1;
936 }
937 dignext = DATE_YEAR;
938 }
939
940 if (!found && (dignext == DATE_YEAR) && (year == -1)) {
941 year = val;
942 found = 1;
943 if (year < 1900) {
944 year += year > 70 ? 1900 : 2000;
945 }
946 if(monthday == -1) {
947 dignext = DATE_MDAY;
948 }
949 }
950
951 if (!found) {
952 return -1;
953 }
954
955 date = end;
956 }
957 }
958
959 part++;
960 }
961
962 if (-1 == seconds) {
963 seconds = minutes = hours = 0; /* no time, make it zero */
964 }
965
966 if ((-1 == monthday) || (-1 == month) || (-1 == year)) {
967 /* lacks vital info, fail */
968 return -1;
969 }
970
971 if (sizeof(time_t) < 5) {
972 /* 32 bit time_t can only hold dates to the beginning of 2038 */
973 if (year > 2037) {
974 return 0x7fffffff;
975 }
976 }
977
978 tm.tm_sec = seconds;
979 tm.tm_min = minutes;
980 tm.tm_hour = hours;
981 tm.tm_mday = monthday;
982 tm.tm_mon = month;
983 tm.tm_year = year - 1900;
984 tm.tm_wday = 0;
985 tm.tm_yday = 0;
986 tm.tm_isdst = 0;
987
988 t = mktime(&tm);
989
990 /* time zone adjust */
991 {
992 struct tm *gmt, keeptime2;
993 long delta;
994 time_t t2;
995
996 if(!(gmt = php_gmtime_r(&t, &keeptime2))) {
997 return -1; /* illegal date/time */
998 }
999
1000 t2 = mktime(gmt);
1001
1002 /* Add the time zone diff (between the given timezone and GMT) and the
1003 diff between the local time zone and GMT. */
1004 delta = (tz_offset != -1 ? tz_offset : 0) + (t - t2);
1005
1006 if((delta > 0) && (t + delta < t)) {
1007 return -1; /* time_t overflow */
1008 }
1009
1010 t += delta;
1011 }
1012
1013 return t;
1014 }
1015 /* }}} */
1016
1017 /* {{{ inline char *http_etag(void *, size_t, http_send_mode) */
1018 PHP_HTTP_API inline char *_http_etag(const void *data_ptr, const size_t data_len,
1019 const http_send_mode data_mode TSRMLS_DC)
1020 {
1021 char ssb_buf[128] = {0};
1022 unsigned char digest[16];
1023 PHP_MD5_CTX ctx;
1024 char *new_etag = ecalloc(1, 33);
1025
1026 PHP_MD5Init(&ctx);
1027
1028 switch (data_mode)
1029 {
1030 case SEND_DATA:
1031 PHP_MD5Update(&ctx, data_ptr, data_len);
1032 break;
1033
1034 case SEND_RSRC:
1035 if (!HTTP_G(ssb).sb.st_ino) {
1036 if (php_stream_stat((php_stream *) data_ptr, &HTTP_G(ssb))) {
1037 return NULL;
1038 }
1039 }
1040 snprintf(ssb_buf, 127, "%ld=%ld=%ld",
1041 HTTP_G(ssb).sb.st_mtime,
1042 HTTP_G(ssb).sb.st_ino,
1043 HTTP_G(ssb).sb.st_size
1044 );
1045 PHP_MD5Update(&ctx, ssb_buf, strlen(ssb_buf));
1046 break;
1047
1048 default:
1049 efree(new_etag);
1050 return NULL;
1051 break;
1052 }
1053
1054 PHP_MD5Final(digest, &ctx);
1055 make_digest(new_etag, digest);
1056
1057 return new_etag;
1058 }
1059 /* }}} */
1060
1061 /* {{{ inline http_lmod(void *, http_send_mode) */
1062 PHP_HTTP_API inline time_t _http_lmod(const void *data_ptr, const http_send_mode data_mode TSRMLS_DC)
1063 {
1064 switch (data_mode)
1065 {
1066 case SEND_DATA:
1067 {
1068 return time(NULL);
1069 }
1070
1071 case SEND_RSRC:
1072 {
1073 if (!HTTP_G(ssb).sb.st_mtime) {
1074 if (php_stream_stat((php_stream *) data_ptr, &HTTP_G(ssb))) {
1075 return 0;
1076 }
1077 }
1078 return HTTP_G(ssb).sb.st_mtime;
1079 }
1080
1081 default:
1082 {
1083 if (!HTTP_G(ssb).sb.st_mtime) {
1084 if(php_stream_stat_path(Z_STRVAL_P((zval *) data_ptr), &HTTP_G(ssb))) {
1085 return 0;
1086 }
1087 }
1088 return HTTP_G(ssb).sb.st_mtime;
1089 }
1090 }
1091 }
1092 /* }}} */
1093
1094 /* {{{inline int http_is_range_request(void) */
1095 PHP_HTTP_API inline int _http_is_range_request(TSRMLS_D)
1096 {
1097 return zend_hash_exists(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]),
1098 "HTTP_RANGE", strlen("HTTP_RANGE") + 1);
1099 }
1100 /* }}} */
1101
1102 /* {{{ inline STATUS http_send_status(int) */
1103 PHP_HTTP_API inline STATUS _http_send_status(const int status TSRMLS_DC)
1104 {
1105 int s = status;
1106 return sapi_header_op(SAPI_HEADER_SET_STATUS, (void *) s TSRMLS_CC);
1107 }
1108 /* }}} */
1109
1110 /* {{{ inline STATUS http_send_header(char *) */
1111 PHP_HTTP_API inline STATUS _http_send_header(const char *header TSRMLS_DC)
1112 {
1113 return http_send_status_header(0, header);
1114 }
1115 /* }}} */
1116
1117 /* {{{ inline STATUS http_send_status_header(int, char *) */
1118 PHP_HTTP_API inline STATUS _http_send_status_header(const int status, const char *header TSRMLS_DC)
1119 {
1120 sapi_header_line h = {(char *) header, strlen(header), status};
1121 return sapi_header_op(SAPI_HEADER_REPLACE, &h TSRMLS_CC);
1122 }
1123 /* }}} */
1124
1125 /* {{{ inline zval *http_get_server_var(char *) */
1126 PHP_HTTP_API inline zval *_http_get_server_var(const char *key TSRMLS_DC)
1127 {
1128 zval **var;
1129 if (SUCCESS == zend_hash_find(
1130 HTTP_SERVER_VARS,
1131 (char *) key, strlen(key) + 1, (void **) &var)) {
1132 return *var;
1133 }
1134 return NULL;
1135 }
1136 /* }}} */
1137
1138 /* {{{ void http_ob_etaghandler(char *, uint, char **, uint *, int) */
1139 PHP_HTTP_API void _http_ob_etaghandler(char *output, uint output_len,
1140 char **handled_output, uint *handled_output_len, int mode TSRMLS_DC)
1141 {
1142 char etag[33] = { 0 };
1143 unsigned char digest[16];
1144
1145 if (mode & PHP_OUTPUT_HANDLER_START) {
1146 PHP_MD5Init(&HTTP_G(etag_md5));
1147 }
1148
1149 PHP_MD5Update(&HTTP_G(etag_md5), output, output_len);
1150
1151 if (mode & PHP_OUTPUT_HANDLER_END) {
1152 PHP_MD5Final(digest, &HTTP_G(etag_md5));
1153
1154 /* just do that if desired */
1155 if (HTTP_G(etag_started)) {
1156 make_digest(etag, digest);
1157
1158 if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1159 http_send_status(304);
1160 } else {
1161 http_send_etag(etag, 32);
1162 }
1163 }
1164 }
1165
1166 *handled_output_len = output_len;
1167 *handled_output = estrndup(output, output_len);
1168 }
1169 /* }}} */
1170
1171 /* {{{ STATUS http_start_ob_handler(php_output_handler_func_t, char *, uint, zend_bool) */
1172 PHP_HTTP_API STATUS _http_start_ob_handler(php_output_handler_func_t handler_func,
1173 char *handler_name, uint chunk_size, zend_bool erase TSRMLS_DC)
1174 {
1175 php_ob_buffer **stack;
1176 int count, i;
1177
1178 if (count = OG(ob_nesting_level)) {
1179 stack = ecalloc(count, sizeof(php_ob_buffer *));
1180
1181 if (count > 1) {
1182 zend_stack_apply_with_argument(&OG(ob_buffers), ZEND_STACK_APPLY_BOTTOMUP,
1183 (int (*)(void *elem, void *)) http_ob_stack_get, stack);
1184 }
1185
1186 if (count > 0) {
1187 http_ob_stack_get(&OG(active_ob_buffer), stack);
1188 }
1189
1190 while (OG(ob_nesting_level)) {
1191 php_end_ob_buffer(0, 0 TSRMLS_CC);
1192 }
1193 }
1194
1195 php_ob_set_internal_handler(handler_func, chunk_size, handler_name, erase TSRMLS_CC);
1196
1197 for (i = 0; i < count; i++) {
1198 php_ob_buffer *s = stack[i];
1199 if (strcmp(s->handler_name, "default output handler")) {
1200 php_start_ob_buffer_named(s->handler_name, s->chunk_size, s->erase TSRMLS_CC);
1201 }
1202 php_body_write(s->buffer, s->text_length TSRMLS_CC);
1203 efree(s->handler_name);
1204 efree(s->buffer);
1205 efree(s);
1206 }
1207 if (count) {
1208 efree(stack);
1209 }
1210
1211 return SUCCESS;
1212 }
1213 /* }}} */
1214
1215 /* {{{ int http_modified_match(char *, int) */
1216 PHP_HTTP_API int _http_modified_match(const char *entry, const time_t t TSRMLS_DC)
1217 {
1218 int retval;
1219 zval *zmodified;
1220 char *modified, *chr_ptr;
1221
1222 HTTP_GSC(zmodified, entry, 0);
1223
1224 modified = estrndup(Z_STRVAL_P(zmodified), Z_STRLEN_P(zmodified));
1225 if (chr_ptr = strrchr(modified, ';')) {
1226 chr_ptr = 0;
1227 }
1228 retval = (t <= http_parse_date(modified));
1229 efree(modified);
1230 return retval;
1231 }
1232 /* }}} */
1233
1234 /* {{{ int http_etag_match(char *, char *) */
1235 PHP_HTTP_API int _http_etag_match(const char *entry, const char *etag TSRMLS_DC)
1236 {
1237 zval *zetag;
1238 char *quoted_etag;
1239 STATUS result;
1240
1241 HTTP_GSC(zetag, entry, 0);
1242
1243 if (NULL != strchr(Z_STRVAL_P(zetag), '*')) {
1244 return 1;
1245 }
1246
1247 quoted_etag = (char *) emalloc(strlen(etag) + 3);
1248 sprintf(quoted_etag, "\"%s\"", etag);
1249
1250 if (!strchr(Z_STRVAL_P(zetag), ',')) {
1251 result = !strcmp(Z_STRVAL_P(zetag), quoted_etag);
1252 } else {
1253 result = (NULL != strstr(Z_STRVAL_P(zetag), quoted_etag));
1254 }
1255 efree(quoted_etag);
1256 return result;
1257 }
1258 /* }}} */
1259
1260 /* {{{ STATUS http_send_last_modified(int) */
1261 PHP_HTTP_API STATUS _http_send_last_modified(const time_t t TSRMLS_DC)
1262 {
1263 char modified[96] = "Last-Modified: ", *date;
1264 date = http_date(t);
1265 strcat(modified, date);
1266 efree(date);
1267
1268 /* remember */
1269 HTTP_G(lmod) = t;
1270
1271 return http_send_header(modified);
1272 }
1273 /* }}} */
1274
1275 /* {{{ static STATUS http_send_etag(char *, int) */
1276 PHP_HTTP_API STATUS _http_send_etag(const char *etag,
1277 const int etag_len TSRMLS_DC)
1278 {
1279 STATUS status;
1280 char *etag_header;
1281
1282 if (!etag_len){
1283 php_error_docref(NULL TSRMLS_CC,E_ERROR,
1284 "Attempt to send empty ETag (previous: %s)\n", HTTP_G(etag));
1285 return FAILURE;
1286 }
1287
1288 /* remember */
1289 if (HTTP_G(etag)) {
1290 efree(HTTP_G(etag));
1291 }
1292 HTTP_G(etag) = estrdup(etag);
1293
1294 etag_header = ecalloc(1, sizeof("ETag: \"\"") + etag_len);
1295 sprintf(etag_header, "ETag: \"%s\"", etag);
1296 if (SUCCESS != (status = http_send_header(etag_header))) {
1297 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't send '%s' header", etag_header);
1298 }
1299 efree(etag_header);
1300 return status;
1301 }
1302 /* }}} */
1303
1304 /* {{{ STATUS http_send_cache_control(char *, size_t) */
1305 PHP_HTTP_API STATUS _http_send_cache_control(const char *cache_control,
1306 const size_t cc_len TSRMLS_DC)
1307 {
1308 STATUS status;
1309 char *cc_header = ecalloc(1, sizeof("Cache-Control: ") + cc_len);
1310
1311 sprintf(cc_header, "Cache-Control: %s", cache_control);
1312 if (SUCCESS != (status = http_send_header(cc_header))) {
1313 php_error_docref(NULL TSRMLS_CC, E_NOTICE,
1314 "Could not send '%s' header", cc_header);
1315 }
1316 efree(cc_header);
1317 return status;
1318 }
1319 /* }}} */
1320
1321 /* {{{ STATUS http_send_content_type(char *, size_t) */
1322 PHP_HTTP_API STATUS _http_send_content_type(const char *content_type,
1323 const size_t ct_len TSRMLS_DC)
1324 {
1325 STATUS status;
1326 char *ct_header;
1327
1328 if (!strchr(content_type, '/')) {
1329 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1330 "Content-Type '%s' doesn't seem to consist of a primary and a secondary part",
1331 content_type);
1332 return FAILURE;
1333 }
1334
1335 /* remember for multiple ranges */
1336 if (HTTP_G(ctype)) {
1337 efree(HTTP_G(ctype));
1338 }
1339 HTTP_G(ctype) = estrndup(content_type, ct_len);
1340
1341 ct_header = ecalloc(1, sizeof("Content-Type: ") + ct_len);
1342 sprintf(ct_header, "Content-Type: %s", content_type);
1343
1344 if (SUCCESS != (status = http_send_header(ct_header))) {
1345 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1346 "Couldn't send '%s' header", ct_header);
1347 }
1348 efree(ct_header);
1349 return status;
1350 }
1351 /* }}} */
1352
1353 /* {{{ STATUS http_send_content_disposition(char *, size_t, zend_bool) */
1354 PHP_HTTP_API STATUS _http_send_content_disposition(const char *filename,
1355 const size_t f_len, const int send_inline TSRMLS_DC)
1356 {
1357 STATUS status;
1358 char *cd_header;
1359
1360 if (send_inline) {
1361 cd_header = ecalloc(1, sizeof("Content-Disposition: inline; filename=\"\"") + f_len);
1362 sprintf(cd_header, "Content-Disposition: inline; filename=\"%s\"", filename);
1363 } else {
1364 cd_header = ecalloc(1, sizeof("Content-Disposition: attachment; filename=\"\"") + f_len);
1365 sprintf(cd_header, "Content-Disposition: attachment; filename=\"%s\"", filename);
1366 }
1367
1368 if (SUCCESS != (status = http_send_header(cd_header))) {
1369 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't send '%s' header", cd_header);
1370 }
1371 efree(cd_header);
1372 return status;
1373 }
1374 /* }}} */
1375
1376 /* {{{ STATUS http_cache_last_modified(time_t, time_t, char *, size_t) */
1377 PHP_HTTP_API STATUS _http_cache_last_modified(const time_t last_modified,
1378 const time_t send_modified, const char *cache_control, const size_t cc_len TSRMLS_DC)
1379 {
1380 if (cc_len) {
1381 http_send_cache_control(cache_control, cc_len);
1382 }
1383
1384 if (http_modified_match("HTTP_IF_MODIFIED_SINCE", last_modified)) {
1385 if (SUCCESS == http_send_status(304)) {
1386 zend_bailout();
1387 } else {
1388 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified");
1389 return FAILURE;
1390 }
1391 }
1392 return http_send_last_modified(send_modified);
1393 }
1394 /* }}} */
1395
1396 /* {{{ STATUS http_cache_etag(char *, size_t, char *, size_t) */
1397 PHP_HTTP_API STATUS _http_cache_etag(const char *etag, const size_t etag_len,
1398 const char *cache_control, const size_t cc_len TSRMLS_DC)
1399 {
1400 if (cc_len) {
1401 http_send_cache_control(cache_control, cc_len);
1402 }
1403
1404 if (etag_len) {
1405 http_send_etag(etag, etag_len);
1406 if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1407 if (SUCCESS == http_send_status(304)) {
1408 zend_bailout();
1409 } else {
1410 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified");
1411 return FAILURE;
1412 }
1413 }
1414 }
1415
1416 /* if no etag is given and we didn't already start ob_etaghandler -- start it */
1417 if (!HTTP_G(etag_started)) {
1418 if (SUCCESS == http_start_ob_handler(_http_ob_etaghandler, "ob_etaghandler", 4096, 1)) {
1419 HTTP_G(etag_started) = 1;
1420 return SUCCESS;
1421 } else {
1422 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not start ob_etaghandler");
1423 return FAILURE;
1424 }
1425 }
1426 return SUCCESS;
1427 }
1428 /* }}} */
1429
1430 /* {{{ char *http_absolute_uri(char *, char *) */
1431 PHP_HTTP_API char *_http_absolute_uri(const char *url,
1432 const char *proto TSRMLS_DC)
1433 {
1434 char *proto_ptr, *host, *path, *PTR, *URI = ecalloc(1, HTTP_URI_MAXLEN + 1);
1435 zval *zhost;
1436
1437 if (!url || !strlen(url)) {
1438 if (!SG(request_info).request_uri) {
1439 return NULL;
1440 }
1441 url = SG(request_info).request_uri;
1442 }
1443 /* Mess around with already absolute URIs */
1444 else if (proto_ptr = strstr(url, "://")) {
1445 if (!proto || !strncmp(url, proto, strlen(proto))) {
1446 strncpy(URI, url, HTTP_URI_MAXLEN);
1447 return URI;
1448 } else {
1449 snprintf(URI, HTTP_URI_MAXLEN, "%s%s", proto, proto_ptr + 3);
1450 return URI;
1451 }
1452 }
1453
1454 /* protocol defaults to http */
1455 if (!proto || !strlen(proto)) {
1456 proto = "http";
1457 }
1458
1459 /* get host name */
1460 if ( (zhost = http_get_server_var("HTTP_HOST")) ||
1461 (zhost = http_get_server_var("SERVER_NAME"))) {
1462 host = Z_STRVAL_P(zhost);
1463 } else {
1464 host = "localhost";
1465 }
1466
1467
1468 /* glue together */
1469 if (url[0] == '/') {
1470 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s%s", proto, host, url);
1471 } else if (SG(request_info).request_uri) {
1472 path = estrdup(SG(request_info).request_uri);
1473 php_dirname(path, strlen(path));
1474 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s%s/%s", proto, host, path, url);
1475 efree(path);
1476 } else {
1477 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s/%s", proto, host, url);
1478 }
1479
1480 /* strip everything after a new line */
1481 if ((PTR = strchr(URI, '\r')) || (PTR = strchr(URI, '\n'))) {
1482 PTR = 0;
1483 }
1484
1485 return URI;
1486 }
1487 /* }}} */
1488
1489 /* {{{ char *http_negotiate_q(char *, zval *, char *, hash_entry_type) */
1490 PHP_HTTP_API char *_http_negotiate_q(const char *entry, const zval *supported,
1491 const char *def TSRMLS_DC)
1492 {
1493 zval *zaccept, *zarray, *zdelim, **zentry, *zentries, **zsupp;
1494 char *q_ptr, *result;
1495 int i, c;
1496 double qual;
1497
1498 HTTP_GSC(zaccept, entry, estrdup(def));
1499
1500 MAKE_STD_ZVAL(zarray);
1501 array_init(zarray);
1502
1503 MAKE_STD_ZVAL(zdelim);
1504 ZVAL_STRING(zdelim, ",", 0);
1505 php_explode(zdelim, zaccept, zarray, -1);
1506 efree(zdelim);
1507
1508 MAKE_STD_ZVAL(zentries);
1509 array_init(zentries);
1510
1511 c = zend_hash_num_elements(Z_ARRVAL_P(zarray));
1512 for (i = 0; i < c; i++, zend_hash_move_forward(Z_ARRVAL_P(zarray))) {
1513
1514 if (SUCCESS != zend_hash_get_current_data(
1515 Z_ARRVAL_P(zarray), (void **) &zentry)) {
1516 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1517 "Cannot parse %s header: %s", entry, Z_STRVAL_P(zaccept));
1518 break;
1519 }
1520
1521 /* check for qualifier */
1522 if (NULL != (q_ptr = strrchr(Z_STRVAL_PP(zentry), ';'))) {
1523 qual = strtod(q_ptr + 3, NULL);
1524 } else {
1525 qual = 1000.0 - i;
1526 }
1527
1528 /* walk through the supported array */
1529 for ( zend_hash_internal_pointer_reset(Z_ARRVAL_P(supported));
1530 SUCCESS == zend_hash_get_current_data(
1531 Z_ARRVAL_P(supported), (void **) &zsupp);
1532 zend_hash_move_forward(Z_ARRVAL_P(supported))) {
1533 if (!strcasecmp(Z_STRVAL_PP(zsupp), Z_STRVAL_PP(zentry))) {
1534 add_assoc_double(zentries, Z_STRVAL_PP(zsupp), qual);
1535 break;
1536 }
1537 }
1538 }
1539
1540 zval_dtor(zarray);
1541 efree(zarray);
1542
1543 zend_hash_internal_pointer_reset(Z_ARRVAL_P(zentries));
1544
1545 if ( (SUCCESS != zend_hash_sort(Z_ARRVAL_P(zentries), zend_qsort,
1546 http_sort_q, 0 TSRMLS_CC)) ||
1547 (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key(
1548 Z_ARRVAL_P(zentries), &result, 0, 1))) {
1549 result = estrdup(def);
1550 }
1551
1552 zval_dtor(zentries);
1553 efree(zentries);
1554
1555 return result;
1556 }
1557 /* }}} */
1558
1559 /* {{{ http_range_status http_get_request_ranges(zval *zranges, size_t) */
1560 PHP_HTTP_API http_range_status _http_get_request_ranges(zval *zranges,
1561 const size_t length TSRMLS_DC)
1562 {
1563 zval *zrange;
1564 char *range, c;
1565 long begin = -1, end = -1, *ptr;
1566
1567 HTTP_GSC(zrange, "HTTP_RANGE", RANGE_NO);
1568 range = Z_STRVAL_P(zrange);
1569
1570 if (strncmp(range, "bytes=", strlen("bytes="))) {
1571 php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Range header misses bytes=");
1572 return RANGE_NO;
1573 }
1574
1575 ptr = &begin;
1576 range += strlen("bytes=");
1577
1578 do {
1579 switch (c = *(range++))
1580 {
1581 case '0':
1582 *ptr *= 10;
1583 break;
1584
1585 case '1': case '2': case '3':
1586 case '4': case '5': case '6':
1587 case '7': case '8': case '9':
1588 /*
1589 * If the value of the pointer is already set (non-negative)
1590 * then multiply its value by ten and add the current value,
1591 * else initialise the pointers value with the current value
1592 * --
1593 * This let us recognize empty fields when validating the
1594 * ranges, i.e. a "-10" for begin and "12345" for the end
1595 * was the following range request: "Range: bytes=0-12345";
1596 * While a "-1" for begin and "12345" for the end would
1597 * have been: "Range: bytes=-12345".
1598 */
1599 if (*ptr > 0) {
1600 *ptr *= 10;
1601 *ptr += c - '0';
1602 } else {
1603 *ptr = c - '0';
1604 }
1605 break;
1606
1607 case '-':
1608 ptr = &end;
1609 break;
1610
1611 case ' ':
1612 /* IE - ignore for now */
1613 break;
1614
1615 case 0:
1616 case ',':
1617
1618 if (length) {
1619 /* validate ranges */
1620 switch (begin)
1621 {
1622 /* "0-12345" */
1623 case -10:
1624 if ((length - end) < 1) {
1625 return RANGE_ERR;
1626 }
1627 begin = 0;
1628 break;
1629
1630 /* "-12345" */
1631 case -1:
1632 if ((length - end) < 1) {
1633 return RANGE_ERR;
1634 }
1635 begin = length - end;
1636 end = length;
1637 break;
1638
1639 /* "12345-(xxx)" */
1640 default:
1641 switch (end)
1642 {
1643 /* "12345-" */
1644 case -1:
1645 if ((length - begin) < 1) {
1646 return RANGE_ERR;
1647 }
1648 end = length - 1;
1649 break;
1650
1651 /* "12345-67890" */
1652 default:
1653 if ( ((length - begin) < 1) ||
1654 ((length - end) < 1) ||
1655 ((begin - end) >= 0)) {
1656 return RANGE_ERR;
1657 }
1658 break;
1659 }
1660 break;
1661 }
1662 }
1663 {
1664 zval *zentry;
1665 MAKE_STD_ZVAL(zentry);
1666 array_init(zentry);
1667 add_index_long(zentry, 0, begin);
1668 add_index_long(zentry, 1, end);
1669 add_next_index_zval(zranges, zentry);
1670
1671 begin = -1;
1672 end = -1;
1673 ptr = &begin;
1674 }
1675 break;
1676
1677 default:
1678 return RANGE_NO;
1679 break;
1680 }
1681 } while (c != 0);
1682
1683 return RANGE_OK;
1684 }
1685 /* }}} */
1686
1687 /* {{{ STATUS http_send_ranges(zval *, void *, size_t, http_send_mode) */
1688 PHP_HTTP_API STATUS _http_send_ranges(zval *zranges, const void *data, const size_t size, const http_send_mode mode TSRMLS_DC)
1689 {
1690 int c;
1691 long **begin, **end;
1692 zval **zrange;
1693
1694 /* Send HTTP 206 Partial Content */
1695 http_send_status(206);
1696
1697 /* single range */
1698 if ((c = zend_hash_num_elements(Z_ARRVAL_P(zranges))) == 1) {
1699 char range_header[256] = {0};
1700
1701 zend_hash_index_find(Z_ARRVAL_P(zranges), 0, (void **) &zrange);
1702 zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin);
1703 zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end);
1704
1705 /* send content range header */
1706 snprintf(range_header, 255, "Content-Range: bytes %d-%d/%d", **begin, **end, size);
1707 http_send_header(range_header);
1708
1709 /* send requested chunk */
1710 return http_send_chunk(data, **begin, **end + 1, mode);
1711 }
1712
1713 /* multi range */
1714 else {
1715 int i;
1716 char bound[23] = {0}, preface[1024] = {0},
1717 multi_header[68] = "Content-Type: multipart/byteranges; boundary=";
1718
1719 snprintf(bound, 22, "--%d%0.9f", time(NULL), php_combined_lcg(TSRMLS_C));
1720 strncat(multi_header, bound + 2, 21);
1721 http_send_header(multi_header);
1722
1723 /* send each requested chunk */
1724 for ( i = 0, zend_hash_internal_pointer_reset(Z_ARRVAL_P(zranges));
1725 i < c;
1726 i++, zend_hash_move_forward(Z_ARRVAL_P(zranges))) {
1727 if ( HASH_KEY_NON_EXISTANT == zend_hash_get_current_data(
1728 Z_ARRVAL_P(zranges), (void **) &zrange) ||
1729 SUCCESS != zend_hash_index_find(
1730 Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
1731 SUCCESS != zend_hash_index_find(
1732 Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
1733 break;
1734 }
1735
1736 snprintf(preface, 1023,
1737 HTTP_CRLF "%s"
1738 HTTP_CRLF "Content-Type: %s"
1739 HTTP_CRLF "Content-Range: bytes %ld-%ld/%ld"
1740 HTTP_CRLF
1741 HTTP_CRLF,
1742
1743 bound,
1744 HTTP_G(ctype) ? HTTP_G(ctype) : "application/x-octetstream",
1745 **begin,
1746 **end,
1747 size
1748 );
1749
1750 php_body_write(preface, strlen(preface) TSRMLS_CC);
1751 http_send_chunk(data, **begin, **end + 1, mode);
1752 }
1753
1754 /* write boundary once more */
1755 php_body_write(HTTP_CRLF, 2 TSRMLS_CC);
1756 php_body_write(bound, strlen(bound) TSRMLS_CC);
1757
1758 return SUCCESS;
1759 }
1760 }
1761 /* }}} */
1762
1763 /* {{{ STATUS http_send(void *, sizezo_t, http_send_mode) */
1764 PHP_HTTP_API STATUS _http_send(const void *data_ptr, const size_t data_size,
1765 const http_send_mode data_mode TSRMLS_DC)
1766 {
1767 int is_range_request = http_is_range_request();
1768
1769 if (!data_ptr) {
1770 return FAILURE;
1771 }
1772
1773 /* etag handling */
1774 if (HTTP_G(etag_started)) {
1775 char *etag;
1776 /* interrupt */
1777 HTTP_G(etag_started) = 0;
1778 /* never ever use the output to compute the ETag if http_send() is used */
1779 php_end_ob_buffer(0, 0 TSRMLS_CC);
1780 if (!(etag = http_etag(data_ptr, data_size, data_mode))) {
1781 return FAILURE;
1782 }
1783
1784 /* send 304 Not Modified if etag matches */
1785 if ((!is_range_request) && http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1786 efree(etag);
1787 return http_send_status(304);
1788 }
1789
1790 http_send_etag(etag, 32);
1791 efree(etag);
1792 }
1793
1794 /* send 304 Not Modified if last-modified matches*/
1795 if ((!is_range_request) && http_modified_match("HTTP_IF_MODIFIED_SINCE", HTTP_G(lmod))) {
1796 return http_send_status(304);
1797 }
1798
1799 if (is_range_request) {
1800
1801 /* only send ranges if entity hasn't changed */
1802 if (
1803 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_MATCH", 13)) ||
1804 http_etag_match("HTTP_IF_MATCH", HTTP_G(etag)))
1805 &&
1806 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_UNMODIFIED_SINCE", 25)) ||
1807 http_modified_match("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(lmod)))
1808 ) {
1809
1810 STATUS result = FAILURE;
1811 zval *zranges = NULL;
1812 MAKE_STD_ZVAL(zranges);
1813 array_init(zranges);
1814
1815 switch (http_get_request_ranges(zranges, data_size))
1816 {
1817 case RANGE_NO:
1818 zval_dtor(zranges);
1819 efree(zranges);
1820 /* go ahead and send all */
1821 break;
1822
1823 case RANGE_OK:
1824 result = http_send_ranges(zranges, data_ptr, data_size, data_mode);
1825 zval_dtor(zranges);
1826 efree(zranges);
1827 return result;
1828 break;
1829
1830 case RANGE_ERR:
1831 zval_dtor(zranges);
1832 efree(zranges);
1833 http_send_status(416);
1834 return FAILURE;
1835 break;
1836
1837 default:
1838 return FAILURE;
1839 break;
1840 }
1841 }
1842 }
1843 /* send all */
1844 return http_send_chunk(data_ptr, 0, data_size, data_mode);
1845 }
1846 /* }}} */
1847
1848 /* {{{ STATUS http_send_data(zval *) */
1849 PHP_HTTP_API STATUS _http_send_data(const zval *zdata TSRMLS_DC)
1850 {
1851 if (!Z_STRLEN_P(zdata)) {
1852 return SUCCESS;
1853 }
1854 if (!Z_STRVAL_P(zdata)) {
1855 return FAILURE;
1856 }
1857
1858 return http_send(Z_STRVAL_P(zdata), Z_STRLEN_P(zdata), SEND_DATA);
1859 }
1860 /* }}} */
1861
1862 /* {{{ STATUS http_send_stream(php_stream *) */
1863 PHP_HTTP_API STATUS _http_send_stream(const php_stream *file TSRMLS_DC)
1864 {
1865 if (php_stream_stat((php_stream *) file, &HTTP_G(ssb))) {
1866 return FAILURE;
1867 }
1868
1869 return http_send(file, HTTP_G(ssb).sb.st_size, SEND_RSRC);
1870 }
1871 /* }}} */
1872
1873 /* {{{ STATUS http_send_file(zval *) */
1874 PHP_HTTP_API STATUS _http_send_file(const zval *zfile TSRMLS_DC)
1875 {
1876 php_stream *file;
1877 STATUS ret;
1878
1879 if (!Z_STRLEN_P(zfile)) {
1880 return FAILURE;
1881 }
1882
1883 if (!(file = php_stream_open_wrapper(Z_STRVAL_P(zfile), "rb",
1884 REPORT_ERRORS|ENFORCE_SAFE_MODE, NULL))) {
1885 return FAILURE;
1886 }
1887
1888 ret = http_send_stream(file);
1889 php_stream_close(file);
1890 return ret;
1891 }
1892 /* }}} */
1893
1894 /* {{{ proto STATUS http_chunked_decode(char *, size_t, char **, size_t *) */
1895 PHP_HTTP_API STATUS _http_chunked_decode(const char *encoded,
1896 const size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
1897 {
1898 const char *e_ptr;
1899 char *d_ptr;
1900
1901 *decoded_len = 0;
1902 *decoded = ecalloc(1, encoded_len);
1903 d_ptr = *decoded;
1904 e_ptr = encoded;
1905
1906 while (((e_ptr - encoded) - encoded_len) > 0) {
1907 char hex_len[9] = {0};
1908 size_t chunk_len = 0;
1909 int i = 0;
1910
1911 /* read in chunk size */
1912 while (isxdigit(*e_ptr)) {
1913 if (i == 9) {
1914 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1915 "Chunk size is too long: 0x%s...", hex_len);
1916 efree(*decoded);
1917 return FAILURE;
1918 }
1919 hex_len[i++] = *e_ptr++;
1920 }
1921
1922 /* reached the end */
1923 if (!strcmp(hex_len, "0")) {
1924 break;
1925 }
1926
1927 /* new line */
1928 if (strncmp(e_ptr, HTTP_CRLF, 2)) {
1929 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1930 "Invalid character (expected 0x0D 0x0A; got: %x %x)",
1931 *e_ptr, *(e_ptr + 1));
1932 efree(*decoded);
1933 return FAILURE;
1934 }
1935
1936 /* hex to long */
1937 {
1938 char *error = NULL;
1939 chunk_len = strtol(hex_len, &error, 16);
1940 if (error == hex_len) {
1941 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1942 "Invalid chunk size string: '%s'", hex_len);
1943 efree(*decoded);
1944 return FAILURE;
1945 }
1946 }
1947
1948 memcpy(d_ptr, e_ptr += 2, chunk_len);
1949 d_ptr += chunk_len;
1950 e_ptr += chunk_len + 2;
1951 *decoded_len += chunk_len;
1952 }
1953
1954 return SUCCESS;
1955 }
1956 /* }}} */
1957
1958 /* {{{ proto STATUS http_split_response_ex(char *, size_t, zval *, zval *) */
1959 PHP_HTTP_API STATUS _http_split_response_ex( char *response,
1960 size_t response_len, zval *zheaders, zval *zbody TSRMLS_DC)
1961 {
1962 char *body = NULL;
1963 char *header = response;
1964
1965 while ((response - header + 4) < response_len) {
1966 if ( (*response++ == '\r') &&
1967 (*response++ == '\n') &&
1968 (*response++ == '\r') &&
1969 (*response++ == '\n')) {
1970 body = response;
1971 break;
1972 }
1973 }
1974
1975 if (body && (response_len - (body - header))) {
1976 ZVAL_STRINGL(zbody, body, response_len - (body - header) - 1, 1);
1977 } else {
1978 Z_TYPE_P(zbody) = IS_NULL;
1979 }
1980
1981 return http_parse_headers(header, body ? body - header : response_len, zheaders);
1982 }
1983 /* }}} */
1984
1985 /* {{{ STATUS http_parse_headers(char *, long, zval *) */
1986 PHP_HTTP_API STATUS _http_parse_headers(char *header, int header_len, zval *array TSRMLS_DC)
1987 {
1988 char *colon = NULL, *line = NULL, *begin = header;
1989
1990 if (header_len < 2) {
1991 return FAILURE;
1992 }
1993
1994 /* status code */
1995 if (!strncmp(header, "HTTP/1.", 7)) {
1996 char *end = strstr(header, HTTP_CRLF);
1997 add_assoc_stringl(array, "Status",
1998 header + strlen("HTTP/1.x "),
1999 end - (header + strlen("HTTP/1.x ")), 1);
2000 header = end + 2;
2001 }
2002
2003 line = header;
2004
2005 while (header_len >= (line - begin)) {
2006 int value_len = 0;
2007
2008 switch (*line++)
2009 {
2010 case 0:
2011 --value_len; /* we don't have CR so value length is one char less */
2012 case '\n':
2013 if (colon && ((!(*line - 1)) || ((*line != ' ') && (*line != '\t')))) {
2014
2015 /* skip empty key */
2016 if (header != colon) {
2017 char *key = estrndup(header, colon - header);
2018 value_len += line - colon - 1;
2019
2020 /* skip leading ws */
2021 while (isspace(*(++colon))) --value_len;
2022 /* skip trailing ws */
2023 while (isspace(colon[value_len - 1])) --value_len;
2024
2025 if (value_len < 1) {
2026 /* hm, empty header? */
2027 add_assoc_stringl(array, key, "", 0, 1);
2028 } else {
2029 add_assoc_stringl(array, key, colon, value_len, 1);
2030 }
2031 efree(key);
2032 }
2033
2034 colon = NULL;
2035 value_len = 0;
2036 header += line - header;
2037 }
2038 break;
2039
2040 case ':':
2041 if (!colon) {
2042 colon = line - 1;
2043 }
2044 break;
2045 }
2046 }
2047 return SUCCESS;
2048 }
2049 /* }}} */
2050
2051 /* {{{ void http_get_request_headers(zval *) */
2052 PHP_HTTP_API void _http_get_request_headers(zval *array TSRMLS_DC)
2053 {
2054 char *key;
2055
2056 for ( zend_hash_internal_pointer_reset(HTTP_SERVER_VARS);
2057 zend_hash_get_current_key(HTTP_SERVER_VARS, &key, NULL, 0) != HASH_KEY_NON_EXISTANT;
2058 zend_hash_move_forward(HTTP_SERVER_VARS)) {
2059 if (!strncmp(key, "HTTP_", 5)) {
2060 zval **header;
2061 zend_hash_get_current_data(HTTP_SERVER_VARS, (void **) &header);
2062 add_assoc_stringl(array, pretty_key(key + 5, strlen(key) - 5, 1, 1), Z_STRVAL_PP(header), Z_STRLEN_PP(header), 1);
2063 }
2064 }
2065 }
2066 /* }}} */
2067
2068 /* {{{ HAVE_CURL */
2069 #ifdef HTTP_HAVE_CURL
2070
2071 /* {{{ STATUS http_get(char *, HashTable *, HashTable *, char **, size_t *) */
2072 PHP_HTTP_API STATUS _http_get(const char *URL, HashTable *options,
2073 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2074 {
2075 STATUS rs;
2076 CURL *ch = curl_easy_init();
2077
2078 if (!ch) {
2079 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2080 return FAILURE;
2081 }
2082
2083 rs = http_get_ex(ch, URL, options, info, data, data_len);
2084 curl_easy_cleanup(ch);
2085 return rs;
2086 }
2087 /* }}} */
2088
2089 /* {{{ STATUS http_get_ex(CURL *, char *, HashTable *, HashTable *, char **, size_t *) */
2090 PHP_HTTP_API STATUS _http_get_ex(CURL *ch, const char *URL, HashTable *options,
2091 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2092 {
2093 http_curl_initbuf(CURLBUF_EVRY);
2094 http_curl_setopts(ch, URL, options);
2095 curl_easy_setopt(ch, CURLOPT_NOBODY, 0);
2096 curl_easy_setopt(ch, CURLOPT_POST, 0);
2097
2098 if (CURLE_OK != curl_easy_perform(ch)) {
2099 http_curl_freebuf(CURLBUF_EVRY);
2100 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2101 return FAILURE;
2102 }
2103 if (info) {
2104 http_curl_getinfo(ch, info);
2105 }
2106 http_curl_movebuf(CURLBUF_EVRY, data, data_len);
2107 return SUCCESS;
2108 }
2109
2110 /* {{{ STATUS http_head(char *, HashTable *, HashTable *, char **data, size_t *) */
2111 PHP_HTTP_API STATUS _http_head(const char *URL, HashTable *options,
2112 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2113 {
2114 STATUS rs;
2115 CURL *ch = curl_easy_init();
2116
2117 if (!ch) {
2118 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2119 return FAILURE;
2120 }
2121
2122 rs = http_head_ex(ch, URL, options, info, data, data_len);
2123 curl_easy_cleanup(ch);
2124 return rs;
2125 }
2126 /* }}} */
2127
2128 /* {{{ STATUS http_head_ex(CURL *, char *, HashTable *, HashTable *, char **data, size_t *) */
2129 PHP_HTTP_API STATUS _http_head_ex(CURL *ch, const char *URL, HashTable *options,
2130 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2131 {
2132 http_curl_initbuf(CURLBUF_HDRS);
2133 http_curl_setopts(ch, URL, options);
2134 curl_easy_setopt(ch, CURLOPT_NOBODY, 1);
2135 curl_easy_setopt(ch, CURLOPT_POST, 0);
2136
2137 if (CURLE_OK != curl_easy_perform(ch)) {
2138 http_curl_freebuf(CURLBUF_HDRS);
2139 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2140 return FAILURE;
2141 }
2142 if (info) {
2143 http_curl_getinfo(ch, info);
2144 }
2145 http_curl_movebuf(CURLBUF_HDRS, data, data_len);
2146 return SUCCESS;
2147 }
2148
2149 /* {{{ STATUS http_post_data(char *, char *, size_t, HashTable *, HashTable *, char **, size_t *) */
2150 PHP_HTTP_API STATUS _http_post_data(const char *URL, char *postdata,
2151 size_t postdata_len, HashTable *options, HashTable *info, char **data,
2152 size_t *data_len TSRMLS_DC)
2153 {
2154 STATUS rs;
2155 CURL *ch = curl_easy_init();
2156
2157 if (!ch) {
2158 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2159 return FAILURE;
2160 }
2161 rs = http_post_data_ex(ch, URL, postdata, postdata_len, options, info, data, data_len);
2162 curl_easy_cleanup(ch);
2163 return rs;
2164 }
2165 /* }}} */
2166
2167 /* {{{ STATUS http_post_data_ex(CURL *, char *, char *, size_t, HashTable *, HashTable *, char **, size_t *) */
2168 PHP_HTTP_API STATUS _http_post_data_ex(CURL *ch, const char *URL, char *postdata,
2169 size_t postdata_len, HashTable *options, HashTable *info, char **data,
2170 size_t *data_len TSRMLS_DC)
2171 {
2172 http_curl_initbuf(CURLBUF_EVRY);
2173 http_curl_setopts(ch, URL, options);
2174 curl_easy_setopt(ch, CURLOPT_POST, 1);
2175 curl_easy_setopt(ch, CURLOPT_POSTFIELDS, postdata);
2176 curl_easy_setopt(ch, CURLOPT_POSTFIELDSIZE, postdata_len);
2177
2178 if (CURLE_OK != curl_easy_perform(ch)) {
2179 http_curl_freebuf(CURLBUF_EVRY);
2180 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2181 return FAILURE;
2182 }
2183 if (info) {
2184 http_curl_getinfo(ch, info);
2185 }
2186 http_curl_movebuf(CURLBUF_EVRY, data, data_len);
2187 return SUCCESS;
2188 }
2189 /* }}} */
2190
2191 /* {{{ STATUS http_post_array_ex(CURL *, char *, HashTable *, HashTable *, HashTable *, char **, size_t *) */
2192 PHP_HTTP_API STATUS _http_post_array_ex(CURL *ch, const char *URL, HashTable *postarray,
2193 HashTable *options, HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2194 {
2195 smart_str qstr = {0};
2196 STATUS status;
2197
2198 HTTP_URL_ARGSEP_OVERRIDE;
2199 if (php_url_encode_hash_ex(postarray, &qstr, NULL,0,NULL,0,NULL,0,NULL TSRMLS_CC) != SUCCESS) {
2200 if (qstr.c) {
2201 efree(qstr.c);
2202 }
2203 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not encode post data");
2204 HTTP_URL_ARGSEP_RESTORE;
2205 return FAILURE;
2206 }
2207 smart_str_0(&qstr);
2208 HTTP_URL_ARGSEP_RESTORE;
2209
2210 if (ch) {
2211 status = http_post_data_ex(ch, URL, qstr.c, qstr.len, options, info, data, data_len);
2212 } else {
2213 status = http_post_data(URL, qstr.c, qstr.len, options, info, data, data_len);
2214 }
2215
2216 if (qstr.c) {
2217 efree(qstr.c);
2218 }
2219 return status;
2220 }
2221 /* }}} */
2222
2223 /* {{{ STATUS http_post_curldata_ex(CURL *, char *, curl_httppost *, HashTable *, HashTable *, char **, size_t *) */
2224 PHP_HTTP_API STATUS _http_post_curldata_ex(CURL *ch, const char *URL,
2225 struct curl_httppost *curldata, HashTable *options, HashTable *info,
2226 char **data, size_t *data_len TSRMLS_DC)
2227 {
2228 http_curl_initbuf(CURLBUF_EVRY);
2229 http_curl_setopts(ch, URL, options);
2230 curl_easy_setopt(ch, CURLOPT_POST, 1);
2231 curl_easy_setopt(ch, CURLOPT_HTTPPOST, curldata);
2232
2233 if (CURLE_OK != curl_easy_perform(ch)) {
2234 http_curl_freebuf(CURLBUF_EVRY);
2235 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2236 return FAILURE;
2237 }
2238 if (info) {
2239 http_curl_getinfo(ch, info);
2240 }
2241 http_curl_movebuf(CURLBUF_EVRY, data, data_len);
2242 return SUCCESS;}
2243 /* }}} */
2244
2245 #endif
2246 /* }}} HAVE_CURL */
2247
2248 /* {{{ STATUS http_auth_header(char *, char*) */
2249 PHP_HTTP_API STATUS _http_auth_header(const char *type, const char *realm TSRMLS_DC)
2250 {
2251 char realm_header[1024] = {0};
2252 snprintf(realm_header, 1023, "WWW-Authenticate: %s realm=\"%s\"", type, realm);
2253 return http_send_status_header(401, realm_header);
2254 }
2255 /* }}} */
2256
2257 /* {{{ STATUS http_auth_credentials(char **, char **) */
2258 PHP_HTTP_API STATUS _http_auth_credentials(char **user, char **pass TSRMLS_DC)
2259 {
2260 if (strncmp(sapi_module.name, "isapi", 5)) {
2261 zval *zuser, *zpass;
2262
2263 HTTP_GSC(zuser, "PHP_AUTH_USER", FAILURE);
2264 HTTP_GSC(zpass, "PHP_AUTH_PW", FAILURE);
2265
2266 *user = estrndup(Z_STRVAL_P(zuser), Z_STRLEN_P(zuser));
2267 *pass = estrndup(Z_STRVAL_P(zpass), Z_STRLEN_P(zpass));
2268
2269 return SUCCESS;
2270 } else {
2271 zval *zauth = NULL;
2272 HTTP_GSC(zauth, "HTTP_AUTHORIZATION", FAILURE);
2273 {
2274 char *decoded, *colon;
2275 int decoded_len;
2276 decoded = php_base64_decode(Z_STRVAL_P(zauth), Z_STRLEN_P(zauth),
2277 &decoded_len);
2278
2279 if (colon = strchr(decoded + 6, ':')) {
2280 *user = estrndup(decoded + 6, colon - decoded - 6);
2281 *pass = estrndup(colon + 1, decoded + decoded_len - colon - 6 - 1);
2282
2283 return SUCCESS;
2284 } else {
2285 return FAILURE;
2286 }
2287 }
2288 }
2289 }
2290 /* }}} */
2291
2292 /* }}} public API */
2293
2294 /*
2295 * Local variables:
2296 * tab-width: 4
2297 * c-basic-offset: 4
2298 * End:
2299 * vim600: noet sw=4 ts=4 fdm=marker
2300 * vim<600: noet sw=4 ts=4
2301 */