commit
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
|
||||
final class BodySummarizer implements BodySummarizerInterface
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $truncateAt;
|
||||
|
||||
public function __construct(?int $truncateAt = null)
|
||||
{
|
||||
$this->truncateAt = $truncateAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summarized message body.
|
||||
*/
|
||||
public function summarize(MessageInterface $message): ?string
|
||||
{
|
||||
return $this->truncateAt === null
|
||||
? Psr7\Message::bodySummary($message)
|
||||
: Psr7\Message::bodySummary($message, $this->truncateAt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
|
||||
interface BodySummarizerInterface
|
||||
{
|
||||
/**
|
||||
* Returns a summarized message body.
|
||||
*/
|
||||
public function summarize(MessageInterface $message): ?string;
|
||||
}
|
||||
+1381
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Client interface for sending HTTP requests.
|
||||
*/
|
||||
interface ClientInterface
|
||||
{
|
||||
/**
|
||||
* The Guzzle major version.
|
||||
*/
|
||||
public const MAJOR_VERSION = 7;
|
||||
|
||||
/**
|
||||
* Send an HTTP request.
|
||||
*
|
||||
* @param RequestInterface $request Request to send
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function send(RequestInterface $request, array $options = []): ResponseInterface;
|
||||
|
||||
/**
|
||||
* Asynchronously send an HTTP request.
|
||||
*
|
||||
* @param RequestInterface $request Request to send
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer.
|
||||
*/
|
||||
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface;
|
||||
|
||||
/**
|
||||
* Create and send an HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function request(string $method, $uri, array $options = []): ResponseInterface;
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;
|
||||
|
||||
/**
|
||||
* Get a client configuration option.
|
||||
*
|
||||
* These options include default request options of the client, a "handler"
|
||||
* (if utilized by the concrete client), and a "base_uri" if utilized by
|
||||
* the concrete client.
|
||||
*
|
||||
* @param string|null $option The config option to retrieve.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0.
|
||||
*/
|
||||
public function getConfig(?string $option = null);
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Client interface for sending HTTP requests.
|
||||
*/
|
||||
trait ClientTrait
|
||||
{
|
||||
/**
|
||||
* Create and send an HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
abstract public function request(string $method, $uri, array $options = []): ResponseInterface;
|
||||
|
||||
/**
|
||||
* Create and send an HTTP GET request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function get($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('GET', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP HEAD request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function head($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('HEAD', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP PUT request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function put($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('PUT', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP POST request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function post($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('POST', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP PATCH request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function patch($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('PATCH', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP DELETE request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function delete($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('DELETE', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP GET request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function getAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('GET', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP HEAD request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function headAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('HEAD', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP PUT request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function putAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('PUT', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP POST request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function postAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('POST', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP PATCH request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function patchAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('PATCH', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP DELETE request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function deleteAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('DELETE', $uri, $options);
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Cookie jar that stores cookies as an array
|
||||
*/
|
||||
class CookieJar implements CookieJarInterface
|
||||
{
|
||||
private const MAX_SET_COOKIE_FIELD_LENGTH = 8190;
|
||||
private const MAX_SET_COOKIE_FIELDS = 50;
|
||||
private const MAX_REQUEST_COOKIES = 150;
|
||||
private const MAX_COOKIE_HEADER_LENGTH = 8190;
|
||||
|
||||
/**
|
||||
* @var SetCookie[] Loaded cookie data
|
||||
*/
|
||||
private $cookies = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $strictMode;
|
||||
|
||||
/**
|
||||
* @param bool $strictMode Set to true to throw exceptions when invalid
|
||||
* cookies are added to the cookie jar.
|
||||
* @param array $cookieArray Array of SetCookie objects or a hash of
|
||||
* arrays that can be used with the SetCookie
|
||||
* constructor
|
||||
*/
|
||||
public function __construct(bool $strictMode = false, array $cookieArray = [])
|
||||
{
|
||||
$this->strictMode = $strictMode;
|
||||
|
||||
foreach ($cookieArray as $cookie) {
|
||||
if (!$cookie instanceof SetCookie) {
|
||||
$cookie = new SetCookie($cookie);
|
||||
}
|
||||
$this->setCookie($cookie);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Cookie jar from an associative array and domain.
|
||||
*
|
||||
* @param array $cookies Cookies to create the jar from
|
||||
* @param string $domain Domain to set the cookies to
|
||||
*/
|
||||
public static function fromArray(array $cookies, string $domain): self
|
||||
{
|
||||
$cookieJar = new self();
|
||||
foreach ($cookies as $name => $value) {
|
||||
$cookieJar->setCookie(new SetCookie([
|
||||
'Domain' => $domain,
|
||||
'Name' => $name,
|
||||
'Value' => $value,
|
||||
'Discard' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
return $cookieJar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate if this cookie should be persisted to storage
|
||||
* that survives between requests.
|
||||
*
|
||||
* @param SetCookie $cookie Being evaluated.
|
||||
* @param bool $allowSessionCookies If we should persist session cookies
|
||||
*/
|
||||
public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool
|
||||
{
|
||||
if ($cookie->getExpires() || $allowSessionCookies) {
|
||||
if (!$cookie->getDiscard()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the cookie based on the name
|
||||
*
|
||||
* @param string $name cookie name to search for
|
||||
*
|
||||
* @return SetCookie|null cookie that was found or null if not found
|
||||
*/
|
||||
public function getCookieByName(string $name): ?SetCookie
|
||||
{
|
||||
foreach ($this->cookies as $cookie) {
|
||||
if ($cookie->getName() !== null && Psr7\Utils::caselessEquals($cookie->getName(), $name)) {
|
||||
return $cookie;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return \array_map(static function (SetCookie $cookie): array {
|
||||
return $cookie->toArray();
|
||||
}, $this->getIterator()->getArrayCopy());
|
||||
}
|
||||
|
||||
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void
|
||||
{
|
||||
if ($domain === null) {
|
||||
$this->cookies = [];
|
||||
|
||||
return;
|
||||
} elseif ($path === null) {
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($domain): bool {
|
||||
return $cookie->getDomain() === null || !$cookie->matchesDomain($domain);
|
||||
}
|
||||
);
|
||||
} elseif ($name === null) {
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($path, $domain): bool {
|
||||
return !($cookie->getDomain() !== null
|
||||
&& $cookie->matchesPath($path)
|
||||
&& $cookie->matchesDomain($domain));
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($path, $domain, $name) {
|
||||
return !($cookie->getDomain() !== null
|
||||
&& $cookie->getName() === $name
|
||||
&& $cookie->matchesPath($path)
|
||||
&& $cookie->matchesDomain($domain));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearSessionCookies(): void
|
||||
{
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie): bool {
|
||||
return !$cookie->getDiscard() && $cookie->getExpires();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function setCookie(SetCookie $cookie): bool
|
||||
{
|
||||
// If the name string is empty (but not 0), ignore the set-cookie
|
||||
// string entirely.
|
||||
$name = $cookie->getName();
|
||||
if (!$name && $name !== '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow cookies with set and valid domain, name, value
|
||||
$result = $cookie->validate();
|
||||
if ($result !== true) {
|
||||
if ($this->strictMode) {
|
||||
throw new \RuntimeException('Invalid cookie: '.$result);
|
||||
}
|
||||
$this->removeCookieIfEmpty($cookie);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$maxAge = $cookie->getMaxAge();
|
||||
if ($maxAge !== null && $maxAge <= 0) {
|
||||
if ($cookie->getDomain() !== null) {
|
||||
$this->removeCookie($cookie);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve conflicts with previously set cookies
|
||||
foreach ($this->cookies as $i => $c) {
|
||||
// Two cookies are identical, when their path, and domain are
|
||||
// identical.
|
||||
if ($c->getPath() !== $cookie->getPath()
|
||||
|| $c->getDomain() !== $cookie->getDomain()
|
||||
|| $c->getHostOnly() !== $cookie->getHostOnly()
|
||||
|| $c->getName() !== $cookie->getName()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The previously set cookie is a discard cookie and this one is
|
||||
// not so allow the new cookie to be set
|
||||
if (!$cookie->getDiscard() && $c->getDiscard()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the new cookie's expiration is further into the future, then
|
||||
// replace the old cookie
|
||||
if ($cookie->getExpires() > $c->getExpires()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the value has changed, we better change it
|
||||
if ($cookie->getValue() !== $c->getValue()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The cookie exists, so no need to continue
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->cookies[] = $cookie;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \ArrayIterator<int, SetCookie>
|
||||
*/
|
||||
public function getIterator(): \ArrayIterator
|
||||
{
|
||||
return new \ArrayIterator(\array_values($this->cookies));
|
||||
}
|
||||
|
||||
public function extractCookies(RequestInterface $request, ResponseInterface $response): void
|
||||
{
|
||||
if ($cookieHeader = $response->getHeader('Set-Cookie')) {
|
||||
$accepted = 0;
|
||||
foreach ($cookieHeader as $cookie) {
|
||||
if (\strlen($cookie) > self::MAX_SET_COOKIE_FIELD_LENGTH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sc = SetCookie::fromString($cookie);
|
||||
$domain = $sc->getDomain();
|
||||
if ($domain === null || $domain === '') {
|
||||
$sc->setDomain($request->getUri()->getHost());
|
||||
$sc->setHostOnly(true);
|
||||
} elseif (\substr($domain, -1) === '.' && '' !== \trim($domain, '.')) {
|
||||
// Keep pure-dot domains rejected by the dot-only fix.
|
||||
$sc->setDomain($request->getUri()->getHost());
|
||||
$sc->setHostOnly(true);
|
||||
} else {
|
||||
$sc->setHostOnly(false);
|
||||
}
|
||||
if (0 !== \strpos($sc->getPath(), '/')) {
|
||||
$sc->setPath($this->getCookiePathFromRequest($request));
|
||||
}
|
||||
if (!$sc->matchesDomain($request->getUri()->getHost())) {
|
||||
continue;
|
||||
}
|
||||
// Note: At this point `$sc->getDomain()` being a public suffix should
|
||||
// be rejected, but we don't want to pull in the full PSL dependency.
|
||||
if ($this->setCookie($sc) && ++$accepted === self::MAX_SET_COOKIE_FIELDS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes cookie path following RFC 6265 section 5.1.4
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
|
||||
*/
|
||||
private function getCookiePathFromRequest(RequestInterface $request): string
|
||||
{
|
||||
$uriPath = $request->getUri()->getPath();
|
||||
if ('' === $uriPath) {
|
||||
return '/';
|
||||
}
|
||||
if (0 !== \strpos($uriPath, '/')) {
|
||||
return '/';
|
||||
}
|
||||
if ('/' === $uriPath) {
|
||||
return '/';
|
||||
}
|
||||
$lastSlashPos = \strrpos($uriPath, '/');
|
||||
if (0 === $lastSlashPos || false === $lastSlashPos) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
return \substr($uriPath, 0, $lastSlashPos);
|
||||
}
|
||||
|
||||
public function withCookieHeader(RequestInterface $request): RequestInterface
|
||||
{
|
||||
$values = [];
|
||||
$headerLength = 8;
|
||||
$uri = $request->getUri();
|
||||
$scheme = $uri->getScheme();
|
||||
$host = $uri->getHost();
|
||||
$path = $uri->getPath() ?: '/';
|
||||
|
||||
foreach ($this->cookies as $cookie) {
|
||||
if ($cookie->getDomain() !== null
|
||||
&& $cookie->matchesPath($path)
|
||||
&& $cookie->matchesDomain($host)
|
||||
&& !$cookie->isExpired()
|
||||
&& (!$cookie->getSecure() || $scheme === 'https')
|
||||
) {
|
||||
$name = (string) $cookie->getName();
|
||||
$value = (string) $cookie->getValue();
|
||||
$separatorLength = $values === [] ? 0 : 2;
|
||||
$valueLength = \strlen($name) + 1 + \strlen($value);
|
||||
if ($headerLength + $separatorLength + $valueLength > self::MAX_COOKIE_HEADER_LENGTH) {
|
||||
break;
|
||||
}
|
||||
|
||||
$values[] = $name.'='.$value;
|
||||
$headerLength += $separatorLength + $valueLength;
|
||||
if (\count($values) === self::MAX_REQUEST_COOKIES) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $values
|
||||
? $request->withHeader('Cookie', \implode('; ', $values))
|
||||
: $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a cookie already exists and the server asks to set it again with a
|
||||
* null value, the cookie must be deleted.
|
||||
*/
|
||||
private function removeCookieIfEmpty(SetCookie $cookie): void
|
||||
{
|
||||
$cookieValue = $cookie->getValue();
|
||||
if (($cookieValue === null || $cookieValue === '') && $cookie->getDomain() !== null) {
|
||||
$this->removeCookie($cookie);
|
||||
}
|
||||
}
|
||||
|
||||
private function removeCookie(SetCookie $cookie): void
|
||||
{
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $stored) use ($cookie): bool {
|
||||
return !($stored->getName() === $cookie->getName()
|
||||
&& $stored->getPath() === $cookie->getPath()
|
||||
&& self::cookieDomainsEqual($stored->getDomain(), $cookie->getDomain())
|
||||
&& $stored->getHostOnly() === $cookie->getHostOnly());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static function cookieDomainsEqual(?string $first, ?string $second): bool
|
||||
{
|
||||
if ($first === null || $second === null) {
|
||||
return $first === $second;
|
||||
}
|
||||
|
||||
if (isset($first[0]) && $first[0] === '.') {
|
||||
$first = \substr($first, 1);
|
||||
}
|
||||
if (isset($second[0]) && $second[0] === '.') {
|
||||
$second = \substr($second, 1);
|
||||
}
|
||||
|
||||
return Psr7\Utils::caselessEquals($first, $second);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Stores HTTP cookies.
|
||||
*
|
||||
* It extracts cookies from HTTP requests, and returns them in HTTP responses.
|
||||
* CookieJarInterface instances automatically expire contained cookies when
|
||||
* necessary. Subclasses are also responsible for storing and retrieving
|
||||
* cookies from a file, database, etc.
|
||||
*
|
||||
* @see https://docs.python.org/2/library/cookielib.html Inspiration
|
||||
*
|
||||
* @extends \IteratorAggregate<SetCookie>
|
||||
*/
|
||||
interface CookieJarInterface extends \Countable, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Create a request with added cookie headers.
|
||||
*
|
||||
* If no matching cookies are found in the cookie jar, then no Cookie
|
||||
* header is added to the request and the same request is returned.
|
||||
*
|
||||
* @param RequestInterface $request Request object to modify.
|
||||
*
|
||||
* @return RequestInterface returns the modified request.
|
||||
*/
|
||||
public function withCookieHeader(RequestInterface $request): RequestInterface;
|
||||
|
||||
/**
|
||||
* Extract cookies from an HTTP response and store them in the CookieJar.
|
||||
*
|
||||
* @param RequestInterface $request Request that was sent
|
||||
* @param ResponseInterface $response Response that was received
|
||||
*/
|
||||
public function extractCookies(RequestInterface $request, ResponseInterface $response): void;
|
||||
|
||||
/**
|
||||
* Sets a cookie in the cookie jar.
|
||||
*
|
||||
* @param SetCookie $cookie Cookie to set.
|
||||
*
|
||||
* @return bool Returns true on success or false on failure
|
||||
*/
|
||||
public function setCookie(SetCookie $cookie): bool;
|
||||
|
||||
/**
|
||||
* Remove cookies currently held in the cookie jar.
|
||||
*
|
||||
* Invoking this method without arguments will empty the whole cookie jar.
|
||||
* If given a $domain argument only cookies belonging to that domain will
|
||||
* be removed. If given a $domain and $path argument, cookies belonging to
|
||||
* the specified path within that domain are removed. If given all three
|
||||
* arguments, then the cookie with the specified name, path and domain is
|
||||
* removed.
|
||||
*
|
||||
* @param string|null $domain Clears cookies matching a domain
|
||||
* @param string|null $path Clears cookies matching a domain and path
|
||||
* @param string|null $name Clears cookies matching a domain, path, and name
|
||||
*/
|
||||
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void;
|
||||
|
||||
/**
|
||||
* Discard all sessions cookies.
|
||||
*
|
||||
* Removes cookies that don't have an expire field or a have a discard
|
||||
* field set to true. To be called when the user agent shuts down according
|
||||
* to RFC 2965.
|
||||
*/
|
||||
public function clearSessionCookies(): void;
|
||||
|
||||
/**
|
||||
* Converts the cookie jar to an array.
|
||||
*/
|
||||
public function toArray(): array;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use GuzzleHttp\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Persists non-session cookies using a JSON formatted file
|
||||
*/
|
||||
class FileCookieJar extends CookieJar
|
||||
{
|
||||
/**
|
||||
* @var string filename
|
||||
*/
|
||||
private $filename;
|
||||
|
||||
/**
|
||||
* @var bool Control whether to persist session cookies or not.
|
||||
*/
|
||||
private $storeSessionCookies;
|
||||
|
||||
/**
|
||||
* Create a new FileCookieJar object
|
||||
*
|
||||
* @param string $cookieFile File to store the cookie data
|
||||
* @param bool $storeSessionCookies Set to true to store session cookies
|
||||
* in the cookie jar.
|
||||
*
|
||||
* @throws \RuntimeException if the file cannot be found or created
|
||||
*/
|
||||
public function __construct(string $cookieFile, bool $storeSessionCookies = false)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->filename = $cookieFile;
|
||||
$this->storeSessionCookies = $storeSessionCookies;
|
||||
|
||||
if (\file_exists($cookieFile)) {
|
||||
$this->load($cookieFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the file when shutting down
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->save($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the cookies to a file.
|
||||
*
|
||||
* @param string $filename File to save
|
||||
*
|
||||
* @throws \RuntimeException if the file cannot be found or created
|
||||
*/
|
||||
public function save(string $filename): void
|
||||
{
|
||||
$json = [];
|
||||
/** @var SetCookie $cookie */
|
||||
foreach ($this as $cookie) {
|
||||
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
|
||||
$data = $cookie->toArray();
|
||||
$data['HostOnly'] = $cookie->getHostOnly();
|
||||
$json[] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
$jsonStr = \json_encode($json);
|
||||
if (\JSON_ERROR_NONE !== \json_last_error()) {
|
||||
throw new InvalidArgumentException('json_encode error: '.\json_last_error_msg());
|
||||
}
|
||||
|
||||
/** @var non-empty-string $jsonStr */
|
||||
if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) {
|
||||
throw new \RuntimeException("Unable to save file {$filename}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cookies from a JSON formatted file.
|
||||
*
|
||||
* Old cookies are kept unless overwritten by newly loaded ones.
|
||||
*
|
||||
* @param string $filename Cookie file to load.
|
||||
*
|
||||
* @throws \RuntimeException if the file cannot be loaded.
|
||||
*/
|
||||
public function load(string $filename): void
|
||||
{
|
||||
$json = \file_get_contents($filename);
|
||||
if (false === $json) {
|
||||
throw new \RuntimeException("Unable to load file {$filename}");
|
||||
}
|
||||
if ($json === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = \json_decode($json, true);
|
||||
if (\JSON_ERROR_NONE !== \json_last_error()) {
|
||||
throw new InvalidArgumentException('json_decode error: '.\json_last_error_msg());
|
||||
}
|
||||
|
||||
if (\is_array($data)) {
|
||||
$cookies = [];
|
||||
foreach ($data as $cookie) {
|
||||
if (!\is_array($cookie) || !\array_key_exists('HostOnly', $cookie) || !\is_bool($cookie['HostOnly'])) {
|
||||
throw new \RuntimeException("Invalid cookie file: {$filename}");
|
||||
}
|
||||
|
||||
$cookies[] = new SetCookie($cookie);
|
||||
}
|
||||
|
||||
foreach ($cookies as $cookie) {
|
||||
$this->setCookie($cookie);
|
||||
}
|
||||
} elseif (\is_scalar($data) && !empty($data)) {
|
||||
throw new \RuntimeException("Invalid cookie file: {$filename}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
/**
|
||||
* Persists cookies in the client session
|
||||
*/
|
||||
class SessionCookieJar extends CookieJar
|
||||
{
|
||||
/**
|
||||
* @var string session key
|
||||
*/
|
||||
private $sessionKey;
|
||||
|
||||
/**
|
||||
* @var bool Control whether to persist session cookies or not.
|
||||
*/
|
||||
private $storeSessionCookies;
|
||||
|
||||
/**
|
||||
* Create a new SessionCookieJar object
|
||||
*
|
||||
* @param string $sessionKey Session key name to store the cookie
|
||||
* data in session
|
||||
* @param bool $storeSessionCookies Set to true to store session cookies
|
||||
* in the cookie jar.
|
||||
*/
|
||||
public function __construct(string $sessionKey, bool $storeSessionCookies = false)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->sessionKey = $sessionKey;
|
||||
$this->storeSessionCookies = $storeSessionCookies;
|
||||
$this->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves cookies to session when shutting down
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save cookies to the client session
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$json = [];
|
||||
/** @var SetCookie $cookie */
|
||||
foreach ($this as $cookie) {
|
||||
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
|
||||
$data = $cookie->toArray();
|
||||
$data['HostOnly'] = $cookie->getHostOnly();
|
||||
$json[] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
$json = \json_encode($json);
|
||||
if (false === $json) {
|
||||
throw new \RuntimeException('Unable to encode cookie data');
|
||||
}
|
||||
|
||||
$_SESSION[$this->sessionKey] = $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the contents of the client session into the data array
|
||||
*/
|
||||
protected function load(): void
|
||||
{
|
||||
if (!isset($_SESSION[$this->sessionKey])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$json = $_SESSION[$this->sessionKey];
|
||||
if (!\is_string($json)) {
|
||||
throw new \RuntimeException('Invalid cookie data');
|
||||
}
|
||||
|
||||
$data = \json_decode($json, true);
|
||||
if (\is_array($data)) {
|
||||
$cookies = [];
|
||||
foreach ($data as $cookie) {
|
||||
if (!\is_array($cookie) || !\array_key_exists('HostOnly', $cookie) || !\is_bool($cookie['HostOnly'])) {
|
||||
throw new \RuntimeException('Invalid cookie data');
|
||||
}
|
||||
|
||||
$cookies[] = new SetCookie($cookie);
|
||||
}
|
||||
|
||||
foreach ($cookies as $cookie) {
|
||||
$this->setCookie($cookie);
|
||||
}
|
||||
} elseif (\is_scalar($data) && \strlen((string) $data)) {
|
||||
throw new \RuntimeException('Invalid cookie data');
|
||||
}
|
||||
}
|
||||
}
|
||||
+597
@@ -0,0 +1,597 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* Set-Cookie object
|
||||
*/
|
||||
class SetCookie
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $defaults = [
|
||||
'Name' => null,
|
||||
'Value' => null,
|
||||
'Domain' => null,
|
||||
'Path' => '/',
|
||||
'Max-Age' => null,
|
||||
'Expires' => null,
|
||||
'Secure' => false,
|
||||
'Discard' => false,
|
||||
'HttpOnly' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array Cookie data
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @var bool Whether this cookie was set without a Domain attribute
|
||||
*/
|
||||
private $hostOnly = false;
|
||||
|
||||
/**
|
||||
* Create a new SetCookie object from a string.
|
||||
*
|
||||
* @param string $cookie Set-Cookie header string
|
||||
*/
|
||||
public static function fromString(string $cookie): self
|
||||
{
|
||||
// Create the default return array
|
||||
$data = self::$defaults;
|
||||
// Explode the cookie string using a series of semicolons
|
||||
$pieces = \array_filter(\array_map(static function (string $piece): string {
|
||||
return \trim($piece, " \n\r\t\0\x0B");
|
||||
}, \explode(';', $cookie)));
|
||||
// The name of the cookie (first kvp) must exist and include an equal sign.
|
||||
if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) {
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
// Add the cookie pieces into the parsed data array
|
||||
foreach ($pieces as $part) {
|
||||
$cookieParts = \explode('=', $part, 2);
|
||||
$key = \trim($cookieParts[0], " \n\r\t\0\x0B");
|
||||
$value = isset($cookieParts[1])
|
||||
? \trim($cookieParts[1], " \n\r\t\0\x0B")
|
||||
: true;
|
||||
|
||||
// Only check for non-cookies when cookies have been found
|
||||
if (!isset($data['Name'])) {
|
||||
$data['Name'] = $key;
|
||||
$data['Value'] = $value;
|
||||
} else {
|
||||
foreach (\array_keys(self::$defaults) as $search) {
|
||||
if (Psr7\Utils::caselessEquals($search, $key)) {
|
||||
if ($search === 'Max-Age') {
|
||||
if (is_numeric($value)) {
|
||||
$data[$search] = (int) $value;
|
||||
}
|
||||
} elseif ($search === 'Secure' || $search === 'Discard' || $search === 'HttpOnly') {
|
||||
if ($value) {
|
||||
$data[$search] = true;
|
||||
}
|
||||
} else {
|
||||
$data[$search] = $value;
|
||||
}
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
if (Psr7\Utils::caselessEquals('HostOnly', $key)) {
|
||||
continue;
|
||||
}
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data Array of cookie data provided by a Cookie parser
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = self::$defaults;
|
||||
|
||||
if (\array_key_exists('HostOnly', $data)) {
|
||||
if (!\is_bool($data['HostOnly'])) {
|
||||
throw new \InvalidArgumentException('Cookie field "HostOnly" must be a boolean');
|
||||
}
|
||||
$this->setHostOnly($data['HostOnly']);
|
||||
unset($data['HostOnly']);
|
||||
}
|
||||
|
||||
if (isset($data['Name'])) {
|
||||
$this->setName($data['Name']);
|
||||
}
|
||||
|
||||
if (isset($data['Value'])) {
|
||||
$this->setValue($data['Value']);
|
||||
}
|
||||
|
||||
if (isset($data['Domain'])) {
|
||||
$this->setDomain($data['Domain']);
|
||||
}
|
||||
|
||||
if (isset($data['Path'])) {
|
||||
$this->setPath($data['Path']);
|
||||
}
|
||||
|
||||
if (isset($data['Max-Age'])) {
|
||||
$this->setMaxAge($data['Max-Age']);
|
||||
}
|
||||
|
||||
if (isset($data['Expires'])) {
|
||||
$this->setExpires($data['Expires']);
|
||||
}
|
||||
|
||||
if (isset($data['Secure'])) {
|
||||
$this->setSecure($data['Secure']);
|
||||
}
|
||||
|
||||
if (isset($data['Discard'])) {
|
||||
$this->setDiscard($data['Discard']);
|
||||
}
|
||||
|
||||
if (isset($data['HttpOnly'])) {
|
||||
$this->setHttpOnly($data['HttpOnly']);
|
||||
}
|
||||
|
||||
// Set the remaining values that don't have extra validation logic
|
||||
foreach (array_diff(array_keys($data), array_keys(self::$defaults)) as $key) {
|
||||
$this->data[$key] = $data[$key];
|
||||
}
|
||||
|
||||
// Extract the Expires value and turn it into a UNIX timestamp if needed
|
||||
$maxAge = $this->getMaxAge();
|
||||
if (!$this->getExpires() && $maxAge !== null) {
|
||||
// Calculate the Expires date
|
||||
$this->setExpires(self::maxAgeToExpires($maxAge, \time()));
|
||||
} elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) {
|
||||
$this->setExpires($expires);
|
||||
}
|
||||
}
|
||||
|
||||
private static function maxAgeToExpires(int $maxAge, int $now): int
|
||||
{
|
||||
if ($maxAge <= 0) {
|
||||
return $now - 1;
|
||||
}
|
||||
if ($maxAge > \PHP_INT_MAX - $now) {
|
||||
return \PHP_INT_MAX;
|
||||
}
|
||||
|
||||
return $now + $maxAge;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$str = $this->data['Name'].'='.($this->data['Value'] ?? '').'; ';
|
||||
foreach ($this->data as $k => $v) {
|
||||
if ($k === 'Domain' && $this->getHostOnly()) {
|
||||
continue;
|
||||
}
|
||||
if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
|
||||
if ($k === 'Expires') {
|
||||
$str .= 'Expires='.\gmdate('D, d M Y H:i:s \G\M\T', $v).'; ';
|
||||
} else {
|
||||
$str .= ($v === true ? $k : "{$k}={$v}").'; ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \rtrim($str, '; ');
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = $this->data;
|
||||
if ($this->getHostOnly()) {
|
||||
$data['HostOnly'] = true;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->data['Name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cookie name.
|
||||
*
|
||||
* @param string $name Cookie name
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->data['Name'] = (string) $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie value.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->data['Value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cookie value.
|
||||
*
|
||||
* @param string $value Cookie value
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->data['Value'] = (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the domain.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->data['Domain'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the domain of the cookie.
|
||||
*
|
||||
* @param string|null $domain
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->data['Domain'] = null === $domain ? null : (string) $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this cookie is scoped to the origin host only.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getHostOnly()
|
||||
{
|
||||
return $this->hostOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this cookie is scoped to the origin host only.
|
||||
*
|
||||
* @param bool $hostOnly Set to true for host-only cookies
|
||||
*/
|
||||
public function setHostOnly(bool $hostOnly): void
|
||||
{
|
||||
$this->hostOnly = $hostOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->data['Path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path of the cookie.
|
||||
*
|
||||
* @param string $path Path of the cookie
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->data['Path'] = (string) $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum lifetime of the cookie in seconds.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMaxAge()
|
||||
{
|
||||
return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max-age of the cookie.
|
||||
*
|
||||
* @param int|null $maxAge Max age of the cookie in seconds
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* The UNIX timestamp when the cookie Expires.
|
||||
*
|
||||
* @return string|int|null
|
||||
*/
|
||||
public function getExpires()
|
||||
{
|
||||
return $this->data['Expires'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the unix timestamp for which the cookie will expire.
|
||||
*
|
||||
* @param int|string|null $timestamp Unix timestamp or any English textual datetime description.
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is a secure cookie.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getSecure()
|
||||
{
|
||||
return $this->data['Secure'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the cookie is secure.
|
||||
*
|
||||
* @param bool $secure Set to true or false if secure
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->data['Secure'] = (bool) $secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is a session cookie.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getDiscard()
|
||||
{
|
||||
return $this->data['Discard'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this is a session cookie.
|
||||
*
|
||||
* @param bool $discard Set to true or false if this is a session cookie
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->data['Discard'] = (bool) $discard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is an HTTP only cookie.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getHttpOnly()
|
||||
{
|
||||
return $this->data['HttpOnly'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this is an HTTP only cookie.
|
||||
*
|
||||
* @param bool $httpOnly Set to true or false if this is HTTP only
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->data['HttpOnly'] = (bool) $httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie matches a path value.
|
||||
*
|
||||
* A request-path path-matches a given cookie-path if at least one of
|
||||
* the following conditions holds:
|
||||
*
|
||||
* - The cookie-path and the request-path are identical.
|
||||
* - The cookie-path is a prefix of the request-path, and the last
|
||||
* character of the cookie-path is %x2F ("/").
|
||||
* - The cookie-path is a prefix of the request-path, and the first
|
||||
* character of the request-path that is not included in the cookie-
|
||||
* path is a %x2F ("/") character.
|
||||
*
|
||||
* @param string $requestPath Path to check against
|
||||
*/
|
||||
public function matchesPath(string $requestPath): bool
|
||||
{
|
||||
$cookiePath = $this->getPath();
|
||||
|
||||
// Match on exact matches or when path is the default empty "/"
|
||||
if ($cookiePath === '/' || $cookiePath === $requestPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure that the cookie-path is a prefix of the request path.
|
||||
if (0 !== \strpos($requestPath, $cookiePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match if the last character of the cookie-path is "/"
|
||||
if (\substr($cookiePath, -1, 1) === '/') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Match if the first character not included in cookie path is "/"
|
||||
return \substr($requestPath, \strlen($cookiePath), 1) === '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie matches a domain value.
|
||||
*
|
||||
* @param string $domain Domain to check against
|
||||
*/
|
||||
public function matchesDomain(string $domain): bool
|
||||
{
|
||||
$cookieDomain = $this->getDomain();
|
||||
if (null === $cookieDomain) {
|
||||
return !$this->getHostOnly();
|
||||
}
|
||||
|
||||
if ($this->getHostOnly()) {
|
||||
return Psr7\Utils::asciiToLower($domain) === Psr7\Utils::asciiToLower($cookieDomain);
|
||||
}
|
||||
|
||||
// Remove the leading '.' as per spec in RFC 6265.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
|
||||
$cookieDomain = Psr7\Utils::asciiToLower($cookieDomain);
|
||||
if ($cookieDomain !== '' && $cookieDomain[0] === '.') {
|
||||
/** @var string */
|
||||
$cookieDomain = \substr($cookieDomain, 1);
|
||||
}
|
||||
if ('' === $cookieDomain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$domain = Psr7\Utils::asciiToLower($domain);
|
||||
if ($domain === $cookieDomain) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// IP literals and numeric hosts are exact-match-only per RFC 6265.
|
||||
// Only the exact match above may succeed for those cookie domains.
|
||||
if (self::isIpAddressOrNumericHost($cookieDomain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Matching the subdomain according to RFC 6265.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
|
||||
if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) \preg_match('/\.'.\preg_quote($cookieDomain, '/').'$/D', $domain);
|
||||
}
|
||||
|
||||
private static function isIpAddressOrNumericHost(string $host): bool
|
||||
{
|
||||
// Strip one root dot before detection so trailing-dot numeric hosts
|
||||
// still cannot be matched by subdomains.
|
||||
if ($host !== '' && \str_ends_with($host, '.')) {
|
||||
$host = \substr($host, 0, -1);
|
||||
}
|
||||
|
||||
if (\str_starts_with($host, '[') && \str_ends_with($host, ']')) {
|
||||
$host = \substr($host, 1, -1);
|
||||
}
|
||||
|
||||
if (\filter_var($host, \FILTER_VALIDATE_IP) !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Public DNS names do not have an all-numeric rightmost label; treat
|
||||
// those private/internal hosts as exact-match-only too.
|
||||
$labels = \explode('.', $host);
|
||||
$last = (string) \end($labels);
|
||||
|
||||
return $last !== '' && \ctype_digit($last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie is expired.
|
||||
*/
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->getExpires() !== null && \time() > $this->getExpires();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie is valid according to RFC 6265.
|
||||
*
|
||||
* @return bool|string Returns true if valid or an error message if invalid
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
$name = $this->getName();
|
||||
if ($name === '') {
|
||||
return 'The cookie name must not be empty';
|
||||
}
|
||||
|
||||
// Check if any of the invalid characters are present in the cookie name
|
||||
if (\preg_match('/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', $name) !== 0) {
|
||||
return 'Cookie name must not contain invalid characters: ASCII '
|
||||
.'Control characters (0-31;127), space, tab and the '
|
||||
.'following characters: ()<>@,;:\"/?={}';
|
||||
}
|
||||
|
||||
// Value must not be null. 0 and empty string are valid. Empty strings
|
||||
// are technically against RFC 6265, but known to happen in the wild.
|
||||
$value = $this->getValue();
|
||||
if ($value === null) {
|
||||
return 'The cookie value must not be empty';
|
||||
}
|
||||
|
||||
// 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 && '' === \ltrim(\trim($domain, " \n\r\t\0\x0B"), '.'))) {
|
||||
return 'The cookie domain must not be empty';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Exception when an HTTP error occurs (4xx or 5xx error)
|
||||
*/
|
||||
class BadResponseException extends RequestException
|
||||
{
|
||||
public function __construct(
|
||||
string $message,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
?\Throwable $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
parent::__construct($message, $request, $response, $previous, $handlerContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Current exception and the ones that extend it will always have a response.
|
||||
*/
|
||||
public function hasResponse(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function narrows the return type from the parent class and does not allow it to be nullable.
|
||||
*/
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
/** @var ResponseInterface */
|
||||
return parent::getResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
/**
|
||||
* Exception when a client error is encountered (4xx codes)
|
||||
*/
|
||||
class ClientException extends BadResponseException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Exception thrown when a connection cannot be established.
|
||||
*/
|
||||
class ConnectException extends TransferException implements NetworkExceptionInterface
|
||||
{
|
||||
/**
|
||||
* @var RequestInterface
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $handlerContext;
|
||||
|
||||
public function __construct(
|
||||
string $message,
|
||||
RequestInterface $request,
|
||||
?\Throwable $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
parent::__construct($message, 0, $previous);
|
||||
$this->request = $request;
|
||||
$this->handlerContext = $handlerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request that caused the exception
|
||||
*/
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contextual information about the error from the underlying handler.
|
||||
*
|
||||
* The contents of this array will vary depending on which handler you are
|
||||
* using. It may also be just an empty array. Relying on this data will
|
||||
* couple you to a specific handler, but can give more debug information
|
||||
* when needed.
|
||||
*/
|
||||
public function getHandlerContext(): array
|
||||
{
|
||||
return $this->handlerContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
|
||||
interface GuzzleException extends ClientExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
final class InvalidArgumentException extends \InvalidArgumentException implements GuzzleException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use GuzzleHttp\BodySummarizer;
|
||||
use GuzzleHttp\BodySummarizerInterface;
|
||||
use Psr\Http\Client\RequestExceptionInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* HTTP Request exception
|
||||
*/
|
||||
class RequestException extends TransferException implements RequestExceptionInterface
|
||||
{
|
||||
/**
|
||||
* @var RequestInterface
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var ResponseInterface|null
|
||||
*/
|
||||
private $response;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $handlerContext;
|
||||
|
||||
public function __construct(
|
||||
string $message,
|
||||
RequestInterface $request,
|
||||
?ResponseInterface $response = null,
|
||||
?\Throwable $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
// Set the code of the exception if the response is set and not future.
|
||||
$code = $response ? $response->getStatusCode() : 0;
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->handlerContext = $handlerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new exception with a normalized error message
|
||||
*
|
||||
* @param RequestInterface $request Request sent
|
||||
* @param ResponseInterface $response Response received
|
||||
* @param \Throwable|null $previous Previous exception
|
||||
* @param array $handlerContext Optional handler context
|
||||
* @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer
|
||||
*/
|
||||
public static function create(
|
||||
RequestInterface $request,
|
||||
?ResponseInterface $response = null,
|
||||
?\Throwable $previous = null,
|
||||
array $handlerContext = [],
|
||||
?BodySummarizerInterface $bodySummarizer = null
|
||||
): self {
|
||||
if (!$response) {
|
||||
return new self(
|
||||
'Error completing request',
|
||||
$request,
|
||||
null,
|
||||
$previous,
|
||||
$handlerContext
|
||||
);
|
||||
}
|
||||
|
||||
$level = (int) \floor($response->getStatusCode() / 100);
|
||||
if ($level === 4) {
|
||||
$label = 'Client error';
|
||||
$className = ClientException::class;
|
||||
} elseif ($level === 5) {
|
||||
$label = 'Server error';
|
||||
$className = ServerException::class;
|
||||
} else {
|
||||
$label = 'Unsuccessful request';
|
||||
$className = __CLASS__;
|
||||
}
|
||||
|
||||
$uri = \GuzzleHttp\Psr7\Utils::redactUserInfo($request->getUri());
|
||||
|
||||
// Client Error: `GET /` resulted in a `404 Not Found` response:
|
||||
// <html> ... (truncated)
|
||||
$message = \sprintf(
|
||||
'%s: `%s %s` resulted in a `%s %s` response',
|
||||
$label,
|
||||
$request->getMethod(),
|
||||
$uri->__toString(),
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
);
|
||||
|
||||
$summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response);
|
||||
|
||||
if ($summary !== null) {
|
||||
$message .= ":\n{$summary}\n";
|
||||
}
|
||||
|
||||
return new $className($message, $request, $response, $previous, $handlerContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request that caused the exception
|
||||
*/
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated response
|
||||
*/
|
||||
public function getResponse(): ?ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a response was received
|
||||
*/
|
||||
public function hasResponse(): bool
|
||||
{
|
||||
return $this->response !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contextual information about the error from the underlying handler.
|
||||
*
|
||||
* The contents of this array will vary depending on which handler you are
|
||||
* using. It may also be just an empty array. Relying on this data will
|
||||
* couple you to a specific handler, but can give more debug information
|
||||
* when needed.
|
||||
*/
|
||||
public function getHandlerContext(): array
|
||||
{
|
||||
return $this->handlerContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
/**
|
||||
* Exception when a server error is encountered (5xx codes)
|
||||
*/
|
||||
class ServerException extends BadResponseException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
class TooManyRedirectsException extends RequestException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
class TransferException extends \RuntimeException implements GuzzleException
|
||||
{
|
||||
}
|
||||
+2585
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
interface CurlFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Creates a cURL handle resource.
|
||||
*
|
||||
* @param RequestInterface $request Request
|
||||
* @param array $options Transfer options
|
||||
*
|
||||
* @throws \RuntimeException when an option cannot be applied
|
||||
*/
|
||||
public function create(RequestInterface $request, array $options): EasyHandle;
|
||||
|
||||
/**
|
||||
* Release an easy handle, allowing it to be reused or closed.
|
||||
*
|
||||
* This function must call unset on the easy handle's "handle" property.
|
||||
*/
|
||||
public function release(EasyHandle $easy): void;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\TransportSharing;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* HTTP handler that uses cURL easy handles as a transport layer.
|
||||
*
|
||||
* When using the CurlHandler, custom curl options can be specified as an
|
||||
* associative array of curl option constants mapping to values in the
|
||||
* **curl** key of the "client" key of the request.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class CurlHandler
|
||||
{
|
||||
private const KNOWN_CONSTRUCTOR_OPTIONS = [
|
||||
'handle_factory' => true,
|
||||
'transport_sharing' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var CurlFactoryInterface
|
||||
*/
|
||||
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, transport_sharing?: mixed} $options Array of options to use with the handler
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
foreach ($options as $name => $_) {
|
||||
if (!isset(self::KNOWN_CONSTRUCTOR_OPTIONS[$name])) {
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.14', \sprintf('The "%s" CurlHandler constructor option is unknown; guzzlehttp/guzzle 8.0 will reject unknown constructor options.', (string) $name));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
: new CurlFactory(3);
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
if (isset($options['delay'])) {
|
||||
\usleep($options['delay'] * 1000);
|
||||
}
|
||||
|
||||
// A Multiplexing::NONE request option holds unconditionally here:
|
||||
// transport sharing never shares the connection cache on this
|
||||
// branch, and nothing else executes during the blocking curl_exec(),
|
||||
// so the transfer cannot share its connection with a concurrent
|
||||
// transfer.
|
||||
$easy = $this->factory->create($request, $options);
|
||||
\curl_exec($easy->handle);
|
||||
$easy->errno = \curl_errno($easy->handle);
|
||||
|
||||
return CurlFactory::finish($this, $easy, $this->factory);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\TransportSharing;
|
||||
|
||||
/**
|
||||
* @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,
|
||||
\get_debug_type($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($mode);
|
||||
$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(string $mode): array
|
||||
{
|
||||
CurlVersion::ensureHandlerSharingSupported();
|
||||
|
||||
if ($mode === TransportSharing::HANDLER_REQUIRE) {
|
||||
CurlVersion::ensureSslSessionSharingSupported();
|
||||
}
|
||||
|
||||
$locks = [
|
||||
self::requireCurlConstant('CURL_LOCK_DATA_DNS'),
|
||||
];
|
||||
|
||||
if (CurlVersion::supportsSslSessionSharing()) {
|
||||
$locks[] = self::requireCurlConstant('CURL_LOCK_DATA_SSL_SESSION');
|
||||
}
|
||||
|
||||
return $locks;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CurlVersion
|
||||
{
|
||||
private const MIN_VERSION = '7.21.2';
|
||||
|
||||
private const TLS_12_VERSION = '7.34.0';
|
||||
|
||||
private const TLS_13_VERSION = '7.52.0';
|
||||
|
||||
private const CONNECTION_CAP_VERSION = '7.30.0';
|
||||
|
||||
// CURLOPT_PIPEWAIT exists since libcurl 7.43.0, and multi handles have
|
||||
// multiplexed by default since 7.62.0 - but a 7.65.0-7.65.1 regression
|
||||
// dropped that default, which 7.65.2 restored, so 7.65.2 is the floor at
|
||||
// which PIPEWAIT is reliably effective.
|
||||
private const MULTIPLEX_VERSION = '7.65.2';
|
||||
|
||||
// libcurl's connection matcher refuses to hand a transfer wanting
|
||||
// HTTP/1.x a pooled connection that already negotiated HTTP/2 or newer
|
||||
// from 7.77.0: ConnectionExists() in lib/url.c gained the check between
|
||||
// the 7.76.0 and 7.77.0 releases. The HTTP/2 branch of the check
|
||||
// regressed to a debug log in 8.11.0 (curl commit 433d730) and was
|
||||
// restored in 8.13.0 via the negotiation mask (curl commit db72b8d), so
|
||||
// 8.11.0 through 8.12.1 are vulnerable again.
|
||||
private const HTTP_VERSION_REUSE_MATCH_VERSION = '7.77.0';
|
||||
|
||||
private const HTTP_VERSION_REUSE_MATCH_REGRESSION = '8.11.0';
|
||||
|
||||
private const HTTP_VERSION_REUSE_MATCH_RESTORED = '8.13.0';
|
||||
|
||||
// CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE restricts the ALPN offer to h2 only
|
||||
// since libcurl 8.10.0, and connection reuse matching stopped handing
|
||||
// lower-version connections to prior-knowledge transfers in 8.14.0; below
|
||||
// that, a required request could silently be sent over a reused HTTP/1.1
|
||||
// connection.
|
||||
private const REQUIRED_MULTIPLEX_VERSION = '8.14.0';
|
||||
|
||||
// curl 7.52.0 introduced HTTPS proxy support, advertised by a feature bit
|
||||
// (a build can meet the version yet lack the feature). Earlier libcurl
|
||||
// mishandles an https:// proxy: before 7.50.2 it silently downgrades to a
|
||||
// plaintext HTTP proxy, and 7.50.2 through 7.51 reject it at connect time.
|
||||
private const HTTPS_PROXY_VERSION = '7.52.0';
|
||||
|
||||
private const HANDLER_SHARING_VERSION = '7.35.0';
|
||||
|
||||
private const SSL_SESSION_SHARING_VERSION = '8.6.0';
|
||||
|
||||
// curl 7.57.0 added share-handle connection caches through
|
||||
// CURL_LOCK_DATA_CONNECT; older share objects can only hold DNS, TLS
|
||||
// session, and cookie data, never connections.
|
||||
private const SHARE_CONNECTION_CACHE_VERSION = '7.57.0';
|
||||
|
||||
// curl 7.83.1 added proxy TLS-SRP to the connection-reuse match
|
||||
// (CVE-2022-27782); the proxy client certificate was matched from 7.52.0,
|
||||
// so proxy TLS credentials are trusted from 7.83.1 onwards.
|
||||
private const PROXY_TLS_CREDENTIAL_REUSE_VERSION = '7.83.1';
|
||||
|
||||
// curl 8.19.0 fixed proxy tunnel reuse after credential changes
|
||||
// (CVE-2026-3784), but related proxy credential leak flaws were only
|
||||
// fixed in 8.20.0, so connection reuse is trusted from 8.20.0 onwards.
|
||||
private const PROXY_CREDENTIAL_REUSE_VERSION = '8.20.0';
|
||||
|
||||
// curl 7.69.0 started comparing SOCKS proxy credentials when matching
|
||||
// connections for reuse (curl #4835); older libcurl matches a SOCKS proxy
|
||||
// by type, host, and port only.
|
||||
private const SOCKS_PROXY_CREDENTIAL_REUSE_VERSION = '7.69.0';
|
||||
|
||||
private const PROXY_HEADER_SEPARATION_VERSION = '7.37.0';
|
||||
|
||||
/**
|
||||
* @var array{version: string, features: int}|false|null
|
||||
*/
|
||||
private static $versionInfo;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function supportsCurlHandler(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return $version !== null && \version_compare($version, self::MIN_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsTls12(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return self::supportsSsl()
|
||||
&& \defined('CURL_SSLVERSION_TLSv1_2')
|
||||
&& $version !== null
|
||||
&& \version_compare($version, self::TLS_12_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsTls13(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return self::supportsSsl()
|
||||
&& \defined('CURL_SSLVERSION_TLSv1_3')
|
||||
&& $version !== null
|
||||
&& \version_compare($version, self::TLS_13_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsHttp2(): bool
|
||||
{
|
||||
$versionInfo = self::getVersionInfo();
|
||||
|
||||
return self::supportsTls12()
|
||||
&& \defined('CURL_VERSION_HTTP2')
|
||||
&& $versionInfo !== null
|
||||
&& 0 !== (\CURL_VERSION_HTTP2 & $versionInfo['features']);
|
||||
}
|
||||
|
||||
public static function supportsMultiplex(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return \defined('CURLOPT_PIPEWAIT')
|
||||
&& $version !== null
|
||||
&& \version_compare($version, self::MULTIPLEX_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsHttpVersionReuseMatching(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
if ($version === null || \version_compare($version, self::HTTP_VERSION_REUSE_MATCH_VERSION, '<')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \version_compare($version, self::HTTP_VERSION_REUSE_MATCH_REGRESSION, '<')
|
||||
|| \version_compare($version, self::HTTP_VERSION_REUSE_MATCH_RESTORED, '>=');
|
||||
}
|
||||
|
||||
public static function supportsConnectionCaps(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return \defined('CURLMOPT_MAX_HOST_CONNECTIONS')
|
||||
&& \defined('CURLMOPT_MAX_TOTAL_CONNECTIONS')
|
||||
&& $version !== null
|
||||
&& \version_compare($version, self::CONNECTION_CAP_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function ensureConnectionCapsSupported(string $option): void
|
||||
{
|
||||
if (self::supportsConnectionCaps()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'The "%s" option requires PHP cURL support for CURLMOPT_MAX_HOST_CONNECTIONS and CURLMOPT_MAX_TOTAL_CONNECTIONS with libcurl %s or newer.',
|
||||
$option,
|
||||
self::CONNECTION_CAP_VERSION
|
||||
));
|
||||
}
|
||||
|
||||
public static function supportsRequiredMultiplex(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return \defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')
|
||||
&& $version !== null
|
||||
&& self::supportsHttp2()
|
||||
&& \version_compare($version, self::REQUIRED_MULTIPLEX_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsHttpsProxy(): bool
|
||||
{
|
||||
$versionInfo = self::getVersionInfo();
|
||||
|
||||
// CURL_VERSION_HTTPS_PROXY is not defined on every supported PHP
|
||||
// version; fall back to the curl.h bit value.
|
||||
$httpsProxyFeature = \defined('CURL_VERSION_HTTPS_PROXY') ? \CURL_VERSION_HTTPS_PROXY : (1 << 21);
|
||||
|
||||
return $versionInfo !== null
|
||||
&& \version_compare($versionInfo['version'], self::HTTPS_PROXY_VERSION, '>=')
|
||||
&& 0 !== ($httpsProxyFeature & $versionInfo['features']);
|
||||
}
|
||||
|
||||
public static function supportsNtlm(): bool
|
||||
{
|
||||
$versionInfo = self::getVersionInfo();
|
||||
|
||||
// CURL_VERSION_NTLM is not defined on every supported PHP version; fall
|
||||
// back to the curl.h bit value.
|
||||
$ntlmFeature = \defined('CURL_VERSION_NTLM') ? \CURL_VERSION_NTLM : (1 << 4);
|
||||
|
||||
return \defined('CURLAUTH_NTLM')
|
||||
&& $versionInfo !== null
|
||||
&& 0 !== ($ntlmFeature & $versionInfo['features']);
|
||||
}
|
||||
|
||||
public static function supportsHandlerSharing(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return $version !== null && \version_compare($version, self::HANDLER_SHARING_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function ensureHandlerSharingSupported(): void
|
||||
{
|
||||
if (!self::supportsHandlerSharing()) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'The "transport_sharing" option requires libcurl %s or higher for cURL share handles.',
|
||||
self::HANDLER_SHARING_VERSION
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public static function supportsSslSessionSharing(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return self::supportsSsl()
|
||||
&& $version !== null
|
||||
&& \version_compare($version, self::SSL_SESSION_SHARING_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function ensureSslSessionSharingSupported(): void
|
||||
{
|
||||
if (!self::supportsSslSessionSharing()) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'The "transport_sharing" option requires libcurl %s or higher with SSL support for SSL session sharing.',
|
||||
self::SSL_SESSION_SHARING_VERSION
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public static function supportsShareConnectionCaches(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
// An undetectable libcurl version is treated as capable so the
|
||||
// opaque share safeguards fail closed.
|
||||
return $version === null || \version_compare($version, self::SHARE_CONNECTION_CACHE_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsProxyTlsCredentialAwareConnectionReuse(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return $version !== null
|
||||
&& \version_compare($version, self::PROXY_TLS_CREDENTIAL_REUSE_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsProxyCredentialAwareConnectionReuse(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return $version !== null
|
||||
&& \version_compare($version, self::PROXY_CREDENTIAL_REUSE_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsSocksProxyCredentialAwareConnectionReuse(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return $version !== null
|
||||
&& \version_compare($version, self::SOCKS_PROXY_CREDENTIAL_REUSE_VERSION, '>=');
|
||||
}
|
||||
|
||||
public static function supportsProxyHeaderSeparation(): bool
|
||||
{
|
||||
$version = self::getVersion();
|
||||
|
||||
return $version !== null
|
||||
&& \version_compare($version, self::PROXY_HEADER_SEPARATION_VERSION, '>=')
|
||||
&& \defined('CURLOPT_PROXYHEADER')
|
||||
&& \defined('CURLOPT_HEADEROPT')
|
||||
&& \defined('CURLHEADER_SEPARATE');
|
||||
}
|
||||
|
||||
private static function supportsSsl(): bool
|
||||
{
|
||||
$versionInfo = self::getVersionInfo();
|
||||
|
||||
return \defined('CURL_VERSION_SSL')
|
||||
&& $versionInfo !== null
|
||||
&& 0 !== (\CURL_VERSION_SSL & $versionInfo['features']);
|
||||
}
|
||||
|
||||
public static function getVersion(): ?string
|
||||
{
|
||||
$versionInfo = self::getVersionInfo();
|
||||
|
||||
return $versionInfo === null ? null : $versionInfo['version'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{version: string, features: int}|null
|
||||
*/
|
||||
private static function getVersionInfo(): ?array
|
||||
{
|
||||
if (self::$versionInfo === null) {
|
||||
if (!\function_exists('curl_version')) {
|
||||
self::$versionInfo = false;
|
||||
} else {
|
||||
$versionInfo = \curl_version();
|
||||
self::$versionInfo = \is_array($versionInfo)
|
||||
&& isset($versionInfo['version'], $versionInfo['features'])
|
||||
&& \is_string($versionInfo['version'])
|
||||
&& \is_int($versionInfo['features'])
|
||||
? [
|
||||
'version' => $versionInfo['version'],
|
||||
'features' => $versionInfo['features'],
|
||||
]
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$versionInfo === false ? null : self::$versionInfo;
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Represents a cURL easy handle and the data it populates.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class EasyHandle
|
||||
{
|
||||
/**
|
||||
* @var resource|\CurlHandle cURL resource
|
||||
*/
|
||||
public $handle;
|
||||
|
||||
/**
|
||||
* @var StreamInterface Where data is being written
|
||||
*/
|
||||
public $sink;
|
||||
|
||||
/**
|
||||
* @var array Received HTTP headers so far
|
||||
*/
|
||||
public $headers = [];
|
||||
|
||||
/**
|
||||
* @var array Valid trailer lines, retained only when an on_trailers
|
||||
* callback is configured
|
||||
*/
|
||||
public $trailers = [];
|
||||
|
||||
/**
|
||||
* @var bool Whether this handle was configured with CURLOPT_PIPEWAIT
|
||||
*/
|
||||
public $usesPipewait = false;
|
||||
|
||||
/**
|
||||
* @var ResponseInterface|null Received response (if any)
|
||||
*/
|
||||
public $response;
|
||||
|
||||
/**
|
||||
* @var RequestInterface Request being sent
|
||||
*/
|
||||
public $request;
|
||||
|
||||
/**
|
||||
* @var array Request options
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* @var int cURL error number (if any)
|
||||
*/
|
||||
public $errno = 0;
|
||||
|
||||
/**
|
||||
* @var string|null Effective CURLOPT_PROXY value the handle was created with (if any)
|
||||
*/
|
||||
public $effectiveProxy;
|
||||
|
||||
/**
|
||||
* Proxy tunnel or SOCKS proxy section signature for connection-reuse
|
||||
* isolation, or null when the request does not require sectioning.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $proxyTunnelSignature;
|
||||
|
||||
/**
|
||||
* @var \Throwable|null Exception during on_headers (if any)
|
||||
*/
|
||||
public $onHeadersException;
|
||||
|
||||
/**
|
||||
* @var \Throwable|null Exception during createResponse (if any)
|
||||
*/
|
||||
public $createResponseException;
|
||||
|
||||
/**
|
||||
* Attach a response to the easy handle based on the received headers.
|
||||
*
|
||||
* @throws \RuntimeException if no headers have been received or the first
|
||||
* header line is invalid.
|
||||
*/
|
||||
public function createResponse(): void
|
||||
{
|
||||
$this->response = null;
|
||||
|
||||
[$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($this->headers);
|
||||
|
||||
$normalizedKeys = Utils::normalizeHeaderKeys($headers);
|
||||
|
||||
if (isset($this->options['decode_content']) && $this->options['decode_content'] !== false && isset($normalizedKeys['content-encoding'])) {
|
||||
$headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
|
||||
unset($headers[$normalizedKeys['content-encoding']]);
|
||||
if (isset($normalizedKeys['content-length'])) {
|
||||
$headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
|
||||
|
||||
$bodyLength = (int) $this->sink->getSize();
|
||||
if ($bodyLength) {
|
||||
$headers[$normalizedKeys['content-length']] = [(string) $bodyLength];
|
||||
} else {
|
||||
unset($headers[$normalizedKeys['content-length']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach a response to the easy handle with the parsed headers.
|
||||
$this->response = new Response(
|
||||
$status,
|
||||
$headers,
|
||||
$this->sink,
|
||||
$ver,
|
||||
$reason
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
$msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: '.$name;
|
||||
throw new \BadMethodCallException($msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Utils;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HeaderProcessor
|
||||
{
|
||||
/**
|
||||
* Returns the HTTP version, status code, reason phrase, and headers.
|
||||
*
|
||||
* @param string[] $headers
|
||||
*
|
||||
* @return array{0:string, 1:int, 2:?string, 3:array}
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function parseHeaders(array $headers): array
|
||||
{
|
||||
if ($headers === []) {
|
||||
throw new \RuntimeException('Expected a non-empty array of header data');
|
||||
}
|
||||
|
||||
$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) {
|
||||
throw new \RuntimeException('HTTP version missing from header data');
|
||||
}
|
||||
|
||||
$status = $parts[1] ?? null;
|
||||
|
||||
if ($status === null) {
|
||||
throw new \RuntimeException('HTTP status code missing from header data');
|
||||
}
|
||||
|
||||
if (!\preg_match('/^\d{3}$/D', $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)];
|
||||
}
|
||||
|
||||
public static function isStatusLineCandidate(string $line): bool
|
||||
{
|
||||
return \preg_match('/^HTTP\/[0-9]+(?:\.[0-9]+)? [0-9]{3}(?: [^\r\n]*)?(?:\r\n|\r|\n)?$/iD', $line) === 1;
|
||||
}
|
||||
|
||||
public static function isValidHeaderFieldLine(string $line): bool
|
||||
{
|
||||
$parts = \explode(':', $line, 2);
|
||||
|
||||
if (!isset($parts[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $parts[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*(?:\r\n|\r|\n)?$/D', \trim($parts[1], " \t")) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-list<string> $headers
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function getLastHeaderBlock(array $headers): array
|
||||
{
|
||||
$lastStatusLine = 0;
|
||||
|
||||
foreach ($headers as $index => $line) {
|
||||
if (self::isStatusLineCandidate($line)) {
|
||||
$lastStatusLine = $index;
|
||||
}
|
||||
}
|
||||
|
||||
return \array_slice($headers, $lastStatusLine);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Handler that returns responses or throw exceptions from a queue.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class MockHandler implements \Countable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $queue = [];
|
||||
|
||||
/**
|
||||
* @var RequestInterface|null
|
||||
*/
|
||||
private $lastRequest;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lastOptions = [];
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $onFulfilled;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $onRejected;
|
||||
|
||||
/**
|
||||
* Creates a new MockHandler that uses the default handler stack list of
|
||||
* middlewares.
|
||||
*
|
||||
* @param array|null $queue Array of responses, callables, or exceptions.
|
||||
* @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
|
||||
* @param callable|null $onRejected Callback to invoke when the return value is rejected.
|
||||
*/
|
||||
public static function createWithMiddleware(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null): HandlerStack
|
||||
{
|
||||
return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
|
||||
}
|
||||
|
||||
/**
|
||||
* The passed in value must be an array of
|
||||
* {@see ResponseInterface} objects, Exceptions,
|
||||
* callables, or Promises.
|
||||
*
|
||||
* @param array<int, mixed>|null $queue The parameters to be passed to the append function, as an indexed array.
|
||||
* @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
|
||||
* @param callable|null $onRejected Callback to invoke when the return value is rejected.
|
||||
*/
|
||||
public function __construct(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null)
|
||||
{
|
||||
$this->onFulfilled = $onFulfilled;
|
||||
$this->onRejected = $onRejected;
|
||||
|
||||
if ($queue) {
|
||||
// array_values included for BC
|
||||
$this->append(...array_values($queue));
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
if (!$this->queue) {
|
||||
throw new \OutOfBoundsException('Mock queue is empty');
|
||||
}
|
||||
|
||||
if (isset($options['delay']) && \is_numeric($options['delay'])) {
|
||||
\usleep((int) $options['delay'] * 1000);
|
||||
}
|
||||
|
||||
$this->lastRequest = $request;
|
||||
$this->lastOptions = $options;
|
||||
$response = \array_shift($this->queue);
|
||||
|
||||
if (isset($options['on_headers'])) {
|
||||
if (!\is_callable($options['on_headers'])) {
|
||||
throw new \InvalidArgumentException('on_headers must be callable');
|
||||
}
|
||||
try {
|
||||
$options['on_headers']($response);
|
||||
} catch (\Exception $e) {
|
||||
$msg = 'An error was encountered during the on_headers event';
|
||||
$response = new RequestException($msg, $request, $response, $e);
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_callable($response)) {
|
||||
$response = $response($request, $options);
|
||||
}
|
||||
|
||||
$response = $response instanceof \Throwable
|
||||
? P\Create::rejectionFor($response)
|
||||
: P\Create::promiseFor($response);
|
||||
|
||||
return $response->then(
|
||||
function (?ResponseInterface $value) use ($request, $options) {
|
||||
$this->invokeStats($request, $options, $value);
|
||||
if ($this->onFulfilled) {
|
||||
($this->onFulfilled)($value);
|
||||
}
|
||||
|
||||
if ($value !== null && isset($options['sink'])) {
|
||||
$contents = (string) $value->getBody();
|
||||
$sink = $options['sink'];
|
||||
|
||||
if (\is_resource($sink)) {
|
||||
\fwrite($sink, $contents);
|
||||
} elseif (\is_string($sink)) {
|
||||
\file_put_contents($sink, $contents);
|
||||
} elseif ($sink instanceof StreamInterface) {
|
||||
$sink->write($contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function ($reason) use ($request, $options) {
|
||||
$this->invokeStats($request, $options, null, $reason);
|
||||
if ($this->onRejected) {
|
||||
($this->onRejected)($reason);
|
||||
}
|
||||
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more variadic requests, exceptions, callables, or promises
|
||||
* to the queue.
|
||||
*
|
||||
* @param mixed ...$values
|
||||
*/
|
||||
public function append(...$values): void
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
if ($value instanceof ResponseInterface
|
||||
|| $value instanceof \Throwable
|
||||
|| $value instanceof PromiseInterface
|
||||
|| \is_callable($value)
|
||||
) {
|
||||
$this->queue[] = $value;
|
||||
} else {
|
||||
throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found '.\get_debug_type($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last received request.
|
||||
*/
|
||||
public function getLastRequest(): ?RequestInterface
|
||||
{
|
||||
return $this->lastRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last received request options.
|
||||
*/
|
||||
public function getLastOptions(): array
|
||||
{
|
||||
return $this->lastOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of remaining items in the queue.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->queue);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->queue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $reason Promise or reason.
|
||||
*/
|
||||
private function invokeStats(
|
||||
RequestInterface $request,
|
||||
array $options,
|
||||
?ResponseInterface $response = null,
|
||||
$reason = null
|
||||
): void {
|
||||
if (isset($options['on_stats'])) {
|
||||
$transferTime = $options['transfer_time'] ?? 0;
|
||||
$stats = new TransferStats($request, $response, $transferTime, $reason);
|
||||
($options['on_stats'])($stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Provides basic proxies for handlers.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class Proxy
|
||||
{
|
||||
/**
|
||||
* Sends synchronous requests to a specific handler while sending all other
|
||||
* requests to another handler.
|
||||
*
|
||||
* @param callable(RequestInterface, array): PromiseInterface $default Handler used for normal responses
|
||||
* @param callable(RequestInterface, array): PromiseInterface $sync Handler used for synchronous responses.
|
||||
*
|
||||
* @return callable(RequestInterface, array): PromiseInterface Returns the composed handler.
|
||||
*/
|
||||
public static function wrapSync(callable $default, callable $sync): callable
|
||||
{
|
||||
return static function (RequestInterface $request, array $options) use ($default, $sync): PromiseInterface {
|
||||
return empty($options[RequestOptions::SYNCHRONOUS]) ? $default($request, $options) : $sync($request, $options);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends streaming requests to a streaming compatible handler while sending
|
||||
* all other requests to a default handler.
|
||||
*
|
||||
* This, for example, could be useful for taking advantage of the
|
||||
* performance benefits of curl while still supporting true streaming
|
||||
* through the StreamHandler.
|
||||
*
|
||||
* @param callable(RequestInterface, array): PromiseInterface $default Handler used for non-streaming responses
|
||||
* @param callable(RequestInterface, array): PromiseInterface $streaming Handler used for streaming responses
|
||||
*
|
||||
* @return callable(RequestInterface, array): PromiseInterface Returns the composed handler.
|
||||
*/
|
||||
public static function wrapStreaming(callable $default, callable $streaming): callable
|
||||
{
|
||||
return static function (RequestInterface $request, array $options) use ($default, $streaming): PromiseInterface {
|
||||
return empty($options['stream']) ? $default($request, $options) : $streaming($request, $options);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends requests to a fallback handler when the default cURL handler cannot
|
||||
* honor TLS 1.2 selection.
|
||||
*
|
||||
* @param callable(RequestInterface, array): PromiseInterface $default
|
||||
* @param callable(RequestInterface, array): PromiseInterface $fallback
|
||||
*
|
||||
* @return callable(RequestInterface, array): PromiseInterface Returns the composed handler.
|
||||
*/
|
||||
public static function wrapTlsFallback(callable $default, callable $fallback): callable
|
||||
{
|
||||
return static function (RequestInterface $request, array $options) use ($default, $fallback): PromiseInterface {
|
||||
if (self::requiresTls12Fallback($options)) {
|
||||
return $fallback($request, $options);
|
||||
}
|
||||
|
||||
return $default($request, $options);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
private static function requiresTls12Fallback(array $options): bool
|
||||
{
|
||||
return isset($options[RequestOptions::CRYPTO_METHOD])
|
||||
&& $options[RequestOptions::CRYPTO_METHOD] === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
|
||||
&& !CurlVersion::supportsTls12();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* Resolves proxy configuration from the process environment with the same
|
||||
* semantics libcurl applies, so the cURL handlers can pin CURLOPT_PROXY and
|
||||
* CURLOPT_NOPROXY explicitly and libcurl never reads the environment itself.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ProxyEnvironment
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the proxy to use for the given request scheme.
|
||||
*
|
||||
* The lookup mirrors libcurl for the http and https schemes the handlers
|
||||
* accept: the lowercase scheme-specific variable first, its uppercase
|
||||
* variant next (except for "http", where uppercase HTTP_PROXY is never
|
||||
* read), then all_proxy/ALL_PROXY.
|
||||
*
|
||||
* @return string|null The proxy to use; null when the environment
|
||||
* configures none.
|
||||
*/
|
||||
public static function getProxyForScheme(string $scheme): ?string
|
||||
{
|
||||
$scheme = Psr7\Utils::asciiToLower($scheme);
|
||||
$candidates = [$scheme.'_proxy'];
|
||||
if ($scheme !== 'http') {
|
||||
// Uppercase HTTP_PROXY is deliberately never consulted: a CGI
|
||||
// request header "Proxy:" becomes HTTP_PROXY in the environment.
|
||||
// See https://httpoxy.org for more information.
|
||||
$candidates[] = Psr7\Utils::asciiToUpper($scheme).'_PROXY';
|
||||
}
|
||||
$candidates[] = 'all_proxy';
|
||||
$candidates[] = 'ALL_PROXY';
|
||||
|
||||
foreach ($candidates as $name) {
|
||||
$value = self::getenv($name);
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null The no-proxy list; null when nothing is set.
|
||||
*/
|
||||
public static function getNoProxy(): ?string
|
||||
{
|
||||
foreach (['no_proxy', 'NO_PROXY'] as $name) {
|
||||
$value = self::getenv($name);
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a no_proxy environment value into matchable entries.
|
||||
*
|
||||
* Mirrors libcurl's tokenization: entries may be separated by commas or
|
||||
* blanks, and a single leading dot is ignored, so ".example.com" bypasses
|
||||
* example.com and its subdomains exactly as a bare domain entry does.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function splitNoProxy(string $noProxy): array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
$split = \preg_split('/[\s,]+/', $noProxy);
|
||||
|
||||
if ($split === false) {
|
||||
throw new \RuntimeException('Unable to split the no_proxy value: '.\preg_last_error_msg());
|
||||
}
|
||||
|
||||
foreach ($split as $entry) {
|
||||
if ($entry !== '' && $entry[0] === '.') {
|
||||
$entry = \substr($entry, 1);
|
||||
}
|
||||
|
||||
if ($entry !== '') {
|
||||
$entries[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
private static function getenv(string $name): ?string
|
||||
{
|
||||
// Windows environment variables are case-insensitive, so the
|
||||
// lowercase-only httpoxy defence does not hold there. Outside the
|
||||
// CLI SAPI on Windows, environment proxies are not resolved at all
|
||||
// (a safe-side divergence from libcurl).
|
||||
if (\PHP_OS_FAMILY === 'Windows' && \PHP_SAPI !== 'cli') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// local_only: the OS environment and putenv() only - the same
|
||||
// environ(7) libcurl reads. SAPI request environments such as
|
||||
// fastcgi_param or SetEnv are deliberately excluded.
|
||||
$value = \getenv($name, true);
|
||||
|
||||
// libcurl's GetEnv (lib/getenv.c) treats variables set to an empty
|
||||
// string as unset on every version, so the lookup falls through to
|
||||
// the next candidate.
|
||||
return $value === false || $value === '' ? null : $value;
|
||||
}
|
||||
}
|
||||
+1159
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class TlsVersion
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function ordinal(string $option, $value): int
|
||||
{
|
||||
if ($value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT) {
|
||||
return 10;
|
||||
}
|
||||
if ($value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT) {
|
||||
return 11;
|
||||
}
|
||||
if ($value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT) {
|
||||
return 12;
|
||||
}
|
||||
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && $value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT) {
|
||||
return 13;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid %s request option: unknown version provided', $option));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $min
|
||||
* @param mixed $max
|
||||
*/
|
||||
public static function assertRange($min, $max): void
|
||||
{
|
||||
if ($min === null || $max === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::ordinal('crypto_method_max', $max) < self::ordinal('crypto_method', $min)) {
|
||||
throw new \InvalidArgumentException('Invalid crypto_method_max request option: maximum TLS version must be greater than or equal to crypto_method');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function streamProtocolVersion(string $option, $value): int
|
||||
{
|
||||
if ($value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT) {
|
||||
return self::requireStreamProto('STREAM_CRYPTO_PROTO_TLSv1_0', $option);
|
||||
}
|
||||
if ($value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT) {
|
||||
return self::requireStreamProto('STREAM_CRYPTO_PROTO_TLSv1_1', $option);
|
||||
}
|
||||
if ($value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT) {
|
||||
return self::requireStreamProto('STREAM_CRYPTO_PROTO_TLSv1_2', $option);
|
||||
}
|
||||
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && $value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT) {
|
||||
return self::requireStreamProto('STREAM_CRYPTO_PROTO_TLSv1_3', $option);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid %s request option: unknown version provided', $option));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a STREAM_CRYPTO_PROTO_* constant. The ssl.max_proto_version
|
||||
* context option and these constants were added in PHP 7.3.0 (TLS 1.3 in
|
||||
* 7.4.0); on older runtimes the option cannot be honored, so reject loudly.
|
||||
*/
|
||||
private static function requireStreamProto(string $constant, string $option): int
|
||||
{
|
||||
if (\defined($constant)) {
|
||||
/** @var int */
|
||||
return \constant($constant);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'Invalid %s request option: maximum TLS version control is not supported by your version of PHP',
|
||||
$option
|
||||
));
|
||||
}
|
||||
}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Creates a composed Guzzle handler function by stacking middlewares on top of
|
||||
* an HTTP handler function.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class HandlerStack
|
||||
{
|
||||
/**
|
||||
* @var (callable(RequestInterface, array): PromiseInterface)|null
|
||||
*/
|
||||
private $handler;
|
||||
|
||||
/**
|
||||
* @var array{(callable(callable(RequestInterface, array): PromiseInterface): callable), (string|null)}[]
|
||||
*/
|
||||
private $stack = [];
|
||||
|
||||
/**
|
||||
* @var (callable(RequestInterface, array): PromiseInterface)|null
|
||||
*/
|
||||
private $cached;
|
||||
|
||||
/**
|
||||
* Creates a default handler stack that can be used by clients.
|
||||
*
|
||||
* The returned handler will wrap the provided handler or use the most
|
||||
* appropriate default handler for your system. The returned HandlerStack has
|
||||
* support for cookies, redirects, HTTP error exceptions, and preparing a body
|
||||
* before sending.
|
||||
*
|
||||
* The returned handler stack can be passed to a client in the "handler"
|
||||
* option.
|
||||
*
|
||||
* @param (callable(RequestInterface, array): PromiseInterface)|null $handler HTTP handler function to use with the stack. If no
|
||||
* handler is provided, the best handler for your
|
||||
* system will be utilized.
|
||||
*/
|
||||
public static function create(?callable $handler = null): self
|
||||
{
|
||||
$stack = new self($handler ?: Utils::chooseHandler());
|
||||
$stack->push(Middleware::httpErrors(), 'http_errors');
|
||||
$stack->push(Middleware::redirect(), 'allow_redirects');
|
||||
$stack->push(Middleware::cookies(), 'cookies');
|
||||
$stack->push(Middleware::prepareBody(), 'prepare_body');
|
||||
|
||||
return $stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler.
|
||||
*/
|
||||
public function __construct(?callable $handler = null)
|
||||
{
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the handler stack as a composed handler
|
||||
*
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
$handler = $this->resolve();
|
||||
|
||||
return $handler($request, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a string representation of the stack.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$depth = 0;
|
||||
$stack = [];
|
||||
|
||||
if ($this->handler !== null) {
|
||||
$stack[] = '0) Handler: '.$this->debugCallable($this->handler);
|
||||
}
|
||||
|
||||
$result = '';
|
||||
foreach (\array_reverse($this->stack) as $tuple) {
|
||||
++$depth;
|
||||
$str = "{$depth}) Name: '{$tuple[1]}', ";
|
||||
$str .= 'Function: '.$this->debugCallable($tuple[0]);
|
||||
$result = "> {$str}\n{$result}";
|
||||
$stack[] = $str;
|
||||
}
|
||||
|
||||
foreach (\array_keys($stack) as $k) {
|
||||
$result .= "< {$stack[$k]}\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP handler that actually returns a promise.
|
||||
*
|
||||
* @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and
|
||||
* returns a Promise.
|
||||
*/
|
||||
public function setHandler(callable $handler): void
|
||||
{
|
||||
$this->handler = $handler;
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the builder has a handler.
|
||||
*/
|
||||
public function hasHandler(): bool
|
||||
{
|
||||
return $this->handler !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshift a middleware to the bottom of the stack.
|
||||
*
|
||||
* @param callable(callable): callable $middleware Middleware function
|
||||
* @param string $name Name to register for this middleware.
|
||||
*/
|
||||
public function unshift(callable $middleware, ?string $name = null): void
|
||||
{
|
||||
\array_unshift($this->stack, [$middleware, $name]);
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a middleware to the top of the stack.
|
||||
*
|
||||
* @param callable(callable): callable $middleware Middleware function
|
||||
* @param string $name Name to register for this middleware.
|
||||
*/
|
||||
public function push(callable $middleware, string $name = ''): void
|
||||
{
|
||||
$this->stack[] = [$middleware, $name];
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a middleware before another middleware by name.
|
||||
*
|
||||
* @param string $findName Middleware to find
|
||||
* @param callable(callable): callable $middleware Middleware function
|
||||
* @param string $withName Name to register for this middleware.
|
||||
*/
|
||||
public function before(string $findName, callable $middleware, string $withName = ''): void
|
||||
{
|
||||
$this->splice($findName, $withName, $middleware, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a middleware after another middleware by name.
|
||||
*
|
||||
* @param string $findName Middleware to find
|
||||
* @param callable(callable): callable $middleware Middleware function
|
||||
* @param string $withName Name to register for this middleware.
|
||||
*/
|
||||
public function after(string $findName, callable $middleware, string $withName = ''): void
|
||||
{
|
||||
$this->splice($findName, $withName, $middleware, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a middleware by instance or name from the stack.
|
||||
*
|
||||
* @param callable|string $remove Middleware to remove by instance or name.
|
||||
*/
|
||||
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__);
|
||||
}
|
||||
|
||||
$this->cached = null;
|
||||
|
||||
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 ($remove) {
|
||||
return $tuple[0] !== $remove;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the middleware and handler into a single callable function.
|
||||
*
|
||||
* @return callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
public function resolve(): callable
|
||||
{
|
||||
if ($this->cached === null) {
|
||||
if (($prev = $this->handler) === null) {
|
||||
throw new \LogicException('No handler has been specified');
|
||||
}
|
||||
|
||||
foreach (\array_reverse($this->stack) as $fn) {
|
||||
/** @var callable(RequestInterface, array): PromiseInterface $prev */
|
||||
$prev = $fn[0]($prev);
|
||||
}
|
||||
|
||||
$this->cached = $prev;
|
||||
}
|
||||
|
||||
return $this->cached;
|
||||
}
|
||||
|
||||
private function findByName(string $name): int
|
||||
{
|
||||
foreach ($this->stack as $k => $v) {
|
||||
if ($v[1] === $name) {
|
||||
return $k;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Middleware not found: $name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Splices a function into the middleware list at a specific position.
|
||||
*/
|
||||
private function splice(string $findName, string $withName, callable $middleware, bool $before): void
|
||||
{
|
||||
$this->cached = null;
|
||||
$idx = $this->findByName($findName);
|
||||
$tuple = [$middleware, $withName];
|
||||
|
||||
if ($before) {
|
||||
if ($idx === 0) {
|
||||
\array_unshift($this->stack, $tuple);
|
||||
} else {
|
||||
$replacement = [$tuple, $this->stack[$idx]];
|
||||
\array_splice($this->stack, $idx, 1, $replacement);
|
||||
}
|
||||
} elseif ($idx === \count($this->stack) - 1) {
|
||||
$this->stack[] = $tuple;
|
||||
} else {
|
||||
$replacement = [$this->stack[$idx], $tuple];
|
||||
\array_splice($this->stack, $idx, 1, $replacement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a debug string for a given callable.
|
||||
*
|
||||
* @param callable|string $fn Function to write as a string.
|
||||
*/
|
||||
private function debugCallable($fn): string
|
||||
{
|
||||
if (\is_string($fn)) {
|
||||
return "callable({$fn})";
|
||||
}
|
||||
|
||||
if (\is_array($fn)) {
|
||||
return \is_string($fn[0])
|
||||
? "callable({$fn[0]}::{$fn[1]})"
|
||||
: "callable(['".\get_class($fn[0])."', '{$fn[1]}'])";
|
||||
}
|
||||
|
||||
/** @var object $fn */
|
||||
return 'callable('.\spl_object_hash($fn).')';
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Formats log messages using variable substitutions for requests, responses,
|
||||
* and other transactional data.
|
||||
*
|
||||
* The following variable substitutions are supported:
|
||||
*
|
||||
* - {request}: Full HTTP request message
|
||||
* - {response}: Full HTTP response message
|
||||
* - {ts}: ISO 8601 date in GMT
|
||||
* - {date_iso_8601} ISO 8601 date in GMT
|
||||
* - {date_common_log} Apache common log date using the configured timezone.
|
||||
* - {host}: Host of the request
|
||||
* - {method}: Method of the request
|
||||
* - {uri}: URI of the request
|
||||
* - {version}: Protocol version
|
||||
* - {target}: Request target of the request (path + query + fragment)
|
||||
* - {hostname}: Hostname of the machine that sent the request
|
||||
* - {code}: Status code of the response (if available)
|
||||
* - {phrase}: Reason phrase of the response (if available)
|
||||
* - {error}: Any error messages (if available)
|
||||
* - {req_header_*}: Replace `*` with the lowercased name of a request header to add to the message
|
||||
* - {res_header_*}: Replace `*` with the lowercased name of a response header to add to the message
|
||||
* - {req_headers}: Request headers
|
||||
* - {res_headers}: Response headers
|
||||
* - {req_body}: Request body
|
||||
* - {res_body}: Response body
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class MessageFormatter implements MessageFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Apache Common Log Format.
|
||||
*
|
||||
* @see https://httpd.apache.org/docs/2.4/logs.html#common
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CLF = '{hostname} {req_header_User-Agent} - [{date_common_log}] "{method} {target} HTTP/{version}" {code} {res_header_Content-Length}';
|
||||
public const DEBUG = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
|
||||
public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';
|
||||
|
||||
/**
|
||||
* @var string Template used to format log messages
|
||||
*/
|
||||
private $template;
|
||||
|
||||
/**
|
||||
* @param string $template Log message template
|
||||
*/
|
||||
public function __construct(?string $template = self::CLF)
|
||||
{
|
||||
$this->template = $template ?: self::CLF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted message string.
|
||||
*
|
||||
* @param RequestInterface $request Request that was sent
|
||||
* @param ResponseInterface|null $response Response that was received
|
||||
* @param \Throwable|null $error Exception that was received
|
||||
*/
|
||||
public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string
|
||||
{
|
||||
$cache = [];
|
||||
|
||||
$result = \preg_replace_callback(
|
||||
'/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
|
||||
function (array $matches) use ($request, $response, $error, &$cache) {
|
||||
if (isset($cache[$matches[1]])) {
|
||||
return $cache[$matches[1]];
|
||||
}
|
||||
|
||||
$result = '';
|
||||
switch ($matches[1]) {
|
||||
case 'request':
|
||||
$result = Psr7\Message::toString($request);
|
||||
break;
|
||||
case 'response':
|
||||
$result = $response ? Psr7\Message::toString($response) : '';
|
||||
break;
|
||||
case 'req_headers':
|
||||
$result = \trim($request->getMethod()
|
||||
.' '.$request->getRequestTarget(), " \n\r\t\0\x0B")
|
||||
.' HTTP/'.$request->getProtocolVersion()."\r\n"
|
||||
.$this->headers($request);
|
||||
break;
|
||||
case 'res_headers':
|
||||
$result = $response ?
|
||||
\sprintf(
|
||||
'HTTP/%s %d %s',
|
||||
$response->getProtocolVersion(),
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
)."\r\n".$this->headers($response)
|
||||
: 'NULL';
|
||||
break;
|
||||
case 'req_body':
|
||||
$result = $request->getBody()->__toString();
|
||||
break;
|
||||
case 'res_body':
|
||||
if (!$response instanceof ResponseInterface) {
|
||||
$result = 'NULL';
|
||||
break;
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
|
||||
if (!$body->isSeekable()) {
|
||||
$result = 'RESPONSE_NOT_LOGGEABLE';
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $response->getBody()->__toString();
|
||||
break;
|
||||
case 'ts':
|
||||
case 'date_iso_8601':
|
||||
$result = \gmdate('c');
|
||||
break;
|
||||
case 'date_common_log':
|
||||
$result = \date('d/M/Y:H:i:s O');
|
||||
break;
|
||||
case 'method':
|
||||
$result = $request->getMethod();
|
||||
break;
|
||||
case 'version':
|
||||
$result = $request->getProtocolVersion();
|
||||
break;
|
||||
case 'uri':
|
||||
case 'url':
|
||||
$result = $request->getUri()->__toString();
|
||||
break;
|
||||
case 'target':
|
||||
$result = $request->getRequestTarget();
|
||||
break;
|
||||
case 'req_version':
|
||||
$result = $request->getProtocolVersion();
|
||||
break;
|
||||
case 'res_version':
|
||||
$result = $response
|
||||
? $response->getProtocolVersion()
|
||||
: 'NULL';
|
||||
break;
|
||||
case 'host':
|
||||
$result = $request->getHeaderLine('Host');
|
||||
break;
|
||||
case 'hostname':
|
||||
$result = \gethostname();
|
||||
break;
|
||||
case 'code':
|
||||
$result = $response ? $response->getStatusCode() : 'NULL';
|
||||
break;
|
||||
case 'phrase':
|
||||
$result = $response ? $response->getReasonPhrase() : 'NULL';
|
||||
break;
|
||||
case 'error':
|
||||
$result = $error ? $error->getMessage() : 'NULL';
|
||||
break;
|
||||
default:
|
||||
// handle prefixed dynamic headers
|
||||
if (\strpos($matches[1], 'req_header_') === 0) {
|
||||
$result = $request->getHeaderLine(\substr($matches[1], 11));
|
||||
} elseif (\strpos($matches[1], 'res_header_') === 0) {
|
||||
$result = $response
|
||||
? $response->getHeaderLine(\substr($matches[1], 11))
|
||||
: 'NULL';
|
||||
}
|
||||
}
|
||||
|
||||
$cache[$matches[1]] = $result;
|
||||
|
||||
return $result;
|
||||
},
|
||||
$this->template
|
||||
);
|
||||
|
||||
if ($result === null) {
|
||||
throw new \RuntimeException('Unable to format message: '.\preg_last_error_msg());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers from message as string
|
||||
*/
|
||||
private function headers(MessageInterface $message): string
|
||||
{
|
||||
$result = '';
|
||||
foreach ($message->getHeaders() as $name => $values) {
|
||||
$result .= $name.': '.\implode(', ', $values)."\r\n";
|
||||
}
|
||||
|
||||
return \trim($result, " \n\r\t\0\x0B");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
interface MessageFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Returns a formatted message string.
|
||||
*
|
||||
* @param RequestInterface $request Request that was sent
|
||||
* @param ResponseInterface|null $response Response that was received
|
||||
* @param \Throwable|null $error Exception that was received
|
||||
*/
|
||||
public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string;
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Cookie\CookieJarInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Functions used to create and wrap handlers with handler middleware.
|
||||
*/
|
||||
final class Middleware
|
||||
{
|
||||
/**
|
||||
* Middleware that adds cookies to requests.
|
||||
*
|
||||
* The options array must be set to a CookieJarInterface in order to use
|
||||
* cookies. This is typically handled for you by a client.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function cookies(): callable
|
||||
{
|
||||
return static function (callable $handler): callable {
|
||||
return static function ($request, array $options) use ($handler) {
|
||||
if (empty($options['cookies'])) {
|
||||
return $handler($request, $options);
|
||||
} elseif (!$options['cookies'] instanceof CookieJarInterface) {
|
||||
throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
|
||||
}
|
||||
$cookieJar = $options['cookies'];
|
||||
$request = $cookieJar->withCookieHeader($request);
|
||||
|
||||
return $handler($request, $options)
|
||||
->then(
|
||||
static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface {
|
||||
$cookieJar->extractCookies($request, $response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that throws exceptions for 4xx or 5xx responses when the
|
||||
* "http_errors" request option is set to true.
|
||||
*
|
||||
* @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages.
|
||||
*
|
||||
* @return callable(callable): callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function httpErrors(?BodySummarizerInterface $bodySummarizer = null): callable
|
||||
{
|
||||
return static function (callable $handler) use ($bodySummarizer): callable {
|
||||
return static function ($request, array $options) use ($handler, $bodySummarizer) {
|
||||
if (empty($options['http_errors'])) {
|
||||
return $handler($request, $options);
|
||||
}
|
||||
|
||||
return $handler($request, $options)->then(
|
||||
static function (ResponseInterface $response) use ($request, $bodySummarizer) {
|
||||
$code = $response->getStatusCode();
|
||||
if ($code < 400) {
|
||||
return $response;
|
||||
}
|
||||
throw RequestException::create($request, $response, null, [], $bodySummarizer);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that pushes history data to an ArrayAccess container.
|
||||
*
|
||||
* @param array|\ArrayAccess<int, array> $container Container to hold the history (by reference).
|
||||
*
|
||||
* @return callable(callable): callable Returns a function that accepts the next handler.
|
||||
*
|
||||
* @throws \InvalidArgumentException if container is not an array or ArrayAccess.
|
||||
*/
|
||||
public static function history(&$container): callable
|
||||
{
|
||||
if (!\is_array($container) && !$container instanceof \ArrayAccess) {
|
||||
throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
|
||||
}
|
||||
|
||||
return static function (callable $handler) use (&$container): callable {
|
||||
return static function (RequestInterface $request, array $options) use ($handler, &$container) {
|
||||
return $handler($request, $options)->then(
|
||||
static function ($value) use ($request, &$container, $options) {
|
||||
$container[] = [
|
||||
'request' => $request,
|
||||
'response' => $value,
|
||||
'error' => null,
|
||||
'options' => $options,
|
||||
];
|
||||
|
||||
return $value;
|
||||
},
|
||||
static function ($reason) use ($request, &$container, $options) {
|
||||
$container[] = [
|
||||
'request' => $request,
|
||||
'response' => null,
|
||||
'error' => $reason,
|
||||
'options' => $options,
|
||||
];
|
||||
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that invokes a callback before and after sending a request.
|
||||
*
|
||||
* The provided listener cannot modify or alter the response. It simply
|
||||
* "taps" into the chain to be notified before returning the promise. The
|
||||
* before listener accepts a request and options array, and the after
|
||||
* listener accepts a request, options array, and response promise.
|
||||
*
|
||||
* @param callable $before Function to invoke before forwarding the request.
|
||||
* @param callable $after Function invoked after forwarding.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function tap(?callable $before = null, ?callable $after = null): callable
|
||||
{
|
||||
return static function (callable $handler) use ($before, $after): callable {
|
||||
return static function (RequestInterface $request, array $options) use ($handler, $before, $after) {
|
||||
if ($before) {
|
||||
$before($request, $options);
|
||||
}
|
||||
$response = $handler($request, $options);
|
||||
if ($after) {
|
||||
$after($request, $options, $response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that handles request redirects.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function redirect(): callable
|
||||
{
|
||||
return static function (callable $handler): RedirectMiddleware {
|
||||
return new RedirectMiddleware($handler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that retries requests based on the boolean result of
|
||||
* invoking the provided "decider" function.
|
||||
*
|
||||
* If no delay function is provided, a simple implementation of exponential
|
||||
* backoff will be utilized.
|
||||
*
|
||||
* @param callable $decider Function that accepts the number of retries,
|
||||
* a request, [response], and [exception] and
|
||||
* returns true if the request is to be retried.
|
||||
* @param callable $delay Function that accepts the number of retries and
|
||||
* returns the number of milliseconds to delay.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function retry(callable $decider, ?callable $delay = null): callable
|
||||
{
|
||||
return static function (callable $handler) use ($decider, $delay): RetryMiddleware {
|
||||
return new RetryMiddleware($decider, $handler, $delay);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that logs requests, responses, and errors using a message
|
||||
* formatter.
|
||||
*
|
||||
* @param LoggerInterface $logger Logs messages.
|
||||
* @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
|
||||
* @param string $logLevel Level at which to log requests.
|
||||
*
|
||||
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable
|
||||
{
|
||||
// To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter
|
||||
if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) {
|
||||
throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class));
|
||||
}
|
||||
|
||||
return static function (callable $handler) use ($logger, $formatter, $logLevel): callable {
|
||||
return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) {
|
||||
return $handler($request, $options)->then(
|
||||
static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface {
|
||||
$message = $formatter->format($request, $response);
|
||||
$logger->log($logLevel, $message);
|
||||
|
||||
return $response;
|
||||
},
|
||||
static function ($reason) use ($logger, $request, $formatter): PromiseInterface {
|
||||
$response = $reason instanceof RequestException ? $reason->getResponse() : null;
|
||||
$message = $formatter->format($request, $response, P\Create::exceptionFor($reason));
|
||||
$logger->error($message);
|
||||
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This middleware adds a default content-type if possible, a default
|
||||
* content-length or transfer-encoding header, and the expect header.
|
||||
*/
|
||||
public static function prepareBody(): callable
|
||||
{
|
||||
return static function (callable $handler): PrepareBodyMiddleware {
|
||||
return new PrepareBodyMiddleware($handler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that applies a map function to the request before passing to
|
||||
* the next handler.
|
||||
*
|
||||
* @param callable $fn Function that accepts a RequestInterface and returns
|
||||
* a RequestInterface.
|
||||
*/
|
||||
public static function mapRequest(callable $fn): callable
|
||||
{
|
||||
return static function (callable $handler) use ($fn): callable {
|
||||
return static function (RequestInterface $request, array $options) use ($handler, $fn) {
|
||||
return $handler($fn($request), $options);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that applies a map function to the resolved promise's
|
||||
* response.
|
||||
*
|
||||
* @param callable $fn Function that accepts a ResponseInterface and
|
||||
* returns a ResponseInterface.
|
||||
*/
|
||||
public static function mapResponse(callable $fn): callable
|
||||
{
|
||||
return static function (callable $handler) use ($fn): callable {
|
||||
return static function (RequestInterface $request, array $options) use ($handler, $fn) {
|
||||
return $handler($request, $options)->then($fn);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
/**
|
||||
* Multiplexing modes for the "multiplex" request option.
|
||||
*
|
||||
* NONE disables multiplexing for a whole handler when passed as the
|
||||
* "multiplex" client configuration option or, when constructing a handler
|
||||
* directly, as the CurlMultiHandler "multiplex" constructor option. As a
|
||||
* request option value it guarantees the transfer does not share its
|
||||
* connection with any concurrent transfer, and is accepted only where that
|
||||
* guarantee holds.
|
||||
*/
|
||||
final class Multiplexing
|
||||
{
|
||||
public const NONE = 'none';
|
||||
public const EAGER = 'eager';
|
||||
public const WAIT = 'wait';
|
||||
public const REQUIRE_EAGER = 'require_eager';
|
||||
public const REQUIRE_WAIT = 'require_wait';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\EachPromise;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Promise\PromisorInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Sends an iterator of requests concurrently using a capped pool size.
|
||||
*
|
||||
* The pool will read from an iterator until it is cancelled or until the
|
||||
* iterator is consumed. When a request is yielded, the request is sent after
|
||||
* applying the "request_options" request options (if provided in the ctor).
|
||||
*
|
||||
* When a function is yielded by the iterator, the function is provided the
|
||||
* "request_options" array that should be merged on top of any existing
|
||||
* options, and the function MUST then return a wait-able promise.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class Pool implements PromisorInterface
|
||||
{
|
||||
/**
|
||||
* @var EachPromise
|
||||
*/
|
||||
private $each;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client used to send the requests.
|
||||
* @param array|\Iterator $requests Requests or functions that return
|
||||
* requests to send concurrently.
|
||||
* @param array $config Associative array of options
|
||||
* - concurrency: (int) Maximum number of requests to send concurrently
|
||||
* - options: Array of request options to apply to each request.
|
||||
* - fulfilled: (callable) Function to invoke when a request completes.
|
||||
* - rejected: (callable) Function to invoke when a request is rejected.
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $requests, array $config = [])
|
||||
{
|
||||
if (!isset($config['concurrency'])) {
|
||||
$config['concurrency'] = 25;
|
||||
}
|
||||
|
||||
if (isset($config['options'])) {
|
||||
$opts = $config['options'];
|
||||
unset($config['options']);
|
||||
} else {
|
||||
$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) {
|
||||
if ($rfn instanceof RequestInterface) {
|
||||
yield $key => $client->sendAsync($rfn, $opts);
|
||||
} elseif (\is_callable($rfn)) {
|
||||
yield $key => $rfn($opts);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr\Http\Message\ResponseInterface object.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$this->each = new EachPromise($requests(), $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get promise
|
||||
*/
|
||||
public function promise(): PromiseInterface
|
||||
{
|
||||
return $this->each->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends multiple requests concurrently and returns an array of responses
|
||||
* and exceptions that uses the same ordering as the provided requests.
|
||||
*
|
||||
* IMPORTANT: This method keeps every request and response in memory, and
|
||||
* as such, is NOT recommended when sending a large number or an
|
||||
* indeterminate number of requests concurrently.
|
||||
*
|
||||
* @param ClientInterface $client Client used to send the requests
|
||||
* @param array|\Iterator $requests Requests to send concurrently.
|
||||
* @param array $options Passes through the options available in
|
||||
* {@see Pool::__construct}
|
||||
*
|
||||
* @return array Returns an array containing the response or an exception
|
||||
* in the same order that the requests were sent.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the event format is incorrect.
|
||||
*/
|
||||
public static function batch(ClientInterface $client, $requests, array $options = []): array
|
||||
{
|
||||
$res = [];
|
||||
self::cmpCallback($options, 'fulfilled', $res);
|
||||
self::cmpCallback($options, 'rejected', $res);
|
||||
$pool = new static($client, $requests, $options);
|
||||
$pool->promise()->wait();
|
||||
\ksort($res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute callback(s)
|
||||
*/
|
||||
private static function cmpCallback(array &$options, string $name, array &$results): void
|
||||
{
|
||||
if (!isset($options[$name])) {
|
||||
$options[$name] = static function ($v, $k) use (&$results) {
|
||||
$results[$k] = $v;
|
||||
};
|
||||
} else {
|
||||
$currentFn = $options[$name];
|
||||
$options[$name] = static function ($v, $k) use (&$results, $currentFn) {
|
||||
$currentFn($v, $k);
|
||||
$results[$k] = $v;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Prepares requests that contain a body, adding the Content-Length,
|
||||
* Content-Type, and Expect headers.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class PrepareBodyMiddleware
|
||||
{
|
||||
/**
|
||||
* @var callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
private $nextHandler;
|
||||
|
||||
/**
|
||||
* @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
|
||||
*/
|
||||
public function __construct(callable $nextHandler)
|
||||
{
|
||||
$this->nextHandler = $nextHandler;
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
$fn = $this->nextHandler;
|
||||
|
||||
// Don't do anything if the request has no body.
|
||||
if ($request->getBody()->getSize() === 0) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
$modify = [];
|
||||
|
||||
// Add a default content-type if possible.
|
||||
if (!$request->hasHeader('Content-Type')) {
|
||||
if ($uri = $request->getBody()->getMetadata('uri')) {
|
||||
if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) {
|
||||
$modify['set_headers']['Content-Type'] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a default content-length or transfer-encoding header.
|
||||
if (!$request->hasHeader('Content-Length')
|
||||
&& !$request->hasHeader('Transfer-Encoding')
|
||||
) {
|
||||
$size = $request->getBody()->getSize();
|
||||
if ($size !== null) {
|
||||
$modify['set_headers']['Content-Length'] = (string) $size;
|
||||
} else {
|
||||
$modify['set_headers']['Transfer-Encoding'] = 'chunked';
|
||||
}
|
||||
}
|
||||
|
||||
// Add the expect header if needed.
|
||||
$this->addExpectHeader($request, $options, $modify);
|
||||
|
||||
return $fn(Psr7\Utils::modifyRequest($request, $modify), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add expect header
|
||||
*/
|
||||
private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void
|
||||
{
|
||||
// Determine if the Expect header should be used
|
||||
if ($request->hasHeader('Expect')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$expect = $options['expect'] ?? null;
|
||||
|
||||
// Return if disabled or using HTTP/1.0
|
||||
if ($expect === false || $request->getProtocolVersion() === '1.0') {
|
||||
return;
|
||||
}
|
||||
|
||||
// The expect header is unconditionally enabled
|
||||
if ($expect === true) {
|
||||
$modify['set_headers']['Expect'] = '100-Continue';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// By default, send the expect header when the payload is > 1mb
|
||||
if ($expect === null) {
|
||||
$expect = 1048576;
|
||||
}
|
||||
|
||||
// Always add if the body cannot be rewound, the size cannot be
|
||||
// determined, or the size is greater than the cutoff threshold
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
|
||||
$modify['set_headers']['Expect'] = '100-Continue';
|
||||
}
|
||||
}
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Exception\TooManyRedirectsException;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Request redirect middleware.
|
||||
*
|
||||
* Apply this middleware like other middleware using
|
||||
* {@see Middleware::redirect()}.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class RedirectMiddleware
|
||||
{
|
||||
public const HISTORY_HEADER = 'X-Guzzle-Redirect-History';
|
||||
|
||||
public const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $defaultSettings = [
|
||||
'max' => 5,
|
||||
'protocols' => ['http', 'https'],
|
||||
'strict' => false,
|
||||
'referer' => false,
|
||||
'track_redirects' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
private $nextHandler;
|
||||
|
||||
/**
|
||||
* @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
|
||||
*/
|
||||
public function __construct(callable $nextHandler)
|
||||
{
|
||||
$this->nextHandler = $nextHandler;
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
$fn = $this->nextHandler;
|
||||
|
||||
if (empty($options['allow_redirects'])) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
if ($options['allow_redirects'] === true) {
|
||||
$options['allow_redirects'] = self::$defaultSettings;
|
||||
} elseif (!\is_array($options['allow_redirects'])) {
|
||||
throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
|
||||
} else {
|
||||
// Merge the default settings with the provided settings
|
||||
$options['allow_redirects'] += self::$defaultSettings;
|
||||
}
|
||||
|
||||
if (empty($options['allow_redirects']['max'])) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
return $fn($request, $options)
|
||||
->then(function (ResponseInterface $response) use ($request, $options) {
|
||||
return $this->checkRedirect($request, $options, $response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response)
|
||||
{
|
||||
if (\strpos((string) $response->getStatusCode(), '3') !== 0
|
||||
|| !$response->hasHeader('Location')
|
||||
) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->guardMax($request, $response, $options);
|
||||
$nextRequest = $this->modifyRequest($request, $options, $response);
|
||||
|
||||
// If authorization is handled by curl, unset it if URI is cross-origin.
|
||||
if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) {
|
||||
unset(
|
||||
$options['curl'][\CURLOPT_HTTPAUTH],
|
||||
$options['curl'][\CURLOPT_USERPWD]
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($options['allow_redirects']['on_redirect'])) {
|
||||
($options['allow_redirects']['on_redirect'])(
|
||||
$request,
|
||||
$response,
|
||||
$nextRequest->getUri()
|
||||
);
|
||||
}
|
||||
|
||||
// The caller's delay applies once, before the initial request, not
|
||||
// before each followed redirect.
|
||||
unset($options['delay']);
|
||||
|
||||
$promise = $this($nextRequest, $options);
|
||||
|
||||
// Add headers to be able to track history of redirects.
|
||||
if (!empty($options['allow_redirects']['track_redirects'])) {
|
||||
return $this->withTracking(
|
||||
$promise,
|
||||
(string) $nextRequest->getUri(),
|
||||
$response->getStatusCode()
|
||||
);
|
||||
}
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable tracking on promise.
|
||||
*/
|
||||
private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface
|
||||
{
|
||||
return $promise->then(
|
||||
static function (ResponseInterface $response) use ($uri, $statusCode) {
|
||||
// Note that we are pushing to the front of the list as this
|
||||
// would be an earlier response than what is currently present
|
||||
// in the history header.
|
||||
$historyHeader = $response->getHeader(self::HISTORY_HEADER);
|
||||
$statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
|
||||
\array_unshift($historyHeader, $uri);
|
||||
\array_unshift($statusHeader, (string) $statusCode);
|
||||
|
||||
return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
|
||||
->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for too many redirects.
|
||||
*
|
||||
* @throws TooManyRedirectsException Too many redirects.
|
||||
*/
|
||||
private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void
|
||||
{
|
||||
$current = $options['__redirect_count']
|
||||
?? 0;
|
||||
$options['__redirect_count'] = $current + 1;
|
||||
$max = $options['allow_redirects']['max'];
|
||||
|
||||
if ($options['__redirect_count'] > $max) {
|
||||
throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response);
|
||||
}
|
||||
}
|
||||
|
||||
public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface
|
||||
{
|
||||
// Request modifications to apply.
|
||||
$modify = [];
|
||||
$protocols = $options['allow_redirects']['protocols'];
|
||||
|
||||
// Use a GET request if this is an entity enclosing request and we are
|
||||
// not forcing RFC compliance, but rather emulating what all browsers
|
||||
// would do.
|
||||
$statusCode = $response->getStatusCode();
|
||||
if ($statusCode == 303
|
||||
|| ($statusCode <= 302 && !$options['allow_redirects']['strict'])
|
||||
) {
|
||||
$requestMethod = $request->getMethod();
|
||||
|
||||
if ($requestMethod !== 'QUERY' || !\in_array($statusCode, [301, 302], true)) {
|
||||
$modify['method'] = \in_array($requestMethod, ['GET', 'HEAD', 'OPTIONS'], true) ? $requestMethod : 'GET';
|
||||
$modify['body'] = '';
|
||||
$modify['remove_headers'] = ['Content-Length', 'Transfer-Encoding'];
|
||||
}
|
||||
}
|
||||
|
||||
$uri = self::redirectUri($request, $response, $protocols);
|
||||
$idnOptions = Utils::normalizeIdnConversionOption($options['idn_conversion'] ?? null);
|
||||
if ($idnOptions !== null) {
|
||||
$uri = Utils::idnUriConvert($uri, $idnOptions);
|
||||
}
|
||||
|
||||
$modify['uri'] = $uri;
|
||||
|
||||
// The body only needs to be rewound when the next request reuses it.
|
||||
if (!isset($modify['body'])) {
|
||||
try {
|
||||
Psr7\Message::rewindBody($request);
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new RequestException(
|
||||
'Redirect failed because the request body could not be rewound: '.$e->getMessage(),
|
||||
$request,
|
||||
$response,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the Referer header if it is told to do so and only
|
||||
// add the header if we are not redirecting from https to http.
|
||||
if ($options['allow_redirects']['referer']
|
||||
&& $modify['uri']->getScheme() === $request->getUri()->getScheme()
|
||||
) {
|
||||
$uri = $request->getUri()->withUserInfo('')->withFragment('');
|
||||
$modify['set_headers']['Referer'] = (string) $uri;
|
||||
} else {
|
||||
$modify['remove_headers'][] = 'Referer';
|
||||
}
|
||||
|
||||
// Remove Authorization and Cookie headers if URI is cross-origin.
|
||||
if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) {
|
||||
$modify['remove_headers'][] = 'Authorization';
|
||||
$modify['remove_headers'][] = 'Cookie';
|
||||
}
|
||||
|
||||
return Psr7\Utils::modifyRequest($request, $modify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the appropriate URL on the request based on the location header.
|
||||
*/
|
||||
private static function redirectUri(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
array $protocols
|
||||
): UriInterface {
|
||||
$location = Psr7\UriResolver::resolve(
|
||||
$request->getUri(),
|
||||
new Psr7\Uri($response->getHeaderLine('Location'))
|
||||
);
|
||||
|
||||
// Ensure that the redirect URI is allowed based on the protocols.
|
||||
if (!\in_array($location->getScheme(), $protocols)) {
|
||||
throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response);
|
||||
}
|
||||
|
||||
return $location;
|
||||
}
|
||||
}
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
/**
|
||||
* This class contains a list of built-in Guzzle request options.
|
||||
*
|
||||
* @see https://github.com/guzzle/guzzle/blob/7.15/docs/request-options.md
|
||||
*/
|
||||
final class RequestOptions
|
||||
{
|
||||
/**
|
||||
* allow_redirects: (bool|array) Controls redirect behavior. Pass false
|
||||
* to disable redirects, pass true to enable redirects, pass an
|
||||
* associative to provide custom redirect settings. Defaults to "false".
|
||||
* This option only works if your handler has the RedirectMiddleware. When
|
||||
* passing an associative array, you can provide the following key value
|
||||
* pairs:
|
||||
*
|
||||
* - max: (int, default=5) maximum number of allowed redirects.
|
||||
* - strict: (bool, default=false) Set to true to use strict redirects
|
||||
* meaning redirect POST requests with POST requests vs. doing what most
|
||||
* browsers do which is redirect POST requests with GET requests. The
|
||||
* QUERY method keeps its method and body across non-strict 301 and 302
|
||||
* redirects, and a 303 redirect is followed with a body-less GET.
|
||||
* - referer: (bool, default=false) Set to true to enable the Referer
|
||||
* header.
|
||||
* - 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{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|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{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';
|
||||
|
||||
/**
|
||||
* 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
|
||||
* an instance of {@see Cookie\CookieJarInterface}.
|
||||
*/
|
||||
public const COOKIES = 'cookies';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* crypto_method: (int) A value describing the minimum TLS protocol
|
||||
* version to use.
|
||||
*
|
||||
* This setting must be set to one of the
|
||||
* ``STREAM_CRYPTO_METHOD_TLS*_CLIENT`` constants. PHP 7.4 or higher is
|
||||
* required in order to use TLS 1.3, and cURL 7.34.0 or higher is required
|
||||
* in order to specify a crypto method, with cURL 7.52.0 or higher being
|
||||
* required to use TLS 1.3.
|
||||
*/
|
||||
public const CRYPTO_METHOD = 'crypto_method';
|
||||
|
||||
/**
|
||||
* crypto_method_max: (int) A value describing the maximum TLS protocol
|
||||
* version to use.
|
||||
*
|
||||
* This setting must be set to one of the
|
||||
* ``STREAM_CRYPTO_METHOD_TLS*_CLIENT`` constants. On the stream handler,
|
||||
* PHP 7.3 or higher is required to set a maximum TLS version, and PHP 7.4
|
||||
* or higher is required to use TLS 1.3. cURL 7.54.0 or higher is required
|
||||
* in order to specify a maximum TLS version with the cURL handler.
|
||||
*/
|
||||
public const CRYPTO_METHOD_MAX = 'crypto_method_max';
|
||||
|
||||
/**
|
||||
* curl: (array) Raw cURL options to apply when using a built-in cURL handler.
|
||||
*/
|
||||
public const CURL = 'curl';
|
||||
|
||||
/**
|
||||
* debug: (bool|resource) Set to true or set to a PHP stream returned by
|
||||
* fopen() enable debug output with the HTTP handler used to send a
|
||||
* request.
|
||||
*/
|
||||
public const DEBUG = 'debug';
|
||||
|
||||
/**
|
||||
* 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|float) The amount of time to delay before sending in
|
||||
* milliseconds.
|
||||
*/
|
||||
public const DELAY = 'delay';
|
||||
|
||||
/**
|
||||
* expect: (bool|integer) Controls the behavior of the
|
||||
* "Expect: 100-Continue" header.
|
||||
*
|
||||
* Set to `true` to enable the "Expect: 100-Continue" header for all
|
||||
* requests that sends a body. Set to `false` to disable the
|
||||
* "Expect: 100-Continue" header for all requests. Set to a number so that
|
||||
* the size of the payload must be greater than the number in order to send
|
||||
* the Expect header. Setting to a number will send the Expect header for
|
||||
* all requests in which the size of the payload cannot be determined or
|
||||
* where the body is not rewindable.
|
||||
*
|
||||
* By default, Guzzle will add the "Expect: 100-Continue" header when the
|
||||
* size of the body of a request is greater than 1 MB and a request is
|
||||
* using HTTP/1.1.
|
||||
*/
|
||||
public const EXPECT = 'expect';
|
||||
|
||||
/**
|
||||
* 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<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';
|
||||
|
||||
/**
|
||||
* http_errors: (bool, default=true) Set to false to disable exceptions
|
||||
* when a non- successful HTTP response is received. By default,
|
||||
* exceptions will be thrown for 4xx and 5xx responses. This option only
|
||||
* works if your handler has the `httpErrors` middleware.
|
||||
*/
|
||||
public const HTTP_ERRORS = 'http_errors';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* json: (mixed) Adds JSON data to a request. The provided value is JSON
|
||||
* encoded and a Content-Type header of application/json will be added to
|
||||
* the request if no Content-Type header is already present.
|
||||
*/
|
||||
public const JSON = 'json';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* multiplex: (string) Controls how a request sent through a built-in
|
||||
* cURL handler relates to shared, multiplexed connections: how an HTTP/2
|
||||
* request pursues one, or, with Multiplexing::NONE, whether the transfer
|
||||
* may share its connection at all. When the option is not set,
|
||||
* multiplexing is left to libcurl: nothing waits, and established
|
||||
* multiplex-capable connections are still shared. Use
|
||||
* Multiplexing::EAGER to explicitly never wait for pending connections,
|
||||
* Multiplexing::WAIT to wait on libcurl-eligible pending connections with
|
||||
* CURLOPT_PIPEWAIT, normally to the same origin,
|
||||
* Multiplexing::REQUIRE_EAGER to fail unless a multiplexed protocol is
|
||||
* guaranteed while dialing eagerly, or Multiplexing::REQUIRE_WAIT for the
|
||||
* same guarantee while also waiting on pending connections. The required
|
||||
* modes require a handler that permits actual multiplexing, not merely a
|
||||
* multiplexed protocol, and are rejected on a Multiplexing::NONE handler.
|
||||
* The stream handler ignores EAGER and WAIT, and rejects the required
|
||||
* family; CurlHandler has no multi handle to multiplex over. Explicit
|
||||
* modes reject deprecated raw cURL options they conflict with: the
|
||||
* required family cannot be combined with a raw CURLOPT_HTTP_VERSION,
|
||||
* CURLOPT_URL, or CURLOPT_FOLLOWLOCATION; no explicit mode can be
|
||||
* combined with a raw CURLOPT_PIPEWAIT on the CurlMultiHandler; and
|
||||
* Multiplexing::NONE on a CurlMultiHandler that permits multiplexing
|
||||
* cannot be combined with the raw CURLOPT_HTTP_VERSION, CURLOPT_HTTPAUTH
|
||||
* (including the "auth" request option's "digest" and "ntlm" modes,
|
||||
* which set it), CURLOPT_PROXYAUTH, CURLOPT_FOLLOWLOCATION,
|
||||
* CURLOPT_HTTPHEADER, CURLOPT_ALTSVC, CURLOPT_ALTSVC_CTRL, or
|
||||
* CURLOPT_PROXYTYPE cURL options. The required family also
|
||||
* rejects final CURLOPT_HTTPAUTH masks that permit NTLM, which libcurl
|
||||
* retries over HTTP/1.1. The required family validates its cleartext
|
||||
* proxy rule against the final cURL configuration, after raw options
|
||||
* such as CURLOPT_PROXY and CURLOPT_PRE_PROXY are applied; only the
|
||||
* exact raw CURLOPT_NOPROXY wildcard '*' disables the primary proxy and
|
||||
* pre-proxy there, and raw host-specific patterns are conservatively
|
||||
* treated as leaving them active. These rejections are
|
||||
* configuration-conflict checks, not remote security checks.
|
||||
*
|
||||
* Multiplexing::NONE disables multiplexing for a whole handler when
|
||||
* passed as the "multiplex" client configuration option, which
|
||||
* configures the default handler and also becomes the default request
|
||||
* option, or, when constructing a handler directly, as the
|
||||
* CurlMultiHandler "multiplex" constructor option. A handler
|
||||
* configured with Multiplexing::NONE rejects explicitly requested wait
|
||||
* modes as a configuration conflict when the transfer would actually
|
||||
* wait, and always rejects the required modes, because they require a
|
||||
* handler that permits actual multiplexing, not merely a multiplexed
|
||||
* protocol. As a request option value, Multiplexing::NONE guarantees the
|
||||
* transfer does not share its connection with any concurrent transfer.
|
||||
* Multiplexing::NONE does not force HTTP/1.1: on a Multiplexing::NONE
|
||||
* handler, HTTP/2 still negotiates and each transfer keeps its
|
||||
* connection to itself.
|
||||
*
|
||||
* The request option value is accepted exactly where the guarantee
|
||||
* holds and can be verified: on a CurlMultiHandler configured with
|
||||
* Multiplexing::NONE, for requests whose declared protocol version is
|
||||
* HTTP/1.x, on CurlHandler, and on the stream handler, which never
|
||||
* multiplexes. An HTTP/2 request with a Multiplexing::NONE request
|
||||
* option is rejected on a CurlMultiHandler that permits multiplexing.
|
||||
* On a CurlMultiHandler that permits multiplexing, Multiplexing::NONE
|
||||
* is also rejected with a custom "handle_factory", alongside a raw
|
||||
* CURLMOPT_PIPELINING cURL multi option, and combined with the raw
|
||||
* CURLOPT_HTTP_VERSION, CURLOPT_HTTPAUTH (including the "auth" request
|
||||
* option's "digest" and "ntlm" modes, which set it), CURLOPT_PROXYAUTH,
|
||||
* CURLOPT_FOLLOWLOCATION, CURLOPT_HTTPHEADER, CURLOPT_ALTSVC,
|
||||
* CURLOPT_ALTSVC_CTRL, or CURLOPT_PROXYTYPE cURL options. It is also
|
||||
* rejected when the request carries an Expect: 100-continue header (its
|
||||
* 417 retries select connections outside the safeguards; remove an
|
||||
* explicitly supplied header, or set the "expect" request option to
|
||||
* false to prevent it being added automatically).
|
||||
*
|
||||
* On a client whose multi handler permits multiplexing, the ordinary
|
||||
* non-streaming default stack - both cURL handlers available and no
|
||||
* connection caps forcing multi-only routing - runs synchronous
|
||||
* requests on the CurlHandler path, which satisfies the guarantee for
|
||||
* any protocol version, while asynchronous requests run on the
|
||||
* CurlMultiHandler, so an HTTP/2 request with Multiplexing::NONE
|
||||
* succeeds synchronously and is rejected asynchronously on the same
|
||||
* client. Keep-alive reuse between consecutive transfers is
|
||||
* unaffected, except on libcurl versions below 7.77.0 and from 8.11.0
|
||||
* through 8.12.1, where an accepted HTTP/1.x request on a multiplexing
|
||||
* CurlMultiHandler forces a fresh connection. Custom handlers receive
|
||||
* the "multiplex" option unchanged: its semantics are handler-defined,
|
||||
* Guzzle does not guarantee it is honored, and a client-level
|
||||
* Multiplexing::NONE with a custom handler flows to it as a default
|
||||
* request option without client-side enforcement.
|
||||
*/
|
||||
public const MULTIPLEX = 'multiplex';
|
||||
|
||||
/**
|
||||
* on_headers: (callable) A callable that is invoked when the HTTP headers
|
||||
* of the response have been received but the body has not yet begun to
|
||||
* download.
|
||||
*/
|
||||
public const ON_HEADERS = 'on_headers';
|
||||
|
||||
/**
|
||||
* on_stats: (callable) allows you to get access to transfer statistics of
|
||||
* a request and access the lower level transfer details of the handler
|
||||
* associated with your client. ``on_stats`` is a callable that is invoked
|
||||
* when a handler has finished sending a request. The callback is invoked
|
||||
* with transfer statistics about the request, the response received, or
|
||||
* the error encountered. Included in the data is the total amount of time
|
||||
* taken to send the request.
|
||||
*/
|
||||
public const ON_STATS = 'on_stats';
|
||||
|
||||
/**
|
||||
* on_trailers: (callable) A callable that is invoked by the built-in cURL
|
||||
* handlers once per successful transfer, after the response body has been
|
||||
* received, with an associative array of the parsed HTTP trailers followed
|
||||
* by the response. Trailer field names are lowercased and grouped
|
||||
* case-insensitively; values keep their wire order. Malformed trailer
|
||||
* field lines are discarded before parsing. Trailer fields are reported
|
||||
* separately from response headers and are never merged into the response.
|
||||
*/
|
||||
public const ON_TRAILERS = 'on_trailers';
|
||||
|
||||
/**
|
||||
* progress: (callable) Defines a function to invoke when transfer
|
||||
* progress is made. The function accepts the following positional
|
||||
* arguments: the total number of bytes expected to be downloaded, the
|
||||
* number of bytes downloaded so far, the number of bytes expected to be
|
||||
* uploaded, the number of bytes uploaded so far.
|
||||
*/
|
||||
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 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<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|\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';
|
||||
|
||||
/**
|
||||
* synchronous: (bool) Set to true to inform HTTP handlers that you intend
|
||||
* on waiting on the response. This can be useful for optimizations. Note
|
||||
* that a promise is still returned if you are using one of the async
|
||||
* client methods.
|
||||
*/
|
||||
public const SYNCHRONOUS = 'synchronous';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* stream_context: (array) PHP stream context options to merge into the
|
||||
* context used by the built-in stream handler.
|
||||
*/
|
||||
public const STREAM_CONTEXT = 'stream_context';
|
||||
|
||||
/**
|
||||
* verify: (bool|string, default=true) Describes the SSL certificate
|
||||
* verification behavior of a request. Set to true to enable SSL
|
||||
* certificate verification using the system CA bundle when available
|
||||
* (the default). Set to false to disable certificate verification (this
|
||||
* is insecure!). Set to a string to provide the path to a CA bundle on
|
||||
* disk to enable verification using a custom certificate.
|
||||
*/
|
||||
public const VERIFY = 'verify';
|
||||
|
||||
/**
|
||||
* 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: (int|float, default=default_socket_timeout ini setting)
|
||||
* Number describing the body read timeout, for stream requests.
|
||||
*/
|
||||
public const READ_TIMEOUT = 'read_timeout';
|
||||
|
||||
/**
|
||||
* retries: (int) Current retry count used by the retry middleware.
|
||||
*/
|
||||
public const RETRIES = 'retries';
|
||||
|
||||
/**
|
||||
* version: (string|int|float) Specifies the HTTP protocol version to attempt
|
||||
* to use.
|
||||
*/
|
||||
public const VERSION = 'version';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Middleware that retries requests based on the boolean result of
|
||||
* invoking the provided "decider" function.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class RetryMiddleware
|
||||
{
|
||||
/**
|
||||
* @var callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
private $nextHandler;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $decider;
|
||||
|
||||
/**
|
||||
* @var callable(int)
|
||||
*/
|
||||
private $delay;
|
||||
|
||||
/**
|
||||
* @param callable $decider Function that accepts the number of retries,
|
||||
* a request, [response], and [exception] and
|
||||
* returns true if the request is to be
|
||||
* retried.
|
||||
* @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
|
||||
* @param (callable(int): int)|null $delay Function that accepts the number of retries
|
||||
* and returns the number of
|
||||
* milliseconds to delay.
|
||||
*/
|
||||
public function __construct(callable $decider, callable $nextHandler, ?callable $delay = null)
|
||||
{
|
||||
$this->decider = $decider;
|
||||
$this->nextHandler = $nextHandler;
|
||||
$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;
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
if (!isset($options['retries'])) {
|
||||
$options['retries'] = 0;
|
||||
}
|
||||
|
||||
$fn = $this->nextHandler;
|
||||
|
||||
return $fn($request, $options)
|
||||
->then(
|
||||
$this->onFulfilled($request, $options),
|
||||
$this->onRejected($request, $options)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute fulfilled closure
|
||||
*/
|
||||
private function onFulfilled(RequestInterface $request, array $options): callable
|
||||
{
|
||||
return function ($value) use ($request, $options) {
|
||||
if (!($this->decider)(
|
||||
$options['retries'],
|
||||
$request,
|
||||
$value,
|
||||
null
|
||||
)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $this->doRetry($request, $options, $value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute rejected closure
|
||||
*/
|
||||
private function onRejected(RequestInterface $req, array $options): callable
|
||||
{
|
||||
return function ($reason) use ($req, $options) {
|
||||
if (!($this->decider)(
|
||||
$options['retries'],
|
||||
$req,
|
||||
null,
|
||||
$reason
|
||||
)) {
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
|
||||
return $this->doRetry($req, $options);
|
||||
};
|
||||
}
|
||||
|
||||
private function doRetry(RequestInterface $request, array $options, ?ResponseInterface $response = null): PromiseInterface
|
||||
{
|
||||
$options['delay'] = ($this->delay)(++$options['retries'], $response, $request);
|
||||
|
||||
return $this($request, $options);
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Represents data at the point after it was transferred either successfully
|
||||
* or after a network error.
|
||||
*/
|
||||
final class TransferStats
|
||||
{
|
||||
/**
|
||||
* @var RequestInterface
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var ResponseInterface|null
|
||||
*/
|
||||
private $response;
|
||||
|
||||
/**
|
||||
* @var float|null
|
||||
*/
|
||||
private $transferTime;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $handlerStats;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
private $handlerErrorData;
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request Request that was sent.
|
||||
* @param ResponseInterface|null $response Response received (if any)
|
||||
* @param float|null $transferTime Total handler transfer time.
|
||||
* @param mixed $handlerErrorData Handler error data.
|
||||
* @param array $handlerStats Handler specific stats.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestInterface $request,
|
||||
?ResponseInterface $response = null,
|
||||
?float $transferTime = null,
|
||||
$handlerErrorData = null,
|
||||
array $handlerStats = []
|
||||
) {
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->transferTime = $transferTime;
|
||||
$this->handlerErrorData = $handlerErrorData;
|
||||
$this->handlerStats = $handlerStats;
|
||||
}
|
||||
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response that was received (if any).
|
||||
*/
|
||||
public function getResponse(): ?ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a response was received.
|
||||
*/
|
||||
public function hasResponse(): bool
|
||||
{
|
||||
return $this->response !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets handler specific error data.
|
||||
*
|
||||
* This might be an exception, a integer representing an error code, or
|
||||
* anything else. Relying on this value assumes that you know what handler
|
||||
* you are using.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getHandlerErrorData()
|
||||
{
|
||||
return $this->handlerErrorData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective URI the request was sent to.
|
||||
*/
|
||||
public function getEffectiveUri(): UriInterface
|
||||
{
|
||||
return $this->request->getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated time the request was being transferred by the handler.
|
||||
*
|
||||
* @return float|null Time in seconds.
|
||||
*/
|
||||
public function getTransferTime(): ?float
|
||||
{
|
||||
return $this->transferTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all of the handler specific transfer data.
|
||||
*/
|
||||
public function getHandlerStats(): array
|
||||
{
|
||||
return $this->handlerStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific handler statistic from the handler by name.
|
||||
*
|
||||
* @param string $stat Handler specific transfer stat to retrieve.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getHandlerStat(string $stat)
|
||||
{
|
||||
return $this->handlerStats[$stat] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
+942
@@ -0,0 +1,942 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\InvalidArgumentException;
|
||||
use GuzzleHttp\Handler\CurlHandler;
|
||||
use GuzzleHttp\Handler\CurlMultiHandler;
|
||||
use GuzzleHttp\Handler\CurlShareHandleState;
|
||||
use GuzzleHttp\Handler\CurlVersion;
|
||||
use GuzzleHttp\Handler\Proxy;
|
||||
use GuzzleHttp\Handler\StreamHandler;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
final class Utils
|
||||
{
|
||||
/**
|
||||
* Debug function used to describe the provided value type and class.
|
||||
*
|
||||
* @param mixed $input
|
||||
*
|
||||
* @return string Returns a string containing the type of the variable and
|
||||
* if a class is provided, the class name.
|
||||
*
|
||||
* @deprecated Utils::describeType() will be removed in guzzlehttp/guzzle:8.0. Use get_debug_type() instead.
|
||||
*/
|
||||
public static function describeType($input): string
|
||||
{
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/guzzle',
|
||||
'7.12',
|
||||
'%s() is deprecated and will be removed in 8.0. Use get_debug_type() instead.',
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
switch (\gettype($input)) {
|
||||
case 'object':
|
||||
return 'object('.\get_class($input).')';
|
||||
case 'array':
|
||||
return 'array('.\count($input).')';
|
||||
default:
|
||||
\ob_start();
|
||||
\var_dump($input);
|
||||
// normalize float vs double
|
||||
/** @var string $varDumpContent */
|
||||
$varDumpContent = \ob_get_clean();
|
||||
|
||||
return \str_replace('double(', 'float(', \rtrim($varDumpContent, " \n\r\t\0\x0B"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an array of header lines into an associative array of headers.
|
||||
*
|
||||
* @param iterable $lines Header lines array of strings in the following
|
||||
* format: "Name: Value"
|
||||
*/
|
||||
public static function headersFromLines(iterable $lines): array
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$parts = \explode(':', $line, 2);
|
||||
$headers[\trim($parts[0], " \n\r\t\0\x0B")][] = isset($parts[1]) ? \trim($parts[1], " \n\r\t\0\x0B") : null;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a debug stream based on the provided variable.
|
||||
*
|
||||
* @param mixed $value Optional value
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public static function debugResource($value = null)
|
||||
{
|
||||
if (\is_resource($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (\defined('STDOUT')) {
|
||||
return \STDOUT;
|
||||
}
|
||||
|
||||
return Psr7\Utils::tryFopen('php://output', 'w');
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses and creates a default handler to use based on the environment.
|
||||
*
|
||||
* The returned handler is not wrapped by any default middlewares.
|
||||
*
|
||||
* @param array{transport_sharing?: mixed, max_host_connections?: mixed, max_total_connections?: mixed, multiplex?: 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(array $handlerOptions = []): callable
|
||||
{
|
||||
$sharingMode = CurlShareHandleState::normalizeMode($handlerOptions['transport_sharing'] ?? null, 'transport_sharing');
|
||||
$sharingRequired = self::isTransportSharingRequired($sharingMode);
|
||||
$connectionCapsRequired = self::hasConnectionCapOptions($handlerOptions);
|
||||
$handler = self::createCurlHandler($sharingMode, $handlerOptions);
|
||||
|
||||
if ($sharingRequired && $handler === null) {
|
||||
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 (\ini_get('allow_url_fopen')) {
|
||||
return self::addStreamHandler($handler, $sharingMode, $sharingRequired, self::connectionCapOptions($handlerOptions));
|
||||
}
|
||||
|
||||
if ($handler !== null) {
|
||||
return $handler;
|
||||
}
|
||||
|
||||
if ($connectionCapsRequired) {
|
||||
throw new \RuntimeException('Connection cap options require a cap-capable cURL multi handler or the allow_url_fopen ini setting for stream fallback.');
|
||||
}
|
||||
|
||||
throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.');
|
||||
}
|
||||
|
||||
private static function isTransportSharingRequired(string $sharingMode): bool
|
||||
{
|
||||
return $sharingMode === TransportSharing::HANDLER_REQUIRE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{max_host_connections?: mixed, max_total_connections?: mixed} $handlerOptions
|
||||
*/
|
||||
private static function hasConnectionCapOptions(array $handlerOptions): bool
|
||||
{
|
||||
return self::connectionCapOptions($handlerOptions) !== [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{max_host_connections?: mixed, max_total_connections?: mixed, multiplex?: mixed} $handlerOptions
|
||||
*
|
||||
* @return (callable(RequestInterface, array): Promise\PromiseInterface)|null
|
||||
*/
|
||||
private static function createCurlHandler(string $sharingMode, array $handlerOptions): ?callable
|
||||
{
|
||||
if (!\defined('CURLOPT_CUSTOMREQUEST') || !CurlVersion::supportsCurlHandler()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$connectionCapOptions = self::connectionCapOptions($handlerOptions);
|
||||
if ($connectionCapOptions !== [] && (!CurlVersion::supportsConnectionCaps() || !\function_exists('curl_multi_exec'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$curlHandlerOptions = self::createCurlHandlerOptions($sharingMode);
|
||||
$curlMultiHandlerOptions = $curlHandlerOptions + $connectionCapOptions;
|
||||
if (($handlerOptions['multiplex'] ?? null) === Multiplexing::NONE) {
|
||||
// Forwarded to the CurlMultiHandler only: CurlHandler and
|
||||
// StreamHandler validate known options, and both satisfy NONE
|
||||
// per-request without a handler option.
|
||||
$curlMultiHandlerOptions['multiplex'] = Multiplexing::NONE;
|
||||
}
|
||||
|
||||
if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) {
|
||||
$multiHandler = new CurlMultiHandler($curlMultiHandlerOptions);
|
||||
|
||||
if ($connectionCapOptions !== []) {
|
||||
// Connection caps only govern transfers on the multi handle, so
|
||||
// the synchronous CurlHandler fast path would escape them.
|
||||
return $multiHandler;
|
||||
}
|
||||
|
||||
return Proxy::wrapSync($multiHandler, new CurlHandler($curlHandlerOptions));
|
||||
}
|
||||
|
||||
if ($connectionCapOptions === [] && \function_exists('curl_exec')) {
|
||||
return new CurlHandler($curlHandlerOptions);
|
||||
}
|
||||
|
||||
if (\function_exists('curl_multi_exec')) {
|
||||
return new CurlMultiHandler($curlMultiHandlerOptions);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function createCurlHandlerOptions(string $sharingMode): array
|
||||
{
|
||||
if ($sharingMode === TransportSharing::NONE) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$shareState = CurlShareHandleState::fromOption($sharingMode);
|
||||
|
||||
return $shareState === null ? [] : ['transport_sharing' => $shareState];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{max_host_connections?: mixed, max_total_connections?: mixed} $handlerOptions
|
||||
*
|
||||
* @return array{max_host_connections?: int, max_total_connections?: int}
|
||||
*/
|
||||
private static function connectionCapOptions(array $handlerOptions): array
|
||||
{
|
||||
$options = [];
|
||||
foreach (['max_host_connections', 'max_total_connections'] as $capOption) {
|
||||
$value = $handlerOptions[$capOption] ?? null;
|
||||
if ($value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\is_int($value) || $value < 1) {
|
||||
throw new InvalidArgumentException(\sprintf('%s must be a positive integer.', $capOption));
|
||||
}
|
||||
|
||||
$options[$capOption] = $value;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param (callable(RequestInterface, array): Promise\PromiseInterface)|null $handler
|
||||
* @param array{max_host_connections?: int, max_total_connections?: int} $connectionCapOptions
|
||||
*
|
||||
* @return callable(RequestInterface, array): Promise\PromiseInterface
|
||||
*/
|
||||
private static function addStreamHandler(?callable $handler, string $sharingMode, bool $sharingRequired, array $connectionCapOptions): callable
|
||||
{
|
||||
$streamHandler = new StreamHandler(['transport_sharing' => $sharingMode] + $connectionCapOptions);
|
||||
|
||||
if ($handler === null) {
|
||||
return $streamHandler;
|
||||
}
|
||||
|
||||
if (!$sharingRequired) {
|
||||
$handler = Proxy::wrapTlsFallback($handler, $streamHandler);
|
||||
}
|
||||
|
||||
return Proxy::wrapStreaming($handler, $streamHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default User-Agent string to use with Guzzle.
|
||||
*/
|
||||
public static function defaultUserAgent(): string
|
||||
{
|
||||
return sprintf('GuzzleHttp/%d', ClientInterface::MAJOR_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default cacert bundle for the current system.
|
||||
*
|
||||
* First, the openssl.cafile and curl.cainfo php.ini settings are checked.
|
||||
* If those settings are not configured, then the common locations for
|
||||
* bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
|
||||
* and Windows are checked. If any of these file locations are found on
|
||||
* disk, they will be utilized.
|
||||
*
|
||||
* Note: the result of this function is cached for subsequent calls.
|
||||
*
|
||||
* @throws \RuntimeException if no bundle can be found.
|
||||
*
|
||||
* @deprecated Utils::defaultCaBundle will be removed in guzzlehttp/guzzle:8.0. This method is not needed in PHP 5.6+.
|
||||
*/
|
||||
public static function defaultCaBundle(): string
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. This method is not needed in PHP 5.6+.', __METHOD__);
|
||||
|
||||
static $cached = null;
|
||||
static $cafiles = [
|
||||
// Red Hat, CentOS, Fedora (provided by the ca-certificates package)
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
// Ubuntu, Debian (provided by the ca-certificates package)
|
||||
'/etc/ssl/certs/ca-certificates.crt',
|
||||
// FreeBSD (provided by the ca_root_nss package)
|
||||
'/usr/local/share/certs/ca-root-nss.crt',
|
||||
// SLES 12 (provided by the ca-certificates package)
|
||||
'/var/lib/ca-certificates/ca-bundle.pem',
|
||||
// OS X provided by homebrew (using the default path)
|
||||
'/usr/local/etc/openssl/cert.pem',
|
||||
// Google app engine
|
||||
'/etc/ca-certificates.crt',
|
||||
// Windows?
|
||||
'C:\\windows\\system32\\curl-ca-bundle.crt',
|
||||
'C:\\windows\\curl-ca-bundle.crt',
|
||||
];
|
||||
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if ($ca = \ini_get('openssl.cafile')) {
|
||||
return $cached = $ca;
|
||||
}
|
||||
|
||||
if ($ca = \ini_get('curl.cainfo')) {
|
||||
return $cached = $ca;
|
||||
}
|
||||
|
||||
foreach ($cafiles as $filename) {
|
||||
if (\file_exists($filename)) {
|
||||
return $cached = $filename;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException(
|
||||
<<< EOT
|
||||
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://github.com/guzzle/guzzle/blob/7.15/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.se/ca/cacert.pem. Once you have a CA bundle available on
|
||||
disk, you can set the 'openssl.cafile' PHP ini setting to point to the path to
|
||||
the file, allowing you to omit the 'verify' request option. See
|
||||
https://curl.se/docs/sslcerts.html for more information.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an associative array of lowercase header names to the actual
|
||||
* header casing.
|
||||
*/
|
||||
public static function normalizeHeaderKeys(array $headers): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (\array_keys($headers) as $key) {
|
||||
$result[Psr7\Utils::asciiToLower((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.
|
||||
*
|
||||
* This method will strip a port from the host if it is present. Domain
|
||||
* patterns are matched case-insensitively. Exact IP literal patterns are
|
||||
* matched by their normalized binary address.
|
||||
*
|
||||
* Areas are matched in the following cases:
|
||||
* 1. "*" (without quotes) always matches any hosts.
|
||||
* 2. An exact domain or IP literal match.
|
||||
* 3. A bare domain matches itself and its subdomains. e.g. 'mit.edu' will
|
||||
* match 'mit.edu' and 'foo.mit.edu'.
|
||||
* 4. The area starts with "." and the area is the last part of the host. e.g.
|
||||
* '.mit.edu' will match any host that ends with '.mit.edu'.
|
||||
* 5. IP CIDR entries match IP literal hosts. e.g. '192.168.0.0/16' will
|
||||
* match '192.168.1.10' and 'fd00::/8' will match '[fd00::1]'.
|
||||
*
|
||||
* @param string $host Host to check against the patterns.
|
||||
* @param string[] $noProxyArray An array of host or CIDR patterns.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function isHostInNoProxy(string $host, array $noProxyArray): bool
|
||||
{
|
||||
if (\strlen($host) === 0) {
|
||||
throw new InvalidArgumentException('Empty host provided');
|
||||
}
|
||||
|
||||
$target = self::parseNoProxyHostString($host);
|
||||
if ($target === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::matchesNoProxyList($target, $noProxyArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided URI matches any of the no proxy areas.
|
||||
*
|
||||
* Matching follows the same rules as isHostInNoProxy(), with the
|
||||
* addition that areas may carry a port (e.g. "example.com:8080" or
|
||||
* "[::1]:8080") which is compared against the URI port (or the scheme
|
||||
* default port when the URI has none).
|
||||
*
|
||||
* @param mixed $noProxy No-proxy host, host-and-port, or CIDR patterns.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function isUriInNoProxy(UriInterface $uri, $noProxy): bool
|
||||
{
|
||||
if (\is_string($noProxy)) {
|
||||
$noProxy = \explode(',', $noProxy);
|
||||
}
|
||||
|
||||
if (!\is_array($noProxy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$target = self::parseNoProxyTarget($uri);
|
||||
if ($target === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::matchesNoProxyList($target, $noProxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{type: string, value: string, port: int|null, matchesRoot: bool} $target
|
||||
* @param array<array-key, mixed> $noProxy
|
||||
*/
|
||||
private static function matchesNoProxyList(array $target, array $noProxy): bool
|
||||
{
|
||||
foreach ($noProxy as $area) {
|
||||
if (!\is_string($area)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$area = \trim($area, " \n\r\t\0\x0B");
|
||||
|
||||
// Always match on wildcards.
|
||||
if ($area === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$rule = self::parseNoProxyRule($area);
|
||||
if ($rule !== null && self::noProxyRuleMatches($target, $rule)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null
|
||||
*/
|
||||
private static function parseNoProxyTarget(UriInterface $uri): ?array
|
||||
{
|
||||
$host = $uri->getHost();
|
||||
if ($host === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::parseNoProxyHost($host, $uri->getPort() ?? self::getDefaultPort($uri->getScheme()), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null
|
||||
*/
|
||||
private static function parseNoProxyHostString(string $host): ?array
|
||||
{
|
||||
$hostAndPort = self::splitNoProxyHostAndPort($host);
|
||||
if ($hostAndPort === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$host] = $hostAndPort;
|
||||
|
||||
return self::parseNoProxyHost($host, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, value: string, port: int|null, matchesRoot: bool}|array{type: string, value: string, prefix: int}|null
|
||||
*/
|
||||
private static function parseNoProxyRule(string $area): ?array
|
||||
{
|
||||
$area = \trim($area, " \n\r\t\0\x0B");
|
||||
if ($area === '' || $area === '*') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (\strpos($area, '/') !== false) {
|
||||
return self::parseNoProxyCidrRule($area);
|
||||
}
|
||||
|
||||
$matchesRoot = true;
|
||||
if ($area[0] === '.') {
|
||||
$matchesRoot = false;
|
||||
$area = \substr($area, 1);
|
||||
}
|
||||
|
||||
$hostAndPort = self::splitNoProxyHostAndPort($area);
|
||||
if ($hostAndPort === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$host, $port] = $hostAndPort;
|
||||
|
||||
if ($host === '*') {
|
||||
if (!$matchesRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'wildcard',
|
||||
'value' => '*',
|
||||
'port' => $port,
|
||||
'matchesRoot' => true,
|
||||
];
|
||||
}
|
||||
|
||||
$rule = self::parseNoProxyHost($host, $port, $matchesRoot);
|
||||
if ($rule !== null && !$matchesRoot && $rule['type'] === 'ip') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null
|
||||
*/
|
||||
private static function parseNoProxyHost(string $host, ?int $port, bool $matchesRoot): ?array
|
||||
{
|
||||
if ($host !== '' && $host[0] === '[') {
|
||||
if (\substr($host, -1) !== ']') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$address = \substr($host, 1, -1);
|
||||
if (!\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$host = $address;
|
||||
}
|
||||
|
||||
$packedIp = self::packIpAddress($host);
|
||||
if ($packedIp !== false) {
|
||||
return [
|
||||
'type' => 'ip',
|
||||
'value' => $packedIp,
|
||||
'port' => $port,
|
||||
'matchesRoot' => $matchesRoot,
|
||||
];
|
||||
}
|
||||
|
||||
if ($host === '' || \strpos($host, ':') !== false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Normalize a single DNS root dot for no-proxy domain matching.
|
||||
if (\substr($host, -1) === '.') {
|
||||
$host = \substr($host, 0, -1);
|
||||
if ($host === '') {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'domain',
|
||||
'value' => Psr7\Utils::asciiToLower($host),
|
||||
'port' => $port,
|
||||
'matchesRoot' => $matchesRoot,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: int|null}|null
|
||||
*/
|
||||
private static function splitNoProxyHostAndPort(string $area): ?array
|
||||
{
|
||||
if ($area !== '' && $area[0] === '[') {
|
||||
$closingBracket = \strpos($area, ']');
|
||||
if ($closingBracket === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$host = \substr($area, 0, $closingBracket + 1);
|
||||
$tail = \substr($area, $closingBracket + 1);
|
||||
if ($tail === '') {
|
||||
return [$host, null];
|
||||
}
|
||||
|
||||
if ($tail[0] !== ':') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$port = self::parseNoProxyPort(\substr($tail, 1));
|
||||
|
||||
return $port === null ? null : [$host, $port];
|
||||
}
|
||||
|
||||
if (self::packIpAddress($area) !== false) {
|
||||
return [$area, null];
|
||||
}
|
||||
|
||||
$colon = \strrpos($area, ':');
|
||||
if ($colon === false) {
|
||||
return [$area, null];
|
||||
}
|
||||
|
||||
$port = self::parseNoProxyPort(\substr($area, $colon + 1));
|
||||
if ($port === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [\substr($area, 0, $colon), $port];
|
||||
}
|
||||
|
||||
private static function parseNoProxyPort(string $port): ?int
|
||||
{
|
||||
return self::parseBoundedUnsignedInteger($port, 65535);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type: string, value: string, prefix: int}|null
|
||||
*/
|
||||
private static function parseNoProxyCidrRule(string $area): ?array
|
||||
{
|
||||
$slash = \strpos($area, '/');
|
||||
if ($slash === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$prefix = \substr($area, $slash + 1);
|
||||
|
||||
$network = \substr($area, 0, $slash);
|
||||
if ($network !== '' && $network[0] === '[' && \substr($network, -1) === ']') {
|
||||
$network = \substr($network, 1, -1);
|
||||
}
|
||||
|
||||
$network = self::packIpAddress($network);
|
||||
if ($network === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$prefix = self::parseBoundedUnsignedInteger($prefix, \strlen($network) * 8);
|
||||
if ($prefix === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'cidr',
|
||||
'value' => $network,
|
||||
'prefix' => $prefix,
|
||||
];
|
||||
}
|
||||
|
||||
private static function parseBoundedUnsignedInteger(string $value, int $max): ?int
|
||||
{
|
||||
if ($value === '' || !\ctype_digit($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$normalized = \ltrim($value, '0');
|
||||
$normalized = $normalized === '' ? '0' : $normalized;
|
||||
$limit = (string) $max;
|
||||
|
||||
if (\strlen($normalized) > \strlen($limit) || (\strlen($normalized) === \strlen($limit) && \strcmp($normalized, $limit) > 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{type: string, value: string, port: int|null, matchesRoot: bool} $target
|
||||
* @param array{type: string, value: string, port?: int|null, matchesRoot?: bool, prefix?: int|null} $rule
|
||||
*/
|
||||
private static function noProxyRuleMatches(array $target, array $rule): bool
|
||||
{
|
||||
if ($rule['type'] === 'wildcard') {
|
||||
return ($rule['port'] ?? null) === null || $rule['port'] === $target['port'];
|
||||
}
|
||||
|
||||
if ($rule['type'] === 'cidr') {
|
||||
if ($target['type'] !== 'ip' || !isset($rule['prefix'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\strlen($target['value']) !== \strlen($rule['value'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::ipMatchesPrefix($target['value'], $rule['value'], $rule['prefix']);
|
||||
}
|
||||
|
||||
if (($rule['port'] ?? null) !== null && $rule['port'] !== $target['port']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($rule['type'] !== $target['type']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($rule['type'] === 'ip') {
|
||||
return $rule['value'] === $target['value'];
|
||||
}
|
||||
|
||||
if (($rule['matchesRoot'] ?? false) && $target['value'] === $rule['value']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$suffix = '.'.$rule['value'];
|
||||
|
||||
return \substr($target['value'], -\strlen($suffix)) === $suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
private static function packIpAddress(string $ip)
|
||||
{
|
||||
if (!\filter_var($ip, \FILTER_VALIDATE_IP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \inet_pton($ip);
|
||||
}
|
||||
|
||||
private static function ipMatchesPrefix(string $address, string $network, int $prefix): bool
|
||||
{
|
||||
$fullBytes = \intdiv($prefix, 8);
|
||||
$remainingBits = $prefix % 8;
|
||||
|
||||
if ($fullBytes > 0 && \substr($address, 0, $fullBytes) !== \substr($network, 0, $fullBytes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($remainingBits === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$mask = (0xFF << (8 - $remainingBits)) & 0xFF;
|
||||
|
||||
return (\ord($address[$fullBytes]) & $mask) === (\ord($network[$fullBytes]) & $mask);
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param string $json JSON data to parse
|
||||
* @param bool $assoc When true, returned objects will be converted
|
||||
* into associative arrays.
|
||||
* @param int $depth User specified recursion depth.
|
||||
* @param int $options Bitmask of JSON decode options.
|
||||
*
|
||||
* @return object|array|string|int|float|bool|null
|
||||
*
|
||||
* @throws InvalidArgumentException if the JSON cannot be decoded.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.json-decode.php
|
||||
* @deprecated Utils::jsonDecode() will be removed in guzzlehttp/guzzle:8.0. Use PHP's json_decode() instead.
|
||||
*/
|
||||
public static function jsonDecode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.15', '%s() is deprecated and will be removed in 8.0. Use PHP\'s json_decode() instead.', __METHOD__);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for JSON encoding that throws when an error occurs.
|
||||
*
|
||||
* @param mixed $value The value being encoded
|
||||
* @param int $options JSON encode option bitmask
|
||||
* @param int $depth Set the maximum depth. Must be greater than zero.
|
||||
*
|
||||
* @throws InvalidArgumentException if the JSON cannot be encoded.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.json-encode.php
|
||||
* @deprecated Utils::jsonEncode() will be removed in guzzlehttp/guzzle:8.0. Use PHP's json_encode() instead.
|
||||
*/
|
||||
public static function jsonEncode($value, int $options = 0, int $depth = 512): string
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.15', '%s() is deprecated and will be removed in 8.0. Use PHP\'s json_encode() instead.', __METHOD__);
|
||||
|
||||
$json = \json_encode($value, $options, $depth);
|
||||
if (\JSON_ERROR_NONE !== \json_last_error()) {
|
||||
throw new InvalidArgumentException('json_encode error: '.\json_last_error_msg());
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the hrtime() or microtime() functions
|
||||
* (depending on the PHP version, one of the two is used)
|
||||
*
|
||||
* @return float UNIX timestamp
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function currentTime(): float
|
||||
{
|
||||
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.',
|
||||
\get_debug_type($value)
|
||||
);
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('idn_conversion must be true, false, null, or an integer IDNA_* bitmask');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function idnUriConvert(UriInterface $uri, int $options = 0): UriInterface
|
||||
{
|
||||
if ($uri->getHost()) {
|
||||
$asciiHost = self::idnToAsci($uri->getHost(), $options, $info);
|
||||
if ($asciiHost === false) {
|
||||
$errorBitSet = $info['errors'] ?? 0;
|
||||
|
||||
$errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool {
|
||||
return substr($name, 0, 11) === 'IDNA_ERROR_';
|
||||
});
|
||||
|
||||
$errors = [];
|
||||
foreach ($errorConstants as $errorConstant) {
|
||||
if ($errorBitSet & constant($errorConstant)) {
|
||||
$errors[] = $errorConstant;
|
||||
}
|
||||
}
|
||||
|
||||
$errorMessage = 'IDN conversion failed';
|
||||
if ($errors) {
|
||||
$errorMessage .= ' (errors: '.implode(', ', $errors).')';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException($errorMessage);
|
||||
}
|
||||
if ($uri->getHost() !== $asciiHost) {
|
||||
// Replace URI only if the ASCII version is different
|
||||
$uri = $uri->withHost($asciiHost);
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function getenv(string $name): ?string
|
||||
{
|
||||
if (isset($_SERVER[$name])) {
|
||||
return (string) $_SERVER[$name];
|
||||
}
|
||||
|
||||
if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
private static function idnToAsci(string $domain, int $options, ?array &$info = [])
|
||||
{
|
||||
if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) {
|
||||
return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info);
|
||||
}
|
||||
|
||||
throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old');
|
||||
}
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
/**
|
||||
* Debug function used to describe the provided value type and class.
|
||||
*
|
||||
* @param mixed $input Any type of variable to describe the type of. This
|
||||
* parameter misses a typehint because of that.
|
||||
*
|
||||
* @return string Returns a string containing the type of the variable and
|
||||
* if a class is provided, the class name.
|
||||
*
|
||||
* @deprecated describe_type will be removed in guzzlehttp/guzzle:8.0. Use get_debug_type() instead.
|
||||
*/
|
||||
function describe_type($input): string
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use get_debug_type() instead.', __FUNCTION__);
|
||||
|
||||
switch (\gettype($input)) {
|
||||
case 'object':
|
||||
return 'object('.\get_class($input).')';
|
||||
case 'array':
|
||||
return 'array('.\count($input).')';
|
||||
default:
|
||||
\ob_start();
|
||||
\var_dump($input);
|
||||
// normalize float vs double
|
||||
/** @var string $varDumpContent */
|
||||
$varDumpContent = \ob_get_clean();
|
||||
|
||||
return \str_replace('double(', 'float(', \rtrim($varDumpContent, " \n\r\t\0\x0B"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an array of header lines into an associative array of headers.
|
||||
*
|
||||
* @param iterable $lines Header lines array of strings in the following
|
||||
* format: "Name: Value"
|
||||
*
|
||||
* @deprecated headers_from_lines will be removed in guzzlehttp/guzzle:8.0. Use Utils::headersFromLines instead.
|
||||
*/
|
||||
function headers_from_lines(iterable $lines): array
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use Utils::headersFromLines() instead.', __FUNCTION__);
|
||||
|
||||
return Utils::headersFromLines($lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a debug stream based on the provided variable.
|
||||
*
|
||||
* @param mixed $value Optional value
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @deprecated debug_resource will be removed in guzzlehttp/guzzle:8.0. Use Utils::debugResource instead.
|
||||
*/
|
||||
function debug_resource($value = null)
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use Utils::debugResource() instead.', __FUNCTION__);
|
||||
|
||||
return Utils::debugResource($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses and creates a default handler to use based on the environment.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @throws \RuntimeException if no viable Handler is available.
|
||||
*
|
||||
* @deprecated choose_handler will be removed in guzzlehttp/guzzle:8.0. Use Utils::chooseHandler instead.
|
||||
*/
|
||||
function choose_handler(): callable
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use Utils::chooseHandler() instead.', __FUNCTION__);
|
||||
|
||||
return Utils::chooseHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default User-Agent string to use with Guzzle.
|
||||
*
|
||||
* @deprecated default_user_agent will be removed in guzzlehttp/guzzle:8.0. Use Utils::defaultUserAgent instead.
|
||||
*/
|
||||
function default_user_agent(): string
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use Utils::defaultUserAgent() instead.', __FUNCTION__);
|
||||
|
||||
return Utils::defaultUserAgent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default cacert bundle for the current system.
|
||||
*
|
||||
* First, the openssl.cafile and curl.cainfo php.ini settings are checked.
|
||||
* If those settings are not configured, then the common locations for
|
||||
* bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
|
||||
* and Windows are checked. If any of these file locations are found on
|
||||
* disk, they will be utilized.
|
||||
*
|
||||
* Note: the result of this function is cached for subsequent calls.
|
||||
*
|
||||
* @throws \RuntimeException if no bundle can be found.
|
||||
*
|
||||
* @deprecated default_ca_bundle will be removed in guzzlehttp/guzzle:8.0. This function is not needed in PHP 5.6+.
|
||||
*/
|
||||
function default_ca_bundle(): string
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. This function is not needed in PHP 5.6+.', __FUNCTION__);
|
||||
|
||||
static $cached = null;
|
||||
static $cafiles = [
|
||||
// Red Hat, CentOS, Fedora (provided by the ca-certificates package)
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
// Ubuntu, Debian (provided by the ca-certificates package)
|
||||
'/etc/ssl/certs/ca-certificates.crt',
|
||||
// FreeBSD (provided by the ca_root_nss package)
|
||||
'/usr/local/share/certs/ca-root-nss.crt',
|
||||
// SLES 12 (provided by the ca-certificates package)
|
||||
'/var/lib/ca-certificates/ca-bundle.pem',
|
||||
// OS X provided by homebrew (using the default path)
|
||||
'/usr/local/etc/openssl/cert.pem',
|
||||
// Google app engine
|
||||
'/etc/ca-certificates.crt',
|
||||
// Windows?
|
||||
'C:\\windows\\system32\\curl-ca-bundle.crt',
|
||||
'C:\\windows\\curl-ca-bundle.crt',
|
||||
];
|
||||
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if ($ca = \ini_get('openssl.cafile')) {
|
||||
return $cached = $ca;
|
||||
}
|
||||
|
||||
if ($ca = \ini_get('curl.cainfo')) {
|
||||
return $cached = $ca;
|
||||
}
|
||||
|
||||
foreach ($cafiles as $filename) {
|
||||
if (\file_exists($filename)) {
|
||||
return $cached = $filename;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException(
|
||||
<<< EOT
|
||||
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://github.com/guzzle/guzzle/blob/7.15/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.se/ca/cacert.pem. Once you have a CA bundle available on
|
||||
disk, you can set the 'openssl.cafile' PHP ini setting to point to the path to
|
||||
the file, allowing you to omit the 'verify' request option. See
|
||||
https://curl.se/docs/sslcerts.html for more information.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an associative array of lowercase header names to the actual
|
||||
* header casing.
|
||||
*
|
||||
* @deprecated normalize_header_keys will be removed in guzzlehttp/guzzle:8.0. Use Utils::normalizeHeaderKeys instead.
|
||||
*/
|
||||
function normalize_header_keys(array $headers): array
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use Utils::normalizeHeaderKeys() instead.', __FUNCTION__);
|
||||
|
||||
return Utils::normalizeHeaderKeys($headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided host matches any of the no proxy areas.
|
||||
*
|
||||
* This method will strip a port from the host if it is present. Each pattern
|
||||
* can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
|
||||
* partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
|
||||
* "baz.foo.com", but ".foo.com" != "foo.com").
|
||||
*
|
||||
* Areas are matched in the following cases:
|
||||
* 1. "*" (without quotes) always matches any hosts.
|
||||
* 2. An exact match.
|
||||
* 3. The area starts with "." and the area is the last part of the host. e.g.
|
||||
* '.mit.edu' will match any host that ends with '.mit.edu'.
|
||||
*
|
||||
* @param string $host Host to check against the patterns.
|
||||
* @param string[] $noProxyArray An array of host patterns.
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*
|
||||
* @deprecated is_host_in_noproxy will be removed in guzzlehttp/guzzle:8.0. Use Utils::isHostInNoProxy instead.
|
||||
*/
|
||||
function is_host_in_noproxy(string $host, array $noProxyArray): bool
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use Utils::isHostInNoProxy() instead.', __FUNCTION__);
|
||||
|
||||
return Utils::isHostInNoProxy($host, $noProxyArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for json_decode that throws when an error occurs.
|
||||
*
|
||||
* @param string $json JSON data to parse
|
||||
* @param bool $assoc When true, returned objects will be converted
|
||||
* into associative arrays.
|
||||
* @param int $depth User specified recursion depth.
|
||||
* @param int $options Bitmask of JSON decode options.
|
||||
*
|
||||
* @return object|array|string|int|float|bool|null
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException if the JSON cannot be decoded.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.json-decode.php
|
||||
* @deprecated json_decode will be removed in guzzlehttp/guzzle:8.0. Use PHP's json_decode() instead.
|
||||
*/
|
||||
function json_decode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use PHP\'s json_decode() instead.', __FUNCTION__);
|
||||
|
||||
if ($depth < 1) {
|
||||
throw new Exception\InvalidArgumentException('json_decode error: Maximum stack depth exceeded');
|
||||
}
|
||||
|
||||
$data = \json_decode($json, $assoc, $depth, $options);
|
||||
if (\JSON_ERROR_NONE !== \json_last_error()) {
|
||||
throw new Exception\InvalidArgumentException('json_decode error: '.\json_last_error_msg());
|
||||
}
|
||||
|
||||
/** @var object|array|string|int|float|bool|null $data */
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for JSON encoding that throws when an error occurs.
|
||||
*
|
||||
* @param mixed $value The value being encoded
|
||||
* @param int $options JSON encode option bitmask
|
||||
* @param int $depth Set the maximum depth. Must be greater than zero.
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException if the JSON cannot be encoded.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.json-encode.php
|
||||
* @deprecated json_encode will be removed in guzzlehttp/guzzle:8.0. Use PHP's json_encode() instead.
|
||||
*/
|
||||
function json_encode($value, int $options = 0, int $depth = 512): string
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s() is deprecated and will be removed in 8.0. Use PHP\'s json_encode() instead.', __FUNCTION__);
|
||||
|
||||
/** @var positive-int $depth */
|
||||
$json = \json_encode($value, $options, $depth);
|
||||
if (\JSON_ERROR_NONE !== \json_last_error()) {
|
||||
throw new Exception\InvalidArgumentException('json_encode error: '.\json_last_error_msg());
|
||||
}
|
||||
|
||||
/** @var non-empty-string $json */
|
||||
return $json;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
// Don't redefine the functions if included multiple times.
|
||||
if (!\function_exists('GuzzleHttp\describe_type')) {
|
||||
require __DIR__.'/functions.php';
|
||||
}
|
||||
Reference in New Issue
Block a user