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