317e902ccac993dfc5dd6c9f338ced5255a08a73
[m6w6/ext-http] / http_api.c
1 /*
2 +----------------------------------------------------------------------+
3 | PECL :: http |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.0 of the PHP license, that |
6 | is bundled with this package in the file LICENSE, and is available |
7 | through the world-wide-web at http://www.php.net/license/3_0.txt. |
8 | If you did not receive a copy of the PHP license and are unable to |
9 | obtain it through the world-wide-web, please send a note to |
10 | license@php.net so we can mail you a copy immediately. |
11 +----------------------------------------------------------------------+
12 | Copyright (c) 2004-2005 Michael Wallner <mike@php.net> |
13 +----------------------------------------------------------------------+
14 */
15
16 /* $Id$ */
17
18 #define _WINSOCKAPI_
19 #define ZEND_INCLUDE_FULL_WINDOWS_HEADERS
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <ctype.h>
26
27 #include "php.h"
28 #include "php_version.h"
29 #include "php_streams.h"
30 #include "snprintf.h"
31 #include "spprintf.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 php_url *purl, furl = {NULL};
891 struct servent *se;
892 size_t full_len = 0;
893 zval *zhost = NULL;
894 char *scheme = NULL, *URL = ecalloc(1, HTTP_URI_MAXLEN + 1);
895
896 if ((!url || !url_len) && (
897 (!(url = SG(request_info).request_uri)) ||
898 (!(url_len = strlen(SG(request_info).request_uri))))) {
899 php_error_docref(NULL TSRMLS_CC, E_WARNING,
900 "Cannot build an absolute URI if supplied URL and REQUEST_URI is empty");
901 return NULL;
902 }
903
904 if (!(purl = php_url_parse(url))) {
905 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse supplied URL");
906 return NULL;
907 }
908
909 furl.user = purl->user;
910 furl.pass = purl->pass;
911 furl.path = purl->path;
912 furl.fragment = purl->fragment;
913
914 if (proto) {
915 furl.scheme = scheme = estrdup(proto);
916 } else if (purl->scheme) {
917 furl.scheme = purl->scheme;
918 } else if (port && (se = getservbyport(htons(port), "tcp"))) {
919 furl.scheme = (scheme = estrdup(se->s_name));
920 } else {
921 furl.scheme = "http";
922 }
923
924 if (port) {
925 furl.port = port;
926 } else if (purl->port) {
927 furl.port = purl->port;
928 } else if (strncmp(furl.scheme, "http", 4) && (se = getservbyname(furl.scheme, "tcp"))) {
929 furl.port = ntohs(se->s_port);
930 } else {
931 furl.port = furl.scheme[5] ? 443 : 80;
932 }
933
934 if (host) {
935 furl.host = (char *) host;
936 } else if (purl->host) {
937 furl.host = purl->host;
938 } else if ( (zhost = http_get_server_var("HTTP_HOST")) ||
939 (zhost = http_get_server_var("SERVER_NAME"))) {
940 furl.host = Z_STRVAL_P(zhost);
941 } else {
942 furl.host = "localhost";
943 }
944
945 #define HTTP_URI_STRLCATS(URL, full_len, add_string) HTTP_URI_STRLCAT(URL, full_len, add_string, sizeof(add_string)-1)
946 #define HTTP_URI_STRLCATL(URL, full_len, add_string) HTTP_URI_STRLCAT(URL, full_len, add_string, strlen(add_string))
947 #define HTTP_URI_STRLCAT(URL, full_len, add_string, add_len) \
948 if ((full_len += add_len) > HTTP_URI_MAXLEN) { \
949 php_error_docref(NULL TSRMLS_CC, E_NOTICE, \
950 "Absolute URI would have exceeded max URI length (%d bytes) - " \
951 "tried to add %d bytes ('%s')", \
952 HTTP_URI_MAXLEN, add_len, add_string); \
953 if (scheme) { \
954 efree(scheme); \
955 } \
956 php_url_free(purl); \
957 return URL; \
958 } else { \
959 strcat(URL, add_string); \
960 }
961
962 HTTP_URI_STRLCATL(URL, full_len, furl.scheme);
963 HTTP_URI_STRLCATS(URL, full_len, "://");
964
965 if (furl.user) {
966 HTTP_URI_STRLCATL(URL, full_len, furl.user);
967 if (furl.pass) {
968 HTTP_URI_STRLCATS(URL, full_len, ":");
969 HTTP_URI_STRLCATL(URL, full_len, furl.pass);
970 }
971 HTTP_URI_STRLCATS(URL, full_len, "@");
972 }
973
974 HTTP_URI_STRLCATL(URL, full_len, furl.host);
975
976 if ( (strcmp(furl.scheme, "http") && (furl.port != 80)) ||
977 (strcmp(furl.scheme, "https") && (furl.port != 443))) {
978 char port_string[8] = {0};
979 snprintf(port_string, 7, ":%u", furl.port);
980 HTTP_URI_STRLCATL(URL, full_len, port_string);
981 }
982
983 if (furl.path) {
984 HTTP_URI_STRLCATL(URL, full_len, furl.path);
985 if (furl.query) {
986 HTTP_URI_STRLCATS(URL, full_len, "?");
987 HTTP_URI_STRLCATL(URL, full_len, furl.query);
988 }
989 if (furl.fragment) {
990 HTTP_URI_STRLCATS(URL, full_len, "#");
991 HTTP_URI_STRLCATL(URL, full_len, furl.fragment);
992 }
993 }
994
995 if (scheme) {
996 efree(scheme);
997 }
998 php_url_free(purl);
999
1000 return URL;
1001 }
1002 /* }}} */
1003
1004 /* {{{ char *http_negotiate_q(char *, zval *, char *, hash_entry_type) */
1005 PHP_HTTP_API char *_http_negotiate_q(const char *entry, const zval *supported,
1006 const char *def TSRMLS_DC)
1007 {
1008 zval *zaccept, *zarray, *zdelim, **zentry, *zentries, **zsupp;
1009 char *q_ptr, *result;
1010 int i, c;
1011 double qual;
1012
1013 HTTP_GSC(zaccept, entry, estrdup(def));
1014
1015 MAKE_STD_ZVAL(zarray);
1016 array_init(zarray);
1017
1018 MAKE_STD_ZVAL(zdelim);
1019 ZVAL_STRING(zdelim, ",", 0);
1020 php_explode(zdelim, zaccept, zarray, -1);
1021 efree(zdelim);
1022
1023 MAKE_STD_ZVAL(zentries);
1024 array_init(zentries);
1025
1026 c = zend_hash_num_elements(Z_ARRVAL_P(zarray));
1027 for (i = 0; i < c; i++, zend_hash_move_forward(Z_ARRVAL_P(zarray))) {
1028
1029 if (SUCCESS != zend_hash_get_current_data(
1030 Z_ARRVAL_P(zarray), (void **) &zentry)) {
1031 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1032 "Cannot parse %s header: %s", entry, Z_STRVAL_P(zaccept));
1033 break;
1034 }
1035
1036 /* check for qualifier */
1037 if (NULL != (q_ptr = strrchr(Z_STRVAL_PP(zentry), ';'))) {
1038 qual = strtod(q_ptr + 3, NULL);
1039 } else {
1040 qual = 1000.0 - i;
1041 }
1042
1043 /* walk through the supported array */
1044 FOREACH_VAL(supported, zsupp) {
1045 if (!strcasecmp(Z_STRVAL_PP(zsupp), Z_STRVAL_PP(zentry))) {
1046 add_assoc_double(zentries, Z_STRVAL_PP(zsupp), qual);
1047 break;
1048 }
1049 }
1050 }
1051
1052 zval_dtor(zarray);
1053 efree(zarray);
1054
1055 zend_hash_internal_pointer_reset(Z_ARRVAL_P(zentries));
1056
1057 if ( (SUCCESS != zend_hash_sort(Z_ARRVAL_P(zentries), zend_qsort,
1058 http_sort_q, 0 TSRMLS_CC)) ||
1059 (HASH_KEY_NON_EXISTANT == zend_hash_get_current_key(
1060 Z_ARRVAL_P(zentries), &result, 0, 1))) {
1061 result = estrdup(def);
1062 }
1063
1064 zval_dtor(zentries);
1065 efree(zentries);
1066
1067 return result;
1068 }
1069 /* }}} */
1070
1071 /* {{{ http_range_status http_get_request_ranges(HashTable *ranges, size_t) */
1072 PHP_HTTP_API http_range_status _http_get_request_ranges(HashTable *ranges,
1073 const size_t length TSRMLS_DC)
1074 {
1075 zval *zrange;
1076 char *range, c;
1077 long begin = -1, end = -1, *ptr;
1078
1079 HTTP_GSC(zrange, "HTTP_RANGE", RANGE_NO);
1080 range = Z_STRVAL_P(zrange);
1081
1082 if (strncmp(range, "bytes=", sizeof("bytes=") - 1)) {
1083 php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Range header misses bytes=");
1084 return RANGE_NO;
1085 }
1086
1087 ptr = &begin;
1088 range += sizeof("bytes=") - 1;
1089
1090 do {
1091 switch (c = *(range++))
1092 {
1093 case '0':
1094 *ptr *= 10;
1095 break;
1096
1097 case '1': case '2': case '3':
1098 case '4': case '5': case '6':
1099 case '7': case '8': case '9':
1100 /*
1101 * If the value of the pointer is already set (non-negative)
1102 * then multiply its value by ten and add the current value,
1103 * else initialise the pointers value with the current value
1104 * --
1105 * This let us recognize empty fields when validating the
1106 * ranges, i.e. a "-10" for begin and "12345" for the end
1107 * was the following range request: "Range: bytes=0-12345";
1108 * While a "-1" for begin and "12345" for the end would
1109 * have been: "Range: bytes=-12345".
1110 */
1111 if (*ptr > 0) {
1112 *ptr *= 10;
1113 *ptr += c - '0';
1114 } else {
1115 *ptr = c - '0';
1116 }
1117 break;
1118
1119 case '-':
1120 ptr = &end;
1121 break;
1122
1123 case ' ':
1124 /* IE - ignore for now */
1125 break;
1126
1127 case 0:
1128 case ',':
1129
1130 if (length) {
1131 /* validate ranges */
1132 switch (begin)
1133 {
1134 /* "0-12345" */
1135 case -10:
1136 if ((length - end) < 1) {
1137 return RANGE_ERR;
1138 }
1139 begin = 0;
1140 break;
1141
1142 /* "-12345" */
1143 case -1:
1144 if ((length - end) < 1) {
1145 return RANGE_ERR;
1146 }
1147 begin = length - end;
1148 end = length;
1149 break;
1150
1151 /* "12345-(xxx)" */
1152 default:
1153 switch (end)
1154 {
1155 /* "12345-" */
1156 case -1:
1157 if ((length - begin) < 1) {
1158 return RANGE_ERR;
1159 }
1160 end = length - 1;
1161 break;
1162
1163 /* "12345-67890" */
1164 default:
1165 if ( ((length - begin) < 1) ||
1166 ((length - end) < 1) ||
1167 ((begin - end) >= 0)) {
1168 return RANGE_ERR;
1169 }
1170 break;
1171 }
1172 break;
1173 }
1174 }
1175 {
1176 zval *zentry;
1177 MAKE_STD_ZVAL(zentry);
1178 array_init(zentry);
1179 add_index_long(zentry, 0, begin);
1180 add_index_long(zentry, 1, end);
1181 zend_hash_next_index_insert(ranges, &zentry, sizeof(zval *), NULL);
1182
1183 begin = -1;
1184 end = -1;
1185 ptr = &begin;
1186 }
1187 break;
1188
1189 default:
1190 return RANGE_NO;
1191 break;
1192 }
1193 } while (c != 0);
1194
1195 return RANGE_OK;
1196 }
1197 /* }}} */
1198
1199 /* {{{ STATUS http_send_ranges(HashTable *, void *, size_t, http_send_mode) */
1200 PHP_HTTP_API STATUS _http_send_ranges(HashTable *ranges, const void *data, const size_t size, const http_send_mode mode TSRMLS_DC)
1201 {
1202 long **begin, **end;
1203 zval **zrange;
1204
1205 /* single range */
1206 if (zend_hash_num_elements(ranges) == 1) {
1207 char range_header[256] = {0};
1208
1209 if (SUCCESS != zend_hash_index_find(ranges, 0, (void **) &zrange) ||
1210 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
1211 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
1212 return FAILURE;
1213 }
1214
1215 /* Send HTTP 206 Partial Content */
1216 http_send_status(206);
1217
1218 /* send content range header */
1219 snprintf(range_header, 255, "Content-Range: bytes %d-%d/%d", **begin, **end, size);
1220 http_send_header(range_header);
1221
1222 /* send requested chunk */
1223 return http_send_chunk(data, **begin, **end + 1, mode);
1224 }
1225
1226 /* multi range */
1227 else {
1228 char bound[23] = {0}, preface[1024] = {0},
1229 multi_header[68] = "Content-Type: multipart/byteranges; boundary=";
1230
1231 /* Send HTTP 206 Partial Content */
1232 http_send_status(206);
1233
1234 /* send multipart/byteranges header */
1235 snprintf(bound, 22, "--%d%0.9f", time(NULL), php_combined_lcg(TSRMLS_C));
1236 strncat(multi_header, bound + 2, 21);
1237 http_send_header(multi_header);
1238
1239 /* send each requested chunk */
1240 FOREACH_HASH_VAL(ranges, zrange) {
1241 if (SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
1242 SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
1243 break;
1244 }
1245
1246 snprintf(preface, 1023,
1247 HTTP_CRLF "%s"
1248 HTTP_CRLF "Content-Type: %s"
1249 HTTP_CRLF "Content-Range: bytes %ld-%ld/%ld"
1250 HTTP_CRLF
1251 HTTP_CRLF,
1252
1253 bound,
1254 HTTP_G(ctype) ? HTTP_G(ctype) : "application/x-octetstream",
1255 **begin,
1256 **end,
1257 size
1258 );
1259
1260 php_body_write(preface, strlen(preface) TSRMLS_CC);
1261 http_send_chunk(data, **begin, **end + 1, mode);
1262 }
1263
1264 /* write boundary once more */
1265 php_body_write(HTTP_CRLF, sizeof(HTTP_CRLF) - 1 TSRMLS_CC);
1266 php_body_write(bound, strlen(bound) TSRMLS_CC);
1267 php_body_write("--", 2 TSRMLS_CC);
1268
1269 return SUCCESS;
1270 }
1271 }
1272 /* }}} */
1273
1274 /* {{{ STATUS http_send(void *, size_t, http_send_mode) */
1275 PHP_HTTP_API STATUS _http_send(const void *data_ptr, const size_t data_size,
1276 const http_send_mode data_mode TSRMLS_DC)
1277 {
1278 int is_range_request = http_is_range_request();
1279
1280 if (!data_ptr) {
1281 return FAILURE;
1282 }
1283 if (!data_size) {
1284 return SUCCESS;
1285 }
1286
1287 /* etag handling */
1288 if (HTTP_G(etag_started)) {
1289 char *etag;
1290 /* interrupt */
1291 HTTP_G(etag_started) = 0;
1292 /* never ever use the output to compute the ETag if http_send() is used */
1293 php_end_ob_buffer(0, 0 TSRMLS_CC);
1294 if (!(etag = http_etag(data_ptr, data_size, data_mode))) {
1295 return FAILURE;
1296 }
1297
1298 /* send 304 Not Modified if etag matches */
1299 if ((!is_range_request) && http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
1300 efree(etag);
1301 return http_send_status(304);
1302 }
1303
1304 http_send_etag(etag, 32);
1305 efree(etag);
1306 }
1307
1308 /* send 304 Not Modified if last-modified matches*/
1309 if ((!is_range_request) && http_modified_match("HTTP_IF_MODIFIED_SINCE", HTTP_G(lmod))) {
1310 return http_send_status(304);
1311 }
1312
1313 if (is_range_request) {
1314
1315 /* only send ranges if entity hasn't changed */
1316 if (
1317 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_MATCH", 13)) ||
1318 http_etag_match("HTTP_IF_MATCH", HTTP_G(etag)))
1319 &&
1320 ((!zend_hash_exists(HTTP_SERVER_VARS, "HTTP_IF_UNMODIFIED_SINCE", 25)) ||
1321 http_modified_match("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(lmod)))
1322 ) {
1323
1324 STATUS result = FAILURE;
1325 HashTable ranges;
1326 zend_hash_init(&ranges, 0, NULL, ZVAL_PTR_DTOR, 0);
1327
1328 switch (http_get_request_ranges(&ranges, data_size))
1329 {
1330 case RANGE_NO:
1331 zend_hash_destroy(&ranges);
1332 /* go ahead and send all */
1333 break;
1334
1335 case RANGE_OK:
1336 result = http_send_ranges(&ranges, data_ptr, data_size, data_mode);
1337 zend_hash_destroy(&ranges);
1338 return result;
1339 break;
1340
1341 case RANGE_ERR:
1342 zend_hash_destroy(&ranges);
1343 http_send_status(416);
1344 return FAILURE;
1345 break;
1346
1347 default:
1348 return FAILURE;
1349 break;
1350 }
1351 }
1352 }
1353 /* send all */
1354 return http_send_chunk(data_ptr, 0, data_size, data_mode);
1355 }
1356 /* }}} */
1357
1358 /* {{{ STATUS http_send_stream(php_stream *) */
1359 PHP_HTTP_API STATUS _http_send_stream_ex(php_stream *file,
1360 zend_bool close_stream TSRMLS_DC)
1361 {
1362 STATUS status;
1363
1364 if ((!file) || php_stream_stat(file, &HTTP_G(ssb))) {
1365 return FAILURE;
1366 }
1367
1368 status = http_send(file, HTTP_G(ssb).sb.st_size, SEND_RSRC);
1369
1370 if (close_stream) {
1371 php_stream_close(file);
1372 }
1373
1374 return status;
1375 }
1376 /* }}} */
1377
1378 /* {{{ proto STATUS http_chunked_decode(char *, size_t, char **, size_t *) */
1379 PHP_HTTP_API STATUS _http_chunked_decode(const char *encoded,
1380 const size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
1381 {
1382 const char *e_ptr;
1383 char *d_ptr;
1384
1385 *decoded_len = 0;
1386 *decoded = ecalloc(1, encoded_len);
1387 d_ptr = *decoded;
1388 e_ptr = encoded;
1389
1390 while (((e_ptr - encoded) - encoded_len) > 0) {
1391 char hex_len[9] = {0};
1392 size_t chunk_len = 0;
1393 int i = 0;
1394
1395 /* read in chunk size */
1396 while (isxdigit(*e_ptr)) {
1397 if (i == 9) {
1398 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1399 "Chunk size is too long: 0x%s...", hex_len);
1400 efree(*decoded);
1401 return FAILURE;
1402 }
1403 hex_len[i++] = *e_ptr++;
1404 }
1405
1406 /* reached the end */
1407 if (!strcmp(hex_len, "0")) {
1408 break;
1409 }
1410
1411 /* new line */
1412 if (strncmp(e_ptr, HTTP_CRLF, 2)) {
1413 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1414 "Invalid character (expected 0x0D 0x0A; got: %x %x)",
1415 *e_ptr, *(e_ptr + 1));
1416 efree(*decoded);
1417 return FAILURE;
1418 }
1419
1420 /* hex to long */
1421 {
1422 char *error = NULL;
1423 chunk_len = strtol(hex_len, &error, 16);
1424 if (error == hex_len) {
1425 php_error_docref(NULL TSRMLS_CC, E_WARNING,
1426 "Invalid chunk size string: '%s'", hex_len);
1427 efree(*decoded);
1428 return FAILURE;
1429 }
1430 }
1431
1432 memcpy(d_ptr, e_ptr += 2, chunk_len);
1433 d_ptr += chunk_len;
1434 e_ptr += chunk_len + 2;
1435 *decoded_len += chunk_len;
1436 }
1437
1438 return SUCCESS;
1439 }
1440 /* }}} */
1441
1442 /* {{{ proto STATUS http_split_response_ex(char *, size_t, zval *, zval *) */
1443 PHP_HTTP_API STATUS _http_split_response_ex(char *response,
1444 size_t response_len, HashTable *headers, char **body, size_t *body_len TSRMLS_DC)
1445 {
1446 char *header = response;
1447 *body = NULL;
1448
1449 while (0 < (response_len - (response - header + 4))) {
1450 if ( (*response++ == '\r') &&
1451 (*response++ == '\n') &&
1452 (*response++ == '\r') &&
1453 (*response++ == '\n')) {
1454 *body = response;
1455 break;
1456 }
1457 }
1458
1459 if (*body && (*body_len = response_len - (*body - header))) {
1460 *body = estrndup(*body, *body_len - 1);
1461 }
1462
1463 return http_parse_headers(header, *body ? *body - header : response_len, headers);
1464 }
1465 /* }}} */
1466
1467 /* {{{ STATUS http_parse_headers(char *, long, zval *) */
1468 PHP_HTTP_API STATUS _http_parse_headers(char *header, int header_len, HashTable *headers TSRMLS_DC)
1469 {
1470 char *colon = NULL, *line = NULL, *begin = header;
1471 zval array;
1472
1473 Z_ARRVAL(array) = headers;
1474
1475 if (header_len < 2) {
1476 return FAILURE;
1477 }
1478
1479 /* status code */
1480 if (!strncmp(header, "HTTP/1.", 7)) {
1481 char *end = strstr(header, HTTP_CRLF);
1482 add_assoc_stringl(&array, "Status",
1483 header + sizeof("HTTP/1.x ") - 1,
1484 end - (header + sizeof("HTTP/1.x ") - 1), 1);
1485 header = end + 2;
1486 }
1487
1488 line = header;
1489
1490 while (header_len >= (line - begin)) {
1491 int value_len = 0;
1492
1493 switch (*line++)
1494 {
1495 case 0:
1496 --value_len; /* we don't have CR so value length is one char less */
1497 case '\n':
1498 if (colon && ((!(*line - 1)) || ((*line != ' ') && (*line != '\t')))) {
1499
1500 /* skip empty key */
1501 if (header != colon) {
1502 char *key = estrndup(header, colon - header);
1503 value_len += line - colon - 1;
1504
1505 /* skip leading ws */
1506 while (isspace(*(++colon))) --value_len;
1507 /* skip trailing ws */
1508 while (isspace(colon[value_len - 1])) --value_len;
1509
1510 if (value_len < 1) {
1511 /* hm, empty header? */
1512 add_assoc_stringl(&array, key, "", 0, 1);
1513 } else {
1514 add_assoc_stringl(&array, key, colon, value_len, 1);
1515 }
1516 efree(key);
1517 }
1518
1519 colon = NULL;
1520 value_len = 0;
1521 header += line - header;
1522 }
1523 break;
1524
1525 case ':':
1526 if (!colon) {
1527 colon = line - 1;
1528 }
1529 break;
1530 }
1531 }
1532 return SUCCESS;
1533 }
1534 /* }}} */
1535
1536 /* {{{ void http_get_request_headers(zval *) */
1537 PHP_HTTP_API void _http_get_request_headers(zval *array TSRMLS_DC)
1538 {
1539 char *key = NULL;
1540 long idx = 0;
1541
1542 FOREACH_HASH_KEY(HTTP_SERVER_VARS, key, idx) {
1543 if (key && !strncmp(key, "HTTP_", 5)) {
1544 zval **header;
1545 zend_hash_get_current_data(HTTP_SERVER_VARS, (void **) &header);
1546 add_assoc_stringl(array, pretty_key(key + 5, strlen(key) - 5, 1, 1), Z_STRVAL_PP(header), Z_STRLEN_PP(header), 1);
1547 }
1548 }
1549 }
1550 /* }}} */
1551
1552 /* {{{ STATUS http_urlencode_hash_ex(HashTable *, int, char **, size_t *) */
1553 PHP_HTTP_API STATUS _http_urlencode_hash_ex(HashTable *hash, int override_argsep,
1554 char *pre_encoded_data, size_t pre_encoded_len,
1555 char **encoded_data, size_t *encoded_len TSRMLS_DC)
1556 {
1557 smart_str qstr = {0};
1558
1559 if (override_argsep) {
1560 HTTP_URL_ARGSEP_OVERRIDE;
1561 }
1562
1563 if (pre_encoded_len && pre_encoded_data) {
1564 smart_str_appendl(&qstr, pre_encoded_data, pre_encoded_len);
1565 }
1566
1567 if (SUCCESS != php_url_encode_hash_ex(hash, &qstr, NULL, 0, NULL, 0, NULL, 0, NULL TSRMLS_CC)) {
1568 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't encode query data");
1569 if (qstr.c) {
1570 efree(qstr.c);
1571 }
1572 if (override_argsep) {
1573 HTTP_URL_ARGSEP_RESTORE;
1574 }
1575 return FAILURE;
1576 }
1577
1578 if (override_argsep) {
1579 HTTP_URL_ARGSEP_RESTORE;
1580 }
1581
1582 smart_str_0(&qstr);
1583
1584 *encoded_data = qstr.c;
1585 if (encoded_len) {
1586 *encoded_len = qstr.len;
1587 }
1588
1589 return SUCCESS;
1590 }
1591 /* }}} */
1592
1593 /* {{{ STATUS http_auth_header(char *, char*) */
1594 PHP_HTTP_API STATUS _http_auth_header(const char *type, const char *realm TSRMLS_DC)
1595 {
1596 char realm_header[1024] = {0};
1597 snprintf(realm_header, 1023, "WWW-Authenticate: %s realm=\"%s\"", type, realm);
1598 return http_send_status_header(401, realm_header);
1599 }
1600 /* }}} */
1601
1602 /* {{{ STATUS http_auth_credentials(char **, char **) */
1603 PHP_HTTP_API STATUS _http_auth_credentials(char **user, char **pass TSRMLS_DC)
1604 {
1605 if (strncmp(sapi_module.name, "isapi", 5)) {
1606 zval *zuser, *zpass;
1607
1608 HTTP_GSC(zuser, "PHP_AUTH_USER", FAILURE);
1609 HTTP_GSC(zpass, "PHP_AUTH_PW", FAILURE);
1610
1611 *user = estrndup(Z_STRVAL_P(zuser), Z_STRLEN_P(zuser));
1612 *pass = estrndup(Z_STRVAL_P(zpass), Z_STRLEN_P(zpass));
1613
1614 return SUCCESS;
1615 } else {
1616 zval *zauth = NULL;
1617 HTTP_GSC(zauth, "HTTP_AUTHORIZATION", FAILURE);
1618 {
1619 char *decoded, *colon;
1620 int decoded_len;
1621 decoded = php_base64_decode(Z_STRVAL_P(zauth), Z_STRLEN_P(zauth),
1622 &decoded_len);
1623
1624 if (colon = strchr(decoded + 6, ':')) {
1625 *user = estrndup(decoded + 6, colon - decoded - 6);
1626 *pass = estrndup(colon + 1, decoded + decoded_len - colon - 6 - 1);
1627
1628 return SUCCESS;
1629 } else {
1630 return FAILURE;
1631 }
1632 }
1633 }
1634 }
1635 /* }}} */
1636
1637 #ifndef ZEND_ENGINE_2
1638 /* {{{ php_url_encode_hash
1639 Author: Sara Golemon <pollita@php.net> */
1640 PHP_HTTP_API STATUS php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
1641 const char *num_prefix, int num_prefix_len,
1642 const char *key_prefix, int key_prefix_len,
1643 const char *key_suffix, int key_suffix_len,
1644 zval *type TSRMLS_DC)
1645 {
1646 char *arg_sep = NULL, *key = NULL, *ekey, *newprefix, *p;
1647 int arg_sep_len, key_len, ekey_len, key_type, newprefix_len;
1648 ulong idx;
1649 zval **zdata = NULL, *copyzval;
1650
1651 if (!ht) {
1652 return FAILURE;
1653 }
1654
1655 if (ht->nApplyCount > 0) {
1656 /* Prevent recursion */
1657 return SUCCESS;
1658 }
1659
1660 arg_sep = INI_STR("arg_separator.output");
1661 if (!arg_sep || !strlen(arg_sep)) {
1662 arg_sep = HTTP_URL_ARGSEP_DEFAULT;
1663 }
1664 arg_sep_len = strlen(arg_sep);
1665
1666 for (zend_hash_internal_pointer_reset(ht);
1667 (key_type = zend_hash_get_current_key_ex(ht, &key, &key_len, &idx, 0, NULL)) != HASH_KEY_NON_EXISTANT;
1668 zend_hash_move_forward(ht)
1669 ) {
1670 if (key_type == HASH_KEY_IS_STRING && key_len && key[key_len-1] == '\0') {
1671 /* We don't want that trailing NULL */
1672 key_len -= 1;
1673 }
1674
1675 #ifdef ZEND_ENGINE_2
1676 /* handling for private & protected object properties */
1677 if (key && *key == '\0' && type != NULL) {
1678 char *tmp;
1679
1680 zend_object *zobj = zend_objects_get_address(type TSRMLS_CC);
1681 if (zend_check_property_access(zobj, key TSRMLS_CC) != SUCCESS) {
1682 /* private or protected property access outside of the class */
1683 continue;
1684 }
1685 zend_unmangle_property_name(key, &tmp, &key);
1686 key_len = strlen(key);
1687 }
1688 #endif
1689
1690 if (zend_hash_get_current_data_ex(ht, (void **)&zdata, NULL) == FAILURE || !zdata || !(*zdata)) {
1691 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error traversing form data array.");
1692 return FAILURE;
1693 }
1694 if (Z_TYPE_PP(zdata) == IS_ARRAY || Z_TYPE_PP(zdata) == IS_OBJECT) {
1695 if (key_type == HASH_KEY_IS_STRING) {
1696 ekey = php_url_encode(key, key_len, &ekey_len);
1697 newprefix_len = key_suffix_len + ekey_len + key_prefix_len + 1;
1698 newprefix = emalloc(newprefix_len + 1);
1699 p = newprefix;
1700
1701 if (key_prefix) {
1702 memcpy(p, key_prefix, key_prefix_len);
1703 p += key_prefix_len;
1704 }
1705
1706 memcpy(p, ekey, ekey_len);
1707 p += ekey_len;
1708 efree(ekey);
1709
1710 if (key_suffix) {
1711 memcpy(p, key_suffix, key_suffix_len);
1712 p += key_suffix_len;
1713 }
1714
1715 *(p++) = '[';
1716 *p = '\0';
1717 } else {
1718 /* Is an integer key */
1719 ekey_len = spprintf(&ekey, 12, "%ld", idx);
1720 newprefix_len = key_prefix_len + num_prefix_len + ekey_len + key_suffix_len + 1;
1721 newprefix = emalloc(newprefix_len + 1);
1722 p = newprefix;
1723
1724 if (key_prefix) {
1725 memcpy(p, key_prefix, key_prefix_len);
1726 p += key_prefix_len;
1727 }
1728
1729 memcpy(p, num_prefix, num_prefix_len);
1730 p += num_prefix_len;
1731
1732 memcpy(p, ekey, ekey_len);
1733 p += ekey_len;
1734 efree(ekey);
1735
1736 if (key_suffix) {
1737 memcpy(p, key_suffix, key_suffix_len);
1738 p += key_suffix_len;
1739 }
1740 *(p++) = '[';
1741 *p = '\0';
1742 }
1743 ht->nApplyCount++;
1744 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);
1745 ht->nApplyCount--;
1746 efree(newprefix);
1747 } else if (Z_TYPE_PP(zdata) == IS_NULL || Z_TYPE_PP(zdata) == IS_RESOURCE) {
1748 /* Skip these types */
1749 continue;
1750 } else {
1751 if (formstr->len) {
1752 smart_str_appendl(formstr, arg_sep, arg_sep_len);
1753 }
1754 /* Simple key=value */
1755 smart_str_appendl(formstr, key_prefix, key_prefix_len);
1756 if (key_type == HASH_KEY_IS_STRING) {
1757 ekey = php_url_encode(key, key_len, &ekey_len);
1758 smart_str_appendl(formstr, ekey, ekey_len);
1759 efree(ekey);
1760 } else {
1761 /* Numeric key */
1762 if (num_prefix) {
1763 smart_str_appendl(formstr, num_prefix, num_prefix_len);
1764 }
1765 ekey_len = spprintf(&ekey, 12, "%ld", idx);
1766 smart_str_appendl(formstr, ekey, ekey_len);
1767 efree(ekey);
1768 }
1769 smart_str_appendl(formstr, key_suffix, key_suffix_len);
1770 smart_str_appendl(formstr, "=", 1);
1771 switch (Z_TYPE_PP(zdata)) {
1772 case IS_STRING:
1773 ekey = php_url_encode(Z_STRVAL_PP(zdata), Z_STRLEN_PP(zdata), &ekey_len);
1774 break;
1775 case IS_LONG:
1776 case IS_BOOL:
1777 ekey_len = spprintf(&ekey, 12, "%ld", Z_LVAL_PP(zdata));
1778 break;
1779 case IS_DOUBLE:
1780 ekey_len = spprintf(&ekey, 48, "%.*G", (int) EG(precision), Z_DVAL_PP(zdata));
1781 break;
1782 default:
1783 /* fall back on convert to string */
1784 MAKE_STD_ZVAL(copyzval);
1785 *copyzval = **zdata;
1786 zval_copy_ctor(copyzval);
1787 convert_to_string_ex(&copyzval);
1788 ekey = php_url_encode(Z_STRVAL_P(copyzval), Z_STRLEN_P(copyzval), &ekey_len);
1789 zval_ptr_dtor(&copyzval);
1790 }
1791 smart_str_appendl(formstr, ekey, ekey_len);
1792 efree(ekey);
1793 }
1794 }
1795
1796 return SUCCESS;
1797 }
1798 /* }}} */
1799 #endif /* !ZEND_ENDGINE_2 */
1800
1801 /* }}} public API */
1802
1803 /*
1804 * Local variables:
1805 * tab-width: 4
1806 * c-basic-offset: 4
1807 * End:
1808 * vim600: noet sw=4 ts=4 fdm=marker
1809 * vim<600: noet sw=4 ts=4
1810 */
1811