162 // The transports decrement this, store a copy of the original value for loop purposes. |
224 // The transports decrement this, store a copy of the original value for loop purposes. |
163 if ( ! isset( $r['_redirection'] ) ) |
225 if ( ! isset( $r['_redirection'] ) ) |
164 $r['_redirection'] = $r['redirection']; |
226 $r['_redirection'] = $r['redirection']; |
165 |
227 |
166 /** |
228 /** |
167 * Filter whether to preempt an HTTP request's return. |
229 * Filters whether to preempt an HTTP request's return value. |
168 * |
230 * |
169 * Returning a truthy value to the filter will short-circuit |
231 * Returning a non-false value from the filter will short-circuit the HTTP request and return |
170 * the HTTP request and return early with that value. |
232 * early with that value. A filter should return either: |
|
233 * |
|
234 * - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements |
|
235 * - A WP_Error instance |
|
236 * - boolean false (to avoid short-circuiting the response) |
|
237 * |
|
238 * Returning any other value may result in unexpected behaviour. |
171 * |
239 * |
172 * @since 2.9.0 |
240 * @since 2.9.0 |
173 * |
241 * |
174 * @param bool $preempt Whether to preempt an HTTP request return. Default false. |
242 * @param false|array|WP_Error $preempt Whether to preempt an HTTP request's return value. Default false. |
175 * @param array $r HTTP request arguments. |
243 * @param array $r HTTP request arguments. |
176 * @param string $url The request URL. |
244 * @param string $url The request URL. |
177 */ |
245 */ |
178 $pre = apply_filters( 'pre_http_request', false, $r, $url ); |
246 $pre = apply_filters( 'pre_http_request', false, $r, $url ); |
|
247 |
179 if ( false !== $pre ) |
248 if ( false !== $pre ) |
180 return $pre; |
249 return $pre; |
181 |
250 |
182 if ( function_exists( 'wp_kses_bad_protocol' ) ) { |
251 if ( function_exists( 'wp_kses_bad_protocol' ) ) { |
183 if ( $r['reject_unsafe_urls'] ) |
252 if ( $r['reject_unsafe_urls'] ) { |
184 $url = wp_http_validate_url( $url ); |
253 $url = wp_http_validate_url( $url ); |
|
254 } |
185 if ( $url ) { |
255 if ( $url ) { |
186 $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) ); |
256 $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) ); |
187 } |
257 } |
188 } |
258 } |
189 |
259 |
190 $arrURL = @parse_url( $url ); |
260 $arrURL = @parse_url( $url ); |
191 |
261 |
192 if ( empty( $url ) || empty( $arrURL['scheme'] ) ) |
262 if ( empty( $url ) || empty( $arrURL['scheme'] ) ) { |
193 return new WP_Error('http_request_failed', __('A valid URL was not provided.')); |
263 return new WP_Error('http_request_failed', __('A valid URL was not provided.')); |
194 |
264 } |
195 if ( $this->block_request( $url ) ) |
265 |
|
266 if ( $this->block_request( $url ) ) { |
196 return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) ); |
267 return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) ); |
197 |
268 } |
198 /* |
269 |
199 * Determine if this is a https call and pass that on to the transport functions |
270 // If we are streaming to a file but no filename was given drop it in the WP temp dir |
200 * so that we can blacklist the transports that do not support ssl verification |
271 // and pick its name using the basename of the $url |
|
272 if ( $r['stream'] ) { |
|
273 if ( empty( $r['filename'] ) ) { |
|
274 $r['filename'] = get_temp_dir() . basename( $url ); |
|
275 } |
|
276 |
|
277 // Force some settings if we are streaming to a file and check for existence and perms of destination directory |
|
278 $r['blocking'] = true; |
|
279 if ( ! wp_is_writable( dirname( $r['filename'] ) ) ) { |
|
280 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) ); |
|
281 } |
|
282 } |
|
283 |
|
284 if ( is_null( $r['headers'] ) ) { |
|
285 $r['headers'] = array(); |
|
286 } |
|
287 |
|
288 // WP allows passing in headers as a string, weirdly. |
|
289 if ( ! is_array( $r['headers'] ) ) { |
|
290 $processedHeaders = WP_Http::processHeaders( $r['headers'] ); |
|
291 $r['headers'] = $processedHeaders['headers']; |
|
292 } |
|
293 |
|
294 // Setup arguments |
|
295 $headers = $r['headers']; |
|
296 $data = $r['body']; |
|
297 $type = $r['method']; |
|
298 $options = array( |
|
299 'timeout' => $r['timeout'], |
|
300 'useragent' => $r['user-agent'], |
|
301 'blocking' => $r['blocking'], |
|
302 'hooks' => new WP_HTTP_Requests_Hooks( $url, $r ), |
|
303 ); |
|
304 |
|
305 // Ensure redirects follow browser behaviour. |
|
306 $options['hooks']->register( 'requests.before_redirect', array( get_class(), 'browser_redirect_compatibility' ) ); |
|
307 |
|
308 // Validate redirected URLs. |
|
309 if ( function_exists( 'wp_kses_bad_protocol' ) && $r['reject_unsafe_urls'] ) { |
|
310 $options['hooks']->register( 'requests.before_redirect', array( get_class(), 'validate_redirects' ) ); |
|
311 } |
|
312 |
|
313 if ( $r['stream'] ) { |
|
314 $options['filename'] = $r['filename']; |
|
315 } |
|
316 if ( empty( $r['redirection'] ) ) { |
|
317 $options['follow_redirects'] = false; |
|
318 } else { |
|
319 $options['redirects'] = $r['redirection']; |
|
320 } |
|
321 |
|
322 // Use byte limit, if we can |
|
323 if ( isset( $r['limit_response_size'] ) ) { |
|
324 $options['max_bytes'] = $r['limit_response_size']; |
|
325 } |
|
326 |
|
327 // If we've got cookies, use and convert them to Requests_Cookie. |
|
328 if ( ! empty( $r['cookies'] ) ) { |
|
329 $options['cookies'] = WP_Http::normalize_cookies( $r['cookies'] ); |
|
330 } |
|
331 |
|
332 // SSL certificate handling |
|
333 if ( ! $r['sslverify'] ) { |
|
334 $options['verify'] = false; |
|
335 $options['verifyname'] = false; |
|
336 } else { |
|
337 $options['verify'] = $r['sslcertificates']; |
|
338 } |
|
339 |
|
340 // All non-GET/HEAD requests should put the arguments in the form body. |
|
341 if ( 'HEAD' !== $type && 'GET' !== $type ) { |
|
342 $options['data_format'] = 'body'; |
|
343 } |
|
344 |
|
345 /** |
|
346 * Filters whether SSL should be verified for non-local requests. |
|
347 * |
|
348 * @since 2.8.0 |
|
349 * |
|
350 * @param bool $ssl_verify Whether to verify the SSL connection. Default true. |
201 */ |
351 */ |
202 $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl'; |
352 $options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'] ); |
203 |
353 |
204 // Determine if this request is to OUR install of WordPress. |
354 // Check for proxies. |
205 $homeURL = parse_url( get_bloginfo( 'url' ) ); |
355 $proxy = new WP_HTTP_Proxy(); |
206 $r['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] ); |
356 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { |
207 unset( $homeURL ); |
357 $options['proxy'] = new Requests_Proxy_HTTP( $proxy->host() . ':' . $proxy->port() ); |
208 |
358 |
209 /* |
359 if ( $proxy->use_authentication() ) { |
210 * If we are streaming to a file but no filename was given drop it in the WP temp dir |
360 $options['proxy']->use_authentication = true; |
211 * and pick its name using the basename of the $url. |
361 $options['proxy']->user = $proxy->username(); |
|
362 $options['proxy']->pass = $proxy->password(); |
|
363 } |
|
364 } |
|
365 |
|
366 // Avoid issues where mbstring.func_overload is enabled |
|
367 mbstring_binary_safe_encoding(); |
|
368 |
|
369 try { |
|
370 $requests_response = Requests::request( $url, $headers, $data, $type, $options ); |
|
371 |
|
372 // Convert the response into an array |
|
373 $http_response = new WP_HTTP_Requests_Response( $requests_response, $r['filename'] ); |
|
374 $response = $http_response->to_array(); |
|
375 |
|
376 // Add the original object to the array. |
|
377 $response['http_response'] = $http_response; |
|
378 } |
|
379 catch ( Requests_Exception $e ) { |
|
380 $response = new WP_Error( 'http_request_failed', $e->getMessage() ); |
|
381 } |
|
382 |
|
383 reset_mbstring_encoding(); |
|
384 |
|
385 /** |
|
386 * Fires after an HTTP API response is received and before the response is returned. |
|
387 * |
|
388 * @since 2.8.0 |
|
389 * |
|
390 * @param array|WP_Error $response HTTP response or WP_Error object. |
|
391 * @param string $context Context under which the hook is fired. |
|
392 * @param string $class HTTP transport used. |
|
393 * @param array $r HTTP request arguments. |
|
394 * @param string $url The request URL. |
212 */ |
395 */ |
213 if ( $r['stream'] && empty( $r['filename'] ) ) { |
396 do_action( 'http_api_debug', $response, 'response', 'Requests', $r, $url ); |
214 $r['filename'] = wp_unique_filename( get_temp_dir(), basename( $url ) ); |
397 if ( is_wp_error( $response ) ) { |
215 } |
398 return $response; |
216 |
399 } |
217 /* |
400 |
218 * Force some settings if we are streaming to a file and check for existence and perms |
401 if ( ! $r['blocking'] ) { |
219 * of destination directory. |
402 return array( |
|
403 'headers' => array(), |
|
404 'body' => '', |
|
405 'response' => array( |
|
406 'code' => false, |
|
407 'message' => false, |
|
408 ), |
|
409 'cookies' => array(), |
|
410 'http_response' => null, |
|
411 ); |
|
412 } |
|
413 |
|
414 /** |
|
415 * Filters the HTTP API response immediately before the response is returned. |
|
416 * |
|
417 * @since 2.9.0 |
|
418 * |
|
419 * @param array $response HTTP response. |
|
420 * @param array $r HTTP request arguments. |
|
421 * @param string $url The request URL. |
220 */ |
422 */ |
221 if ( $r['stream'] ) { |
423 return apply_filters( 'http_response', $response, $r, $url ); |
222 $r['blocking'] = true; |
424 } |
223 if ( ! wp_is_writable( dirname( $r['filename'] ) ) ) |
425 |
224 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) ); |
426 /** |
225 } |
427 * Normalizes cookies for using in Requests. |
226 |
428 * |
227 if ( is_null( $r['headers'] ) ) |
429 * @since 4.6.0 |
228 $r['headers'] = array(); |
430 * @static |
229 |
431 * |
230 if ( ! is_array( $r['headers'] ) ) { |
432 * @param array $cookies List of cookies to send with the request. |
231 $processedHeaders = self::processHeaders( $r['headers'], $url ); |
433 * @return Requests_Cookie_Jar Cookie holder object. |
232 $r['headers'] = $processedHeaders['headers']; |
434 */ |
233 } |
435 public static function normalize_cookies( $cookies ) { |
234 |
436 $cookie_jar = new Requests_Cookie_Jar(); |
235 if ( isset( $r['headers']['User-Agent'] ) ) { |
437 |
236 $r['user-agent'] = $r['headers']['User-Agent']; |
438 foreach ( $cookies as $name => $value ) { |
237 unset( $r['headers']['User-Agent'] ); |
439 if ( $value instanceof WP_Http_Cookie ) { |
238 } |
440 $cookie_jar[ $value->name ] = new Requests_Cookie( $value->name, $value->value, $value->get_attributes() ); |
239 |
441 } elseif ( is_scalar( $value ) ) { |
240 if ( isset( $r['headers']['user-agent'] ) ) { |
442 $cookie_jar[ $name ] = new Requests_Cookie( $name, $value ); |
241 $r['user-agent'] = $r['headers']['user-agent']; |
443 } |
242 unset( $r['headers']['user-agent'] ); |
444 } |
243 } |
445 |
244 |
446 return $cookie_jar; |
245 if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) { |
447 } |
246 $r['headers']['connection'] = 'close'; |
448 |
247 } |
449 /** |
248 |
450 * Match redirect behaviour to browser handling. |
249 // Construct Cookie: header if any cookies are set. |
451 * |
250 self::buildCookieHeader( $r ); |
452 * Changes 302 redirects from POST to GET to match browser handling. Per |
251 |
453 * RFC 7231, user agents can deviate from the strict reading of the |
252 // Avoid issues where mbstring.func_overload is enabled. |
454 * specification for compatibility purposes. |
253 mbstring_binary_safe_encoding(); |
455 * |
254 |
456 * @since 4.6.0 |
255 if ( ! isset( $r['headers']['Accept-Encoding'] ) ) { |
457 * @static |
256 if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) ) |
458 * |
257 $r['headers']['Accept-Encoding'] = $encoding; |
459 * @param string $location URL to redirect to. |
258 } |
460 * @param array $headers Headers for the redirect. |
259 |
461 * @param string|array $data Body to send with the request. |
260 if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) { |
462 * @param array $options Redirect request options. |
261 if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) { |
463 * @param Requests_Response $original Response object. |
262 $r['body'] = http_build_query( $r['body'], null, '&' ); |
464 */ |
263 |
465 public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) { |
264 if ( ! isset( $r['headers']['Content-Type'] ) ) |
466 // Browser compat |
265 $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' ); |
467 if ( $original->status_code === 302 ) { |
266 } |
468 $options['type'] = Requests::GET; |
267 |
469 } |
268 if ( '' === $r['body'] ) |
470 } |
269 $r['body'] = null; |
471 |
270 |
472 /** |
271 if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) ) |
473 * Validate redirected URLs. |
272 $r['headers']['Content-Length'] = strlen( $r['body'] ); |
474 * |
273 } |
475 * @since 4.7.5 |
274 |
476 * |
275 $response = $this->_dispatch_request( $url, $r ); |
477 * @throws Requests_Exception On unsuccessful URL validation |
276 |
478 * @param string $location URL to redirect to. |
277 reset_mbstring_encoding(); |
479 */ |
278 |
480 public static function validate_redirects( $location ) { |
279 if ( is_wp_error( $response ) ) |
481 if ( ! wp_http_validate_url( $location ) ) { |
280 return $response; |
482 throw new Requests_Exception( __('A valid URL was not provided.'), 'wp_http.redirect_failed_validation' ); |
281 |
483 } |
282 // Append cookies that were used in this request to the response |
|
283 if ( ! empty( $r['cookies'] ) ) { |
|
284 $cookies_set = wp_list_pluck( $response['cookies'], 'name' ); |
|
285 foreach ( $r['cookies'] as $cookie ) { |
|
286 if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) { |
|
287 $response['cookies'][] = $cookie; |
|
288 } |
|
289 } |
|
290 } |
|
291 |
|
292 return $response; |
|
293 } |
484 } |
294 |
485 |
295 /** |
486 /** |
296 * Tests which transports are capable of supporting the request. |
487 * Tests which transports are capable of supporting the request. |
297 * |
488 * |
298 * @since 3.2.0 |
489 * @since 3.2.0 |
299 * @access private |
|
300 * |
490 * |
301 * @param array $args Request arguments |
491 * @param array $args Request arguments |
302 * @param string $url URL to Request |
492 * @param string $url URL to Request |
303 * |
493 * |
304 * @return string|false Class name for the first transport that claims to support the request. False if no transport claims to support the request. |
494 * @return string|false Class name for the first transport that claims to support the request. False if no transport claims to support the request. |
305 */ |
495 */ |
306 public function _get_first_available_transport( $args, $url = null ) { |
496 public function _get_first_available_transport( $args, $url = null ) { |
|
497 $transports = array( 'curl', 'streams' ); |
|
498 |
307 /** |
499 /** |
308 * Filter which HTTP transports are available and in what order. |
500 * Filters which HTTP transports are available and in what order. |
309 * |
501 * |
310 * @since 3.7.0 |
502 * @since 3.7.0 |
311 * |
503 * |
312 * @param array $value Array of HTTP transports to check. Default array contains |
504 * @param array $transports Array of HTTP transports to check. Default array contains |
313 * 'curl', and 'streams', in that order. |
505 * 'curl', and 'streams', in that order. |
314 * @param array $args HTTP request arguments. |
506 * @param array $args HTTP request arguments. |
315 * @param string $url The URL to request. |
507 * @param string $url The URL to request. |
316 */ |
508 */ |
317 $request_order = apply_filters( 'http_api_transports', array( 'curl', 'streams' ), $args, $url ); |
509 $request_order = apply_filters( 'http_api_transports', $transports, $args, $url ); |
318 |
510 |
319 // Loop over each transport on each HTTP request looking for one which will serve this request's needs. |
511 // Loop over each transport on each HTTP request looking for one which will serve this request's needs. |
320 foreach ( $request_order as $transport ) { |
512 foreach ( $request_order as $transport ) { |
321 $class = 'WP_HTTP_' . $transport; |
513 if ( in_array( $transport, $transports ) ) { |
|
514 $transport = ucfirst( $transport ); |
|
515 } |
|
516 $class = 'WP_Http_' . $transport; |
322 |
517 |
323 // Check to see if this transport is a possibility, calls the transport statically. |
518 // Check to see if this transport is a possibility, calls the transport statically. |
324 if ( !call_user_func( array( $class, 'test' ), $args, $url ) ) |
519 if ( !call_user_func( array( $class, 'test' ), $args, $url ) ) |
325 continue; |
520 continue; |
326 |
521 |
862 |
1019 |
863 return false; |
1020 return false; |
864 } |
1021 } |
865 |
1022 |
866 } |
1023 } |
867 |
|
868 /** |
|
869 * HTTP request method uses PHP Streams to retrieve the url. |
|
870 * |
|
871 * @since 2.7.0 |
|
872 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client(). |
|
873 */ |
|
874 class WP_Http_Streams { |
|
875 /** |
|
876 * Send a HTTP request to a URI using PHP Streams. |
|
877 * |
|
878 * @see WP_Http::request For default options descriptions. |
|
879 * |
|
880 * @since 2.7.0 |
|
881 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client(). |
|
882 * |
|
883 * @access public |
|
884 * @param string $url The request URL. |
|
885 * @param string|array $args Optional. Override the defaults. |
|
886 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error |
|
887 */ |
|
888 public function request($url, $args = array()) { |
|
889 $defaults = array( |
|
890 'method' => 'GET', 'timeout' => 5, |
|
891 'redirection' => 5, 'httpversion' => '1.0', |
|
892 'blocking' => true, |
|
893 'headers' => array(), 'body' => null, 'cookies' => array() |
|
894 ); |
|
895 |
|
896 $r = wp_parse_args( $args, $defaults ); |
|
897 |
|
898 if ( isset( $r['headers']['User-Agent'] ) ) { |
|
899 $r['user-agent'] = $r['headers']['User-Agent']; |
|
900 unset( $r['headers']['User-Agent'] ); |
|
901 } elseif ( isset( $r['headers']['user-agent'] ) ) { |
|
902 $r['user-agent'] = $r['headers']['user-agent']; |
|
903 unset( $r['headers']['user-agent'] ); |
|
904 } |
|
905 |
|
906 // Construct Cookie: header if any cookies are set. |
|
907 WP_Http::buildCookieHeader( $r ); |
|
908 |
|
909 $arrURL = parse_url($url); |
|
910 |
|
911 $connect_host = $arrURL['host']; |
|
912 |
|
913 $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ); |
|
914 if ( ! isset( $arrURL['port'] ) ) { |
|
915 if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) { |
|
916 $arrURL['port'] = 443; |
|
917 $secure_transport = true; |
|
918 } else { |
|
919 $arrURL['port'] = 80; |
|
920 } |
|
921 } |
|
922 |
|
923 // Always pass a Path, defaulting to the root in cases such as http://example.com |
|
924 if ( ! isset( $arrURL['path'] ) ) { |
|
925 $arrURL['path'] = '/'; |
|
926 } |
|
927 |
|
928 if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) { |
|
929 if ( isset( $r['headers']['Host'] ) ) |
|
930 $arrURL['host'] = $r['headers']['Host']; |
|
931 else |
|
932 $arrURL['host'] = $r['headers']['host']; |
|
933 unset( $r['headers']['Host'], $r['headers']['host'] ); |
|
934 } |
|
935 |
|
936 /* |
|
937 * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect |
|
938 * to ::1, which fails when the server is not set up for it. For compatibility, always |
|
939 * connect to the IPv4 address. |
|
940 */ |
|
941 if ( 'localhost' == strtolower( $connect_host ) ) |
|
942 $connect_host = '127.0.0.1'; |
|
943 |
|
944 $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host; |
|
945 |
|
946 $is_local = isset( $r['local'] ) && $r['local']; |
|
947 $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify']; |
|
948 if ( $is_local ) { |
|
949 /** |
|
950 * Filter whether SSL should be verified for local requests. |
|
951 * |
|
952 * @since 2.8.0 |
|
953 * |
|
954 * @param bool $ssl_verify Whether to verify the SSL connection. Default true. |
|
955 */ |
|
956 $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); |
|
957 } elseif ( ! $is_local ) { |
|
958 /** |
|
959 * Filter whether SSL should be verified for non-local requests. |
|
960 * |
|
961 * @since 2.8.0 |
|
962 * |
|
963 * @param bool $ssl_verify Whether to verify the SSL connection. Default true. |
|
964 */ |
|
965 $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); |
|
966 } |
|
967 |
|
968 $proxy = new WP_HTTP_Proxy(); |
|
969 |
|
970 $context = stream_context_create( array( |
|
971 'ssl' => array( |
|
972 'verify_peer' => $ssl_verify, |
|
973 //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate() |
|
974 'capture_peer_cert' => $ssl_verify, |
|
975 'SNI_enabled' => true, |
|
976 'cafile' => $r['sslcertificates'], |
|
977 'allow_self_signed' => ! $ssl_verify, |
|
978 ) |
|
979 ) ); |
|
980 |
|
981 $timeout = (int) floor( $r['timeout'] ); |
|
982 $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000; |
|
983 $connect_timeout = max( $timeout, 1 ); |
|
984 |
|
985 // Store error number. |
|
986 $connection_error = null; |
|
987 |
|
988 // Store error string. |
|
989 $connection_error_str = null; |
|
990 |
|
991 if ( !WP_DEBUG ) { |
|
992 // In the event that the SSL connection fails, silence the many PHP Warnings. |
|
993 if ( $secure_transport ) |
|
994 $error_reporting = error_reporting(0); |
|
995 |
|
996 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) |
|
997 $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); |
|
998 else |
|
999 $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); |
|
1000 |
|
1001 if ( $secure_transport ) |
|
1002 error_reporting( $error_reporting ); |
|
1003 |
|
1004 } else { |
|
1005 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) |
|
1006 $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); |
|
1007 else |
|
1008 $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); |
|
1009 } |
|
1010 |
|
1011 if ( false === $handle ) { |
|
1012 // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken. |
|
1013 if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str ) |
|
1014 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) ); |
|
1015 |
|
1016 return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str ); |
|
1017 } |
|
1018 |
|
1019 // Verify that the SSL certificate is valid for this request. |
|
1020 if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) { |
|
1021 if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) ) |
|
1022 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) ); |
|
1023 } |
|
1024 |
|
1025 stream_set_timeout( $handle, $timeout, $utimeout ); |
|
1026 |
|
1027 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field. |
|
1028 $requestPath = $url; |
|
1029 else |
|
1030 $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' ); |
|
1031 |
|
1032 $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n"; |
|
1033 |
|
1034 $include_port_in_host_header = ( |
|
1035 ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) || |
|
1036 ( 'http' == $arrURL['scheme'] && 80 != $arrURL['port'] ) || |
|
1037 ( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] ) |
|
1038 ); |
|
1039 |
|
1040 if ( $include_port_in_host_header ) { |
|
1041 $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n"; |
|
1042 } else { |
|
1043 $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n"; |
|
1044 } |
|
1045 |
|
1046 if ( isset($r['user-agent']) ) |
|
1047 $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n"; |
|
1048 |
|
1049 if ( is_array($r['headers']) ) { |
|
1050 foreach ( (array) $r['headers'] as $header => $headerValue ) |
|
1051 $strHeaders .= $header . ': ' . $headerValue . "\r\n"; |
|
1052 } else { |
|
1053 $strHeaders .= $r['headers']; |
|
1054 } |
|
1055 |
|
1056 if ( $proxy->use_authentication() ) |
|
1057 $strHeaders .= $proxy->authentication_header() . "\r\n"; |
|
1058 |
|
1059 $strHeaders .= "\r\n"; |
|
1060 |
|
1061 if ( ! is_null($r['body']) ) |
|
1062 $strHeaders .= $r['body']; |
|
1063 |
|
1064 fwrite($handle, $strHeaders); |
|
1065 |
|
1066 if ( ! $r['blocking'] ) { |
|
1067 stream_set_blocking( $handle, 0 ); |
|
1068 fclose( $handle ); |
|
1069 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); |
|
1070 } |
|
1071 |
|
1072 $strResponse = ''; |
|
1073 $bodyStarted = false; |
|
1074 $keep_reading = true; |
|
1075 $block_size = 4096; |
|
1076 if ( isset( $r['limit_response_size'] ) ) |
|
1077 $block_size = min( $block_size, $r['limit_response_size'] ); |
|
1078 |
|
1079 // If streaming to a file setup the file handle. |
|
1080 if ( $r['stream'] ) { |
|
1081 if ( ! WP_DEBUG ) |
|
1082 $stream_handle = @fopen( $r['filename'], 'w+' ); |
|
1083 else |
|
1084 $stream_handle = fopen( $r['filename'], 'w+' ); |
|
1085 if ( ! $stream_handle ) |
|
1086 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); |
|
1087 |
|
1088 $bytes_written = 0; |
|
1089 while ( ! feof($handle) && $keep_reading ) { |
|
1090 $block = fread( $handle, $block_size ); |
|
1091 if ( ! $bodyStarted ) { |
|
1092 $strResponse .= $block; |
|
1093 if ( strpos( $strResponse, "\r\n\r\n" ) ) { |
|
1094 $process = WP_Http::processResponse( $strResponse ); |
|
1095 $bodyStarted = true; |
|
1096 $block = $process['body']; |
|
1097 unset( $strResponse ); |
|
1098 $process['body'] = ''; |
|
1099 } |
|
1100 } |
|
1101 |
|
1102 $this_block_size = strlen( $block ); |
|
1103 |
|
1104 if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] ) { |
|
1105 $this_block_size = ( $r['limit_response_size'] - $bytes_written ); |
|
1106 $block = substr( $block, 0, $this_block_size ); |
|
1107 } |
|
1108 |
|
1109 $bytes_written_to_file = fwrite( $stream_handle, $block ); |
|
1110 |
|
1111 if ( $bytes_written_to_file != $this_block_size ) { |
|
1112 fclose( $handle ); |
|
1113 fclose( $stream_handle ); |
|
1114 return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); |
|
1115 } |
|
1116 |
|
1117 $bytes_written += $bytes_written_to_file; |
|
1118 |
|
1119 $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size']; |
|
1120 } |
|
1121 |
|
1122 fclose( $stream_handle ); |
|
1123 |
|
1124 } else { |
|
1125 $header_length = 0; |
|
1126 while ( ! feof( $handle ) && $keep_reading ) { |
|
1127 $block = fread( $handle, $block_size ); |
|
1128 $strResponse .= $block; |
|
1129 if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) { |
|
1130 $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4; |
|
1131 $bodyStarted = true; |
|
1132 } |
|
1133 $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) ); |
|
1134 } |
|
1135 |
|
1136 $process = WP_Http::processResponse( $strResponse ); |
|
1137 unset( $strResponse ); |
|
1138 |
|
1139 } |
|
1140 |
|
1141 fclose( $handle ); |
|
1142 |
|
1143 $arrHeaders = WP_Http::processHeaders( $process['headers'], $url ); |
|
1144 |
|
1145 $response = array( |
|
1146 'headers' => $arrHeaders['headers'], |
|
1147 // Not yet processed. |
|
1148 'body' => null, |
|
1149 'response' => $arrHeaders['response'], |
|
1150 'cookies' => $arrHeaders['cookies'], |
|
1151 'filename' => $r['filename'] |
|
1152 ); |
|
1153 |
|
1154 // Handle redirects. |
|
1155 if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) |
|
1156 return $redirect_response; |
|
1157 |
|
1158 // If the body was chunk encoded, then decode it. |
|
1159 if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] ) |
|
1160 $process['body'] = WP_Http::chunkTransferDecode($process['body']); |
|
1161 |
|
1162 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) ) |
|
1163 $process['body'] = WP_Http_Encoding::decompress( $process['body'] ); |
|
1164 |
|
1165 if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] ) |
|
1166 $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] ); |
|
1167 |
|
1168 $response['body'] = $process['body']; |
|
1169 |
|
1170 return $response; |
|
1171 } |
|
1172 |
|
1173 /** |
|
1174 * Verifies the received SSL certificate against it's Common Names and subjectAltName fields |
|
1175 * |
|
1176 * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if |
|
1177 * the certificate is valid for the hostname which was requested. |
|
1178 * This function verifies the requested hostname against certificate's subjectAltName field, |
|
1179 * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used. |
|
1180 * |
|
1181 * IP Address support is included if the request is being made to an IP address. |
|
1182 * |
|
1183 * @since 3.7.0 |
|
1184 * @static |
|
1185 * |
|
1186 * @param stream $stream The PHP Stream which the SSL request is being made over |
|
1187 * @param string $host The hostname being requested |
|
1188 * @return bool If the cerficiate presented in $stream is valid for $host |
|
1189 */ |
|
1190 public static function verify_ssl_certificate( $stream, $host ) { |
|
1191 $context_options = stream_context_get_options( $stream ); |
|
1192 |
|
1193 if ( empty( $context_options['ssl']['peer_certificate'] ) ) |
|
1194 return false; |
|
1195 |
|
1196 $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] ); |
|
1197 if ( ! $cert ) |
|
1198 return false; |
|
1199 |
|
1200 /* |
|
1201 * If the request is being made to an IP address, we'll validate against IP fields |
|
1202 * in the cert (if they exist) |
|
1203 */ |
|
1204 $host_type = ( WP_HTTP::is_ip_address( $host ) ? 'ip' : 'dns' ); |
|
1205 |
|
1206 $certificate_hostnames = array(); |
|
1207 if ( ! empty( $cert['extensions']['subjectAltName'] ) ) { |
|
1208 $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] ); |
|
1209 foreach ( $match_against as $match ) { |
|
1210 list( $match_type, $match_host ) = explode( ':', $match ); |
|
1211 if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS: |
|
1212 $certificate_hostnames[] = strtolower( trim( $match_host ) ); |
|
1213 } |
|
1214 } elseif ( !empty( $cert['subject']['CN'] ) ) { |
|
1215 // Only use the CN when the certificate includes no subjectAltName extension. |
|
1216 $certificate_hostnames[] = strtolower( $cert['subject']['CN'] ); |
|
1217 } |
|
1218 |
|
1219 // Exact hostname/IP matches. |
|
1220 if ( in_array( strtolower( $host ), $certificate_hostnames ) ) |
|
1221 return true; |
|
1222 |
|
1223 // IP's can't be wildcards, Stop processing. |
|
1224 if ( 'ip' == $host_type ) |
|
1225 return false; |
|
1226 |
|
1227 // Test to see if the domain is at least 2 deep for wildcard support. |
|
1228 if ( substr_count( $host, '.' ) < 2 ) |
|
1229 return false; |
|
1230 |
|
1231 // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com. |
|
1232 $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host ); |
|
1233 |
|
1234 return in_array( strtolower( $wildcard_host ), $certificate_hostnames ); |
|
1235 } |
|
1236 |
|
1237 /** |
|
1238 * Whether this class can be used for retrieving a URL. |
|
1239 * |
|
1240 * @static |
|
1241 * @access public |
|
1242 * @since 2.7.0 |
|
1243 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client(). |
|
1244 * |
|
1245 * @return boolean False means this class can not be used, true means it can. |
|
1246 */ |
|
1247 public static function test( $args = array() ) { |
|
1248 if ( ! function_exists( 'stream_socket_client' ) ) |
|
1249 return false; |
|
1250 |
|
1251 $is_ssl = isset( $args['ssl'] ) && $args['ssl']; |
|
1252 |
|
1253 if ( $is_ssl ) { |
|
1254 if ( ! extension_loaded( 'openssl' ) ) |
|
1255 return false; |
|
1256 if ( ! function_exists( 'openssl_x509_parse' ) ) |
|
1257 return false; |
|
1258 } |
|
1259 |
|
1260 /** |
|
1261 * Filter whether streams can be used as a transport for retrieving a URL. |
|
1262 * |
|
1263 * @since 2.7.0 |
|
1264 * |
|
1265 * @param bool $use_class Whether the class can be used. Default true. |
|
1266 * @param array $args Request arguments. |
|
1267 */ |
|
1268 return apply_filters( 'use_streams_transport', true, $args ); |
|
1269 } |
|
1270 } |
|
1271 |
|
1272 /** |
|
1273 * Deprecated HTTP Transport method which used fsockopen. |
|
1274 * |
|
1275 * This class is not used, and is included for backwards compatibility only. |
|
1276 * All code should make use of WP_HTTP directly through it's API. |
|
1277 * |
|
1278 * @see WP_HTTP::request |
|
1279 * |
|
1280 * @since 2.7.0 |
|
1281 * @deprecated 3.7.0 Please use WP_HTTP::request() directly |
|
1282 */ |
|
1283 class WP_HTTP_Fsockopen extends WP_HTTP_Streams { |
|
1284 // For backwards compatibility for users who are using the class directly. |
|
1285 } |
|
1286 |
|
1287 /** |
|
1288 * HTTP request method uses Curl extension to retrieve the url. |
|
1289 * |
|
1290 * Requires the Curl extension to be installed. |
|
1291 * |
|
1292 * @package WordPress |
|
1293 * @subpackage HTTP |
|
1294 * @since 2.7.0 |
|
1295 */ |
|
1296 class WP_Http_Curl { |
|
1297 |
|
1298 /** |
|
1299 * Temporary header storage for during requests. |
|
1300 * |
|
1301 * @since 3.2.0 |
|
1302 * @access private |
|
1303 * @var string |
|
1304 */ |
|
1305 private $headers = ''; |
|
1306 |
|
1307 /** |
|
1308 * Temporary body storage for during requests. |
|
1309 * |
|
1310 * @since 3.6.0 |
|
1311 * @access private |
|
1312 * @var string |
|
1313 */ |
|
1314 private $body = ''; |
|
1315 |
|
1316 /** |
|
1317 * The maximum amount of data to receive from the remote server. |
|
1318 * |
|
1319 * @since 3.6.0 |
|
1320 * @access private |
|
1321 * @var int |
|
1322 */ |
|
1323 private $max_body_length = false; |
|
1324 |
|
1325 /** |
|
1326 * The file resource used for streaming to file. |
|
1327 * |
|
1328 * @since 3.6.0 |
|
1329 * @access private |
|
1330 * @var resource |
|
1331 */ |
|
1332 private $stream_handle = false; |
|
1333 |
|
1334 /** |
|
1335 * The total bytes written in the current request. |
|
1336 * |
|
1337 * @since 4.1.0 |
|
1338 * @access private |
|
1339 * @var int |
|
1340 */ |
|
1341 private $bytes_written_total = 0; |
|
1342 |
|
1343 /** |
|
1344 * Send a HTTP request to a URI using cURL extension. |
|
1345 * |
|
1346 * @access public |
|
1347 * @since 2.7.0 |
|
1348 * |
|
1349 * @param string $url The request URL. |
|
1350 * @param string|array $args Optional. Override the defaults. |
|
1351 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error |
|
1352 */ |
|
1353 public function request($url, $args = array()) { |
|
1354 $defaults = array( |
|
1355 'method' => 'GET', 'timeout' => 5, |
|
1356 'redirection' => 5, 'httpversion' => '1.0', |
|
1357 'blocking' => true, |
|
1358 'headers' => array(), 'body' => null, 'cookies' => array() |
|
1359 ); |
|
1360 |
|
1361 $r = wp_parse_args( $args, $defaults ); |
|
1362 |
|
1363 if ( isset( $r['headers']['User-Agent'] ) ) { |
|
1364 $r['user-agent'] = $r['headers']['User-Agent']; |
|
1365 unset( $r['headers']['User-Agent'] ); |
|
1366 } elseif ( isset( $r['headers']['user-agent'] ) ) { |
|
1367 $r['user-agent'] = $r['headers']['user-agent']; |
|
1368 unset( $r['headers']['user-agent'] ); |
|
1369 } |
|
1370 |
|
1371 // Construct Cookie: header if any cookies are set. |
|
1372 WP_Http::buildCookieHeader( $r ); |
|
1373 |
|
1374 $handle = curl_init(); |
|
1375 |
|
1376 // cURL offers really easy proxy support. |
|
1377 $proxy = new WP_HTTP_Proxy(); |
|
1378 |
|
1379 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { |
|
1380 |
|
1381 curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP ); |
|
1382 curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() ); |
|
1383 curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() ); |
|
1384 |
|
1385 if ( $proxy->use_authentication() ) { |
|
1386 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY ); |
|
1387 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() ); |
|
1388 } |
|
1389 } |
|
1390 |
|
1391 $is_local = isset($r['local']) && $r['local']; |
|
1392 $ssl_verify = isset($r['sslverify']) && $r['sslverify']; |
|
1393 if ( $is_local ) { |
|
1394 /** This filter is documented in wp-includes/class-http.php */ |
|
1395 $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); |
|
1396 } elseif ( ! $is_local ) { |
|
1397 /** This filter is documented in wp-includes/class-http.php */ |
|
1398 $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); |
|
1399 } |
|
1400 |
|
1401 /* |
|
1402 * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since. |
|
1403 * a value of 0 will allow an unlimited timeout. |
|
1404 */ |
|
1405 $timeout = (int) ceil( $r['timeout'] ); |
|
1406 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout ); |
|
1407 curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); |
|
1408 |
|
1409 curl_setopt( $handle, CURLOPT_URL, $url); |
|
1410 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true ); |
|
1411 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false ); |
|
1412 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify ); |
|
1413 curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] ); |
|
1414 curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] ); |
|
1415 |
|
1416 /* |
|
1417 * The option doesn't work with safe mode or when open_basedir is set, and there's |
|
1418 * a bug #17490 with redirected POST requests, so handle redirections outside Curl. |
|
1419 */ |
|
1420 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false ); |
|
1421 if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4 |
|
1422 curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS ); |
|
1423 |
|
1424 switch ( $r['method'] ) { |
|
1425 case 'HEAD': |
|
1426 curl_setopt( $handle, CURLOPT_NOBODY, true ); |
|
1427 break; |
|
1428 case 'POST': |
|
1429 curl_setopt( $handle, CURLOPT_POST, true ); |
|
1430 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); |
|
1431 break; |
|
1432 case 'PUT': |
|
1433 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' ); |
|
1434 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); |
|
1435 break; |
|
1436 default: |
|
1437 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] ); |
|
1438 if ( ! is_null( $r['body'] ) ) |
|
1439 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); |
|
1440 break; |
|
1441 } |
|
1442 |
|
1443 if ( true === $r['blocking'] ) { |
|
1444 curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); |
|
1445 curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) ); |
|
1446 } |
|
1447 |
|
1448 curl_setopt( $handle, CURLOPT_HEADER, false ); |
|
1449 |
|
1450 if ( isset( $r['limit_response_size'] ) ) |
|
1451 $this->max_body_length = intval( $r['limit_response_size'] ); |
|
1452 else |
|
1453 $this->max_body_length = false; |
|
1454 |
|
1455 // If streaming to a file open a file handle, and setup our curl streaming handler. |
|
1456 if ( $r['stream'] ) { |
|
1457 if ( ! WP_DEBUG ) |
|
1458 $this->stream_handle = @fopen( $r['filename'], 'w+' ); |
|
1459 else |
|
1460 $this->stream_handle = fopen( $r['filename'], 'w+' ); |
|
1461 if ( ! $this->stream_handle ) |
|
1462 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); |
|
1463 } else { |
|
1464 $this->stream_handle = false; |
|
1465 } |
|
1466 |
|
1467 if ( !empty( $r['headers'] ) ) { |
|
1468 // cURL expects full header strings in each element. |
|
1469 $headers = array(); |
|
1470 foreach ( $r['headers'] as $name => $value ) { |
|
1471 $headers[] = "{$name}: $value"; |
|
1472 } |
|
1473 curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); |
|
1474 } |
|
1475 |
|
1476 if ( $r['httpversion'] == '1.0' ) |
|
1477 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); |
|
1478 else |
|
1479 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 ); |
|
1480 |
|
1481 /** |
|
1482 * Fires before the cURL request is executed. |
|
1483 * |
|
1484 * Cookies are not currently handled by the HTTP API. This action allows |
|
1485 * plugins to handle cookies themselves. |
|
1486 * |
|
1487 * @since 2.8.0 |
|
1488 * |
|
1489 * @param resource &$handle The cURL handle returned by curl_init(). |
|
1490 * @param array $r The HTTP request arguments. |
|
1491 * @param string $url The request URL. |
|
1492 */ |
|
1493 do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) ); |
|
1494 |
|
1495 // We don't need to return the body, so don't. Just execute request and return. |
|
1496 if ( ! $r['blocking'] ) { |
|
1497 curl_exec( $handle ); |
|
1498 |
|
1499 if ( $curl_error = curl_error( $handle ) ) { |
|
1500 curl_close( $handle ); |
|
1501 return new WP_Error( 'http_request_failed', $curl_error ); |
|
1502 } |
|
1503 if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { |
|
1504 curl_close( $handle ); |
|
1505 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); |
|
1506 } |
|
1507 |
|
1508 curl_close( $handle ); |
|
1509 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); |
|
1510 } |
|
1511 |
|
1512 curl_exec( $handle ); |
|
1513 $theHeaders = WP_Http::processHeaders( $this->headers, $url ); |
|
1514 $theBody = $this->body; |
|
1515 $bytes_written_total = $this->bytes_written_total; |
|
1516 |
|
1517 $this->headers = ''; |
|
1518 $this->body = ''; |
|
1519 $this->bytes_written_total = 0; |
|
1520 |
|
1521 $curl_error = curl_errno( $handle ); |
|
1522 |
|
1523 // If an error occurred, or, no response. |
|
1524 if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) { |
|
1525 if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error ) { |
|
1526 if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) { |
|
1527 if ( $r['stream'] ) { |
|
1528 curl_close( $handle ); |
|
1529 fclose( $this->stream_handle ); |
|
1530 return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); |
|
1531 } else { |
|
1532 curl_close( $handle ); |
|
1533 return new WP_Error( 'http_request_failed', curl_error( $handle ) ); |
|
1534 } |
|
1535 } |
|
1536 } else { |
|
1537 if ( $curl_error = curl_error( $handle ) ) { |
|
1538 curl_close( $handle ); |
|
1539 return new WP_Error( 'http_request_failed', $curl_error ); |
|
1540 } |
|
1541 } |
|
1542 if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { |
|
1543 curl_close( $handle ); |
|
1544 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); |
|
1545 } |
|
1546 } |
|
1547 |
|
1548 curl_close( $handle ); |
|
1549 |
|
1550 if ( $r['stream'] ) |
|
1551 fclose( $this->stream_handle ); |
|
1552 |
|
1553 $response = array( |
|
1554 'headers' => $theHeaders['headers'], |
|
1555 'body' => null, |
|
1556 'response' => $theHeaders['response'], |
|
1557 'cookies' => $theHeaders['cookies'], |
|
1558 'filename' => $r['filename'] |
|
1559 ); |
|
1560 |
|
1561 // Handle redirects. |
|
1562 if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) |
|
1563 return $redirect_response; |
|
1564 |
|
1565 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) ) |
|
1566 $theBody = WP_Http_Encoding::decompress( $theBody ); |
|
1567 |
|
1568 $response['body'] = $theBody; |
|
1569 |
|
1570 return $response; |
|
1571 } |
|
1572 |
|
1573 /** |
|
1574 * Grab the headers of the cURL request |
|
1575 * |
|
1576 * Each header is sent individually to this callback, so we append to the $header property for temporary storage |
|
1577 * |
|
1578 * @since 3.2.0 |
|
1579 * @access private |
|
1580 * @return int |
|
1581 */ |
|
1582 private function stream_headers( $handle, $headers ) { |
|
1583 $this->headers .= $headers; |
|
1584 return strlen( $headers ); |
|
1585 } |
|
1586 |
|
1587 /** |
|
1588 * Grab the body of the cURL request |
|
1589 * |
|
1590 * The contents of the document are passed in chunks, so we append to the $body property for temporary storage. |
|
1591 * Returning a length shorter than the length of $data passed in will cause cURL to abort the request with CURLE_WRITE_ERROR |
|
1592 * |
|
1593 * @since 3.6.0 |
|
1594 * @access private |
|
1595 * @return int |
|
1596 */ |
|
1597 private function stream_body( $handle, $data ) { |
|
1598 $data_length = strlen( $data ); |
|
1599 |
|
1600 if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) { |
|
1601 $data_length = ( $this->max_body_length - $this->bytes_written_total ); |
|
1602 $data = substr( $data, 0, $data_length ); |
|
1603 } |
|
1604 |
|
1605 if ( $this->stream_handle ) { |
|
1606 $bytes_written = fwrite( $this->stream_handle, $data ); |
|
1607 } else { |
|
1608 $this->body .= $data; |
|
1609 $bytes_written = $data_length; |
|
1610 } |
|
1611 |
|
1612 $this->bytes_written_total += $bytes_written; |
|
1613 |
|
1614 // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR. |
|
1615 return $bytes_written; |
|
1616 } |
|
1617 |
|
1618 /** |
|
1619 * Whether this class can be used for retrieving an URL. |
|
1620 * |
|
1621 * @static |
|
1622 * @since 2.7.0 |
|
1623 * |
|
1624 * @return boolean False means this class can not be used, true means it can. |
|
1625 */ |
|
1626 public static function test( $args = array() ) { |
|
1627 if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) |
|
1628 return false; |
|
1629 |
|
1630 $is_ssl = isset( $args['ssl'] ) && $args['ssl']; |
|
1631 |
|
1632 if ( $is_ssl ) { |
|
1633 $curl_version = curl_version(); |
|
1634 // Check whether this cURL version support SSL requests. |
|
1635 if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) |
|
1636 return false; |
|
1637 } |
|
1638 |
|
1639 /** |
|
1640 * Filter whether cURL can be used as a transport for retrieving a URL. |
|
1641 * |
|
1642 * @since 2.7.0 |
|
1643 * |
|
1644 * @param bool $use_class Whether the class can be used. Default true. |
|
1645 * @param array $args An array of request arguments. |
|
1646 */ |
|
1647 return apply_filters( 'use_curl_transport', true, $args ); |
|
1648 } |
|
1649 } |
|
1650 |
|
1651 /** |
|
1652 * Adds Proxy support to the WordPress HTTP API. |
|
1653 * |
|
1654 * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to |
|
1655 * enable proxy support. There are also a few filters that plugins can hook into for some of the |
|
1656 * constants. |
|
1657 * |
|
1658 * Please note that only BASIC authentication is supported by most transports. |
|
1659 * cURL MAY support more methods (such as NTLM authentication) depending on your environment. |
|
1660 * |
|
1661 * The constants are as follows: |
|
1662 * <ol> |
|
1663 * <li>WP_PROXY_HOST - Enable proxy support and host for connecting.</li> |
|
1664 * <li>WP_PROXY_PORT - Proxy port for connection. No default, must be defined.</li> |
|
1665 * <li>WP_PROXY_USERNAME - Proxy username, if it requires authentication.</li> |
|
1666 * <li>WP_PROXY_PASSWORD - Proxy password, if it requires authentication.</li> |
|
1667 * <li>WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy. |
|
1668 * You do not need to have localhost and the blog host in this list, because they will not be passed |
|
1669 * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org</li> |
|
1670 * </ol> |
|
1671 * |
|
1672 * An example can be as seen below. |
|
1673 * |
|
1674 * define('WP_PROXY_HOST', '192.168.84.101'); |
|
1675 * define('WP_PROXY_PORT', '8080'); |
|
1676 * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org'); |
|
1677 * |
|
1678 * @link https://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress. |
|
1679 * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS |
|
1680 * @since 2.8.0 |
|
1681 */ |
|
1682 class WP_HTTP_Proxy { |
|
1683 |
|
1684 /** |
|
1685 * Whether proxy connection should be used. |
|
1686 * |
|
1687 * @since 2.8.0 |
|
1688 * |
|
1689 * @use WP_PROXY_HOST |
|
1690 * @use WP_PROXY_PORT |
|
1691 * |
|
1692 * @return bool |
|
1693 */ |
|
1694 public function is_enabled() { |
|
1695 return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT'); |
|
1696 } |
|
1697 |
|
1698 /** |
|
1699 * Whether authentication should be used. |
|
1700 * |
|
1701 * @since 2.8.0 |
|
1702 * |
|
1703 * @use WP_PROXY_USERNAME |
|
1704 * @use WP_PROXY_PASSWORD |
|
1705 * |
|
1706 * @return bool |
|
1707 */ |
|
1708 public function use_authentication() { |
|
1709 return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD'); |
|
1710 } |
|
1711 |
|
1712 /** |
|
1713 * Retrieve the host for the proxy server. |
|
1714 * |
|
1715 * @since 2.8.0 |
|
1716 * |
|
1717 * @return string |
|
1718 */ |
|
1719 public function host() { |
|
1720 if ( defined('WP_PROXY_HOST') ) |
|
1721 return WP_PROXY_HOST; |
|
1722 |
|
1723 return ''; |
|
1724 } |
|
1725 |
|
1726 /** |
|
1727 * Retrieve the port for the proxy server. |
|
1728 * |
|
1729 * @since 2.8.0 |
|
1730 * |
|
1731 * @return string |
|
1732 */ |
|
1733 public function port() { |
|
1734 if ( defined('WP_PROXY_PORT') ) |
|
1735 return WP_PROXY_PORT; |
|
1736 |
|
1737 return ''; |
|
1738 } |
|
1739 |
|
1740 /** |
|
1741 * Retrieve the username for proxy authentication. |
|
1742 * |
|
1743 * @since 2.8.0 |
|
1744 * |
|
1745 * @return string |
|
1746 */ |
|
1747 public function username() { |
|
1748 if ( defined('WP_PROXY_USERNAME') ) |
|
1749 return WP_PROXY_USERNAME; |
|
1750 |
|
1751 return ''; |
|
1752 } |
|
1753 |
|
1754 /** |
|
1755 * Retrieve the password for proxy authentication. |
|
1756 * |
|
1757 * @since 2.8.0 |
|
1758 * |
|
1759 * @return string |
|
1760 */ |
|
1761 public function password() { |
|
1762 if ( defined('WP_PROXY_PASSWORD') ) |
|
1763 return WP_PROXY_PASSWORD; |
|
1764 |
|
1765 return ''; |
|
1766 } |
|
1767 |
|
1768 /** |
|
1769 * Retrieve authentication string for proxy authentication. |
|
1770 * |
|
1771 * @since 2.8.0 |
|
1772 * |
|
1773 * @return string |
|
1774 */ |
|
1775 public function authentication() { |
|
1776 return $this->username() . ':' . $this->password(); |
|
1777 } |
|
1778 |
|
1779 /** |
|
1780 * Retrieve header string for proxy authentication. |
|
1781 * |
|
1782 * @since 2.8.0 |
|
1783 * |
|
1784 * @return string |
|
1785 */ |
|
1786 public function authentication_header() { |
|
1787 return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() ); |
|
1788 } |
|
1789 |
|
1790 /** |
|
1791 * Whether URL should be sent through the proxy server. |
|
1792 * |
|
1793 * We want to keep localhost and the blog URL from being sent through the proxy server, because |
|
1794 * some proxies can not handle this. We also have the constant available for defining other |
|
1795 * hosts that won't be sent through the proxy. |
|
1796 * |
|
1797 * @since 2.8.0 |
|
1798 * |
|
1799 * @param string $uri URI to check. |
|
1800 * @return bool True, to send through the proxy and false if, the proxy should not be used. |
|
1801 */ |
|
1802 public function send_through_proxy( $uri ) { |
|
1803 /* |
|
1804 * parse_url() only handles http, https type URLs, and will emit E_WARNING on failure. |
|
1805 * This will be displayed on blogs, which is not reasonable. |
|
1806 */ |
|
1807 $check = @parse_url($uri); |
|
1808 |
|
1809 // Malformed URL, can not process, but this could mean ssl, so let through anyway. |
|
1810 if ( $check === false ) |
|
1811 return true; |
|
1812 |
|
1813 $home = parse_url( get_option('siteurl') ); |
|
1814 |
|
1815 /** |
|
1816 * Filter whether to preempt sending the request through the proxy server. |
|
1817 * |
|
1818 * Returning false will bypass the proxy; returning true will send |
|
1819 * the request through the proxy. Returning null bypasses the filter. |
|
1820 * |
|
1821 * @since 3.5.0 |
|
1822 * |
|
1823 * @param null $override Whether to override the request result. Default null. |
|
1824 * @param string $uri URL to check. |
|
1825 * @param array $check Associative array result of parsing the URI. |
|
1826 * @param array $home Associative array result of parsing the site URL. |
|
1827 */ |
|
1828 $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home ); |
|
1829 if ( ! is_null( $result ) ) |
|
1830 return $result; |
|
1831 |
|
1832 if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) |
|
1833 return false; |
|
1834 |
|
1835 if ( !defined('WP_PROXY_BYPASS_HOSTS') ) |
|
1836 return true; |
|
1837 |
|
1838 static $bypass_hosts; |
|
1839 static $wildcard_regex = false; |
|
1840 if ( null == $bypass_hosts ) { |
|
1841 $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS); |
|
1842 |
|
1843 if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) { |
|
1844 $wildcard_regex = array(); |
|
1845 foreach ( $bypass_hosts as $host ) |
|
1846 $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); |
|
1847 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i'; |
|
1848 } |
|
1849 } |
|
1850 |
|
1851 if ( !empty($wildcard_regex) ) |
|
1852 return !preg_match($wildcard_regex, $check['host']); |
|
1853 else |
|
1854 return !in_array( $check['host'], $bypass_hosts ); |
|
1855 } |
|
1856 } |
|
1857 /** |
|
1858 * Internal representation of a single cookie. |
|
1859 * |
|
1860 * Returned cookies are represented using this class, and when cookies are set, if they are not |
|
1861 * already a WP_Http_Cookie() object, then they are turned into one. |
|
1862 * |
|
1863 * @todo The WordPress convention is to use underscores instead of camelCase for function and method |
|
1864 * names. Need to switch to use underscores instead for the methods. |
|
1865 * |
|
1866 * @package WordPress |
|
1867 * @subpackage HTTP |
|
1868 * @since 2.8.0 |
|
1869 */ |
|
1870 class WP_Http_Cookie { |
|
1871 |
|
1872 /** |
|
1873 * Cookie name. |
|
1874 * |
|
1875 * @since 2.8.0 |
|
1876 * @var string |
|
1877 */ |
|
1878 public $name; |
|
1879 |
|
1880 /** |
|
1881 * Cookie value. |
|
1882 * |
|
1883 * @since 2.8.0 |
|
1884 * @var string |
|
1885 */ |
|
1886 public $value; |
|
1887 |
|
1888 /** |
|
1889 * When the cookie expires. |
|
1890 * |
|
1891 * @since 2.8.0 |
|
1892 * @var string |
|
1893 */ |
|
1894 public $expires; |
|
1895 |
|
1896 /** |
|
1897 * Cookie URL path. |
|
1898 * |
|
1899 * @since 2.8.0 |
|
1900 * @var string |
|
1901 */ |
|
1902 public $path; |
|
1903 |
|
1904 /** |
|
1905 * Cookie Domain. |
|
1906 * |
|
1907 * @since 2.8.0 |
|
1908 * @var string |
|
1909 */ |
|
1910 public $domain; |
|
1911 |
|
1912 /** |
|
1913 * Sets up this cookie object. |
|
1914 * |
|
1915 * The parameter $data should be either an associative array containing the indices names below |
|
1916 * or a header string detailing it. |
|
1917 * |
|
1918 * @since 2.8.0 |
|
1919 * @access public |
|
1920 * |
|
1921 * @param string|array $data { |
|
1922 * Raw cookie data as header string or data array. |
|
1923 * |
|
1924 * @type string $name Cookie name. |
|
1925 * @type mixed $value Value. Should NOT already be urlencoded. |
|
1926 * @type string|int $expires Optional. Unix timestamp or formatted date. Default null. |
|
1927 * @type string $path Optional. Path. Default '/'. |
|
1928 * @type string $domain Optional. Domain. Default host of parsed $requested_url. |
|
1929 * @type int $port Optional. Port. Default null. |
|
1930 * } |
|
1931 * @param string $requested_url The URL which the cookie was set on, used for default $domain |
|
1932 * and $port values. |
|
1933 */ |
|
1934 public function __construct( $data, $requested_url = '' ) { |
|
1935 if ( $requested_url ) |
|
1936 $arrURL = @parse_url( $requested_url ); |
|
1937 if ( isset( $arrURL['host'] ) ) |
|
1938 $this->domain = $arrURL['host']; |
|
1939 $this->path = isset( $arrURL['path'] ) ? $arrURL['path'] : '/'; |
|
1940 if ( '/' != substr( $this->path, -1 ) ) |
|
1941 $this->path = dirname( $this->path ) . '/'; |
|
1942 |
|
1943 if ( is_string( $data ) ) { |
|
1944 // Assume it's a header string direct from a previous request. |
|
1945 $pairs = explode( ';', $data ); |
|
1946 |
|
1947 // Special handling for first pair; name=value. Also be careful of "=" in value. |
|
1948 $name = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) ); |
|
1949 $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 ); |
|
1950 $this->name = $name; |
|
1951 $this->value = urldecode( $value ); |
|
1952 |
|
1953 // Removes name=value from items. |
|
1954 array_shift( $pairs ); |
|
1955 |
|
1956 // Set everything else as a property. |
|
1957 foreach ( $pairs as $pair ) { |
|
1958 $pair = rtrim($pair); |
|
1959 |
|
1960 // Handle the cookie ending in ; which results in a empty final pair. |
|
1961 if ( empty($pair) ) |
|
1962 continue; |
|
1963 |
|
1964 list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' ); |
|
1965 $key = strtolower( trim( $key ) ); |
|
1966 if ( 'expires' == $key ) |
|
1967 $val = strtotime( $val ); |
|
1968 $this->$key = $val; |
|
1969 } |
|
1970 } else { |
|
1971 if ( !isset( $data['name'] ) ) |
|
1972 return; |
|
1973 |
|
1974 // Set properties based directly on parameters. |
|
1975 foreach ( array( 'name', 'value', 'path', 'domain', 'port' ) as $field ) { |
|
1976 if ( isset( $data[ $field ] ) ) |
|
1977 $this->$field = $data[ $field ]; |
|
1978 } |
|
1979 |
|
1980 if ( isset( $data['expires'] ) ) |
|
1981 $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] ); |
|
1982 else |
|
1983 $this->expires = null; |
|
1984 } |
|
1985 } |
|
1986 |
|
1987 /** |
|
1988 * Confirms that it's OK to send this cookie to the URL checked against. |
|
1989 * |
|
1990 * Decision is based on RFC 2109/2965, so look there for details on validity. |
|
1991 * |
|
1992 * @access public |
|
1993 * @since 2.8.0 |
|
1994 * |
|
1995 * @param string $url URL you intend to send this cookie to |
|
1996 * @return boolean true if allowed, false otherwise. |
|
1997 */ |
|
1998 public function test( $url ) { |
|
1999 if ( is_null( $this->name ) ) |
|
2000 return false; |
|
2001 |
|
2002 // Expires - if expired then nothing else matters. |
|
2003 if ( isset( $this->expires ) && time() > $this->expires ) |
|
2004 return false; |
|
2005 |
|
2006 // Get details on the URL we're thinking about sending to. |
|
2007 $url = parse_url( $url ); |
|
2008 $url['port'] = isset( $url['port'] ) ? $url['port'] : ( 'https' == $url['scheme'] ? 443 : 80 ); |
|
2009 $url['path'] = isset( $url['path'] ) ? $url['path'] : '/'; |
|
2010 |
|
2011 // Values to use for comparison against the URL. |
|
2012 $path = isset( $this->path ) ? $this->path : '/'; |
|
2013 $port = isset( $this->port ) ? $this->port : null; |
|
2014 $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] ); |
|
2015 if ( false === stripos( $domain, '.' ) ) |
|
2016 $domain .= '.local'; |
|
2017 |
|
2018 // Host - very basic check that the request URL ends with the domain restriction (minus leading dot). |
|
2019 $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain; |
|
2020 if ( substr( $url['host'], -strlen( $domain ) ) != $domain ) |
|
2021 return false; |
|
2022 |
|
2023 // Port - supports "port-lists" in the format: "80,8000,8080". |
|
2024 if ( !empty( $port ) && !in_array( $url['port'], explode( ',', $port) ) ) |
|
2025 return false; |
|
2026 |
|
2027 // Path - request path must start with path restriction. |
|
2028 if ( substr( $url['path'], 0, strlen( $path ) ) != $path ) |
|
2029 return false; |
|
2030 |
|
2031 return true; |
|
2032 } |
|
2033 |
|
2034 /** |
|
2035 * Convert cookie name and value back to header string. |
|
2036 * |
|
2037 * @access public |
|
2038 * @since 2.8.0 |
|
2039 * |
|
2040 * @return string Header encoded cookie name and value. |
|
2041 */ |
|
2042 public function getHeaderValue() { |
|
2043 if ( ! isset( $this->name ) || ! isset( $this->value ) ) |
|
2044 return ''; |
|
2045 |
|
2046 /** |
|
2047 * Filter the header-encoded cookie value. |
|
2048 * |
|
2049 * @since 3.4.0 |
|
2050 * |
|
2051 * @param string $value The cookie value. |
|
2052 * @param string $name The cookie name. |
|
2053 */ |
|
2054 return $this->name . '=' . apply_filters( 'wp_http_cookie_value', $this->value, $this->name ); |
|
2055 } |
|
2056 |
|
2057 /** |
|
2058 * Retrieve cookie header for usage in the rest of the WordPress HTTP API. |
|
2059 * |
|
2060 * @access public |
|
2061 * @since 2.8.0 |
|
2062 * |
|
2063 * @return string |
|
2064 */ |
|
2065 public function getFullHeader() { |
|
2066 return 'Cookie: ' . $this->getHeaderValue(); |
|
2067 } |
|
2068 } |
|
2069 |
|
2070 /** |
|
2071 * Implementation for deflate and gzip transfer encodings. |
|
2072 * |
|
2073 * Includes RFC 1950, RFC 1951, and RFC 1952. |
|
2074 * |
|
2075 * @since 2.8.0 |
|
2076 * @package WordPress |
|
2077 * @subpackage HTTP |
|
2078 */ |
|
2079 class WP_Http_Encoding { |
|
2080 |
|
2081 /** |
|
2082 * Compress raw string using the deflate format. |
|
2083 * |
|
2084 * Supports the RFC 1951 standard. |
|
2085 * |
|
2086 * @since 2.8.0 |
|
2087 * |
|
2088 * @param string $raw String to compress. |
|
2089 * @param int $level Optional, default is 9. Compression level, 9 is highest. |
|
2090 * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports. |
|
2091 * @return string|false False on failure. |
|
2092 */ |
|
2093 public static function compress( $raw, $level = 9, $supports = null ) { |
|
2094 return gzdeflate( $raw, $level ); |
|
2095 } |
|
2096 |
|
2097 /** |
|
2098 * Decompression of deflated string. |
|
2099 * |
|
2100 * Will attempt to decompress using the RFC 1950 standard, and if that fails |
|
2101 * then the RFC 1951 standard deflate will be attempted. Finally, the RFC |
|
2102 * 1952 standard gzip decode will be attempted. If all fail, then the |
|
2103 * original compressed string will be returned. |
|
2104 * |
|
2105 * @since 2.8.0 |
|
2106 * |
|
2107 * @param string $compressed String to decompress. |
|
2108 * @param int $length The optional length of the compressed data. |
|
2109 * @return string|bool False on failure. |
|
2110 */ |
|
2111 public static function decompress( $compressed, $length = null ) { |
|
2112 |
|
2113 if ( empty($compressed) ) |
|
2114 return $compressed; |
|
2115 |
|
2116 if ( false !== ( $decompressed = @gzinflate( $compressed ) ) ) |
|
2117 return $decompressed; |
|
2118 |
|
2119 if ( false !== ( $decompressed = self::compatible_gzinflate( $compressed ) ) ) |
|
2120 return $decompressed; |
|
2121 |
|
2122 if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) ) |
|
2123 return $decompressed; |
|
2124 |
|
2125 if ( function_exists('gzdecode') ) { |
|
2126 $decompressed = @gzdecode( $compressed ); |
|
2127 |
|
2128 if ( false !== $decompressed ) |
|
2129 return $decompressed; |
|
2130 } |
|
2131 |
|
2132 return $compressed; |
|
2133 } |
|
2134 |
|
2135 /** |
|
2136 * Decompression of deflated string while staying compatible with the majority of servers. |
|
2137 * |
|
2138 * Certain Servers will return deflated data with headers which PHP's gzinflate() |
|
2139 * function cannot handle out of the box. The following function has been created from |
|
2140 * various snippets on the gzinflate() PHP documentation. |
|
2141 * |
|
2142 * Warning: Magic numbers within. Due to the potential different formats that the compressed |
|
2143 * data may be returned in, some "magic offsets" are needed to ensure proper decompression |
|
2144 * takes place. For a simple progmatic way to determine the magic offset in use, see: |
|
2145 * https://core.trac.wordpress.org/ticket/18273 |
|
2146 * |
|
2147 * @since 2.8.1 |
|
2148 * @link https://core.trac.wordpress.org/ticket/18273 |
|
2149 * @link http://au2.php.net/manual/en/function.gzinflate.php#70875 |
|
2150 * @link http://au2.php.net/manual/en/function.gzinflate.php#77336 |
|
2151 * |
|
2152 * @param string $gzData String to decompress. |
|
2153 * @return string|bool False on failure. |
|
2154 */ |
|
2155 public static function compatible_gzinflate($gzData) { |
|
2156 |
|
2157 // Compressed data might contain a full header, if so strip it for gzinflate(). |
|
2158 if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) { |
|
2159 $i = 10; |
|
2160 $flg = ord( substr($gzData, 3, 1) ); |
|
2161 if ( $flg > 0 ) { |
|
2162 if ( $flg & 4 ) { |
|
2163 list($xlen) = unpack('v', substr($gzData, $i, 2) ); |
|
2164 $i = $i + 2 + $xlen; |
|
2165 } |
|
2166 if ( $flg & 8 ) |
|
2167 $i = strpos($gzData, "\0", $i) + 1; |
|
2168 if ( $flg & 16 ) |
|
2169 $i = strpos($gzData, "\0", $i) + 1; |
|
2170 if ( $flg & 2 ) |
|
2171 $i = $i + 2; |
|
2172 } |
|
2173 $decompressed = @gzinflate( substr($gzData, $i, -8) ); |
|
2174 if ( false !== $decompressed ) |
|
2175 return $decompressed; |
|
2176 } |
|
2177 |
|
2178 // Compressed data from java.util.zip.Deflater amongst others. |
|
2179 $decompressed = @gzinflate( substr($gzData, 2) ); |
|
2180 if ( false !== $decompressed ) |
|
2181 return $decompressed; |
|
2182 |
|
2183 return false; |
|
2184 } |
|
2185 |
|
2186 /** |
|
2187 * What encoding types to accept and their priority values. |
|
2188 * |
|
2189 * @since 2.8.0 |
|
2190 * |
|
2191 * @param string $url |
|
2192 * @param array $args |
|
2193 * @return string Types of encoding to accept. |
|
2194 */ |
|
2195 public static function accept_encoding( $url, $args ) { |
|
2196 $type = array(); |
|
2197 $compression_enabled = self::is_available(); |
|
2198 |
|
2199 if ( ! $args['decompress'] ) // Decompression specifically disabled. |
|
2200 $compression_enabled = false; |
|
2201 elseif ( $args['stream'] ) // Disable when streaming to file. |
|
2202 $compression_enabled = false; |
|
2203 elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it. |
|
2204 $compression_enabled = false; |
|
2205 |
|
2206 if ( $compression_enabled ) { |
|
2207 if ( function_exists( 'gzinflate' ) ) |
|
2208 $type[] = 'deflate;q=1.0'; |
|
2209 |
|
2210 if ( function_exists( 'gzuncompress' ) ) |
|
2211 $type[] = 'compress;q=0.5'; |
|
2212 |
|
2213 if ( function_exists( 'gzdecode' ) ) |
|
2214 $type[] = 'gzip;q=0.5'; |
|
2215 } |
|
2216 |
|
2217 /** |
|
2218 * Filter the allowed encoding types. |
|
2219 * |
|
2220 * @since 3.6.0 |
|
2221 * |
|
2222 * @param array $type Encoding types allowed. Accepts 'gzinflate', |
|
2223 * 'gzuncompress', 'gzdecode'. |
|
2224 * @param string $url URL of the HTTP request. |
|
2225 * @param array $args HTTP request arguments. |
|
2226 */ |
|
2227 $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args ); |
|
2228 |
|
2229 return implode(', ', $type); |
|
2230 } |
|
2231 |
|
2232 /** |
|
2233 * What encoding the content used when it was compressed to send in the headers. |
|
2234 * |
|
2235 * @since 2.8.0 |
|
2236 * |
|
2237 * @return string Content-Encoding string to send in the header. |
|
2238 */ |
|
2239 public static function content_encoding() { |
|
2240 return 'deflate'; |
|
2241 } |
|
2242 |
|
2243 /** |
|
2244 * Whether the content be decoded based on the headers. |
|
2245 * |
|
2246 * @since 2.8.0 |
|
2247 * |
|
2248 * @param array|string $headers All of the available headers. |
|
2249 * @return bool |
|
2250 */ |
|
2251 public static function should_decode($headers) { |
|
2252 if ( is_array( $headers ) ) { |
|
2253 if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) ) |
|
2254 return true; |
|
2255 } elseif ( is_string( $headers ) ) { |
|
2256 return ( stripos($headers, 'content-encoding:') !== false ); |
|
2257 } |
|
2258 |
|
2259 return false; |
|
2260 } |
|
2261 |
|
2262 /** |
|
2263 * Whether decompression and compression are supported by the PHP version. |
|
2264 * |
|
2265 * Each function is tested instead of checking for the zlib extension, to |
|
2266 * ensure that the functions all exist in the PHP version and aren't |
|
2267 * disabled. |
|
2268 * |
|
2269 * @since 2.8.0 |
|
2270 * |
|
2271 * @return bool |
|
2272 */ |
|
2273 public static function is_available() { |
|
2274 return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') ); |
|
2275 } |
|
2276 } |
|