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