6567e6b487894dcf56ac163c2a97f4efd5bcbdfe
[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 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21
22 #include <ctype.h>
23
24 #ifdef PHP_WIN32
25 # include <winsock2.h>
26 #elif defined(HAVE_NETDB_H)
27 # include <netdb.h>
28 #endif
29
30 #include "php.h"
31 #include "php_version.h"
32 #include "php_streams.h"
33 #include "snprintf.h"
34 #include "ext/standard/md5.h"
35 #include "ext/standard/url.h"
36 #include "ext/standard/base64.h"
37 #include "ext/standard/php_string.h"
38 #include "ext/standard/php_smart_str.h"
39 #include "ext/standard/php_lcg.h"
40
41 #include "SAPI.h"
42
43 #ifdef ZEND_ENGINE_2
44 # include "ext/standard/php_http.h"
45 #endif
46
47 #include "php_http.h"
48 #include "php_http_api.h"
49 #include "php_http_std_defs.h"
50
51 ZEND_DECLARE_MODULE_GLOBALS(http)
52
53 /* {{{ day/month names */
54 static const char *days[] = {
55 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
56 };
57 static const char *wkdays[] = {
58 "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
59 };
60 static const char *weekdays[] = {
61 "Monday", "Tuesday", "Wednesday",
62 "Thursday", "Friday", "Saturday", "Sunday"
63 };
64 static const char *months[] = {
65 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
66 "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"
67 };
68 enum assume_next {
69 DATE_MDAY,
70 DATE_YEAR,
71 DATE_TIME
72 };
73 static const struct time_zone {
74 const char *name;
75 const int offset;
76 } time_zones[] = {
77 {"GMT", 0}, /* Greenwich Mean */
78 {"UTC", 0}, /* Universal (Coordinated) */
79 {"WET", 0}, /* Western European */
80 {"BST", 0}, /* British Summer */
81 {"WAT", 60}, /* West Africa */
82 {"AST", 240}, /* Atlantic Standard */
83 {"ADT", 240}, /* Atlantic Daylight */
84 {"EST", 300}, /* Eastern Standard */
85 {"EDT", 300}, /* Eastern Daylight */
86 {"CST", 360}, /* Central Standard */
87 {"CDT", 360}, /* Central Daylight */
88 {"MST", 420}, /* Mountain Standard */
89 {"MDT", 420}, /* Mountain Daylight */
90 {"PST", 480}, /* Pacific Standard */
91 {"PDT", 480}, /* Pacific Daylight */
92 {"YST", 540}, /* Yukon Standard */
93 {"YDT", 540}, /* Yukon Daylight */
94 {"HST", 600}, /* Hawaii Standard */
95 {"HDT", 600}, /* Hawaii Daylight */
96 {"CAT", 600}, /* Central Alaska */
97 {"AHST", 600}, /* Alaska-Hawaii Standard */
98 {"NT", 660}, /* Nome */
99 {"IDLW", 720}, /* International Date Line West */
100 {"CET", -60}, /* Central European */
101 {"MET", -60}, /* Middle European */
102 {"MEWT", -60}, /* Middle European Winter */
103 {"MEST", -120}, /* Middle European Summer */
104 {"CEST", -120}, /* Central European Summer */
105 {"MESZ", -60}, /* Middle European Summer */
106 {"FWT", -60}, /* French Winter */
107 {"FST", -60}, /* French Summer */
108 {"EET", -120}, /* Eastern Europe, USSR Zone 1 */
109 {"WAST", -420}, /* West Australian Standard */
110 {"WADT", -420}, /* West Australian Daylight */
111 {"CCT", -480}, /* China Coast, USSR Zone 7 */
112 {"JST", -540}, /* Japan Standard, USSR Zone 8 */
113 {"EAST", -600}, /* Eastern Australian Standard */
114 {"EADT", -600}, /* Eastern Australian Daylight */
115 {"GST", -600}, /* Guam Standard, USSR Zone 9 */
116 {"NZT", -720}, /* New Zealand */
117 {"NZST", -720}, /* New Zealand Standard */
118 {"NZDT", -720}, /* New Zealand Daylight */
119 {"IDLE", -720}, /* International Date Line East */
120 };
121 /* }}} */
122
123 /* {{{ internals */
124
125 static int http_sort_q(const void *a, const void *b TSRMLS_DC);
126 #define http_send_chunk(d, b, e, m) _http_send_chunk((d), (b), (e), (m) TSRMLS_CC)
127 static STATUS _http_send_chunk(const void *data, const size_t begin, const size_t end, const http_send_mode mode TSRMLS_DC);
128
129 static int check_day(char *day, size_t len);
130 static int check_month(char *month);
131 static int check_tzone(char *tzone);
132
133 static int http_ob_stack_get(php_ob_buffer *, php_ob_buffer **);
134
135 /* {{{ static int http_sort_q(const void *, const void *) */
136 static int http_sort_q(const void *a, const void *b TSRMLS_DC)
137 {
138 Bucket *f, *s;
139 zval result, *first, *second;
140
141 f = *((Bucket **) a);
142 s = *((Bucket **) b);
143
144 first = *((zval **) f->pData);
145 second= *((zval **) s->pData);
146
147 if (numeric_compare_function(&result, first, second TSRMLS_CC) != SUCCESS) {
148 return 0;
149 }
150 return (Z_LVAL(result) > 0 ? -1 : (Z_LVAL(result) < 0 ? 1 : 0));
151 }
152 /* }}} */
153
154 /* {{{ static STATUS http_send_chunk(const void *, size_t, size_t,
155 http_send_mode) */
156 static STATUS _http_send_chunk(const void *data, const size_t begin,
157 const size_t end, const http_send_mode mode TSRMLS_DC)
158 {
159 char *buf;
160 size_t read = 0;
161 long len = end - begin;
162 php_stream *s;
163
164 switch (mode)
165 {
166 case SEND_RSRC:
167 s = (php_stream *) data;
168 if (php_stream_seek(s, begin, SEEK_SET)) {
169 return FAILURE;
170 }
171 buf = (char *) ecalloc(1, HTTP_SENDBUF_SIZE);
172 /* read into buf and write out */
173 while ((len -= HTTP_SENDBUF_SIZE) >= 0) {
174 if (!(read = php_stream_read(s, buf, HTTP_SENDBUF_SIZE))) {
175 efree(buf);
176 return FAILURE;
177 }
178 if (read - php_body_write(buf, read TSRMLS_CC)) {
179 efree(buf);
180 return FAILURE;
181 }
182 }
183
184 /* read & write left over */
185 if (len) {
186 if (read = php_stream_read(s, buf, HTTP_SENDBUF_SIZE + len)) {
187 if (read - php_body_write(buf, read TSRMLS_CC)) {
188 efree(buf);
189 return FAILURE;
190 }
191 } else {
192 efree(buf);
193 return FAILURE;
194 }
195 }
196 efree(buf);
197 return SUCCESS;
198 break;
199
200 case SEND_DATA:
201 return len == php_body_write(((char *)data) + begin, len TSRMLS_CC)
202 ? SUCCESS : FAILURE;
203 break;
204
205 default:
206 return FAILURE;
207 break;
208 }
209 }
210 /* }}} */
211
212 /* {{{ Day/Month/TZ checks for http_parse_date()
213 Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
214 static int check_day(char *day, size_t len)
215 {
216 int i;
217 const char * const *check = (len > 3) ? &weekdays[0] : &wkdays[0];
218 for (i = 0; i < 7; i++) {
219 if (!strcmp(day, check[0])) {
220 return i;
221 }
222 check++;
223 }
224 return -1;
225 }
226
227 static int check_month(char *month)
228 {
229 int i;
230 const char * const *check = &months[0];
231 for (i = 0; i < 12; i++) {
232 if (!strcmp(month, check[0])) {
233 return i;
234 }
235 check++;
236 }
237 return -1;
238 }
239
240 /* return the time zone offset between GMT and the input one, in number
241 of seconds or -1 if the timezone wasn't found/legal */
242
243 static int check_tzone(char *tzone)
244 {
245 int i;
246 const struct time_zone *check = time_zones;
247 for (i = 0; i < sizeof(time_zones) / sizeof(time_zones[0]); i++) {
248 if (!strcmp(tzone, check->name)) {
249 return check->offset * 60;
250 }
251 check++;
252 }
253 return -1;
254 }
255 /* }}} */
256
257 /* char *pretty_key(char *, int, int, int) */
258 char *pretty_key(char *key, int key_len, int uctitle, int xhyphen)
259 {
260 if (key && key_len) {
261 int i, wasalpha;
262 if (wasalpha = isalpha(key[0])) {
263 key[0] = uctitle ? toupper(key[0]) : tolower(key[0]);
264 }
265 for (i = 1; i < key_len; i++) {
266 if (isalpha(key[i])) {
267 key[i] = ((!wasalpha) && uctitle) ? toupper(key[i]) : tolower(key[i]);
268 wasalpha = 1;
269 } else {
270 if (xhyphen && (key[i] == '_')) {
271 key[i] = '-';
272 }
273 wasalpha = 0;
274 }
275 }
276 }
277 return key;
278 }
279 /* }}} */
280
281 /* {{{ static STATUS http_ob_stack_get(php_ob_buffer *, php_ob_buffer **) */
282 static STATUS http_ob_stack_get(php_ob_buffer *o, php_ob_buffer **s)
283 {
284 static int i = 0;
285 php_ob_buffer *b = emalloc(sizeof(php_ob_buffer));
286 b->handler_name = estrdup(o->handler_name);
287 b->buffer = estrndup(o->buffer, o->text_length);
288 b->text_length = o->text_length;
289 b->chunk_size = o->chunk_size;
290 b->erase = o->erase;
291 s[i++] = b;
292 return SUCCESS;
293 }
294 /* }}} */
295
296 /* }}} internals */
297
298 /* {{{ public API */
299
300 /* {{{ char *http_date(time_t) */
301 PHP_HTTP_API char *_http_date(time_t t TSRMLS_DC)
302 {
303 struct tm *gmtime, tmbuf;
304
305 if (gmtime = php_gmtime_r(&t, &tmbuf)) {
306 char *date = ecalloc(1, 31);
307 snprintf(date, 30,
308 "%s, %02d %s %04d %02d:%02d:%02d GMT",
309 days[gmtime->tm_wday], gmtime->tm_mday,
310 months[gmtime->tm_mon], gmtime->tm_year + 1900,
311 gmtime->tm_hour, gmtime->tm_min, gmtime->tm_sec
312 );
313 return date;
314 }
315
316 return NULL;
317 }
318 /* }}} */
319
320 /* {{{ time_t http_parse_date(char *)
321 Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
322 PHP_HTTP_API time_t _http_parse_date(const char *date)
323 {
324 time_t t = 0;
325 int tz_offset = -1, year = -1, month = -1, monthday = -1, weekday = -1,
326 hours = -1, minutes = -1, seconds = -1;
327 struct tm tm;
328 enum assume_next dignext = DATE_MDAY;
329 const char *indate = date;
330
331 int found = 0, part = 0; /* max 6 parts */
332
333 while (*date && (part < 6)) {
334 int found = 0;
335
336 while (*date && !isalnum(*date)) {
337 date++;
338 }
339
340 if (isalpha(*date)) {
341 /* a name coming up */
342 char buf[32] = "";
343 size_t len;
344 sscanf(date, "%31[A-Za-z]", buf);
345 len = strlen(buf);
346
347 if (weekday == -1) {
348 weekday = check_day(buf, len);
349 if (weekday != -1) {
350 found = 1;
351 }
352 }
353
354 if (!found && (month == -1)) {
355 month = check_month(buf);
356 if (month != -1) {
357 found = 1;
358 }
359 }
360
361 if (!found && (tz_offset == -1)) {
362 /* this just must be a time zone string */
363 tz_offset = check_tzone(buf);
364 if (tz_offset != -1) {
365 found = 1;
366 }
367 }
368
369 if (!found) {
370 return -1; /* bad string */
371 }
372 date += len;
373 }
374 else if (isdigit(*date)) {
375 /* a digit */
376 int val;
377 char *end;
378 if ((seconds == -1) &&
379 (3 == sscanf(date, "%02d:%02d:%02d", &hours, &minutes, &seconds))) {
380 /* time stamp! */
381 date += 8;
382 found = 1;
383 }
384 else {
385 val = (int) strtol(date, &end, 10);
386
387 if ((tz_offset == -1) && ((end - date) == 4) && (val < 1300) &&
388 (indate < date) && ((date[-1] == '+' || date[-1] == '-'))) {
389 /* four digits and a value less than 1300 and it is preceeded with
390 a plus or minus. This is a time zone indication. */
391 found = 1;
392 tz_offset = (val / 100 * 60 + val % 100) * 60;
393
394 /* the + and - prefix indicates the local time compared to GMT,
395 this we need ther reversed math to get what we want */
396 tz_offset = date[-1] == '+' ? -tz_offset : tz_offset;
397 }
398
399 if (((end - date) == 8) && (year == -1) && (month == -1) && (monthday == -1)) {
400 /* 8 digits, no year, month or day yet. This is YYYYMMDD */
401 found = 1;
402 year = val / 10000;
403 month = (val % 10000) / 100 - 1; /* month is 0 - 11 */
404 monthday = val % 100;
405 }
406
407 if (!found && (dignext == DATE_MDAY) && (monthday == -1)) {
408 if ((val > 0) && (val < 32)) {
409 monthday = val;
410 found = 1;
411 }
412 dignext = DATE_YEAR;
413 }
414
415 if (!found && (dignext == DATE_YEAR) && (year == -1)) {
416 year = val;
417 found = 1;
418 if (year < 1900) {
419 year += year > 70 ? 1900 : 2000;
420 }
421 if(monthday == -1) {
422 dignext = DATE_MDAY;
423 }
424 }
425
426 if (!found) {
427 return -1;
428 }
429
430 date = end;
431 }
432 }
433
434 part++;
435 }
436
437 if (-1 == seconds) {
438 seconds = minutes = hours = 0; /* no time, make it zero */
439 }
440
441 if ((-1 == monthday) || (-1 == month) || (-1 == year)) {
442 /* lacks vital info, fail */
443 return -1;
444 }
445
446 if (sizeof(time_t) < 5) {
447 /* 32 bit time_t can only hold dates to the beginning of 2038 */
448 if (year > 2037) {
449 return 0x7fffffff;
450 }
451 }
452
453 tm.tm_sec = seconds;
454 tm.tm_min = minutes;
455 tm.tm_hour = hours;
456 tm.tm_mday = monthday;
457 tm.tm_mon = month;
458 tm.tm_year = year - 1900;
459 tm.tm_wday = 0;
460 tm.tm_yday = 0;
461 tm.tm_isdst = 0;
462
463 t = mktime(&tm);
464
465 /* time zone adjust */
466 {
467 struct tm *gmt, keeptime2;
468 long delta;
469 time_t t2;
470
471 if(!(gmt = php_gmtime_r(&t, &keeptime2))) {
472 return -1; /* illegal date/time */
473 }
474
475 t2 = mktime(gmt);
476
477 /* Add the time zone diff (between the given timezone and GMT) and the
478 diff between the local time zone and GMT. */
479 delta = (tz_offset != -1 ? tz_offset : 0) + (t - t2);
480
481 if((delta > 0) && (t + delta < t)) {
482 return -1; /* time_t overflow */
483 }
484
485 t += delta;
486 }
487
488 return t;
489 }
490 /* }}} */
491
492 /* {{{ char *http_etag(void *, size_t, http_send_mode) */
493 PHP_HTTP_API char *_http_etag(const void *data_ptr, const size_t data_len,
494 const http_send_mode data_mode TSRMLS_DC)
495 {
496 char ssb_buf[128] = {0};
497 unsigned char digest[16];
498 PHP_MD5_CTX ctx;
499 char *new_etag = ecalloc(1, 33);
500
501 PHP_MD5Init(&ctx);
502
503 switch (data_mode)
504 {
505 case SEND_DATA:
506 PHP_MD5Update(&ctx, data_ptr, data_len);
507 break;
508
509 case SEND_RSRC:
510 if (!HTTP_G(ssb).sb.st_ino) {
511 if (php_stream_stat((php_stream *) data_ptr, &HTTP_G(ssb))) {
512 return NULL;
513 }
514 }
515 snprintf(ssb_buf, 127, "%ld=%ld=%ld",
516 HTTP_G(ssb).sb.st_mtime,
517 HTTP_G(ssb).sb.st_ino,
518 HTTP_G(ssb).sb.st_size
519 );
520 PHP_MD5Update(&ctx, ssb_buf, strlen(ssb_buf));
521 break;
522
523 default:
524 efree(new_etag);
525 return NULL;
526 break;
527 }
528
529 PHP_MD5Final(digest, &ctx);
530 make_digest(new_etag, digest);
531
532 return new_etag;
533 }
534 /* }}} */
535
536 /* {{{ time_t http_lmod(void *, http_send_mode) */
537 PHP_HTTP_API time_t _http_lmod(const void *data_ptr, const http_send_mode data_mode TSRMLS_DC)
538 {
539 switch (data_mode)
540 {
541 case SEND_DATA:
542 {
543 return time(NULL);
544 }
545
546 case SEND_RSRC:
547 {
548 if (!HTTP_G(ssb).sb.st_mtime) {
549 if (php_stream_stat((php_stream *) data_ptr, &HTTP_G(ssb))) {
550 return 0;
551 }
552 }
553 return HTTP_G(ssb).sb.st_mtime;
554 }
555
556 default:
557 {
558 if (!HTTP_G(ssb).sb.st_mtime) {
559 if(php_stream_stat_path(Z_STRVAL_P((zval *) data_ptr), &HTTP_G(ssb))) {
560 return 0;
561 }
562 }
563 return HTTP_G(ssb).sb.st_mtime;
564 }
565 }
566 }
567 /* }}} */
568
569 /* {{{ STATUS http_send_status_header(int, char *) */
570 PHP_HTTP_API STATUS _http_send_status_header(const int status, const char *header TSRMLS_DC)
571 {
572 sapi_header_line h = {(char *) header, strlen(header), status};
573 return sapi_header_op(SAPI_HEADER_REPLACE, &h TSRMLS_CC);
574 }
575 /* }}} */
576
577 /* {{{ zval *http_get_server_var(char *) */
578 PHP_HTTP_API zval *_http_get_server_var(const char *key TSRMLS_DC)
579 {
580 zval **var;
581 if (SUCCESS == zend_hash_find(
582 HTTP_SERVER_VARS,
583 (char *) key, strlen(key) + 1, (void **) &var)) {
584 return *var;
585 }
586 return NULL;
587 }
588 /* }}} */
589
590 /* {{{ void http_ob_etaghandler(char *, uint, char **, uint *, int) */
591 PHP_HTTP_API void _http_ob_etaghandler(char *output, uint output_len,
592 char **handled_output, uint *handled_output_len, int mode TSRMLS_DC)
593 {
594 char etag[33] = { 0 };
595 unsigned char digest[16];
596
597 if (mode & PHP_OUTPUT_HANDLER_START) {
598 PHP_MD5Init(&HTTP_G(etag_md5));
599 }
600
601 PHP_MD5Update(&HTTP_G(etag_md5), output, output_len);
602
603 if (mode & PHP_OUTPUT_HANDLER_END) {
604 PHP_MD5Final(digest, &HTTP_G(etag_md5));
605
606 /* just do that if desired */
607 if (HTTP_G(etag_started)) {
608 make_digest(etag, digest);
609
610 if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
611 http_send_status(304);
612 } else {
613 http_send_etag(etag, 32);
614 }
615 }
616 }
617
618 *handled_output_len = output_len;
619 *handled_output = estrndup(output, output_len);
620 }
621 /* }}} */
622
623 /* {{{ STATUS http_start_ob_handler(php_output_handler_func_t, char *, uint, zend_bool) */
624 PHP_HTTP_API STATUS _http_start_ob_handler(php_output_handler_func_t handler_func,
625 char *handler_name, uint chunk_size, zend_bool erase TSRMLS_DC)
626 {
627 php_ob_buffer **stack;
628 int count, i;
629
630 if (count = OG(ob_nesting_level)) {
631 stack = ecalloc(count, sizeof(php_ob_buffer *));
632
633 if (count > 1) {
634 zend_stack_apply_with_argument(&OG(ob_buffers), ZEND_STACK_APPLY_BOTTOMUP,
635 (int (*)(void *elem, void *)) http_ob_stack_get, stack);
636 }
637
638 if (count > 0) {
639 http_ob_stack_get(&OG(active_ob_buffer), stack);
640 }
641
642 while (OG(ob_nesting_level)) {
643 php_end_ob_buffer(0, 0 TSRMLS_CC);
644 }
645 }
646
647 php_ob_set_internal_handler(handler_func, chunk_size, handler_name, erase TSRMLS_CC);
648
649 for (i = 0; i < count; i++) {
650 php_ob_buffer *s = stack[i];
651 if (strcmp(s->handler_name, "default output handler")) {
652 php_start_ob_buffer_named(s->handler_name, s->chunk_size, s->erase TSRMLS_CC);
653 }
654 php_body_write(s->buffer, s->text_length TSRMLS_CC);
655 efree(s->handler_name);
656 efree(s->buffer);
657 efree(s);
658 }
659 if (count) {
660 efree(stack);
661 }
662
663 return SUCCESS;
664 }
665 /* }}} */
666
667 /* {{{ int http_modified_match(char *, int) */
668 PHP_HTTP_API int _http_modified_match(const char *entry, const time_t t TSRMLS_DC)
669 {
670 int retval;
671 zval *zmodified;
672 char *modified, *chr_ptr;
673
674 HTTP_GSC(zmodified, entry, 0);
675
676 modified = estrndup(Z_STRVAL_P(zmodified), Z_STRLEN_P(zmodified));
677 if (chr_ptr = strrchr(modified, ';')) {
678 chr_ptr = 0;
679 }
680 retval = (t <= http_parse_date(modified));
681 efree(modified);
682 return retval;
683 }
684 /* }}} */
685
686 /* {{{ int http_etag_match(char *, char *) */
687 PHP_HTTP_API int _http_etag_match(const char *entry, const char *etag TSRMLS_DC)
688 {
689 zval *zetag;
690 char *quoted_etag;
691 STATUS result;
692
693 HTTP_GSC(zetag, entry, 0);
694
695 if (NULL != strchr(Z_STRVAL_P(zetag), '*')) {
696 return 1;
697 }
698
699 quoted_etag = (char *) emalloc(strlen(etag) + 3);
700 sprintf(quoted_etag, "\"%s\"", etag);
701
702 if (!strchr(Z_STRVAL_P(zetag), ',')) {
703 result = !strcmp(Z_STRVAL_P(zetag), quoted_etag);
704 } else {
705 result = (NULL != strstr(Z_STRVAL_P(zetag), quoted_etag));
706 }
707 efree(quoted_etag);
708 return result;
709 }
710 /* }}} */
711
712 /* {{{ STATUS http_send_last_modified(int) */
713 PHP_HTTP_API STATUS _http_send_last_modified(const time_t t TSRMLS_DC)
714 {
715 char *date = NULL;
716 if (date = http_date(t)) {
717 char modified[96] = "Last-Modified: ";
718 strcat(modified, date);
719 efree(date);
720
721 /* remember */
722 HTTP_G(lmod) = t;
723
724 return http_send_header(modified);
725 }
726 return FAILURE;
727 }
728 /* }}} */
729
730 /* {{{ static STATUS http_send_etag(char *, int) */
731 PHP_HTTP_API STATUS _http_send_etag(const char *etag,
732 const int etag_len TSRMLS_DC)
733 {
734 STATUS status;
735 char *etag_header;
736
737 if (!etag_len){
738 php_error_docref(NULL TSRMLS_CC,E_ERROR,
739 "Attempt to send empty ETag (previous: %s)\n", HTTP_G(etag));
740 return FAILURE;
741 }
742
743 /* remember */
744 if (HTTP_G(etag)) {
745 efree(HTTP_G(etag));
746 }
747 HTTP_G(etag) = estrdup(etag);
748
749 etag_header = ecalloc(1, sizeof("ETag: \"\"") + etag_len);
750 sprintf(etag_header, "ETag: \"%s\"", etag);
751 if (SUCCESS != (status = http_send_header(etag_header))) {
752 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't send '%s' header", etag_header);
753 }
754 efree(etag_header);
755 return status;
756 }
757 /* }}} */
758
759 /* {{{ STATUS http_send_cache_control(char *, size_t) */
760 PHP_HTTP_API STATUS _http_send_cache_control(const char *cache_control,
761 const size_t cc_len TSRMLS_DC)
762 {
763 STATUS status;
764 char *cc_header = ecalloc(1, sizeof("Cache-Control: ") + cc_len);
765
766 sprintf(cc_header, "Cache-Control: %s", cache_control);
767 if (SUCCESS != (status = http_send_header(cc_header))) {
768 php_error_docref(NULL TSRMLS_CC, E_NOTICE,
769 "Could not send '%s' header", cc_header);
770 }
771 efree(cc_header);
772 return status;
773 }
774 /* }}} */
775
776 /* {{{ STATUS http_send_content_type(char *, size_t) */
777 PHP_HTTP_API STATUS _http_send_content_type(const char *content_type,
778 const size_t ct_len TSRMLS_DC)
779 {
780 STATUS status;
781 char *ct_header;
782
783 if (!strchr(content_type, '/')) {
784 php_error_docref(NULL TSRMLS_CC, E_WARNING,
785 "Content-Type '%s' doesn't seem to consist of a primary and a secondary part",
786 content_type);
787 return FAILURE;
788 }
789
790 /* remember for multiple ranges */
791 if (HTTP_G(ctype)) {
792 efree(HTTP_G(ctype));
793 }
794 HTTP_G(ctype) = estrndup(content_type, ct_len);
795
796 ct_header = ecalloc(1, sizeof("Content-Type: ") + ct_len);
797 sprintf(ct_header, "Content-Type: %s", content_type);
798
799 if (SUCCESS != (status = http_send_header(ct_header))) {
800 php_error_docref(NULL TSRMLS_CC, E_WARNING,
801 "Couldn't send '%s' header", ct_header);
802 }
803 efree(ct_header);
804 return status;
805 }
806 /* }}} */
807
808 /* {{{ STATUS http_send_content_disposition(char *, size_t, zend_bool) */
809 PHP_HTTP_API STATUS _http_send_content_disposition(const char *filename,
810 const size_t f_len, const int send_inline TSRMLS_DC)
811 {
812 STATUS status;
813 char *cd_header;
814
815 if (send_inline) {
816 cd_header = ecalloc(1, sizeof("Content-Disposition: inline; filename=\"\"") + f_len);
817 sprintf(cd_header, "Content-Disposition: inline; filename=\"%s\"", filename);
818 } else {
819 cd_header = ecalloc(1, sizeof("Content-Disposition: attachment; filename=\"\"") + f_len);
820 sprintf(cd_header, "Content-Disposition: attachment; filename=\"%s\"", filename);
821 }
822
823 if (SUCCESS != (status = http_send_header(cd_header))) {
824 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't send '%s' header", cd_header);
825 }
826 efree(cd_header);
827 return status;
828 }
829 /* }}} */
830
831 /* {{{ STATUS http_cache_last_modified(time_t, time_t, char *, size_t) */
832 PHP_HTTP_API STATUS _http_cache_last_modified(const time_t last_modified,
833 const time_t send_modified, const char *cache_control, const size_t cc_len TSRMLS_DC)
834 {
835 if (cc_len) {
836 http_send_cache_control(cache_control, cc_len);
837 }
838
839 if (http_modified_match("HTTP_IF_MODIFIED_SINCE", last_modified)) {
840 if (SUCCESS == http_send_status(304)) {
841 zend_bailout();
842 } else {
843 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified");
844 return FAILURE;
845 }
846 }
847 return http_send_last_modified(send_modified);
848 }
849 /* }}} */
850
851 /* {{{ STATUS http_cache_etag(char *, size_t, char *, size_t) */
852 PHP_HTTP_API STATUS _http_cache_etag(const char *etag, const size_t etag_len,
853 const char *cache_control, const size_t cc_len TSRMLS_DC)
854 {
855 if (cc_len) {
856 http_send_cache_control(cache_control, cc_len);
857 }
858
859 if (etag_len) {
860 http_send_etag(etag, etag_len);
861 if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
862 if (SUCCESS == http_send_status(304)) {
863 zend_bailout();
864 } else {
865 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified");
866 return FAILURE;
867 }
868 }
869 }
870
871 /* if no etag is given and we didn't already start ob_etaghandler -- start it */
872 if (!HTTP_G(etag_started)) {
873 if (SUCCESS == http_start_ob_handler(_http_ob_etaghandler, "ob_etaghandler", 4096, 1)) {
874 HTTP_G(etag_started) = 1;
875 return SUCCESS;
876 } else {
877 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not start ob_etaghandler");
878 return FAILURE;
879 }
880 }
881 return SUCCESS;
882 }
883 /* }}} */
884
885 /* {{{ char *http_absolute_uri(char *) */
886 PHP_HTTP_API char *_http_absolute_uri_ex(
887 const char *url, size_t url_len,
888 const char *proto, size_t proto_len,
889 const char *host, size_t host_len,
890 unsigned port TSRMLS_DC)
891 {
892 #if defined(PHP_WIN32) || defined(HAVE_NETDB_H)
893 struct servent *se;
894 #endif
895 php_url *purl, furl = {NULL};
896 size_t full_len = 0;
897 zval *zhost = NULL;
898 char *scheme = NULL, *URL = ecalloc(1, HTTP_URI_MAXLEN + 1);
899
900 if ((!url || !url_len) && (
901 (!(url = SG(request_info).request_uri)) ||
902 (!(url_len = strlen(SG(request_info).request_uri))))) {
903 php_error_docref(NULL TSRMLS_CC, E_WARNING,
904 "Cannot build an absolute URI if supplied URL and REQUEST_URI is empty");
905 return NULL;
906 }
907
908 if (!(purl = php_url_parse(url))) {
909 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse supplied URL");
910 return NULL;
911 }
912
913 furl.user = purl->user;
914 furl.pass = purl->pass;
915 furl.path = purl->path;
916 furl.query = purl->query;
917 furl.fragment = purl->fragment;
918
919 if (proto) {
920 furl.scheme = scheme = estrdup(proto);
921 } else if (purl->scheme) {
922 furl.scheme = purl->scheme;
923 #if defined(PHP_WIN32) || defined(HAVE_NETDB_H)
924 } else if (port && (se = getservbyport(port, "tcp"))) {
925 furl.scheme = (scheme = estrdup(se->s_name));
926 #endif
927 } else {
928 furl.scheme = "http";
929 }
930
931 if (port) {
932 furl.port = port;
933 } else if (purl->port) {
934 furl.port = purl->port;
935 } else if (strncmp(furl.scheme, "http", 4)) {
936 #if defined(PHP_WIN32) || defined(HAVE_NETDB_H)
937 if (se = getservbyname(furl.scheme, "tcp")) {
938 furl.port = se->s_port;
939 } else
940 #endif
941 furl.port = 80;
942 } else {
943 furl.port = (furl.scheme[5] == 's') ? 443 : 80;
944 }
945
946 if (host) {
947 furl.host = (char *) host;
948 } else if (purl->host) {
949 furl.host = purl->host;
950 } else if ( (zhost = http_get_server_var("HTTP_HOST")) ||
951 (zhost = http_get_server_var("SERVER_NAME"))) {
952 furl.host = Z_STRVAL_P(zhost);
953 } else {
954 furl.host = "localhost";
955 }
956
957 #define HTTP_URI_STRLCATS(URL, full_len, add_string) HTTP_URI_STRLCAT(URL, full_len, add_string, sizeof(add_string)-1)
958 #define HTTP_URI_STRLCATL(URL, full_len, add_string) HTTP_URI_STRLCAT(URL, full_len, add_string, strlen(add_string))
959 #define HTTP_URI_STRLCAT(URL, full_len, add_string, add_len) \
960 if ((full_len += add_len) > HTTP_URI_MAXLEN) { \
961 php_error_docref(NULL TSRMLS_CC, E_NOTICE, \
962 "Absolute URI would have exceeded max URI length (%d bytes) - " \
963 "tried to add %d bytes ('%s')", \
964 HTTP_URI_MAXLEN, add_len, add_string); \
965 if (scheme) { \
966 efree(scheme); \
967 } \
968 php_url_free(purl); \
969 return URL; \
970 } else { \
971 strcat(URL, add_string); \
972 }
973
974 HTTP_URI_STRLCATL(URL, full_len, furl.scheme);
975 HTTP_URI_STRLCATS(URL, full_len, "://");
976
977 if (furl.user) {
978 HTTP_URI_STRLCATL(URL, full_len, furl.user);
979 if (furl.pass) {
980 HTTP_URI_STRLCATS(URL, full_len, ":");
981 HTTP_URI_STRLCATL(URL, full_len, furl.pass);
982 }
983 HTTP_URI_STRLCATS(URL, full_len, "@");
984 }
985
986 HTTP_URI_STRLCATL(URL, full_len, furl.host);
987
988 if ( (!strcmp(furl.scheme, "http") && (furl.port != 80)) ||
989 (!strcmp(furl.scheme, "https") && (furl.port != 443))) {
990 char port_string[8] = {0};
991 snprintf(port_string, 7, ":%u", furl.port);
992 HTTP_URI_STRLCATL(URL, full_len, port_string);
993 }
994
995 if (furl.path) {
996 if (furl.path[0] != '/') {
997 HTTP_URI_STRLCATS(URL, full_len, "/");
998 }
999 HTTP_URI_STRLCATL(URL, full_len, furl.path);
1000 } else {
1001 HTTP_URI_STRLCATS(URL, full_len, "/");
1002 }
1003
1004 if (furl.query) {
1005 HTTP_URI_STRLCATS(URL, full_len, "?");
1006 HTTP_URI_STRLCATL(URL, full_len, furl.query);
1007 }
1008
1009 if (furl.fragment) {
1010 HTTP_URI_STRLCATS(URL, full_len, "#");
1011 HTTP_URI_STRLCATL(URL, full_len, furl.fragment);
1012 }
1013
1014 if (scheme) {
1015 efree(scheme);
1016 }
1017 php_url_free(purl);
1018
1019 return URL;
1020 }
1021 /* }}} */
1022
1023 /* {{{ char *http_negotiate_q(char *, zval *, char *, hash_entry_type) */
1024 PHP_HTTP_API char *_http_negotiate_q(const char *entry, const zval *supported,
1025 const char *def TSRMLS_DC)
1026 {
1027 zval *zaccept, *zarray, *zdelim, **zentry, *zentries, **zsupp;
1028 char *q_ptr, *result;
1029 int i, c;
1030 double qual;
1031
1032 HTTP_GSC(zaccept, entry, estrdup(def));
1033
1034 MAKE_STD_ZVAL(zarray);
1035 array_init(zarray);
1036
1037 MAKE_STD_ZVAL(zdelim);
1038 ZVAL_STRING(zdelim, ",", 0);
1039 php_explode(zdelim, zaccept, zarray, -1);
1040 efree(zdelim);
1041
1042 MAKE_STD_ZVAL(zentries);
1043 array_init(zentries);
1044
1045 c = zend_hash_num_elements(Z_ARRVAL_P(zarray));
1046 for (i = 0; i < c; i++, zend_hash_move_forward(Z_ARRVAL_P(zarray))) {
1047
1048 if (SUCCESS != zend_hash_get_current_data(
1049 Z_ARRVAL_P(zarray), (void **) &zentry)) {
1050 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1051 "Cannot parse %s header: %s", entry, Z_STRVAL_P(zaccept));
1052 break;
1053 }
1054
1055 /* check for qualifier */
1056 if (NULL != (q_ptr = strrchr(Z_STRVAL_PP(zentry), ';'))) {
1057 qual = strtod(q_ptr + 3, NULL);
1058 } else {
1059 qual = 1000.0 - i;
1060 }
1061
1062 /* walk through the supported array */
1063 FOREACH_VAL(supported, zsupp) {
1064 if (!strcasecmp(Z_STRVAL_PP(zsupp), Z_STRVAL_PP(zentry))) {
1065 add_assoc_double(zentries, Z_STRVAL_PP(zsupp), qual);
1066 break;
1067 }
1068 }
1069 }
1070
1071 zval_dtor(zarray);
1072 efree(zarray);
1073
1074 zend_hash_internal_pointer_reset(Z_ARRVAL_P(zentries));
1075
1076 if ( (SUCCESS != zend_hash_sort(Z_ARRVAL_P(zentries), zend_qsort,
1077 http_sort_q, 0 TSRMLS_CC)) ||
1078 (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key(
1079 Z_ARRVAL_P(zentries), &result, 0, 1))) {
1080 result = estrdup(def);
1081 }
1082
1083 zval_dtor(zentries);
1084 efree(zentries);
1085
1086 return result;
1087 }
1088 /* }}} */
1089
1090 /* {{{ http_range_status http_get_request_ranges(HashTable *ranges, size_t) */
1091 PHP_HTTP_API http_range_status _http_get_request_ranges(HashTable *ranges,
1092 const size_t length TSRMLS_DC)
1093 {
1094 zval *zrange;
1095 char *range, c;
1096 long begin = -1, end = -1, *ptr;
1097
1098 HTTP_GSC(zrange, "HTTP_RANGE", RANGE_NO);
1099 range = Z_STRVAL_P(zrange);
1100
1101 if (strncmp(range, "bytes=", sizeof("bytes=") - 1)) {
1102 php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Range header misses bytes=");
1103 return RANGE_NO;
1104 }
1105
1106 ptr = &begin;
1107 range += sizeof("bytes=") - 1;
1108
1109 do {
1110 switch (c = *(range++))
1111 {
1112 case '0':
1113 *ptr *= 10;
1114 break;
1115
1116 case '1': case '2': case '3':
1117 case '4': case '5': case '6':
1118 case '7': case '8': case '9':
1119 /*
1120 * If the value of the pointer is already set (non-negative)
1121 * then multiply its value by ten and add the current value,
1122 * else initialise the pointers value with the current value
1123 * --
1124 * This let us recognize empty fields when validating the
1125 * ranges, i.e. a "-10" for begin and "12345" for the end
1126 * was the following range request: "Range: bytes=0-12345";
1127 * While a "-1" for begin and "12345" for the end would
1128 * have been: "Range: bytes=-12345".
1129 */
1130 if (*ptr > 0) {
1131 *ptr *= 10;
1132 *ptr += c - '0';
1133 } else {
1134 *ptr = c - '0';
1135 }
1136 break;
1137
1138 case '-':
1139 ptr = &end;
1140 break;
1141
1142 case ' ':
1143 /* IE - ignore for now */
1144 break;
1145
1146 case 0:
1147 case ',':
1148
1149 if (length) {
1150 /* validate ranges */
1151 switch (begin)
1152 {
1153 /* "0-12345" */
1154 case -10:
1155 if ((length - end) < 1) {
1156 return RANGE_ERR;
1157 }
1158 begin = 0;
1159 break;
1160
1161 /* "-12345" */
1162 case -1:
1163 if ((length - end) < 1) {
1164 return RANGE_ERR;
1165 }
1166 begin = length - end;
1167 end = length;
1168 break;
1169
1170 /* "12345-(xxx)" */
1171 default:
1172 switch (end)
1173 {
1174 /* "12345-" */
1175 case -1:
1176 if ((length - begin) < 1) {
1177 return RANGE_ERR;
1178 }
1179 end = length - 1;
1180 break;
1181
1182 /* "12345-67890" */
1183 default:
1184 if ( ((length - begin) < 1) ||
1185 ((length - end) < 1) ||
1186 ((begin - end) >= 0)) {
1187 return RANGE_ERR;
1188 }
1189 break;
1190 }
1191 break;
1192 }
1193 }
1194 {
1195 zval *zentry;
1196 MAKE_STD_ZVAL(zentry);
1197 array_init(zentry);
1198 add_index_long(zentry, 0, begin);
1199 add_index_long(zentry, 1, end);
1200 zend_hash_next_index_insert(ranges, &zentry, sizeof(zval *), NULL);
1201
1202 begin = -1;
1203 end = -1;
1204 ptr = &begin;
1205 }
1206 break;
1207
1208 default:
1209 return RANGE_NO;
1210 break;
1211 }
1212 } while (c != 0);
1213
1214 return RANGE_OK;
1215 }
1216 /* }}} */
1217
1218 /* {{{ STATUS http_send_ranges(HashTable *, void *, size_t, http_send_mode) */
1219 PHP_HTTP_API STATUS _http_send_ranges(HashTable *ranges, const void *data, const size_t size, const http_send_mode mode TSRMLS_DC)
1220 {
1221 long **begin, **end;
1222 zval **zrange;
1223
1224 /* single range */
1225 if (zend_hash_num_elements(ranges) == 1) {
1226 char range_header[256] = {0};
1227
1228 if (SUCCESS != zend_hash_index_find(ranges, 0, (void **) &zrange) ||
1229 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
1230 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
1231 return FAILURE;
1232 }
1233
1234 /* Send HTTP 206 Partial Content */
1235 http_send_status(206);
1236
1237 /* send content range header */
1238 snprintf(range_header, 255, "Content-Range: bytes %d-%d/%d", **begin, **end, size);
1239 http_send_header(range_header);
1240
1241 /* send requested chunk */
1242 return http_send_chunk(data, **begin, **end + 1, mode);
1243 }
1244
1245 /* multi range */
1246 else {
1247 char bound[23] = {0}, preface[1024] = {0},
1248 multi_header[68] = "Content-Type: multipart/byteranges; boundary=";
1249
1250 /* Send HTTP 206 Partial Content */
1251 http_send_status(206);
1252
1253 /* send multipart/byteranges header */
1254 snprintf(bound, 22, "--%d%0.9f", time(NULL), php_combined_lcg(TSRMLS_C));
1255 strncat(multi_header, bound + 2, 21);
1256 http_send_header(multi_header);
1257
1258 /* send each requested chunk */
1259 FOREACH_HASH_VAL(ranges, zrange) {
1260 if (SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
1261 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
1262 break;
1263 }
1264
1265 snprintf(preface, 1023,
1266 HTTP_CRLF "%s"
1267 HTTP_CRLF "Content-Type: %s"
1268 HTTP_CRLF "Content-Range: bytes %ld-%ld/%ld"
1269 HTTP_CRLF
1270 HTTP_CRLF,
1271
1272 bound,
1273 HTTP_G(ctype) ? HTTP_G(ctype) : "application/x-octetstream",
1274 **begin,
1275 **end,
1276 size
1277 );
1278
1279 php_body_write(preface, strlen(preface) TSRMLS_CC);
1280 http_send_chunk(data, **begin, **end + 1, mode);
1281 }
1282
1283 /* write boundary once more */
1284 php_body_write(HTTP_CRLF, sizeof(HTTP_CRLF) - 1 TSRMLS_CC);
1285 php_body_write(bound, strlen(bound) TSRMLS_CC);
1286 php_body_write("--", 2 TSRMLS_CC);
1287
1288 return SUCCESS;
1289 }
1290 }
1291 /* }}} */
1292
1293 /* {{{ STATUS http_send(void *, size_t, http_send_mode) */
1294 PHP_HTTP_API STATUS _http_send(const void *data_ptr, const size_t data_size,
1295 const http_send_mode data_mode TSRMLS_DC)
1296 {
1297 int is_range_request = http_is_range_request();
1298
1299 if (!data_ptr) {
1300 return FAILURE;
1301 }
1302 if (!data_size) {
1303 return SUCCESS;
1304 }
1305
1306 /* etag handling */
1307 if (HTTP_G(etag_started)) {
1308 char *etag;
1309 /* interrupt */
1310 HTTP_G(etag_started) = 0;
1311 /* never ever use the output to compute the ETag if http_send() is used */
1312 php_end_ob_buffer(0, 0 TSRMLS_CC);
1313 if (!(etag = http_etag(data_ptr, data_size, data_mode))) {
1314 return FAILURE;
1315 }
1316
1317 /* send 304 Not Modified if etag matches */
1318 if ((!is_range_request) && http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1319 efree(etag);
1320 return http_send_status(304);
1321 }
1322
1323 http_send_etag(etag, 32);
1324 efree(etag);
1325 }
1326
1327 /* send 304 Not Modified if last-modified matches*/
1328 if ((!is_range_request) && http_modified_match("HTTP_IF_MODIFIED_SINCE", HTTP_G(lmod))) {
1329 return http_send_status(304);
1330 }
1331
1332 if (is_range_request) {
1333
1334 /* only send ranges if entity hasn't changed */
1335 if (
1336 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_MATCH", 13)) ||
1337 http_etag_match("HTTP_IF_MATCH", HTTP_G(etag)))
1338 &&
1339 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_UNMODIFIED_SINCE", 25)) ||
1340 http_modified_match("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(lmod)))
1341 ) {
1342
1343 STATUS result = FAILURE;
1344 HashTable ranges;
1345 zend_hash_init(&ranges, 0, NULL, ZVAL_PTR_DTOR, 0);
1346
1347 switch (http_get_request_ranges(&ranges, data_size))
1348 {
1349 case RANGE_NO:
1350 zend_hash_destroy(&ranges);
1351 /* go ahead and send all */
1352 break;
1353
1354 case RANGE_OK:
1355 result = http_send_ranges(&ranges, data_ptr, data_size, data_mode);
1356 zend_hash_destroy(&ranges);
1357 return result;
1358 break;
1359
1360 case RANGE_ERR:
1361 zend_hash_destroy(&ranges);
1362 http_send_status(416);
1363 return FAILURE;
1364 break;
1365
1366 default:
1367 return FAILURE;
1368 break;
1369 }
1370 }
1371 }
1372 /* send all */
1373 return http_send_chunk(data_ptr, 0, data_size, data_mode);
1374 }
1375 /* }}} */
1376
1377 /* {{{ STATUS http_send_stream(php_stream *) */
1378 PHP_HTTP_API STATUS _http_send_stream_ex(php_stream *file,
1379 zend_bool close_stream TSRMLS_DC)
1380 {
1381 STATUS status;
1382
1383 if ((!file) || php_stream_stat(file, &HTTP_G(ssb))) {
1384 return FAILURE;
1385 }
1386
1387 status = http_send(file, HTTP_G(ssb).sb.st_size, SEND_RSRC);
1388
1389 if (close_stream) {
1390 php_stream_close(file);
1391 }
1392
1393 return status;
1394 }
1395 /* }}} */
1396
1397 /* {{{ STATUS http_chunked_decode(char *, size_t, char **, size_t *) */
1398 PHP_HTTP_API STATUS _http_chunked_decode(const char *encoded,
1399 const size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
1400 {
1401 const char *e_ptr;
1402 char *d_ptr;
1403
1404 *decoded_len = 0;
1405 *decoded = ecalloc(1, encoded_len);
1406 d_ptr = *decoded;
1407 e_ptr = encoded;
1408
1409 while (((e_ptr - encoded) - encoded_len) > 0) {
1410 char hex_len[9] = {0};
1411 size_t chunk_len = 0;
1412 int i = 0;
1413
1414 /* read in chunk size */
1415 while (isxdigit(*e_ptr)) {
1416 if (i == 9) {
1417 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1418 "Chunk size is too long: 0x%s...", hex_len);
1419 efree(*decoded);
1420 return FAILURE;
1421 }
1422 hex_len[i++] = *e_ptr++;
1423 }
1424
1425 /* reached the end */
1426 if (!strcmp(hex_len, "0")) {
1427 break;
1428 }
1429
1430 /* new line */
1431 if (strncmp(e_ptr, HTTP_CRLF, 2)) {
1432 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1433 "Invalid character (expected 0x0D 0x0A; got: %x %x)",
1434 *e_ptr, *(e_ptr + 1));
1435 efree(*decoded);
1436 return FAILURE;
1437 }
1438
1439 /* hex to long */
1440 {
1441 char *error = NULL;
1442 chunk_len = strtol(hex_len, &error, 16);
1443 if (error == hex_len) {
1444 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1445 "Invalid chunk size string: '%s'", hex_len);
1446 efree(*decoded);
1447 return FAILURE;
1448 }
1449 }
1450
1451 memcpy(d_ptr, e_ptr += 2, chunk_len);
1452 d_ptr += chunk_len;
1453 e_ptr += chunk_len + 2;
1454 *decoded_len += chunk_len;
1455 }
1456
1457 return SUCCESS;
1458 }
1459 /* }}} */
1460
1461 /* {{{ STATUS http_split_response(zval *, zval *, zval *) */
1462 PHP_HTTP_API STATUS _http_split_response(zval *response, zval *headers, zval *body TSRMLS_DC)
1463 {
1464 char *b = NULL;
1465 size_t l = 0;
1466 STATUS status = http_split_response_ex(Z_STRVAL_P(response), Z_STRLEN_P(response), Z_ARRVAL_P(headers), &b, &l);
1467 ZVAL_STRINGL(body, b, l, 0);
1468 return status;
1469 }
1470 /* }}} */
1471
1472 /* {{{ STATUS http_split_response(char *, size_t, HashTable *, char **, size_t *) */
1473 PHP_HTTP_API STATUS _http_split_response_ex(char *response,
1474 size_t response_len, HashTable *headers, char **body, size_t *body_len TSRMLS_DC)
1475 {
1476 char *header = response, *real_body = NULL;
1477
1478 while (0 < (response_len - (response - header + 4))) {
1479 if ( (*response++ == '\r') &&
1480 (*response++ == '\n') &&
1481 (*response++ == '\r') &&
1482 (*response++ == '\n')) {
1483 real_body = response;
1484 break;
1485 }
1486 }
1487
1488 if (real_body && (*body_len = (response_len - (real_body - header)))) {
1489 *body = ecalloc(1, *body_len + 1);
1490 memcpy(*body, real_body, *body_len);
1491 }
1492
1493 return http_parse_headers_ex(header, real_body ? response_len - *body_len : response_len, headers, 1);
1494 }
1495 /* }}} */
1496
1497 /* {{{ STATUS http_parse_headers(char *, long, zval *) */
1498 PHP_HTTP_API STATUS _http_parse_headers_ex(char *header, int header_len,
1499 HashTable *headers, zend_bool prettify TSRMLS_DC)
1500 {
1501 char *colon = NULL, *line = NULL, *begin = header;
1502 zval array;
1503
1504 Z_ARRVAL(array) = headers;
1505
1506 if (header_len < 2) {
1507 return FAILURE;
1508 }
1509
1510 /* status code */
1511 if (!strncmp(header, "HTTP/1.", 7)) {
1512 char *end = strstr(header, HTTP_CRLF);
1513 add_assoc_stringl(&array, "Status",
1514 header + sizeof("HTTP/1.x ") - 1,
1515 end - (header + sizeof("HTTP/1.x ") - 1), 1);
1516 header = end + 2;
1517 }
1518
1519 line = header;
1520
1521 while (header_len >= (line - begin)) {
1522 int value_len = 0;
1523
1524 switch (*line++)
1525 {
1526 case 0:
1527 --value_len; /* we don't have CR so value length is one char less */
1528 case '\n':
1529 if (colon && ((!(*line - 1)) || ((*line != ' ') && (*line != '\t')))) {
1530
1531 /* skip empty key */
1532 if (header != colon) {
1533 char *key = estrndup(header, colon - header);
1534
1535 if (prettify) {
1536 key = pretty_key(key, colon - header, 1, 1);
1537 }
1538
1539 value_len += line - colon - 1;
1540
1541 /* skip leading ws */
1542 while (isspace(*(++colon))) --value_len;
1543 /* skip trailing ws */
1544 while (isspace(colon[value_len - 1])) --value_len;
1545
1546 if (value_len < 1) {
1547 /* hm, empty header? */
1548 add_assoc_stringl(&array, key, "", 0, 1);
1549 } else {
1550 add_assoc_stringl(&array, key, colon, value_len, 1);
1551 }
1552 efree(key);
1553 }
1554
1555 colon = NULL;
1556 value_len = 0;
1557 header += line - header;
1558 }
1559 break;
1560
1561 case ':':
1562 if (!colon) {
1563 colon = line - 1;
1564 }
1565 break;
1566 }
1567 }
1568 return SUCCESS;
1569 }
1570 /* }}} */
1571
1572 /* {{{ void http_get_request_headers_ex(HashTable *, zend_bool) */
1573 PHP_HTTP_API void _http_get_request_headers_ex(HashTable *headers, zend_bool prettify TSRMLS_DC)
1574 {
1575 char *key = NULL;
1576 long idx = 0;
1577 zval array;
1578
1579 Z_ARRVAL(array) = headers;
1580
1581 FOREACH_HASH_KEY(HTTP_SERVER_VARS, key, idx) {
1582 if (key && !strncmp(key, "HTTP_", 5)) {
1583 zval **header;
1584
1585 if (prettify) {
1586 key = pretty_key(key + 5, strlen(key) - 5, 1, 1);
1587 }
1588
1589 zend_hash_get_current_data(HTTP_SERVER_VARS, (void **) &header);
1590 add_assoc_stringl(&array, key, Z_STRVAL_PP(header), Z_STRLEN_PP(header), 1);
1591 key = NULL;
1592 }
1593 }
1594 }
1595 /* }}} */
1596
1597 /* {{{ STATUS http_urlencode_hash_ex(HashTable *, int, char **, size_t *) */
1598 PHP_HTTP_API STATUS _http_urlencode_hash_ex(HashTable *hash, int override_argsep,
1599 char *pre_encoded_data, size_t pre_encoded_len,
1600 char **encoded_data, size_t *encoded_len TSRMLS_DC)
1601 {
1602 smart_str qstr = {0};
1603
1604 if (override_argsep) {
1605 HTTP_URL_ARGSEP_OVERRIDE;
1606 }
1607
1608 if (pre_encoded_len && pre_encoded_data) {
1609 smart_str_appendl(&qstr, pre_encoded_data, pre_encoded_len);
1610 }
1611
1612 if (SUCCESS != php_url_encode_hash_ex(hash, &qstr, NULL, 0, NULL, 0, NULL, 0, NULL TSRMLS_CC)) {
1613 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't encode query data");
1614 if (qstr.c) {
1615 efree(qstr.c);
1616 }
1617 if (override_argsep) {
1618 HTTP_URL_ARGSEP_RESTORE;
1619 }
1620 return FAILURE;
1621 }
1622
1623 if (override_argsep) {
1624 HTTP_URL_ARGSEP_RESTORE;
1625 }
1626
1627 smart_str_0(&qstr);
1628
1629 *encoded_data = qstr.c;
1630 if (encoded_len) {
1631 *encoded_len = qstr.len;
1632 }
1633
1634 return SUCCESS;
1635 }
1636 /* }}} */
1637
1638 /* {{{ STATUS http_auth_header(char *, char*) */
1639 PHP_HTTP_API STATUS _http_auth_header(const char *type, const char *realm TSRMLS_DC)
1640 {
1641 char realm_header[1024] = {0};
1642 snprintf(realm_header, 1023, "WWW-Authenticate: %s realm=\"%s\"", type, realm);
1643 return http_send_status_header(401, realm_header);
1644 }
1645 /* }}} */
1646
1647 /* {{{ STATUS http_auth_credentials(char **, char **) */
1648 PHP_HTTP_API STATUS _http_auth_credentials(char **user, char **pass TSRMLS_DC)
1649 {
1650 if (strncmp(sapi_module.name, "isapi", 5)) {
1651 zval *zuser, *zpass;
1652
1653 HTTP_GSC(zuser, "PHP_AUTH_USER", FAILURE);
1654 HTTP_GSC(zpass, "PHP_AUTH_PW", FAILURE);
1655
1656 *user = estrndup(Z_STRVAL_P(zuser), Z_STRLEN_P(zuser));
1657 *pass = estrndup(Z_STRVAL_P(zpass), Z_STRLEN_P(zpass));
1658
1659 return SUCCESS;
1660 } else {
1661 zval *zauth = NULL;
1662 HTTP_GSC(zauth, "HTTP_AUTHORIZATION", FAILURE);
1663 {
1664 char *decoded, *colon;
1665 int decoded_len;
1666 decoded = php_base64_decode(Z_STRVAL_P(zauth), Z_STRLEN_P(zauth),
1667 &decoded_len);
1668
1669 if (colon = strchr(decoded + 6, ':')) {
1670 *user = estrndup(decoded + 6, colon - decoded - 6);
1671 *pass = estrndup(colon + 1, decoded + decoded_len - colon - 6 - 1);
1672
1673 return SUCCESS;
1674 } else {
1675 return FAILURE;
1676 }
1677 }
1678 }
1679 }
1680 /* }}} */
1681
1682 #ifndef ZEND_ENGINE_2
1683 /* {{{ php_url_encode_hash
1684 Author: Sara Golemon <pollita@php.net> */
1685 PHP_HTTP_API STATUS php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
1686 const char *num_prefix, int num_prefix_len,
1687 const char *key_prefix, int key_prefix_len,
1688 const char *key_suffix, int key_suffix_len,
1689 zval *type TSRMLS_DC)
1690 {
1691 char *arg_sep = NULL, *key = NULL, *ekey, *newprefix, *p;
1692 int arg_sep_len, key_len, ekey_len, key_type, newprefix_len;
1693 ulong idx;
1694 zval **zdata = NULL, *copyzval;
1695
1696 if (!ht) {
1697 return FAILURE;
1698 }
1699
1700 if (ht->nApplyCount > 0) {
1701 /* Prevent recursion */
1702 return SUCCESS;
1703 }
1704
1705 arg_sep = INI_STR("arg_separator.output");
1706 if (!arg_sep || !strlen(arg_sep)) {
1707 arg_sep = HTTP_URL_ARGSEP_DEFAULT;
1708 }
1709 arg_sep_len = strlen(arg_sep);
1710
1711 for (zend_hash_internal_pointer_reset(ht);
1712 (key_type = zend_hash_get_current_key_ex(ht, &key, &key_len, &idx, 0, NULL)) != HASH_KEY_NON_EXISTANT;
1713 zend_hash_move_forward(ht)
1714 ) {
1715 if (key_type == HASH_KEY_IS_STRING && key_len && key[key_len-1] == '\0') {
1716 /* We don't want that trailing NULL */
1717 key_len -= 1;
1718 }
1719
1720 #ifdef ZEND_ENGINE_2
1721 /* handling for private & protected object properties */
1722 if (key && *key == '\0' && type != NULL) {
1723 char *tmp;
1724
1725 zend_object *zobj = zend_objects_get_address(type TSRMLS_CC);
1726 if (zend_check_property_access(zobj, key TSRMLS_CC) != SUCCESS) {
1727 /* private or protected property access outside of the class */
1728 continue;
1729 }
1730 zend_unmangle_property_name(key, &tmp, &key);
1731 key_len = strlen(key);
1732 }
1733 #endif
1734
1735 if (zend_hash_get_current_data_ex(ht, (void **)&zdata, NULL) == FAILURE || !zdata || !(*zdata)) {
1736 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error traversing form data array.");
1737 return FAILURE;
1738 }
1739 if (Z_TYPE_PP(zdata) == IS_ARRAY || Z_TYPE_PP(zdata) == IS_OBJECT) {
1740 if (key_type == HASH_KEY_IS_STRING) {
1741 ekey = php_url_encode(key, key_len, &ekey_len);
1742 newprefix_len = key_suffix_len + ekey_len + key_prefix_len + 1;
1743 newprefix = emalloc(newprefix_len + 1);
1744 p = newprefix;
1745
1746 if (key_prefix) {
1747 memcpy(p, key_prefix, key_prefix_len);
1748 p += key_prefix_len;
1749 }
1750
1751 memcpy(p, ekey, ekey_len);
1752 p += ekey_len;
1753 efree(ekey);
1754
1755 if (key_suffix) {
1756 memcpy(p, key_suffix, key_suffix_len);
1757 p += key_suffix_len;
1758 }
1759
1760 *(p++) = '[';
1761 *p = '\0';
1762 } else {
1763 /* Is an integer key */
1764 ekey_len = spprintf(&ekey, 12, "%ld", idx);
1765 newprefix_len = key_prefix_len + num_prefix_len + ekey_len + key_suffix_len + 1;
1766 newprefix = emalloc(newprefix_len + 1);
1767 p = newprefix;
1768
1769 if (key_prefix) {
1770 memcpy(p, key_prefix, key_prefix_len);
1771 p += key_prefix_len;
1772 }
1773
1774 memcpy(p, num_prefix, num_prefix_len);
1775 p += num_prefix_len;
1776
1777 memcpy(p, ekey, ekey_len);
1778 p += ekey_len;
1779 efree(ekey);
1780
1781 if (key_suffix) {
1782 memcpy(p, key_suffix, key_suffix_len);
1783 p += key_suffix_len;
1784 }
1785 *(p++) = '[';
1786 *p = '\0';
1787 }
1788 ht->nApplyCount++;
1789 php_url_encode_hash_ex(HASH_OF(*zdata), formstr, NULL, 0, newprefix, newprefix_len, "]", 1, (Z_TYPE_PP(zdata) == IS_OBJECT ? *zdata : NULL) TSRMLS_CC);
1790 ht->nApplyCount--;
1791 efree(newprefix);
1792 } else if (Z_TYPE_PP(zdata) == IS_NULL || Z_TYPE_PP(zdata) == IS_RESOURCE) {
1793 /* Skip these types */
1794 continue;
1795 } else {
1796 if (formstr->len) {
1797 smart_str_appendl(formstr, arg_sep, arg_sep_len);
1798 }
1799 /* Simple key=value */
1800 smart_str_appendl(formstr, key_prefix, key_prefix_len);
1801 if (key_type == HASH_KEY_IS_STRING) {
1802 ekey = php_url_encode(key, key_len, &ekey_len);
1803 smart_str_appendl(formstr, ekey, ekey_len);
1804 efree(ekey);
1805 } else {
1806 /* Numeric key */
1807 if (num_prefix) {
1808 smart_str_appendl(formstr, num_prefix, num_prefix_len);
1809 }
1810 ekey_len = spprintf(&ekey, 12, "%ld", idx);
1811 smart_str_appendl(formstr, ekey, ekey_len);
1812 efree(ekey);
1813 }
1814 smart_str_appendl(formstr, key_suffix, key_suffix_len);
1815 smart_str_appendl(formstr, "=", 1);
1816 switch (Z_TYPE_PP(zdata)) {
1817 case IS_STRING:
1818 ekey = php_url_encode(Z_STRVAL_PP(zdata), Z_STRLEN_PP(zdata), &ekey_len);
1819 break;
1820 case IS_LONG:
1821 case IS_BOOL:
1822 ekey_len = spprintf(&ekey, 12, "%ld", Z_LVAL_PP(zdata));
1823 break;
1824 case IS_DOUBLE:
1825 ekey_len = spprintf(&ekey, 48, "%.*G", (int) EG(precision), Z_DVAL_PP(zdata));
1826 break;
1827 default:
1828 /* fall back on convert to string */
1829 MAKE_STD_ZVAL(copyzval);
1830 *copyzval = **zdata;
1831 zval_copy_ctor(copyzval);
1832 convert_to_string_ex(&copyzval);
1833 ekey = php_url_encode(Z_STRVAL_P(copyzval), Z_STRLEN_P(copyzval), &ekey_len);
1834 zval_ptr_dtor(&copyzval);
1835 }
1836 smart_str_appendl(formstr, ekey, ekey_len);
1837 efree(ekey);
1838 }
1839 }
1840
1841 return SUCCESS;
1842 }
1843 /* }}} */
1844 #endif /* !ZEND_ENDGINE_2 */
1845
1846 /* }}} public API */
1847
1848 /*
1849 * Local variables:
1850 * tab-width: 4
1851 * c-basic-offset: 4
1852 * End:
1853 * vim600: noet sw=4 ts=4 fdm=marker
1854 * vim<600: noet sw=4 ts=4
1855 */
1856