first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
<?php
namespace GuzzleHttp\Subscriber;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Cookie\CookieJarInterface;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
/**
* Adds, extracts, and persists cookies between HTTP requests
*/
class Cookie implements SubscriberInterface
{
/** @var CookieJarInterface */
private $cookieJar;
/**
* @param CookieJarInterface $cookieJar Cookie jar used to hold cookies
*/
public function __construct(CookieJarInterface $cookieJar = null)
{
$this->cookieJar = $cookieJar ?: new CookieJar();
}
public function getEvents()
{
// Fire the cookie plugin complete event before redirecting
return [
'before' => ['onBefore'],
'complete' => ['onComplete', RequestEvents::REDIRECT_RESPONSE + 10]
];
}
/**
* Get the cookie cookieJar
*
* @return CookieJarInterface
*/
public function getCookieJar()
{
return $this->cookieJar;
}
public function onBefore(BeforeEvent $event)
{
$this->cookieJar->addCookieHeader($event->getRequest());
}
public function onComplete(CompleteEvent $event)
{
$this->cookieJar->extractCookies(
$event->getRequest(),
$event->getResponse()
);
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace GuzzleHttp\Subscriber;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\ErrorEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Message\ResponseInterface;
/**
* Maintains a list of requests and responses sent using a request or client
*/
class History implements SubscriberInterface, \IteratorAggregate, \Countable
{
/** @var int The maximum number of requests to maintain in the history */
private $limit;
/** @var array Requests and responses that have passed through the plugin */
private $transactions = [];
public function __construct($limit = 10)
{
$this->limit = $limit;
}
public function getEvents()
{
return [
'complete' => ['onComplete', RequestEvents::EARLY],
'error' => ['onError', RequestEvents::EARLY],
];
}
/**
* Convert to a string that contains all request and response headers
*
* @return string
*/
public function __toString()
{
$lines = array();
foreach ($this->transactions as $entry) {
$response = isset($entry['response']) ? $entry['response'] : '';
$lines[] = '> ' . trim($entry['sent_request'])
. "\n\n< " . trim($response) . "\n";
}
return implode("\n", $lines);
}
public function onComplete(CompleteEvent $event)
{
$this->add($event->getRequest(), $event->getResponse());
}
public function onError(ErrorEvent $event)
{
// Only track when no response is present, meaning this didn't ever
// emit a complete event
if (!$event->getResponse()) {
$this->add($event->getRequest());
}
}
/**
* Returns an Iterator that yields associative array values where each
* associative array contains the following key value pairs:
*
* - request: Representing the actual request that was received.
* - sent_request: A clone of the request that will not be mutated.
* - response: The response that was received (if available).
*
* @return \Iterator
*/
public function getIterator()
{
return new \ArrayIterator($this->transactions);
}
/**
* Get all of the requests sent through the plugin.
*
* Requests can be modified after they are logged by the history
* subscriber. By default this method will return the actual request
* instances that were received. Pass true to this method if you wish to
* get copies of the requests that represent the request state when it was
* initially logged by the history subscriber.
*
* @param bool $asSent Set to true to get clones of the requests that have
* not been mutated since the request was received by
* the history subscriber.
*
* @return RequestInterface[]
*/
public function getRequests($asSent = false)
{
return array_map(function ($t) use ($asSent) {
return $asSent ? $t['sent_request'] : $t['request'];
}, $this->transactions);
}
/**
* Get the number of requests in the history
*
* @return int
*/
public function count()
{
return count($this->transactions);
}
/**
* Get the last request sent.
*
* Requests can be modified after they are logged by the history
* subscriber. By default this method will return the actual request
* instance that was received. Pass true to this method if you wish to get
* a copy of the request that represents the request state when it was
* initially logged by the history subscriber.
*
* @param bool $asSent Set to true to get a clone of the last request that
* has not been mutated since the request was received
* by the history subscriber.
*
* @return RequestInterface
*/
public function getLastRequest($asSent = false)
{
return $asSent
? end($this->transactions)['sent_request']
: end($this->transactions)['request'];
}
/**
* Get the last response in the history
*
* @return ResponseInterface|null
*/
public function getLastResponse()
{
return end($this->transactions)['response'];
}
/**
* Clears the history
*/
public function clear()
{
$this->transactions = array();
}
/**
* Add a request to the history
*
* @param RequestInterface $request Request to add
* @param ResponseInterface $response Response of the request
*/
private function add(
RequestInterface $request,
ResponseInterface $response = null
) {
$this->transactions[] = [
'request' => $request,
'sent_request' => clone $request,
'response' => $response
];
if (count($this->transactions) > $this->limit) {
array_shift($this->transactions);
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace GuzzleHttp\Subscriber;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Exception\RequestException;
/**
* Throws exceptions when a 4xx or 5xx response is received
*/
class HttpError implements SubscriberInterface
{
public function getEvents()
{
return ['complete' => ['onComplete', RequestEvents::VERIFY_RESPONSE]];
}
/**
* Throw a RequestException on an HTTP protocol error
*
* @param CompleteEvent $event Emitted event
* @throws RequestException
*/
public function onComplete(CompleteEvent $event)
{
$code = (string) $event->getResponse()->getStatusCode();
// Throw an exception for an unsuccessful response
if ($code[0] >= 4) {
throw RequestException::create(
$event->getRequest(),
$event->getResponse()
);
}
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace GuzzleHttp\Subscriber;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Message\MessageFactory;
use GuzzleHttp\Message\ResponseInterface;
use GuzzleHttp\Stream\StreamInterface;
/**
* Queues mock responses or exceptions and delivers mock responses or
* exceptions in a fifo order.
*/
class Mock implements SubscriberInterface, \Countable
{
/** @var array Array of mock responses / exceptions */
private $queue = [];
/** @var bool Whether or not to consume an entity body when mocking */
private $readBodies;
/** @var MessageFactory */
private $factory;
/**
* @param array $items Array of responses or exceptions to queue
* @param bool $readBodies Set to false to not consume the entity body of
* a request when a mock is served.
*/
public function __construct(array $items = [], $readBodies = true)
{
$this->factory = new MessageFactory();
$this->readBodies = $readBodies;
$this->addMultiple($items);
}
public function getEvents()
{
// Fire the event last, after signing
return ['before' => ['onBefore', RequestEvents::SIGN_REQUEST - 10]];
}
/**
* @throws \OutOfBoundsException|\Exception
*/
public function onBefore(BeforeEvent $event)
{
if (!$item = array_shift($this->queue)) {
throw new \OutOfBoundsException('Mock queue is empty');
} elseif ($item instanceof RequestException) {
throw $item;
}
// Emulate reading a response body
$request = $event->getRequest();
if ($this->readBodies && $request->getBody()) {
while (!$request->getBody()->eof()) {
$request->getBody()->read(8096);
}
}
$saveTo = $event->getRequest()->getConfig()->get('save_to');
if (null !== $saveTo) {
$body = $item->getBody();
if (is_resource($saveTo)) {
fwrite($saveTo, $body);
} elseif (is_string($saveTo)) {
file_put_contents($saveTo, $body);
} elseif ($saveTo instanceof StreamInterface) {
$saveTo->write($body);
}
}
$event->intercept($item);
}
public function count()
{
return count($this->queue);
}
/**
* Add a response to the end of the queue
*
* @param string|ResponseInterface $response Response or path to response file
*
* @return self
* @throws \InvalidArgumentException if a string or Response is not passed
*/
public function addResponse($response)
{
if (is_string($response)) {
$response = file_exists($response)
? $this->factory->fromMessage(file_get_contents($response))
: $this->factory->fromMessage($response);
} elseif (!($response instanceof ResponseInterface)) {
throw new \InvalidArgumentException('Response must a message '
. 'string, response object, or path to a file');
}
$this->queue[] = $response;
return $this;
}
/**
* Add an exception to the end of the queue
*
* @param RequestException $e Exception to throw when the request is executed
*
* @return self
*/
public function addException(RequestException $e)
{
$this->queue[] = $e;
return $this;
}
/**
* Add multiple items to the queue
*
* @param array $items Items to add
*/
public function addMultiple(array $items)
{
foreach ($items as $item) {
if ($item instanceof RequestException) {
$this->addException($item);
} else {
$this->addResponse($item);
}
}
}
/**
* Clear the queue
*/
public function clearQueue()
{
$this->queue = [];
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace GuzzleHttp\Subscriber;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Message\AppliesHeadersInterface;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Mimetypes;
use GuzzleHttp\Stream\StreamInterface;
/**
* Prepares requests with a body before sending
*
* **Request Options**
*
* - expect: Set to true to enable the "Expect: 100-Continue" header for a
* request that send a body. Set to false to disable "Expect: 100-Continue".
* 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.
*/
class Prepare implements SubscriberInterface
{
public function getEvents()
{
return ['before' => ['onBefore', RequestEvents::PREPARE_REQUEST]];
}
public function onBefore(BeforeEvent $event)
{
$request = $event->getRequest();
// Set the appropriate Content-Type for a request if one is not set and
// there are form fields
if (!($body = $request->getBody())) {
return;
}
$this->addContentLength($request, $body);
if ($body instanceof AppliesHeadersInterface) {
// Synchronize the body with the request headers
$body->applyRequestHeaders($request);
} elseif (!$request->hasHeader('Content-Type')) {
$this->addContentType($request, $body);
}
$this->addExpectHeader($request, $body);
}
private function addContentType(
RequestInterface $request,
StreamInterface $body
) {
if (!($uri = $body->getMetadata('uri'))) {
return;
}
// Guess the content-type based on the stream's "uri" metadata value.
// The file extension is used to determine the appropriate mime-type.
if ($contentType = Mimetypes::getInstance()->fromFilename($uri)) {
$request->setHeader('Content-Type', $contentType);
}
}
private function addContentLength(
RequestInterface $request,
StreamInterface $body
) {
// Set the Content-Length header if it can be determined, and never
// send a Transfer-Encoding: chunked and Content-Length header in
// the same request.
if ($request->hasHeader('Content-Length')) {
// Remove transfer-encoding if content-length is set.
$request->removeHeader('Transfer-Encoding');
return;
}
if ($request->hasHeader('Transfer-Encoding')) {
return;
}
if (null !== ($size = $body->getSize())) {
$request->setHeader('Content-Length', $size);
$request->removeHeader('Transfer-Encoding');
} elseif ('1.1' == $request->getProtocolVersion()) {
// Use chunked Transfer-Encoding if there is no determinable
// content-length header and we're using HTTP/1.1.
$request->setHeader('Transfer-Encoding', 'chunked');
$request->removeHeader('Content-Length');
}
}
private function addExpectHeader(
RequestInterface $request,
StreamInterface $body
) {
// Determine if the Expect header should be used
if ($request->hasHeader('Expect')) {
return;
}
$expect = $request->getConfig()['expect'];
// Return if disabled or if you're not using HTTP/1.1
if ($expect === false || $request->getProtocolVersion() !== '1.1') {
return;
}
// The expect header is unconditionally enabled
if ($expect === true) {
$request->setHeader('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
$size = $body->getSize();
if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
$request->setHeader('Expect', '100-Continue');
}
}
}

View File

@@ -0,0 +1,176 @@
<?php
namespace GuzzleHttp\Subscriber;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\CouldNotRewindStreamException;
use GuzzleHttp\Exception\TooManyRedirectsException;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Message\ResponseInterface;
use GuzzleHttp\Url;
/**
* Subscriber used to implement HTTP redirects.
*
* **Request options**
*
* - redirect: Associative array containing the 'max', 'strict', and 'referer'
* keys.
*
* - max: Maximum number of redirects allowed per-request
* - strict: You can use strict redirects by setting this value to ``true``.
* Strict redirects adhere to strict RFC compliant redirection (e.g.,
* redirect POST with POST) vs doing what most clients do (e.g., redirect
* POST request with a GET request).
* - referer: Set to true to automatically add the "Referer" header when a
* redirect request is sent.
* - protocols: Array of allowed protocols. Defaults to 'http' and 'https'.
* When a redirect attempts to utilize a protocol that is not white listed,
* an exception is thrown.
*/
class Redirect implements SubscriberInterface
{
public function getEvents()
{
return ['complete' => ['onComplete', RequestEvents::REDIRECT_RESPONSE]];
}
/**
* Rewind the entity body of the request if needed
*
* @param RequestInterface $redirectRequest
* @throws CouldNotRewindStreamException
*/
public static function rewindEntityBody(RequestInterface $redirectRequest)
{
// Rewind the entity body of the request if needed
if ($body = $redirectRequest->getBody()) {
// Only rewind the body if some of it has been read already, and
// throw an exception if the rewind fails
if ($body->tell() && !$body->seek(0)) {
throw new CouldNotRewindStreamException(
'Unable to rewind the non-seekable request body after redirecting',
$redirectRequest
);
}
}
}
/**
* Called when a request receives a redirect response
*
* @param CompleteEvent $event Event emitted
* @throws TooManyRedirectsException
*/
public function onComplete(CompleteEvent $event)
{
$response = $event->getResponse();
if (substr($response->getStatusCode(), 0, 1) != '3'
|| !$response->hasHeader('Location')
) {
return;
}
$request = $event->getRequest();
$config = $request->getConfig();
// Increment the redirect and initialize the redirect state.
if ($redirectCount = $config['redirect_count']) {
$config['redirect_count'] = ++$redirectCount;
} else {
$config['redirect_scheme'] = $request->getScheme();
$config['redirect_count'] = $redirectCount = 1;
}
$max = $config->getPath('redirect/max') ?: 5;
if ($redirectCount > $max) {
throw new TooManyRedirectsException(
"Will not follow more than {$redirectCount} redirects",
$request
);
}
$this->modifyRedirectRequest($request, $response);
$event->retry();
}
private function modifyRedirectRequest(
RequestInterface $request,
ResponseInterface $response
) {
$config = $request->getConfig();
$protocols = $config->getPath('redirect/protocols') ?: ['http', 'https'];
// 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 && $request->getBody() && !$config->getPath('redirect/strict'))
) {
$request->setMethod('GET');
$request->setBody(null);
}
$previousUrl = $request->getUrl();
$this->setRedirectUrl($request, $response, $protocols);
$this->rewindEntityBody($request);
// 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 ($config->getPath('redirect/referer')
&& ($request->getScheme() == 'https' || $request->getScheme() == $config['redirect_scheme'])
) {
$url = Url::fromString($previousUrl);
$url->setUsername(null);
$url->setPassword(null);
$request->setHeader('Referer', (string) $url);
} else {
$request->removeHeader('Referer');
}
}
/**
* Set the appropriate URL on the request based on the location header
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param array $protocols
*/
private function setRedirectUrl(
RequestInterface $request,
ResponseInterface $response,
array $protocols
) {
$location = $response->getHeader('Location');
$location = Url::fromString($location);
// Combine location with the original URL if it is not absolute.
if (!$location->isAbsolute()) {
$originalUrl = Url::fromString($request->getUrl());
// Remove query string parameters and just take what is present on
// the redirect Location header
$originalUrl->getQuery()->clear();
$location = $originalUrl->combine($location);
}
// Ensure that the redirect URL is allowed based on the protocols.
if (!in_array($location->getScheme(), $protocols)) {
throw new BadResponseException(
sprintf(
'Redirect URL, %s, does not use one of the allowed redirect protocols: %s',
$location,
implode(', ', $protocols)
),
$request,
$response
);
}
$request->setUrl($location);
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;