home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / Requests / Transport / cURL.php next >
Encoding:
PHP Script  |  2016-10-04  |  15.0 KB  |  543 lines

  1. <?php
  2. /**
  3.  * cURL HTTP transport
  4.  *
  5.  * @package Requests
  6.  * @subpackage Transport
  7.  */
  8.  
  9. /**
  10.  * cURL HTTP transport
  11.  *
  12.  * @package Requests
  13.  * @subpackage Transport
  14.  */
  15. class Requests_Transport_cURL implements Requests_Transport {
  16.     const CURL_7_10_5 = 0x070A05;
  17.     const CURL_7_16_2 = 0x071002;
  18.  
  19.     /**
  20.      * Raw HTTP data
  21.      *
  22.      * @var string
  23.      */
  24.     public $headers = '';
  25.  
  26.     /**
  27.      * Raw body data
  28.      *
  29.      * @var string
  30.      */
  31.     public $response_data = '';
  32.  
  33.     /**
  34.      * Information on the current request
  35.      *
  36.      * @var array cURL information array, see {@see https://secure.php.net/curl_getinfo}
  37.      */
  38.     public $info;
  39.  
  40.     /**
  41.      * Version string
  42.      *
  43.      * @var long
  44.      */
  45.     public $version;
  46.  
  47.     /**
  48.      * cURL handle
  49.      *
  50.      * @var resource
  51.      */
  52.     protected $handle;
  53.  
  54.     /**
  55.      * Hook dispatcher instance
  56.      *
  57.      * @var Requests_Hooks
  58.      */
  59.     protected $hooks;
  60.  
  61.     /**
  62.      * Have we finished the headers yet?
  63.      *
  64.      * @var boolean
  65.      */
  66.     protected $done_headers = false;
  67.  
  68.     /**
  69.      * If streaming to a file, keep the file pointer
  70.      *
  71.      * @var resource
  72.      */
  73.     protected $stream_handle;
  74.  
  75.     /**
  76.      * How many bytes are in the response body?
  77.      *
  78.      * @var int
  79.      */
  80.     protected $response_bytes;
  81.  
  82.     /**
  83.      * What's the maximum number of bytes we should keep?
  84.      *
  85.      * @var int|bool Byte count, or false if no limit.
  86.      */
  87.     protected $response_byte_limit;
  88.  
  89.     /**
  90.      * Constructor
  91.      */
  92.     public function __construct() {
  93.         $curl = curl_version();
  94.         $this->version = $curl['version_number'];
  95.         $this->handle = curl_init();
  96.  
  97.         curl_setopt($this->handle, CURLOPT_HEADER, false);
  98.         curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);
  99.         if ($this->version >= self::CURL_7_10_5) {
  100.             curl_setopt($this->handle, CURLOPT_ENCODING, '');
  101.         }
  102.         if (defined('CURLOPT_PROTOCOLS')) {
  103.             curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
  104.         }
  105.         if (defined('CURLOPT_REDIR_PROTOCOLS')) {
  106.             curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
  107.         }
  108.     }
  109.  
  110.     /**
  111.      * Destructor
  112.      */
  113.     public function __destruct() {
  114.         if (is_resource($this->handle)) {
  115.             curl_close($this->handle);
  116.         }
  117.     }
  118.  
  119.     /**
  120.      * Perform a request
  121.      *
  122.      * @throws Requests_Exception On a cURL error (`curlerror`)
  123.      *
  124.      * @param string $url URL to request
  125.      * @param array $headers Associative array of request headers
  126.      * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
  127.      * @param array $options Request options, see {@see Requests::response()} for documentation
  128.      * @return string Raw HTTP result
  129.      */
  130.     public function request($url, $headers = array(), $data = array(), $options = array()) {
  131.         $this->hooks = $options['hooks'];
  132.  
  133.         $this->setup_handle($url, $headers, $data, $options);
  134.  
  135.         $options['hooks']->dispatch('curl.before_send', array(&$this->handle));
  136.  
  137.         if ($options['filename'] !== false) {
  138.             $this->stream_handle = fopen($options['filename'], 'wb');
  139.         }
  140.  
  141.         $this->response_data = '';
  142.         $this->response_bytes = 0;
  143.         $this->response_byte_limit = false;
  144.         if ($options['max_bytes'] !== false) {
  145.             $this->response_byte_limit = $options['max_bytes'];
  146.         }
  147.  
  148.         if (isset($options['verify'])) {
  149.             if ($options['verify'] === false) {
  150.                 curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
  151.                 curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0);
  152.             }
  153.             elseif (is_string($options['verify'])) {
  154.                 curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']);
  155.             }
  156.         }
  157.  
  158.         if (isset($options['verifyname']) && $options['verifyname'] === false) {
  159.             curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
  160.         }
  161.  
  162.         curl_exec($this->handle);
  163.         $response = $this->response_data;
  164.  
  165.         $options['hooks']->dispatch('curl.after_send', array());
  166.  
  167.         if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) {
  168.             // Reset encoding and try again
  169.             curl_setopt($this->handle, CURLOPT_ENCODING, 'none');
  170.  
  171.             $this->response_data = '';
  172.             $this->response_bytes = 0;
  173.             curl_exec($this->handle);
  174.             $response = $this->response_data;
  175.         }
  176.  
  177.         $this->process_response($response, $options);
  178.  
  179.         // Need to remove the $this reference from the curl handle.
  180.         // Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called.
  181.         curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
  182.         curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
  183.  
  184.         return $this->headers;
  185.     }
  186.  
  187.     /**
  188.      * Send multiple requests simultaneously
  189.      *
  190.      * @param array $requests Request data
  191.      * @param array $options Global options
  192.      * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
  193.      */
  194.     public function request_multiple($requests, $options) {
  195.         // If you're not requesting, we can't get any responses ┬»\_(πâä)_/┬»
  196.         if (empty($requests)) {
  197.             return array();
  198.         }
  199.  
  200.         $multihandle = curl_multi_init();
  201.         $subrequests = array();
  202.         $subhandles = array();
  203.  
  204.         $class = get_class($this);
  205.         foreach ($requests as $id => $request) {
  206.             $subrequests[$id] = new $class();
  207.             $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
  208.             $request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id]));
  209.             curl_multi_add_handle($multihandle, $subhandles[$id]);
  210.         }
  211.  
  212.         $completed = 0;
  213.         $responses = array();
  214.  
  215.         $request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));
  216.  
  217.         do {
  218.             $active = false;
  219.  
  220.             do {
  221.                 $status = curl_multi_exec($multihandle, $active);
  222.             }
  223.             while ($status === CURLM_CALL_MULTI_PERFORM);
  224.  
  225.             $to_process = array();
  226.  
  227.             // Read the information as needed
  228.             while ($done = curl_multi_info_read($multihandle)) {
  229.                 $key = array_search($done['handle'], $subhandles, true);
  230.                 if (!isset($to_process[$key])) {
  231.                     $to_process[$key] = $done;
  232.                 }
  233.             }
  234.  
  235.             // Parse the finished requests before we start getting the new ones
  236.             foreach ($to_process as $key => $done) {
  237.                 $options = $requests[$key]['options'];
  238.                 if (CURLE_OK !== $done['result']) {
  239.                     //get error string for handle.
  240.                     $reason = curl_error($done['handle']);
  241.                     $exception = new Requests_Exception_Transport_cURL(
  242.                                     $reason,
  243.                                     Requests_Exception_Transport_cURL::EASY,
  244.                                     $done['handle'],
  245.                                     $done['result']
  246.                                 );
  247.                     $responses[$key] = $exception;
  248.                     $options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key]));
  249.                 }
  250.                 else {
  251.                     $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);
  252.  
  253.                     $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key]));
  254.                 }
  255.  
  256.                 curl_multi_remove_handle($multihandle, $done['handle']);
  257.                 curl_close($done['handle']);
  258.  
  259.                 if (!is_string($responses[$key])) {
  260.                     $options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key));
  261.                 }
  262.                 $completed++;
  263.             }
  264.         }
  265.         while ($active || $completed < count($subrequests));
  266.  
  267.         $request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle));
  268.  
  269.         curl_multi_close($multihandle);
  270.  
  271.         return $responses;
  272.     }
  273.  
  274.     /**
  275.      * Get the cURL handle for use in a multi-request
  276.      *
  277.      * @param string $url URL to request
  278.      * @param array $headers Associative array of request headers
  279.      * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
  280.      * @param array $options Request options, see {@see Requests::response()} for documentation
  281.      * @return resource Subrequest's cURL handle
  282.      */
  283.     public function &get_subrequest_handle($url, $headers, $data, $options) {
  284.         $this->setup_handle($url, $headers, $data, $options);
  285.  
  286.         if ($options['filename'] !== false) {
  287.             $this->stream_handle = fopen($options['filename'], 'wb');
  288.         }
  289.  
  290.         $this->response_data = '';
  291.         $this->response_bytes = 0;
  292.         $this->response_byte_limit = false;
  293.         if ($options['max_bytes'] !== false) {
  294.             $this->response_byte_limit = $options['max_bytes'];
  295.         }
  296.         $this->hooks = $options['hooks'];
  297.  
  298.         return $this->handle;
  299.     }
  300.  
  301.     /**
  302.      * Setup the cURL handle for the given data
  303.      *
  304.      * @param string $url URL to request
  305.      * @param array $headers Associative array of request headers
  306.      * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
  307.      * @param array $options Request options, see {@see Requests::response()} for documentation
  308.      */
  309.     protected function setup_handle($url, $headers, $data, $options) {
  310.         $options['hooks']->dispatch('curl.before_request', array(&$this->handle));
  311.  
  312.         // Force closing the connection for old versions of cURL (<7.22).
  313.         if ( ! isset( $headers['Connection'] ) ) {
  314.             $headers['Connection'] = 'close';
  315.         }
  316.  
  317.         $headers = Requests::flatten($headers);
  318.  
  319.         if (!empty($data)) {
  320.             $data_format = $options['data_format'];
  321.  
  322.             if ($data_format === 'query') {
  323.                 $url = self::format_get($url, $data);
  324.                 $data = '';
  325.             }
  326.             elseif (!is_string($data)) {
  327.                 $data = http_build_query($data, null, '&');
  328.             }
  329.         }
  330.  
  331.         switch ($options['type']) {
  332.             case Requests::POST:
  333.                 curl_setopt($this->handle, CURLOPT_POST, true);
  334.                 curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
  335.                 break;
  336.             case Requests::HEAD:
  337.                 curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
  338.                 curl_setopt($this->handle, CURLOPT_NOBODY, true);
  339.                 break;
  340.             case Requests::TRACE:
  341.                 curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
  342.                 break;
  343.             case Requests::PATCH:
  344.             case Requests::PUT:
  345.             case Requests::DELETE:
  346.             case Requests::OPTIONS:
  347.             default:
  348.                 curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
  349.                 if (!empty($data)) {
  350.                     curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
  351.                 }
  352.         }
  353.  
  354.         // cURL requires a minimum timeout of 1 second when using the system
  355.         // DNS resolver, as it uses `alarm()`, which is second resolution only.
  356.         // There's no way to detect which DNS resolver is being used from our
  357.         // end, so we need to round up regardless of the supplied timeout.
  358.         //
  359.         // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
  360.         $timeout = max($options['timeout'], 1);
  361.  
  362.         if (is_int($timeout) || $this->version < self::CURL_7_16_2) {
  363.             curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout));
  364.         }
  365.         else {
  366.             curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));
  367.         }
  368.  
  369.         if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
  370.             curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
  371.         }
  372.         else {
  373.             curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
  374.         }
  375.         curl_setopt($this->handle, CURLOPT_URL, $url);
  376.         curl_setopt($this->handle, CURLOPT_REFERER, $url);
  377.         curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
  378.         if (!empty($headers)) {
  379.             curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
  380.         }
  381.         if ($options['protocol_version'] === 1.1) {
  382.             curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  383.         }
  384.         else {
  385.             curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
  386.         }
  387.  
  388.         if (true === $options['blocking']) {
  389.             curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers'));
  390.             curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body'));
  391.             curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
  392.         }
  393.     }
  394.  
  395.     /**
  396.      * Process a response
  397.      *
  398.      * @param string $response Response data from the body
  399.      * @param array $options Request options
  400.      * @return string HTTP response data including headers
  401.      */
  402.     public function process_response($response, $options) {
  403.         if ($options['blocking'] === false) {
  404.             $fake_headers = '';
  405.             $options['hooks']->dispatch('curl.after_request', array(&$fake_headers));
  406.             return false;
  407.         }
  408.         if ($options['filename'] !== false) {
  409.             fclose($this->stream_handle);
  410.             $this->headers = trim($this->headers);
  411.         }
  412.         else {
  413.             $this->headers .= $response;
  414.         }
  415.  
  416.         if (curl_errno($this->handle)) {
  417.             $error = sprintf(
  418.                 'cURL error %s: %s',
  419.                 curl_errno($this->handle),
  420.                 curl_error($this->handle)
  421.             );
  422.             throw new Requests_Exception($error, 'curlerror', $this->handle);
  423.         }
  424.         $this->info = curl_getinfo($this->handle);
  425.  
  426.         $options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info));
  427.         return $this->headers;
  428.     }
  429.  
  430.     /**
  431.      * Collect the headers as they are received
  432.      *
  433.      * @param resource $handle cURL resource
  434.      * @param string $headers Header string
  435.      * @return integer Length of provided header
  436.      */
  437.     public function stream_headers($handle, $headers) {
  438.         // Why do we do this? cURL will send both the final response and any
  439.         // interim responses, such as a 100 Continue. We don't need that.
  440.         // (We may want to keep this somewhere just in case)
  441.         if ($this->done_headers) {
  442.             $this->headers = '';
  443.             $this->done_headers = false;
  444.         }
  445.         $this->headers .= $headers;
  446.  
  447.         if ($headers === "\r\n") {
  448.             $this->done_headers = true;
  449.         }
  450.         return strlen($headers);
  451.     }
  452.  
  453.     /**
  454.      * Collect data as it's received
  455.      *
  456.      * @since 1.6.1
  457.      *
  458.      * @param resource $handle cURL resource
  459.      * @param string $data Body data
  460.      * @return integer Length of provided data
  461.      */
  462.     public function stream_body($handle, $data) {
  463.         $this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit));
  464.         $data_length = strlen($data);
  465.  
  466.         // Are we limiting the response size?
  467.         if ($this->response_byte_limit) {
  468.             if ($this->response_bytes === $this->response_byte_limit) {
  469.                 // Already at maximum, move on
  470.                 return $data_length;
  471.             }
  472.  
  473.             if (($this->response_bytes + $data_length) > $this->response_byte_limit) {
  474.                 // Limit the length
  475.                 $limited_length = ($this->response_byte_limit - $this->response_bytes);
  476.                 $data = substr($data, 0, $limited_length);
  477.             }
  478.         }
  479.  
  480.         if ($this->stream_handle) {
  481.             fwrite($this->stream_handle, $data);
  482.         }
  483.         else {
  484.             $this->response_data .= $data;
  485.         }
  486.  
  487.         $this->response_bytes += strlen($data);
  488.         return $data_length;
  489.     }
  490.  
  491.     /**
  492.      * Format a URL given GET data
  493.      *
  494.      * @param string $url
  495.      * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
  496.      * @return string URL with data
  497.      */
  498.     protected static function format_get($url, $data) {
  499.         if (!empty($data)) {
  500.             $url_parts = parse_url($url);
  501.             if (empty($url_parts['query'])) {
  502.                 $query = $url_parts['query'] = '';
  503.             }
  504.             else {
  505.                 $query = $url_parts['query'];
  506.             }
  507.  
  508.             $query .= '&' . http_build_query($data, null, '&');
  509.             $query = trim($query, '&');
  510.  
  511.             if (empty($url_parts['query'])) {
  512.                 $url .= '?' . $query;
  513.             }
  514.             else {
  515.                 $url = str_replace($url_parts['query'], $query, $url);
  516.             }
  517.         }
  518.         return $url;
  519.     }
  520.  
  521.     /**
  522.      * Whether this transport is valid
  523.      *
  524.      * @codeCoverageIgnore
  525.      * @return boolean True if the transport is valid, false otherwise.
  526.      */
  527.     public static function test($capabilities = array()) {
  528.         if (!function_exists('curl_init') || !function_exists('curl_exec')) {
  529.             return false;
  530.         }
  531.  
  532.         // If needed, check that our installed curl version supports SSL
  533.         if (isset($capabilities['ssl']) && $capabilities['ssl']) {
  534.             $curl_version = curl_version();
  535.             if (!(CURL_VERSION_SSL & $curl_version['features'])) {
  536.                 return false;
  537.             }
  538.         }
  539.  
  540.         return true;
  541.     }
  542. }
  543.