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