v2.1.3
This commit is contained in:
parent
3daee2df53
commit
19ef0566df
610 changed files with 21958 additions and 3112 deletions
1780
vendor/guzzlehttp/guzzle/CHANGELOG.md
vendored
Normal file
1780
vendor/guzzlehttp/guzzle/CHANGELOG.md
vendored
Normal file
File diff suppressed because it is too large
Load diff
94
vendor/guzzlehttp/guzzle/README.md
vendored
Normal file
94
vendor/guzzlehttp/guzzle/README.md
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||

|
||||
|
||||
# Guzzle, PHP HTTP client
|
||||
|
||||
[](https://github.com/guzzle/guzzle/releases)
|
||||
[](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI)
|
||||
[](https://packagist.org/packages/guzzlehttp/guzzle)
|
||||
|
||||
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
|
||||
trivial to integrate with web services.
|
||||
|
||||
- Simple interface for building query strings, POST requests, streaming large
|
||||
uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
|
||||
etc...
|
||||
- Can send both synchronous and asynchronous requests using the same interface.
|
||||
- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
|
||||
to utilize other PSR-7 compatible libraries with Guzzle.
|
||||
- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients.
|
||||
- Abstracts away the underlying HTTP transport, allowing you to write
|
||||
environment and transport agnostic code; i.e., no hard dependency on cURL,
|
||||
PHP streams, sockets, or non-blocking event loops.
|
||||
- Middleware system allows you to augment and compose client behavior.
|
||||
|
||||
```php
|
||||
$client = new \GuzzleHttp\Client();
|
||||
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
|
||||
|
||||
echo $response->getStatusCode(); // 200
|
||||
echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8'
|
||||
echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}'
|
||||
|
||||
// Send an asynchronous request.
|
||||
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
|
||||
$promise = $client->sendAsync($request)->then(function ($response) {
|
||||
echo 'I completed! ' . $response->getBody();
|
||||
});
|
||||
|
||||
$promise->wait();
|
||||
```
|
||||
|
||||
## Help and docs
|
||||
|
||||
We use GitHub issues only to discuss bugs and new features. For support please refer to:
|
||||
|
||||
- [Documentation](docs/index.md)
|
||||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle)
|
||||
- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/)
|
||||
- [Gitter](https://gitter.im/guzzle/guzzle)
|
||||
|
||||
|
||||
## Installing Guzzle
|
||||
|
||||
The recommended way to install Guzzle is through
|
||||
[Composer](https://getcomposer.org/).
|
||||
|
||||
```bash
|
||||
composer require guzzlehttp/guzzle
|
||||
```
|
||||
|
||||
|
||||
## Version Guidance
|
||||
|
||||
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
|
||||
|---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------|
|
||||
| 3.x | EOL (2016-10-31) | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 |
|
||||
| 4.x | EOL (2016-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 |
|
||||
| 5.x | EOL (2019-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 |
|
||||
| 6.x | EOL (2023-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 |
|
||||
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.6 |
|
||||
|
||||
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
|
||||
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
|
||||
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
|
||||
[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5
|
||||
[guzzle-7-repo]: https://github.com/guzzle/guzzle/tree/7.11
|
||||
[guzzle-3-docs]: https://github.com/guzzle/guzzle3/tree/master/docs
|
||||
[guzzle-5-docs]: https://github.com/guzzle/guzzle/tree/5.3/docs
|
||||
[guzzle-6-docs]: https://github.com/guzzle/guzzle/tree/6.5/docs
|
||||
[guzzle-7-docs]: https://github.com/guzzle/guzzle/blob/7.11/docs/index.md
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information.
|
||||
|
||||
## License
|
||||
|
||||
Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.
|
||||
|
||||
## For Enterprise
|
||||
|
||||
Available as part of the Tidelift Subscription
|
||||
|
||||
The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
|
||||
1251
vendor/guzzlehttp/guzzle/UPGRADING.md
vendored
Normal file
1251
vendor/guzzlehttp/guzzle/UPGRADING.md
vendored
Normal file
File diff suppressed because it is too large
Load diff
6
vendor/guzzlehttp/guzzle/package-lock.json
generated
vendored
Normal file
6
vendor/guzzlehttp/guzzle/package-lock.json
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "guzzle",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
579
vendor/guzzlehttp/guzzle/src/Client.php
vendored
579
vendor/guzzlehttp/guzzle/src/Client.php
vendored
|
|
@ -5,10 +5,12 @@ namespace GuzzleHttp;
|
|||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\InvalidArgumentException;
|
||||
use GuzzleHttp\Handler\CurlShareHandleState;
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -48,6 +50,8 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
* default middleware to the handler.
|
||||
* - base_uri: (string|UriInterface) Base URI of the client that is merged
|
||||
* into relative URIs. Can be a string or instance of UriInterface.
|
||||
* - transport_sharing: (string|null) Transport sharing mode for the
|
||||
* default handler. Accepts TransportSharing::* or null. Defaults to null.
|
||||
* - **: any request option
|
||||
*
|
||||
* @param array $config Client configuration settings.
|
||||
|
|
@ -56,10 +60,18 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$transportSharing = \array_key_exists('transport_sharing', $config) ? $config['transport_sharing'] : null;
|
||||
$transportSharingMode = CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing');
|
||||
unset($config['transport_sharing']);
|
||||
|
||||
if (!isset($config['handler'])) {
|
||||
$config['handler'] = HandlerStack::create();
|
||||
$config['handler'] = $transportSharingMode === TransportSharing::NONE
|
||||
? HandlerStack::create()
|
||||
: HandlerStack::create(Utils::chooseHandler(['transport_sharing' => $transportSharingMode]));
|
||||
} elseif (!\is_callable($config['handler'])) {
|
||||
throw new InvalidArgumentException('handler must be a callable');
|
||||
} elseif ($transportSharingMode === TransportSharing::HANDLER_REQUIRE) {
|
||||
throw new InvalidArgumentException('The "transport_sharing" client option can only require sharing when Guzzle creates the default handler. Configure the "transport_sharing" option on CurlHandler or CurlMultiHandler when providing a custom cURL handler.');
|
||||
}
|
||||
|
||||
// Convert the base_uri to a UriInterface
|
||||
|
|
@ -87,8 +99,12 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
$uri = $args[0];
|
||||
$opts = $args[1] ?? [];
|
||||
|
||||
return \substr($method, -5) === 'Async'
|
||||
? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
|
||||
$isAsync = \substr($method, -5) === 'Async';
|
||||
$method = $isAsync ? \substr($method, 0, -5) : $method;
|
||||
$method = \strtoupper($method);
|
||||
|
||||
return $isAsync
|
||||
? $this->requestAsync($method, $uri, $opts)
|
||||
: $this->request($method, $uri, $opts);
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +112,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
* Asynchronously send an HTTP request.
|
||||
*
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer. See \GuzzleHttp\RequestOptions.
|
||||
* request and to the transfer. See {@see RequestOptions}.
|
||||
*/
|
||||
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
|
||||
{
|
||||
|
|
@ -113,7 +129,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
* Send an HTTP request.
|
||||
*
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer. See \GuzzleHttp\RequestOptions.
|
||||
* request and to the transfer. See {@see RequestOptions}.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
|
|
@ -148,15 +164,29 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
*
|
||||
* @param string $method HTTP method
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
|
||||
* @param array $options Request options to apply. See {@see RequestOptions}.
|
||||
*/
|
||||
public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface
|
||||
{
|
||||
$normalizedMethod = \strtoupper($method);
|
||||
if ($method !== $normalizedMethod) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/guzzle',
|
||||
'7.11',
|
||||
'Passing a non-uppercase HTTP method to Client::requestAsync() is deprecated; guzzlehttp/guzzle 8.0 will preserve HTTP method casing. Pass an uppercase method explicitly if uppercase is required.'
|
||||
);
|
||||
$method = $normalizedMethod;
|
||||
}
|
||||
|
||||
$options = $this->prepareDefaults($options);
|
||||
// Remove request modifying parameter because it can be done up-front.
|
||||
$headers = $options['headers'] ?? [];
|
||||
$droppedHeaderNames = self::castDeprecatedHeaderOptionValues($headers);
|
||||
if ($droppedHeaderNames !== [] && isset($options['_conditional'])) {
|
||||
$options['_conditional'] = Psr7\Utils::caselessRemove($droppedHeaderNames, $options['_conditional']);
|
||||
}
|
||||
$body = $options['body'] ?? null;
|
||||
$version = $options['version'] ?? '1.1';
|
||||
$version = self::normalizeProtocolVersion($options['version'] ?? '1.1');
|
||||
// Merge the URI into the base URI.
|
||||
$uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options);
|
||||
if (\is_array($body)) {
|
||||
|
|
@ -178,12 +208,22 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
|
||||
* @param array $options Request options to apply. See {@see RequestOptions}.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function request(string $method, $uri = '', array $options = []): ResponseInterface
|
||||
{
|
||||
$normalizedMethod = \strtoupper($method);
|
||||
if ($method !== $normalizedMethod) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/guzzle',
|
||||
'7.11',
|
||||
'Passing a non-uppercase HTTP method to Client::request() is deprecated; guzzlehttp/guzzle 8.0 will preserve HTTP method casing. Pass an uppercase method explicitly if uppercase is required.'
|
||||
);
|
||||
$method = $normalizedMethod;
|
||||
}
|
||||
|
||||
$options[RequestOptions::SYNCHRONOUS] = true;
|
||||
|
||||
return $this->requestAsync($method, $uri, $options)->wait();
|
||||
|
|
@ -199,8 +239,6 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
* @param string|null $option The config option to retrieve.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
|
||||
*/
|
||||
public function getConfig(?string $option = null)
|
||||
{
|
||||
|
|
@ -215,8 +253,8 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
$uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri);
|
||||
}
|
||||
|
||||
if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
|
||||
$idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion'];
|
||||
$idnOptions = Utils::normalizeIdnConversionOption($config['idn_conversion'] ?? null);
|
||||
if ($idnOptions !== null) {
|
||||
$uri = Utils::idnUriConvert($uri, $idnOptions);
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +273,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
'verify' => true,
|
||||
'cookies' => false,
|
||||
'idn_conversion' => false,
|
||||
'protocols' => ['http', 'https'],
|
||||
];
|
||||
|
||||
// Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
|
||||
|
|
@ -266,12 +305,22 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
$this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()];
|
||||
} else {
|
||||
// Add the User-Agent header if one was not already set.
|
||||
$hasUserAgent = false;
|
||||
foreach (\array_keys($this->config['headers']) as $name) {
|
||||
if (\strtolower($name) === 'user-agent') {
|
||||
return;
|
||||
if (\strtolower((string) $name) === 'user-agent') {
|
||||
$hasUserAgent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->config['headers']['User-Agent'] = Utils::defaultUserAgent();
|
||||
|
||||
if (!$hasUserAgent) {
|
||||
$this->config['headers']['User-Agent'] = Utils::defaultUserAgent();
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_array($this->config['headers'])) {
|
||||
self::warnAboutInvalidHeaderOptionTypes($this->config['headers']);
|
||||
self::castDeprecatedHeaderOptionValues($this->config['headers']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -312,20 +361,446 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
}
|
||||
}
|
||||
|
||||
self::warnAboutInvalidRequestOptionTypes($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function warnAboutInvalidRequestOptionTypes(array $options): void
|
||||
{
|
||||
if (isset($options['handler']) && !\is_callable($options['handler'])) {
|
||||
self::warnInvalidRequestOptionType('handler', 'callable', $options['handler']);
|
||||
}
|
||||
|
||||
if (isset($options['allow_redirects']) && \is_array($options['allow_redirects'])) {
|
||||
self::warnAboutInvalidAllowRedirectsOptionTypes($options['allow_redirects']);
|
||||
}
|
||||
|
||||
if (isset($options['auth'])) {
|
||||
self::warnAboutInvalidAuthOptionTypes($options['auth']);
|
||||
}
|
||||
|
||||
if (isset($options['body']) && \is_array($options['body'])) {
|
||||
self::warnInvalidRequestOptionType('body', 'resource|string|null|int|float|bool|StreamInterface|(callable&object)|\Iterator|\Stringable', $options['body']);
|
||||
}
|
||||
|
||||
self::warnAboutInvalidTlsFileOptionTypes($options, 'cert');
|
||||
self::warnIfPresentAndNotString($options, 'cert_type');
|
||||
self::warnIfPresentAndNotNumber($options, 'connect_timeout');
|
||||
self::warnIfPresentAndNotInt($options, 'crypto_method');
|
||||
self::warnIfPresentAndNotBoolOrResource($options, 'debug');
|
||||
self::warnIfPresentAndNotBoolOrString($options, 'decode_content');
|
||||
self::warnIfPresentAndNotNumber($options, 'delay');
|
||||
self::warnIfPresentAndNotBoolOrInt($options, 'expect');
|
||||
|
||||
if (isset($options['form_params'])) {
|
||||
self::warnAboutInvalidFormParamTypes($options['form_params']);
|
||||
}
|
||||
|
||||
if (isset($options['force_ip_resolve']) && !\is_string($options['force_ip_resolve'])) {
|
||||
self::warnInvalidRequestOptionType('force_ip_resolve', 'string', $options['force_ip_resolve']);
|
||||
}
|
||||
|
||||
if (isset($options['headers'])) {
|
||||
self::warnAboutInvalidHeaderOptionTypes($options['headers']);
|
||||
}
|
||||
|
||||
self::warnIfPresentAndNotBool($options, 'http_errors');
|
||||
|
||||
if (isset($options['multipart'])) {
|
||||
self::warnAboutInvalidMultipartOptionTypes($options['multipart']);
|
||||
}
|
||||
|
||||
self::warnIfPresentAndNotCallable($options, 'on_headers');
|
||||
self::warnIfPresentAndNotCallable($options, 'on_stats');
|
||||
self::warnIfPresentAndNotCallable($options, 'progress');
|
||||
self::warnIfPresentAndNotStringArray($options, 'protocols', true);
|
||||
self::warnAboutInvalidProxyOptionTypes($options);
|
||||
|
||||
self::warnIfPresentAndNotNumber($options, 'read_timeout');
|
||||
self::warnIfPresentAndNotInt($options, 'retries');
|
||||
|
||||
if (isset($options['sink']) && !\is_bool($options['sink']) && !\is_resource($options['sink']) && !\is_string($options['sink']) && !$options['sink'] instanceof StreamInterface) {
|
||||
self::warnInvalidRequestOptionType('sink', 'resource|string|StreamInterface', $options['sink']);
|
||||
}
|
||||
|
||||
self::warnAboutInvalidTlsFileOptionTypes($options, 'ssl_key');
|
||||
self::warnIfPresentAndNotString($options, 'ssl_key_type');
|
||||
self::warnIfPresentAndNotBool($options, 'stream');
|
||||
self::warnIfPresentAndNotArray($options, 'stream_context', 'array<array-key, mixed>');
|
||||
self::warnIfPresentAndNotBool($options, 'synchronous');
|
||||
self::warnIfPresentAndNotNumber($options, 'timeout');
|
||||
self::warnIfPresentAndNotBoolOrString($options, 'verify');
|
||||
self::warnIfPresentAndNotStringOrNumber($options, 'version');
|
||||
self::warnIfPresentAndNotArray($options, 'curl', 'array<int|string, mixed>');
|
||||
|
||||
if (isset($options['cookies']) && $options['cookies'] === true) {
|
||||
self::warnInvalidRequestOptionType('cookies', 'false|CookieJarInterface', $options['cookies']);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnAboutInvalidAllowRedirectsOptionTypes(array $allowRedirects): void
|
||||
{
|
||||
self::warnIfPresentAndNotInt($allowRedirects, 'max', 'allow_redirects.max');
|
||||
self::warnIfPresentAndNotBool($allowRedirects, 'strict', 'allow_redirects.strict');
|
||||
self::warnIfPresentAndNotBool($allowRedirects, 'referer', 'allow_redirects.referer');
|
||||
self::warnIfPresentAndNotStringArray($allowRedirects, 'protocols', true, 'allow_redirects.protocols');
|
||||
self::warnIfPresentAndNotCallable($allowRedirects, 'on_redirect', 'allow_redirects.on_redirect');
|
||||
self::warnIfPresentAndNotBool($allowRedirects, 'track_redirects', 'allow_redirects.track_redirects');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $auth
|
||||
*/
|
||||
private static function warnAboutInvalidAuthOptionTypes($auth): void
|
||||
{
|
||||
if ($auth === false || \is_string($auth) || $auth === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_array($auth)) {
|
||||
self::warnInvalidRequestOptionType('auth', 'array{0: string, 1: string, 2?: string|null}|string|false|null', $auth);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\array_key_exists(0, $auth) || !\is_string($auth[0])) {
|
||||
self::warnInvalidRequestOptionType('auth.0', 'string', $auth[0] ?? null);
|
||||
}
|
||||
|
||||
if (!\array_key_exists(1, $auth) || !\is_string($auth[1])) {
|
||||
self::warnInvalidRequestOptionType('auth.1', 'string', $auth[1] ?? null);
|
||||
}
|
||||
|
||||
if (\array_key_exists(2, $auth) && $auth[2] !== null && !\is_string($auth[2])) {
|
||||
self::warnInvalidRequestOptionType('auth.2', 'string|null', $auth[2]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function warnAboutInvalidFormParamTypes($value): void
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
self::warnInvalidRequestOptionType('form_params', 'array<array-key, string|int|float|bool|null|array>', $value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self::warnAboutInvalidFormParamArray($value, 'form_params');
|
||||
}
|
||||
|
||||
private static function warnAboutInvalidFormParamArray(array $values, string $path): bool
|
||||
{
|
||||
foreach ($values as $key => $item) {
|
||||
$itemPath = $path.'.'.(string) $key;
|
||||
if (\is_array($item)) {
|
||||
if (!self::warnAboutInvalidFormParamArray($item, $itemPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item !== null && !\is_scalar($item)) {
|
||||
self::warnInvalidRequestOptionType($itemPath, 'string|int|float|bool|null|array', $item);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $headers
|
||||
*/
|
||||
private static function warnAboutInvalidHeaderOptionTypes($headers): void
|
||||
{
|
||||
if (!\is_array($headers)) {
|
||||
self::warnInvalidRequestOptionType('headers', 'array<array-key, string|non-empty-array<array-key, string>>|null', $headers);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($headers as $name => $value) {
|
||||
$path = 'headers.'.(string) $name;
|
||||
if (\is_array($value)) {
|
||||
if ($value === []) {
|
||||
self::warnInvalidRequestOptionType($path, 'string|non-empty-array<array-key, string>', $value);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($value as $index => $item) {
|
||||
if (!\is_string($item)) {
|
||||
self::warnInvalidRequestOptionType($path.'.'.(string) $index, 'string', $item);
|
||||
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
} elseif (!\is_string($value)) {
|
||||
self::warnInvalidRequestOptionType($path, 'string|non-empty-array<array-key, string>', $value);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $multipart
|
||||
*/
|
||||
private static function warnAboutInvalidMultipartOptionTypes($multipart): void
|
||||
{
|
||||
if (!\is_array($multipart)) {
|
||||
self::warnInvalidRequestOptionType('multipart', 'array<array-key, array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}>', $multipart);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($multipart as $index => $part) {
|
||||
$path = 'multipart.'.(string) $index;
|
||||
if (!\is_array($part)) {
|
||||
self::warnInvalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}', $part);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\array_key_exists('name', $part) || (!\is_string($part['name']) && !\is_int($part['name']))) {
|
||||
self::warnInvalidRequestOptionType($path.'.name', 'string|int', $part['name'] ?? null);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('contents', $part)) {
|
||||
self::warnInvalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}', $part);
|
||||
}
|
||||
|
||||
if (\array_key_exists('headers', $part)) {
|
||||
if (!\is_array($part['headers'])) {
|
||||
self::warnInvalidRequestOptionType($path.'.headers', 'array<array-key, string>', $part['headers']);
|
||||
} else {
|
||||
foreach ($part['headers'] as $name => $value) {
|
||||
if (!\is_string($value)) {
|
||||
self::warnInvalidRequestOptionType($path.'.headers.'.(string) $name, 'string', $value);
|
||||
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\array_key_exists('filename', $part) && !\is_string($part['filename'])) {
|
||||
self::warnInvalidRequestOptionType($path.'.filename', 'string', $part['filename']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnAboutInvalidProxyOptionTypes(array $options): void
|
||||
{
|
||||
if (!isset($options['proxy'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_string($options['proxy']) && !\is_array($options['proxy'])) {
|
||||
self::warnInvalidRequestOptionType('proxy', 'string|array{http?: string|null, https?: string|null, no?: string|array<array-key, string>|null}', $options['proxy']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_array($options['proxy'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (['http', 'https'] as $scheme) {
|
||||
if (\array_key_exists($scheme, $options['proxy']) && $options['proxy'][$scheme] !== null && !\is_string($options['proxy'][$scheme])) {
|
||||
self::warnInvalidRequestOptionType('proxy.'.$scheme, 'string|null', $options['proxy'][$scheme]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!\array_key_exists('no', $options['proxy']) || $options['proxy']['no'] === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (\is_string($options['proxy']['no'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_array($options['proxy']['no'])) {
|
||||
self::warnInvalidRequestOptionType('proxy.no', 'string|array<array-key, string>|null', $options['proxy']['no']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($options['proxy']['no'] as $index => $noProxy) {
|
||||
if (!\is_string($noProxy)) {
|
||||
self::warnInvalidRequestOptionType('proxy.no.'.(string) $index, 'string', $noProxy);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnAboutInvalidTlsFileOptionTypes(array $options, string $option): void
|
||||
{
|
||||
if (!isset($options[$option])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (\is_string($options[$option])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_array($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($option, 'string|array{0: string, 1?: string}', $options[$option]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\array_key_exists(0, $options[$option]) || !\is_string($options[$option][0])) {
|
||||
self::warnInvalidRequestOptionType($option.'.0', 'string', $options[$option][0] ?? null);
|
||||
}
|
||||
|
||||
if (\array_key_exists(1, $options[$option]) && $options[$option][1] !== null && !\is_string($options[$option][1])) {
|
||||
self::warnInvalidRequestOptionType($option.'.1', 'string|null', $options[$option][1]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotArray(array $options, string $option, string $expected): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_array($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($option, $expected, $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotBool(array $options, string $option, ?string $path = null): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_bool($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($path ?? $option, 'bool', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotBoolOrInt(array $options, string $option): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_int($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($option, 'bool|int', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotBoolOrResource(array $options, string $option): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_resource($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($option, 'bool|resource', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotBoolOrString(array $options, string $option): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_string($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($option, 'bool|string', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotCallable(array $options, string $option, ?string $path = null): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_callable($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($path ?? $option, 'callable', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotInt(array $options, string $option, ?string $path = null): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_int($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($path ?? $option, 'int', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotNumber(array $options, string $option): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_int($options[$option]) && !\is_float($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($option, 'int|float', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotString(array $options, string $option): void
|
||||
{
|
||||
if (\array_key_exists($option, $options) && !\is_string($options[$option])) {
|
||||
self::warnInvalidRequestOptionType($option, 'string', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotStringArray(array $options, string $option, bool $nonEmpty, ?string $path = null): void
|
||||
{
|
||||
if (!\array_key_exists($option, $options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $path ?? $option;
|
||||
$expected = ($nonEmpty ? 'non-empty-' : '').'array<array-key, string>';
|
||||
|
||||
if (!\is_array($options[$option]) || ($nonEmpty && $options[$option] === [])) {
|
||||
self::warnInvalidRequestOptionType($path, $expected, $options[$option]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($options[$option] as $index => $item) {
|
||||
if (!\is_string($item)) {
|
||||
self::warnInvalidRequestOptionType($path.'.'.(string) $index, 'string', $item);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function warnIfPresentAndNotStringOrNumber(array $options, string $option): void
|
||||
{
|
||||
if (
|
||||
\array_key_exists($option, $options)
|
||||
&& !\is_string($options[$option])
|
||||
&& !\is_int($options[$option])
|
||||
&& !\is_float($options[$option])
|
||||
) {
|
||||
self::warnInvalidRequestOptionType($option, 'string|int|float', $options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function warnInvalidRequestOptionType(string $option, string $expected, $value): void
|
||||
{
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/guzzle',
|
||||
'7.11',
|
||||
'Passing %s to request option "%s" is deprecated; guzzlehttp/guzzle 8.0 requires %s.',
|
||||
\get_debug_type($value),
|
||||
$option,
|
||||
$expected
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers the given request and applies request options.
|
||||
*
|
||||
* The URI of the request is not modified and the request options are used
|
||||
* as-is without merging in default options.
|
||||
*
|
||||
* @param array $options See \GuzzleHttp\RequestOptions.
|
||||
* @param array $options See {@see RequestOptions}.
|
||||
*/
|
||||
private function transfer(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
$request = $this->applyOptions($request, $options);
|
||||
|
||||
$protocolVersion = $request->getProtocolVersion();
|
||||
|
||||
if ('' === $protocolVersion) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with an empty protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.');
|
||||
|
||||
$request = Psr7\Utils::modifyRequest($request, ['version' => '1.1']);
|
||||
} elseif (!self::isProtocolVersionValid($protocolVersion)) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with a malformed protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject malformed protocol versions.');
|
||||
}
|
||||
|
||||
/** @var HandlerStack $handler */
|
||||
$handler = $options['handler'];
|
||||
|
||||
|
|
@ -349,7 +824,12 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) {
|
||||
throw new InvalidArgumentException('The headers array must have header name as keys.');
|
||||
}
|
||||
$modify['set_headers'] = $options['headers'];
|
||||
$headers = $options['headers'];
|
||||
$droppedHeaderNames = self::castDeprecatedHeaderOptionValues($headers);
|
||||
if ($droppedHeaderNames !== [] && isset($options['_conditional'])) {
|
||||
$options['_conditional'] = Psr7\Utils::caselessRemove($droppedHeaderNames, $options['_conditional']);
|
||||
}
|
||||
$modify['set_headers'] = $headers;
|
||||
unset($options['headers']);
|
||||
}
|
||||
|
||||
|
|
@ -386,7 +866,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
) {
|
||||
// Ensure that we don't have the header in different case and set the new value.
|
||||
$options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
|
||||
$modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
|
||||
$modify['set_headers']['Accept-Encoding'] = (string) $options['decode_content'];
|
||||
}
|
||||
|
||||
if (isset($options['body'])) {
|
||||
|
|
@ -440,7 +920,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
}
|
||||
|
||||
if (isset($options['version'])) {
|
||||
$modify['version'] = $options['version'];
|
||||
$modify['version'] = self::normalizeProtocolVersion($options['version']);
|
||||
}
|
||||
|
||||
$request = Psr7\Utils::modifyRequest($request, $modify);
|
||||
|
|
@ -457,8 +937,9 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
// Build up the changes so it's in a single clone of the message.
|
||||
$modify = [];
|
||||
foreach ($options['_conditional'] as $k => $v) {
|
||||
if (!$request->hasHeader($k)) {
|
||||
$modify['set_headers'][$k] = $v;
|
||||
$name = (string) $k;
|
||||
if (!$request->hasHeader($name)) {
|
||||
$modify['set_headers'][$name] = $v;
|
||||
}
|
||||
}
|
||||
$request = Psr7\Utils::modifyRequest($request, $modify);
|
||||
|
|
@ -469,6 +950,62 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
|||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $headers
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function castDeprecatedHeaderOptionValues(array &$headers): array
|
||||
{
|
||||
$droppedHeaderNames = [];
|
||||
|
||||
foreach ($headers as $name => $value) {
|
||||
if (\is_array($value)) {
|
||||
if ($value === []) {
|
||||
$droppedHeaderNames[] = (string) $name;
|
||||
unset($headers[$name]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($value as $index => $item) {
|
||||
if ($item === null || (!\is_string($item) && \is_scalar($item))) {
|
||||
$value[$index] = (string) $item;
|
||||
}
|
||||
}
|
||||
|
||||
$headers[$name] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === null || (!\is_string($value) && \is_scalar($value))) {
|
||||
$headers[$name] = (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $droppedHeaderNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int|float $version
|
||||
*/
|
||||
private static function normalizeProtocolVersion($version): string
|
||||
{
|
||||
if ('' === $version) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing an empty "version" request option is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.');
|
||||
|
||||
return '1.1';
|
||||
}
|
||||
|
||||
return \is_float($version) ? \number_format($version, 1, '.', '') : (string) $version;
|
||||
}
|
||||
|
||||
private static function isProtocolVersionValid(string $version): bool
|
||||
{
|
||||
return 1 === \preg_match('/^\d+(?:\.\d+)?$/D', $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an InvalidArgumentException with pre-set message.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class CookieJar implements CookieJarInterface
|
|||
$this->strictMode = $strictMode;
|
||||
|
||||
foreach ($cookieArray as $cookie) {
|
||||
if (!($cookie instanceof SetCookie)) {
|
||||
if (!$cookie instanceof SetCookie) {
|
||||
$cookie = new SetCookie($cookie);
|
||||
}
|
||||
$this->setCookie($cookie);
|
||||
|
|
@ -105,7 +105,7 @@ class CookieJar implements CookieJarInterface
|
|||
|
||||
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void
|
||||
{
|
||||
if (!$domain) {
|
||||
if ($domain === null) {
|
||||
$this->cookies = [];
|
||||
|
||||
return;
|
||||
|
|
@ -113,14 +113,15 @@ class CookieJar implements CookieJarInterface
|
|||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($domain): bool {
|
||||
return !$cookie->matchesDomain($domain);
|
||||
return $cookie->getDomain() === null || !$cookie->matchesDomain($domain);
|
||||
}
|
||||
);
|
||||
} elseif (!$name) {
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($path, $domain): bool {
|
||||
return !($cookie->matchesPath($path)
|
||||
return !($cookie->getDomain() !== null
|
||||
&& $cookie->matchesPath($path)
|
||||
&& $cookie->matchesDomain($domain));
|
||||
}
|
||||
);
|
||||
|
|
@ -128,7 +129,8 @@ class CookieJar implements CookieJarInterface
|
|||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($path, $domain, $name) {
|
||||
return !($cookie->getName() == $name
|
||||
return !($cookie->getDomain() !== null
|
||||
&& $cookie->getName() == $name
|
||||
&& $cookie->matchesPath($path)
|
||||
&& $cookie->matchesDomain($domain));
|
||||
}
|
||||
|
|
@ -274,7 +276,8 @@ class CookieJar implements CookieJarInterface
|
|||
$path = $uri->getPath() ?: '/';
|
||||
|
||||
foreach ($this->cookies as $cookie) {
|
||||
if ($cookie->matchesPath($path)
|
||||
if ($cookie->getDomain() !== null
|
||||
&& $cookie->matchesPath($path)
|
||||
&& $cookie->matchesDomain($host)
|
||||
&& !$cookie->isExpired()
|
||||
&& (!$cookie->getSecure() || $scheme === 'https')
|
||||
|
|
@ -296,7 +299,7 @@ class CookieJar implements CookieJarInterface
|
|||
private function removeCookieIfEmpty(SetCookie $cookie): void
|
||||
{
|
||||
$cookieValue = $cookie->getValue();
|
||||
if ($cookieValue === null || $cookieValue === '') {
|
||||
if (($cookieValue === null || $cookieValue === '') && $cookie->getDomain() !== null) {
|
||||
$this->clear(
|
||||
$cookie->getDomain(),
|
||||
$cookie->getPath(),
|
||||
|
|
|
|||
|
|
@ -54,7 +54,12 @@ class SessionCookieJar extends CookieJar
|
|||
}
|
||||
}
|
||||
|
||||
$_SESSION[$this->sessionKey] = \json_encode($json);
|
||||
$json = \json_encode($json);
|
||||
if (false === $json) {
|
||||
throw new \RuntimeException('Unable to encode cookie data');
|
||||
}
|
||||
|
||||
$_SESSION[$this->sessionKey] = $json;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -65,12 +70,22 @@ class SessionCookieJar extends CookieJar
|
|||
if (!isset($_SESSION[$this->sessionKey])) {
|
||||
return;
|
||||
}
|
||||
$data = \json_decode($_SESSION[$this->sessionKey], true);
|
||||
|
||||
$json = $_SESSION[$this->sessionKey];
|
||||
if (!\is_string($json)) {
|
||||
throw new \RuntimeException('Invalid cookie data');
|
||||
}
|
||||
|
||||
$data = \json_decode($json, true);
|
||||
if (\is_array($data)) {
|
||||
foreach ($data as $cookie) {
|
||||
if (!\is_array($cookie)) {
|
||||
throw new \RuntimeException('Invalid cookie data');
|
||||
}
|
||||
|
||||
$this->setCookie(new SetCookie($cookie));
|
||||
}
|
||||
} elseif (\strlen($data)) {
|
||||
} elseif (\is_scalar($data) && \strlen((string) $data)) {
|
||||
throw new \RuntimeException('Invalid cookie data');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ class SetCookie
|
|||
public function setName($name): void
|
||||
{
|
||||
if (!is_string($name)) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['Name'] = (string) $name;
|
||||
|
|
@ -199,7 +199,7 @@ class SetCookie
|
|||
public function setValue($value): void
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['Value'] = (string) $value;
|
||||
|
|
@ -223,7 +223,7 @@ class SetCookie
|
|||
public function setDomain($domain): void
|
||||
{
|
||||
if (!is_string($domain) && null !== $domain) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['Domain'] = null === $domain ? null : (string) $domain;
|
||||
|
|
@ -247,7 +247,7 @@ class SetCookie
|
|||
public function setPath($path): void
|
||||
{
|
||||
if (!is_string($path)) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['Path'] = (string) $path;
|
||||
|
|
@ -271,7 +271,7 @@ class SetCookie
|
|||
public function setMaxAge($maxAge): void
|
||||
{
|
||||
if (!is_int($maxAge) && null !== $maxAge) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge;
|
||||
|
|
@ -295,10 +295,18 @@ class SetCookie
|
|||
public function setExpires($timestamp): void
|
||||
{
|
||||
if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp));
|
||||
if (null === $timestamp) {
|
||||
$this->data['Expires'] = null;
|
||||
} elseif (\is_numeric($timestamp)) {
|
||||
$this->data['Expires'] = (int) $timestamp;
|
||||
} else {
|
||||
// Store unparseable dates as session cookies, not as expired cookies.
|
||||
$expires = \strtotime((string) $timestamp);
|
||||
$this->data['Expires'] = $expires === false ? null : $expires;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -319,7 +327,7 @@ class SetCookie
|
|||
public function setSecure($secure): void
|
||||
{
|
||||
if (!is_bool($secure)) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['Secure'] = (bool) $secure;
|
||||
|
|
@ -343,7 +351,7 @@ class SetCookie
|
|||
public function setDiscard($discard): void
|
||||
{
|
||||
if (!is_bool($discard)) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['Discard'] = (bool) $discard;
|
||||
|
|
@ -367,7 +375,7 @@ class SetCookie
|
|||
public function setHttpOnly($httpOnly): void
|
||||
{
|
||||
if (!is_bool($httpOnly)) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->data['HttpOnly'] = (bool) $httpOnly;
|
||||
|
|
@ -480,10 +488,10 @@ class SetCookie
|
|||
return 'The cookie value must not be empty';
|
||||
}
|
||||
|
||||
// Domains must not be empty, but can be 0. "0" is not a valid internet
|
||||
// domain, but may be used as server name in a private network.
|
||||
// Domains must not be empty, but may be omitted. "0" is not a valid
|
||||
// internet domain, but may be used as server name in a private network.
|
||||
$domain = $this->getDomain();
|
||||
if ($domain === null || $domain === '') {
|
||||
if ($domain === '') {
|
||||
return 'The cookie domain must not be empty';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ use Psr\Http\Message\RequestInterface;
|
|||
|
||||
/**
|
||||
* Exception thrown when a connection cannot be established.
|
||||
*
|
||||
* Note that no response is present for a ConnectException
|
||||
*/
|
||||
class ConnectException extends TransferException implements NetworkExceptionInterface
|
||||
{
|
||||
|
|
|
|||
|
|
@ -45,9 +45,13 @@ class RequestException extends TransferException implements RequestExceptionInte
|
|||
|
||||
/**
|
||||
* Wrap non-RequestExceptions with a RequestException
|
||||
*
|
||||
* @deprecated since 7.11. Create a RequestException directly instead.
|
||||
*/
|
||||
public static function wrapException(RequestInterface $request, \Throwable $e): RequestException
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', '%s::wrapException() is deprecated and will be removed in 8.0. Create a %s directly instead.', self::class, self::class);
|
||||
|
||||
return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e);
|
||||
}
|
||||
|
||||
|
|
|
|||
407
vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php
vendored
407
vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php
vendored
|
|
@ -9,6 +9,7 @@ use GuzzleHttp\Promise\FulfilledPromise;
|
|||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Psr7\LazyOpenStream;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use GuzzleHttp\TransportSharing;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
|
@ -38,17 +39,62 @@ class CurlFactory implements CurlFactoryInterface
|
|||
private $maxHandles;
|
||||
|
||||
/**
|
||||
* @param int $maxHandles Maximum number of idle handles.
|
||||
* @var resource|\CurlShareHandle|null
|
||||
*/
|
||||
public function __construct(int $maxHandles)
|
||||
private $shareHandle;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $shareMode;
|
||||
|
||||
/**
|
||||
* @param int $maxHandles Maximum number of idle handles.
|
||||
* @param resource|\CurlShareHandle|null $shareHandle
|
||||
*/
|
||||
public function __construct(int $maxHandles, string $shareMode = TransportSharing::NONE, $shareHandle = null)
|
||||
{
|
||||
$this->maxHandles = $maxHandles;
|
||||
$this->shareMode = CurlShareHandleState::normalizeMode($shareMode, 'transport_sharing');
|
||||
|
||||
if ($this->shareMode === TransportSharing::NONE && $shareHandle !== null) {
|
||||
throw new \InvalidArgumentException('A cURL share handle cannot be provided when transport sharing is disabled.');
|
||||
}
|
||||
|
||||
if ($this->shareMode !== TransportSharing::NONE && $shareHandle === null) {
|
||||
throw new \InvalidArgumentException('A cURL share handle is required when transport sharing is enabled.');
|
||||
}
|
||||
|
||||
if ($shareHandle !== null && !self::isCurlShareHandle($shareHandle)) {
|
||||
throw new \InvalidArgumentException('A cURL share handle must be an instance of CurlShareHandle or a curl_share resource.');
|
||||
}
|
||||
|
||||
$this->shareHandle = $shareHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function isCurlShareHandle($value): bool
|
||||
{
|
||||
if (\PHP_VERSION_ID < 80000) {
|
||||
return \is_resource($value) && \get_resource_type($value) === 'curl_share';
|
||||
}
|
||||
|
||||
return $value instanceof \CurlShareHandle;
|
||||
}
|
||||
|
||||
public function create(RequestInterface $request, array $options): EasyHandle
|
||||
{
|
||||
$protocolVersion = $request->getProtocolVersion();
|
||||
|
||||
if ('' === $protocolVersion) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with an empty protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.');
|
||||
|
||||
$protocolVersion = '1.1';
|
||||
$request = \GuzzleHttp\Psr7\Utils::modifyRequest($request, ['version' => $protocolVersion]);
|
||||
}
|
||||
|
||||
if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
|
||||
if (!self::supportsHttp2()) {
|
||||
throw new ConnectException('HTTP/2 is supported by the cURL handler, however libcurl is built without HTTP/2 support.', $request);
|
||||
|
|
@ -62,6 +108,10 @@ class CurlFactory implements CurlFactoryInterface
|
|||
unset($options['curl']['body_as_string']);
|
||||
}
|
||||
|
||||
self::triggerUnsupportedRequestOptionDeprecations($options);
|
||||
$this->rejectRequestLevelShareConflict($options);
|
||||
self::triggerConflictingCurlOptionDeprecations($options);
|
||||
|
||||
$easy = new EasyHandle();
|
||||
$easy->request = $request;
|
||||
$easy->options = $options;
|
||||
|
|
@ -77,12 +127,257 @@ class CurlFactory implements CurlFactoryInterface
|
|||
}
|
||||
|
||||
$conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
|
||||
$easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init();
|
||||
curl_setopt_array($easy->handle, $conf);
|
||||
if ($this->shareHandle !== null) {
|
||||
if (!\defined('CURLOPT_SHARE')) {
|
||||
throw new \InvalidArgumentException('The configured cURL share handle requires CURLOPT_SHARE, but it is not available in the installed PHP cURL extension.');
|
||||
}
|
||||
|
||||
$conf[(int) \constant('CURLOPT_SHARE')] = $this->shareHandle;
|
||||
}
|
||||
|
||||
$handle = $this->handles ? \array_pop($this->handles) : \curl_init();
|
||||
if (false === $handle) {
|
||||
throw new \RuntimeException('Can not initialize cURL handle.');
|
||||
}
|
||||
$easy->handle = $handle;
|
||||
|
||||
try {
|
||||
$this->applyCurlOptions($handle, $conf);
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID < 80000 && \is_resource($handle)) {
|
||||
\curl_close($handle);
|
||||
}
|
||||
unset($easy->handle);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $easy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|\CurlHandle $handle
|
||||
* @param array<int|string, mixed> $conf
|
||||
*/
|
||||
private function applyCurlOptions($handle, array $conf): void
|
||||
{
|
||||
foreach ($conf as $option => $value) {
|
||||
if (!\is_int($option)) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'Invalid cURL option %s.',
|
||||
self::formatCurlOption($option)
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
$success = curl_setopt($handle, $option, $value);
|
||||
} catch (\Throwable $e) {
|
||||
throw new \InvalidArgumentException(
|
||||
\sprintf(
|
||||
'Unable to set cURL option %s: %s',
|
||||
self::formatCurlOption($option),
|
||||
$e->getMessage()
|
||||
),
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'Unable to set cURL option %s.',
|
||||
self::formatCurlOption($option)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function rejectRequestLevelShareConflict(array $options): void
|
||||
{
|
||||
if ($this->shareHandle === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!\defined('CURLOPT_SHARE')
|
||||
|| !isset($options['curl'])
|
||||
|| !\is_array($options['curl'])
|
||||
|| !\array_key_exists((int) \constant('CURLOPT_SHARE'), $options['curl'])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('The request-level CURLOPT_SHARE cURL option cannot be combined with configured transport sharing.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string $option
|
||||
*/
|
||||
private static function formatCurlOption($option): string
|
||||
{
|
||||
if (!\is_int($option)) {
|
||||
return \sprintf('"%s"', $option);
|
||||
}
|
||||
|
||||
static $names = null;
|
||||
|
||||
if (null === $names) {
|
||||
$names = [];
|
||||
foreach (\get_defined_constants(true)['curl'] ?? [] as $name => $value) {
|
||||
if (\is_int($value) && \strpos($name, 'CURLOPT_') === 0 && !isset($names[$value])) {
|
||||
$names[$value] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($names[$option])) {
|
||||
return \sprintf('%s (%d)', $names[$option], $option);
|
||||
}
|
||||
|
||||
return (string) $option;
|
||||
}
|
||||
|
||||
private static function triggerConflictingCurlOptionDeprecations(array $options): void
|
||||
{
|
||||
if (!isset($options['curl']) || !\is_array($options['curl']) || $options['curl'] === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$conflictingOptions = self::conflictingCurlOptions();
|
||||
|
||||
foreach ($options['curl'] as $option => $_) {
|
||||
if (!\array_key_exists($option, $conflictingOptions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = self::formatCurlOption($option);
|
||||
$replacement = $conflictingOptions[$option];
|
||||
if ($replacement !== null) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/guzzle',
|
||||
'7.11',
|
||||
\sprintf(
|
||||
'Passing %s in the "curl" request option is deprecated; guzzlehttp/guzzle 8.0 will reject this option because it conflicts with Guzzle-managed request handling. Use %s instead.',
|
||||
$name,
|
||||
$replacement
|
||||
)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/guzzle',
|
||||
'7.11',
|
||||
\sprintf(
|
||||
'Passing %s in the "curl" request option is deprecated; guzzlehttp/guzzle 8.0 will reject this option because it conflicts with Guzzle-managed cURL internals.',
|
||||
$name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static function triggerUnsupportedRequestOptionDeprecations(array $options): void
|
||||
{
|
||||
if (\array_key_exists('stream_context', $options)) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing the "stream_context" request option to a cURL handler is deprecated; guzzlehttp/guzzle 8.0 will reject this option because cURL handlers ignore PHP stream context options.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
private static function conflictingCurlOptions(): array
|
||||
{
|
||||
static $options = null;
|
||||
|
||||
if ($options !== null) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
$options = [];
|
||||
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SHARE', 'the "transport_sharing" client option or cURL handler option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_URL', 'the request URI');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_PORT', 'the request URI');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_CUSTOMREQUEST', 'the request method');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_HTTPGET', 'the request method');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_POST', 'the request method and body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_PUT', 'the request method and body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_NOBODY', 'the request method');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_UPLOAD', 'the request body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_POSTFIELDS', 'the request body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_READFUNCTION', 'the request body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_READDATA', 'the request body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_INFILE', 'the request body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_INFILESIZE', 'the request body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_INFILESIZE_LARGE', 'the request body');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_HTTPHEADER', 'the request headers');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_USERAGENT', 'the request headers');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_REFERER', 'the request headers');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_HEADERFUNCTION', 'the "on_headers" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_WRITEFUNCTION', 'the "sink" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_FILE', 'the "sink" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_RETURNTRANSFER', null);
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_HEADER', null);
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_TIMEOUT', 'the "timeout" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_TIMEOUT_MS', 'the "timeout" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_CONNECTTIMEOUT', 'the "connect_timeout" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_CONNECTTIMEOUT_MS', 'the "connect_timeout" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_NOSIGNAL', 'the "timeout" or "connect_timeout" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_NOPROGRESS', 'the "progress" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_PROGRESSFUNCTION', 'the "progress" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_XFERINFOFUNCTION', 'the "progress" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_VERBOSE', 'the "debug" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_STDERR', 'the "debug" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_PROXY', 'the "proxy" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_NOPROXY', 'the "proxy" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_FOLLOWLOCATION', 'the "allow_redirects" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_MAXREDIRS', 'the "allow_redirects" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_POSTREDIR', 'the "allow_redirects" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_REDIR_PROTOCOLS', 'the "allow_redirects" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_REDIR_PROTOCOLS_STR', 'the "allow_redirects" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_PROTOCOLS', 'the "protocols" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_PROTOCOLS_STR', 'the "protocols" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_HTTP09_ALLOWED', null);
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_HTTP_VERSION', 'the request protocol version');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_IPRESOLVE', 'the "force_ip_resolve" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSL_VERIFYPEER', 'the "verify" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSL_VERIFYHOST', 'the "verify" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_CAINFO', 'the "verify" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_CAPATH', 'the "verify" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSLVERSION', 'the "crypto_method" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSLCERT', 'the "cert" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSLCERTPASSWD', 'the "cert" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSLCERTTYPE', 'the "cert_type" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSLKEY', 'the "ssl_key" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSLKEYPASSWD', 'the "ssl_key" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_KEYPASSWD', 'the "ssl_key" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_SSLKEYTYPE', 'the "ssl_key_type" request option');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_COOKIE', 'the "Cookie" request header or Guzzle cookie middleware');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_COOKIEFILE', 'Guzzle cookie middleware');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_COOKIEJAR', 'Guzzle cookie middleware');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_COOKIELIST', 'Guzzle cookie middleware');
|
||||
self::addConflictingCurlOption($options, 'CURLOPT_COOKIESESSION', 'Guzzle cookie middleware');
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string|null> $options
|
||||
*/
|
||||
private static function addConflictingCurlOption(array &$options, string $constant, ?string $replacement): void
|
||||
{
|
||||
if (!\defined($constant)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = \constant($constant);
|
||||
if (\is_int($value)) {
|
||||
$options[$value] = $replacement;
|
||||
}
|
||||
}
|
||||
|
||||
private static function supportsHttp2(): bool
|
||||
{
|
||||
static $supportsHttp2 = null;
|
||||
|
|
@ -233,7 +528,7 @@ class CurlFactory implements CurlFactoryInterface
|
|||
new RequestException(
|
||||
'An error was encountered while creating the response',
|
||||
$easy->request,
|
||||
$easy->response,
|
||||
null,
|
||||
$easy->createResponseException,
|
||||
$ctx
|
||||
)
|
||||
|
|
@ -312,8 +607,14 @@ class CurlFactory implements CurlFactoryInterface
|
|||
\CURLOPT_CONNECTTIMEOUT => 300,
|
||||
];
|
||||
|
||||
$protocols = Utils::normalizeProtocols($easy->options['protocols'] ?? ['http', 'https']);
|
||||
$scheme = $easy->request->getUri()->getScheme();
|
||||
if (!\in_array($scheme, $protocols, true)) {
|
||||
throw new RequestException(\sprintf('The scheme "%s" is not allowed by the protocols request option.', $scheme), $easy->request);
|
||||
}
|
||||
|
||||
if (\defined('CURLOPT_PROTOCOLS')) {
|
||||
$conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS;
|
||||
$conf[\CURLOPT_PROTOCOLS] = self::curlProtocolMask($protocols);
|
||||
}
|
||||
|
||||
$version = $easy->request->getProtocolVersion();
|
||||
|
|
@ -329,6 +630,41 @@ class CurlFactory implements CurlFactoryInterface
|
|||
return $conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $protocols
|
||||
*/
|
||||
private static function curlProtocolMask(array $protocols): int
|
||||
{
|
||||
$mask = 0;
|
||||
|
||||
if (\in_array('http', $protocols, true)) {
|
||||
$mask |= \CURLPROTO_HTTP;
|
||||
}
|
||||
|
||||
if (\in_array('https', $protocols, true)) {
|
||||
$mask |= \CURLPROTO_HTTPS;
|
||||
}
|
||||
|
||||
return $mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $type
|
||||
*/
|
||||
private static function normalizeTlsFileType(string $option, $type): string
|
||||
{
|
||||
if (!\is_string($type) || $type === '') {
|
||||
throw new \InvalidArgumentException(\sprintf('%s must be a non-empty string', $option));
|
||||
}
|
||||
|
||||
return \strtoupper($type);
|
||||
}
|
||||
|
||||
private static function shouldValidateSslKeyFile(?string $type): bool
|
||||
{
|
||||
return $type !== 'ENG' && $type !== 'PROV';
|
||||
}
|
||||
|
||||
private function applyMethod(EasyHandle $easy, array &$conf): void
|
||||
{
|
||||
$body = $easy->request->getBody();
|
||||
|
|
@ -426,7 +762,7 @@ class CurlFactory implements CurlFactoryInterface
|
|||
private function removeHeader(string $name, array &$options): void
|
||||
{
|
||||
foreach (\array_keys($options['_headers']) as $key) {
|
||||
if (!\strcasecmp($key, $name)) {
|
||||
if (!\strcasecmp((string) $key, $name)) {
|
||||
unset($options['_headers'][$key]);
|
||||
|
||||
return;
|
||||
|
|
@ -532,8 +868,10 @@ class CurlFactory implements CurlFactoryInterface
|
|||
} else {
|
||||
$scheme = $easy->request->getUri()->getScheme();
|
||||
if (isset($options['proxy'][$scheme])) {
|
||||
$host = $easy->request->getUri()->getHost();
|
||||
if (isset($options['proxy']['no']) && Utils::isHostInNoProxy($host, $options['proxy']['no'])) {
|
||||
if (
|
||||
isset($options['proxy']['no'])
|
||||
&& Utils::isUriInNoProxy($easy->request->getUri(), $options['proxy']['no'])
|
||||
) {
|
||||
unset($conf[\CURLOPT_PROXY]);
|
||||
} else {
|
||||
$conf[\CURLOPT_PROXY] = $options['proxy'][$scheme];
|
||||
|
|
@ -580,36 +918,68 @@ class CurlFactory implements CurlFactoryInterface
|
|||
}
|
||||
}
|
||||
|
||||
$certType = null;
|
||||
if (isset($options['cert_type'])) {
|
||||
$certType = self::normalizeTlsFileType('cert_type', $options['cert_type']);
|
||||
$conf[\CURLOPT_SSLCERTTYPE] = $certType;
|
||||
}
|
||||
|
||||
if (isset($options['cert'])) {
|
||||
$cert = $options['cert'];
|
||||
if (\is_array($cert)) {
|
||||
$conf[\CURLOPT_SSLCERTPASSWD] = $cert[1];
|
||||
if (!isset($cert[0]) || !\is_string($cert[0])) {
|
||||
throw new \InvalidArgumentException('Invalid cert request option');
|
||||
}
|
||||
if (isset($cert[1])) {
|
||||
if (!\is_string($cert[1])) {
|
||||
throw new \InvalidArgumentException('Invalid cert request option');
|
||||
}
|
||||
$conf[\CURLOPT_SSLCERTPASSWD] = $cert[1];
|
||||
}
|
||||
$cert = $cert[0];
|
||||
}
|
||||
if (!\is_string($cert)) {
|
||||
throw new \InvalidArgumentException('Invalid cert request option');
|
||||
}
|
||||
if (!\file_exists($cert)) {
|
||||
throw new \InvalidArgumentException("SSL certificate not found: {$cert}");
|
||||
}
|
||||
// OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files.
|
||||
// see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html
|
||||
$ext = pathinfo($cert, \PATHINFO_EXTENSION);
|
||||
if (preg_match('#^(der|p12)$#i', $ext)) {
|
||||
if ($certType === null && preg_match('#^(der|p12)$#i', $ext)) {
|
||||
$conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext);
|
||||
}
|
||||
$conf[\CURLOPT_SSLCERT] = $cert;
|
||||
}
|
||||
|
||||
$sslKeyType = null;
|
||||
if (isset($options['ssl_key_type'])) {
|
||||
$sslKeyType = self::normalizeTlsFileType('ssl_key_type', $options['ssl_key_type']);
|
||||
$conf[\CURLOPT_SSLKEYTYPE] = $sslKeyType;
|
||||
}
|
||||
|
||||
if (isset($options['ssl_key'])) {
|
||||
if (\is_array($options['ssl_key'])) {
|
||||
if (\count($options['ssl_key']) === 2) {
|
||||
[$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key'];
|
||||
} else {
|
||||
[$sslKey] = $options['ssl_key'];
|
||||
if (!isset($options['ssl_key'][0]) || !\is_string($options['ssl_key'][0])) {
|
||||
throw new \InvalidArgumentException('Invalid ssl_key request option');
|
||||
}
|
||||
if (isset($options['ssl_key'][1])) {
|
||||
if (!\is_string($options['ssl_key'][1])) {
|
||||
throw new \InvalidArgumentException('Invalid ssl_key request option');
|
||||
}
|
||||
$conf[\CURLOPT_SSLKEYPASSWD] = $options['ssl_key'][1];
|
||||
}
|
||||
$sslKey = $options['ssl_key'][0];
|
||||
}
|
||||
|
||||
$sslKey = $sslKey ?? $options['ssl_key'];
|
||||
|
||||
if (!\file_exists($sslKey)) {
|
||||
if (!\is_string($sslKey)) {
|
||||
throw new \InvalidArgumentException('Invalid ssl_key request option');
|
||||
}
|
||||
|
||||
if (self::shouldValidateSslKeyFile($sslKeyType) && !\file_exists($sslKey)) {
|
||||
throw new \InvalidArgumentException("SSL private key not found: {$sslKey}");
|
||||
}
|
||||
$conf[\CURLOPT_SSLKEY] = $sslKey;
|
||||
|
|
@ -701,7 +1071,8 @@ class CurlFactory implements CurlFactoryInterface
|
|||
$startingResponse = true;
|
||||
try {
|
||||
$easy->createResponse();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$easy->response = null;
|
||||
$easy->createResponseException = $e;
|
||||
|
||||
return -1;
|
||||
|
|
@ -709,7 +1080,7 @@ class CurlFactory implements CurlFactoryInterface
|
|||
if ($onHeaders !== null) {
|
||||
try {
|
||||
$onHeaders($easy->response);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// Associate the exception with the handle and trigger
|
||||
// a curl header write error by returning 0.
|
||||
$easy->onHeadersException = $e;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\TransportSharing;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -21,17 +22,39 @@ class CurlHandler
|
|||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @var CurlShareHandleState|null
|
||||
*/
|
||||
private $shareHandleState;
|
||||
|
||||
/**
|
||||
* Accepts an associative array of options:
|
||||
*
|
||||
* - handle_factory: Optional curl factory used to create cURL handles.
|
||||
* - transport_sharing: Optional transport sharing mode.
|
||||
*
|
||||
* @param array{handle_factory?: ?CurlFactoryInterface} $options Array of options to use with the handler
|
||||
* @param array{handle_factory?: ?CurlFactoryInterface, transport_sharing?: mixed} $options Array of options to use with the handler
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->factory = $options['handle_factory']
|
||||
?? new CurlFactory(3);
|
||||
CurlShareHandleState::assertNoRequiredSharingCustomFactoryConflict($options, 'CurlHandler');
|
||||
$transportSharing = $options['transport_sharing'] ?? null;
|
||||
$sharingMode = CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing');
|
||||
|
||||
if (\array_key_exists('handle_factory', $options) && $options['handle_factory'] !== null) {
|
||||
$this->shareHandleState = null;
|
||||
$this->factory = $options['handle_factory'];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->shareHandleState = $sharingMode !== TransportSharing::NONE
|
||||
? CurlShareHandleState::fromOption($transportSharing)
|
||||
: null;
|
||||
|
||||
$this->factory = $this->shareHandleState !== null
|
||||
? new CurlFactory(3, $this->shareHandleState->mode, $this->shareHandleState->handle)
|
||||
: new CurlFactory(3);
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use Closure;
|
|||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\Promise;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\TransportSharing;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
|
|
@ -25,6 +26,11 @@ class CurlMultiHandler
|
|||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @var CurlShareHandleState|null
|
||||
*/
|
||||
private $shareHandleState;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
|
@ -57,10 +63,21 @@ class CurlMultiHandler
|
|||
/** @var resource|\CurlMultiHandle */
|
||||
private $_mh;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $executingMulti = false;
|
||||
|
||||
/**
|
||||
* @var array<int, EasyHandle>
|
||||
*/
|
||||
private $deferredCancels = [];
|
||||
|
||||
/**
|
||||
* This handler accepts the following options:
|
||||
*
|
||||
* - handle_factory: An optional factory used to create curl handles
|
||||
* - transport_sharing: Optional transport sharing mode.
|
||||
* - select_timeout: Optional timeout (in seconds) to block before timing
|
||||
* out while selecting curl handles. Defaults to 1 second.
|
||||
* - options: An associative array of CURLMOPT_* options and
|
||||
|
|
@ -68,12 +85,27 @@ class CurlMultiHandler
|
|||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->factory = $options['handle_factory'] ?? new CurlFactory(50);
|
||||
CurlShareHandleState::assertNoRequiredSharingCustomFactoryConflict($options, 'CurlMultiHandler');
|
||||
$transportSharing = $options['transport_sharing'] ?? null;
|
||||
$sharingMode = CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing');
|
||||
|
||||
if (\array_key_exists('handle_factory', $options) && $options['handle_factory'] !== null) {
|
||||
$this->shareHandleState = null;
|
||||
$this->factory = $options['handle_factory'];
|
||||
} else {
|
||||
$this->shareHandleState = $sharingMode !== TransportSharing::NONE
|
||||
? CurlShareHandleState::fromOption($transportSharing)
|
||||
: null;
|
||||
|
||||
$this->factory = $this->shareHandleState !== null
|
||||
? new CurlFactory(50, $this->shareHandleState->mode, $this->shareHandleState->handle)
|
||||
: new CurlFactory(50);
|
||||
}
|
||||
|
||||
if (isset($options['select_timeout'])) {
|
||||
$this->selectTimeout = $options['select_timeout'];
|
||||
} elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
|
||||
@trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.2', 'The GUZZLE_CURL_SELECT_TIMEOUT environment variable is deprecated; use the "select_timeout" option instead.');
|
||||
$this->selectTimeout = (int) $selectTimeout;
|
||||
} else {
|
||||
$this->selectTimeout = 1;
|
||||
|
|
@ -119,8 +151,13 @@ class CurlMultiHandler
|
|||
public function __destruct()
|
||||
{
|
||||
if (isset($this->_mh)) {
|
||||
\curl_multi_close($this->_mh);
|
||||
unset($this->_mh);
|
||||
try {
|
||||
\curl_multi_close($this->_mh);
|
||||
} catch (\Throwable $e) {
|
||||
// Destructors must not throw.
|
||||
} finally {
|
||||
unset($this->_mh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,10 +209,21 @@ class CurlMultiHandler
|
|||
\usleep(250);
|
||||
}
|
||||
|
||||
while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
|
||||
do {
|
||||
$this->executingMulti = true;
|
||||
|
||||
try {
|
||||
$exec = \curl_multi_exec($this->_mh, $this->active);
|
||||
} finally {
|
||||
$this->executingMulti = false;
|
||||
$this->cleanupDeferredCancels();
|
||||
}
|
||||
|
||||
// Prevent busy looping for slow HTTP requests.
|
||||
\curl_multi_select($this->_mh, $this->selectTimeout);
|
||||
}
|
||||
if ($exec === \CURLM_CALL_MULTI_PERFORM) {
|
||||
\curl_multi_select($this->_mh, $this->selectTimeout);
|
||||
}
|
||||
} while ($exec === \CURLM_CALL_MULTI_PERFORM);
|
||||
|
||||
$this->processMessages();
|
||||
}
|
||||
|
|
@ -185,7 +233,16 @@ class CurlMultiHandler
|
|||
*/
|
||||
private function tickInQueue(): void
|
||||
{
|
||||
if (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
|
||||
$this->executingMulti = true;
|
||||
|
||||
try {
|
||||
$exec = \curl_multi_exec($this->_mh, $this->active);
|
||||
} finally {
|
||||
$this->executingMulti = false;
|
||||
$this->cleanupDeferredCancels();
|
||||
}
|
||||
|
||||
if ($exec === \CURLM_CALL_MULTI_PERFORM) {
|
||||
\curl_multi_select($this->_mh, 0);
|
||||
P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue']));
|
||||
}
|
||||
|
|
@ -229,7 +286,7 @@ class CurlMultiHandler
|
|||
private function cancel($id): bool
|
||||
{
|
||||
if (!is_int($id)) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
// Cannot cancel if it has been processed.
|
||||
|
|
@ -237,15 +294,42 @@ class CurlMultiHandler
|
|||
return false;
|
||||
}
|
||||
|
||||
$handle = $this->handles[$id]['easy']->handle;
|
||||
$easy = $this->handles[$id]['easy'];
|
||||
unset($this->delays[$id], $this->handles[$id]);
|
||||
|
||||
if ($this->executingMulti) {
|
||||
$this->deferredCancels[$id] = $easy;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->cleanupCancelledHandle($easy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function cleanupDeferredCancels(): void
|
||||
{
|
||||
if ($this->deferredCancels === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entries = $this->deferredCancels;
|
||||
$this->deferredCancels = [];
|
||||
|
||||
foreach ($entries as $easy) {
|
||||
$this->cleanupCancelledHandle($easy);
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupCancelledHandle(EasyHandle $easy): void
|
||||
{
|
||||
$handle = $easy->handle;
|
||||
\curl_multi_remove_handle($this->_mh, $handle);
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
\curl_close($handle);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function processMessages(): void
|
||||
|
|
@ -255,6 +339,12 @@ class CurlMultiHandler
|
|||
// if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216
|
||||
continue;
|
||||
}
|
||||
if (!isset($done['handle'])) {
|
||||
// Work around a PHP issue where cancelled transfers may omit the handle.
|
||||
// Remove this once we no longer support PHP versions before the fix in
|
||||
// https://github.com/php/php-src/pull/16302.
|
||||
continue;
|
||||
}
|
||||
$id = (int) $done['handle'];
|
||||
\curl_multi_remove_handle($this->_mh, $done['handle']);
|
||||
|
||||
|
|
@ -266,9 +356,16 @@ class CurlMultiHandler
|
|||
$entry = $this->handles[$id];
|
||||
unset($this->handles[$id], $this->delays[$id]);
|
||||
$entry['easy']->errno = $done['result'];
|
||||
$entry['deferred']->resolve(
|
||||
CurlFactory::finish($this, $entry['easy'], $this->factory)
|
||||
);
|
||||
|
||||
try {
|
||||
$result = CurlFactory::finish($this, $entry['easy'], $this->factory);
|
||||
} catch (\Throwable $e) {
|
||||
$entry['deferred']->reject($e);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry['deferred']->resolve($result);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
172
vendor/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php
vendored
Normal file
172
vendor/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\TransportSharing;
|
||||
use GuzzleHttp\Utils;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CurlShareHandleState
|
||||
{
|
||||
/**
|
||||
* @var resource|\CurlShareHandle|null
|
||||
*/
|
||||
public $handle;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $mode;
|
||||
|
||||
/**
|
||||
* @param resource|\CurlShareHandle|null $handle
|
||||
*/
|
||||
private function __construct(string $mode, $handle)
|
||||
{
|
||||
$this->mode = $mode;
|
||||
$this->handle = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $sharing
|
||||
*/
|
||||
public static function fromOption($sharing): ?self
|
||||
{
|
||||
if ($sharing instanceof self) {
|
||||
return $sharing;
|
||||
}
|
||||
|
||||
$mode = self::normalizeMode($sharing, 'transport_sharing');
|
||||
if ($mode === TransportSharing::NONE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($mode === TransportSharing::HANDLER_PREFER) {
|
||||
return self::createHandlerShareOrNull($mode);
|
||||
}
|
||||
|
||||
return self::createHandlerShare($mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $sharing
|
||||
*/
|
||||
public static function normalizeMode($sharing, string $option): string
|
||||
{
|
||||
if ($sharing instanceof self) {
|
||||
return $sharing->mode;
|
||||
}
|
||||
|
||||
if ($sharing === null || $sharing === TransportSharing::NONE) {
|
||||
return TransportSharing::NONE;
|
||||
}
|
||||
|
||||
if ($sharing === TransportSharing::HANDLER_PREFER || $sharing === TransportSharing::HANDLER_REQUIRE) {
|
||||
return $sharing;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'The "%s" option must be null or a GuzzleHttp\\TransportSharing::* constant; received %s.',
|
||||
$option,
|
||||
Utils::describeType($sharing)
|
||||
));
|
||||
}
|
||||
|
||||
public static function assertNoRequiredSharingCustomFactoryConflict(array $options, string $handlerName): void
|
||||
{
|
||||
if (!\array_key_exists('handle_factory', $options) || $options['handle_factory'] === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mode = self::normalizeMode($options['transport_sharing'] ?? null, 'transport_sharing');
|
||||
if ($mode !== TransportSharing::HANDLER_REQUIRE) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'The "transport_sharing" %s option cannot require sharing with a custom "handle_factory" because Guzzle cannot ensure that the custom factory applies CURLOPT_SHARE.',
|
||||
$handlerName
|
||||
));
|
||||
}
|
||||
|
||||
private static function createHandlerShareOrNull(string $mode): ?self
|
||||
{
|
||||
try {
|
||||
return self::createHandlerShare($mode);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static function createHandlerShare(string $mode): self
|
||||
{
|
||||
if (!\function_exists('curl_share_init') || !\function_exists('curl_share_setopt')) {
|
||||
throw new \InvalidArgumentException('The "transport_sharing" option requires cURL share support.');
|
||||
}
|
||||
|
||||
self::requireCurlConstant('CURLOPT_SHARE');
|
||||
$shareOption = self::requireCurlConstant('CURLSHOPT_SHARE');
|
||||
$locks = self::handlerLocks();
|
||||
$handle = curl_share_init();
|
||||
|
||||
try {
|
||||
foreach ($locks as $lock) {
|
||||
try {
|
||||
$success = curl_share_setopt($handle, $shareOption, $lock);
|
||||
} catch (\Throwable $e) {
|
||||
throw new \InvalidArgumentException('Unable to configure cURL share handle: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
throw new \InvalidArgumentException(\sprintf('Unable to configure cURL share handle with lock data %d.', $lock));
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
self::closeHandlerShareHandleOnPhp7($handle);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return new self($mode, $handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private static function handlerLocks(): array
|
||||
{
|
||||
return [
|
||||
self::requireCurlConstant('CURL_LOCK_DATA_DNS'),
|
||||
self::requireCurlConstant('CURL_LOCK_DATA_SSL_SESSION'),
|
||||
];
|
||||
}
|
||||
|
||||
private static function requireCurlConstant(string $constant): int
|
||||
{
|
||||
if (!\defined($constant)) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'The "transport_sharing" option requires %s, but it is not available in the installed PHP cURL extension.',
|
||||
$constant
|
||||
));
|
||||
}
|
||||
|
||||
$value = \constant($constant);
|
||||
if (!\is_int($value)) {
|
||||
throw new \InvalidArgumentException(\sprintf('The cURL constant %s must resolve to an integer.', $constant));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|\CurlShareHandle $handle
|
||||
*/
|
||||
private static function closeHandlerShareHandleOnPhp7($handle): void
|
||||
{
|
||||
if (\PHP_VERSION_ID < 80000 && \is_resource($handle)) {
|
||||
curl_share_close($handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ final class EasyHandle
|
|||
public $onHeadersException;
|
||||
|
||||
/**
|
||||
* @var \Exception|null Exception during createResponse (if any)
|
||||
* @var \Throwable|null Exception during createResponse (if any)
|
||||
*/
|
||||
public $createResponseException;
|
||||
|
||||
|
|
@ -68,6 +68,8 @@ final class EasyHandle
|
|||
*/
|
||||
public function createResponse(): void
|
||||
{
|
||||
$this->response = null;
|
||||
|
||||
[$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($this->headers);
|
||||
|
||||
$normalizedKeys = Utils::normalizeHeaderKeys($headers);
|
||||
|
|
@ -80,7 +82,7 @@ final class EasyHandle
|
|||
|
||||
$bodyLength = (int) $this->sink->getSize();
|
||||
if ($bodyLength) {
|
||||
$headers[$normalizedKeys['content-length']] = $bodyLength;
|
||||
$headers[$normalizedKeys['content-length']] = [(string) $bodyLength];
|
||||
} else {
|
||||
unset($headers[$normalizedKeys['content-length']]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,14 @@ final class HeaderProcessor
|
|||
throw new \RuntimeException('Expected a non-empty array of header data');
|
||||
}
|
||||
|
||||
$parts = \explode(' ', \array_shift($headers), 3);
|
||||
$headers = self::getLastHeaderBlock(\array_values($headers));
|
||||
|
||||
$statusLine = \array_shift($headers);
|
||||
if ($statusLine === null) {
|
||||
throw new \RuntimeException('Expected a non-empty array of header data');
|
||||
}
|
||||
|
||||
$parts = \explode(' ', $statusLine, 3);
|
||||
$version = \explode('/', $parts[0])[1] ?? null;
|
||||
|
||||
if ($version === null) {
|
||||
|
|
@ -37,6 +44,34 @@ final class HeaderProcessor
|
|||
throw new \RuntimeException('HTTP status code missing from header data');
|
||||
}
|
||||
|
||||
if (!\preg_match('/^\d{3}$/', $status)) {
|
||||
throw new \RuntimeException('HTTP status code is invalid');
|
||||
}
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (\strpos($header, ':') === false) {
|
||||
throw new \RuntimeException('HTTP header line is invalid');
|
||||
}
|
||||
}
|
||||
|
||||
return [$version, (int) $status, $parts[2] ?? null, Utils::headersFromLines($headers)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-list<string> $headers
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function getLastHeaderBlock(array $headers): array
|
||||
{
|
||||
$lastStatusLine = 0;
|
||||
|
||||
foreach ($headers as $index => $line) {
|
||||
if (\preg_match('/^HTTP\/\S+\s+/i', $line)) {
|
||||
$lastStatusLine = $index;
|
||||
}
|
||||
}
|
||||
|
||||
return \array_slice($headers, $lastStatusLine);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use GuzzleHttp\Promise\FulfilledPromise;
|
|||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use GuzzleHttp\TransportSharing;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
|
@ -22,11 +23,49 @@ use Psr\Http\Message\UriInterface;
|
|||
*/
|
||||
class StreamHandler
|
||||
{
|
||||
private const CONNECTION_ERRORS = [
|
||||
'php_network_getaddresses:',
|
||||
'getaddrinfo',
|
||||
'gethostbyname failed',
|
||||
'Connection refused',
|
||||
'No connection could be made because the target machine actively refused it',
|
||||
"couldn't connect to host", // error on HHVM
|
||||
'connection attempt failed',
|
||||
'connect() failed',
|
||||
'Connection timed out',
|
||||
'Operation timed out',
|
||||
'Network is unreachable',
|
||||
'No route to host',
|
||||
'Host is unreachable',
|
||||
'Host is down',
|
||||
'Cannot connect to HTTPS server through proxy',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lastHeaders = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $transportSharingMode;
|
||||
|
||||
/**
|
||||
* Accepts an associative array of options:
|
||||
*
|
||||
* - transport_sharing: Optional transport sharing mode.
|
||||
*
|
||||
* @param array{transport_sharing?: mixed} $options Array of options to use with the handler
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->transportSharingMode = CurlShareHandleState::normalizeMode(
|
||||
$options['transport_sharing'] ?? null,
|
||||
'transport_sharing'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP request.
|
||||
*
|
||||
|
|
@ -42,12 +81,22 @@ class StreamHandler
|
|||
|
||||
$protocolVersion = $request->getProtocolVersion();
|
||||
|
||||
if ('' === $protocolVersion) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with an empty protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.');
|
||||
|
||||
$protocolVersion = '1.1';
|
||||
$request = Psr7\Utils::modifyRequest($request, ['version' => $protocolVersion]);
|
||||
}
|
||||
|
||||
if ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
|
||||
throw new ConnectException(sprintf('HTTP/%s is not supported by the stream handler.', $protocolVersion), $request);
|
||||
}
|
||||
|
||||
$startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
|
||||
|
||||
self::triggerUnsupportedRequestOptionDeprecations($request, $options);
|
||||
$this->assertTransportSharingSupported();
|
||||
|
||||
try {
|
||||
// Does not support the expect header.
|
||||
$request = $request->withoutHeader('Expect');
|
||||
|
|
@ -74,16 +123,10 @@ class StreamHandler
|
|||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
// Determine if the error was a networking error.
|
||||
$message = $e->getMessage();
|
||||
// This list can probably get more comprehensive.
|
||||
if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed
|
||||
|| false !== \strpos($message, 'Connection refused')
|
||||
|| false !== \strpos($message, "couldn't connect to host") // error on HHVM
|
||||
|| false !== \strpos($message, 'connection attempt failed')
|
||||
) {
|
||||
if (self::isConnectionError($e->getMessage())) {
|
||||
$e = new ConnectException($e->getMessage(), $request, $e);
|
||||
} else {
|
||||
$e = RequestException::wrapException($request, $e);
|
||||
$e = $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e);
|
||||
}
|
||||
$this->invokeStats($options, $request, $startTime, null, $e);
|
||||
|
||||
|
|
@ -91,6 +134,17 @@ class StreamHandler
|
|||
}
|
||||
}
|
||||
|
||||
private static function isConnectionError(string $message): bool
|
||||
{
|
||||
foreach (self::CONNECTION_ERRORS as $connectionError) {
|
||||
if (false !== \strpos($message, $connectionError)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function invokeStats(
|
||||
array $options,
|
||||
RequestInterface $request,
|
||||
|
|
@ -114,10 +168,8 @@ class StreamHandler
|
|||
|
||||
try {
|
||||
[$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs);
|
||||
} catch (\Exception $e) {
|
||||
return P\Create::rejectionFor(
|
||||
new RequestException('An error was encountered while creating the response', $request, null, $e)
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->rejectResponseCreation($options, $request, $startTime, $e);
|
||||
}
|
||||
|
||||
[$stream, $headers] = $this->checkDecode($options, $headers, $stream);
|
||||
|
|
@ -130,16 +182,14 @@ class StreamHandler
|
|||
|
||||
try {
|
||||
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
|
||||
} catch (\Exception $e) {
|
||||
return P\Create::rejectionFor(
|
||||
new RequestException('An error was encountered while creating the response', $request, null, $e)
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->rejectResponseCreation($options, $request, $startTime, $e);
|
||||
}
|
||||
|
||||
if (isset($options['on_headers'])) {
|
||||
try {
|
||||
$options['on_headers']($response);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return P\Create::rejectionFor(
|
||||
new RequestException('An error was encountered during the on_headers event', $request, $response, $e)
|
||||
);
|
||||
|
|
@ -157,6 +207,24 @@ class StreamHandler
|
|||
return new FulfilledPromise($response);
|
||||
}
|
||||
|
||||
private function rejectResponseCreation(
|
||||
array $options,
|
||||
RequestInterface $request,
|
||||
?float $startTime,
|
||||
\Throwable $previous
|
||||
): PromiseInterface {
|
||||
$reason = new RequestException(
|
||||
'An error was encountered while creating the response',
|
||||
$request,
|
||||
null,
|
||||
$previous
|
||||
);
|
||||
|
||||
$this->invokeStats($options, $request, $startTime, null, $reason);
|
||||
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
|
||||
private function createSink(StreamInterface $stream, array $options): StreamInterface
|
||||
{
|
||||
if (!empty($options['stream'])) {
|
||||
|
|
@ -185,15 +253,12 @@ class StreamHandler
|
|||
// Remove content-encoding header
|
||||
unset($headers[$normalizedKeys['content-encoding']]);
|
||||
|
||||
// Fix content-length header
|
||||
// The decoded length cannot be known without inflating the
|
||||
// stream, so keep the original length for inspection and
|
||||
// drop the now-unknown Content-Length header.
|
||||
if (isset($normalizedKeys['content-length'])) {
|
||||
$headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
|
||||
$length = (int) $stream->getSize();
|
||||
if ($length === 0) {
|
||||
unset($headers[$normalizedKeys['content-length']]);
|
||||
} else {
|
||||
$headers[$normalizedKeys['content-length']] = [$length];
|
||||
}
|
||||
unset($headers[$normalizedKeys['content-length']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -279,8 +344,14 @@ class StreamHandler
|
|||
$methods = \array_flip(\get_class_methods(__CLASS__));
|
||||
}
|
||||
|
||||
if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) {
|
||||
throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request);
|
||||
$scheme = $request->getUri()->getScheme();
|
||||
if (!\in_array($scheme, ['http', 'https'], true)) {
|
||||
throw new RequestException(\sprintf("The scheme '%s' is not supported.", $scheme), $request);
|
||||
}
|
||||
|
||||
$protocols = Utils::normalizeProtocols($options['protocols'] ?? ['http', 'https']);
|
||||
if (!\in_array($scheme, $protocols, true)) {
|
||||
throw new RequestException(\sprintf('The scheme "%s" is not allowed by the protocols request option.', $scheme), $request);
|
||||
}
|
||||
|
||||
// HTTP/1.1 streams using the PHP stream wrapper require a
|
||||
|
|
@ -338,7 +409,6 @@ class StreamHandler
|
|||
|
||||
// See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable
|
||||
if (function_exists('http_get_last_response_headers')) {
|
||||
/** @var array|null */
|
||||
$http_response_header = \http_get_last_response_headers();
|
||||
}
|
||||
|
||||
|
|
@ -423,6 +493,121 @@ class StreamHandler
|
|||
return $context;
|
||||
}
|
||||
|
||||
private static function triggerUnsupportedRequestOptionDeprecations(RequestInterface $request, array $options): void
|
||||
{
|
||||
if (
|
||||
\array_key_exists('curl', $options)
|
||||
&& $options['curl'] !== null
|
||||
&& $options['curl'] !== []
|
||||
&& !self::isCurlOptionGeneratedByAuth($options)
|
||||
) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing the "curl" request option to the stream handler is deprecated; guzzlehttp/guzzle 8.0 will reject this option because the stream handler ignores cURL options.');
|
||||
}
|
||||
|
||||
if (self::usesDigestAuth($options)) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing digest authentication to the stream handler is deprecated; guzzlehttp/guzzle 8.0 will reject digest authentication with the stream handler because it is only supported by cURL handlers.');
|
||||
}
|
||||
|
||||
if (\array_key_exists('expect', $options) && $options['expect'] !== false && $request->hasHeader('Expect')) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing the "expect" request option to the stream handler is deprecated when it adds an Expect header; guzzlehttp/guzzle 8.0 will reject this option because the stream handler does not support Expect: 100-Continue.');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertTransportSharingSupported(): void
|
||||
{
|
||||
if ($this->transportSharingMode === TransportSharing::HANDLER_REQUIRE) {
|
||||
throw new \InvalidArgumentException('The "transport_sharing" option requires transport sharing, but the stream handler does not support it.');
|
||||
}
|
||||
}
|
||||
|
||||
private static function isCurlOptionGeneratedByAuth(array $options): bool
|
||||
{
|
||||
if (!isset($options['curl']) || !\is_array($options['curl']) || !isset($options['auth'][2]) || !\is_string($options['auth'][2])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\defined('CURLOPT_HTTPAUTH') || !\defined('CURLOPT_USERPWD')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = \strtolower($options['auth'][2]);
|
||||
if ($type === 'digest') {
|
||||
$httpAuth = \defined('CURLAUTH_DIGEST') ? \constant('CURLAUTH_DIGEST') : null;
|
||||
} elseif ($type === 'ntlm') {
|
||||
$httpAuth = \defined('CURLAUTH_NTLM') ? \constant('CURLAUTH_NTLM') : null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $httpAuth !== null
|
||||
&& \count($options['curl']) === 2
|
||||
&& isset($options['curl'][\CURLOPT_HTTPAUTH], $options['curl'][\CURLOPT_USERPWD])
|
||||
&& $options['curl'][\CURLOPT_HTTPAUTH] === $httpAuth;
|
||||
}
|
||||
|
||||
private static function usesDigestAuth(array $options): bool
|
||||
{
|
||||
return isset($options['auth'][2])
|
||||
&& \is_string($options['auth'][2])
|
||||
&& \strtolower($options['auth'][2]) === 'digest';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*
|
||||
* @return array{0: string, 1: string|null}
|
||||
*/
|
||||
private static function normalizeTlsFileOption(string $option, $value): array
|
||||
{
|
||||
$passphrase = null;
|
||||
|
||||
if (\is_array($value)) {
|
||||
if (!isset($value[0]) || !\is_string($value[0])) {
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option));
|
||||
}
|
||||
if (isset($value[1])) {
|
||||
if (!\is_string($value[1])) {
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option));
|
||||
}
|
||||
$passphrase = $value[1];
|
||||
}
|
||||
$value = $value[0];
|
||||
}
|
||||
|
||||
if (!\is_string($value)) {
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option));
|
||||
}
|
||||
|
||||
return [$value, $passphrase];
|
||||
}
|
||||
|
||||
private static function setTlsPassphrase(array &$options, ?string $passphrase, string $option): void
|
||||
{
|
||||
if ($passphrase === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($options['ssl']['passphrase']) && $options['ssl']['passphrase'] !== $passphrase) {
|
||||
throw new \InvalidArgumentException(\sprintf('Cannot use different passphrases for cert and ssl_key with the stream handler; %s conflicts with an existing TLS passphrase.', $option));
|
||||
}
|
||||
|
||||
$options['ssl']['passphrase'] = $passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private static function assertStreamTlsType(string $option, $value): void
|
||||
{
|
||||
if (!\is_string($value) || $value === '') {
|
||||
throw new \InvalidArgumentException(\sprintf('%s must be a non-empty string', $option));
|
||||
}
|
||||
|
||||
if (\strtoupper($value) !== 'PEM') {
|
||||
throw new \InvalidArgumentException(\sprintf('The stream handler only supports "PEM" for the %s request option.', $option));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
|
|
@ -435,7 +620,10 @@ class StreamHandler
|
|||
} else {
|
||||
$scheme = $request->getUri()->getScheme();
|
||||
if (isset($value[$scheme])) {
|
||||
if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) {
|
||||
if (
|
||||
!isset($value['no'])
|
||||
|| !Utils::isUriInNoProxy($request->getUri(), $value['no'])
|
||||
) {
|
||||
$uri = $value[$scheme];
|
||||
}
|
||||
}
|
||||
|
|
@ -544,23 +732,56 @@ class StreamHandler
|
|||
*/
|
||||
private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
if (\is_array($value)) {
|
||||
$options['ssl']['passphrase'] = $value[1];
|
||||
$value = $value[0];
|
||||
}
|
||||
[$value, $passphrase] = self::normalizeTlsFileOption('cert', $value);
|
||||
|
||||
if (!\file_exists($value)) {
|
||||
throw new \RuntimeException("SSL certificate not found: {$value}");
|
||||
}
|
||||
|
||||
self::setTlsPassphrase($options, $passphrase, 'cert');
|
||||
$options['ssl']['local_cert'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_cert_type(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
self::assertStreamTlsType('cert_type', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_ssl_key(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
[$value, $passphrase] = self::normalizeTlsFileOption('ssl_key', $value);
|
||||
|
||||
if (!\file_exists($value)) {
|
||||
throw new \RuntimeException("SSL private key not found: {$value}");
|
||||
}
|
||||
|
||||
self::setTlsPassphrase($options, $passphrase, 'ssl_key');
|
||||
$options['ssl']['local_pk'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_ssl_key_type(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
self::assertStreamTlsType('ssl_key_type', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
if (!\is_callable($value)) {
|
||||
throw new \InvalidArgumentException('progress client option must be callable');
|
||||
}
|
||||
|
||||
self::addNotification(
|
||||
$params,
|
||||
static function ($code, $a, $b, $c, $transferred, $total) use ($value) {
|
||||
|
|
|
|||
22
vendor/guzzlehttp/guzzle/src/HandlerStack.php
vendored
22
vendor/guzzlehttp/guzzle/src/HandlerStack.php
vendored
|
|
@ -181,15 +181,29 @@ class HandlerStack
|
|||
public function remove($remove): void
|
||||
{
|
||||
if (!is_string($remove) && !is_callable($remove)) {
|
||||
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
|
||||
}
|
||||
|
||||
$this->cached = null;
|
||||
$idx = \is_callable($remove) ? 0 : 1;
|
||||
|
||||
if (\is_string($remove)) {
|
||||
$count = \count($this->stack);
|
||||
$this->stack = \array_values(\array_filter(
|
||||
$this->stack,
|
||||
static function ($tuple) use ($remove) {
|
||||
return $tuple[1] !== $remove;
|
||||
}
|
||||
));
|
||||
|
||||
if ($count !== \count($this->stack) || !\is_callable($remove)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->stack = \array_values(\array_filter(
|
||||
$this->stack,
|
||||
static function ($tuple) use ($idx, $remove) {
|
||||
return $tuple[$idx] !== $remove;
|
||||
static function ($tuple) use ($remove) {
|
||||
return $tuple[0] !== $remove;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
|
|
|||
2
vendor/guzzlehttp/guzzle/src/Middleware.php
vendored
2
vendor/guzzlehttp/guzzle/src/Middleware.php
vendored
|
|
@ -29,7 +29,7 @@ final class Middleware
|
|||
return static function ($request, array $options) use ($handler) {
|
||||
if (empty($options['cookies'])) {
|
||||
return $handler($request, $options);
|
||||
} elseif (!($options['cookies'] instanceof CookieJarInterface)) {
|
||||
} elseif (!$options['cookies'] instanceof CookieJarInterface) {
|
||||
throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
|
||||
}
|
||||
$cookieJar = $options['cookies'];
|
||||
|
|
|
|||
12
vendor/guzzlehttp/guzzle/src/Pool.php
vendored
12
vendor/guzzlehttp/guzzle/src/Pool.php
vendored
|
|
@ -51,6 +51,18 @@ class Pool implements PromisorInterface
|
|||
$opts = [];
|
||||
}
|
||||
|
||||
if (!\is_iterable($requests)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/guzzle',
|
||||
'7.11',
|
||||
'Passing a non-iterable request collection to %s::__construct() or %s::batch() is deprecated; guzzlehttp/guzzle 8.0 will require an iterable.',
|
||||
__CLASS__,
|
||||
__CLASS__
|
||||
);
|
||||
|
||||
$requests = [$requests];
|
||||
}
|
||||
|
||||
$iterable = P\Create::iterFor($requests);
|
||||
$requests = static function () use ($iterable, $client, $opts) {
|
||||
foreach ($iterable as $key => $rfn) {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class PrepareBodyMiddleware
|
|||
) {
|
||||
$size = $request->getBody()->getSize();
|
||||
if ($size !== null) {
|
||||
$modify['set_headers']['Content-Length'] = $size;
|
||||
$modify['set_headers']['Content-Length'] = (string) $size;
|
||||
} else {
|
||||
$modify['set_headers']['Transfer-Encoding'] = 'chunked';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use Psr\Http\Message\UriInterface;
|
|||
* Request redirect middleware.
|
||||
*
|
||||
* Apply this middleware like other middleware using
|
||||
* {@see \GuzzleHttp\Middleware::redirect()}.
|
||||
* {@see Middleware::redirect()}.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
|
|
@ -177,8 +177,8 @@ class RedirectMiddleware
|
|||
}
|
||||
|
||||
$uri = self::redirectUri($request, $response, $protocols);
|
||||
if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
|
||||
$idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion'];
|
||||
$idnOptions = Utils::normalizeIdnConversionOption($options['idn_conversion'] ?? null);
|
||||
if ($idnOptions !== null) {
|
||||
$uri = Utils::idnUriConvert($uri, $idnOptions);
|
||||
}
|
||||
|
||||
|
|
|
|||
144
vendor/guzzlehttp/guzzle/src/RequestOptions.php
vendored
144
vendor/guzzlehttp/guzzle/src/RequestOptions.php
vendored
|
|
@ -5,7 +5,7 @@ namespace GuzzleHttp;
|
|||
/**
|
||||
* This class contains a list of built-in Guzzle request options.
|
||||
*
|
||||
* @see https://docs.guzzlephp.org/en/latest/request-options.html
|
||||
* @see https://github.com/guzzle/guzzle/blob/7.11/docs/request-options.md
|
||||
*/
|
||||
final class RequestOptions
|
||||
{
|
||||
|
|
@ -23,41 +23,53 @@ final class RequestOptions
|
|||
* browsers do which is redirect POST requests with GET requests
|
||||
* - referer: (bool, default=false) Set to true to enable the Referer
|
||||
* header.
|
||||
* - protocols: (array, default=['http', 'https']) Allowed redirect
|
||||
* protocols.
|
||||
* - protocols: (non-empty-array<array-key, string>, default=['http', 'https'])
|
||||
* Allowed redirect protocols. Redirect matching is case-sensitive; use
|
||||
* "http" and "https".
|
||||
* - on_redirect: (callable) PHP callable that is invoked when a redirect
|
||||
* is encountered. The callable is invoked with the request, the redirect
|
||||
* response that was received, and the effective URI. Any return value
|
||||
* from the on_redirect function is ignored.
|
||||
* - track_redirects: (bool, default=false) Track redirected URI and status
|
||||
* history in response headers.
|
||||
*/
|
||||
public const ALLOW_REDIRECTS = 'allow_redirects';
|
||||
|
||||
/**
|
||||
* auth: (array) Pass an array of HTTP authentication parameters to use
|
||||
* with the request. The array must contain the username in index [0],
|
||||
* the password in index [1], and you can optionally provide a built-in
|
||||
* authentication type in index [2]. Pass null to disable authentication
|
||||
* for a request.
|
||||
* auth: (array{0: string, 1: string, 2?: string|null}|string|false|null)
|
||||
* Pass an array of HTTP authentication parameters to use with the request.
|
||||
* The array must contain the username in index [0], the password in index
|
||||
* [1], and you can optionally provide a built-in authentication type in
|
||||
* index [2]. Pass false or null to disable authentication for a request.
|
||||
* String values are passed through for custom handlers.
|
||||
*/
|
||||
public const AUTH = 'auth';
|
||||
|
||||
/**
|
||||
* body: (resource|string|null|int|float|StreamInterface|callable|\Iterator)
|
||||
* Body to send in the request.
|
||||
* body: (resource|string|null|int|float|bool|\Psr\Http\Message\StreamInterface|(callable&object)|\Iterator|\Stringable)
|
||||
* Body to send in the request. Callable arrays are arrays, and arrays are
|
||||
* not valid body values in Guzzle.
|
||||
*/
|
||||
public const BODY = 'body';
|
||||
|
||||
/**
|
||||
* cert: (string|array) Set to a string to specify the path to a file
|
||||
* containing a PEM formatted SSL client side certificate. If a password
|
||||
* is required, then set cert to an array containing the path to the PEM
|
||||
* file in the first array element followed by the certificate password
|
||||
* in the second array element.
|
||||
* cert: (string|array{0: string, 1?: string|null}) Set to a string to
|
||||
* specify the path to a client certificate file. PEM is the default
|
||||
* certificate format. If a password is required, set cert to an array
|
||||
* containing the certificate path in the first array element followed by
|
||||
* the certificate password in the second array element. A null password is
|
||||
* treated the same as omitting it. Use cert_type to specify another
|
||||
* supported certificate format.
|
||||
*/
|
||||
public const CERT = 'cert';
|
||||
|
||||
/**
|
||||
* cookies: (bool|GuzzleHttp\Cookie\CookieJarInterface, default=false)
|
||||
* cert_type: (string) Specify the SSL client certificate file type.
|
||||
*/
|
||||
public const CERT_TYPE = 'cert_type';
|
||||
|
||||
/**
|
||||
* cookies: (false|GuzzleHttp\Cookie\CookieJarInterface, default=false)
|
||||
* Specifies whether or not cookies are used in a request or what cookie
|
||||
* jar to use or what cookies to send. This option only works if your
|
||||
* handler has the `cookie` middleware. Valid values are `false` and
|
||||
|
|
@ -66,9 +78,9 @@ final class RequestOptions
|
|||
public const COOKIES = 'cookies';
|
||||
|
||||
/**
|
||||
* connect_timeout: (float, default=0) Float describing the number of
|
||||
* seconds to wait while trying to connect to a server. Use 0 to wait
|
||||
* 300 seconds (the default behavior).
|
||||
* connect_timeout: (int|float, default=0) Number of seconds to wait while
|
||||
* trying to connect to a server. Use 0 to wait 300 seconds (the default
|
||||
* behavior).
|
||||
*/
|
||||
public const CONNECT_TIMEOUT = 'connect_timeout';
|
||||
|
||||
|
|
@ -92,14 +104,15 @@ final class RequestOptions
|
|||
public const DEBUG = 'debug';
|
||||
|
||||
/**
|
||||
* decode_content: (bool, default=true) Specify whether or not
|
||||
* decode_content: (bool|string, default=true) Specify whether or not
|
||||
* Content-Encoding responses (gzip, deflate, etc.) are automatically
|
||||
* decoded.
|
||||
*/
|
||||
public const DECODE_CONTENT = 'decode_content';
|
||||
|
||||
/**
|
||||
* delay: (int) The amount of time to delay before sending in milliseconds.
|
||||
* delay: (int|float) The amount of time to delay before sending in
|
||||
* milliseconds.
|
||||
*/
|
||||
public const DELAY = 'delay';
|
||||
|
||||
|
|
@ -122,16 +135,17 @@ final class RequestOptions
|
|||
public const EXPECT = 'expect';
|
||||
|
||||
/**
|
||||
* form_params: (array) Associative array of form field names to values
|
||||
* where each value is a string or array of strings. Sets the Content-Type
|
||||
* header to application/x-www-form-urlencoded when no Content-Type header
|
||||
* is already present.
|
||||
* form_params: (array<array-key, string|int|float|bool|null|array>)
|
||||
* Associative array of form field names to scalar, null, or nested array
|
||||
* values. Sets the Content-Type header to application/x-www-form-urlencoded
|
||||
* when no Content-Type header is already present.
|
||||
*/
|
||||
public const FORM_PARAMS = 'form_params';
|
||||
|
||||
/**
|
||||
* headers: (array) Associative array of HTTP headers. Each value MUST be
|
||||
* a string or array of strings.
|
||||
* headers: (array<array-key, string|non-empty-array<array-key, string>>|null)
|
||||
* Associative array of HTTP headers. Each value MUST be a string or non-empty
|
||||
* array of strings.
|
||||
*/
|
||||
public const HEADERS = 'headers';
|
||||
|
||||
|
|
@ -144,10 +158,10 @@ final class RequestOptions
|
|||
public const HTTP_ERRORS = 'http_errors';
|
||||
|
||||
/**
|
||||
* idn: (bool|int, default=true) A combination of IDNA_* constants for
|
||||
* idn_to_ascii() PHP's function (see "options" parameter). Set to false to
|
||||
* disable IDN support completely, or to true to use the default
|
||||
* configuration (IDNA_DEFAULT constant).
|
||||
* idn_conversion: (bool|int|null, default=false) A combination of IDNA_*
|
||||
* constants for PHP's idn_to_ascii() function. Set to false or null to
|
||||
* disable IDN support, or to true to use the default configuration
|
||||
* (IDNA_DEFAULT constant).
|
||||
*/
|
||||
public const IDN_CONVERSION = 'idn_conversion';
|
||||
|
||||
|
|
@ -159,13 +173,13 @@ final class RequestOptions
|
|||
public const JSON = 'json';
|
||||
|
||||
/**
|
||||
* multipart: (array) Array of associative arrays, each containing a
|
||||
* required "name" key mapping to the form field, name, a required
|
||||
* "contents" key mapping to a StreamInterface|resource|string, an
|
||||
* optional "headers" associative array of custom headers, and an
|
||||
* optional "filename" key mapping to a string to send as the filename in
|
||||
* the part. If no "filename" key is present, then no "filename" attribute
|
||||
* will be added to the part.
|
||||
* multipart: (array) Array of part arrays, each containing a required
|
||||
* "name" key mapping to the string or integer form field name, a required
|
||||
* "contents" key mapping to any non-array value accepted by PSR-7
|
||||
* Utils::streamFor() or a nested array of field values, an optional
|
||||
* "headers" array of string custom header values, and an optional
|
||||
* "filename" key mapping to a string to send as the filename in the part.
|
||||
* "headers" and "filename" cannot be used when "contents" is an array.
|
||||
*/
|
||||
public const MULTIPART = 'multipart';
|
||||
|
||||
|
|
@ -196,25 +210,34 @@ final class RequestOptions
|
|||
*/
|
||||
public const PROGRESS = 'progress';
|
||||
|
||||
/**
|
||||
* protocols: (non-empty-array<array-key, string>, default=['http', 'https'])
|
||||
* Allowed URI schemes. Built-in handlers accept only the case-sensitive
|
||||
* values "http" and "https".
|
||||
*/
|
||||
public const PROTOCOLS = 'protocols';
|
||||
|
||||
/**
|
||||
* proxy: (string|array) Pass a string to specify an HTTP proxy, or an
|
||||
* array to specify different proxies for different protocols (where the
|
||||
* key is the protocol and the value is a proxy string).
|
||||
* key is the protocol and the value is a proxy string or null). Provide a
|
||||
* "no" key as a comma-delimited string, array of strings, or null to
|
||||
* specify hosts or host-and-port pairs that should not be proxied.
|
||||
*/
|
||||
public const PROXY = 'proxy';
|
||||
|
||||
/**
|
||||
* query: (array|string) Associative array of query string values to add
|
||||
* to the request. This option uses PHP's http_build_query() to create
|
||||
* the string representation. Pass a string value if you need more
|
||||
* control than what this method provides
|
||||
* query: (array<array-key, mixed>|string) Associative array of query string
|
||||
* values to add to the request. This option uses PHP's http_build_query()
|
||||
* to create the string representation. Pass a string value if you need
|
||||
* more control than what this method provides
|
||||
*/
|
||||
public const QUERY = 'query';
|
||||
|
||||
/**
|
||||
* sink: (resource|string|StreamInterface) Where the data of the
|
||||
* response is written to. Defaults to a PHP temp stream. Providing a
|
||||
* string will write data to a file by the given name.
|
||||
* sink: (resource|string|\Psr\Http\Message\StreamInterface) Where the data
|
||||
* of the response is written to. Defaults to a PHP temp stream. Providing
|
||||
* a string will write data to a file by the given name.
|
||||
*/
|
||||
public const SINK = 'sink';
|
||||
|
||||
|
|
@ -227,15 +250,22 @@ final class RequestOptions
|
|||
public const SYNCHRONOUS = 'synchronous';
|
||||
|
||||
/**
|
||||
* ssl_key: (array|string) Specify the path to a file containing a private
|
||||
* SSL key in PEM format. If a password is required, then set to an array
|
||||
* containing the path to the SSL key in the first array element followed
|
||||
* by the password required for the certificate in the second element.
|
||||
* ssl_key: (array{0: string, 1?: string|null}|string) Specify the path to
|
||||
* a private SSL key file. PEM is the default private key format. If a
|
||||
* password is required, set ssl_key to an array containing the key path in
|
||||
* the first array element followed by the key password in the second
|
||||
* element. A null password is treated the same as omitting it. Use
|
||||
* ssl_key_type to specify another supported key format.
|
||||
*/
|
||||
public const SSL_KEY = 'ssl_key';
|
||||
|
||||
/**
|
||||
* stream: Set to true to attempt to stream a response rather than
|
||||
* ssl_key_type: (string) Specify the SSL private key file type.
|
||||
*/
|
||||
public const SSL_KEY_TYPE = 'ssl_key_type';
|
||||
|
||||
/**
|
||||
* stream: (bool) Set to true to attempt to stream a response rather than
|
||||
* download it all up-front.
|
||||
*/
|
||||
public const STREAM = 'stream';
|
||||
|
|
@ -251,24 +281,26 @@ final class RequestOptions
|
|||
public const VERIFY = 'verify';
|
||||
|
||||
/**
|
||||
* timeout: (float, default=0) Float describing the timeout of the
|
||||
* timeout: (int|float, default=0) Number describing the timeout of the
|
||||
* request in seconds. Use 0 to wait indefinitely (the default behavior).
|
||||
*/
|
||||
public const TIMEOUT = 'timeout';
|
||||
|
||||
/**
|
||||
* read_timeout: (float, default=default_socket_timeout ini setting) Float describing
|
||||
* the body read timeout, for stream requests.
|
||||
* read_timeout: (int|float, default=default_socket_timeout ini setting)
|
||||
* Number describing the body read timeout, for stream requests.
|
||||
*/
|
||||
public const READ_TIMEOUT = 'read_timeout';
|
||||
|
||||
/**
|
||||
* version: (float) Specifies the HTTP protocol version to attempt to use.
|
||||
* version: (string|int|float) Specifies the HTTP protocol version to attempt
|
||||
* to use.
|
||||
*/
|
||||
public const VERSION = 'version';
|
||||
|
||||
/**
|
||||
* force_ip_resolve: (bool) Force client to use only ipv4 or ipv6 protocol
|
||||
* force_ip_resolve: (string) Set to "v4" to force IPv4 resolution or "v6"
|
||||
* for IPv6 resolution when supported by the handler.
|
||||
*/
|
||||
public const FORCE_IP_RESOLVE = 'force_ip_resolve';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,16 +44,22 @@ class RetryMiddleware
|
|||
{
|
||||
$this->decider = $decider;
|
||||
$this->nextHandler = $nextHandler;
|
||||
$this->delay = $delay ?: __CLASS__.'::exponentialDelay';
|
||||
$this->delay = $delay ?: static function (int $retries): int {
|
||||
return (int) 2 ** ($retries - 1) * 1000;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default exponential backoff delay function.
|
||||
*
|
||||
* @return int milliseconds.
|
||||
*
|
||||
* @deprecated since 7.11, will be removed in 8.0.
|
||||
*/
|
||||
public static function exponentialDelay(int $retries): int
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.11', '%s::%s() is deprecated and will be removed in 8.0.', __CLASS__, __FUNCTION__);
|
||||
|
||||
return (int) 2 ** ($retries - 1) * 1000;
|
||||
}
|
||||
|
||||
|
|
|
|||
14
vendor/guzzlehttp/guzzle/src/TransportSharing.php
vendored
Normal file
14
vendor/guzzlehttp/guzzle/src/TransportSharing.php
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
final class TransportSharing
|
||||
{
|
||||
public const NONE = 'none';
|
||||
public const HANDLER_PREFER = 'handler_prefer';
|
||||
public const HANDLER_REQUIRE = 'handler_require';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
274
vendor/guzzlehttp/guzzle/src/Utils.php
vendored
274
vendor/guzzlehttp/guzzle/src/Utils.php
vendored
|
|
@ -5,8 +5,10 @@ namespace GuzzleHttp;
|
|||
use GuzzleHttp\Exception\InvalidArgumentException;
|
||||
use GuzzleHttp\Handler\CurlHandler;
|
||||
use GuzzleHttp\Handler\CurlMultiHandler;
|
||||
use GuzzleHttp\Handler\CurlShareHandleState;
|
||||
use GuzzleHttp\Handler\Proxy;
|
||||
use GuzzleHttp\Handler\StreamHandler;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
final class Utils
|
||||
|
|
@ -79,28 +81,51 @@ final class Utils
|
|||
*
|
||||
* The returned handler is not wrapped by any default middlewares.
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system.
|
||||
* @param array{transport_sharing?: mixed} $handlerOptions Handler constructor options.
|
||||
*
|
||||
* @return callable(RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system.
|
||||
*
|
||||
* @throws \RuntimeException if no viable Handler is available.
|
||||
*/
|
||||
public static function chooseHandler(): callable
|
||||
public static function chooseHandler(array $handlerOptions = []): callable
|
||||
{
|
||||
$handler = null;
|
||||
$sharingMode = CurlShareHandleState::normalizeMode($handlerOptions['transport_sharing'] ?? null, 'transport_sharing');
|
||||
$sharingRequested = $sharingMode !== TransportSharing::NONE;
|
||||
$sharingRequired = $sharingMode === TransportSharing::HANDLER_REQUIRE;
|
||||
$curlHandlerOptions = [];
|
||||
$curlSupported = \defined('CURLOPT_CUSTOMREQUEST')
|
||||
&& \function_exists('curl_version')
|
||||
&& version_compare(curl_version()['version'], '7.21.2') >= 0
|
||||
&& (\function_exists('curl_multi_exec') || \function_exists('curl_exec'));
|
||||
|
||||
if ($sharingRequired && !$curlSupported) {
|
||||
throw new \RuntimeException('Required transport sharing requires the PHP cURL extension, curl_exec() or curl_multi_exec(), and libcurl 7.21.2 or higher.');
|
||||
}
|
||||
|
||||
if ($curlSupported) {
|
||||
if ($sharingRequested) {
|
||||
$shareState = CurlShareHandleState::fromOption($sharingMode);
|
||||
if ($shareState !== null) {
|
||||
$curlHandlerOptions['transport_sharing'] = $shareState;
|
||||
}
|
||||
}
|
||||
|
||||
if (\defined('CURLOPT_CUSTOMREQUEST') && \function_exists('curl_version') && version_compare(curl_version()['version'], '7.21.2') >= 0) {
|
||||
if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) {
|
||||
$handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
|
||||
$handler = Proxy::wrapSync(new CurlMultiHandler($curlHandlerOptions), new CurlHandler($curlHandlerOptions));
|
||||
} elseif (\function_exists('curl_exec')) {
|
||||
$handler = new CurlHandler();
|
||||
$handler = new CurlHandler($curlHandlerOptions);
|
||||
} elseif (\function_exists('curl_multi_exec')) {
|
||||
$handler = new CurlMultiHandler();
|
||||
$handler = new CurlMultiHandler($curlHandlerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (\ini_get('allow_url_fopen')) {
|
||||
$streamHandler = new StreamHandler(['transport_sharing' => $sharingMode]);
|
||||
|
||||
$handler = $handler
|
||||
? Proxy::wrapStreaming($handler, new StreamHandler())
|
||||
: new StreamHandler();
|
||||
? Proxy::wrapStreaming($handler, $streamHandler)
|
||||
: $streamHandler;
|
||||
} elseif (!$handler) {
|
||||
throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.');
|
||||
}
|
||||
|
|
@ -176,7 +201,7 @@ No system CA bundle could be found in any of the the common system locations.
|
|||
PHP versions earlier than 5.6 are not properly configured to use the system's
|
||||
CA bundle by default. In order to verify peer certificates, you will need to
|
||||
supply the path on disk to a certificate bundle to the 'verify' request
|
||||
option: https://docs.guzzlephp.org/en/latest/request-options.html#verify. If
|
||||
option: https://github.com/guzzle/guzzle/blob/7.11/docs/request-options.md#verify. If
|
||||
you do not need a specific certificate bundle, then Mozilla provides a commonly
|
||||
used CA bundle which can be downloaded here (provided by the maintainer of
|
||||
cURL): https://curl.haxx.se/ca/cacert.pem. Once you have a CA bundle available
|
||||
|
|
@ -195,12 +220,42 @@ EOT
|
|||
{
|
||||
$result = [];
|
||||
foreach (\array_keys($headers) as $key) {
|
||||
$result[\strtolower($key)] = $key;
|
||||
$result[\strtolower((string) $key)] = $key;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $protocols
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function normalizeProtocols($protocols): array
|
||||
{
|
||||
if (!\is_array($protocols) || $protocols === []) {
|
||||
throw new InvalidArgumentException('protocols must be a non-empty array of "http" and/or "https"');
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
|
||||
foreach ($protocols as $protocol) {
|
||||
if (!\is_string($protocol)) {
|
||||
throw new InvalidArgumentException('protocols must contain only strings');
|
||||
}
|
||||
|
||||
if ($protocol !== 'http' && $protocol !== 'https') {
|
||||
throw new InvalidArgumentException('protocols may only contain "http" and "https"');
|
||||
}
|
||||
|
||||
$normalized[$protocol] = true;
|
||||
}
|
||||
|
||||
return \array_keys($normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided host matches any of the no proxy areas.
|
||||
*
|
||||
|
|
@ -226,8 +281,7 @@ EOT
|
|||
throw new InvalidArgumentException('Empty host provided');
|
||||
}
|
||||
|
||||
// Strip port if present.
|
||||
[$host] = \explode(':', $host, 2);
|
||||
$host = self::normalizeNoProxyHost($host, true);
|
||||
|
||||
foreach ($noProxyArray as $area) {
|
||||
// Always match on wildcards.
|
||||
|
|
@ -235,11 +289,12 @@ EOT
|
|||
return true;
|
||||
}
|
||||
|
||||
if (empty($area)) {
|
||||
// Don't match on empty values.
|
||||
if ($area === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$area = self::normalizeNoProxyHost($area, false);
|
||||
|
||||
if ($area === $host) {
|
||||
// Exact matches.
|
||||
return true;
|
||||
|
|
@ -247,7 +302,11 @@ EOT
|
|||
// Special match if the area when prefixed with ".". Remove any
|
||||
// existing leading "." and add a new leading ".".
|
||||
$area = '.'.\ltrim($area, '.');
|
||||
if (\substr($host, -\strlen($area)) === $area) {
|
||||
if (
|
||||
\strpos($host, ':') === false
|
||||
&& \strpos($area, ':') === false
|
||||
&& \substr($host, -\strlen($area)) === $area
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -255,6 +314,154 @@ EOT
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided URI matches any of the no proxy areas.
|
||||
*
|
||||
* @param mixed $noProxy No-proxy host patterns.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function isUriInNoProxy(UriInterface $uri, $noProxy): bool
|
||||
{
|
||||
if (\is_string($noProxy)) {
|
||||
$noProxy = \explode(',', $noProxy);
|
||||
}
|
||||
|
||||
if (!\is_array($noProxy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$host = $uri->getHost();
|
||||
if ($host === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$port = $uri->getPort();
|
||||
if ($port === null) {
|
||||
$port = self::getDefaultPort($uri->getScheme());
|
||||
}
|
||||
|
||||
foreach ($noProxy as $area) {
|
||||
if (!\is_string($area)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$area = \trim($area);
|
||||
|
||||
// Always match on wildcards.
|
||||
if ($area === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($area === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$area, $areaPort] = self::splitNoProxyHostAndPort($area);
|
||||
if ($areaPort !== null && $areaPort !== $port) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::isHostInNoProxy($host, [$area])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function normalizeNoProxyHost(string $host, bool $stripPort): string
|
||||
{
|
||||
if ($host !== '' && $host[0] === '[') {
|
||||
$closingBracket = \strpos($host, ']');
|
||||
|
||||
if ($closingBracket !== false) {
|
||||
$address = \substr($host, 1, $closingBracket - 1);
|
||||
$tail = \substr($host, $closingBracket + 1);
|
||||
|
||||
if (
|
||||
($tail === '' || ($stripPort && \preg_match('/^:\d+$/', $tail)))
|
||||
&& \filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)
|
||||
) {
|
||||
return \strtolower($address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
|
||||
return \strtolower($host);
|
||||
}
|
||||
|
||||
if ($stripPort) {
|
||||
[$host] = \explode(':', $host, 2);
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: int|null}
|
||||
*/
|
||||
private static function splitNoProxyHostAndPort(string $area): array
|
||||
{
|
||||
if ($area !== '' && $area[0] === '[') {
|
||||
$closingBracket = \strpos($area, ']');
|
||||
|
||||
if ($closingBracket !== false) {
|
||||
$tail = \substr($area, $closingBracket + 1);
|
||||
if ($tail !== '' && $tail[0] === ':') {
|
||||
$port = self::parseNoProxyPort(\substr($tail, 1));
|
||||
|
||||
if ($port !== null) {
|
||||
return [\substr($area, 0, $closingBracket + 1), $port];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$area, null];
|
||||
}
|
||||
|
||||
if (\filter_var($area, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
|
||||
return [$area, null];
|
||||
}
|
||||
|
||||
$colon = \strrpos($area, ':');
|
||||
if ($colon === false) {
|
||||
return [$area, null];
|
||||
}
|
||||
|
||||
$port = self::parseNoProxyPort(\substr($area, $colon + 1));
|
||||
if ($port === null) {
|
||||
return [$area, null];
|
||||
}
|
||||
|
||||
return [\substr($area, 0, $colon), $port];
|
||||
}
|
||||
|
||||
private static function parseNoProxyPort(string $port): ?int
|
||||
{
|
||||
if ($port === '' || !\ctype_digit($port)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$port = (int) $port;
|
||||
|
||||
return $port <= 65535 ? $port : null;
|
||||
}
|
||||
|
||||
private static function getDefaultPort(string $scheme): ?int
|
||||
{
|
||||
if ($scheme === 'http') {
|
||||
return 80;
|
||||
}
|
||||
|
||||
if ($scheme === 'https') {
|
||||
return 443;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for json_decode that throws when an error occurs.
|
||||
*
|
||||
|
|
@ -272,6 +479,10 @@ EOT
|
|||
*/
|
||||
public static function jsonDecode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
|
||||
{
|
||||
if ($depth < 1) {
|
||||
throw new InvalidArgumentException('json_decode error: Maximum stack depth exceeded');
|
||||
}
|
||||
|
||||
$data = \json_decode($json, $assoc, $depth, $options);
|
||||
if (\JSON_ERROR_NONE !== \json_last_error()) {
|
||||
throw new InvalidArgumentException('json_decode error: '.\json_last_error_msg());
|
||||
|
|
@ -315,6 +526,39 @@ EOT
|
|||
return (float) \function_exists('hrtime') ? \hrtime(true) / 1e9 : \microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function normalizeIdnConversionOption($value): ?int
|
||||
{
|
||||
if ($value === null || $value === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value === true) {
|
||||
return \IDNA_DEFAULT;
|
||||
}
|
||||
|
||||
if (\is_int($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ((\is_string($value) && \is_numeric($value)) || (\is_float($value) && \is_finite($value))) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/guzzle',
|
||||
'7.11',
|
||||
'Passing %s as the "idn_conversion" request option is deprecated; guzzlehttp/guzzle 8.0 will reject values that are not true, false, null, or an integer IDNA_* bitmask.',
|
||||
self::describeType($value)
|
||||
);
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('idn_conversion must be true, false, null, or an integer IDNA_* bitmask');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in a new issue