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