Back to index

php5  5.3.10
http_fopen_wrapper.c
Go to the documentation of this file.
00001 /*
00002    +----------------------------------------------------------------------+
00003    | PHP Version 5                                                        |
00004    +----------------------------------------------------------------------+
00005    | Copyright (c) 1997-2012 The PHP Group                                |
00006    +----------------------------------------------------------------------+
00007    | This source file is subject to version 3.01 of the PHP license,      |
00008    | that is bundled with this package in the file LICENSE, and is        |
00009    | available through the world-wide-web at the following url:           |
00010    | http://www.php.net/license/3_01.txt                                  |
00011    | If you did not receive a copy of the PHP license and are unable to   |
00012    | obtain it through the world-wide-web, please send a note to          |
00013    | license@php.net so we can mail you a copy immediately.               |
00014    +----------------------------------------------------------------------+
00015    | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
00016    |          Jim Winstead <jimw@php.net>                                 |
00017    |          Hartmut Holzgraefe <hholzgra@php.net>                       |
00018    |          Wez Furlong <wez@thebrainroom.com>                          |
00019    |          Sara Golemon <pollita@php.net>                              |
00020    +----------------------------------------------------------------------+
00021  */
00022 /* $Id: http_fopen_wrapper.c 321634 2012-01-01 13:15:04Z felipe $ */ 
00023 
00024 #include "php.h"
00025 #include "php_globals.h"
00026 #include "php_streams.h"
00027 #include "php_network.h"
00028 #include "php_ini.h"
00029 #include "ext/standard/basic_functions.h"
00030 #include "ext/standard/php_smart_str.h"
00031 
00032 #include <stdio.h>
00033 #include <stdlib.h>
00034 #include <errno.h>
00035 #include <sys/types.h>
00036 #include <sys/stat.h>
00037 #include <fcntl.h>
00038 
00039 #ifdef PHP_WIN32
00040 #define O_RDONLY _O_RDONLY
00041 #include "win32/param.h"
00042 #else
00043 #include <sys/param.h>
00044 #endif
00045 
00046 #include "php_standard.h"
00047 
00048 #include <sys/types.h>
00049 #if HAVE_SYS_SOCKET_H
00050 #include <sys/socket.h>
00051 #endif
00052 
00053 #ifdef PHP_WIN32
00054 #include <winsock2.h>
00055 #elif defined(NETWARE) && defined(USE_WINSOCK)
00056 #include <novsock2.h>
00057 #else
00058 #include <netinet/in.h>
00059 #include <netdb.h>
00060 #if HAVE_ARPA_INET_H
00061 #include <arpa/inet.h>
00062 #endif
00063 #endif
00064 
00065 #if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE)
00066 #undef AF_UNIX
00067 #endif
00068 
00069 #if defined(AF_UNIX)
00070 #include <sys/un.h>
00071 #endif
00072 
00073 #include "php_fopen_wrappers.h"
00074 
00075 #define HTTP_HEADER_BLOCK_SIZE            1024
00076 #define PHP_URL_REDIRECT_MAX              20
00077 #define HTTP_HEADER_USER_AGENT            1
00078 #define HTTP_HEADER_HOST                  2
00079 #define HTTP_HEADER_AUTH                  4
00080 #define HTTP_HEADER_FROM                  8
00081 #define HTTP_HEADER_CONTENT_LENGTH 16
00082 #define HTTP_HEADER_TYPE                  32
00083 
00084 #define HTTP_WRAPPER_HEADER_INIT    1
00085 #define HTTP_WRAPPER_REDIRECTED     2
00086 
00087 php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context, int redirect_max, int flags STREAMS_DC TSRMLS_DC) /* {{{ */
00088 {
00089        php_stream *stream = NULL;
00090        php_url *resource = NULL;
00091        int use_ssl;
00092        int use_proxy = 0;
00093        char *scratch = NULL;
00094        char *tmp = NULL;
00095        char *ua_str = NULL;
00096        zval **ua_zval = NULL, **tmpzval = NULL;
00097        int scratch_len = 0;
00098        int body = 0;
00099        char location[HTTP_HEADER_BLOCK_SIZE];
00100        zval *response_header = NULL;
00101        int reqok = 0;
00102        char *http_header_line = NULL;
00103        char tmp_line[128];
00104        size_t chunk_size = 0, file_size = 0;
00105        int eol_detect = 0;
00106        char *transport_string, *errstr = NULL;
00107        int transport_len, have_header = 0, request_fulluri = 0, ignore_errors = 0;
00108        char *protocol_version = NULL;
00109        int protocol_version_len = 3; /* Default: "1.0" */
00110        struct timeval timeout;
00111        char *user_headers = NULL;
00112        int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
00113        int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
00114        int follow_location = 1;
00115        php_stream_filter *transfer_encoding = NULL;
00116 
00117        tmp_line[0] = '\0';
00118 
00119        if (redirect_max < 1) {
00120               php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Redirection limit reached, aborting");
00121               return NULL;
00122        }
00123 
00124        resource = php_url_parse(path);
00125        if (resource == NULL) {
00126               return NULL;
00127        }
00128 
00129        if (strncasecmp(resource->scheme, "http", sizeof("http")) && strncasecmp(resource->scheme, "https", sizeof("https"))) {
00130               if (!context || 
00131                      php_stream_context_get_option(context, wrapper->wops->label, "proxy", &tmpzval) == FAILURE ||
00132                      Z_TYPE_PP(tmpzval) != IS_STRING ||
00133                      Z_STRLEN_PP(tmpzval) <= 0) {
00134                      php_url_free(resource);
00135                      return php_stream_open_wrapper_ex(path, mode, ENFORCE_SAFE_MODE | REPORT_ERRORS, NULL, context);
00136               }
00137               /* Called from a non-http wrapper with http proxying requested (i.e. ftp) */
00138               request_fulluri = 1;
00139               use_ssl = 0;
00140               use_proxy = 1;
00141 
00142               transport_len = Z_STRLEN_PP(tmpzval);
00143               transport_string = estrndup(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval));
00144        } else {
00145               /* Normal http request (possibly with proxy) */
00146        
00147               if (strpbrk(mode, "awx+")) {
00148                      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP wrapper does not support writeable connections");
00149                      php_url_free(resource);
00150                      return NULL;
00151               }
00152 
00153               use_ssl = resource->scheme && (strlen(resource->scheme) > 4) && resource->scheme[4] == 's';
00154               /* choose default ports */
00155               if (use_ssl && resource->port == 0)
00156                      resource->port = 443;
00157               else if (resource->port == 0)
00158                      resource->port = 80;
00159 
00160               if (context &&
00161                      php_stream_context_get_option(context, wrapper->wops->label, "proxy", &tmpzval) == SUCCESS &&
00162                      Z_TYPE_PP(tmpzval) == IS_STRING &&
00163                      Z_STRLEN_PP(tmpzval) > 0) {
00164                      use_proxy = 1;
00165                      transport_len = Z_STRLEN_PP(tmpzval);
00166                      transport_string = estrndup(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval));
00167               } else {
00168                      transport_len = spprintf(&transport_string, 0, "%s://%s:%d", use_ssl ? "ssl" : "tcp", resource->host, resource->port);
00169               }
00170        }
00171 
00172        if (context && php_stream_context_get_option(context, wrapper->wops->label, "timeout", &tmpzval) == SUCCESS) {
00173               SEPARATE_ZVAL(tmpzval);
00174               convert_to_double_ex(tmpzval);
00175               timeout.tv_sec = (time_t) Z_DVAL_PP(tmpzval);
00176               timeout.tv_usec = (size_t) ((Z_DVAL_PP(tmpzval) - timeout.tv_sec) * 1000000);
00177        } else {
00178               timeout.tv_sec = FG(default_socket_timeout);
00179               timeout.tv_usec = 0;
00180        }
00181 
00182        stream = php_stream_xport_create(transport_string, transport_len, options,
00183                      STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
00184                      NULL, &timeout, context, &errstr, NULL);
00185     
00186        if (stream) {
00187               php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &timeout);
00188        }
00189                      
00190        if (errstr) {
00191               php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", errstr);
00192               efree(errstr);
00193               errstr = NULL;
00194        }
00195 
00196        efree(transport_string);
00197 
00198        if (stream && use_proxy && use_ssl) {
00199               smart_str header = {0};
00200 
00201               smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1);
00202               smart_str_appends(&header, resource->host);
00203               smart_str_appendc(&header, ':');
00204               smart_str_append_unsigned(&header, resource->port);
00205               smart_str_appendl(&header, " HTTP/1.0\r\n", sizeof(" HTTP/1.0\r\n")-1);
00206 
00207            /* check if we have Proxy-Authorization header */
00208               if (context && php_stream_context_get_option(context, "http", "header", &tmpzval) == SUCCESS) {
00209                      char *s, *p;
00210 
00211                      if (Z_TYPE_PP(tmpzval) == IS_ARRAY) {
00212                             HashPosition pos;
00213                             zval **tmpheader = NULL;
00214 
00215                             for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(tmpzval), &pos);
00216                                    SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(tmpzval), (void *)&tmpheader, &pos);
00217                                    zend_hash_move_forward_ex(Z_ARRVAL_PP(tmpzval), &pos)) {
00218                                    if (Z_TYPE_PP(tmpheader) == IS_STRING) {
00219                                           s = Z_STRVAL_PP(tmpheader);
00220                                           do {
00221                                                  while (*s == ' ' || *s == '\t') s++;
00222                                                  p = s;
00223                                                  while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++;
00224                                                  if (*p == ':') {
00225                                                         p++;
00226                                                         if (p - s == sizeof("Proxy-Authorization:") - 1 &&
00227                                                             zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1,
00228                                                                 "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) {
00229                                                                while (*p != 0 && *p != '\r' && *p !='\n') p++;
00230                                                                smart_str_appendl(&header, s, p - s);
00231                                                                smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
00232                                                                goto finish;
00233                                                         } else {
00234                                                                while (*p != 0 && *p != '\r' && *p !='\n') p++;
00235                                                         }
00236                                                  }
00237                                                  s = p;
00238                                                  while (*s == '\r' || *s == '\n') s++;
00239                                           } while (*s != 0);
00240                                    }
00241                             }
00242                      } else if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval)) {
00243                             s = Z_STRVAL_PP(tmpzval);
00244                             do {
00245                                    while (*s == ' ' || *s == '\t') s++;
00246                                    p = s;
00247                                    while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++;
00248                                    if (*p == ':') {
00249                                           p++;
00250                                           if (p - s == sizeof("Proxy-Authorization:") - 1 &&
00251                                               zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1,
00252                                                   "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) {
00253                                                  while (*p != 0 && *p != '\r' && *p !='\n') p++;
00254                                                  smart_str_appendl(&header, s, p - s);
00255                                                  smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
00256                                                  goto finish;
00257                                           } else {
00258                                                  while (*p != 0 && *p != '\r' && *p !='\n') p++;
00259                                           }
00260                                    }
00261                                    s = p;
00262                                    while (*s == '\r' || *s == '\n') s++;
00263                             } while (*s != 0);
00264                      }
00265               }
00266 finish:
00267               smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
00268 
00269               if (php_stream_write(stream, header.c, header.len) != header.len) {
00270                      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Cannot connect to HTTPS server through proxy");
00271                      php_stream_close(stream);
00272                      stream = NULL;
00273               }
00274               smart_str_free(&header);
00275 
00276               if (stream) {
00277                      char header_line[HTTP_HEADER_BLOCK_SIZE];
00278 
00279                      /* get response header */
00280                      while (php_stream_gets(stream, header_line, HTTP_HEADER_BLOCK_SIZE-1) != NULL) {
00281                             if (header_line[0] == '\n' ||
00282                                 header_line[0] == '\r' ||
00283                                 header_line[0] == '\0') {
00284                               break;
00285                             }
00286                      }
00287               }
00288 
00289               /* enable SSL transport layer */
00290               if (stream) {
00291                      if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
00292                          php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
00293                             php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Cannot connect to HTTPS server through proxy");
00294                             php_stream_close(stream);
00295                             stream = NULL;
00296                      }
00297               }
00298        }
00299 
00300        if (stream == NULL)
00301               goto out;
00302 
00303        /* avoid buffering issues while reading header */
00304        if (options & STREAM_WILL_CAST)
00305               chunk_size = php_stream_set_chunk_size(stream, 1);
00306        
00307        /* avoid problems with auto-detecting when reading the headers -> the headers
00308         * are always in canonical \r\n format */
00309        eol_detect = stream->flags & (PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC);
00310        stream->flags &= ~(PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC);
00311 
00312        php_stream_context_set(stream, context);
00313 
00314        php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0);
00315 
00316        if (header_init && context && php_stream_context_get_option(context, "http", "max_redirects", &tmpzval) == SUCCESS) {
00317               SEPARATE_ZVAL(tmpzval);
00318               convert_to_long_ex(tmpzval);
00319               redirect_max = Z_LVAL_PP(tmpzval);
00320        }
00321 
00322        if (context && php_stream_context_get_option(context, "http", "method", &tmpzval) == SUCCESS) {
00323               if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval) > 0) {
00324                      /* As per the RFC, automatically redirected requests MUST NOT use other methods than
00325                       * GET and HEAD unless it can be confirmed by the user */
00326                      if (!redirected
00327                             || (Z_STRLEN_PP(tmpzval) == 3 && memcmp("GET", Z_STRVAL_PP(tmpzval), 3) == 0)
00328                             || (Z_STRLEN_PP(tmpzval) == 4 && memcmp("HEAD",Z_STRVAL_PP(tmpzval), 4) == 0)
00329                      ) {
00330                             scratch_len = strlen(path) + 29 + Z_STRLEN_PP(tmpzval);
00331                             scratch = emalloc(scratch_len);
00332                             strlcpy(scratch, Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval) + 1);
00333                             strncat(scratch, " ", 1);
00334                      }
00335               }
00336        }
00337  
00338        if (context && php_stream_context_get_option(context, "http", "protocol_version", &tmpzval) == SUCCESS) {
00339               SEPARATE_ZVAL(tmpzval);
00340               convert_to_double_ex(tmpzval);
00341               protocol_version_len = spprintf(&protocol_version, 0, "%.1F", Z_DVAL_PP(tmpzval));
00342        }
00343 
00344        if (!scratch) {
00345               scratch_len = strlen(path) + 29 + protocol_version_len;
00346               scratch = emalloc(scratch_len);
00347               strncpy(scratch, "GET ", scratch_len);
00348        }
00349 
00350        /* Should we send the entire path in the request line, default to no. */
00351        if (!request_fulluri &&
00352               context &&
00353               php_stream_context_get_option(context, "http", "request_fulluri", &tmpzval) == SUCCESS) {
00354               zval ztmp = **tmpzval;
00355 
00356               zval_copy_ctor(&ztmp);
00357               convert_to_boolean(&ztmp);
00358               request_fulluri = Z_BVAL(ztmp) ? 1 : 0;
00359               zval_dtor(&ztmp);
00360        }
00361 
00362        if (request_fulluri) {
00363               /* Ask for everything */
00364               strcat(scratch, path);
00365        } else {
00366               /* Send the traditional /path/to/file?query_string */
00367 
00368               /* file */
00369               if (resource->path && *resource->path) {
00370                      strlcat(scratch, resource->path, scratch_len);
00371               } else {
00372                      strlcat(scratch, "/", scratch_len);
00373               }
00374 
00375               /* query string */
00376               if (resource->query) {
00377                      strlcat(scratch, "?", scratch_len);
00378                      strlcat(scratch, resource->query, scratch_len);
00379               }
00380        }
00381 
00382        /* protocol version we are speaking */
00383        if (protocol_version) {
00384               strlcat(scratch, " HTTP/", scratch_len);
00385               strlcat(scratch, protocol_version, scratch_len);
00386               strlcat(scratch, "\r\n", scratch_len);
00387               efree(protocol_version);
00388               protocol_version = NULL;
00389        } else {
00390               strlcat(scratch, " HTTP/1.0\r\n", scratch_len);
00391        }
00392 
00393        /* send it */
00394        php_stream_write(stream, scratch, strlen(scratch));
00395 
00396        if (context && php_stream_context_get_option(context, "http", "header", &tmpzval) == SUCCESS) {
00397               tmp = NULL;
00398               
00399               if (Z_TYPE_PP(tmpzval) == IS_ARRAY) {
00400                      HashPosition pos;
00401                      zval **tmpheader = NULL;
00402                      smart_str tmpstr = {0};
00403 
00404                      for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(tmpzval), &pos);
00405                             SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(tmpzval), (void *)&tmpheader, &pos);
00406                             zend_hash_move_forward_ex(Z_ARRVAL_PP(tmpzval), &pos)
00407                      ) {
00408                             if (Z_TYPE_PP(tmpheader) == IS_STRING) {
00409                                    smart_str_appendl(&tmpstr, Z_STRVAL_PP(tmpheader), Z_STRLEN_PP(tmpheader));
00410                                    smart_str_appendl(&tmpstr, "\r\n", sizeof("\r\n") - 1);
00411                             }
00412                      }
00413                      smart_str_0(&tmpstr);
00414                      /* Remove newlines and spaces from start and end. there's at least one extra \r\n at the end that needs to go. */
00415                      if (tmpstr.c) {
00416                             tmp = php_trim(tmpstr.c, strlen(tmpstr.c), NULL, 0, NULL, 3 TSRMLS_CC);
00417                             smart_str_free(&tmpstr);
00418                      }
00419               }
00420               if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval)) {
00421                      /* Remove newlines and spaces from start and end php_trim will estrndup() */
00422                      tmp = php_trim(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval), NULL, 0, NULL, 3 TSRMLS_CC);
00423               }
00424               if (tmp && strlen(tmp) > 0) {
00425                      char *s;
00426 
00427                      if (!header_init) { /* Remove post headers for redirects */
00428                             int l = strlen(tmp);
00429                             char *s2, *tmp_c = estrdup(tmp);
00430                             
00431                             php_strtolower(tmp_c, l);
00432                             if ((s = strstr(tmp_c, "content-length:"))) {
00433                                    if ((s2 = memchr(s, '\n', tmp_c + l - s))) {
00434                                           int b = tmp_c + l - 1 - s2;
00435                                           memmove(tmp, tmp + (s2 + 1 - tmp_c), b);
00436                                           memmove(tmp_c, s2 + 1, b);
00437                                           
00438                                    } else {
00439                                           tmp[s - tmp_c] = *s = '\0';
00440                                    }
00441                                    l = strlen(tmp_c);
00442                             }
00443                             if ((s = strstr(tmp_c, "content-type:"))) {
00444                                    if ((s2 = memchr(s, '\n', tmp_c + l - s))) {
00445                                           memmove(tmp, tmp + (s2 + 1 - tmp_c), tmp_c + l - 1 - s2);
00446                                    } else {
00447                                           tmp[s - tmp_c] = '\0';
00448                                    }
00449                             }
00450 
00451                             efree(tmp_c);
00452                             tmp_c = php_trim(tmp, strlen(tmp), NULL, 0, NULL, 3 TSRMLS_CC);
00453                             efree(tmp);
00454                             tmp = tmp_c;
00455                      }
00456 
00457                      user_headers = estrdup(tmp);
00458 
00459                      /* Make lowercase for easy comparison against 'standard' headers */
00460                      php_strtolower(tmp, strlen(tmp));
00461                      if ((s = strstr(tmp, "user-agent:")) && 
00462                          (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' || 
00463                                       *(s-1) == '\t' || *(s-1) == ' ')) {
00464                              have_header |= HTTP_HEADER_USER_AGENT;
00465                      }
00466                      if ((s = strstr(tmp, "host:")) &&
00467                          (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' || 
00468                                       *(s-1) == '\t' || *(s-1) == ' ')) {
00469                              have_header |= HTTP_HEADER_HOST;
00470                      }
00471                      if ((s = strstr(tmp, "from:")) &&
00472                          (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' || 
00473                                       *(s-1) == '\t' || *(s-1) == ' ')) {
00474                              have_header |= HTTP_HEADER_FROM;
00475                             }
00476                      if ((s = strstr(tmp, "authorization:")) &&
00477                          (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' || 
00478                                       *(s-1) == '\t' || *(s-1) == ' ')) {
00479                              have_header |= HTTP_HEADER_AUTH;
00480                      }
00481                      if ((s = strstr(tmp, "content-length:")) &&
00482                          (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' || 
00483                                       *(s-1) == '\t' || *(s-1) == ' ')) {
00484                              have_header |= HTTP_HEADER_CONTENT_LENGTH;
00485                      }
00486                      if ((s = strstr(tmp, "content-type:")) &&
00487                          (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' || 
00488                                       *(s-1) == '\t' || *(s-1) == ' ')) {
00489                              have_header |= HTTP_HEADER_TYPE;
00490                      }
00491                      /* remove Proxy-Authorization header */
00492                      if (use_proxy && use_ssl && (s = strstr(tmp, "proxy-authorization:")) &&
00493                          (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' || 
00494                                       *(s-1) == '\t' || *(s-1) == ' ')) {
00495                             char *p = s + sizeof("proxy-authorization:") - 1;
00496                             
00497                             while (s > tmp && (*(s-1) == ' ' || *(s-1) == '\t')) s--;
00498                             while (*p != 0 && *p != '\r' && *p != '\n') p++;
00499                             while (*p == '\r' || *p == '\n') p++;
00500                             if (*p == 0) {
00501                                    if (s == tmp) {
00502                                           efree(user_headers);
00503                                           user_headers = NULL;
00504                                    } else {
00505                                           while (s > tmp && (*(s-1) == '\r' || *(s-1) == '\n')) s--;
00506                                           user_headers[s - tmp] = 0;
00507                                    }
00508                             } else {
00509                                    memmove(user_headers + (s - tmp), user_headers + (p - tmp), strlen(p) + 1);
00510                             }
00511                      }
00512 
00513               }
00514               if (tmp) {
00515                      efree(tmp);
00516               }
00517        }
00518 
00519        /* auth header if it was specified */
00520        if (((have_header & HTTP_HEADER_AUTH) == 0) && resource->user) {
00521               /* decode the strings first */
00522               php_url_decode(resource->user, strlen(resource->user));
00523 
00524               /* scratch is large enough, since it was made large enough for the whole URL */
00525               strcpy(scratch, resource->user);
00526               strcat(scratch, ":");
00527 
00528               /* Note: password is optional! */
00529               if (resource->pass) {
00530                      php_url_decode(resource->pass, strlen(resource->pass));
00531                      strcat(scratch, resource->pass);
00532               }
00533 
00534               tmp = (char*)php_base64_encode((unsigned char*)scratch, strlen(scratch), NULL);
00535               
00536               if (snprintf(scratch, scratch_len, "Authorization: Basic %s\r\n", tmp) > 0) {
00537                      php_stream_write(stream, scratch, strlen(scratch));
00538                      php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, NULL, 0);
00539               }
00540 
00541               efree(tmp);
00542               tmp = NULL;
00543        }
00544 
00545        /* if the user has configured who they are, send a From: line */
00546        {
00547               char *from_address = php_ini_string("from", sizeof("from"), 0);
00548               if (((have_header & HTTP_HEADER_FROM) == 0) && from_address && from_address[0] != '\0') {
00549                      if (snprintf(scratch, scratch_len, "From: %s\r\n", from_address) > 0)
00550                             php_stream_write(stream, scratch, strlen(scratch));
00551               }
00552        }
00553 
00554        /* Send Host: header so name-based virtual hosts work */
00555        if ((have_header & HTTP_HEADER_HOST) == 0) {
00556               if ((use_ssl && resource->port != 443 && resource->port != 0) || 
00557                      (!use_ssl && resource->port != 80 && resource->port != 0)) {
00558                      if (snprintf(scratch, scratch_len, "Host: %s:%i\r\n", resource->host, resource->port) > 0)
00559                             php_stream_write(stream, scratch, strlen(scratch));
00560               } else {
00561                      if (snprintf(scratch, scratch_len, "Host: %s\r\n", resource->host) > 0) {
00562                             php_stream_write(stream, scratch, strlen(scratch));
00563                      }
00564               }
00565        }
00566 
00567        if (context && 
00568            php_stream_context_get_option(context, "http", "user_agent", &ua_zval) == SUCCESS &&
00569               Z_TYPE_PP(ua_zval) == IS_STRING) {
00570               ua_str = Z_STRVAL_PP(ua_zval);
00571        } else if (FG(user_agent)) {
00572               ua_str = FG(user_agent);
00573        }
00574 
00575        if (((have_header & HTTP_HEADER_USER_AGENT) == 0) && ua_str) {
00576 #define _UA_HEADER "User-Agent: %s\r\n"
00577               char *ua;
00578               size_t ua_len;
00579               
00580               ua_len = sizeof(_UA_HEADER) + strlen(ua_str);
00581               
00582               /* ensure the header is only sent if user_agent is not blank */
00583               if (ua_len > sizeof(_UA_HEADER)) {
00584                      ua = emalloc(ua_len + 1);
00585                      if ((ua_len = slprintf(ua, ua_len, _UA_HEADER, ua_str)) > 0) {
00586                             ua[ua_len] = 0;
00587                             php_stream_write(stream, ua, ua_len);
00588                      } else {
00589                             php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot construct User-agent header");
00590                      }
00591 
00592                      if (ua) {
00593                             efree(ua);
00594                      }
00595               }      
00596        }
00597 
00598        if (user_headers) {
00599               /* A bit weird, but some servers require that Content-Length be sent prior to Content-Type for POST
00600                * see bug #44603 for details. Since Content-Type maybe part of user's headers we need to do this check first.
00601                */
00602               if (
00603                             header_init &&
00604                             context &&
00605                             !(have_header & HTTP_HEADER_CONTENT_LENGTH) &&
00606                             php_stream_context_get_option(context, "http", "content", &tmpzval) == SUCCESS &&
00607                             Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval) > 0
00608               ) {
00609                      scratch_len = slprintf(scratch, scratch_len, "Content-Length: %d\r\n", Z_STRLEN_PP(tmpzval));
00610                      php_stream_write(stream, scratch, scratch_len);
00611                      have_header |= HTTP_HEADER_CONTENT_LENGTH;
00612               }
00613 
00614               php_stream_write(stream, user_headers, strlen(user_headers));
00615               php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
00616               efree(user_headers);
00617        }
00618 
00619        /* Request content, such as for POST requests */
00620        if (header_init && context &&
00621               php_stream_context_get_option(context, "http", "content", &tmpzval) == SUCCESS &&
00622               Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval) > 0) {
00623               if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) {
00624                      scratch_len = slprintf(scratch, scratch_len, "Content-Length: %d\r\n", Z_STRLEN_PP(tmpzval));
00625                      php_stream_write(stream, scratch, scratch_len);
00626               }
00627               if (!(have_header & HTTP_HEADER_TYPE)) {
00628                      php_stream_write(stream, "Content-Type: application/x-www-form-urlencoded\r\n",
00629                             sizeof("Content-Type: application/x-www-form-urlencoded\r\n") - 1);
00630                      php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Content-type not specified assuming application/x-www-form-urlencoded");
00631               }
00632               php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
00633               php_stream_write(stream, Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval));
00634        } else {
00635               php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
00636        }
00637 
00638        location[0] = '\0';
00639 
00640        if (!EG(active_symbol_table)) {
00641               zend_rebuild_symbol_table(TSRMLS_C);
00642        }
00643 
00644        if (header_init) {
00645               zval *ztmp;
00646               MAKE_STD_ZVAL(ztmp);
00647               array_init(ztmp);
00648               ZEND_SET_SYMBOL(EG(active_symbol_table), "http_response_header", ztmp);
00649        }
00650 
00651        {
00652               zval **rh;
00653               zend_hash_find(EG(active_symbol_table), "http_response_header", sizeof("http_response_header"), (void **) &rh);
00654               response_header = *rh;
00655        }
00656 
00657        if (!php_stream_eof(stream)) {
00658               size_t tmp_line_len;
00659               /* get response header */
00660 
00661               if (php_stream_get_line(stream, tmp_line, sizeof(tmp_line) - 1, &tmp_line_len) != NULL) {
00662                      zval *http_response;
00663                      int response_code;
00664 
00665                      if (tmp_line_len > 9) {
00666                             response_code = atoi(tmp_line + 9);
00667                      } else {
00668                             response_code = 0;
00669                      }
00670                      if (context && SUCCESS==php_stream_context_get_option(context, "http", "ignore_errors", &tmpzval)) {
00671                             ignore_errors = zend_is_true(*tmpzval);
00672                      }
00673                      /* when we request only the header, don't fail even on error codes */
00674                      if ((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) {
00675                             reqok = 1;
00676                      }
00677                      /* all status codes in the 2xx range are defined by the specification as successful;
00678                       * all status codes in the 3xx range are for redirection, and so also should never
00679                       * fail */
00680                      if (response_code >= 200 && response_code < 400) {
00681                             reqok = 1;
00682                      } else {
00683                             switch(response_code) {
00684                                    case 403:
00685                                           php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT,
00686                                                         tmp_line, response_code);
00687                                           break;
00688                                    default:
00689                                           /* safety net in the event tmp_line == NULL */
00690                                           if (!tmp_line_len) {
00691                                                  tmp_line[0] = '\0';
00692                                           }
00693                                           php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE,
00694                                                         tmp_line, response_code);
00695                             }
00696                      }
00697                      if (tmp_line[tmp_line_len - 1] == '\n') {
00698                             --tmp_line_len;
00699                             if (tmp_line[tmp_line_len - 1] == '\r') {
00700                                    --tmp_line_len;
00701                             }
00702                      }
00703                      MAKE_STD_ZVAL(http_response);
00704                      ZVAL_STRINGL(http_response, tmp_line, tmp_line_len, 1);
00705                      zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_response, sizeof(zval *), NULL);
00706               }
00707        } else {
00708               php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP request failed, unexpected end of socket!");
00709               goto out;
00710        }
00711        
00712        /* read past HTTP headers */
00713        
00714        http_header_line = emalloc(HTTP_HEADER_BLOCK_SIZE);
00715 
00716        while (!body && !php_stream_eof(stream)) {
00717               size_t http_header_line_length;
00718               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') {
00719                      char *e = http_header_line + http_header_line_length - 1;
00720                      if (*e != '\n') {
00721                             do { /* partial header */
00722                                    if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) == NULL) {
00723                                           php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Failed to read HTTP headers");
00724                                           goto out;
00725                                    }
00726                                    e = http_header_line + http_header_line_length - 1;
00727                             } while (*e != '\n');
00728                             continue;
00729                      }
00730                      while (*e == '\n' || *e == '\r') {
00731                             e--;
00732                      }
00733                      http_header_line_length = e - http_header_line + 1;
00734                      http_header_line[http_header_line_length] = '\0';
00735 
00736                      if (!strncasecmp(http_header_line, "Location: ", 10)) {
00737                             if (context && php_stream_context_get_option(context, "http", "follow_location", &tmpzval) == SUCCESS) {
00738                                    SEPARATE_ZVAL(tmpzval);
00739                                    convert_to_long_ex(tmpzval);
00740                                    follow_location = Z_LVAL_PP(tmpzval);
00741                             }
00742                             strlcpy(location, http_header_line + 10, sizeof(location));
00743                      } else if (!strncasecmp(http_header_line, "Content-Type: ", 14)) {
00744                             php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_line + 14, 0);
00745                      } else if (!strncasecmp(http_header_line, "Content-Length: ", 16)) {
00746                             file_size = atoi(http_header_line + 16);
00747                             php_stream_notify_file_size(context, file_size, http_header_line, 0);
00748                      } else if (!strncasecmp(http_header_line, "Transfer-Encoding: chunked", sizeof("Transfer-Encoding: chunked"))) {
00749 
00750                             /* create filter to decode response body */
00751                             if (!(options & STREAM_ONLY_GET_HEADERS)) {
00752                                    long decode = 1;
00753 
00754                                    if (context && php_stream_context_get_option(context, "http", "auto_decode", &tmpzval) == SUCCESS) {
00755                                           SEPARATE_ZVAL(tmpzval);
00756                                           convert_to_boolean(*tmpzval);
00757                                           decode = Z_LVAL_PP(tmpzval);
00758                                    }
00759                                    if (decode) {
00760                                           transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream) TSRMLS_CC);
00761                                           if (transfer_encoding) {
00762                                                  /* don't store transfer-encodeing header */
00763                                                  continue;
00764                                           }
00765                                    }
00766                             }
00767                      }
00768 
00769                      if (http_header_line[0] == '\0') {
00770                             body = 1;
00771                      } else {
00772                             zval *http_header;
00773 
00774                             MAKE_STD_ZVAL(http_header);
00775 
00776                             ZVAL_STRINGL(http_header, http_header_line, http_header_line_length, 1);
00777                             
00778                             zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header, sizeof(zval *), NULL);
00779                      }
00780               } else {
00781                      break;
00782               }
00783        }
00784 
00785        if (!reqok || (location[0] != '\0' && follow_location)) {
00786               if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
00787                      goto out;
00788               }
00789 
00790               if (location[0] != '\0')
00791                      php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0);
00792 
00793               php_stream_close(stream);
00794               stream = NULL;
00795 
00796               if (location[0] != '\0') {
00797 
00798                      char new_path[HTTP_HEADER_BLOCK_SIZE];
00799                      char loc_path[HTTP_HEADER_BLOCK_SIZE];
00800 
00801                      *new_path='\0';
00802                      if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) && 
00803                                                  strncasecmp(location, "https://", sizeof("https://")-1) && 
00804                                                  strncasecmp(location, "ftp://", sizeof("ftp://")-1) && 
00805                                                  strncasecmp(location, "ftps://", sizeof("ftps://")-1))) 
00806                      {
00807                             if (*location != '/') {
00808                                    if (*(location+1) != '\0' && resource->path) {
00809                                           char *s = strrchr(resource->path, '/');
00810                                           if (!s) {
00811                                                  s = resource->path;
00812                                                  if (!s[0]) {
00813                                                         efree(s);
00814                                                         s = resource->path = estrdup("/");
00815                                                  } else {
00816                                                         *s = '/';
00817                                                  }
00818                                           }
00819                                           s[1] = '\0'; 
00820                                           if (resource->path && *(resource->path) == '/' && *(resource->path + 1) == '\0') {
00821                                                  snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", resource->path, location);
00822                                           } else {
00823                                                  snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", resource->path, location);
00824                                           }
00825                                    } else {
00826                                           snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location);
00827                                    }
00828                             } else {
00829                                    strlcpy(loc_path, location, sizeof(loc_path));
00830                             }
00831                             if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
00832                                    snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", resource->scheme, resource->host, resource->port, loc_path);
00833                             } else {
00834                                    snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", resource->scheme, resource->host, loc_path);
00835                             }
00836                      } else {
00837                             strlcpy(new_path, location, sizeof(new_path));
00838                      }
00839 
00840                      php_url_free(resource);
00841                      /* check for invalid redirection URLs */
00842                      if ((resource = php_url_parse(new_path)) == NULL) {
00843                             php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Invalid redirect URL! %s", new_path);
00844                             goto out;
00845                      }
00846 
00847 #define CHECK_FOR_CNTRL_CHARS(val) { \
00848        if (val) { \
00849               unsigned char *s, *e; \
00850               int l; \
00851               l = php_url_decode(val, strlen(val)); \
00852               s = (unsigned char*)val; e = s + l; \
00853               while (s < e) { \
00854                      if (iscntrl(*s)) { \
00855                             php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Invalid redirect URL! %s", new_path); \
00856                             goto out; \
00857                      } \
00858                      s++; \
00859               } \
00860        } \
00861 }
00862                      /* check for control characters in login, password & path */
00863                      if (strncasecmp(new_path, "http://", sizeof("http://") - 1) || strncasecmp(new_path, "https://", sizeof("https://") - 1)) {
00864                             CHECK_FOR_CNTRL_CHARS(resource->user)
00865                             CHECK_FOR_CNTRL_CHARS(resource->pass)
00866                             CHECK_FOR_CNTRL_CHARS(resource->path)
00867                      }
00868                      stream = php_stream_url_wrap_http_ex(wrapper, new_path, mode, options, opened_path, context, --redirect_max, HTTP_WRAPPER_REDIRECTED STREAMS_CC TSRMLS_CC);
00869               } else {
00870                      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP request failed! %s", tmp_line);
00871               }
00872        }
00873 out:
00874        if (protocol_version) {
00875               efree(protocol_version);
00876        }
00877 
00878        if (http_header_line) {
00879               efree(http_header_line);
00880        }
00881 
00882        if (scratch) {
00883               efree(scratch);
00884        }
00885 
00886        if (resource) {
00887               php_url_free(resource);
00888        }
00889 
00890        if (stream) {
00891               if (header_init) {
00892                      zval_add_ref(&response_header);
00893                      stream->wrapperdata = response_header;
00894               }
00895               php_stream_notify_progress_init(context, 0, file_size);
00896               
00897               /* Restore original chunk size now that we're done with headers */
00898               if (options & STREAM_WILL_CAST)
00899                      php_stream_set_chunk_size(stream, chunk_size);
00900 
00901               /* restore the users auto-detect-line-endings setting */
00902               stream->flags |= eol_detect;
00903               
00904               /* as far as streams are concerned, we are now at the start of
00905                * the stream */
00906               stream->position = 0;
00907 
00908               /* restore mode */
00909               strlcpy(stream->mode, mode, sizeof(stream->mode));
00910 
00911               if (transfer_encoding) {
00912                      php_stream_filter_append(&stream->readfilters, transfer_encoding);
00913               }
00914        } else if (transfer_encoding) {
00915               php_stream_filter_free(transfer_encoding TSRMLS_CC);
00916        }
00917 
00918        return stream;
00919 }
00920 /* }}} */
00921 
00922 php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) /* {{{ */
00923 {
00924        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);
00925 }
00926 /* }}} */
00927 
00928 static int php_stream_http_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) /* {{{ */
00929 {
00930        /* one day, we could fill in the details based on Date: and Content-Length:
00931         * headers.  For now, we return with a failure code to prevent the underlying
00932         * file's details from being used instead. */
00933        return -1;
00934 }
00935 /* }}} */
00936 
00937 static php_stream_wrapper_ops http_stream_wops = {
00938        php_stream_url_wrap_http,
00939        NULL, /* stream_close */
00940        php_stream_http_stream_stat,
00941        NULL, /* stat_url */
00942        NULL, /* opendir */
00943        "http",
00944        NULL, /* unlink */
00945        NULL, /* rename */
00946        NULL, /* mkdir */
00947        NULL  /* rmdir */
00948 };
00949 
00950 PHPAPI php_stream_wrapper php_stream_http_wrapper = {
00951        &http_stream_wops,
00952        NULL,
00953        1 /* is_url */
00954 };
00955 
00956 /*
00957  * Local variables:
00958  * tab-width: 4
00959  * c-basic-offset: 4
00960  * End:
00961  * vim600: sw=4 ts=4 fdm=marker
00962  * vim<600: sw=4 ts=4
00963  */