aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/http/lib/Client.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sabre/http/lib/Client.php')
-rw-r--r--vendor/sabre/http/lib/Client.php325
1 files changed, 170 insertions, 155 deletions
diff --git a/vendor/sabre/http/lib/Client.php b/vendor/sabre/http/lib/Client.php
index 0810c4a25..48862e7da 100644
--- a/vendor/sabre/http/lib/Client.php
+++ b/vendor/sabre/http/lib/Client.php
@@ -1,5 +1,7 @@
<?php
+declare(strict_types=1);
+
namespace Sabre\HTTP;
use Sabre\Event\EventEmitter;
@@ -41,10 +43,10 @@ use Sabre\Uri;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
-class Client extends EventEmitter {
-
+class Client extends EventEmitter
+{
/**
- * List of curl settings
+ * List of curl settings.
*
* @var array
*/
@@ -64,53 +66,61 @@ class Client extends EventEmitter {
*/
protected $maxRedirects = 5;
+ protected $headerLinesMap = [];
+
/**
* Initializes the client.
- *
- * @return void
*/
- function __construct() {
+ public function __construct()
+ {
+ // See https://github.com/sabre-io/http/pull/115#discussion_r241292068
+ // Preserve compatibility for sub-classes that implements their own method `parseCurlResult`
+ $separatedHeaders = __CLASS__ === get_class($this);
$this->curlSettings = [
CURLOPT_RETURNTRANSFER => true,
- CURLOPT_HEADER => true,
- CURLOPT_NOBODY => false,
- CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
+ CURLOPT_NOBODY => false,
+ CURLOPT_USERAGENT => 'sabre-http/'.Version::VERSION.' (http://sabre.io/)',
];
+ if ($separatedHeaders) {
+ $this->curlSettings[CURLOPT_HEADERFUNCTION] = [$this, 'receiveCurlHeader'];
+ } else {
+ $this->curlSettings[CURLOPT_HEADER] = true;
+ }
+ }
+
+ protected function receiveCurlHeader($curlHandle, $headerLine)
+ {
+ $this->headerLinesMap[(int) $curlHandle][] = $headerLine;
+ return strlen($headerLine);
}
/**
* Sends a request to a HTTP server, and returns a response.
- *
- * @param RequestInterface $request
- * @return ResponseInterface
*/
- function send(RequestInterface $request) {
-
+ public function send(RequestInterface $request): ResponseInterface
+ {
$this->emit('beforeRequest', [$request]);
$retryCount = 0;
$redirects = 0;
do {
-
$doRedirect = false;
$retry = false;
try {
-
$response = $this->doRequest($request);
- $code = (int)$response->getStatus();
+ $code = $response->getStatus();
// We are doing in-PHP redirects, because curl's
// FOLLOW_LOCATION throws errors when PHP is configured with
// open_basedir.
//
// https://github.com/fruux/sabre-http/issues/12
- if (in_array($code, [301, 302, 307, 308]) && $redirects < $this->maxRedirects) {
-
+ if ($redirects < $this->maxRedirects && in_array($code, [301, 302, 307, 308])) {
$oldLocation = $request->getUrl();
// Creating a new instance of the request object.
@@ -123,20 +133,15 @@ class Client extends EventEmitter {
));
$doRedirect = true;
- $redirects++;
-
+ ++$redirects;
}
// This was a HTTP error
if ($code >= 400) {
-
$this->emit('error', [$request, $response, &$retry, $retryCount]);
- $this->emit('error:' . $code, [$request, $response, &$retry, $retryCount]);
-
+ $this->emit('error:'.$code, [$request, $response, &$retry, $retryCount]);
}
-
} catch (ClientException $e) {
-
$this->emit('exception', [$request, $e, &$retry, $retryCount]);
// If retry was still set to false, it means no event handler
@@ -145,13 +150,11 @@ class Client extends EventEmitter {
if (!$retry) {
throw $e;
}
-
}
if ($retry) {
- $retryCount++;
+ ++$retryCount;
}
-
} while ($retry || $doRedirect);
$this->emit('afterRequest', [$request, $response]);
@@ -161,7 +164,6 @@ class Client extends EventEmitter {
}
return $response;
-
}
/**
@@ -172,32 +174,23 @@ class Client extends EventEmitter {
*
* After calling sendAsync, you must therefore occasionally call the poll()
* method, or wait().
- *
- * @param RequestInterface $request
- * @param callable $success
- * @param callable $error
- * @return void
*/
- function sendAsync(RequestInterface $request, callable $success = null, callable $error = null) {
-
+ public function sendAsync(RequestInterface $request, callable $success = null, callable $error = null)
+ {
$this->emit('beforeRequest', [$request]);
$this->sendAsyncInternal($request, $success, $error);
$this->poll();
-
}
-
/**
* This method checks if any http requests have gotten results, and if so,
* call the appropriate success or error handlers.
*
* This method will return true if there are still requests waiting to
* return, and false if all the work is done.
- *
- * @return bool
*/
- function poll() {
-
+ public function poll(): bool
+ {
// nothing to do?
if (!$this->curlMultiMap) {
return false;
@@ -208,10 +201,10 @@ class Client extends EventEmitter {
$this->curlMultiHandle,
$stillRunning
);
- } while ($r === CURLM_CALL_MULTI_PERFORM);
+ } while (CURLM_CALL_MULTI_PERFORM === $r);
+ $messagesInQueue = 0;
do {
-
messageQueue:
$status = curl_multi_info_read(
@@ -219,26 +212,25 @@ class Client extends EventEmitter {
$messagesInQueue
);
- if ($status && $status['msg'] === CURLMSG_DONE) {
-
- $resourceId = intval($status['handle']);
+ if ($status && CURLMSG_DONE === $status['msg']) {
+ $resourceId = (int) $status['handle'];
list(
$request,
$successCallback,
$errorCallback,
- $retryCount,
- ) = $this->curlMultiMap[$resourceId];
+ $retryCount) = $this->curlMultiMap[$resourceId];
unset($this->curlMultiMap[$resourceId]);
- $curlResult = $this->parseCurlResult(curl_multi_getcontent($status['handle']), $status['handle']);
- $retry = false;
- if ($curlResult['status'] === self::STATUS_CURLERROR) {
+ $curlHandle = $status['handle'];
+ $curlResult = $this->parseResponse(curl_multi_getcontent($curlHandle), $curlHandle);
+ $retry = false;
+ if (self::STATUS_CURLERROR === $curlResult['status']) {
$e = new ClientException($curlResult['curl_errmsg'], $curlResult['curl_errno']);
$this->emit('exception', [$request, $e, &$retry, $retryCount]);
if ($retry) {
- $retryCount++;
+ ++$retryCount;
$this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
goto messageQueue;
}
@@ -248,18 +240,14 @@ class Client extends EventEmitter {
if ($errorCallback) {
$errorCallback($curlResult);
}
-
- } elseif ($curlResult['status'] === self::STATUS_HTTPERROR) {
-
+ } elseif (self::STATUS_HTTPERROR === $curlResult['status']) {
$this->emit('error', [$request, $curlResult['response'], &$retry, $retryCount]);
- $this->emit('error:' . $curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]);
+ $this->emit('error:'.$curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]);
if ($retry) {
-
- $retryCount++;
+ ++$retryCount;
$this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
goto messageQueue;
-
}
$curlResult['request'] = $request;
@@ -267,37 +255,29 @@ class Client extends EventEmitter {
if ($errorCallback) {
$errorCallback($curlResult);
}
-
} else {
-
$this->emit('afterRequest', [$request, $curlResult['response']]);
if ($successCallback) {
$successCallback($curlResult['response']);
}
-
}
}
-
} while ($messagesInQueue > 0);
return count($this->curlMultiMap) > 0;
-
}
/**
* Processes every HTTP request in the queue, and waits till they are all
* completed.
- *
- * @return void
*/
- function wait() {
-
+ public function wait()
+ {
do {
curl_multi_select($this->curlMultiHandle);
$stillRunning = $this->poll();
} while ($stillRunning);
-
}
/**
@@ -309,14 +289,10 @@ class Client extends EventEmitter {
*
* This only works for the send() method. Throwing exceptions for
* sendAsync() is not supported.
- *
- * @param bool $throwExceptions
- * @return void
*/
- function setThrowExceptions($throwExceptions) {
-
+ public function setThrowExceptions(bool $throwExceptions)
+ {
$this->throwExceptions = $throwExceptions;
-
}
/**
@@ -324,40 +300,34 @@ class Client extends EventEmitter {
*
* These settings will be included in every HTTP request.
*
- * @param int $name
* @param mixed $value
- * @return void
*/
- function addCurlSetting($name, $value) {
-
+ public function addCurlSetting(int $name, $value)
+ {
$this->curlSettings[$name] = $value;
-
}
/**
* This method is responsible for performing a single request.
- *
- * @param RequestInterface $request
- * @return ResponseInterface
*/
- protected function doRequest(RequestInterface $request) {
-
+ protected function doRequest(RequestInterface $request): ResponseInterface
+ {
$settings = $this->createCurlSettingsArray($request);
if (!$this->curlHandle) {
$this->curlHandle = curl_init();
+ } else {
+ curl_reset($this->curlHandle);
}
curl_setopt_array($this->curlHandle, $settings);
$response = $this->curlExec($this->curlHandle);
- $response = $this->parseCurlResult($response, $this->curlHandle);
-
- if ($response['status'] === self::STATUS_CURLERROR) {
+ $response = $this->parseResponse($response, $this->curlHandle);
+ if (self::STATUS_CURLERROR === $response['status']) {
throw new ClientException($response['curl_errmsg'], $response['curl_errno']);
}
return $response['response'];
-
}
/**
@@ -389,28 +359,21 @@ class Client extends EventEmitter {
/**
* Turns a RequestInterface object into an array with settings that can be
- * fed to curl_setopt
- *
- * @param RequestInterface $request
- * @return array
+ * fed to curl_setopt.
*/
- protected function createCurlSettingsArray(RequestInterface $request) {
-
+ protected function createCurlSettingsArray(RequestInterface $request): array
+ {
$settings = $this->curlSettings;
switch ($request->getMethod()) {
- case 'HEAD' :
+ case 'HEAD':
$settings[CURLOPT_NOBODY] = true;
$settings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
- $settings[CURLOPT_POSTFIELDS] = '';
- $settings[CURLOPT_PUT] = false;
break;
- case 'GET' :
+ case 'GET':
$settings[CURLOPT_CUSTOMREQUEST] = 'GET';
- $settings[CURLOPT_POSTFIELDS] = '';
- $settings[CURLOPT_PUT] = false;
break;
- default :
+ default:
$body = $request->getBody();
if (is_resource($body)) {
// This needs to be set to PUT, regardless of the actual
@@ -422,20 +385,17 @@ class Client extends EventEmitter {
// For security we cast this to a string. If somehow an array could
// be passed here, it would be possible for an attacker to use @ to
// post local files.
- $settings[CURLOPT_POSTFIELDS] = (string)$body;
+ $settings[CURLOPT_POSTFIELDS] = (string) $body;
}
$settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
break;
-
}
$nHeaders = [];
foreach ($request->getHeaders() as $key => $values) {
-
foreach ($values as $value) {
- $nHeaders[] = $key . ': ' . $value;
+ $nHeaders[] = $key.': '.$value;
}
-
}
$settings[CURLOPT_HTTPHEADER] = $nHeaders;
$settings[CURLOPT_URL] = $request->getUrl();
@@ -449,13 +409,32 @@ class Client extends EventEmitter {
}
return $settings;
-
}
const STATUS_SUCCESS = 0;
const STATUS_CURLERROR = 1;
const STATUS_HTTPERROR = 2;
+ private function parseResponse(string $response, $curlHandle): array
+ {
+ $settings = $this->curlSettings;
+ $separatedHeaders = isset($settings[CURLOPT_HEADERFUNCTION]) && (bool) $settings[CURLOPT_HEADERFUNCTION];
+
+ if ($separatedHeaders) {
+ $resourceId = (int) $curlHandle;
+ if (isset($this->headerLinesMap[$resourceId])) {
+ $headers = $this->headerLinesMap[$resourceId];
+ } else {
+ $headers = [];
+ }
+ $response = $this->parseCurlResponse($headers, $response, $curlHandle);
+ } else {
+ $response = $this->parseCurlResult($response, $curlHandle);
+ }
+
+ return $response;
+ }
+
/**
* Parses the result of a curl call in a format that's a bit more
* convenient to work with.
@@ -471,12 +450,67 @@ class Client extends EventEmitter {
* * http_code - HTTP status code, as an int. Only set if Only set if
* status is STATUS_SUCCESS, or STATUS_HTTPERROR
*
- * @param string $response
+ * @param array $headerLines
+ * @param string $body
* @param resource $curlHandle
- * @return Response
*/
- protected function parseCurlResult($response, $curlHandle) {
+ protected function parseCurlResponse(array $headerLines, string $body, $curlHandle): array
+ {
+ list(
+ $curlInfo,
+ $curlErrNo,
+ $curlErrMsg
+ ) = $this->curlStuff($curlHandle);
+ if ($curlErrNo) {
+ return [
+ 'status' => self::STATUS_CURLERROR,
+ 'curl_errno' => $curlErrNo,
+ 'curl_errmsg' => $curlErrMsg,
+ ];
+ }
+
+ $response = new Response();
+ $response->setStatus($curlInfo['http_code']);
+ $response->setBody($body);
+
+ foreach ($headerLines as $header) {
+ $parts = explode(':', $header, 2);
+ if (2 === count($parts)) {
+ $response->addHeader(trim($parts[0]), trim($parts[1]));
+ }
+ }
+
+ $httpCode = $response->getStatus();
+
+ return [
+ 'status' => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS,
+ 'response' => $response,
+ 'http_code' => $httpCode,
+ ];
+ }
+
+ /**
+ * Parses the result of a curl call in a format that's a bit more
+ * convenient to work with.
+ *
+ * The method returns an array with the following elements:
+ * * status - one of the 3 STATUS constants.
+ * * curl_errno - A curl error number. Only set if status is
+ * STATUS_CURLERROR.
+ * * curl_errmsg - A current error message. Only set if status is
+ * STATUS_CURLERROR.
+ * * response - Response object. Only set if status is STATUS_SUCCESS, or
+ * STATUS_HTTPERROR.
+ * * http_code - HTTP status code, as an int. Only set if Only set if
+ * status is STATUS_SUCCESS, or STATUS_HTTPERROR
+ *
+ * @deprecated Use parseCurlResponse instead
+ *
+ * @param resource $curlHandle
+ */
+ protected function parseCurlResult(string $response, $curlHandle): array
+ {
list(
$curlInfo,
$curlErrNo,
@@ -485,8 +519,8 @@ class Client extends EventEmitter {
if ($curlErrNo) {
return [
- 'status' => self::STATUS_CURLERROR,
- 'curl_errno' => $curlErrNo,
+ 'status' => self::STATUS_CURLERROR,
+ 'curl_errno' => $curlErrNo,
'curl_errmsg' => $curlErrMsg,
];
}
@@ -495,7 +529,7 @@ class Client extends EventEmitter {
// In the case of 204 No Content, strlen($response) == $curlInfo['header_size].
// This will cause substr($response, $curlInfo['header_size']) return FALSE instead of NULL
// An exception will be thrown when calling getBodyAsString then
- $responseBody = substr($response, $curlInfo['header_size']) ?: null;
+ $responseBody = substr($response, $curlInfo['header_size']) ?: '';
unset($response);
@@ -510,26 +544,7 @@ class Client extends EventEmitter {
// Splitting headers
$headerBlob = explode("\r\n", $headerBlob);
- $response = new Response();
- $response->setStatus($curlInfo['http_code']);
-
- foreach ($headerBlob as $header) {
- $parts = explode(':', $header, 2);
- if (count($parts) == 2) {
- $response->addHeader(trim($parts[0]), trim($parts[1]));
- }
- }
-
- $response->setBody($responseBody);
-
- $httpCode = intval($response->getStatus());
-
- return [
- 'status' => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS,
- 'response' => $response,
- 'http_code' => $httpCode,
- ];
-
+ return $this->parseCurlResponse($headerBlob, $responseBody, $curlHandle);
}
/**
@@ -537,14 +552,9 @@ class Client extends EventEmitter {
*
* We keep this in a separate method, so we can call it without triggering
* the beforeRequest event and don't do the poll().
- *
- * @param RequestInterface $request
- * @param callable $success
- * @param callable $error
- * @param int $retryCount
*/
- protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, $retryCount = 0) {
-
+ protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, int $retryCount = 0)
+ {
if (!$this->curlMultiHandle) {
$this->curlMultiHandle = curl_multi_init();
}
@@ -554,29 +564,36 @@ class Client extends EventEmitter {
$this->createCurlSettingsArray($request)
);
curl_multi_add_handle($this->curlMultiHandle, $curl);
- $this->curlMultiMap[intval($curl)] = [
+
+ $resourceId = (int) $curl;
+ $this->headerLinesMap[$resourceId] = [];
+ $this->curlMultiMap[$resourceId] = [
$request,
$success,
$error,
- $retryCount
+ $retryCount,
];
-
}
// @codeCoverageIgnoreStart
/**
- * Calls curl_exec
+ * Calls curl_exec.
*
* This method exists so it can easily be overridden and mocked.
*
* @param resource $curlHandle
- * @return string
*/
- protected function curlExec($curlHandle) {
+ protected function curlExec($curlHandle): string
+ {
+ $this->headerLinesMap[(int) $curlHandle] = [];
- return curl_exec($curlHandle);
+ $result = curl_exec($curlHandle);
+ if (false === $result) {
+ $result = '';
+ }
+ return $result;
}
/**
@@ -585,17 +602,15 @@ class Client extends EventEmitter {
* This method exists so it can easily be overridden and mocked.
*
* @param resource $curlHandle
- * @return array
*/
- protected function curlStuff($curlHandle) {
-
+ protected function curlStuff($curlHandle): array
+ {
return [
curl_getinfo($curlHandle),
curl_errno($curlHandle),
curl_error($curlHandle),
];
-
}
- // @codeCoverageIgnoreEnd
+ // @codeCoverageIgnoreEnd
}