home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / class-wp-http-streams.php < prev    next >
Encoding:
PHP Script  |  2017-10-18  |  14.6 KB  |  433 lines

  1. <?php
  2. /**
  3.  * HTTP API: WP_Http_Streams class
  4.  *
  5.  * @package WordPress
  6.  * @subpackage HTTP
  7.  * @since 4.4.0
  8.  */
  9.  
  10. /**
  11.  * Core class used to integrate PHP Streams as an HTTP transport.
  12.  *
  13.  * @since 2.7.0
  14.  * @since 3.7.0 Combined with the fsockopen transport and switched to `stream_socket_client()`.
  15.  */
  16. class WP_Http_Streams {
  17.     /**
  18.      * Send a HTTP request to a URI using PHP Streams.
  19.      *
  20.      * @see WP_Http::request For default options descriptions.
  21.      *
  22.      * @since 2.7.0
  23.      * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
  24.      *
  25.      * @param string $url The request URL.
  26.      * @param string|array $args Optional. Override the defaults.
  27.      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
  28.      */
  29.     public function request($url, $args = array()) {
  30.         $defaults = array(
  31.             'method' => 'GET', 'timeout' => 5,
  32.             'redirection' => 5, 'httpversion' => '1.0',
  33.             'blocking' => true,
  34.             'headers' => array(), 'body' => null, 'cookies' => array()
  35.         );
  36.  
  37.         $r = wp_parse_args( $args, $defaults );
  38.  
  39.         if ( isset( $r['headers']['User-Agent'] ) ) {
  40.             $r['user-agent'] = $r['headers']['User-Agent'];
  41.             unset( $r['headers']['User-Agent'] );
  42.         } elseif ( isset( $r['headers']['user-agent'] ) ) {
  43.             $r['user-agent'] = $r['headers']['user-agent'];
  44.             unset( $r['headers']['user-agent'] );
  45.         }
  46.  
  47.         // Construct Cookie: header if any cookies are set.
  48.         WP_Http::buildCookieHeader( $r );
  49.  
  50.         $arrURL = parse_url($url);
  51.  
  52.         $connect_host = $arrURL['host'];
  53.  
  54.         $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' );
  55.         if ( ! isset( $arrURL['port'] ) ) {
  56.             if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) {
  57.                 $arrURL['port'] = 443;
  58.                 $secure_transport = true;
  59.             } else {
  60.                 $arrURL['port'] = 80;
  61.             }
  62.         }
  63.  
  64.         // Always pass a Path, defaulting to the root in cases such as http://example.com
  65.         if ( ! isset( $arrURL['path'] ) ) {
  66.             $arrURL['path'] = '/';
  67.         }
  68.  
  69.         if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) {
  70.             if ( isset( $r['headers']['Host'] ) )
  71.                 $arrURL['host'] = $r['headers']['Host'];
  72.             else
  73.                 $arrURL['host'] = $r['headers']['host'];
  74.             unset( $r['headers']['Host'], $r['headers']['host'] );
  75.         }
  76.  
  77.         /*
  78.          * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect
  79.          * to ::1, which fails when the server is not set up for it. For compatibility, always
  80.          * connect to the IPv4 address.
  81.          */
  82.         if ( 'localhost' == strtolower( $connect_host ) )
  83.             $connect_host = '127.0.0.1';
  84.  
  85.         $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host;
  86.  
  87.         $is_local = isset( $r['local'] ) && $r['local'];
  88.         $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify'];
  89.         if ( $is_local ) {
  90.             /**
  91.              * Filters whether SSL should be verified for local requests.
  92.              *
  93.              * @since 2.8.0
  94.              *
  95.              * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
  96.              */
  97.             $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
  98.         } elseif ( ! $is_local ) {
  99.             /**
  100.              * Filters whether SSL should be verified for non-local requests.
  101.              *
  102.              * @since 2.8.0
  103.              *
  104.              * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
  105.              */
  106.             $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
  107.         }
  108.  
  109.         $proxy = new WP_HTTP_Proxy();
  110.  
  111.         $context = stream_context_create( array(
  112.             'ssl' => array(
  113.                 'verify_peer' => $ssl_verify,
  114.                 //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate()
  115.                 'capture_peer_cert' => $ssl_verify,
  116.                 'SNI_enabled' => true,
  117.                 'cafile' => $r['sslcertificates'],
  118.                 'allow_self_signed' => ! $ssl_verify,
  119.             )
  120.         ) );
  121.  
  122.         $timeout = (int) floor( $r['timeout'] );
  123.         $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
  124.         $connect_timeout = max( $timeout, 1 );
  125.  
  126.         // Store error number.
  127.         $connection_error = null;
  128.  
  129.         // Store error string.
  130.         $connection_error_str = null;
  131.  
  132.         if ( !WP_DEBUG ) {
  133.             // In the event that the SSL connection fails, silence the many PHP Warnings.
  134.             if ( $secure_transport )
  135.                 $error_reporting = error_reporting(0);
  136.  
  137.             if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
  138.                 $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
  139.             else
  140.                 $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
  141.  
  142.             if ( $secure_transport )
  143.                 error_reporting( $error_reporting );
  144.  
  145.         } else {
  146.             if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
  147.                 $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
  148.             else
  149.                 $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
  150.         }
  151.  
  152.         if ( false === $handle ) {
  153.             // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken.
  154.             if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str )
  155.                 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
  156.  
  157.             return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str );
  158.         }
  159.  
  160.         // Verify that the SSL certificate is valid for this request.
  161.         if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) {
  162.             if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) )
  163.                 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
  164.         }
  165.  
  166.         stream_set_timeout( $handle, $timeout, $utimeout );
  167.  
  168.         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
  169.             $requestPath = $url;
  170.         else
  171.             $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
  172.  
  173.         $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
  174.  
  175.         $include_port_in_host_header = (
  176.             ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) ||
  177.             ( 'http'  == $arrURL['scheme'] && 80  != $arrURL['port'] ) ||
  178.             ( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] )
  179.         );
  180.  
  181.         if ( $include_port_in_host_header ) {
  182.             $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
  183.         } else {
  184.             $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
  185.         }
  186.  
  187.         if ( isset($r['user-agent']) )
  188.             $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
  189.  
  190.         if ( is_array($r['headers']) ) {
  191.             foreach ( (array) $r['headers'] as $header => $headerValue )
  192.                 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
  193.         } else {
  194.             $strHeaders .= $r['headers'];
  195.         }
  196.  
  197.         if ( $proxy->use_authentication() )
  198.             $strHeaders .= $proxy->authentication_header() . "\r\n";
  199.  
  200.         $strHeaders .= "\r\n";
  201.  
  202.         if ( ! is_null($r['body']) )
  203.             $strHeaders .= $r['body'];
  204.  
  205.         fwrite($handle, $strHeaders);
  206.  
  207.         if ( ! $r['blocking'] ) {
  208.             stream_set_blocking( $handle, 0 );
  209.             fclose( $handle );
  210.             return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
  211.         }
  212.  
  213.         $strResponse = '';
  214.         $bodyStarted = false;
  215.         $keep_reading = true;
  216.         $block_size = 4096;
  217.         if ( isset( $r['limit_response_size'] ) )
  218.             $block_size = min( $block_size, $r['limit_response_size'] );
  219.  
  220.         // If streaming to a file setup the file handle.
  221.         if ( $r['stream'] ) {
  222.             if ( ! WP_DEBUG )
  223.                 $stream_handle = @fopen( $r['filename'], 'w+' );
  224.             else
  225.                 $stream_handle = fopen( $r['filename'], 'w+' );
  226.             if ( ! $stream_handle ) {
  227.                 return new WP_Error( 'http_request_failed', sprintf(
  228.                     /* translators: 1: fopen() 2: file name */
  229.                     __( 'Could not open handle for %1$s to %2$s.' ),
  230.                     'fopen()',
  231.                     $r['filename']
  232.                 ) );
  233.             }
  234.  
  235.             $bytes_written = 0;
  236.             while ( ! feof($handle) && $keep_reading ) {
  237.                 $block = fread( $handle, $block_size );
  238.                 if ( ! $bodyStarted ) {
  239.                     $strResponse .= $block;
  240.                     if ( strpos( $strResponse, "\r\n\r\n" ) ) {
  241.                         $process = WP_Http::processResponse( $strResponse );
  242.                         $bodyStarted = true;
  243.                         $block = $process['body'];
  244.                         unset( $strResponse );
  245.                         $process['body'] = '';
  246.                     }
  247.                 }
  248.  
  249.                 $this_block_size = strlen( $block );
  250.  
  251.                 if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] ) {
  252.                     $this_block_size = ( $r['limit_response_size'] - $bytes_written );
  253.                     $block = substr( $block, 0, $this_block_size );
  254.                 }
  255.  
  256.                 $bytes_written_to_file = fwrite( $stream_handle, $block );
  257.  
  258.                 if ( $bytes_written_to_file != $this_block_size ) {
  259.                     fclose( $handle );
  260.                     fclose( $stream_handle );
  261.                     return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
  262.                 }
  263.  
  264.                 $bytes_written += $bytes_written_to_file;
  265.  
  266.                 $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size'];
  267.             }
  268.  
  269.             fclose( $stream_handle );
  270.  
  271.         } else {
  272.             $header_length = 0;
  273.             while ( ! feof( $handle ) && $keep_reading ) {
  274.                 $block = fread( $handle, $block_size );
  275.                 $strResponse .= $block;
  276.                 if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) {
  277.                     $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4;
  278.                     $bodyStarted = true;
  279.                 }
  280.                 $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) );
  281.             }
  282.  
  283.             $process = WP_Http::processResponse( $strResponse );
  284.             unset( $strResponse );
  285.  
  286.         }
  287.  
  288.         fclose( $handle );
  289.  
  290.         $arrHeaders = WP_Http::processHeaders( $process['headers'], $url );
  291.  
  292.         $response = array(
  293.             'headers' => $arrHeaders['headers'],
  294.             // Not yet processed.
  295.             'body' => null,
  296.             'response' => $arrHeaders['response'],
  297.             'cookies' => $arrHeaders['cookies'],
  298.             'filename' => $r['filename']
  299.         );
  300.  
  301.         // Handle redirects.
  302.         if ( false !== ( $redirect_response = WP_Http::handle_redirects( $url, $r, $response ) ) )
  303.             return $redirect_response;
  304.  
  305.         // If the body was chunk encoded, then decode it.
  306.         if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
  307.             $process['body'] = WP_Http::chunkTransferDecode($process['body']);
  308.  
  309.         if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
  310.             $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
  311.  
  312.         if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] )
  313.             $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] );
  314.  
  315.         $response['body'] = $process['body'];
  316.  
  317.         return $response;
  318.     }
  319.  
  320.     /**
  321.      * Verifies the received SSL certificate against its Common Names and subjectAltName fields.
  322.      *
  323.      * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if
  324.      * the certificate is valid for the hostname which was requested.
  325.      * This function verifies the requested hostname against certificate's subjectAltName field,
  326.      * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used.
  327.      *
  328.      * IP Address support is included if the request is being made to an IP address.
  329.      *
  330.      * @since 3.7.0
  331.      * @static
  332.      *
  333.      * @param stream $stream The PHP Stream which the SSL request is being made over
  334.      * @param string $host The hostname being requested
  335.      * @return bool If the cerficiate presented in $stream is valid for $host
  336.      */
  337.     public static function verify_ssl_certificate( $stream, $host ) {
  338.         $context_options = stream_context_get_options( $stream );
  339.  
  340.         if ( empty( $context_options['ssl']['peer_certificate'] ) )
  341.             return false;
  342.  
  343.         $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] );
  344.         if ( ! $cert )
  345.             return false;
  346.  
  347.         /*
  348.          * If the request is being made to an IP address, we'll validate against IP fields
  349.          * in the cert (if they exist)
  350.          */
  351.         $host_type = ( WP_Http::is_ip_address( $host ) ? 'ip' : 'dns' );
  352.  
  353.         $certificate_hostnames = array();
  354.         if ( ! empty( $cert['extensions']['subjectAltName'] ) ) {
  355.             $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] );
  356.             foreach ( $match_against as $match ) {
  357.                 list( $match_type, $match_host ) = explode( ':', $match );
  358.                 if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS:
  359.                     $certificate_hostnames[] = strtolower( trim( $match_host ) );
  360.             }
  361.         } elseif ( !empty( $cert['subject']['CN'] ) ) {
  362.             // Only use the CN when the certificate includes no subjectAltName extension.
  363.             $certificate_hostnames[] = strtolower( $cert['subject']['CN'] );
  364.         }
  365.  
  366.         // Exact hostname/IP matches.
  367.         if ( in_array( strtolower( $host ), $certificate_hostnames ) )
  368.             return true;
  369.  
  370.         // IP's can't be wildcards, Stop processing.
  371.         if ( 'ip' == $host_type )
  372.             return false;
  373.  
  374.         // Test to see if the domain is at least 2 deep for wildcard support.
  375.         if ( substr_count( $host, '.' ) < 2 )
  376.             return false;
  377.  
  378.         // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com.
  379.         $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host );
  380.  
  381.         return in_array( strtolower( $wildcard_host ), $certificate_hostnames );
  382.     }
  383.  
  384.     /**
  385.      * Determines whether this class can be used for retrieving a URL.
  386.      *
  387.      * @static
  388.      * @since 2.7.0
  389.      * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
  390.      *
  391.      * @param array $args Optional. Array of request arguments. Default empty array.
  392.      * @return bool False means this class can not be used, true means it can.
  393.      */
  394.     public static function test( $args = array() ) {
  395.         if ( ! function_exists( 'stream_socket_client' ) )
  396.             return false;
  397.  
  398.         $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
  399.  
  400.         if ( $is_ssl ) {
  401.             if ( ! extension_loaded( 'openssl' ) )
  402.                 return false;
  403.             if ( ! function_exists( 'openssl_x509_parse' ) )
  404.                 return false;
  405.         }
  406.  
  407.         /**
  408.          * Filters whether streams can be used as a transport for retrieving a URL.
  409.          *
  410.          * @since 2.7.0
  411.          *
  412.          * @param bool  $use_class Whether the class can be used. Default true.
  413.          * @param array $args      Request arguments.
  414.          */
  415.         return apply_filters( 'use_streams_transport', true, $args );
  416.     }
  417. }
  418.  
  419. /**
  420.  * Deprecated HTTP Transport method which used fsockopen.
  421.  *
  422.  * This class is not used, and is included for backward compatibility only.
  423.  * All code should make use of WP_Http directly through its API.
  424.  *
  425.  * @see WP_HTTP::request
  426.  *
  427.  * @since 2.7.0
  428.  * @deprecated 3.7.0 Please use WP_HTTP::request() directly
  429.  */
  430. class WP_HTTP_Fsockopen extends WP_HTTP_Streams {
  431.     // For backward compatibility for users who are using the class directly.
  432. }
  433.