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