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