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

  1. <?php
  2. /**
  3.  * HTTP API: WP_Http_Curl class
  4.  *
  5.  * @package WordPress
  6.  * @subpackage HTTP
  7.  * @since 4.4.0
  8.  */
  9.  
  10. /**
  11.  * Core class used to integrate Curl as an HTTP transport.
  12.  *
  13.  * HTTP request method uses Curl extension to retrieve the url.
  14.  *
  15.  * Requires the Curl extension to be installed.
  16.  *
  17.  * @since 2.7.0
  18.  */
  19. class WP_Http_Curl {
  20.  
  21.     /**
  22.      * Temporary header storage for during requests.
  23.      *
  24.      * @since 3.2.0
  25.      * @var string
  26.      */
  27.     private $headers = '';
  28.  
  29.     /**
  30.      * Temporary body storage for during requests.
  31.      *
  32.      * @since 3.6.0
  33.      * @var string
  34.      */
  35.     private $body = '';
  36.  
  37.     /**
  38.      * The maximum amount of data to receive from the remote server.
  39.      *
  40.      * @since 3.6.0
  41.      * @var int
  42.      */
  43.     private $max_body_length = false;
  44.  
  45.     /**
  46.      * The file resource used for streaming to file.
  47.      *
  48.      * @since 3.6.0
  49.      * @var resource
  50.      */
  51.     private $stream_handle = false;
  52.  
  53.     /**
  54.      * The total bytes written in the current request.
  55.      *
  56.      * @since 4.1.0
  57.      * @var int
  58.      */
  59.     private $bytes_written_total = 0;
  60.  
  61.     /**
  62.      * Send a HTTP request to a URI using cURL extension.
  63.      *
  64.      * @since 2.7.0
  65.      *
  66.      * @param string $url The request URL.
  67.      * @param string|array $args Optional. Override the defaults.
  68.      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
  69.      */
  70.     public function request($url, $args = array()) {
  71.         $defaults = array(
  72.             'method' => 'GET', 'timeout' => 5,
  73.             'redirection' => 5, 'httpversion' => '1.0',
  74.             'blocking' => true,
  75.             'headers' => array(), 'body' => null, 'cookies' => array()
  76.         );
  77.  
  78.         $r = wp_parse_args( $args, $defaults );
  79.  
  80.         if ( isset( $r['headers']['User-Agent'] ) ) {
  81.             $r['user-agent'] = $r['headers']['User-Agent'];
  82.             unset( $r['headers']['User-Agent'] );
  83.         } elseif ( isset( $r['headers']['user-agent'] ) ) {
  84.             $r['user-agent'] = $r['headers']['user-agent'];
  85.             unset( $r['headers']['user-agent'] );
  86.         }
  87.  
  88.         // Construct Cookie: header if any cookies are set.
  89.         WP_Http::buildCookieHeader( $r );
  90.  
  91.         $handle = curl_init();
  92.  
  93.         // cURL offers really easy proxy support.
  94.         $proxy = new WP_HTTP_Proxy();
  95.  
  96.         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
  97.  
  98.             curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
  99.             curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
  100.             curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
  101.  
  102.             if ( $proxy->use_authentication() ) {
  103.                 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
  104.                 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
  105.             }
  106.         }
  107.  
  108.         $is_local = isset($r['local']) && $r['local'];
  109.         $ssl_verify = isset($r['sslverify']) && $r['sslverify'];
  110.         if ( $is_local ) {
  111.             /** This filter is documented in wp-includes/class-wp-http-streams.php */
  112.             $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
  113.         } elseif ( ! $is_local ) {
  114.             /** This filter is documented in wp-includes/class-wp-http-streams.php */
  115.             $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
  116.         }
  117.  
  118.         /*
  119.          * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since.
  120.          * a value of 0 will allow an unlimited timeout.
  121.          */
  122.         $timeout = (int) ceil( $r['timeout'] );
  123.         curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout );
  124.         curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout );
  125.  
  126.         curl_setopt( $handle, CURLOPT_URL, $url);
  127.         curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
  128.         curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
  129.         curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
  130.  
  131.         if ( $ssl_verify ) {
  132.             curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] );
  133.         }
  134.  
  135.         curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
  136.  
  137.         /*
  138.          * The option doesn't work with safe mode or when open_basedir is set, and there's
  139.          * a bug #17490 with redirected POST requests, so handle redirections outside Curl.
  140.          */
  141.         curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false );
  142.         if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4
  143.             curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
  144.  
  145.         switch ( $r['method'] ) {
  146.             case 'HEAD':
  147.                 curl_setopt( $handle, CURLOPT_NOBODY, true );
  148.                 break;
  149.             case 'POST':
  150.                 curl_setopt( $handle, CURLOPT_POST, true );
  151.                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
  152.                 break;
  153.             case 'PUT':
  154.                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
  155.                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
  156.                 break;
  157.             default:
  158.                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] );
  159.                 if ( ! is_null( $r['body'] ) )
  160.                     curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
  161.                 break;
  162.         }
  163.  
  164.         if ( true === $r['blocking'] ) {
  165.             curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
  166.             curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) );
  167.         }
  168.  
  169.         curl_setopt( $handle, CURLOPT_HEADER, false );
  170.  
  171.         if ( isset( $r['limit_response_size'] ) )
  172.             $this->max_body_length = intval( $r['limit_response_size'] );
  173.         else
  174.             $this->max_body_length = false;
  175.  
  176.         // If streaming to a file open a file handle, and setup our curl streaming handler.
  177.         if ( $r['stream'] ) {
  178.             if ( ! WP_DEBUG )
  179.                 $this->stream_handle = @fopen( $r['filename'], 'w+' );
  180.             else
  181.                 $this->stream_handle = fopen( $r['filename'], 'w+' );
  182.             if ( ! $this->stream_handle ) {
  183.                 return new WP_Error( 'http_request_failed', sprintf(
  184.                     /* translators: 1: fopen() 2: file name */
  185.                     __( 'Could not open handle for %1$s to %2$s.' ),
  186.                     'fopen()',
  187.                     $r['filename']
  188.                 ) );
  189.             }
  190.         } else {
  191.             $this->stream_handle = false;
  192.         }
  193.  
  194.         if ( !empty( $r['headers'] ) ) {
  195.             // cURL expects full header strings in each element.
  196.             $headers = array();
  197.             foreach ( $r['headers'] as $name => $value ) {
  198.                 $headers[] = "{$name}: $value";
  199.             }
  200.             curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
  201.         }
  202.  
  203.         if ( $r['httpversion'] == '1.0' )
  204.             curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
  205.         else
  206.             curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
  207.  
  208.         /**
  209.          * Fires before the cURL request is executed.
  210.          *
  211.          * Cookies are not currently handled by the HTTP API. This action allows
  212.          * plugins to handle cookies themselves.
  213.          *
  214.          * @since 2.8.0
  215.          *
  216.          * @param resource $handle  The cURL handle returned by curl_init() (passed by reference).
  217.          * @param array    $r       The HTTP request arguments.
  218.          * @param string   $url     The request URL.
  219.          */
  220.         do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) );
  221.  
  222.         // We don't need to return the body, so don't. Just execute request and return.
  223.         if ( ! $r['blocking'] ) {
  224.             curl_exec( $handle );
  225.  
  226.             if ( $curl_error = curl_error( $handle ) ) {
  227.                 curl_close( $handle );
  228.                 return new WP_Error( 'http_request_failed', $curl_error );
  229.             }
  230.             if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
  231.                 curl_close( $handle );
  232.                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
  233.             }
  234.  
  235.             curl_close( $handle );
  236.             return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
  237.         }
  238.  
  239.         curl_exec( $handle );
  240.         $theHeaders = WP_Http::processHeaders( $this->headers, $url );
  241.         $theBody = $this->body;
  242.         $bytes_written_total = $this->bytes_written_total;
  243.  
  244.         $this->headers = '';
  245.         $this->body = '';
  246.         $this->bytes_written_total = 0;
  247.  
  248.         $curl_error = curl_errno( $handle );
  249.  
  250.         // If an error occurred, or, no response.
  251.         if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) {
  252.             if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error ) {
  253.                 if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) {
  254.                     if ( $r['stream'] ) {
  255.                         curl_close( $handle );
  256.                         fclose( $this->stream_handle );
  257.                         return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
  258.                     } else {
  259.                         curl_close( $handle );
  260.                         return new WP_Error( 'http_request_failed', curl_error( $handle ) );
  261.                     }
  262.                 }
  263.             } else {
  264.                 if ( $curl_error = curl_error( $handle ) ) {
  265.                     curl_close( $handle );
  266.                     return new WP_Error( 'http_request_failed', $curl_error );
  267.                 }
  268.             }
  269.             if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
  270.                 curl_close( $handle );
  271.                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
  272.             }
  273.         }
  274.  
  275.         curl_close( $handle );
  276.  
  277.         if ( $r['stream'] )
  278.             fclose( $this->stream_handle );
  279.  
  280.         $response = array(
  281.             'headers' => $theHeaders['headers'],
  282.             'body' => null,
  283.             'response' => $theHeaders['response'],
  284.             'cookies' => $theHeaders['cookies'],
  285.             'filename' => $r['filename']
  286.         );
  287.  
  288.         // Handle redirects.
  289.         if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
  290.             return $redirect_response;
  291.  
  292.         if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
  293.             $theBody = WP_Http_Encoding::decompress( $theBody );
  294.  
  295.         $response['body'] = $theBody;
  296.  
  297.         return $response;
  298.     }
  299.  
  300.     /**
  301.      * Grabs the headers of the cURL request.
  302.      *
  303.      * Each header is sent individually to this callback, so we append to the `$header` property
  304.      * for temporary storage
  305.      *
  306.      * @since 3.2.0
  307.      *
  308.      * @param resource $handle  cURL handle.
  309.      * @param string   $headers cURL request headers.
  310.      * @return int Length of the request headers.
  311.      */
  312.     private function stream_headers( $handle, $headers ) {
  313.         $this->headers .= $headers;
  314.         return strlen( $headers );
  315.     }
  316.  
  317.     /**
  318.      * Grabs the body of the cURL request.
  319.      *
  320.      * The contents of the document are passed in chunks, so we append to the `$body`
  321.      * property for temporary storage. Returning a length shorter than the length of
  322.      * `$data` passed in will cause cURL to abort the request with `CURLE_WRITE_ERROR`.
  323.      *
  324.      * @since 3.6.0
  325.      *
  326.      * @param resource $handle  cURL handle.
  327.      * @param string   $data    cURL request body.
  328.      * @return int Total bytes of data written.
  329.      */
  330.     private function stream_body( $handle, $data ) {
  331.         $data_length = strlen( $data );
  332.  
  333.         if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) {
  334.             $data_length = ( $this->max_body_length - $this->bytes_written_total );
  335.             $data = substr( $data, 0, $data_length );
  336.         }
  337.  
  338.         if ( $this->stream_handle ) {
  339.             $bytes_written = fwrite( $this->stream_handle, $data );
  340.         } else {
  341.             $this->body .= $data;
  342.             $bytes_written = $data_length;
  343.         }
  344.  
  345.         $this->bytes_written_total += $bytes_written;
  346.  
  347.         // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR.
  348.         return $bytes_written;
  349.     }
  350.  
  351.     /**
  352.      * Determines whether this class can be used for retrieving a URL.
  353.      *
  354.      * @static
  355.      * @since 2.7.0
  356.      *
  357.      * @param array $args Optional. Array of request arguments. Default empty array.
  358.      * @return bool False means this class can not be used, true means it can.
  359.      */
  360.     public static function test( $args = array() ) {
  361.         if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
  362.             return false;
  363.  
  364.         $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
  365.  
  366.         if ( $is_ssl ) {
  367.             $curl_version = curl_version();
  368.             // Check whether this cURL version support SSL requests.
  369.             if ( ! (CURL_VERSION_SSL & $curl_version['features']) )
  370.                 return false;
  371.         }
  372.  
  373.         /**
  374.          * Filters whether cURL can be used as a transport for retrieving a URL.
  375.          *
  376.          * @since 2.7.0
  377.          *
  378.          * @param bool  $use_class Whether the class can be used. Default true.
  379.          * @param array $args      An array of request arguments.
  380.          */
  381.         return apply_filters( 'use_curl_transport', true, $args );
  382.     }
  383. }
  384.