root/ext/standard/http_fopen_wrapper.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. strip_header
  2. php_stream_url_wrap_http_ex
  3. php_stream_url_wrap_http
  4. php_stream_http_stream_stat

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

/* [<][>][^][v][top][bottom][index][help] */