Annotation of embedaddon/php/ext/standard/http_fopen_wrapper.c, revision 1.1.1.4
1.1 misho 1: /*
2: +----------------------------------------------------------------------+
3: | PHP Version 5 |
4: +----------------------------------------------------------------------+
1.1.1.4 ! misho 5: | Copyright (c) 1997-2014 The PHP Group |
1.1 misho 6: +----------------------------------------------------------------------+
7: | This source file is subject to version 3.01 of the PHP license, |
8: | that is bundled with this package in the file LICENSE, and is |
9: | available through the world-wide-web at the following url: |
10: | http://www.php.net/license/3_01.txt |
11: | If you did not receive a copy of the PHP license and are unable to |
12: | obtain it through the world-wide-web, please send a note to |
13: | license@php.net so we can mail you a copy immediately. |
14: +----------------------------------------------------------------------+
15: | Authors: Rasmus Lerdorf <rasmus@php.net> |
16: | Jim Winstead <jimw@php.net> |
17: | Hartmut Holzgraefe <hholzgra@php.net> |
18: | Wez Furlong <wez@thebrainroom.com> |
19: | Sara Golemon <pollita@php.net> |
20: +----------------------------------------------------------------------+
21: */
1.1.1.2 misho 22: /* $Id$ */
1.1 misho 23:
24: #include "php.h"
25: #include "php_globals.h"
26: #include "php_streams.h"
27: #include "php_network.h"
28: #include "php_ini.h"
29: #include "ext/standard/basic_functions.h"
30: #include "ext/standard/php_smart_str.h"
31:
32: #include <stdio.h>
33: #include <stdlib.h>
34: #include <errno.h>
35: #include <sys/types.h>
36: #include <sys/stat.h>
37: #include <fcntl.h>
38:
39: #ifdef PHP_WIN32
40: #define O_RDONLY _O_RDONLY
41: #include "win32/param.h"
42: #else
43: #include <sys/param.h>
44: #endif
45:
46: #include "php_standard.h"
47:
48: #include <sys/types.h>
49: #if HAVE_SYS_SOCKET_H
50: #include <sys/socket.h>
51: #endif
52:
53: #ifdef PHP_WIN32
54: #include <winsock2.h>
55: #elif defined(NETWARE) && defined(USE_WINSOCK)
56: #include <novsock2.h>
57: #else
58: #include <netinet/in.h>
59: #include <netdb.h>
60: #if HAVE_ARPA_INET_H
61: #include <arpa/inet.h>
62: #endif
63: #endif
64:
65: #if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE)
66: #undef AF_UNIX
67: #endif
68:
69: #if defined(AF_UNIX)
70: #include <sys/un.h>
71: #endif
72:
73: #include "php_fopen_wrappers.h"
74:
75: #define HTTP_HEADER_BLOCK_SIZE 1024
76: #define PHP_URL_REDIRECT_MAX 20
77: #define HTTP_HEADER_USER_AGENT 1
78: #define HTTP_HEADER_HOST 2
79: #define HTTP_HEADER_AUTH 4
80: #define HTTP_HEADER_FROM 8
81: #define HTTP_HEADER_CONTENT_LENGTH 16
82: #define HTTP_HEADER_TYPE 32
83:
84: #define HTTP_WRAPPER_HEADER_INIT 1
85: #define HTTP_WRAPPER_REDIRECTED 2
86:
1.1.1.4 ! misho 87: static inline void strip_header(char *header_bag, char *lc_header_bag,
! 88: const char *lc_header_name)
! 89: {
! 90: char *lc_header_start = strstr(lc_header_bag, lc_header_name);
! 91: char *header_start = header_bag + (lc_header_start - lc_header_bag);
! 92:
! 93: if (lc_header_start
! 94: && (lc_header_start == lc_header_bag || *(lc_header_start-1) == '\n')
! 95: ) {
! 96: char *lc_eol = strchr(lc_header_start, '\n');
! 97: char *eol = header_start + (lc_eol - lc_header_start);
! 98:
! 99: if (lc_eol) {
! 100: size_t eollen = strlen(lc_eol);
! 101:
! 102: memmove(lc_header_start, lc_eol+1, eollen);
! 103: memmove(header_start, eol+1, eollen);
! 104: } else {
! 105: *lc_header_start = '\0';
! 106: *header_start = '\0';
! 107: }
! 108: }
! 109: }
! 110:
1.1 misho 111: php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context, int redirect_max, int flags STREAMS_DC TSRMLS_DC) /* {{{ */
112: {
113: php_stream *stream = NULL;
114: php_url *resource = NULL;
115: int use_ssl;
116: int use_proxy = 0;
117: char *scratch = NULL;
118: char *tmp = NULL;
119: char *ua_str = NULL;
120: zval **ua_zval = NULL, **tmpzval = NULL;
121: int scratch_len = 0;
122: int body = 0;
123: char location[HTTP_HEADER_BLOCK_SIZE];
124: zval *response_header = NULL;
125: int reqok = 0;
126: char *http_header_line = NULL;
127: char tmp_line[128];
128: size_t chunk_size = 0, file_size = 0;
129: int eol_detect = 0;
130: char *transport_string, *errstr = NULL;
131: int transport_len, have_header = 0, request_fulluri = 0, ignore_errors = 0;
132: char *protocol_version = NULL;
133: int protocol_version_len = 3; /* Default: "1.0" */
134: struct timeval timeout;
135: char *user_headers = NULL;
136: int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
137: int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
138: int follow_location = 1;
139: php_stream_filter *transfer_encoding = NULL;
1.1.1.3 misho 140: int response_code;
1.1 misho 141:
142: tmp_line[0] = '\0';
143:
144: if (redirect_max < 1) {
145: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Redirection limit reached, aborting");
146: return NULL;
147: }
148:
149: resource = php_url_parse(path);
150: if (resource == NULL) {
151: return NULL;
152: }
153:
154: if (strncasecmp(resource->scheme, "http", sizeof("http")) && strncasecmp(resource->scheme, "https", sizeof("https"))) {
155: if (!context ||
156: php_stream_context_get_option(context, wrapper->wops->label, "proxy", &tmpzval) == FAILURE ||
157: Z_TYPE_PP(tmpzval) != IS_STRING ||
158: Z_STRLEN_PP(tmpzval) <= 0) {
159: php_url_free(resource);
1.1.1.2 misho 160: return php_stream_open_wrapper_ex(path, mode, REPORT_ERRORS, NULL, context);
1.1 misho 161: }
162: /* Called from a non-http wrapper with http proxying requested (i.e. ftp) */
163: request_fulluri = 1;
164: use_ssl = 0;
165: use_proxy = 1;
166:
167: transport_len = Z_STRLEN_PP(tmpzval);
168: transport_string = estrndup(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval));
169: } else {
170: /* Normal http request (possibly with proxy) */
171:
172: if (strpbrk(mode, "awx+")) {
173: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP wrapper does not support writeable connections");
174: php_url_free(resource);
175: return NULL;
176: }
177:
178: use_ssl = resource->scheme && (strlen(resource->scheme) > 4) && resource->scheme[4] == 's';
179: /* choose default ports */
180: if (use_ssl && resource->port == 0)
181: resource->port = 443;
182: else if (resource->port == 0)
183: resource->port = 80;
184:
185: if (context &&
186: php_stream_context_get_option(context, wrapper->wops->label, "proxy", &tmpzval) == SUCCESS &&
187: Z_TYPE_PP(tmpzval) == IS_STRING &&
188: Z_STRLEN_PP(tmpzval) > 0) {
189: use_proxy = 1;
190: transport_len = Z_STRLEN_PP(tmpzval);
191: transport_string = estrndup(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval));
192: } else {
193: transport_len = spprintf(&transport_string, 0, "%s://%s:%d", use_ssl ? "ssl" : "tcp", resource->host, resource->port);
194: }
195: }
196:
197: if (context && php_stream_context_get_option(context, wrapper->wops->label, "timeout", &tmpzval) == SUCCESS) {
198: SEPARATE_ZVAL(tmpzval);
199: convert_to_double_ex(tmpzval);
200: timeout.tv_sec = (time_t) Z_DVAL_PP(tmpzval);
201: timeout.tv_usec = (size_t) ((Z_DVAL_PP(tmpzval) - timeout.tv_sec) * 1000000);
202: } else {
203: timeout.tv_sec = FG(default_socket_timeout);
204: timeout.tv_usec = 0;
205: }
206:
207: stream = php_stream_xport_create(transport_string, transport_len, options,
208: STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
209: NULL, &timeout, context, &errstr, NULL);
210:
211: if (stream) {
212: php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &timeout);
213: }
214:
215: if (errstr) {
216: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", errstr);
217: efree(errstr);
218: errstr = NULL;
219: }
220:
221: efree(transport_string);
222:
223: if (stream && use_proxy && use_ssl) {
224: smart_str header = {0};
225:
226: smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1);
227: smart_str_appends(&header, resource->host);
228: smart_str_appendc(&header, ':');
229: smart_str_append_unsigned(&header, resource->port);
230: smart_str_appendl(&header, " HTTP/1.0\r\n", sizeof(" HTTP/1.0\r\n")-1);
231:
232: /* check if we have Proxy-Authorization header */
233: if (context && php_stream_context_get_option(context, "http", "header", &tmpzval) == SUCCESS) {
234: char *s, *p;
235:
236: if (Z_TYPE_PP(tmpzval) == IS_ARRAY) {
237: HashPosition pos;
238: zval **tmpheader = NULL;
239:
240: for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(tmpzval), &pos);
241: SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(tmpzval), (void *)&tmpheader, &pos);
242: zend_hash_move_forward_ex(Z_ARRVAL_PP(tmpzval), &pos)) {
243: if (Z_TYPE_PP(tmpheader) == IS_STRING) {
244: s = Z_STRVAL_PP(tmpheader);
245: do {
246: while (*s == ' ' || *s == '\t') s++;
247: p = s;
248: while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++;
249: if (*p == ':') {
250: p++;
251: if (p - s == sizeof("Proxy-Authorization:") - 1 &&
252: zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1,
253: "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) {
254: while (*p != 0 && *p != '\r' && *p !='\n') p++;
255: smart_str_appendl(&header, s, p - s);
256: smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
257: goto finish;
258: } else {
259: while (*p != 0 && *p != '\r' && *p !='\n') p++;
260: }
261: }
262: s = p;
263: while (*s == '\r' || *s == '\n') s++;
264: } while (*s != 0);
265: }
266: }
267: } else if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval)) {
268: s = Z_STRVAL_PP(tmpzval);
269: do {
270: while (*s == ' ' || *s == '\t') s++;
271: p = s;
272: while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++;
273: if (*p == ':') {
274: p++;
275: if (p - s == sizeof("Proxy-Authorization:") - 1 &&
276: zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1,
277: "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) {
278: while (*p != 0 && *p != '\r' && *p !='\n') p++;
279: smart_str_appendl(&header, s, p - s);
280: smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
281: goto finish;
282: } else {
283: while (*p != 0 && *p != '\r' && *p !='\n') p++;
284: }
285: }
286: s = p;
287: while (*s == '\r' || *s == '\n') s++;
288: } while (*s != 0);
289: }
290: }
291: finish:
292: smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
293:
294: if (php_stream_write(stream, header.c, header.len) != header.len) {
295: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Cannot connect to HTTPS server through proxy");
296: php_stream_close(stream);
297: stream = NULL;
298: }
299: smart_str_free(&header);
300:
301: if (stream) {
302: char header_line[HTTP_HEADER_BLOCK_SIZE];
303:
304: /* get response header */
305: while (php_stream_gets(stream, header_line, HTTP_HEADER_BLOCK_SIZE-1) != NULL) {
306: if (header_line[0] == '\n' ||
307: header_line[0] == '\r' ||
308: header_line[0] == '\0') {
309: break;
310: }
311: }
312: }
313:
314: /* enable SSL transport layer */
315: if (stream) {
316: if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
317: php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
318: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Cannot connect to HTTPS server through proxy");
319: php_stream_close(stream);
320: stream = NULL;
321: }
322: }
323: }
324:
325: if (stream == NULL)
326: goto out;
327:
328: /* avoid buffering issues while reading header */
329: if (options & STREAM_WILL_CAST)
330: chunk_size = php_stream_set_chunk_size(stream, 1);
331:
332: /* avoid problems with auto-detecting when reading the headers -> the headers
333: * are always in canonical \r\n format */
334: eol_detect = stream->flags & (PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC);
335: stream->flags &= ~(PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC);
336:
337: php_stream_context_set(stream, context);
338:
339: php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0);
340:
341: if (header_init && context && php_stream_context_get_option(context, "http", "max_redirects", &tmpzval) == SUCCESS) {
342: SEPARATE_ZVAL(tmpzval);
343: convert_to_long_ex(tmpzval);
344: redirect_max = Z_LVAL_PP(tmpzval);
345: }
346:
347: if (context && php_stream_context_get_option(context, "http", "method", &tmpzval) == SUCCESS) {
348: if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval) > 0) {
349: /* As per the RFC, automatically redirected requests MUST NOT use other methods than
350: * GET and HEAD unless it can be confirmed by the user */
351: if (!redirected
352: || (Z_STRLEN_PP(tmpzval) == 3 && memcmp("GET", Z_STRVAL_PP(tmpzval), 3) == 0)
353: || (Z_STRLEN_PP(tmpzval) == 4 && memcmp("HEAD",Z_STRVAL_PP(tmpzval), 4) == 0)
354: ) {
355: scratch_len = strlen(path) + 29 + Z_STRLEN_PP(tmpzval);
356: scratch = emalloc(scratch_len);
357: strlcpy(scratch, Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval) + 1);
358: strncat(scratch, " ", 1);
359: }
360: }
361: }
362:
363: if (context && php_stream_context_get_option(context, "http", "protocol_version", &tmpzval) == SUCCESS) {
364: SEPARATE_ZVAL(tmpzval);
365: convert_to_double_ex(tmpzval);
366: protocol_version_len = spprintf(&protocol_version, 0, "%.1F", Z_DVAL_PP(tmpzval));
367: }
368:
369: if (!scratch) {
370: scratch_len = strlen(path) + 29 + protocol_version_len;
371: scratch = emalloc(scratch_len);
372: strncpy(scratch, "GET ", scratch_len);
373: }
374:
375: /* Should we send the entire path in the request line, default to no. */
376: if (!request_fulluri &&
377: context &&
378: php_stream_context_get_option(context, "http", "request_fulluri", &tmpzval) == SUCCESS) {
379: zval ztmp = **tmpzval;
380:
381: zval_copy_ctor(&ztmp);
382: convert_to_boolean(&ztmp);
383: request_fulluri = Z_BVAL(ztmp) ? 1 : 0;
384: zval_dtor(&ztmp);
385: }
386:
387: if (request_fulluri) {
388: /* Ask for everything */
389: strcat(scratch, path);
390: } else {
391: /* Send the traditional /path/to/file?query_string */
392:
393: /* file */
394: if (resource->path && *resource->path) {
395: strlcat(scratch, resource->path, scratch_len);
396: } else {
397: strlcat(scratch, "/", scratch_len);
398: }
399:
400: /* query string */
401: if (resource->query) {
402: strlcat(scratch, "?", scratch_len);
403: strlcat(scratch, resource->query, scratch_len);
404: }
405: }
406:
407: /* protocol version we are speaking */
408: if (protocol_version) {
409: strlcat(scratch, " HTTP/", scratch_len);
410: strlcat(scratch, protocol_version, scratch_len);
411: strlcat(scratch, "\r\n", scratch_len);
412: efree(protocol_version);
413: protocol_version = NULL;
414: } else {
415: strlcat(scratch, " HTTP/1.0\r\n", scratch_len);
416: }
417:
418: /* send it */
419: php_stream_write(stream, scratch, strlen(scratch));
420:
421: if (context && php_stream_context_get_option(context, "http", "header", &tmpzval) == SUCCESS) {
422: tmp = NULL;
423:
424: if (Z_TYPE_PP(tmpzval) == IS_ARRAY) {
425: HashPosition pos;
426: zval **tmpheader = NULL;
427: smart_str tmpstr = {0};
428:
429: for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(tmpzval), &pos);
430: SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(tmpzval), (void *)&tmpheader, &pos);
431: zend_hash_move_forward_ex(Z_ARRVAL_PP(tmpzval), &pos)
432: ) {
433: if (Z_TYPE_PP(tmpheader) == IS_STRING) {
434: smart_str_appendl(&tmpstr, Z_STRVAL_PP(tmpheader), Z_STRLEN_PP(tmpheader));
435: smart_str_appendl(&tmpstr, "\r\n", sizeof("\r\n") - 1);
436: }
437: }
438: smart_str_0(&tmpstr);
439: /* Remove newlines and spaces from start and end. there's at least one extra \r\n at the end that needs to go. */
440: if (tmpstr.c) {
441: tmp = php_trim(tmpstr.c, strlen(tmpstr.c), NULL, 0, NULL, 3 TSRMLS_CC);
442: smart_str_free(&tmpstr);
443: }
444: }
445: if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval)) {
446: /* Remove newlines and spaces from start and end php_trim will estrndup() */
447: tmp = php_trim(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval), NULL, 0, NULL, 3 TSRMLS_CC);
448: }
449: if (tmp && strlen(tmp) > 0) {
450: char *s;
451:
452: user_headers = estrdup(tmp);
453:
454: /* Make lowercase for easy comparison against 'standard' headers */
455: php_strtolower(tmp, strlen(tmp));
1.1.1.4 ! misho 456:
! 457: if (!header_init) {
! 458: /* strip POST headers on redirect */
! 459: strip_header(user_headers, tmp, "content-length:");
! 460: strip_header(user_headers, tmp, "content-type:");
! 461: }
! 462:
1.1 misho 463: if ((s = strstr(tmp, "user-agent:")) &&
464: (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
465: *(s-1) == '\t' || *(s-1) == ' ')) {
466: have_header |= HTTP_HEADER_USER_AGENT;
467: }
468: if ((s = strstr(tmp, "host:")) &&
469: (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
470: *(s-1) == '\t' || *(s-1) == ' ')) {
471: have_header |= HTTP_HEADER_HOST;
472: }
473: if ((s = strstr(tmp, "from:")) &&
474: (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
475: *(s-1) == '\t' || *(s-1) == ' ')) {
476: have_header |= HTTP_HEADER_FROM;
477: }
478: if ((s = strstr(tmp, "authorization:")) &&
479: (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
480: *(s-1) == '\t' || *(s-1) == ' ')) {
481: have_header |= HTTP_HEADER_AUTH;
482: }
483: if ((s = strstr(tmp, "content-length:")) &&
484: (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
485: *(s-1) == '\t' || *(s-1) == ' ')) {
486: have_header |= HTTP_HEADER_CONTENT_LENGTH;
487: }
488: if ((s = strstr(tmp, "content-type:")) &&
489: (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
490: *(s-1) == '\t' || *(s-1) == ' ')) {
491: have_header |= HTTP_HEADER_TYPE;
492: }
493: /* remove Proxy-Authorization header */
494: if (use_proxy && use_ssl && (s = strstr(tmp, "proxy-authorization:")) &&
495: (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
496: *(s-1) == '\t' || *(s-1) == ' ')) {
497: char *p = s + sizeof("proxy-authorization:") - 1;
498:
499: while (s > tmp && (*(s-1) == ' ' || *(s-1) == '\t')) s--;
500: while (*p != 0 && *p != '\r' && *p != '\n') p++;
501: while (*p == '\r' || *p == '\n') p++;
502: if (*p == 0) {
503: if (s == tmp) {
504: efree(user_headers);
505: user_headers = NULL;
506: } else {
507: while (s > tmp && (*(s-1) == '\r' || *(s-1) == '\n')) s--;
508: user_headers[s - tmp] = 0;
509: }
510: } else {
511: memmove(user_headers + (s - tmp), user_headers + (p - tmp), strlen(p) + 1);
512: }
513: }
514:
515: }
516: if (tmp) {
517: efree(tmp);
518: }
519: }
520:
521: /* auth header if it was specified */
522: if (((have_header & HTTP_HEADER_AUTH) == 0) && resource->user) {
523: /* decode the strings first */
524: php_url_decode(resource->user, strlen(resource->user));
525:
526: /* scratch is large enough, since it was made large enough for the whole URL */
527: strcpy(scratch, resource->user);
528: strcat(scratch, ":");
529:
530: /* Note: password is optional! */
531: if (resource->pass) {
532: php_url_decode(resource->pass, strlen(resource->pass));
533: strcat(scratch, resource->pass);
534: }
535:
536: tmp = (char*)php_base64_encode((unsigned char*)scratch, strlen(scratch), NULL);
537:
538: if (snprintf(scratch, scratch_len, "Authorization: Basic %s\r\n", tmp) > 0) {
539: php_stream_write(stream, scratch, strlen(scratch));
540: php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, NULL, 0);
541: }
542:
543: efree(tmp);
544: tmp = NULL;
545: }
546:
547: /* if the user has configured who they are, send a From: line */
1.1.1.2 misho 548: if (((have_header & HTTP_HEADER_FROM) == 0) && FG(from_address)) {
549: if (snprintf(scratch, scratch_len, "From: %s\r\n", FG(from_address)) > 0)
550: php_stream_write(stream, scratch, strlen(scratch));
1.1 misho 551: }
552:
553: /* Send Host: header so name-based virtual hosts work */
554: if ((have_header & HTTP_HEADER_HOST) == 0) {
555: if ((use_ssl && resource->port != 443 && resource->port != 0) ||
556: (!use_ssl && resource->port != 80 && resource->port != 0)) {
557: if (snprintf(scratch, scratch_len, "Host: %s:%i\r\n", resource->host, resource->port) > 0)
558: php_stream_write(stream, scratch, strlen(scratch));
559: } else {
560: if (snprintf(scratch, scratch_len, "Host: %s\r\n", resource->host) > 0) {
561: php_stream_write(stream, scratch, strlen(scratch));
562: }
563: }
564: }
565:
566: if (context &&
567: php_stream_context_get_option(context, "http", "user_agent", &ua_zval) == SUCCESS &&
568: Z_TYPE_PP(ua_zval) == IS_STRING) {
569: ua_str = Z_STRVAL_PP(ua_zval);
570: } else if (FG(user_agent)) {
571: ua_str = FG(user_agent);
572: }
573:
574: if (((have_header & HTTP_HEADER_USER_AGENT) == 0) && ua_str) {
575: #define _UA_HEADER "User-Agent: %s\r\n"
576: char *ua;
577: size_t ua_len;
578:
579: ua_len = sizeof(_UA_HEADER) + strlen(ua_str);
580:
581: /* ensure the header is only sent if user_agent is not blank */
582: if (ua_len > sizeof(_UA_HEADER)) {
583: ua = emalloc(ua_len + 1);
584: if ((ua_len = slprintf(ua, ua_len, _UA_HEADER, ua_str)) > 0) {
585: ua[ua_len] = 0;
586: php_stream_write(stream, ua, ua_len);
587: } else {
588: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot construct User-agent header");
589: }
590:
591: if (ua) {
592: efree(ua);
593: }
594: }
595: }
596:
597: if (user_headers) {
598: /* A bit weird, but some servers require that Content-Length be sent prior to Content-Type for POST
599: * see bug #44603 for details. Since Content-Type maybe part of user's headers we need to do this check first.
600: */
601: if (
602: header_init &&
603: context &&
604: !(have_header & HTTP_HEADER_CONTENT_LENGTH) &&
605: php_stream_context_get_option(context, "http", "content", &tmpzval) == SUCCESS &&
606: Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval) > 0
607: ) {
608: scratch_len = slprintf(scratch, scratch_len, "Content-Length: %d\r\n", Z_STRLEN_PP(tmpzval));
609: php_stream_write(stream, scratch, scratch_len);
610: have_header |= HTTP_HEADER_CONTENT_LENGTH;
611: }
612:
613: php_stream_write(stream, user_headers, strlen(user_headers));
614: php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
615: efree(user_headers);
616: }
617:
618: /* Request content, such as for POST requests */
619: if (header_init && context &&
620: php_stream_context_get_option(context, "http", "content", &tmpzval) == SUCCESS &&
621: Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval) > 0) {
622: if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) {
623: scratch_len = slprintf(scratch, scratch_len, "Content-Length: %d\r\n", Z_STRLEN_PP(tmpzval));
624: php_stream_write(stream, scratch, scratch_len);
625: }
626: if (!(have_header & HTTP_HEADER_TYPE)) {
627: php_stream_write(stream, "Content-Type: application/x-www-form-urlencoded\r\n",
628: sizeof("Content-Type: application/x-www-form-urlencoded\r\n") - 1);
629: php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Content-type not specified assuming application/x-www-form-urlencoded");
630: }
631: php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
632: php_stream_write(stream, Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval));
633: } else {
634: php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
635: }
636:
637: location[0] = '\0';
638:
639: if (!EG(active_symbol_table)) {
640: zend_rebuild_symbol_table(TSRMLS_C);
641: }
642:
643: if (header_init) {
644: zval *ztmp;
645: MAKE_STD_ZVAL(ztmp);
646: array_init(ztmp);
647: ZEND_SET_SYMBOL(EG(active_symbol_table), "http_response_header", ztmp);
648: }
649:
650: {
651: zval **rh;
652: zend_hash_find(EG(active_symbol_table), "http_response_header", sizeof("http_response_header"), (void **) &rh);
653: response_header = *rh;
654: }
655:
656: if (!php_stream_eof(stream)) {
657: size_t tmp_line_len;
658: /* get response header */
659:
660: if (php_stream_get_line(stream, tmp_line, sizeof(tmp_line) - 1, &tmp_line_len) != NULL) {
661: zval *http_response;
662:
663: if (tmp_line_len > 9) {
664: response_code = atoi(tmp_line + 9);
665: } else {
666: response_code = 0;
667: }
668: if (context && SUCCESS==php_stream_context_get_option(context, "http", "ignore_errors", &tmpzval)) {
669: ignore_errors = zend_is_true(*tmpzval);
670: }
671: /* when we request only the header, don't fail even on error codes */
672: if ((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) {
673: reqok = 1;
674: }
675: /* all status codes in the 2xx range are defined by the specification as successful;
676: * all status codes in the 3xx range are for redirection, and so also should never
677: * fail */
678: if (response_code >= 200 && response_code < 400) {
679: reqok = 1;
680: } else {
681: switch(response_code) {
682: case 403:
683: php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT,
684: tmp_line, response_code);
685: break;
686: default:
687: /* safety net in the event tmp_line == NULL */
688: if (!tmp_line_len) {
689: tmp_line[0] = '\0';
690: }
691: php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE,
692: tmp_line, response_code);
693: }
694: }
695: if (tmp_line[tmp_line_len - 1] == '\n') {
696: --tmp_line_len;
697: if (tmp_line[tmp_line_len - 1] == '\r') {
698: --tmp_line_len;
699: }
700: }
701: MAKE_STD_ZVAL(http_response);
702: ZVAL_STRINGL(http_response, tmp_line, tmp_line_len, 1);
703: zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_response, sizeof(zval *), NULL);
704: }
705: } else {
706: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP request failed, unexpected end of socket!");
707: goto out;
708: }
709:
710: /* read past HTTP headers */
711:
712: http_header_line = emalloc(HTTP_HEADER_BLOCK_SIZE);
713:
714: while (!body && !php_stream_eof(stream)) {
715: size_t http_header_line_length;
716: if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) && *http_header_line != '\n' && *http_header_line != '\r') {
717: char *e = http_header_line + http_header_line_length - 1;
718: if (*e != '\n') {
719: do { /* partial header */
720: if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) == NULL) {
721: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Failed to read HTTP headers");
722: goto out;
723: }
724: e = http_header_line + http_header_line_length - 1;
725: } while (*e != '\n');
726: continue;
727: }
728: while (*e == '\n' || *e == '\r') {
729: e--;
730: }
731: http_header_line_length = e - http_header_line + 1;
732: http_header_line[http_header_line_length] = '\0';
733:
734: if (!strncasecmp(http_header_line, "Location: ", 10)) {
735: if (context && php_stream_context_get_option(context, "http", "follow_location", &tmpzval) == SUCCESS) {
736: SEPARATE_ZVAL(tmpzval);
737: convert_to_long_ex(tmpzval);
738: follow_location = Z_LVAL_PP(tmpzval);
1.1.1.3 misho 739: } else if (!(response_code >= 300 && response_code < 304 || 307 == response_code)) {
740: /* we shouldn't redirect automatically
741: if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
742: see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1 */
743: follow_location = 0;
1.1 misho 744: }
745: strlcpy(location, http_header_line + 10, sizeof(location));
746: } else if (!strncasecmp(http_header_line, "Content-Type: ", 14)) {
747: php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_line + 14, 0);
748: } else if (!strncasecmp(http_header_line, "Content-Length: ", 16)) {
749: file_size = atoi(http_header_line + 16);
750: php_stream_notify_file_size(context, file_size, http_header_line, 0);
751: } else if (!strncasecmp(http_header_line, "Transfer-Encoding: chunked", sizeof("Transfer-Encoding: chunked"))) {
752:
753: /* create filter to decode response body */
754: if (!(options & STREAM_ONLY_GET_HEADERS)) {
755: long decode = 1;
756:
757: if (context && php_stream_context_get_option(context, "http", "auto_decode", &tmpzval) == SUCCESS) {
758: SEPARATE_ZVAL(tmpzval);
759: convert_to_boolean(*tmpzval);
760: decode = Z_LVAL_PP(tmpzval);
761: }
762: if (decode) {
763: transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream) TSRMLS_CC);
764: if (transfer_encoding) {
765: /* don't store transfer-encodeing header */
766: continue;
767: }
768: }
769: }
770: }
771:
772: if (http_header_line[0] == '\0') {
773: body = 1;
774: } else {
775: zval *http_header;
776:
777: MAKE_STD_ZVAL(http_header);
778:
779: ZVAL_STRINGL(http_header, http_header_line, http_header_line_length, 1);
780:
781: zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header, sizeof(zval *), NULL);
782: }
783: } else {
784: break;
785: }
786: }
787:
788: if (!reqok || (location[0] != '\0' && follow_location)) {
789: if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
790: goto out;
791: }
792:
793: if (location[0] != '\0')
794: php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0);
795:
796: php_stream_close(stream);
797: stream = NULL;
798:
799: if (location[0] != '\0') {
800:
801: char new_path[HTTP_HEADER_BLOCK_SIZE];
802: char loc_path[HTTP_HEADER_BLOCK_SIZE];
803:
804: *new_path='\0';
805: if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
806: strncasecmp(location, "https://", sizeof("https://")-1) &&
807: strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
808: strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
809: {
810: if (*location != '/') {
811: if (*(location+1) != '\0' && resource->path) {
812: char *s = strrchr(resource->path, '/');
813: if (!s) {
814: s = resource->path;
815: if (!s[0]) {
816: efree(s);
817: s = resource->path = estrdup("/");
818: } else {
819: *s = '/';
820: }
821: }
822: s[1] = '\0';
823: if (resource->path && *(resource->path) == '/' && *(resource->path + 1) == '\0') {
824: snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", resource->path, location);
825: } else {
826: snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", resource->path, location);
827: }
828: } else {
829: snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location);
830: }
831: } else {
832: strlcpy(loc_path, location, sizeof(loc_path));
833: }
834: if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
835: snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", resource->scheme, resource->host, resource->port, loc_path);
836: } else {
837: snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", resource->scheme, resource->host, loc_path);
838: }
839: } else {
840: strlcpy(new_path, location, sizeof(new_path));
841: }
842:
843: php_url_free(resource);
844: /* check for invalid redirection URLs */
845: if ((resource = php_url_parse(new_path)) == NULL) {
846: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Invalid redirect URL! %s", new_path);
847: goto out;
848: }
849:
850: #define CHECK_FOR_CNTRL_CHARS(val) { \
851: if (val) { \
852: unsigned char *s, *e; \
853: int l; \
854: l = php_url_decode(val, strlen(val)); \
855: s = (unsigned char*)val; e = s + l; \
856: while (s < e) { \
857: if (iscntrl(*s)) { \
858: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Invalid redirect URL! %s", new_path); \
859: goto out; \
860: } \
861: s++; \
862: } \
863: } \
864: }
865: /* check for control characters in login, password & path */
866: if (strncasecmp(new_path, "http://", sizeof("http://") - 1) || strncasecmp(new_path, "https://", sizeof("https://") - 1)) {
867: CHECK_FOR_CNTRL_CHARS(resource->user)
868: CHECK_FOR_CNTRL_CHARS(resource->pass)
869: CHECK_FOR_CNTRL_CHARS(resource->path)
870: }
871: stream = php_stream_url_wrap_http_ex(wrapper, new_path, mode, options, opened_path, context, --redirect_max, HTTP_WRAPPER_REDIRECTED STREAMS_CC TSRMLS_CC);
872: } else {
873: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP request failed! %s", tmp_line);
874: }
875: }
876: out:
877: if (protocol_version) {
878: efree(protocol_version);
879: }
880:
881: if (http_header_line) {
882: efree(http_header_line);
883: }
884:
885: if (scratch) {
886: efree(scratch);
887: }
888:
889: if (resource) {
890: php_url_free(resource);
891: }
892:
893: if (stream) {
894: if (header_init) {
895: zval_add_ref(&response_header);
896: stream->wrapperdata = response_header;
897: }
898: php_stream_notify_progress_init(context, 0, file_size);
899:
900: /* Restore original chunk size now that we're done with headers */
901: if (options & STREAM_WILL_CAST)
902: php_stream_set_chunk_size(stream, chunk_size);
903:
904: /* restore the users auto-detect-line-endings setting */
905: stream->flags |= eol_detect;
906:
907: /* as far as streams are concerned, we are now at the start of
908: * the stream */
909: stream->position = 0;
910:
911: /* restore mode */
912: strlcpy(stream->mode, mode, sizeof(stream->mode));
913:
914: if (transfer_encoding) {
915: php_stream_filter_append(&stream->readfilters, transfer_encoding);
916: }
917: } else if (transfer_encoding) {
918: php_stream_filter_free(transfer_encoding TSRMLS_CC);
919: }
920:
921: return stream;
922: }
923: /* }}} */
924:
925: php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) /* {{{ */
926: {
927: return php_stream_url_wrap_http_ex(wrapper, path, mode, options, opened_path, context, PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT STREAMS_CC TSRMLS_CC);
928: }
929: /* }}} */
930:
931: static int php_stream_http_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) /* {{{ */
932: {
933: /* one day, we could fill in the details based on Date: and Content-Length:
934: * headers. For now, we return with a failure code to prevent the underlying
935: * file's details from being used instead. */
936: return -1;
937: }
938: /* }}} */
939:
940: static php_stream_wrapper_ops http_stream_wops = {
941: php_stream_url_wrap_http,
942: NULL, /* stream_close */
943: php_stream_http_stream_stat,
944: NULL, /* stat_url */
945: NULL, /* opendir */
946: "http",
947: NULL, /* unlink */
948: NULL, /* rename */
949: NULL, /* mkdir */
950: NULL /* rmdir */
951: };
952:
953: PHPAPI php_stream_wrapper php_stream_http_wrapper = {
954: &http_stream_wops,
955: NULL,
956: 1 /* is_url */
957: };
958:
959: /*
960: * Local variables:
961: * tab-width: 4
962: * c-basic-offset: 4
963: * End:
964: * vim600: sw=4 ts=4 fdm=marker
965: * vim<600: sw=4 ts=4
966: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>