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