commit vendor

This commit is contained in:
2025-11-11 14:49:30 +01:00
parent f33121a308
commit 6d03080c00
2436 changed files with 483781 additions and 0 deletions

27
vendor/sabre/http/LICENSE vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (C) 2009-2017 fruux GmbH (https://fruux.com/)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Sabre nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

220
vendor/sabre/http/lib/Auth/AWS.php vendored Normal file
View File

@ -0,0 +1,220 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP\Auth;
use Sabre\HTTP;
/**
* HTTP AWS Authentication handler.
*
* Use this class to leverage amazon's AWS authentication header
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class AWS extends AbstractAuth
{
/**
* The signature supplied by the HTTP client.
*
* @var string
*/
private $signature = null;
/**
* The accesskey supplied by the HTTP client.
*
* @var string
*/
private $accessKey = null;
/**
* An error code, if any.
*
* This value will be filled with one of the ERR_* constants
*
* @var int
*/
public $errorCode = 0;
const ERR_NOAWSHEADER = 1;
const ERR_MD5CHECKSUMWRONG = 2;
const ERR_INVALIDDATEFORMAT = 3;
const ERR_REQUESTTIMESKEWED = 4;
const ERR_INVALIDSIGNATURE = 5;
/**
* Gathers all information from the headers.
*
* This method needs to be called prior to anything else.
*/
public function init(): bool
{
$authHeader = $this->request->getHeader('Authorization');
if (null === $authHeader) {
$this->errorCode = self::ERR_NOAWSHEADER;
return false;
}
$authHeader = explode(' ', $authHeader);
if ('AWS' !== $authHeader[0] || !isset($authHeader[1])) {
$this->errorCode = self::ERR_NOAWSHEADER;
return false;
}
list($this->accessKey, $this->signature) = explode(':', $authHeader[1]);
return true;
}
/**
* Returns the username for the request.
*/
public function getAccessKey(): string
{
return $this->accessKey;
}
/**
* Validates the signature based on the secretKey.
*/
public function validate(string $secretKey): bool
{
$contentMD5 = $this->request->getHeader('Content-MD5');
if ($contentMD5) {
// We need to validate the integrity of the request
$body = $this->request->getBody();
$this->request->setBody($body);
if ($contentMD5 !== base64_encode(md5((string) $body, true))) {
// content-md5 header did not match md5 signature of body
$this->errorCode = self::ERR_MD5CHECKSUMWRONG;
return false;
}
}
if (!$requestDate = $this->request->getHeader('x-amz-date')) {
$requestDate = $this->request->getHeader('Date');
}
if (!$this->validateRFC2616Date((string) $requestDate)) {
return false;
}
$amzHeaders = $this->getAmzHeaders();
$signature = base64_encode(
$this->hmacsha1($secretKey,
$this->request->getMethod()."\n".
$contentMD5."\n".
$this->request->getHeader('Content-type')."\n".
$requestDate."\n".
$amzHeaders.
$this->request->getUrl()
)
);
if ($this->signature !== $signature) {
$this->errorCode = self::ERR_INVALIDSIGNATURE;
return false;
}
return true;
}
/**
* Returns an HTTP 401 header, forcing login.
*
* This should be called when username and password are incorrect, or not supplied at all
*/
public function requireLogin()
{
$this->response->addHeader('WWW-Authenticate', 'AWS');
$this->response->setStatus(401);
}
/**
* Makes sure the supplied value is a valid RFC2616 date.
*
* If we would just use strtotime to get a valid timestamp, we have no way of checking if a
* user just supplied the word 'now' for the date header.
*
* This function also makes sure the Date header is within 15 minutes of the operating
* system date, to prevent replay attacks.
*/
protected function validateRFC2616Date(string $dateHeader): bool
{
$date = HTTP\parseDate($dateHeader);
// Unknown format
if (!$date) {
$this->errorCode = self::ERR_INVALIDDATEFORMAT;
return false;
}
$min = new \DateTime('-15 minutes');
$max = new \DateTime('+15 minutes');
// We allow 15 minutes around the current date/time
if ($date > $max || $date < $min) {
$this->errorCode = self::ERR_REQUESTTIMESKEWED;
return false;
}
return true;
}
/**
* Returns a list of AMZ headers.
*/
protected function getAmzHeaders(): string
{
$amzHeaders = [];
$headers = $this->request->getHeaders();
foreach ($headers as $headerName => $headerValue) {
if (0 === strpos(strtolower($headerName), 'x-amz-')) {
$amzHeaders[strtolower($headerName)] = str_replace(["\r\n"], [' '], $headerValue[0])."\n";
}
}
ksort($amzHeaders);
$headerStr = '';
foreach ($amzHeaders as $h => $v) {
$headerStr .= $h.':'.$v;
}
return $headerStr;
}
/**
* Generates an HMAC-SHA1 signature.
*/
private function hmacsha1(string $key, string $message): string
{
if (function_exists('hash_hmac')) {
return hash_hmac('sha1', $message, $key, true);
}
$blocksize = 64;
if (strlen($key) > $blocksize) {
$key = pack('H*', sha1($key));
}
$key = str_pad($key, $blocksize, chr(0x00));
$ipad = str_repeat(chr(0x36), $blocksize);
$opad = str_repeat(chr(0x5c), $blocksize);
$hmac = pack('H*', sha1(($key ^ $opad).pack('H*', sha1(($key ^ $ipad).$message))));
return $hmac;
}
}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP\Auth;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* HTTP Authentication base class.
*
* This class provides some common functionality for the various base classes.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class AbstractAuth
{
/**
* Authentication realm.
*
* @var string
*/
protected $realm;
/**
* Request object.
*
* @var RequestInterface
*/
protected $request;
/**
* Response object.
*
* @var ResponseInterface
*/
protected $response;
/**
* Creates the object.
*/
public function __construct(string $realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response)
{
$this->realm = $realm;
$this->request = $request;
$this->response = $response;
}
/**
* This method sends the needed HTTP header and statuscode (401) to force
* the user to login.
*/
abstract public function requireLogin();
/**
* Returns the HTTP realm.
*/
public function getRealm(): string
{
return $this->realm;
}
}

60
vendor/sabre/http/lib/Auth/Basic.php vendored Normal file
View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP\Auth;
/**
* HTTP Basic authentication utility.
*
* This class helps you setup basic auth. The process is fairly simple:
*
* 1. Instantiate the class.
* 2. Call getCredentials (this will return null or a user/pass pair)
* 3. If you didn't get valid credentials, call 'requireLogin'
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Basic extends AbstractAuth
{
/**
* This method returns a numeric array with a username and password as the
* only elements.
*
* If no credentials were found, this method returns null.
*
* @return array|null
*/
public function getCredentials()
{
$auth = $this->request->getHeader('Authorization');
if (!$auth) {
return null;
}
if ('basic ' !== strtolower(substr($auth, 0, 6))) {
return null;
}
$credentials = explode(':', base64_decode(substr($auth, 6)), 2);
if (2 !== count($credentials)) {
return null;
}
return $credentials;
}
/**
* This method sends the needed HTTP header and statuscode (401) to force
* the user to login.
*/
public function requireLogin()
{
$this->response->addHeader('WWW-Authenticate', 'Basic realm="'.$this->realm.'", charset="UTF-8"');
$this->response->setStatus(401);
}
}

53
vendor/sabre/http/lib/Auth/Bearer.php vendored Normal file
View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP\Auth;
/**
* HTTP Bearer authentication utility.
*
* This class helps you setup bearer auth. The process is fairly simple:
*
* 1. Instantiate the class.
* 2. Call getToken (this will return null or a token as string)
* 3. If you didn't get a valid token, call 'requireLogin'
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author François Kooman (fkooman@tuxed.net)
* @license http://sabre.io/license/ Modified BSD License
*/
class Bearer extends AbstractAuth
{
/**
* This method returns a string with an access token.
*
* If no token was found, this method returns null.
*
* @return string|null
*/
public function getToken()
{
$auth = $this->request->getHeader('Authorization');
if (!$auth) {
return null;
}
if ('bearer ' !== strtolower(substr($auth, 0, 7))) {
return null;
}
return substr($auth, 7);
}
/**
* This method sends the needed HTTP header and statuscode (401) to force
* authentication.
*/
public function requireLogin()
{
$this->response->addHeader('WWW-Authenticate', 'Bearer realm="'.$this->realm.'"');
$this->response->setStatus(401);
}
}

210
vendor/sabre/http/lib/Auth/Digest.php vendored Normal file
View File

@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP\Auth;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* HTTP Digest Authentication handler.
*
* Use this class for easy http digest authentication.
* Instructions:
*
* 1. Create the object
* 2. Call the setRealm() method with the realm you plan to use
* 3. Call the init method function.
* 4. Call the getUserName() function. This function may return null if no
* authentication information was supplied. Based on the username you
* should check your internal database for either the associated password,
* or the so-called A1 hash of the digest.
* 5. Call either validatePassword() or validateA1(). This will return true
* or false.
* 6. To make sure an authentication prompt is displayed, call the
* requireLogin() method.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Digest extends AbstractAuth
{
/**
* These constants are used in setQOP();.
*/
const QOP_AUTH = 1;
const QOP_AUTHINT = 2;
protected $nonce;
protected $opaque;
protected $digestParts;
protected $A1;
protected $qop = self::QOP_AUTH;
/**
* Initializes the object.
*/
public function __construct(string $realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response)
{
$this->nonce = uniqid();
$this->opaque = md5($realm);
parent::__construct($realm, $request, $response);
}
/**
* Gathers all information from the headers.
*
* This method needs to be called prior to anything else.
*/
public function init()
{
$digest = $this->getDigest();
$this->digestParts = $this->parseDigest((string) $digest);
}
/**
* Sets the quality of protection value.
*
* Possible values are:
* Sabre\HTTP\DigestAuth::QOP_AUTH
* Sabre\HTTP\DigestAuth::QOP_AUTHINT
*
* Multiple values can be specified using logical OR.
*
* QOP_AUTHINT ensures integrity of the request body, but this is not
* supported by most HTTP clients. QOP_AUTHINT also requires the entire
* request body to be md5'ed, which can put strains on CPU and memory.
*/
public function setQOP(int $qop)
{
$this->qop = $qop;
}
/**
* Validates the user.
*
* The A1 parameter should be md5($username . ':' . $realm . ':' . $password);
*/
public function validateA1(string $A1): bool
{
$this->A1 = $A1;
return $this->validate();
}
/**
* Validates authentication through a password. The actual password must be provided here.
* It is strongly recommended not store the password in plain-text and use validateA1 instead.
*/
public function validatePassword(string $password): bool
{
$this->A1 = md5($this->digestParts['username'].':'.$this->realm.':'.$password);
return $this->validate();
}
/**
* Returns the username for the request.
* Returns null if there were none.
*
* @return string|null
*/
public function getUsername()
{
return $this->digestParts['username'] ?? null;
}
/**
* Validates the digest challenge.
*/
protected function validate(): bool
{
if (!is_array($this->digestParts)) {
return false;
}
$A2 = $this->request->getMethod().':'.$this->digestParts['uri'];
if ('auth-int' === $this->digestParts['qop']) {
// Making sure we support this qop value
if (!($this->qop & self::QOP_AUTHINT)) {
return false;
}
// We need to add an md5 of the entire request body to the A2 part of the hash
$body = $this->request->getBody($asString = true);
$this->request->setBody($body);
$A2 .= ':'.md5($body);
} elseif (!($this->qop & self::QOP_AUTH)) {
return false;
}
$A2 = md5($A2);
$validResponse = md5("{$this->A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}");
return $this->digestParts['response'] === $validResponse;
}
/**
* Returns an HTTP 401 header, forcing login.
*
* This should be called when username and password are incorrect, or not supplied at all
*/
public function requireLogin()
{
$qop = '';
switch ($this->qop) {
case self::QOP_AUTH:
$qop = 'auth';
break;
case self::QOP_AUTHINT:
$qop = 'auth-int';
break;
case self::QOP_AUTH | self::QOP_AUTHINT:
$qop = 'auth,auth-int';
break;
}
$this->response->addHeader('WWW-Authenticate', 'Digest realm="'.$this->realm.'",qop="'.$qop.'",nonce="'.$this->nonce.'",opaque="'.$this->opaque.'"');
$this->response->setStatus(401);
}
/**
* This method returns the full digest string.
*
* It should be compatibile with mod_php format and other webservers.
*
* If the header could not be found, null will be returned
*
* @return mixed
*/
public function getDigest()
{
return $this->request->getHeader('Authorization');
}
/**
* Parses the different pieces of the digest string into an array.
*
* This method returns false if an incomplete digest was supplied
*
* @return bool|array
*/
protected function parseDigest(string $digest)
{
// protect against missing data
$needed_parts = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1];
$data = [];
preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $digest, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[2] ?: $m[3];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
}

614
vendor/sabre/http/lib/Client.php vendored Normal file
View File

@ -0,0 +1,614 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
use Sabre\Event\EventEmitter;
use Sabre\Uri;
/**
* A rudimentary HTTP client.
*
* This object wraps PHP's curl extension and provides an easy way to send it a
* Request object, and return a Response object.
*
* This is by no means intended as the next best HTTP client, but it does the
* job and provides a simple integration with the rest of sabre/http.
*
* This client emits the following events:
* beforeRequest(RequestInterface $request)
* afterRequest(RequestInterface $request, ResponseInterface $response)
* error(RequestInterface $request, ResponseInterface $response, bool &$retry, int $retryCount)
* exception(RequestInterface $request, ClientException $e, bool &$retry, int $retryCount)
*
* The beforeRequest event allows you to do some last minute changes to the
* request before it's done, such as adding authentication headers.
*
* The afterRequest event will be emitted after the request is completed
* succesfully.
*
* If a HTTP error is returned (status code higher than 399) the error event is
* triggered. It's possible using this event to retry the request, by setting
* retry to true.
*
* The amount of times a request has retried is passed as $retryCount, which
* can be used to avoid retrying indefinitely. The first time the event is
* called, this will be 0.
*
* It's also possible to intercept specific http errors, by subscribing to for
* example 'error:401'.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Client extends EventEmitter
{
/**
* List of curl settings.
*
* @var array
*/
protected $curlSettings = [];
/**
* Wether or not exceptions should be thrown when a HTTP error is returned.
*
* @var bool
*/
protected $throwExceptions = false;
/**
* The maximum number of times we'll follow a redirect.
*
* @var int
*/
protected $maxRedirects = 5;
protected $headerLinesMap = [];
/**
* Initializes the client.
*/
public function __construct()
{
// See https://github.com/sabre-io/http/pull/115#discussion_r241292068
// Preserve compatibility for sub-classes that implements their own method `parseCurlResult`
$separatedHeaders = __CLASS__ === get_class($this);
$this->curlSettings = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_NOBODY => false,
CURLOPT_USERAGENT => 'sabre-http/'.Version::VERSION.' (http://sabre.io/)',
];
if ($separatedHeaders) {
$this->curlSettings[CURLOPT_HEADERFUNCTION] = [$this, 'receiveCurlHeader'];
} else {
$this->curlSettings[CURLOPT_HEADER] = true;
}
}
protected function receiveCurlHeader($curlHandle, $headerLine)
{
$this->headerLinesMap[(int) $curlHandle][] = $headerLine;
return strlen($headerLine);
}
/**
* Sends a request to a HTTP server, and returns a response.
*/
public function send(RequestInterface $request): ResponseInterface
{
$this->emit('beforeRequest', [$request]);
$retryCount = 0;
$redirects = 0;
do {
$doRedirect = false;
$retry = false;
try {
$response = $this->doRequest($request);
$code = $response->getStatus();
// We are doing in-PHP redirects, because curl's
// FOLLOW_LOCATION throws errors when PHP is configured with
// open_basedir.
//
// https://github.com/fruux/sabre-http/issues/12
if ($redirects < $this->maxRedirects && in_array($code, [301, 302, 307, 308])) {
$oldLocation = $request->getUrl();
// Creating a new instance of the request object.
$request = clone $request;
// Setting the new location
$request->setUrl(Uri\resolve(
$oldLocation,
$response->getHeader('Location')
));
$doRedirect = true;
++$redirects;
}
// This was a HTTP error
if ($code >= 400) {
$this->emit('error', [$request, $response, &$retry, $retryCount]);
$this->emit('error:'.$code, [$request, $response, &$retry, $retryCount]);
}
} catch (ClientException $e) {
$this->emit('exception', [$request, $e, &$retry, $retryCount]);
// If retry was still set to false, it means no event handler
// dealt with the problem. In this case we just re-throw the
// exception.
if (!$retry) {
throw $e;
}
}
if ($retry) {
++$retryCount;
}
} while ($retry || $doRedirect);
$this->emit('afterRequest', [$request, $response]);
if ($this->throwExceptions && $code >= 400) {
throw new ClientHttpException($response);
}
return $response;
}
/**
* Sends a HTTP request asynchronously.
*
* Due to the nature of PHP, you must from time to time poll to see if any
* new responses came in.
*
* After calling sendAsync, you must therefore occasionally call the poll()
* method, or wait().
*/
public function sendAsync(RequestInterface $request, callable $success = null, callable $error = null)
{
$this->emit('beforeRequest', [$request]);
$this->sendAsyncInternal($request, $success, $error);
$this->poll();
}
/**
* This method checks if any http requests have gotten results, and if so,
* call the appropriate success or error handlers.
*
* This method will return true if there are still requests waiting to
* return, and false if all the work is done.
*/
public function poll(): bool
{
// nothing to do?
if (!$this->curlMultiMap) {
return false;
}
do {
$r = curl_multi_exec(
$this->curlMultiHandle,
$stillRunning
);
} while (CURLM_CALL_MULTI_PERFORM === $r);
$messagesInQueue = 0;
do {
messageQueue:
$status = curl_multi_info_read(
$this->curlMultiHandle,
$messagesInQueue
);
if ($status && CURLMSG_DONE === $status['msg']) {
$resourceId = (int) $status['handle'];
list(
$request,
$successCallback,
$errorCallback,
$retryCount) = $this->curlMultiMap[$resourceId];
unset($this->curlMultiMap[$resourceId]);
$curlHandle = $status['handle'];
$curlResult = $this->parseResponse(curl_multi_getcontent($curlHandle), $curlHandle);
$retry = false;
if (self::STATUS_CURLERROR === $curlResult['status']) {
$e = new ClientException($curlResult['curl_errmsg'], $curlResult['curl_errno']);
$this->emit('exception', [$request, $e, &$retry, $retryCount]);
if ($retry) {
++$retryCount;
$this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
goto messageQueue;
}
$curlResult['request'] = $request;
if ($errorCallback) {
$errorCallback($curlResult);
}
} elseif (self::STATUS_HTTPERROR === $curlResult['status']) {
$this->emit('error', [$request, $curlResult['response'], &$retry, $retryCount]);
$this->emit('error:'.$curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]);
if ($retry) {
++$retryCount;
$this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
goto messageQueue;
}
$curlResult['request'] = $request;
if ($errorCallback) {
$errorCallback($curlResult);
}
} else {
$this->emit('afterRequest', [$request, $curlResult['response']]);
if ($successCallback) {
$successCallback($curlResult['response']);
}
}
}
} while ($messagesInQueue > 0);
return count($this->curlMultiMap) > 0;
}
/**
* Processes every HTTP request in the queue, and waits till they are all
* completed.
*/
public function wait()
{
do {
curl_multi_select($this->curlMultiHandle);
$stillRunning = $this->poll();
} while ($stillRunning);
}
/**
* If this is set to true, the Client will automatically throw exceptions
* upon HTTP errors.
*
* This means that if a response came back with a status code greater than
* or equal to 400, we will throw a ClientHttpException.
*
* This only works for the send() method. Throwing exceptions for
* sendAsync() is not supported.
*/
public function setThrowExceptions(bool $throwExceptions)
{
$this->throwExceptions = $throwExceptions;
}
/**
* Adds a CURL setting.
*
* These settings will be included in every HTTP request.
*
* @param mixed $value
*/
public function addCurlSetting(int $name, $value)
{
$this->curlSettings[$name] = $value;
}
/**
* This method is responsible for performing a single request.
*/
protected function doRequest(RequestInterface $request): ResponseInterface
{
$settings = $this->createCurlSettingsArray($request);
if (!$this->curlHandle) {
$this->curlHandle = curl_init();
} else {
curl_reset($this->curlHandle);
}
curl_setopt_array($this->curlHandle, $settings);
$response = $this->curlExec($this->curlHandle);
$response = $this->parseResponse($response, $this->curlHandle);
if (self::STATUS_CURLERROR === $response['status']) {
throw new ClientException($response['curl_errmsg'], $response['curl_errno']);
}
return $response['response'];
}
/**
* Cached curl handle.
*
* By keeping this resource around for the lifetime of this object, things
* like persistent connections are possible.
*
* @var resource
*/
private $curlHandle;
/**
* Handler for curl_multi requests.
*
* The first time sendAsync is used, this will be created.
*
* @var resource
*/
private $curlMultiHandle;
/**
* Has a list of curl handles, as well as their associated success and
* error callbacks.
*
* @var array
*/
private $curlMultiMap = [];
/**
* Turns a RequestInterface object into an array with settings that can be
* fed to curl_setopt.
*/
protected function createCurlSettingsArray(RequestInterface $request): array
{
$settings = $this->curlSettings;
switch ($request->getMethod()) {
case 'HEAD':
$settings[CURLOPT_NOBODY] = true;
$settings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
break;
case 'GET':
$settings[CURLOPT_CUSTOMREQUEST] = 'GET';
break;
default:
$body = $request->getBody();
if (is_resource($body)) {
// This needs to be set to PUT, regardless of the actual
// method used. Without it, INFILE will be ignored for some
// reason.
$settings[CURLOPT_PUT] = true;
$settings[CURLOPT_INFILE] = $request->getBody();
} else {
// For security we cast this to a string. If somehow an array could
// be passed here, it would be possible for an attacker to use @ to
// post local files.
$settings[CURLOPT_POSTFIELDS] = (string) $body;
}
$settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
break;
}
$nHeaders = [];
foreach ($request->getHeaders() as $key => $values) {
foreach ($values as $value) {
$nHeaders[] = $key.': '.$value;
}
}
$settings[CURLOPT_HTTPHEADER] = $nHeaders;
$settings[CURLOPT_URL] = $request->getUrl();
// FIXME: CURLOPT_PROTOCOLS is currently unsupported by HHVM
if (defined('CURLOPT_PROTOCOLS')) {
$settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
}
// FIXME: CURLOPT_REDIR_PROTOCOLS is currently unsupported by HHVM
if (defined('CURLOPT_REDIR_PROTOCOLS')) {
$settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
}
return $settings;
}
const STATUS_SUCCESS = 0;
const STATUS_CURLERROR = 1;
const STATUS_HTTPERROR = 2;
private function parseResponse(string $response, $curlHandle): array
{
$settings = $this->curlSettings;
$separatedHeaders = isset($settings[CURLOPT_HEADERFUNCTION]) && (bool) $settings[CURLOPT_HEADERFUNCTION];
if ($separatedHeaders) {
$resourceId = (int) $curlHandle;
if (isset($this->headerLinesMap[$resourceId])) {
$headers = $this->headerLinesMap[$resourceId];
} else {
$headers = [];
}
$response = $this->parseCurlResponse($headers, $response, $curlHandle);
} else {
$response = $this->parseCurlResult($response, $curlHandle);
}
return $response;
}
/**
* Parses the result of a curl call in a format that's a bit more
* convenient to work with.
*
* The method returns an array with the following elements:
* * status - one of the 3 STATUS constants.
* * curl_errno - A curl error number. Only set if status is
* STATUS_CURLERROR.
* * curl_errmsg - A current error message. Only set if status is
* STATUS_CURLERROR.
* * response - Response object. Only set if status is STATUS_SUCCESS, or
* STATUS_HTTPERROR.
* * http_code - HTTP status code, as an int. Only set if Only set if
* status is STATUS_SUCCESS, or STATUS_HTTPERROR
*
* @param resource $curlHandle
*/
protected function parseCurlResponse(array $headerLines, string $body, $curlHandle): array
{
list(
$curlInfo,
$curlErrNo,
$curlErrMsg
) = $this->curlStuff($curlHandle);
if ($curlErrNo) {
return [
'status' => self::STATUS_CURLERROR,
'curl_errno' => $curlErrNo,
'curl_errmsg' => $curlErrMsg,
];
}
$response = new Response();
$response->setStatus($curlInfo['http_code']);
$response->setBody($body);
foreach ($headerLines as $header) {
$parts = explode(':', $header, 2);
if (2 === count($parts)) {
$response->addHeader(trim($parts[0]), trim($parts[1]));
}
}
$httpCode = $response->getStatus();
return [
'status' => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS,
'response' => $response,
'http_code' => $httpCode,
];
}
/**
* Parses the result of a curl call in a format that's a bit more
* convenient to work with.
*
* The method returns an array with the following elements:
* * status - one of the 3 STATUS constants.
* * curl_errno - A curl error number. Only set if status is
* STATUS_CURLERROR.
* * curl_errmsg - A current error message. Only set if status is
* STATUS_CURLERROR.
* * response - Response object. Only set if status is STATUS_SUCCESS, or
* STATUS_HTTPERROR.
* * http_code - HTTP status code, as an int. Only set if Only set if
* status is STATUS_SUCCESS, or STATUS_HTTPERROR
*
* @deprecated Use parseCurlResponse instead
*
* @param resource $curlHandle
*/
protected function parseCurlResult(string $response, $curlHandle): array
{
list(
$curlInfo,
$curlErrNo,
$curlErrMsg
) = $this->curlStuff($curlHandle);
if ($curlErrNo) {
return [
'status' => self::STATUS_CURLERROR,
'curl_errno' => $curlErrNo,
'curl_errmsg' => $curlErrMsg,
];
}
$headerBlob = substr($response, 0, $curlInfo['header_size']);
// In the case of 204 No Content, strlen($response) == $curlInfo['header_size].
// This will cause substr($response, $curlInfo['header_size']) return FALSE instead of NULL
// An exception will be thrown when calling getBodyAsString then
$responseBody = substr($response, $curlInfo['header_size']) ?: '';
unset($response);
// In the case of 100 Continue, or redirects we'll have multiple lists
// of headers for each separate HTTP response. We can easily split this
// because they are separated by \r\n\r\n
$headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n"));
// We only care about the last set of headers
$headerBlob = $headerBlob[count($headerBlob) - 1];
// Splitting headers
$headerBlob = explode("\r\n", $headerBlob);
return $this->parseCurlResponse($headerBlob, $responseBody, $curlHandle);
}
/**
* Sends an asynchronous HTTP request.
*
* We keep this in a separate method, so we can call it without triggering
* the beforeRequest event and don't do the poll().
*/
protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, int $retryCount = 0)
{
if (!$this->curlMultiHandle) {
$this->curlMultiHandle = curl_multi_init();
}
$curl = curl_init();
curl_setopt_array(
$curl,
$this->createCurlSettingsArray($request)
);
curl_multi_add_handle($this->curlMultiHandle, $curl);
$resourceId = (int) $curl;
$this->headerLinesMap[$resourceId] = [];
$this->curlMultiMap[$resourceId] = [
$request,
$success,
$error,
$retryCount,
];
}
// @codeCoverageIgnoreStart
/**
* Calls curl_exec.
*
* This method exists so it can easily be overridden and mocked.
*
* @param resource $curlHandle
*/
protected function curlExec($curlHandle): string
{
$this->headerLinesMap[(int) $curlHandle] = [];
$result = curl_exec($curlHandle);
if (false === $result) {
$result = '';
}
return $result;
}
/**
* Returns a bunch of information about a curl request.
*
* This method exists so it can easily be overridden and mocked.
*
* @param resource $curlHandle
*/
protected function curlStuff($curlHandle): array
{
return [
curl_getinfo($curlHandle),
curl_errno($curlHandle),
curl_error($curlHandle),
];
}
// @codeCoverageIgnoreEnd
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* This exception may be emitted by the HTTP\Client class, in case there was a
* problem emitting the request.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ClientException extends \Exception
{
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* This exception represents a HTTP error coming from the Client.
*
* By default the Client will not emit these, this has to be explicitly enabled
* with the setThrowExceptions method.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ClientHttpException extends \Exception implements HttpException
{
/**
* Response object.
*
* @var ResponseInterface
*/
protected $response;
/**
* Constructor.
*/
public function __construct(ResponseInterface $response)
{
$this->response = $response;
parent::__construct($response->getStatusText(), $response->getStatus());
}
/**
* The http status code for the error.
*/
public function getHttpStatus(): int
{
return $this->response->getStatus();
}
/**
* Returns the full response object.
*/
public function getResponse(): ResponseInterface
{
return $this->response;
}
}

31
vendor/sabre/http/lib/HttpException.php vendored Normal file
View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* An exception representing a HTTP error.
*
* This can be used as a generic exception in your application, if you'd like
* to map HTTP errors to exceptions.
*
* If you'd like to use this, create a new exception class, extending Exception
* and implementing this interface.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface HttpException
{
/**
* The http status code for the error.
*
* This may either be just the number, or a number and a human-readable
* message, separated by a space.
*
* @return string|null
*/
public function getHttpStatus();
}

291
vendor/sabre/http/lib/Message.php vendored Normal file
View File

@ -0,0 +1,291 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* This is the abstract base class for both the Request and Response objects.
*
* This object contains a few simple methods that are shared by both.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class Message implements MessageInterface
{
/**
* Request body.
*
* This should be a stream resource, string or a callback writing the body to php://output
*
* @var resource|string|callable
*/
protected $body;
/**
* Contains the list of HTTP headers.
*
* @var array
*/
protected $headers = [];
/**
* HTTP message version (1.0, 1.1 or 2.0).
*
* @var string
*/
protected $httpVersion = '1.1';
/**
* Returns the body as a readable stream resource.
*
* Note that the stream may not be rewindable, and therefore may only be
* read once.
*
* @return resource
*/
public function getBodyAsStream()
{
$body = $this->getBody();
if (is_callable($this->body)) {
$body = $this->getBodyAsString();
}
if (is_string($body) || null === $body) {
$stream = fopen('php://temp', 'r+');
fwrite($stream, (string) $body);
rewind($stream);
return $stream;
}
return $body;
}
/**
* Returns the body as a string.
*
* Note that because the underlying data may be based on a stream, this
* method could only work correctly the first time.
*/
public function getBodyAsString(): string
{
$body = $this->getBody();
if (is_string($body)) {
return $body;
}
if (null === $body) {
return '';
}
if (is_callable($body)) {
ob_start();
$body();
return ob_get_clean();
}
/**
* @var string|int|null
*/
$contentLength = $this->getHeader('Content-Length');
if (is_int($contentLength) || ctype_digit($contentLength)) {
return stream_get_contents($body, (int) $contentLength);
}
return stream_get_contents($body);
}
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string, a stream or a callback writing the body to php://output.
*
* @return resource|string|callable
*/
public function getBody()
{
return $this->body;
}
/**
* Replaces the body resource with a new stream, string or a callback writing the body to php://output.
*
* @param resource|string|callable $body
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Returns all the HTTP headers as an array.
*
* Every header is returned as an array, with one or more values.
*/
public function getHeaders(): array
{
$result = [];
foreach ($this->headers as $headerInfo) {
$result[$headerInfo[0]] = $headerInfo[1];
}
return $result;
}
/**
* Will return true or false, depending on if a HTTP header exists.
*/
public function hasHeader(string $name): bool
{
return isset($this->headers[strtolower($name)]);
}
/**
* Returns a specific HTTP header, based on it's name.
*
* The name must be treated as case-insensitive.
* If the header does not exist, this method must return null.
*
* If a header appeared more than once in a HTTP request, this method will
* concatenate all the values with a comma.
*
* Note that this not make sense for all headers. Some, such as
* `Set-Cookie` cannot be logically combined with a comma. In those cases
* you *should* use getHeaderAsArray().
*
* @return string|null
*/
public function getHeader(string $name)
{
$name = strtolower($name);
if (isset($this->headers[$name])) {
return implode(',', $this->headers[$name][1]);
}
return null;
}
/**
* Returns a HTTP header as an array.
*
* For every time the HTTP header appeared in the request or response, an
* item will appear in the array.
*
* If the header did not exists, this method will return an empty array.
*
* @return string[]
*/
public function getHeaderAsArray(string $name): array
{
$name = strtolower($name);
if (isset($this->headers[$name])) {
return $this->headers[$name][1];
}
return [];
}
/**
* Updates a HTTP header.
*
* The case-sensitivity of the name value must be retained as-is.
*
* If the header already existed, it will be overwritten.
*
* @param string|string[] $value
*/
public function setHeader(string $name, $value)
{
$this->headers[strtolower($name)] = [$name, (array) $value];
}
/**
* Sets a new set of HTTP headers.
*
* The headers array should contain headernames for keys, and their value
* should be specified as either a string or an array.
*
* Any header that already existed will be overwritten.
*/
public function setHeaders(array $headers)
{
foreach ($headers as $name => $value) {
$this->setHeader($name, $value);
}
}
/**
* Adds a HTTP header.
*
* This method will not overwrite any existing HTTP header, but instead add
* another value. Individual values can be retrieved with
* getHeadersAsArray.
*
* @param string|string[] $value
*/
public function addHeader(string $name, $value)
{
$lName = strtolower($name);
if (isset($this->headers[$lName])) {
$this->headers[$lName][1] = array_merge(
$this->headers[$lName][1],
(array) $value
);
} else {
$this->headers[$lName] = [
$name,
(array) $value,
];
}
}
/**
* Adds a new set of HTTP headers.
*
* Any existing headers will not be overwritten.
*/
public function addHeaders(array $headers)
{
foreach ($headers as $name => $value) {
$this->addHeader($name, $value);
}
}
/**
* Removes a HTTP header.
*
* The specified header name must be treated as case-insensitive.
* This method should return true if the header was successfully deleted,
* and false if the header did not exist.
*/
public function removeHeader(string $name): bool
{
$name = strtolower($name);
if (!isset($this->headers[$name])) {
return false;
}
unset($this->headers[$name]);
return true;
}
/**
* Sets the HTTP version.
*
* Should be 1.0, 1.1 or 2.0.
*/
public function setHttpVersion(string $version)
{
$this->httpVersion = $version;
}
/**
* Returns the HTTP version.
*/
public function getHttpVersion(): string
{
return $this->httpVersion;
}
}

View File

@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* This trait contains a bunch of methods, shared by both the RequestDecorator
* and the ResponseDecorator.
*
* Didn't seem needed to create a full class for this, so we're just
* implementing it as a trait.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
trait MessageDecoratorTrait
{
/**
* The inner request object.
*
* All method calls will be forwarded here.
*
* @var MessageInterface
*/
protected $inner;
/**
* Returns the body as a readable stream resource.
*
* Note that the stream may not be rewindable, and therefore may only be
* read once.
*
* @return resource
*/
public function getBodyAsStream()
{
return $this->inner->getBodyAsStream();
}
/**
* Returns the body as a string.
*
* Note that because the underlying data may be based on a stream, this
* method could only work correctly the first time.
*/
public function getBodyAsString(): string
{
return $this->inner->getBodyAsString();
}
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string or a stream.
*
* @return resource|string
*/
public function getBody()
{
return $this->inner->getBody();
}
/**
* Updates the body resource with a new stream.
*
* @param resource|string|callable $body
*/
public function setBody($body)
{
$this->inner->setBody($body);
}
/**
* Returns all the HTTP headers as an array.
*
* Every header is returned as an array, with one or more values.
*/
public function getHeaders(): array
{
return $this->inner->getHeaders();
}
/**
* Will return true or false, depending on if a HTTP header exists.
*/
public function hasHeader(string $name): bool
{
return $this->inner->hasHeader($name);
}
/**
* Returns a specific HTTP header, based on it's name.
*
* The name must be treated as case-insensitive.
* If the header does not exist, this method must return null.
*
* If a header appeared more than once in a HTTP request, this method will
* concatenate all the values with a comma.
*
* Note that this not make sense for all headers. Some, such as
* `Set-Cookie` cannot be logically combined with a comma. In those cases
* you *should* use getHeaderAsArray().
*
* @return string|null
*/
public function getHeader(string $name)
{
return $this->inner->getHeader($name);
}
/**
* Returns a HTTP header as an array.
*
* For every time the HTTP header appeared in the request or response, an
* item will appear in the array.
*
* If the header did not exists, this method will return an empty array.
*/
public function getHeaderAsArray(string $name): array
{
return $this->inner->getHeaderAsArray($name);
}
/**
* Updates a HTTP header.
*
* The case-sensitivity of the name value must be retained as-is.
*
* If the header already existed, it will be overwritten.
*
* @param string|string[] $value
*/
public function setHeader(string $name, $value)
{
$this->inner->setHeader($name, $value);
}
/**
* Sets a new set of HTTP headers.
*
* The headers array should contain headernames for keys, and their value
* should be specified as either a string or an array.
*
* Any header that already existed will be overwritten.
*/
public function setHeaders(array $headers)
{
$this->inner->setHeaders($headers);
}
/**
* Adds a HTTP header.
*
* This method will not overwrite any existing HTTP header, but instead add
* another value. Individual values can be retrieved with
* getHeadersAsArray.
*
* @param string|string[] $value
*/
public function addHeader(string $name, $value)
{
$this->inner->addHeader($name, $value);
}
/**
* Adds a new set of HTTP headers.
*
* Any existing headers will not be overwritten.
*/
public function addHeaders(array $headers)
{
$this->inner->addHeaders($headers);
}
/**
* Removes a HTTP header.
*
* The specified header name must be treated as case-insensitive.
* This method should return true if the header was successfully deleted,
* and false if the header did not exist.
*/
public function removeHeader(string $name): bool
{
return $this->inner->removeHeader($name);
}
/**
* Sets the HTTP version.
*
* Should be 1.0, 1.1 or 2.0.
*/
public function setHttpVersion(string $version)
{
$this->inner->setHttpVersion($version);
}
/**
* Returns the HTTP version.
*/
public function getHttpVersion(): string
{
return $this->inner->getHttpVersion();
}
}

View File

@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* The MessageInterface is the base interface that's used by both
* the RequestInterface and ResponseInterface.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface MessageInterface
{
/**
* Returns the body as a readable stream resource.
*
* Note that the stream may not be rewindable, and therefore may only be
* read once.
*
* @return resource
*/
public function getBodyAsStream();
/**
* Returns the body as a string.
*
* Note that because the underlying data may be based on a stream, this
* method could only work correctly the first time.
*/
public function getBodyAsString(): string;
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string, a stream or a callback writing the body to php://output
*
* @return resource|string|callable
*/
public function getBody();
/**
* Updates the body resource with a new stream.
*
* @param resource|string|callable $body
*/
public function setBody($body);
/**
* Returns all the HTTP headers as an array.
*
* Every header is returned as an array, with one or more values.
*/
public function getHeaders(): array;
/**
* Will return true or false, depending on if a HTTP header exists.
*/
public function hasHeader(string $name): bool;
/**
* Returns a specific HTTP header, based on it's name.
*
* The name must be treated as case-insensitive.
* If the header does not exist, this method must return null.
*
* If a header appeared more than once in a HTTP request, this method will
* concatenate all the values with a comma.
*
* Note that this not make sense for all headers. Some, such as
* `Set-Cookie` cannot be logically combined with a comma. In those cases
* you *should* use getHeaderAsArray().
*
* @return string|null
*/
public function getHeader(string $name);
/**
* Returns a HTTP header as an array.
*
* For every time the HTTP header appeared in the request or response, an
* item will appear in the array.
*
* If the header did not exists, this method will return an empty array.
*
* @return string[]
*/
public function getHeaderAsArray(string $name): array;
/**
* Updates a HTTP header.
*
* The case-sensitity of the name value must be retained as-is.
*
* If the header already existed, it will be overwritten.
*
* @param string|string[] $value
*/
public function setHeader(string $name, $value);
/**
* Sets a new set of HTTP headers.
*
* The headers array should contain headernames for keys, and their value
* should be specified as either a string or an array.
*
* Any header that already existed will be overwritten.
*/
public function setHeaders(array $headers);
/**
* Adds a HTTP header.
*
* This method will not overwrite any existing HTTP header, but instead add
* another value. Individual values can be retrieved with
* getHeadersAsArray.
*
* @param string|string[] $value
*/
public function addHeader(string $name, $value);
/**
* Adds a new set of HTTP headers.
*
* Any existing headers will not be overwritten.
*/
public function addHeaders(array $headers);
/**
* Removes a HTTP header.
*
* The specified header name must be treated as case-insenstive.
* This method should return true if the header was successfully deleted,
* and false if the header did not exist.
*/
public function removeHeader(string $name): bool;
/**
* Sets the HTTP version.
*
* Should be 1.0, 1.1 or 2.0.
*/
public function setHttpVersion(string $version);
/**
* Returns the HTTP version.
*/
public function getHttpVersion(): string;
}

267
vendor/sabre/http/lib/Request.php vendored Normal file
View File

@ -0,0 +1,267 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
use LogicException;
use Sabre\Uri;
/**
* The Request class represents a single HTTP request.
*
* You can either simply construct the object from scratch, or if you need
* access to the current HTTP request, use Sapi::getRequest.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Request extends Message implements RequestInterface
{
/**
* HTTP Method.
*
* @var string
*/
protected $method;
/**
* Request Url.
*
* @var string
*/
protected $url;
/**
* Creates the request object.
*
* @param resource|callable|string $body
*/
public function __construct(string $method, string $url, array $headers = [], $body = null)
{
$this->setMethod($method);
$this->setUrl($url);
$this->setHeaders($headers);
$this->setBody($body);
}
/**
* Returns the current HTTP method.
*/
public function getMethod(): string
{
return $this->method;
}
/**
* Sets the HTTP method.
*/
public function setMethod(string $method)
{
$this->method = $method;
}
/**
* Returns the request url.
*/
public function getUrl(): string
{
return $this->url;
}
/**
* Sets the request url.
*/
public function setUrl(string $url)
{
$this->url = $url;
}
/**
* Returns the list of query parameters.
*
* This is equivalent to PHP's $_GET superglobal.
*/
public function getQueryParameters(): array
{
$url = $this->getUrl();
if (false === ($index = strpos($url, '?'))) {
return [];
}
parse_str(substr($url, $index + 1), $queryParams);
return $queryParams;
}
protected $absoluteUrl;
/**
* Sets the absolute url.
*/
public function setAbsoluteUrl(string $url)
{
$this->absoluteUrl = $url;
}
/**
* Returns the absolute url.
*/
public function getAbsoluteUrl(): string
{
if (!$this->absoluteUrl) {
// Guessing we're a http endpoint.
$this->absoluteUrl = 'http://'.
($this->getHeader('Host') ?? 'localhost').
$this->getUrl();
}
return $this->absoluteUrl;
}
/**
* Base url.
*
* @var string
*/
protected $baseUrl = '/';
/**
* Sets a base url.
*
* This url is used for relative path calculations.
*/
public function setBaseUrl(string $url)
{
$this->baseUrl = $url;
}
/**
* Returns the current base url.
*/
public function getBaseUrl(): string
{
return $this->baseUrl;
}
/**
* Returns the relative path.
*
* This is being calculated using the base url. This path will not start
* with a slash, so it will always return something like
* 'example/path.html'.
*
* If the full path is equal to the base url, this method will return an
* empty string.
*
* This method will also urldecode the path, and if the url was incoded as
* ISO-8859-1, it will convert it to UTF-8.
*
* If the path is outside of the base url, a LogicException will be thrown.
*/
public function getPath(): string
{
// Removing duplicated slashes.
$uri = str_replace('//', '/', $this->getUrl());
$uri = Uri\normalize($uri);
$baseUri = Uri\normalize($this->getBaseUrl());
if (0 === strpos($uri, $baseUri)) {
// We're not interested in the query part (everything after the ?).
list($uri) = explode('?', $uri);
return trim(decodePath(substr($uri, strlen($baseUri))), '/');
}
if ($uri.'/' === $baseUri) {
return '';
}
// A special case, if the baseUri was accessed without a trailing
// slash, we'll accept it as well.
throw new \LogicException('Requested uri ('.$this->getUrl().') is out of base uri ('.$this->getBaseUrl().')');
}
/**
* Equivalent of PHP's $_POST.
*
* @var array
*/
protected $postData = [];
/**
* Sets the post data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* This would not have been needed, if POST data was accessible as
* php://input, but unfortunately we need to special case it.
*/
public function setPostData(array $postData)
{
$this->postData = $postData;
}
/**
* Returns the POST data.
*
* This is equivalent to PHP's $_POST superglobal.
*/
public function getPostData(): array
{
return $this->postData;
}
/**
* An array containing the raw _SERVER array.
*
* @var array
*/
protected $rawServerData;
/**
* Returns an item from the _SERVER array.
*
* If the value does not exist in the array, null is returned.
*
* @return string|null
*/
public function getRawServerValue(string $valueName)
{
return $this->rawServerData[$valueName] ?? null;
}
/**
* Sets the _SERVER array.
*/
public function setRawServerData(array $data)
{
$this->rawServerData = $data;
}
/**
* Serializes the request object as a string.
*
* This is useful for debugging purposes.
*/
public function __toString(): string
{
$out = $this->getMethod().' '.$this->getUrl().' HTTP/'.$this->getHttpVersion()."\r\n";
foreach ($this->getHeaders() as $key => $value) {
foreach ($value as $v) {
if ('Authorization' === $key) {
list($v) = explode(' ', $v, 2);
$v .= ' REDACTED';
}
$out .= $key.': '.$v."\r\n";
}
}
$out .= "\r\n";
$out .= $this->getBodyAsString();
return $out;
}
}

View File

@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* Request Decorator.
*
* This helper class allows you to easily create decorators for the Request
* object.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class RequestDecorator implements RequestInterface
{
use MessageDecoratorTrait;
/**
* Constructor.
*/
public function __construct(RequestInterface $inner)
{
$this->inner = $inner;
}
/**
* Returns the current HTTP method.
*/
public function getMethod(): string
{
return $this->inner->getMethod();
}
/**
* Sets the HTTP method.
*/
public function setMethod(string $method)
{
$this->inner->setMethod($method);
}
/**
* Returns the request url.
*/
public function getUrl(): string
{
return $this->inner->getUrl();
}
/**
* Sets the request url.
*/
public function setUrl(string $url)
{
$this->inner->setUrl($url);
}
/**
* Returns the absolute url.
*/
public function getAbsoluteUrl(): string
{
return $this->inner->getAbsoluteUrl();
}
/**
* Sets the absolute url.
*/
public function setAbsoluteUrl(string $url)
{
$this->inner->setAbsoluteUrl($url);
}
/**
* Returns the current base url.
*/
public function getBaseUrl(): string
{
return $this->inner->getBaseUrl();
}
/**
* Sets a base url.
*
* This url is used for relative path calculations.
*
* The base url should default to /
*/
public function setBaseUrl(string $url)
{
$this->inner->setBaseUrl($url);
}
/**
* Returns the relative path.
*
* This is being calculated using the base url. This path will not start
* with a slash, so it will always return something like
* 'example/path.html'.
*
* If the full path is equal to the base url, this method will return an
* empty string.
*
* This method will also urldecode the path, and if the url was incoded as
* ISO-8859-1, it will convert it to UTF-8.
*
* If the path is outside of the base url, a LogicException will be thrown.
*/
public function getPath(): string
{
return $this->inner->getPath();
}
/**
* Returns the list of query parameters.
*
* This is equivalent to PHP's $_GET superglobal.
*/
public function getQueryParameters(): array
{
return $this->inner->getQueryParameters();
}
/**
* Returns the POST data.
*
* This is equivalent to PHP's $_POST superglobal.
*/
public function getPostData(): array
{
return $this->inner->getPostData();
}
/**
* Sets the post data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* This would not have been needed, if POST data was accessible as
* php://input, but unfortunately we need to special case it.
*/
public function setPostData(array $postData)
{
$this->inner->setPostData($postData);
}
/**
* Returns an item from the _SERVER array.
*
* If the value does not exist in the array, null is returned.
*
* @return string|null
*/
public function getRawServerValue(string $valueName)
{
return $this->inner->getRawServerValue($valueName);
}
/**
* Sets the _SERVER array.
*/
public function setRawServerData(array $data)
{
$this->inner->setRawServerData($data);
}
/**
* Serializes the request object as a string.
*
* This is useful for debugging purposes.
*/
public function __toString(): string
{
return $this->inner->__toString();
}
}

View File

@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* The RequestInterface represents a HTTP request.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface RequestInterface extends MessageInterface
{
/**
* Returns the current HTTP method.
*/
public function getMethod(): string;
/**
* Sets the HTTP method.
*/
public function setMethod(string $method);
/**
* Returns the request url.
*/
public function getUrl(): string;
/**
* Sets the request url.
*/
public function setUrl(string $url);
/**
* Returns the absolute url.
*/
public function getAbsoluteUrl(): string;
/**
* Sets the absolute url.
*/
public function setAbsoluteUrl(string $url);
/**
* Returns the current base url.
*/
public function getBaseUrl(): string;
/**
* Sets a base url.
*
* This url is used for relative path calculations.
*
* The base url should default to /
*/
public function setBaseUrl(string $url);
/**
* Returns the relative path.
*
* This is being calculated using the base url. This path will not start
* with a slash, so it will always return something like
* 'example/path.html'.
*
* If the full path is equal to the base url, this method will return an
* empty string.
*
* This method will also urldecode the path, and if the url was incoded as
* ISO-8859-1, it will convert it to UTF-8.
*
* If the path is outside of the base url, a LogicException will be thrown.
*/
public function getPath(): string;
/**
* Returns the list of query parameters.
*
* This is equivalent to PHP's $_GET superglobal.
*/
public function getQueryParameters(): array;
/**
* Returns the POST data.
*
* This is equivalent to PHP's $_POST superglobal.
*/
public function getPostData(): array;
/**
* Sets the post data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* This would not have been needed, if POST data was accessible as
* php://input, but unfortunately we need to special case it.
*/
public function setPostData(array $postData);
/**
* Returns an item from the _SERVER array.
*
* If the value does not exist in the array, null is returned.
*
* @return string|null
*/
public function getRawServerValue(string $valueName);
/**
* Sets the _SERVER array.
*/
public function setRawServerData(array $data);
}

188
vendor/sabre/http/lib/Response.php vendored Normal file
View File

@ -0,0 +1,188 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* This class represents a single HTTP response.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Response extends Message implements ResponseInterface
{
/**
* This is the list of currently registered HTTP status codes.
*
* @var array
*/
public static $statusCodes = [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authorative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status', // RFC 4918
208 => 'Already Reported', // RFC 5842
226 => 'IM Used', // RFC 3229
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot', // RFC 2324
421 => 'Misdirected Request', // RFC7540 (HTTP/2)
422 => 'Unprocessable Entity', // RFC 4918
423 => 'Locked', // RFC 4918
424 => 'Failed Dependency', // RFC 4918
426 => 'Upgrade Required',
428 => 'Precondition Required', // RFC 6585
429 => 'Too Many Requests', // RFC 6585
431 => 'Request Header Fields Too Large', // RFC 6585
451 => 'Unavailable For Legal Reasons', // draft-tbray-http-legally-restricted-status
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version not supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage', // RFC 4918
508 => 'Loop Detected', // RFC 5842
509 => 'Bandwidth Limit Exceeded', // non-standard
510 => 'Not extended',
511 => 'Network Authentication Required', // RFC 6585
];
/**
* HTTP status code.
*
* @var int
*/
protected $status;
/**
* HTTP status text.
*
* @var string
*/
protected $statusText;
/**
* Creates the response object.
*
* @param string|int $status
* @param array $headers
* @param resource $body
*/
public function __construct($status = 500, array $headers = null, $body = null)
{
if (null !== $status) {
$this->setStatus($status);
}
if (null !== $headers) {
$this->setHeaders($headers);
}
if (null !== $body) {
$this->setBody($body);
}
}
/**
* Returns the current HTTP status code.
*/
public function getStatus(): int
{
return $this->status;
}
/**
* Returns the human-readable status string.
*
* In the case of a 200, this may for example be 'OK'.
*/
public function getStatusText(): string
{
return $this->statusText;
}
/**
* Sets the HTTP status code.
*
* This can be either the full HTTP status code with human readable string,
* for example: "403 I can't let you do that, Dave".
*
* Or just the code, in which case the appropriate default message will be
* added.
*
* @param string|int $status
*
* @throws \InvalidArgumentException
*/
public function setStatus($status)
{
if (ctype_digit($status) || is_int($status)) {
$statusCode = $status;
$statusText = self::$statusCodes[$status] ?? 'Unknown';
} else {
list(
$statusCode,
$statusText
) = explode(' ', $status, 2);
$statusCode = (int) $statusCode;
}
if ($statusCode < 100 || $statusCode > 999) {
throw new \InvalidArgumentException('The HTTP status code must be exactly 3 digits');
}
$this->status = $statusCode;
$this->statusText = $statusText;
}
/**
* Serializes the response object as a string.
*
* This is useful for debugging purposes.
*/
public function __toString(): string
{
$str = 'HTTP/'.$this->httpVersion.' '.$this->getStatus().' '.$this->getStatusText()."\r\n";
foreach ($this->getHeaders() as $key => $value) {
foreach ($value as $v) {
$str .= $key.': '.$v."\r\n";
}
}
$str .= "\r\n";
$str .= $this->getBodyAsString();
return $str;
}
}

View File

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* Response Decorator.
*
* This helper class allows you to easily create decorators for the Response
* object.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ResponseDecorator implements ResponseInterface
{
use MessageDecoratorTrait;
/**
* Constructor.
*/
public function __construct(ResponseInterface $inner)
{
$this->inner = $inner;
}
/**
* Returns the current HTTP status code.
*/
public function getStatus(): int
{
return $this->inner->getStatus();
}
/**
* Returns the human-readable status string.
*
* In the case of a 200, this may for example be 'OK'.
*/
public function getStatusText(): string
{
return $this->inner->getStatusText();
}
/**
* Sets the HTTP status code.
*
* This can be either the full HTTP status code with human readable string,
* for example: "403 I can't let you do that, Dave".
*
* Or just the code, in which case the appropriate default message will be
* added.
*
* @param string|int $status
*/
public function setStatus($status)
{
$this->inner->setStatus($status);
}
/**
* Serializes the request object as a string.
*
* This is useful for debugging purposes.
*/
public function __toString(): string
{
return $this->inner->__toString();
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* This interface represents a HTTP response.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ResponseInterface extends MessageInterface
{
/**
* Returns the current HTTP status code.
*/
public function getStatus(): int;
/**
* Returns the human-readable status string.
*
* In the case of a 200, this may for example be 'OK'.
*/
public function getStatusText(): string;
/**
* Sets the HTTP status code.
*
* This can be either the full HTTP status code with human readable string,
* for example: "403 I can't let you do that, Dave".
*
* Or just the code, in which case the appropriate default message will be
* added.
*
* @param string|int $status
*
* @throws \InvalidArgumentException
*/
public function setStatus($status);
}

243
vendor/sabre/http/lib/Sapi.php vendored Normal file
View File

@ -0,0 +1,243 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
use InvalidArgumentException;
/**
* PHP SAPI.
*
* This object is responsible for:
* 1. Constructing a Request object based on the current HTTP request sent to
* the PHP process.
* 2. Sending the Response object back to the client.
*
* It could be said that this class provides a mapping between the Request and
* Response objects, and php's:
*
* * $_SERVER
* * $_POST
* * $_FILES
* * php://input
* * echo()
* * header()
* * php://output
*
* You can choose to either call all these methods statically, but you can also
* instantiate this as an object to allow for polymorhpism.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Sapi
{
/**
* This static method will create a new Request object, based on the
* current PHP request.
*/
public static function getRequest(): Request
{
$serverArr = $_SERVER;
if ('cli' === PHP_SAPI) {
// If we're running off the CLI, we're going to set some default
// settings.
$serverArr['REQUEST_URI'] = $_SERVER['REQUEST_URI'] ?? '/';
$serverArr['REQUEST_METHOD'] = $_SERVER['REQUEST_METHOD'] ?? 'CLI';
}
$r = self::createFromServerArray($serverArr);
$r->setBody(fopen('php://input', 'r'));
$r->setPostData($_POST);
return $r;
}
/**
* Sends the HTTP response back to a HTTP client.
*
* This calls php's header() function and streams the body to php://output.
*/
public static function sendResponse(ResponseInterface $response)
{
header('HTTP/'.$response->getHttpVersion().' '.$response->getStatus().' '.$response->getStatusText());
foreach ($response->getHeaders() as $key => $value) {
foreach ($value as $k => $v) {
if (0 === $k) {
header($key.': '.$v);
} else {
header($key.': '.$v, false);
}
}
}
$body = $response->getBody();
if (null === $body) {
return;
}
if (is_callable($body)) {
$body();
return;
}
$contentLength = $response->getHeader('Content-Length');
if (null !== $contentLength) {
$output = fopen('php://output', 'wb');
if (is_resource($body) && 'stream' == get_resource_type($body)) {
if (PHP_INT_SIZE > 4) {
// use the dedicated function on 64 Bit systems
// a workaround to make PHP more possible to use mmap based copy, see https://github.com/sabre-io/http/pull/119
$left = (int) $contentLength;
// copy with 4MiB chunks
$chunk_size = 4 * 1024 * 1024;
stream_set_chunk_size($output, $chunk_size);
// If this is a partial response, flush the beginning bytes until the first position that is a multiple of the page size.
$contentRange = $response->getHeader('Content-Range');
// Matching "Content-Range: bytes 1234-5678/7890"
if (null !== $contentRange && preg_match('/^bytes\s([0-9]+)-([0-9]+)\//i', $contentRange, $matches)) {
// 4kB should be the default page size on most architectures
$pageSize = 4096;
$offset = (int) $matches[1];
$delta = ($offset % $pageSize) > 0 ? ($pageSize - $offset % $pageSize) : 0;
if ($delta > 0) {
$left -= stream_copy_to_stream($body, $output, min($delta, $left));
}
}
while ($left > 0) {
$copied = stream_copy_to_stream($body, $output, min($left, $chunk_size));
// stream_copy_to_stream($src, $dest, $maxLength) must return the number of bytes copied or false in case of failure
// But when the $maxLength is greater than the total number of bytes remaining in the stream,
// It returns the negative number of bytes copied
// So break the loop in such cases.
if ($copied <= 0) {
break;
}
$left -= $copied;
}
} else {
// workaround for 32 Bit systems to avoid stream_copy_to_stream
while (!feof($body)) {
fwrite($output, fread($body, 8192));
}
}
} else {
fwrite($output, $body, (int) $contentLength);
}
} else {
file_put_contents('php://output', $body);
}
if (is_resource($body)) {
fclose($body);
}
}
/**
* This static method will create a new Request object, based on a PHP
* $_SERVER array.
*
* REQUEST_URI and REQUEST_METHOD are required.
*/
public static function createFromServerArray(array $serverArray): Request
{
$headers = [];
$method = null;
$url = null;
$httpVersion = '1.1';
$protocol = 'http';
$hostName = 'localhost';
foreach ($serverArray as $key => $value) {
switch ($key) {
case 'SERVER_PROTOCOL':
if ('HTTP/1.0' === $value) {
$httpVersion = '1.0';
} elseif ('HTTP/2.0' === $value) {
$httpVersion = '2.0';
}
break;
case 'REQUEST_METHOD':
$method = $value;
break;
case 'REQUEST_URI':
$url = $value;
break;
// These sometimes show up without a HTTP_ prefix
case 'CONTENT_TYPE':
$headers['Content-Type'] = $value;
break;
case 'CONTENT_LENGTH':
$headers['Content-Length'] = $value;
break;
// mod_php on apache will put credentials in these variables.
// (fast)cgi does not usually do this, however.
case 'PHP_AUTH_USER':
if (isset($serverArray['PHP_AUTH_PW'])) {
$headers['Authorization'] = 'Basic '.base64_encode($value.':'.$serverArray['PHP_AUTH_PW']);
}
break;
// Similarly, mod_php may also screw around with digest auth.
case 'PHP_AUTH_DIGEST':
$headers['Authorization'] = 'Digest '.$value;
break;
// Apache may prefix the HTTP_AUTHORIZATION header with
// REDIRECT_, if mod_rewrite was used.
case 'REDIRECT_HTTP_AUTHORIZATION':
$headers['Authorization'] = $value;
break;
case 'HTTP_HOST':
$hostName = $value;
$headers['Host'] = $value;
break;
case 'HTTPS':
if (!empty($value) && 'off' !== $value) {
$protocol = 'https';
}
break;
default:
if ('HTTP_' === substr($key, 0, 5)) {
// It's a HTTP header
// Normalizing it to be prettier
$header = strtolower(substr($key, 5));
// Transforming dashes into spaces, and uppercasing
// every first letter.
$header = ucwords(str_replace('_', ' ', $header));
// Turning spaces into dashes.
$header = str_replace(' ', '-', $header);
$headers[$header] = $value;
}
break;
}
}
if (null === $url) {
throw new InvalidArgumentException('The _SERVER array must have a REQUEST_URI key');
}
if (null === $method) {
throw new InvalidArgumentException('The _SERVER array must have a REQUEST_METHOD key');
}
$r = new Request($method, $url, $headers);
$r->setHttpVersion($httpVersion);
$r->setRawServerData($serverArray);
$r->setAbsoluteUrl($protocol.'://'.$hostName.$url);
return $r;
}
}

20
vendor/sabre/http/lib/Version.php vendored Normal file
View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
/**
* This class contains the version number for the HTTP package.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Version
{
/**
* Full version number.
*/
const VERSION = '5.1.0';
}

415
vendor/sabre/http/lib/functions.php vendored Normal file
View File

@ -0,0 +1,415 @@
<?php
declare(strict_types=1);
namespace Sabre\HTTP;
use DateTime;
use InvalidArgumentException;
/**
* A collection of useful helpers for parsing or generating various HTTP
* headers.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
/**
* Parses a HTTP date-string.
*
* This method returns false if the date is invalid.
*
* The following formats are supported:
* Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
* Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
* Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
*
* See:
* http://tools.ietf.org/html/rfc7231#section-7.1.1.1
*
* @return bool|DateTime
*/
function parseDate(string $dateString)
{
// Only the format is checked, valid ranges are checked by strtotime below
$month = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)';
$weekday = '(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)';
$wkday = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)';
$time = '([0-1]\d|2[0-3])(\:[0-5]\d){2}';
$date3 = $month.' ([12]\d|3[01]| [1-9])';
$date2 = '(0[1-9]|[12]\d|3[01])\-'.$month.'\-\d{2}';
// 4-digit year cannot begin with 0 - unix timestamp begins in 1970
$date1 = '(0[1-9]|[12]\d|3[01]) '.$month.' [1-9]\d{3}';
// ANSI C's asctime() format
// 4-digit year cannot begin with 0 - unix timestamp begins in 1970
$asctime_date = $wkday.' '.$date3.' '.$time.' [1-9]\d{3}';
// RFC 850, obsoleted by RFC 1036
$rfc850_date = $weekday.', '.$date2.' '.$time.' GMT';
// RFC 822, updated by RFC 1123
$rfc1123_date = $wkday.', '.$date1.' '.$time.' GMT';
// allowed date formats by RFC 2616
$HTTP_date = "($rfc1123_date|$rfc850_date|$asctime_date)";
// allow for space around the string and strip it
$dateString = trim($dateString, ' ');
if (!preg_match('/^'.$HTTP_date.'$/', $dateString)) {
return false;
}
// append implicit GMT timezone to ANSI C time format
if (false === strpos($dateString, ' GMT')) {
$dateString .= ' GMT';
}
try {
return new DateTime($dateString, new \DateTimeZone('UTC'));
} catch (\Exception $e) {
return false;
}
}
/**
* Transforms a DateTime object to a valid HTTP/1.1 Date header value.
*/
function toDate(DateTime $dateTime): string
{
// We need to clone it, as we don't want to affect the existing
// DateTime.
$dateTime = clone $dateTime;
$dateTime->setTimezone(new \DateTimeZone('GMT'));
return $dateTime->format('D, d M Y H:i:s \G\M\T');
}
/**
* This function can be used to aid with content negotiation.
*
* It takes 2 arguments, the $acceptHeaderValue, which usually comes from
* an Accept header, and $availableOptions, which contains an array of
* items that the server can support.
*
* The result of this function will be the 'best possible option'. If no
* best possible option could be found, null is returned.
*
* When it's null you can according to the spec either return a default, or
* you can choose to emit 406 Not Acceptable.
*
* The method also accepts sending 'null' for the $acceptHeaderValue,
* implying that no accept header was sent.
*
* @param string|null $acceptHeaderValue
*
* @return string|null
*/
function negotiateContentType($acceptHeaderValue, array $availableOptions)
{
if (!$acceptHeaderValue) {
// Grabbing the first in the list.
return reset($availableOptions);
}
$proposals = array_map(
'Sabre\HTTP\parseMimeType',
explode(',', $acceptHeaderValue)
);
// Ensuring array keys are reset.
$availableOptions = array_values($availableOptions);
$options = array_map(
'Sabre\HTTP\parseMimeType',
$availableOptions
);
$lastQuality = 0;
$lastSpecificity = 0;
$lastOptionIndex = 0;
$lastChoice = null;
foreach ($proposals as $proposal) {
// Ignoring broken values.
if (null === $proposal) {
continue;
}
// If the quality is lower we don't have to bother comparing.
if ($proposal['quality'] < $lastQuality) {
continue;
}
foreach ($options as $optionIndex => $option) {
if ('*' !== $proposal['type'] && $proposal['type'] !== $option['type']) {
// no match on type.
continue;
}
if ('*' !== $proposal['subType'] && $proposal['subType'] !== $option['subType']) {
// no match on subtype.
continue;
}
// Any parameters appearing on the options must appear on
// proposals.
foreach ($option['parameters'] as $paramName => $paramValue) {
if (!array_key_exists($paramName, $proposal['parameters'])) {
continue 2;
}
if ($paramValue !== $proposal['parameters'][$paramName]) {
continue 2;
}
}
// If we got here, we have a match on parameters, type and
// subtype. We need to calculate a score for how specific the
// match was.
$specificity =
('*' !== $proposal['type'] ? 20 : 0) +
('*' !== $proposal['subType'] ? 10 : 0) +
count($option['parameters']);
// Does this entry win?
if (
($proposal['quality'] > $lastQuality) ||
($proposal['quality'] === $lastQuality && $specificity > $lastSpecificity) ||
($proposal['quality'] === $lastQuality && $specificity === $lastSpecificity && $optionIndex < $lastOptionIndex)
) {
$lastQuality = $proposal['quality'];
$lastSpecificity = $specificity;
$lastOptionIndex = $optionIndex;
$lastChoice = $availableOptions[$optionIndex];
}
}
}
return $lastChoice;
}
/**
* Parses the Prefer header, as defined in RFC7240.
*
* Input can be given as a single header value (string) or multiple headers
* (array of string).
*
* This method will return a key->value array with the various Prefer
* parameters.
*
* Prefer: return=minimal will result in:
*
* [ 'return' => 'minimal' ]
*
* Prefer: foo, wait=10 will result in:
*
* [ 'foo' => true, 'wait' => '10']
*
* This method also supports the formats from older drafts of RFC7240, and
* it will automatically map them to the new values, as the older values
* are still pretty common.
*
* Parameters are currently discarded. There's no known prefer value that
* uses them.
*
* @param string|string[] $input
*/
function parsePrefer($input): array
{
$token = '[!#$%&\'*+\-.^_`~A-Za-z0-9]+';
// Work in progress
$word = '(?: [a-zA-Z0-9]+ | "[a-zA-Z0-9]*" )';
$regex = <<<REGEX
/
^
(?<name> $token) # Prefer property name
\s* # Optional space
(?: = \s* # Prefer property value
(?<value> $word)
)?
(?: \s* ; (?: .*))? # Prefer parameters (ignored)
$
/x
REGEX;
$output = [];
foreach (getHeaderValues($input) as $value) {
if (!preg_match($regex, $value, $matches)) {
// Ignore
continue;
}
// Mapping old values to their new counterparts
switch ($matches['name']) {
case 'return-asynch':
$output['respond-async'] = true;
break;
case 'return-representation':
$output['return'] = 'representation';
break;
case 'return-minimal':
$output['return'] = 'minimal';
break;
case 'strict':
$output['handling'] = 'strict';
break;
case 'lenient':
$output['handling'] = 'lenient';
break;
default:
if (isset($matches['value'])) {
$value = trim($matches['value'], '"');
} else {
$value = true;
}
$output[strtolower($matches['name'])] = empty($value) ? true : $value;
break;
}
}
return $output;
}
/**
* This method splits up headers into all their individual values.
*
* A HTTP header may have more than one header, such as this:
* Cache-Control: private, no-store
*
* Header values are always split with a comma.
*
* You can pass either a string, or an array. The resulting value is always
* an array with each spliced value.
*
* If the second headers argument is set, this value will simply be merged
* in. This makes it quicker to merge an old list of values with a new set.
*
* @param string|string[] $values
* @param string|string[] $values2
*/
function getHeaderValues($values, $values2 = null): array
{
$values = (array) $values;
if ($values2) {
$values = array_merge($values, (array) $values2);
}
$result = [];
foreach ($values as $l1) {
foreach (explode(',', $l1) as $l2) {
$result[] = trim($l2);
}
}
return $result;
}
/**
* Parses a mime-type and splits it into:.
*
* 1. type
* 2. subtype
* 3. quality
* 4. parameters
*/
function parseMimeType(string $str): array
{
$parameters = [];
// If no q= parameter appears, then quality = 1.
$quality = 1;
$parts = explode(';', $str);
// The first part is the mime-type.
$mimeType = trim(array_shift($parts));
if ('*' === $mimeType) {
$mimeType = '*/*';
}
$mimeType = explode('/', $mimeType);
if (2 !== count($mimeType)) {
// Illegal value
var_dump($mimeType);
die();
throw new InvalidArgumentException('Not a valid mime-type: '.$str);
}
list($type, $subType) = $mimeType;
foreach ($parts as $part) {
$part = trim($part);
if (strpos($part, '=')) {
list($partName, $partValue) =
explode('=', $part, 2);
} else {
$partName = $part;
$partValue = null;
}
// The quality parameter, if it appears, also marks the end of
// the parameter list. Anything after the q= counts as an
// 'accept extension' and could introduce new semantics in
// content-negotation.
if ('q' !== $partName) {
$parameters[$partName] = $part;
} else {
$quality = (float) $partValue;
break; // Stop parsing parts
}
}
return [
'type' => $type,
'subType' => $subType,
'quality' => $quality,
'parameters' => $parameters,
];
}
/**
* Encodes the path of a url.
*
* slashes (/) are treated as path-separators.
*/
function encodePath(string $path): string
{
return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/:@])/', function ($match) {
return '%'.sprintf('%02x', ord($match[0]));
}, $path);
}
/**
* Encodes a 1 segment of a path.
*
* Slashes are considered part of the name, and are encoded as %2f
*/
function encodePathSegment(string $pathSegment): string
{
return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\):@])/', function ($match) {
return '%'.sprintf('%02x', ord($match[0]));
}, $pathSegment);
}
/**
* Decodes a url-encoded path.
*/
function decodePath(string $path): string
{
return decodePathSegment($path);
}
/**
* Decodes a url-encoded path segment.
*/
function decodePathSegment(string $path): string
{
$path = rawurldecode($path);
$encoding = mb_detect_encoding($path, ['UTF-8', 'ISO-8859-1']);
switch ($encoding) {
case 'ISO-8859-1':
$path = utf8_encode($path);
}
return $path;
}

2
vendor/sabre/http/phpstan.neon vendored Normal file
View File

@ -0,0 +1,2 @@
parameters:
level: 1