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