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

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