* ditch mem-leak
[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 efree(qstr.c);
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(31, 1);
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(33, 1);
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(sizeof(php_ob_buffer *), count);
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(sizeof("ETag: \"\"") + etag_len, 1);
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(sizeof("Cache-Control: ") + cc_len, 1);
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(sizeof("Content-Type: ") + ct_len, 1);
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(sizeof("Content-Disposition: inline; filename=\"\"") + f_len, 1);
1362 sprintf(cd_header, "Content-Disposition: inline; filename=\"%s\"", filename);
1363 } else {
1364 cd_header = ecalloc(sizeof("Content-Disposition: attachment; filename=\"\"") + f_len, 1);
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 URI[HTTP_URI_MAXLEN + 1], *PTR, *proto_ptr, *host, *path;
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 return estrdup(url);
1447 } else {
1448 snprintf(URI, HTTP_URI_MAXLEN, "%s%s", proto, proto_ptr + 3);
1449 return estrdup(URI);
1450 }
1451 }
1452
1453 /* protocol defaults to http */
1454 if (!proto || !strlen(proto)) {
1455 proto = "http";
1456 }
1457
1458 /* get host name */
1459 if ( (zhost = http_get_server_var("HTTP_HOST")) ||
1460 (zhost = http_get_server_var("SERVER_NAME"))) {
1461 host = Z_STRVAL_P(zhost);
1462 } else {
1463 host = "localhost";
1464 }
1465
1466
1467 /* glue together */
1468 if (url[0] == '/') {
1469 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s%s", proto, host, url);
1470 } else if (SG(request_info).request_uri) {
1471 path = estrdup(SG(request_info).request_uri);
1472 php_dirname(path, strlen(path));
1473 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s%s/%s", proto, host, path, url);
1474 efree(path);
1475 } else {
1476 snprintf(URI, HTTP_URI_MAXLEN, "%s://%s/%s", proto, host, url);
1477 }
1478
1479 /* strip everything after a new line */
1480 PTR = URI;
1481 while (*PTR != 0) {
1482 if (*PTR == '\n' || *PTR == '\r') {
1483 *PTR = 0;
1484 break;
1485 }
1486 PTR++;
1487 }
1488
1489 return estrdup(URI);
1490 }
1491 /* }}} */
1492
1493 /* {{{ char *http_negotiate_q(char *, zval *, char *, hash_entry_type) */
1494 PHP_HTTP_API char *_http_negotiate_q(const char *entry, const zval *supported,
1495 const char *def TSRMLS_DC)
1496 {
1497 zval *zaccept, *zarray, *zdelim, **zentry, *zentries, **zsupp;
1498 char *q_ptr, *result;
1499 int i, c;
1500 double qual;
1501
1502 HTTP_GSC(zaccept, entry, estrdup(def));
1503
1504 MAKE_STD_ZVAL(zarray);
1505 array_init(zarray);
1506
1507 MAKE_STD_ZVAL(zdelim);
1508 ZVAL_STRING(zdelim, ",", 0);
1509 php_explode(zdelim, zaccept, zarray, -1);
1510 efree(zdelim);
1511
1512 MAKE_STD_ZVAL(zentries);
1513 array_init(zentries);
1514
1515 c = zend_hash_num_elements(Z_ARRVAL_P(zarray));
1516 for (i = 0; i < c; i++, zend_hash_move_forward(Z_ARRVAL_P(zarray))) {
1517
1518 if (SUCCESS != zend_hash_get_current_data(
1519 Z_ARRVAL_P(zarray), (void **) &zentry)) {
1520 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1521 "Cannot parse %s header: %s", entry, Z_STRVAL_P(zaccept));
1522 break;
1523 }
1524
1525 /* check for qualifier */
1526 if (NULL != (q_ptr = strrchr(Z_STRVAL_PP(zentry), ';'))) {
1527 qual = strtod(q_ptr + 3, NULL);
1528 } else {
1529 qual = 1000.0 - i;
1530 }
1531
1532 /* walk through the supported array */
1533 for ( zend_hash_internal_pointer_reset(Z_ARRVAL_P(supported));
1534 SUCCESS == zend_hash_get_current_data(
1535 Z_ARRVAL_P(supported), (void **) &zsupp);
1536 zend_hash_move_forward(Z_ARRVAL_P(supported))) {
1537 if (!strcasecmp(Z_STRVAL_PP(zsupp), Z_STRVAL_PP(zentry))) {
1538 add_assoc_double(zentries, Z_STRVAL_PP(zsupp), qual);
1539 break;
1540 }
1541 }
1542 }
1543
1544 zval_dtor(zarray);
1545 efree(zarray);
1546
1547 zend_hash_internal_pointer_reset(Z_ARRVAL_P(zentries));
1548
1549 if ( (SUCCESS != zend_hash_sort(Z_ARRVAL_P(zentries), zend_qsort,
1550 http_sort_q, 0 TSRMLS_CC)) ||
1551 (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key(
1552 Z_ARRVAL_P(zentries), &result, 0, 1))) {
1553 result = estrdup(def);
1554 }
1555
1556 zval_dtor(zentries);
1557 efree(zentries);
1558
1559 return result;
1560 }
1561 /* }}} */
1562
1563 /* {{{ http_range_status http_get_request_ranges(zval *zranges, size_t) */
1564 PHP_HTTP_API http_range_status _http_get_request_ranges(zval *zranges,
1565 const size_t length TSRMLS_DC)
1566 {
1567 zval *zrange;
1568 char *range, c;
1569 long begin = -1, end = -1, *ptr;
1570
1571 HTTP_GSC(zrange, "HTTP_RANGE", RANGE_NO);
1572 range = Z_STRVAL_P(zrange);
1573
1574 if (strncmp(range, "bytes=", strlen("bytes="))) {
1575 php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Range header misses bytes=");
1576 return RANGE_NO;
1577 }
1578
1579 ptr = &begin;
1580 range += strlen("bytes=");
1581
1582 do {
1583 switch (c = *(range++))
1584 {
1585 case '0':
1586 *ptr *= 10;
1587 break;
1588
1589 case '1': case '2': case '3':
1590 case '4': case '5': case '6':
1591 case '7': case '8': case '9':
1592 /*
1593 * If the value of the pointer is already set (non-negative)
1594 * then multiply its value by ten and add the current value,
1595 * else initialise the pointers value with the current value
1596 * --
1597 * This let us recognize empty fields when validating the
1598 * ranges, i.e. a "-10" for begin and "12345" for the end
1599 * was the following range request: "Range: bytes=0-12345";
1600 * While a "-1" for begin and "12345" for the end would
1601 * have been: "Range: bytes=-12345".
1602 */
1603 if (*ptr > 0) {
1604 *ptr *= 10;
1605 *ptr += c - '0';
1606 } else {
1607 *ptr = c - '0';
1608 }
1609 break;
1610
1611 case '-':
1612 ptr = &end;
1613 break;
1614
1615 case ' ':
1616 /* IE - ignore for now */
1617 break;
1618
1619 case 0:
1620 case ',':
1621
1622 if (length) {
1623 /* validate ranges */
1624 switch (begin)
1625 {
1626 /* "0-12345" */
1627 case -10:
1628 if ((length - end) < 1) {
1629 return RANGE_ERR;
1630 }
1631 begin = 0;
1632 break;
1633
1634 /* "-12345" */
1635 case -1:
1636 if ((length - end) < 1) {
1637 return RANGE_ERR;
1638 }
1639 begin = length - end;
1640 end = length;
1641 break;
1642
1643 /* "12345-(xxx)" */
1644 default:
1645 switch (end)
1646 {
1647 /* "12345-" */
1648 case -1:
1649 if ((length - begin) < 1) {
1650 return RANGE_ERR;
1651 }
1652 end = length - 1;
1653 break;
1654
1655 /* "12345-67890" */
1656 default:
1657 if ( ((length - begin) < 1) ||
1658 ((length - end) < 1) ||
1659 ((begin - end) >= 0)) {
1660 return RANGE_ERR;
1661 }
1662 break;
1663 }
1664 break;
1665 }
1666 }
1667 {
1668 zval *zentry;
1669 MAKE_STD_ZVAL(zentry);
1670 array_init(zentry);
1671 add_index_long(zentry, 0, begin);
1672 add_index_long(zentry, 1, end);
1673 add_next_index_zval(zranges, zentry);
1674
1675 begin = -1;
1676 end = -1;
1677 ptr = &begin;
1678 }
1679 break;
1680
1681 default:
1682 return RANGE_NO;
1683 break;
1684 }
1685 } while (c != 0);
1686
1687 return RANGE_OK;
1688 }
1689 /* }}} */
1690
1691 /* {{{ STATUS http_send_ranges(zval *, void *, size_t, http_send_mode) */
1692 PHP_HTTP_API STATUS _http_send_ranges(zval *zranges, const void *data, const size_t size, const http_send_mode mode TSRMLS_DC)
1693 {
1694 int c;
1695 long **begin, **end;
1696 zval **zrange;
1697
1698 /* Send HTTP 206 Partial Content */
1699 http_send_status(206);
1700
1701 /* single range */
1702 if ((c = zend_hash_num_elements(Z_ARRVAL_P(zranges))) == 1) {
1703 char range_header[256] = {0};
1704
1705 zend_hash_index_find(Z_ARRVAL_P(zranges), 0, (void **) &zrange);
1706 zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin);
1707 zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end);
1708
1709 /* send content range header */
1710 snprintf(range_header, 255, "Content-Range: bytes %d-%d/%d", **begin, **end, size);
1711 http_send_header(range_header);
1712
1713 /* send requested chunk */
1714 return http_send_chunk(data, **begin, **end + 1, mode);
1715 }
1716
1717 /* multi range */
1718 else {
1719 int i;
1720 char bound[23] = {0}, preface[1024] = {0},
1721 multi_header[68] = "Content-Type: multipart/byteranges; boundary=";
1722
1723 snprintf(bound, 22, "--%d%0.9f", time(NULL), php_combined_lcg(TSRMLS_C));
1724 strncat(multi_header, bound + 2, 21);
1725 http_send_header(multi_header);
1726
1727 /* send each requested chunk */
1728 for ( i = 0, zend_hash_internal_pointer_reset(Z_ARRVAL_P(zranges));
1729 i < c;
1730 i++, zend_hash_move_forward(Z_ARRVAL_P(zranges))) {
1731 if ( HASH_KEY_NON_EXISTANT == zend_hash_get_current_data(
1732 Z_ARRVAL_P(zranges), (void **) &zrange) ||
1733 SUCCESS != zend_hash_index_find(
1734 Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
1735 SUCCESS != zend_hash_index_find(
1736 Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
1737 break;
1738 }
1739
1740 snprintf(preface, 1023,
1741 HTTP_CRLF "%s"
1742 HTTP_CRLF "Content-Type: %s"
1743 HTTP_CRLF "Content-Range: bytes %ld-%ld/%ld"
1744 HTTP_CRLF
1745 HTTP_CRLF,
1746
1747 bound,
1748 HTTP_G(ctype) ? HTTP_G(ctype) : "application/x-octetstream",
1749 **begin,
1750 **end,
1751 size
1752 );
1753
1754 php_body_write(preface, strlen(preface) TSRMLS_CC);
1755 http_send_chunk(data, **begin, **end + 1, mode);
1756 }
1757
1758 /* write boundary once more */
1759 php_body_write(HTTP_CRLF, 2 TSRMLS_CC);
1760 php_body_write(bound, strlen(bound) TSRMLS_CC);
1761
1762 return SUCCESS;
1763 }
1764 }
1765 /* }}} */
1766
1767 /* {{{ STATUS http_send(void *, sizezo_t, http_send_mode) */
1768 PHP_HTTP_API STATUS _http_send(const void *data_ptr, const size_t data_size,
1769 const http_send_mode data_mode TSRMLS_DC)
1770 {
1771 int is_range_request = http_is_range_request();
1772
1773 if (!data_ptr) {
1774 return FAILURE;
1775 }
1776
1777 /* etag handling */
1778 if (HTTP_G(etag_started)) {
1779 char *etag;
1780 /* interrupt */
1781 HTTP_G(etag_started) = 0;
1782 /* never ever use the output to compute the ETag if http_send() is used */
1783 php_end_ob_buffer(0, 0 TSRMLS_CC);
1784 if (!(etag = http_etag(data_ptr, data_size, data_mode))) {
1785 return FAILURE;
1786 }
1787
1788 /* send 304 Not Modified if etag matches */
1789 if ((!is_range_request) && http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1790 efree(etag);
1791 return http_send_status(304);
1792 }
1793
1794 http_send_etag(etag, 32);
1795 efree(etag);
1796 }
1797
1798 /* send 304 Not Modified if last-modified matches*/
1799 if ((!is_range_request) && http_modified_match("HTTP_IF_MODIFIED_SINCE", HTTP_G(lmod))) {
1800 return http_send_status(304);
1801 }
1802
1803 if (is_range_request) {
1804
1805 /* only send ranges if entity hasn't changed */
1806 if (
1807 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_MATCH", 13)) ||
1808 http_etag_match("HTTP_IF_MATCH", HTTP_G(etag)))
1809 &&
1810 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_UNMODIFIED_SINCE", 25)) ||
1811 http_modified_match("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(lmod)))
1812 ) {
1813
1814 STATUS result = FAILURE;
1815 zval *zranges = NULL;
1816 MAKE_STD_ZVAL(zranges);
1817 array_init(zranges);
1818
1819 switch (http_get_request_ranges(zranges, data_size))
1820 {
1821 case RANGE_NO:
1822 zval_dtor(zranges);
1823 efree(zranges);
1824 /* go ahead and send all */
1825 break;
1826
1827 case RANGE_OK:
1828 result = http_send_ranges(zranges, data_ptr, data_size, data_mode);
1829 zval_dtor(zranges);
1830 efree(zranges);
1831 return result;
1832 break;
1833
1834 case RANGE_ERR:
1835 zval_dtor(zranges);
1836 efree(zranges);
1837 http_send_status(416);
1838 return FAILURE;
1839 break;
1840
1841 default:
1842 return FAILURE;
1843 break;
1844 }
1845 }
1846 }
1847 /* send all */
1848 return http_send_chunk(data_ptr, 0, data_size, data_mode);
1849 }
1850 /* }}} */
1851
1852 /* {{{ STATUS http_send_data(zval *) */
1853 PHP_HTTP_API STATUS _http_send_data(const zval *zdata TSRMLS_DC)
1854 {
1855 if (!Z_STRLEN_P(zdata)) {
1856 return SUCCESS;
1857 }
1858 if (!Z_STRVAL_P(zdata)) {
1859 return FAILURE;
1860 }
1861
1862 return http_send(Z_STRVAL_P(zdata), Z_STRLEN_P(zdata), SEND_DATA);
1863 }
1864 /* }}} */
1865
1866 /* {{{ STATUS http_send_stream(php_stream *) */
1867 PHP_HTTP_API STATUS _http_send_stream(const php_stream *file TSRMLS_DC)
1868 {
1869 if (php_stream_stat((php_stream *) file, &HTTP_G(ssb))) {
1870 return FAILURE;
1871 }
1872
1873 return http_send(file, HTTP_G(ssb).sb.st_size, SEND_RSRC);
1874 }
1875 /* }}} */
1876
1877 /* {{{ STATUS http_send_file(zval *) */
1878 PHP_HTTP_API STATUS _http_send_file(const zval *zfile TSRMLS_DC)
1879 {
1880 php_stream *file;
1881 STATUS ret;
1882
1883 if (!Z_STRLEN_P(zfile)) {
1884 return FAILURE;
1885 }
1886
1887 if (!(file = php_stream_open_wrapper(Z_STRVAL_P(zfile), "rb",
1888 REPORT_ERRORS|ENFORCE_SAFE_MODE, NULL))) {
1889 return FAILURE;
1890 }
1891
1892 ret = http_send_stream(file);
1893 php_stream_close(file);
1894 return ret;
1895 }
1896 /* }}} */
1897
1898 /* {{{ proto STATUS http_chunked_decode(char *, size_t, char **, size_t *) */
1899 PHP_HTTP_API STATUS _http_chunked_decode(const char *encoded,
1900 const size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
1901 {
1902 const char *e_ptr;
1903 char *d_ptr;
1904
1905 *decoded_len = 0;
1906 *decoded = (char *) ecalloc(encoded_len, 1);
1907 d_ptr = *decoded;
1908 e_ptr = encoded;
1909
1910 while (((e_ptr - encoded) - encoded_len) > 0) {
1911 char hex_len[9] = {0};
1912 size_t chunk_len = 0;
1913 int i = 0;
1914
1915 /* read in chunk size */
1916 while (isxdigit(*e_ptr)) {
1917 if (i == 9) {
1918 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1919 "Chunk size is too long: 0x%s...", hex_len);
1920 efree(*decoded);
1921 return FAILURE;
1922 }
1923 hex_len[i++] = *e_ptr++;
1924 }
1925
1926 /* reached the end */
1927 if (!strcmp(hex_len, "0")) {
1928 break;
1929 }
1930
1931 /* new line */
1932 if (strncmp(e_ptr, HTTP_CRLF, 2)) {
1933 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1934 "Invalid character (expected 0x0D 0x0A; got: %x %x)",
1935 *e_ptr, *(e_ptr + 1));
1936 efree(*decoded);
1937 return FAILURE;
1938 }
1939
1940 /* hex to long */
1941 {
1942 char *error = NULL;
1943 chunk_len = strtol(hex_len, &error, 16);
1944 if (error == hex_len) {
1945 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1946 "Invalid chunk size string: '%s'", hex_len);
1947 efree(*decoded);
1948 return FAILURE;
1949 }
1950 }
1951
1952 memcpy(d_ptr, e_ptr += 2, chunk_len);
1953 d_ptr += chunk_len;
1954 e_ptr += chunk_len + 2;
1955 *decoded_len += chunk_len;
1956 }
1957
1958 return SUCCESS;
1959 }
1960 /* }}} */
1961
1962 /* {{{ proto STATUS http_split_response_ex(char *, size_t, zval *, zval *) */
1963 PHP_HTTP_API STATUS _http_split_response_ex( char *response,
1964 size_t response_len, zval *zheaders, zval *zbody TSRMLS_DC)
1965 {
1966 char *body = NULL;
1967 char *header = response;
1968
1969 while ((response - header + 4) < response_len) {
1970 if ( (*response++ == '\r') &&
1971 (*response++ == '\n') &&
1972 (*response++ == '\r') &&
1973 (*response++ == '\n')) {
1974 body = response;
1975 break;
1976 }
1977 }
1978
1979 if (body && (response_len - (body - header))) {
1980 ZVAL_STRINGL(zbody, body, response_len - (body - header) - 1, 1);
1981 } else {
1982 Z_TYPE_P(zbody) = IS_NULL;
1983 }
1984
1985 return http_parse_headers(header, body ? body - header : response_len, zheaders);
1986 }
1987 /* }}} */
1988
1989 /* {{{ STATUS http_parse_headers(char *, long, zval *) */
1990 PHP_HTTP_API STATUS _http_parse_headers(char *header, int header_len, zval *array TSRMLS_DC)
1991 {
1992 char *colon = NULL, *line = NULL, *begin = header;
1993
1994 if (header_len < 2) {
1995 return FAILURE;
1996 }
1997
1998 /* status code */
1999 if (!strncmp(header, "HTTP/1.", 7)) {
2000 char *end = strstr(header, HTTP_CRLF);
2001 add_assoc_stringl(array, "Status",
2002 header + strlen("HTTP/1.x "),
2003 end - (header + strlen("HTTP/1.x ")), 1);
2004 header = end + 2;
2005 }
2006
2007 line = header;
2008
2009 while (header_len >= (line - begin)) {
2010 int value_len = 0;
2011
2012 switch (*line++)
2013 {
2014 case 0:
2015 --value_len; /* we don't have CR so value length is one char less */
2016 case '\n':
2017 if (colon && ((!(*line - 1)) || ((*line != ' ') && (*line != '\t')))) {
2018
2019 /* skip empty key */
2020 if (header != colon) {
2021 char *key = estrndup(header, colon - header);
2022 value_len += line - colon - 1;
2023
2024 /* skip leading ws */
2025 while (isspace(*(++colon))) --value_len;
2026 /* skip trailing ws */
2027 while (isspace(colon[value_len - 1])) --value_len;
2028
2029 if (value_len < 1) {
2030 /* hm, empty header? */
2031 add_assoc_stringl(array, key, "", 0, 1);
2032 } else {
2033 add_assoc_stringl(array, key, colon, value_len, 1);
2034 }
2035 efree(key);
2036 }
2037
2038 colon = NULL;
2039 value_len = 0;
2040 header += line - header;
2041 }
2042 break;
2043
2044 case ':':
2045 if (!colon) {
2046 colon = line - 1;
2047 }
2048 break;
2049 }
2050 }
2051 return SUCCESS;
2052 }
2053 /* }}} */
2054
2055 /* {{{ void http_get_request_headers(zval *) */
2056 PHP_HTTP_API void _http_get_request_headers(zval *array TSRMLS_DC)
2057 {
2058 char *key;
2059
2060 for ( zend_hash_internal_pointer_reset(HTTP_SERVER_VARS);
2061 zend_hash_get_current_key(HTTP_SERVER_VARS, &key, NULL, 0) != HASH_KEY_NON_EXISTANT;
2062 zend_hash_move_forward(HTTP_SERVER_VARS)) {
2063 if (!strncmp(key, "HTTP_", 5)) {
2064 zval **header;
2065 zend_hash_get_current_data(HTTP_SERVER_VARS, (void **) &header);
2066 add_assoc_stringl(array, pretty_key(key + 5, strlen(key) - 5, 1, 1), Z_STRVAL_PP(header), Z_STRLEN_PP(header), 1);
2067 }
2068 }
2069 }
2070 /* }}} */
2071
2072 /* {{{ HAVE_CURL */
2073 #ifdef HTTP_HAVE_CURL
2074
2075 /* {{{ STATUS http_get(char *, HashTable *, HashTable *, char **, size_t *) */
2076 PHP_HTTP_API STATUS _http_get(const char *URL, HashTable *options,
2077 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2078 {
2079 STATUS rs;
2080 CURL *ch = curl_easy_init();
2081
2082 if (!ch) {
2083 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2084 return FAILURE;
2085 }
2086
2087 rs = http_get_ex(ch, URL, options, info, data, data_len);
2088 curl_easy_cleanup(ch);
2089 return rs;
2090 }
2091 /* }}} */
2092
2093 /* {{{ STATUS http_get_ex(CURL *, char *, HashTable *, HashTable *, char **, size_t *) */
2094 PHP_HTTP_API STATUS _http_get_ex(CURL *ch, const char *URL, HashTable *options,
2095 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2096 {
2097 http_curl_initbuf(CURLBUF_EVRY);
2098 http_curl_setopts(ch, URL, options);
2099 curl_easy_setopt(ch, CURLOPT_NOBODY, 0);
2100 curl_easy_setopt(ch, CURLOPT_POST, 0);
2101
2102 if (CURLE_OK != curl_easy_perform(ch)) {
2103 http_curl_freebuf(CURLBUF_EVRY);
2104 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2105 return FAILURE;
2106 }
2107 if (info) {
2108 http_curl_getinfo(ch, info);
2109 }
2110 http_curl_movebuf(CURLBUF_EVRY, data, data_len);
2111 return SUCCESS;
2112 }
2113
2114 /* {{{ STATUS http_head(char *, HashTable *, HashTable *, char **data, size_t *) */
2115 PHP_HTTP_API STATUS _http_head(const char *URL, HashTable *options,
2116 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2117 {
2118 STATUS rs;
2119 CURL *ch = curl_easy_init();
2120
2121 if (!ch) {
2122 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2123 return FAILURE;
2124 }
2125
2126 rs = http_head_ex(ch, URL, options, info, data, data_len);
2127 curl_easy_cleanup(ch);
2128 return rs;
2129 }
2130 /* }}} */
2131
2132 /* {{{ STATUS http_head_ex(CURL *, char *, HashTable *, HashTable *, char **data, size_t *) */
2133 PHP_HTTP_API STATUS _http_head_ex(CURL *ch, const char *URL, HashTable *options,
2134 HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2135 {
2136 http_curl_initbuf(CURLBUF_HDRS);
2137 http_curl_setopts(ch, URL, options);
2138 curl_easy_setopt(ch, CURLOPT_NOBODY, 1);
2139 curl_easy_setopt(ch, CURLOPT_POST, 0);
2140
2141 if (CURLE_OK != curl_easy_perform(ch)) {
2142 http_curl_freebuf(CURLBUF_HDRS);
2143 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2144 return FAILURE;
2145 }
2146 if (info) {
2147 http_curl_getinfo(ch, info);
2148 }
2149 http_curl_movebuf(CURLBUF_HDRS, data, data_len);
2150 return SUCCESS;
2151 }
2152
2153 /* {{{ STATUS http_post_data(char *, char *, size_t, HashTable *, HashTable *, char **, size_t *) */
2154 PHP_HTTP_API STATUS _http_post_data(const char *URL, char *postdata,
2155 size_t postdata_len, HashTable *options, HashTable *info, char **data,
2156 size_t *data_len TSRMLS_DC)
2157 {
2158 STATUS rs;
2159 CURL *ch = curl_easy_init();
2160
2161 if (!ch) {
2162 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not initialize curl");
2163 return FAILURE;
2164 }
2165 rs = http_post_data_ex(ch, URL, postdata, postdata_len, options, info, data, data_len);
2166 curl_easy_cleanup(ch);
2167 return rs;
2168 }
2169 /* }}} */
2170
2171 /* {{{ STATUS http_post_data_ex(CURL *, char *, char *, size_t, HashTable *, HashTable *, char **, size_t *) */
2172 PHP_HTTP_API STATUS _http_post_data_ex(CURL *ch, const char *URL, char *postdata,
2173 size_t postdata_len, HashTable *options, HashTable *info, char **data,
2174 size_t *data_len TSRMLS_DC)
2175 {
2176 http_curl_initbuf(CURLBUF_EVRY);
2177 http_curl_setopts(ch, URL, options);
2178 curl_easy_setopt(ch, CURLOPT_POST, 1);
2179 curl_easy_setopt(ch, CURLOPT_POSTFIELDS, postdata);
2180 curl_easy_setopt(ch, CURLOPT_POSTFIELDSIZE, postdata_len);
2181
2182 if (CURLE_OK != curl_easy_perform(ch)) {
2183 http_curl_freebuf(CURLBUF_EVRY);
2184 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not perform request");
2185 return FAILURE;
2186 }
2187 if (info) {
2188 http_curl_getinfo(ch, info);
2189 }
2190 http_curl_movebuf(CURLBUF_EVRY, data, data_len);
2191 return SUCCESS;
2192 }
2193 /* }}} */
2194
2195 /* {{{ STATUS http_post_array(char *, HashTable *, HashTable *, HashTable *, char **, size_t *) */
2196 PHP_HTTP_API STATUS _http_post_array(const char *URL, HashTable *postarray,
2197 HashTable *options, HashTable *info, char **data, size_t *data_len TSRMLS_DC)
2198 {
2199 smart_str qstr = {0};
2200 STATUS status;
2201
2202 if (php_url_encode_hash_ex(postarray, &qstr, NULL,0,NULL,0,NULL,0,NULL TSRMLS_CC) != SUCCESS) {
2203 if (qstr.c) {
2204 efree(qstr.c);
2205 }
2206 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not encode post data");
2207 return FAILURE;
2208 }
2209 smart_str_0(&qstr);
2210
2211 status = http_post_data(URL, qstr.c, qstr.len, options, info, data, data_len);
2212 if (qstr.c) {
2213 efree(qstr.c);
2214 }
2215 return status;
2216 }
2217 /* }}} */
2218
2219 #endif
2220 /* }}} HAVE_CURL */
2221
2222 /* {{{ STATUS http_auth_header(char *, char*) */
2223 PHP_HTTP_API STATUS _http_auth_header(const char *type, const char *realm TSRMLS_DC)
2224 {
2225 char realm_header[1024] = {0};
2226 snprintf(realm_header, 1023, "WWW-Authenticate: %s realm=\"%s\"", type, realm);
2227 return http_send_status_header(401, realm_header);
2228 }
2229 /* }}} */
2230
2231 /* {{{ STATUS http_auth_credentials(char **, char **) */
2232 PHP_HTTP_API STATUS _http_auth_credentials(char **user, char **pass TSRMLS_DC)
2233 {
2234 if (strncmp(sapi_module.name, "isapi", 5)) {
2235 zval *zuser, *zpass;
2236
2237 HTTP_GSC(zuser, "PHP_AUTH_USER", FAILURE);
2238 HTTP_GSC(zpass, "PHP_AUTH_PW", FAILURE);
2239
2240 *user = estrndup(Z_STRVAL_P(zuser), Z_STRLEN_P(zuser));
2241 *pass = estrndup(Z_STRVAL_P(zpass), Z_STRLEN_P(zpass));
2242
2243 return SUCCESS;
2244 } else {
2245 zval *zauth = NULL;
2246 HTTP_GSC(zauth, "HTTP_AUTHORIZATION", FAILURE);
2247 {
2248 char *decoded, *colon;
2249 int decoded_len;
2250 decoded = php_base64_decode(Z_STRVAL_P(zauth), Z_STRLEN_P(zauth),
2251 &decoded_len);
2252
2253 if (colon = strchr(decoded + 6, ':')) {
2254 *user = estrndup(decoded + 6, colon - decoded - 6);
2255 *pass = estrndup(colon + 1, decoded + decoded_len - colon - 6 - 1);
2256
2257 return SUCCESS;
2258 } else {
2259 return FAILURE;
2260 }
2261 }
2262 }
2263 }
2264 /* }}} */
2265
2266 /* }}} public API */
2267
2268 /*
2269 * Local variables:
2270 * tab-width: 4
2271 * c-basic-offset: 4
2272 * End:
2273 * vim600: noet sw=4 ts=4 fdm=marker
2274 * vim<600: noet sw=4 ts=4
2275 */