From f250f215c62fa7ecbdb56d4e026247ed5ca04458 Mon Sep 17 00:00:00 2001 From: ozh Date: Sat, 22 Apr 2017 19:32:11 +0200 Subject: [PATCH] Update rmccue/Requests to 1.7 --- includes/Requests/README.md | 148 +++++++++++++++++- includes/Requests/Requests.php | 77 +++++++-- includes/Requests/Requests/Cookie.php | 3 + includes/Requests/Requests/Cookie/Jar.php | 1 + .../Requests/Requests/Exception/HTTP/418.php | 4 +- .../Requests/Requests/Exception/HTTP/428.php | 4 +- .../Requests/Requests/Exception/HTTP/429.php | 4 +- .../Requests/Requests/Exception/HTTP/431.php | 4 +- .../Requests/Requests/Exception/HTTP/511.php | 4 +- .../Requests/Requests/Exception/Transport.php | 5 + .../Requests/Exception/Transport/cURL.php | 56 +++++++ includes/Requests/Requests/IDNAEncoder.php | 12 +- includes/Requests/Requests/IRI.php | 5 +- includes/Requests/Requests/SSL.php | 2 +- includes/Requests/Requests/Session.php | 12 +- includes/Requests/Requests/Transport/cURL.php | 73 ++++++--- .../Requests/Requests/Transport/fsockopen.php | 15 +- 17 files changed, 364 insertions(+), 65 deletions(-) create mode 100644 includes/Requests/Requests/Exception/Transport.php create mode 100644 includes/Requests/Requests/Exception/Transport/cURL.php diff --git a/includes/Requests/README.md b/includes/Requests/README.md index 1abf037..8e99a20 100644 --- a/includes/Requests/README.md +++ b/includes/Requests/README.md @@ -1,10 +1,152 @@ Requests for PHP ================ -**Requests** is a HTTP library written in PHP, for human beings. It is roughly +[![Build Status](https://travis-ci.org/rmccue/Requests.svg?branch=master)](https://travis-ci.org/rmccue/Requests) +[![codecov.io](http://codecov.io/github/rmccue/Requests/coverage.svg?branch=master)](http://codecov.io/github/rmccue/Requests?branch=master) + +Requests is a HTTP library written in PHP, for human beings. It is roughly based on the API from the excellent [Requests Python -library](http://python-requests.org/). **Requests** is [ISC +library](http://python-requests.org/). Requests is [ISC Licensed](https://github.com/rmccue/Requests/blob/master/LICENSE) (similar to the new BSD license) and has no dependencies, except for PHP 5.2+. -**Requests** can be found here : https://github.com/rmccue/Requests +Despite PHP's use as a language for the web, its tools for sending HTTP requests +are severely lacking. cURL has an +[interesting API](http://php.net/manual/en/function.curl-setopt.php), to say the +least, and you can't always rely on it being available. Sockets provide only low +level access, and require you to build most of the HTTP response parsing +yourself. + +We all have better things to do. That's why Requests was born. + +```php +$headers = array('Accept' => 'application/json'); +$options = array('auth' => array('user', 'pass')); +$request = Requests::get('https://api.github.com/gists', $headers, $options); + +var_dump($request->status_code); +// int(200) + +var_dump($request->headers['content-type']); +// string(31) "application/json; charset=utf-8" + +var_dump($request->body); +// string(26891) "[...]" +``` + +Requests allows you to send **HEAD**, **GET**, **POST**, **PUT**, **DELETE**, +and **PATCH** HTTP requests. You can add headers, form data, multipart files, +and parameters with simple arrays, and access the response data in the same way. +Requests uses cURL and fsockopen, depending on what your system has available, +but abstracts all the nasty stuff out of your way, providing a consistent API. + + +Features +-------- + +- International Domains and URLs +- Browser-style SSL Verification +- Basic/Digest Authentication +- Automatic Decompression +- Connection Timeouts + + +Installation +------------ + +### Install with Composer +If you're using [Composer](https://github.com/composer/composer) to manage +dependencies, you can add Requests with it. + +```sh +composer require rmccue/requests +``` + +or + + { + "require": { + "rmccue/requests": ">=1.0" + } + } + +### Install source from GitHub +To install the source code: + + $ git clone git://github.com/rmccue/Requests.git + +And include it in your scripts: + + require_once '/path/to/Requests/library/Requests.php'; + +You'll probably also want to register an autoloader: + + Requests::register_autoloader(); + + +### Install source from zip/tarball +Alternatively, you can fetch a [tarball][] or [zipball][]: + + $ curl -L https://github.com/rmccue/Requests/tarball/master | tar xzv + (or) + $ wget https://github.com/rmccue/Requests/tarball/master -O - | tar xzv + +[tarball]: https://github.com/rmccue/Requests/tarball/master +[zipball]: https://github.com/rmccue/Requests/zipball/master + + +### Using a Class Loader +If you're using a class loader (e.g., [Symfony Class Loader][]) for +[PSR-0][]-style class loading: + + $loader->registerPrefix('Requests', 'path/to/vendor/Requests/library'); + +[Symfony Class Loader]: https://github.com/symfony/ClassLoader +[PSR-0]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + + +Documentation +------------- +The best place to start is our [prose-based documentation][], which will guide +you through using Requests. + +After that, take a look at [the documentation for +`Requests::request()`][request_method], where all the parameters are fully +documented. + +Requests is [100% documented with PHPDoc](http://requests.ryanmccue.info/api/). +If you find any problems with it, [create a new +issue](https://github.com/rmccue/Requests/issues/new)! + +[prose-based documentation]: https://github.com/rmccue/Requests/blob/master/docs/README.md +[request_method]: http://requests.ryanmccue.info/api/class-Requests.html#_request + +Testing +------- + +Requests strives to have 100% code-coverage of the library with an extensive +set of tests. We're not quite there yet, but [we're getting close][codecov]. + +[codecov]: http://codecov.io/github/rmccue/Requests + +To run the test suite, first check that you have the [PHP +JSON extension ](http://php.net/manual/en/book.json.php) enabled. Then +simply: + + $ cd tests + $ phpunit + +If you'd like to run a single set of tests, specify just the name: + + $ phpunit Transport/cURL + +Contribute +---------- + +1. Check for open issues or open a new issue for a feature request or a bug +2. Fork [the repository][] on Github to start making your changes to the + `master` branch (or branch off of it) +3. Write a test which shows that the bug was fixed or that the feature works as expected +4. Send a pull request and bug me until I merge it + +[the repository]: https://github.com/rmccue/Requests diff --git a/includes/Requests/Requests.php b/includes/Requests/Requests.php index 8f1f7be..bb26618 100644 --- a/includes/Requests/Requests.php +++ b/includes/Requests/Requests.php @@ -71,7 +71,7 @@ class Requests { /** * PATCH method * - * @link http://tools.ietf.org/html/rfc5789 + * @link https://tools.ietf.org/html/rfc5789 * @var string */ const PATCH = 'PATCH'; @@ -88,7 +88,7 @@ class Requests { * * @var string */ - const VERSION = '1.6'; + const VERSION = '1.7'; /** * Registered transport classes @@ -106,6 +106,16 @@ class Requests { */ public static $transport = array(); + /** + * Default certificate path. + * + * @see Requests::get_certificate_path() + * @see Requests::set_certificate_path() + * + * @var string + */ + protected static $certificate_path; + /** * This is a static class, do not instantiate it * @@ -277,7 +287,7 @@ class Requests { * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the * specification recommends that should send an ETag * - * @link http://tools.ietf.org/html/rfc5789 + * @link https://tools.ietf.org/html/rfc5789 */ public static function patch($url, $headers, $data = array(), $options = array()) { return self::request($url, $headers, $data, self::PATCH, $options); @@ -294,6 +304,8 @@ class Requests { * options: * * - `timeout`: How long should we wait for a response? + * Note: for cURL, a minimum of 1 second applies, as DNS resolution + * operates at second-resolution only. * (float, seconds with a millisecond precision, default: 10, example: 0.01) * - `connect_timeout`: How long should we wait while trying to connect? * (float, seconds with a millisecond precision, default: 10, example: 0.01) @@ -311,7 +323,7 @@ class Requests { * for Basic authentication * (Requests_Auth|array|boolean, default: false) * - `proxy`: Proxy details to use for proxy by-passing and authentication - * (Requests_Proxy|array|boolean, default: false) + * (Requests_Proxy|array|string|boolean, default: false) * - `max_bytes`: Limit for the response body size. * (integer|boolean, default: false) * - `idn`: Enable IDN parsing @@ -506,7 +518,7 @@ class Requests { 'idn' => true, 'hooks' => null, 'transport' => null, - 'verify' => dirname(__FILE__) . '/Requests/Transport/cacert.pem', + 'verify' => Requests::get_certificate_path(), 'verifyname' => true, ); if ($multirequest !== false) { @@ -515,6 +527,28 @@ class Requests { return $defaults; } + /** + * Get default certificate path. + * + * @return string Default certificate path. + */ + public static function get_certificate_path() { + if ( ! empty( Requests::$certificate_path ) ) { + return Requests::$certificate_path; + } + + return dirname(__FILE__) . '/Requests/Transport/cacert.pem'; + } + + /** + * Set default certificate path. + * + * @param string $path Certificate path, pointing to a PEM file. + */ + public static function set_certificate_path( $path ) { + Requests::$certificate_path = $path; + } + /** * Set the default values * @@ -541,7 +575,7 @@ class Requests { $options['auth']->register($options['hooks']); } - if (!empty($options['proxy'])) { + if (is_string($options['proxy']) || is_array($options['proxy'])) { $options['proxy'] = new Requests_Proxy_HTTP($options['proxy']); } if ($options['proxy'] !== false) { @@ -564,6 +598,9 @@ class Requests { $url = $iri->uri; } + // Massage the type to ensure we support it. + $type = strtoupper($type); + if (!isset($options['data_format'])) { if (in_array($type, array(self::HEAD, self::GET, self::DELETE))) { $options['data_format'] = 'query'; @@ -657,6 +694,15 @@ class Requests { $location = Requests_IRI::absolutize($url, $location); $location = $location->uri; } + + $hook_args = array( + &$location, + &$req_headers, + &$req_data, + &$options, + $return + ); + $options['hooks']->dispatch('requests.before_redirect', $hook_args); $redirected = self::request($location, $req_headers, $req_data, $options['type'], $options); $redirected->history[] = $return; return $redirected; @@ -698,20 +744,22 @@ class Requests { /** * Decoded a chunked body as per RFC 2616 * - * @see http://tools.ietf.org/html/rfc2616#section-3.6.1 + * @see https://tools.ietf.org/html/rfc2616#section-3.6.1 * @param string $data Chunked body * @return string Decoded body */ protected static function decode_chunked($data) { - if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($data))) { + if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) { return $data; } + + $decoded = ''; $encoded = $data; while (true) { - $is_chunked = (bool) preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches); + $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches); if (!$is_chunked) { // Looks like it's not chunked after all return $data; @@ -754,6 +802,7 @@ class Requests { /** * Convert a key => value array to a 'key: value' array for headers * + * @codeCoverageIgnore * @deprecated Misspelling of {@see Requests::flatten} * @param array $array Dictionary of header values * @return array List of headers @@ -803,12 +852,12 @@ class Requests { * Warning: Magic numbers within. Due to the potential different formats that the compressed * data may be returned in, some "magic offsets" are needed to ensure proper decompression * takes place. For a simple progmatic way to determine the magic offset in use, see: - * http://core.trac.wordpress.org/ticket/18273 + * https://core.trac.wordpress.org/ticket/18273 * * @since 2.8.1 - * @link http://core.trac.wordpress.org/ticket/18273 - * @link http://au2.php.net/manual/en/function.gzinflate.php#70875 - * @link http://au2.php.net/manual/en/function.gzinflate.php#77336 + * @link https://core.trac.wordpress.org/ticket/18273 + * @link https://secure.php.net/manual/en/function.gzinflate.php#70875 + * @link https://secure.php.net/manual/en/function.gzinflate.php#77336 * * @param string $gzData String to decompress. * @return string|bool False on failure. @@ -846,7 +895,7 @@ class Requests { // java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's // System.IO.Compression.DeflateStream. // - // See http://decompres.blogspot.com/ for a quick explanation of this + // See https://decompres.blogspot.com/ for a quick explanation of this // data type $huffman_encoded = false; diff --git a/includes/Requests/Requests/Cookie.php b/includes/Requests/Requests/Cookie.php index c239fed..00fbbc7 100644 --- a/includes/Requests/Requests/Cookie.php +++ b/includes/Requests/Requests/Cookie.php @@ -314,6 +314,7 @@ class Requests_Cookie { /** * Format a cookie for a Cookie header * + * @codeCoverageIgnore * @deprecated Use {@see Requests_Cookie::format_for_header} * @return string */ @@ -351,6 +352,7 @@ class Requests_Cookie { /** * Format a cookie for a Set-Cookie header * + * @codeCoverageIgnore * @deprecated Use {@see Requests_Cookie::format_for_set_cookie} * @return string */ @@ -488,6 +490,7 @@ class Requests_Cookie { /** * Parse all Set-Cookie headers from request headers * + * @codeCoverageIgnore * @deprecated Use {@see Requests_Cookie::parse_from_headers} * @return string */ diff --git a/includes/Requests/Requests/Cookie/Jar.php b/includes/Requests/Requests/Cookie/Jar.php index 60f01bd..69be0fb 100644 --- a/includes/Requests/Requests/Cookie/Jar.php +++ b/includes/Requests/Requests/Cookie/Jar.php @@ -46,6 +46,7 @@ class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate { /** * Normalise cookie data into a Requests_Cookie * + * @codeCoverageIgnore * @deprecated Use {@see Requests_Cookie_Jar::normalize_cookie} * @return Requests_Cookie */ diff --git a/includes/Requests/Requests/Exception/HTTP/418.php b/includes/Requests/Requests/Exception/HTTP/418.php index a554d73..d6af806 100644 --- a/includes/Requests/Requests/Exception/HTTP/418.php +++ b/includes/Requests/Requests/Exception/HTTP/418.php @@ -2,14 +2,14 @@ /** * Exception for 418 I'm A Teapot responses * - * @see http://tools.ietf.org/html/rfc2324 + * @see https://tools.ietf.org/html/rfc2324 * @package Requests */ /** * Exception for 418 I'm A Teapot responses * - * @see http://tools.ietf.org/html/rfc2324 + * @see https://tools.ietf.org/html/rfc2324 * @package Requests */ class Requests_Exception_HTTP_418 extends Requests_Exception_HTTP { diff --git a/includes/Requests/Requests/Exception/HTTP/428.php b/includes/Requests/Requests/Exception/HTTP/428.php index e320b46..469e954 100644 --- a/includes/Requests/Requests/Exception/HTTP/428.php +++ b/includes/Requests/Requests/Exception/HTTP/428.php @@ -2,14 +2,14 @@ /** * Exception for 428 Precondition Required responses * - * @see http://tools.ietf.org/html/rfc6585 + * @see https://tools.ietf.org/html/rfc6585 * @package Requests */ /** * Exception for 428 Precondition Required responses * - * @see http://tools.ietf.org/html/rfc6585 + * @see https://tools.ietf.org/html/rfc6585 * @package Requests */ class Requests_Exception_HTTP_428 extends Requests_Exception_HTTP { diff --git a/includes/Requests/Requests/Exception/HTTP/429.php b/includes/Requests/Requests/Exception/HTTP/429.php index 0970e1e..2a21fb3 100644 --- a/includes/Requests/Requests/Exception/HTTP/429.php +++ b/includes/Requests/Requests/Exception/HTTP/429.php @@ -2,14 +2,14 @@ /** * Exception for 429 Too Many Requests responses * - * @see http://tools.ietf.org/html/draft-nottingham-http-new-status-04 + * @see https://tools.ietf.org/html/draft-nottingham-http-new-status-04 * @package Requests */ /** * Exception for 429 Too Many Requests responses * - * @see http://tools.ietf.org/html/draft-nottingham-http-new-status-04 + * @see https://tools.ietf.org/html/draft-nottingham-http-new-status-04 * @package Requests */ class Requests_Exception_HTTP_429 extends Requests_Exception_HTTP { diff --git a/includes/Requests/Requests/Exception/HTTP/431.php b/includes/Requests/Requests/Exception/HTTP/431.php index eec56e5..ba1294e 100644 --- a/includes/Requests/Requests/Exception/HTTP/431.php +++ b/includes/Requests/Requests/Exception/HTTP/431.php @@ -2,14 +2,14 @@ /** * Exception for 431 Request Header Fields Too Large responses * - * @see http://tools.ietf.org/html/rfc6585 + * @see https://tools.ietf.org/html/rfc6585 * @package Requests */ /** * Exception for 431 Request Header Fields Too Large responses * - * @see http://tools.ietf.org/html/rfc6585 + * @see https://tools.ietf.org/html/rfc6585 * @package Requests */ class Requests_Exception_HTTP_431 extends Requests_Exception_HTTP { diff --git a/includes/Requests/Requests/Exception/HTTP/511.php b/includes/Requests/Requests/Exception/HTTP/511.php index 90bcc5d..920d334 100644 --- a/includes/Requests/Requests/Exception/HTTP/511.php +++ b/includes/Requests/Requests/Exception/HTTP/511.php @@ -2,14 +2,14 @@ /** * Exception for 511 Network Authentication Required responses * - * @see http://tools.ietf.org/html/rfc6585 + * @see https://tools.ietf.org/html/rfc6585 * @package Requests */ /** * Exception for 511 Network Authentication Required responses * - * @see http://tools.ietf.org/html/rfc6585 + * @see https://tools.ietf.org/html/rfc6585 * @package Requests */ class Requests_Exception_HTTP_511 extends Requests_Exception_HTTP { diff --git a/includes/Requests/Requests/Exception/Transport.php b/includes/Requests/Requests/Exception/Transport.php new file mode 100644 index 0000000..e60b488 --- /dev/null +++ b/includes/Requests/Requests/Exception/Transport.php @@ -0,0 +1,5 @@ +type = $type; + } + + if ($code !== null) { + $this->code = $code; + } + + if ($message !== null) { + $this->reason = $message; + } + + $message = sprintf('%d %s', $this->code, $this->reason); + parent::__construct($message, $this->type, $data, $this->code); + } + + /** + * Get the error message + */ + public function getReason() { + return $this->reason; + } + +} diff --git a/includes/Requests/Requests/IDNAEncoder.php b/includes/Requests/Requests/IDNAEncoder.php index eaecb73..ebbe211 100644 --- a/includes/Requests/Requests/IDNAEncoder.php +++ b/includes/Requests/Requests/IDNAEncoder.php @@ -7,14 +7,14 @@ * * @package Requests * @subpackage Utilities - * @see http://tools.ietf.org/html/rfc3490 IDNA specification - * @see http://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification + * @see https://tools.ietf.org/html/rfc3490 IDNA specification + * @see https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification */ class Requests_IDNAEncoder { /** * ACE prefix used for IDNA * - * @see http://tools.ietf.org/html/rfc3490#section-5 + * @see https://tools.ietf.org/html/rfc3490#section-5 * @var string */ const ACE_PREFIX = 'xn--'; @@ -22,7 +22,7 @@ class Requests_IDNAEncoder { /**#@+ * Bootstrap constant for Punycode * - * @see http://tools.ietf.org/html/rfc3492#section-5 + * @see https://tools.ietf.org/html/rfc3492#section-5 * @var int */ const BOOTSTRAP_BASE = 36; @@ -333,7 +333,7 @@ class Requests_IDNAEncoder { /** * Convert a digit to its respective character * - * @see http://tools.ietf.org/html/rfc3492#section-5 + * @see https://tools.ietf.org/html/rfc3492#section-5 * @throws Requests_Exception On invalid digit (`idna.invalid_digit`) * * @param int $digit Digit in the range 0-35 @@ -353,7 +353,7 @@ class Requests_IDNAEncoder { /** * Adapt the bias * - * @see http://tools.ietf.org/html/rfc3492#section-6.1 + * @see https://tools.ietf.org/html/rfc3492#section-6.1 * @param int $delta * @param int $numpoints * @param bool $firsttime diff --git a/includes/Requests/Requests/IRI.php b/includes/Requests/Requests/IRI.php index 44a9517..8dc2fa2 100644 --- a/includes/Requests/Requests/IRI.php +++ b/includes/Requests/Requests/IRI.php @@ -688,10 +688,7 @@ class Requests_IRI { $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; if ($this->ipath !== '' && ( - $isauthority && ( - $this->ipath[0] !== '/' || - substr($this->ipath, 0, 2) === '//' - ) || + $isauthority && $this->ipath[0] !== '/' || ( $this->scheme === null && !$isauthority && diff --git a/includes/Requests/Requests/SSL.php b/includes/Requests/Requests/SSL.php index c227f75..2b03768 100644 --- a/includes/Requests/Requests/SSL.php +++ b/includes/Requests/Requests/SSL.php @@ -22,7 +22,7 @@ class Requests_SSL { * names, leading things like 'https://www.github.com/' to be invalid. * Instead * - * @see http://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 + * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 * * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`) * @param string $host Host name to verify against diff --git a/includes/Requests/Requests/Session.php b/includes/Requests/Requests/Session.php index 2660085..af14bbe 100644 --- a/includes/Requests/Requests/Session.php +++ b/includes/Requests/Requests/Session.php @@ -175,7 +175,7 @@ class Requests_Session { * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the * specification recommends that should send an ETag * - * @link http://tools.ietf.org/html/rfc5789 + * @link https://tools.ietf.org/html/rfc5789 */ public function patch($url, $headers, $data = array(), $options = array()) { return $this->request($url, $headers, $data, Requests::PATCH, $options); @@ -240,9 +240,17 @@ class Requests_Session { $request['url'] = $request['url']->uri; } + if (empty($request['headers'])) { + $request['headers'] = array(); + } $request['headers'] = array_merge($this->headers, $request['headers']); - if (is_array($request['data']) && is_array($this->data)) { + if (empty($request['data'])) { + if (is_array($this->data)) { + $request['data'] = $this->data; + } + } + elseif (is_array($request['data']) && is_array($this->data)) { $request['data'] = array_merge($this->data, $request['data']); } diff --git a/includes/Requests/Requests/Transport/cURL.php b/includes/Requests/Requests/Transport/cURL.php index c1ad446..4429edb 100644 --- a/includes/Requests/Requests/Transport/cURL.php +++ b/includes/Requests/Requests/Transport/cURL.php @@ -33,7 +33,7 @@ class Requests_Transport_cURL implements Requests_Transport { /** * Information on the current request * - * @var array cURL information array, see {@see http://php.net/curl_getinfo} + * @var array cURL information array, see {@see https://secure.php.net/curl_getinfo} */ public $info; @@ -176,6 +176,11 @@ class Requests_Transport_cURL implements Requests_Transport { $this->process_response($response, $options); + // Need to remove the $this reference from the curl handle. + // Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called. + curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null); + curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null); + return $this->headers; } @@ -230,9 +235,23 @@ class Requests_Transport_cURL implements Requests_Transport { // Parse the finished requests before we start getting the new ones foreach ($to_process as $key => $done) { $options = $requests[$key]['options']; - $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); + if (CURLE_OK !== $done['result']) { + //get error string for handle. + $reason = curl_error($done['handle']); + $exception = new Requests_Exception_Transport_cURL( + $reason, + Requests_Exception_Transport_cURL::EASY, + $done['handle'], + $done['result'] + ); + $responses[$key] = $exception; + $options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key])); + } + else { + $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); - $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key])); + $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key])); + } curl_multi_remove_handle($multihandle, $done['handle']); curl_close($done['handle']); @@ -290,6 +309,11 @@ class Requests_Transport_cURL implements Requests_Transport { protected function setup_handle($url, $headers, $data, $options) { $options['hooks']->dispatch('curl.before_request', array(&$this->handle)); + // Force closing the connection for old versions of cURL (<7.22). + if ( ! isset( $headers['Connection'] ) ) { + $headers['Connection'] = 'close'; + } + $headers = Requests::flatten($headers); if (!empty($data)) { @@ -309,13 +333,6 @@ class Requests_Transport_cURL implements Requests_Transport { curl_setopt($this->handle, CURLOPT_POST, true); curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); break; - case Requests::PATCH: - case Requests::PUT: - case Requests::DELETE: - case Requests::OPTIONS: - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); - curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); - break; case Requests::HEAD: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); curl_setopt($this->handle, CURLOPT_NOBODY, true); @@ -323,13 +340,30 @@ class Requests_Transport_cURL implements Requests_Transport { case Requests::TRACE: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); break; + case Requests::PATCH: + case Requests::PUT: + case Requests::DELETE: + case Requests::OPTIONS: + default: + curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); + if (!empty($data)) { + curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); + } } - if (is_int($options['timeout']) || $this->version < self::CURL_7_16_2) { - curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($options['timeout'])); + // cURL requires a minimum timeout of 1 second when using the system + // DNS resolver, as it uses `alarm()`, which is second resolution only. + // There's no way to detect which DNS resolver is being used from our + // end, so we need to round up regardless of the supplied timeout. + // + // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609 + $timeout = max($options['timeout'], 1); + + if (is_int($timeout) || $this->version < self::CURL_7_16_2) { + curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout)); } else { - curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($options['timeout'] * 1000)); + curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000)); } if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) { @@ -341,8 +375,9 @@ class Requests_Transport_cURL implements Requests_Transport { curl_setopt($this->handle, CURLOPT_URL, $url); curl_setopt($this->handle, CURLOPT_REFERER, $url); curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']); - curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); - + if (!empty($headers)) { + curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); + } if ($options['protocol_version'] === 1.1) { curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); } @@ -388,7 +423,7 @@ class Requests_Transport_cURL implements Requests_Transport { } $this->info = curl_getinfo($this->handle); - $options['hooks']->dispatch('curl.after_request', array(&$this->headers)); + $options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info)); return $this->headers; } @@ -424,7 +459,7 @@ class Requests_Transport_cURL implements Requests_Transport { * @param string $data Body data * @return integer Length of provided data */ - protected function stream_body($handle, $data) { + public function stream_body($handle, $data) { $this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit)); $data_length = strlen($data); @@ -457,7 +492,7 @@ class Requests_Transport_cURL implements Requests_Transport { * Format a URL given GET data * * @param string $url - * @param array|object $data Data to build query using, see {@see http://php.net/http_build_query} + * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query} * @return string URL with data */ protected static function format_get($url, $data) { @@ -490,7 +525,7 @@ class Requests_Transport_cURL implements Requests_Transport { * @return boolean True if the transport is valid, false otherwise. */ public static function test($capabilities = array()) { - if (!function_exists('curl_init') && !function_exists('curl_exec')) { + if (!function_exists('curl_init') || !function_exists('curl_exec')) { return false; } diff --git a/includes/Requests/Requests/Transport/fsockopen.php b/includes/Requests/Requests/Transport/fsockopen.php index 197d513..21cb56d 100644 --- a/includes/Requests/Requests/Transport/fsockopen.php +++ b/includes/Requests/Requests/Transport/fsockopen.php @@ -30,7 +30,7 @@ class Requests_Transport_fsockopen implements Requests_Transport { /** * Stream metadata * - * @var array Associative array of properties, see {@see http://php.net/stream_get_meta_data} + * @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data} */ public $info; @@ -70,7 +70,9 @@ class Requests_Transport_fsockopen implements Requests_Transport { // HTTPS support if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { $remote_socket = 'ssl://' . $host; - $url_parts['port'] = 443; + if (!isset($url_parts['port'])) { + $url_parts['port'] = 443; + } $context_options = array( 'verify_peer' => true, @@ -97,6 +99,7 @@ class Requests_Transport_fsockopen implements Requests_Transport { } if (isset($options['verifyname']) && $options['verifyname'] === false) { + $context_options['verify_peer_name'] = false; $verifyname = false; } @@ -171,7 +174,7 @@ class Requests_Transport_fsockopen implements Requests_Transport { if (!isset($case_insensitive_headers['Host'])) { $out .= sprintf('Host: %s', $url_parts['host']); - if ($url_parts['port'] !== 80) { + if (( 'http' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 80 ) || ( 'https' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 443 )) { $out .= ':' . $url_parts['port']; } $out .= "\r\n"; @@ -284,7 +287,7 @@ class Requests_Transport_fsockopen implements Requests_Transport { } fclose($socket); - $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers)); + $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info)); return $this->headers; } @@ -341,7 +344,7 @@ class Requests_Transport_fsockopen implements Requests_Transport { * Format a URL given GET data * * @param array $url_parts - * @param array|object $data Data to build query using, see {@see http://php.net/http_build_query} + * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query} * @return string URL with data */ protected static function format_get($url_parts, $data) { @@ -391,7 +394,7 @@ class Requests_Transport_fsockopen implements Requests_Transport { * names, leading things like 'https://www.github.com/' to be invalid. * Instead * - * @see http://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 + * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 * * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`) * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`) -- 2.42.0