initial sabre upgrade (needs lots of work - to wit: authentication, redo the browser interface, and rework event export/import)

This commit is contained in:
redmatrix
2016-05-10 17:26:44 -07:00
parent 40b5b6e9d2
commit 0b02a6d123
735 changed files with 64728 additions and 42397 deletions

15
vendor/sabre/http/.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
# Composer
vendor/
composer.lock
# Tests
tests/cov/
# Composer binaries
bin/phpunit
bin/phpcs
bin/php-cs-fixer
bin/sabre-cs-fixer
# Vim
.*.swp

24
vendor/sabre/http/.travis.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7
- hhvm
matrix:
fast_finish: true
env:
matrix:
- PREFER_LOWEST=""
- PREFER_LOWEST="--prefer-lowest"
before_script:
- composer self-update
- composer update --prefer-source $PREFER_LOWEST
script:
- ./bin/phpunit --configuration tests/phpunit.xml
- ./bin/sabre-cs-fixer fix . --dry-run --diff

250
vendor/sabre/http/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,250 @@
ChangeLog
=========
4.2.1 (2016-01-06)
------------------
* #56: `getBodyAsString` now returns at most as many bytes as the contents of
the `Content-Length` header. This allows users to pass much larger strings
without having to copy and truncate them.
4.2.0 (2016-01-04)
------------------
* This package now supports sabre/event 3.0.
* The client now sets a default `User-Agent` header identifying this library.
4.1.0 (2015-09-04)
------------------
* The async client wouldn't `wait()` for new http requests being started
after the (previous) last request in the queue was resolved.
* Added `Sabre\HTTP\Auth\Bearer`, to easily extract a OAuth2 bearer token.
4.0.0 (2015-05-20)
------------------
* Deprecated: All static functions from `Sabre\HTTP\URLUtil` and
`Sabre\HTTP\Util` moved to a separate `functions.php`, which is also
autoloaded. The old functions are still there, but will be removed in a
future version. (#49)
4.0.0-alpha3 (2015-05-19)
-------------------------
* Added a parser for the HTTP `Prefer` header, as defined in [RFC7240][rfc7240].
* Deprecated `Sabre\HTTP\Util::parseHTTPDate`, use `Sabre\HTTP\parseDate()`.
* Deprecated `Sabre\HTTP\Util::toHTTPDate` use `Sabre\HTTP\toDate()`.
4.0.0-alpha2 (2015-05-18)
-------------------------
* #45: Don't send more data than what is promised in the HTTP content-length.
(@dratini0).
* #43: `getCredentials` returns null if incomplete. (@Hywan)
* #48: Now using php-cs-fixer to make our CS consistent (yay!)
* This includes fixes released in version 3.0.5.
4.0.0-alpha1 (2015-02-25)
-------------------------
* #41: Fixing bugs related to comparing URLs in `Request::getPath()`.
* #41: This library now uses the `sabre/uri` package for uri handling.
* Added `421 Misdirected Request` from the HTTP/2.0 spec.
3.0.5 (2015-05-11)
------------------
* #47 #35: When re-using the client and doing any request after a `HEAD`
request, the client discards the body.
3.0.4 (2014-12-10)
------------------
* #38: The Authentication helpers no longer overwrite any existing
`WWW-Authenticate` headers, but instead append new headers. This ensures
that multiple authentication systems can exist in the same environment.
3.0.3 (2014-12-03)
------------------
* Hiding `Authorization` header value from `Request::__toString`.
3.0.2 (2014-10-09)
------------------
* When parsing `Accept:` headers, we're ignoring invalid parts. Before we
would throw a PHP E_NOTICE.
3.0.1 (2014-09-29)
------------------
* Minor change in unittests.
3.0.0 (2014-09-23)
------------------
* `getHeaders()` now returns header values as an array, just like psr/http.
* Added `hasHeader()`.
2.1.0-alpha1 (2014-09-15)
-------------------------
* Changed: Copied most of the header-semantics for the PSR draft for
representing HTTP messages. [Reference here][psr-http].
* This means that `setHeaders()` does not wipe out every existing header
anymore.
* We also support multiple headers with the same name.
* Use `Request::getHeaderAsArray()` and `Response::getHeaderAsArray()` to
get a hold off multiple headers with the same name.
* If you use `getHeader()`, and there's more than 1 header with that name, we
concatenate all these with a comma.
* `addHeader()` will now preserve an existing header with that name, and add a
second header with the same name.
* The message class should be a lot faster now for looking up headers. No more
array traversal, because we maintain a tiny index.
* Added: `URLUtil::resolve()` to make resolving relative urls super easy.
* Switched to PSR-4.
* #12: Circumventing CURL's FOLLOW_LOCATION and doing it in PHP instead. This
fixes compatibility issues with people that have open_basedir turned on.
* Added: Content negotiation now correctly support mime-type parameters such as
charset.
* Changed: `Util::negotiate()` is now deprecated. Use
`Util::negotiateContentType()` instead.
* #14: The client now only follows http and https urls.
2.0.4 (2014-07-14)
------------------
* Changed: No longer escaping @ in urls when it's not needed.
* Fixed: #7: Client now correctly deals with responses without a body.
2.0.3 (2014-04-17)
------------------
* Now works on hhvm!
* Fixed: Now throwing an error when a Request object is being created with
arguments that were valid for sabre/http 1.0. Hopefully this will aid with
debugging for upgraders.
2.0.2 (2014-02-09)
------------------
* Fixed: Potential security problem in the client.
2.0.1 (2014-01-09)
------------------
* Fixed: getBodyAsString on an empty body now works.
* Fixed: Version string
2.0.0 (2014-01-08)
------------------
* Removed: Request::createFromPHPRequest. This is now handled by
Sapi::getRequest.
2.0.0alpha6 (2014-01-03)
------------------------
* Added: Asynchronous HTTP client. See examples/asyncclient.php.
* Fixed: Issue #4: Don't escape colon (:) when it's not needed.
* Fixed: Fixed a bug in the content negotation script.
* Fixed: Fallback for when CURLOPT_POSTREDIR is not defined (mainly for hhvm).
* Added: The Request and Response object now have a `__toString()` method that
serializes the objects into a standard HTTP message. This is mainly for
debugging purposes.
* Changed: Added Response::getStatusText(). This method returns the
human-readable HTTP status message. This part has been removed from
Response::getStatus(), which now always returns just the status code as an
int.
* Changed: Response::send() is now Sapi::sendResponse($response).
* Changed: Request::createFromPHPRequest is now Sapi::getRequest().
* Changed: Message::getBodyAsStream and Message::getBodyAsString were added. The
existing Message::getBody changed it's behavior, so be careful.
2.0.0alpha5 (2013-11-07)
------------------------
* Added: HTTP Status 451 Unavailable For Legal Reasons. Fight government
censorship!
* Added: Ability to catch and retry http requests in the client when a curl
error occurs.
* Changed: Request::getPath does not return the query part of the url, so
everything after the ? is stripped.
* Added: a reverse proxy example.
2.0.0alpha4 (2013-08-07)
------------------------
* Fixed: Doing a GET request with the client uses the last used HTTP method
instead.
* Added: HttpException
* Added: The Client class can now automatically emit exceptions when HTTP errors
occurred.
2.0.0alpha3 (2013-07-24)
------------------------
* Changed: Now depends on sabre/event package.
* Changed: setHeaders() now overwrites any existing http headers.
* Added: getQueryParameters to RequestInterface.
* Added: Util::negotiate.
* Added: RequestDecorator, ResponseDecorator.
* Added: A very simple HTTP client.
* Added: addHeaders() to append a list of new headers.
* Fixed: Not erroring on unknown HTTP status codes.
* Fixed: Throwing exceptions on invalid HTTP status codes (not 3 digits).
* Fixed: Much better README.md
* Changed: getBody() now uses a bitfield to specify what type to return.
2.0.0alpha2 (2013-07-02)
------------------------
* Added: Digest & AWS Authentication.
* Added: Message::getHttpVersion and Message::setHttpVersion.
* Added: Request::setRawServerArray, getRawServerValue.
* Added: Request::createFromPHPRequest
* Added: Response::send
* Added: Request::getQueryParameters
* Added: Utility for dealing with HTTP dates.
* Added: Request::setPostData and Request::getPostData.
* Added: Request::setAbsoluteUrl and Request::getAbsoluteUrl.
* Added: URLUtil, methods for calculation relative and base urls.
* Removed: Response::sendBody
2.0.0alpha1 (2012-10-07)
------------------------
* Fixed: Lots of small naming improvements
* Added: Introduction of Message, MessageInterface, Response, ResponseInterface.
Before 2.0.0, this package was built-into SabreDAV, where it first appeared in
January 2009.
[psr-http]: https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md
[rfc-7240]: http://tools.ietf.org/html/rfc7240

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

@@ -0,0 +1,27 @@
Copyright (C) 2009-2016 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.

746
vendor/sabre/http/README.md vendored Normal file
View File

@@ -0,0 +1,746 @@
sabre/http
==========
This library provides a toolkit to make working with the HTTP protocol easier.
Most PHP scripts run within a HTTP request but accessing information about the
HTTP request is cumbersome at least.
There's bad practices, inconsistencies and confusion. This library is
effectively a wrapper around the following PHP constructs:
For Input:
* `$_GET`,
* `$_POST`,
* `$_SERVER`,
* `php://input` or `$HTTP_RAW_POST_DATA`.
For output:
* `php://output` or `echo`,
* `header()`.
What this library provides, is a `Request` object, and a `Response` object.
The objects are extendable and easily mockable.
Build status
------------
| branch | status |
| ------ | ------ |
| master | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=master)](https://travis-ci.org/fruux/sabre-http) |
| 3.0 | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-http) |
Installation
------------
Make sure you have [composer][1] installed. In your project directory, create,
or edit a `composer.json` file, and make sure it contains something like this:
```json
{
"require" : {
"sabre/http" : "~3.0.0"
}
}
```
After that, just hit `composer install` and you should be rolling.
Quick history
-------------
This library came to existence in 2009, as a part of the [`sabre/dav`][2]
project, which uses it heavily.
It got split off into a separate library to make it easier to manage
releases and hopefully giving it use outside of the scope of just `sabre/dav`.
Although completely independently developed, this library has a LOT of
overlap with [Symfony's `HttpFoundation`][3].
Said library does a lot more stuff and is significantly more popular,
so if you are looking for something to fulfill this particular requirement,
I'd recommend also considering [`HttpFoundation`][3].
Getting started
---------------
First and foremost, this library wraps the superglobals. The easiest way to
instantiate a request object is as follows:
```php
use Sabre\HTTP;
include 'vendor/autoload.php';
$request = HTTP\Sapi::getRequest();
```
This line should only happen once in your entire application. Everywhere else
you should pass this request object around using dependency injection.
You should always typehint on it's interface:
```php
function handleRequest(HTTP\RequestInterface $request) {
// Do something with this request :)
}
```
A response object you can just create as such:
```php
use Sabre\HTTP;
include 'vendor/autoload.php';
$response = new HTTP\Response();
$response->setStatus(201); // created !
$response->setHeader('X-Foo', 'bar');
$response->setBody(
'success!'
);
```
After you fully constructed your response, you must call:
```php
HTTP\Sapi::sendResponse($response);
```
This line should generally also appear once in your application (at the very
end).
Decorators
----------
It may be useful to extend the `Request` and `Response` objects in your
application, if you for example would like them to carry a bit more
information about the current request.
For instance, you may want to add an `isLoggedIn` method to the Request
object.
Simply extending Request and Response may pose some problems:
1. You may want to extend the objects with new behaviors differently, in
different subsystems of your application,
2. The `Sapi::getRequest` factory always returns a instance of
`Request` so you would have to override the factory method as well,
3. By controlling the instantation and depend on specific `Request` and
`Response` instances in your library or application, you make it harder to
work with other applications which also use `sabre/http`.
In short: it would be bad design. Instead, it's recommended to use the
[decorator pattern][6] to add new behavior where you need it. `sabre/http`
provides helper classes to quickly do this.
Example:
```php
use Sabre\HTTP;
class MyRequest extends HTTP\RequestDecorator {
function isLoggedIn() {
return true;
}
}
```
Our application assumes that the true `Request` object was instantiated
somewhere else, by some other subsystem. This could simply be a call like
`$request = Sapi::getRequest()` at the top of your application,
but could also be somewhere in a unittest.
All we know in the current subsystem, is that we received a `$request` and
that it implements `Sabre\HTTP\RequestInterface`. To decorate this object,
all we need to do is:
```php
$request = new MyRequest($request);
```
And that's it, we now have an `isLoggedIn` method, without having to mess
with the core instances.
Client
------
This package also contains a simple wrapper around [cURL][4], which will allow
you to write simple clients, using the `Request` and `Response` objects you're
already familiar with.
It's by no means a replacement for something like [Guzzle][7], but it provides
a simple and lightweight API for making the occasional API call.
### Usage
```php
use Sabre\HTTP;
$request = new HTTP\Request('GET', 'http://example.org/');
$request->setHeader('X-Foo', 'Bar');
$client = new HTTP\Client();
$response = $client->send($request);
echo $response->getBodyAsString();
```
The client emits 3 event using [`sabre/event`][5]. `beforeRequest`,
`afterRequest` and `error`.
```php
$client = new HTTP\Client();
$client->on('beforeRequest', function($request) {
// You could use beforeRequest to for example inject a few extra headers.
// into the Request object.
});
$client->on('afterRequest', function($request, $response) {
// The afterRequest event could be a good time to do some logging, or
// do some rewriting in the response.
});
$client->on('error', function($request, $response, &$retry, $retryCount) {
// The error event is triggered for every response with a HTTP code higher
// than 399.
});
$client->on('error:401', function($request, $response, &$retry, $retryCount) {
// You can also listen for specific error codes. This example shows how
// to inject HTTP authentication headers if a 401 was returned.
if ($retryCount > 1) {
// We're only going to retry exactly once.
}
$request->setHeader('Authorization', 'Basic xxxxxxxxxx');
$retry = true;
});
```
### Asynchronous requests
The `Client` also supports doing asynchronous requests. This is especially handy
if you need to perform a number of requests, that are allowed to be executed
in parallel.
The underlying system for this is simply [cURL's multi request handler][8],
but this provides a much nicer API to handle this.
Sample usage:
```php
use Sabre\HTTP;
$request = new Request('GET', 'http://localhost/');
$client = new Client();
// Executing 1000 requests
for ($i = 0; $i < 1000; $i++) {
$client->sendAsync(
$request,
function(ResponseInterface $response) {
// Success handler
},
function($error) {
// Error handler
}
);
}
// Wait for all requests to get a result.
$client->wait();
```
Check out `examples/asyncclient.php` for more information.
Writing a reverse proxy
-----------------------
With all these tools combined, it becomes very easy to write a simple reverse
http proxy.
```php
use
Sabre\HTTP\Sapi,
Sabre\HTTP\Client;
// The url we're proxying to.
$remoteUrl = 'http://example.org/';
// The url we're proxying from. Please note that this must be a relative url,
// and basically acts as the base url.
//
// If youre $remoteUrl doesn't end with a slash, this one probably shouldn't
// either.
$myBaseUrl = '/reverseproxy.php';
// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/';
$request = Sapi::getRequest();
$request->setBaseUrl($myBaseUrl);
$subRequest = clone $request;
// Removing the Host header.
$subRequest->removeHeader('Host');
// Rewriting the url.
$subRequest->setUrl($remoteUrl . $request->getPath());
$client = new Client();
// Sends the HTTP request to the server
$response = $client->send($subRequest);
// Sends the response back to the client that connected to the proxy.
Sapi::sendResponse($response);
```
The Request and Response API's
------------------------------
### Request
```php
/**
* Creates the request object
*
* @param string $method
* @param string $url
* @param array $headers
* @param resource $body
*/
public function __construct($method = null, $url = null, array $headers = null, $body = null);
/**
* Returns the current HTTP method
*
* @return string
*/
function getMethod();
/**
* Sets the HTTP method
*
* @param string $method
* @return void
*/
function setMethod($method);
/**
* Returns the request url.
*
* @return string
*/
function getUrl();
/**
* Sets the request url.
*
* @param string $url
* @return void
*/
function setUrl($url);
/**
* Returns the absolute url.
*
* @return string
*/
function getAbsoluteUrl();
/**
* Sets the absolute url.
*
* @param string $url
* @return void
*/
function setAbsoluteUrl($url);
/**
* Returns the current base url.
*
* @return string
*/
function getBaseUrl();
/**
* Sets a base url.
*
* This url is used for relative path calculations.
*
* The base url should default to /
*
* @param string $url
* @return void
*/
function 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.
*
* @return string
*/
function getPath();
/**
* Returns the list of query parameters.
*
* This is equivalent to PHP's $_GET superglobal.
*
* @return array
*/
function getQueryParameters();
/**
* Returns the POST data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* @return array
*/
function 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.
*
* @param array $postData
* @return void
*/
function setPostData(array $postData);
/**
* Returns an item from the _SERVER array.
*
* If the value does not exist in the array, null is returned.
*
* @param string $valueName
* @return string|null
*/
function getRawServerValue($valueName);
/**
* Sets the _SERVER array.
*
* @param array $data
* @return void
*/
function setRawServerData(array $data);
/**
* 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
*/
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.
*
* @return string
*/
function getBodyAsString();
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string or a stream.
*
* @return resource|string
*/
function getBody();
/**
* Updates the body resource with a new stream.
*
* @param resource $body
* @return void
*/
function setBody($body);
/**
* Returns all the HTTP headers as an array.
*
* @return array
*/
function getHeaders();
/**
* 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.
*
* @param string $name
* @return string|null
*/
function getHeader($name);
/**
* Updates a HTTP header.
*
* The case-sensitity of the name value must be retained as-is.
*
* @param string $name
* @param string $value
* @return void
*/
function setHeader($name, $value);
/**
* Resets HTTP headers
*
* This method overwrites all existing HTTP headers
*
* @param array $headers
* @return void
*/
function setHeaders(array $headers);
/**
* Adds a new set of HTTP headers.
*
* Any header specified in the array that already exists will be
* overwritten, but any other existing headers will be retained.
*
* @param array $headers
* @return void
*/
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.
*
* @return bool
*/
function removeHeader($name);
/**
* Sets the HTTP version.
*
* Should be 1.0 or 1.1.
*
* @param string $version
* @return void
*/
function setHttpVersion($version);
/**
* Returns the HTTP version.
*
* @return string
*/
function getHttpVersion();
```
### Response
```php
/**
* Returns the current HTTP status.
*
* This is the status-code as well as the human readable string.
*
* @return string
*/
function getStatus();
/**
* 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 \InvalidArgumentExeption
* @return void
*/
function setStatus($status);
/**
* 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
*/
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.
*
* @return string
*/
function getBodyAsString();
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string or a stream.
*
* @return resource|string
*/
function getBody();
/**
* Updates the body resource with a new stream.
*
* @param resource $body
* @return void
*/
function setBody($body);
/**
* Returns all the HTTP headers as an array.
*
* @return array
*/
function getHeaders();
/**
* 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.
*
* @param string $name
* @return string|null
*/
function getHeader($name);
/**
* Updates a HTTP header.
*
* The case-sensitity of the name value must be retained as-is.
*
* @param string $name
* @param string $value
* @return void
*/
function setHeader($name, $value);
/**
* Resets HTTP headers
*
* This method overwrites all existing HTTP headers
*
* @param array $headers
* @return void
*/
function setHeaders(array $headers);
/**
* Adds a new set of HTTP headers.
*
* Any header specified in the array that already exists will be
* overwritten, but any other existing headers will be retained.
*
* @param array $headers
* @return void
*/
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.
*
* @return bool
*/
function removeHeader($name);
/**
* Sets the HTTP version.
*
* Should be 1.0 or 1.1.
*
* @param string $version
* @return void
*/
function setHttpVersion($version);
/**
* Returns the HTTP version.
*
* @return string
*/
function getHttpVersion();
```
Made at fruux
-------------
This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.
[1]: http://getcomposer.org/
[2]: http://sabre.io/
[3]: https://github.com/symfony/HttpFoundation
[4]: http://php.net/curl
[5]: https://github.com/fruux/sabre-event
[6]: http://en.wikipedia.org/wiki/Decorator_pattern
[7]: http://guzzlephp.org/
[8]: http://php.net/curl_multi_init

0
vendor/sabre/http/bin/.empty vendored Normal file
View File

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

@@ -0,0 +1,234 @@
<?php
namespace Sabre\HTTP\Auth;
use Sabre\HTTP\Util;
/**
* 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.
*
* @return bool
*/
function init() {
$authHeader = $this->request->getHeader('Authorization');
$authHeader = explode(' ', $authHeader);
if ($authHeader[0] != 'AWS' || !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
*
* @return string
*/
function getAccessKey() {
return $this->accessKey;
}
/**
* Validates the signature based on the secretKey
*
* @param string $secretKey
* @return bool
*/
function validate($secretKey) {
$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($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($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
*
* @return void
*/
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.
*
* @param string $dateHeader
* @return bool
*/
protected function validateRFC2616Date($dateHeader) {
$date = Util::parseHTTPDate($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 $date;
}
/**
* Returns a list of AMZ headers
*
* @return string
*/
protected function getAmzHeaders() {
$amzHeaders = [];
$headers = $this->request->getHeaders();
foreach ($headers as $headerName => $headerValue) {
if (strpos(strtolower($headerName), 'x-amz-') === 0) {
$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
*
* @param string $key
* @param string $message
* @return string
*/
private function hmacsha1($key, $message) {
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,73 @@
<?php
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
*
* @param string $realm
* @return void
*/
function __construct($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.
*
* @return void
*/
abstract function requireLogin();
/**
* Returns the HTTP realm
*
* @return string
*/
function getRealm() {
return $this->realm;
}
}

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

@@ -0,0 +1,63 @@
<?php
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 null|array
*/
function getCredentials() {
$auth = $this->request->getHeader('Authorization');
if (!$auth) {
return null;
}
if (strtolower(substr($auth, 0, 6)) !== 'basic ') {
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.
*
* @return void
*/
function requireLogin() {
$this->response->addHeader('WWW-Authenticate', 'Basic realm="' . $this->realm . '"');
$this->response->setStatus(401);
}
}

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

@@ -0,0 +1,56 @@
<?php
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 null|string
*/
function getToken() {
$auth = $this->request->getHeader('Authorization');
if (!$auth) {
return null;
}
if (strtolower(substr($auth, 0, 7)) !== 'bearer ') {
return null;
}
return substr($auth, 7);
}
/**
* This method sends the needed HTTP header and statuscode (401) to force
* authentication.
*
* @return void
*/
function requireLogin() {
$this->response->addHeader('WWW-Authenticate', 'Bearer realm="' . $this->realm . '"');
$this->response->setStatus(401);
}
}

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

@@ -0,0 +1,231 @@
<?php
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
*/
function __construct($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.
*
* @return void
*/
function init() {
$digest = $this->getDigest();
$this->digestParts = $this->parseDigest($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.
*
* @param int $qop
* @return void
*/
function setQOP($qop) {
$this->qop = $qop;
}
/**
* Validates the user.
*
* The A1 parameter should be md5($username . ':' . $realm . ':' . $password);
*
* @param string $A1
* @return bool
*/
function validateA1($A1) {
$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.
*
* @param string $password
* @return bool
*/
function validatePassword($password) {
$this->A1 = md5($this->digestParts['username'] . ':' . $this->realm . ':' . $password);
return $this->validate();
}
/**
* Returns the username for the request
*
* @return string
*/
function getUsername() {
return $this->digestParts['username'];
}
/**
* Validates the digest challenge
*
* @return bool
*/
protected function validate() {
$A2 = $this->request->getMethod() . ':' . $this->digestParts['uri'];
if ($this->digestParts['qop'] == 'auth-int') {
// 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);
} else {
// We need to make sure we support this qop value
if (!($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
*
* @return void
*/
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
*/
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
*
* @param string $digest
* @return mixed
*/
protected function parseDigest($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[2] : $m[3];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
}

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

@@ -0,0 +1,601 @@
<?php
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;
/**
* Initializes the client.
*
* @return void
*/
function __construct() {
$this->curlSettings = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_NOBODY => false,
CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
];
}
/**
* Sends a request to a HTTP server, and returns a response.
*
* @param RequestInterface $request
* @return ResponseInterface
*/
function send(RequestInterface $request) {
$this->emit('beforeRequest', [$request]);
$retryCount = 0;
$redirects = 0;
do {
$doRedirect = false;
$retry = false;
try {
$response = $this->doRequest($request);
$code = (int)$response->getStatus();
// We are doing in-PHP redirects, because curl's
// FOLLOW_LOCATION throws errors when PHP is configured with
// open_basedir.
//
// https://github.com/fruux/sabre-http/issues/12
if (in_array($code, [301, 302, 307, 308]) && $redirects < $this->maxRedirects) {
$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().
*
* @param RequestInterface $request
* @param callable $success
* @param callable $error
* @return void
*/
function sendAsync(RequestInterface $request, callable $success = null, callable $error = null) {
$this->emit('beforeRequest', [$request]);
$this->sendAsyncInternal($request, $success, $error);
$this->poll();
}
/**
* This method checks if any http requests have gotten results, and if so,
* call the appropriate success or error handlers.
*
* This method will return true if there are still requests waiting to
* return, and false if all the work is done.
*
* @return bool
*/
function poll() {
// nothing to do?
if (!$this->curlMultiMap) {
return false;
}
do {
$r = curl_multi_exec(
$this->curlMultiHandle,
$stillRunning
);
} while ($r === CURLM_CALL_MULTI_PERFORM);
do {
messageQueue:
$status = curl_multi_info_read(
$this->curlMultiHandle,
$messagesInQueue
);
if ($status && $status['msg'] === CURLMSG_DONE) {
$resourceId = intval($status['handle']);
list(
$request,
$successCallback,
$errorCallback,
$retryCount,
) = $this->curlMultiMap[$resourceId];
unset($this->curlMultiMap[$resourceId]);
$curlResult = $this->parseCurlResult(curl_multi_getcontent($status['handle']), $status['handle']);
$retry = false;
if ($curlResult['status'] === self::STATUS_CURLERROR) {
$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 ($curlResult['status'] === self::STATUS_HTTPERROR) {
$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.
*
* @return void
*/
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.
*
* @param bool $throwExceptions
* @return void
*/
function setThrowExceptions($throwExceptions) {
$this->throwExceptions = $throwExceptions;
}
/**
* Adds a CURL setting.
*
* These settings will be included in every HTTP request.
*
* @param int $name
* @param mixed $value
* @return void
*/
function addCurlSetting($name, $value) {
$this->curlSettings[$name] = $value;
}
/**
* This method is responsible for performing a single request.
*
* @param RequestInterface $request
* @return ResponseInterface
*/
protected function doRequest(RequestInterface $request) {
$settings = $this->createCurlSettingsArray($request);
if (!$this->curlHandle) {
$this->curlHandle = curl_init();
}
curl_setopt_array($this->curlHandle, $settings);
$response = $this->curlExec($this->curlHandle);
$response = $this->parseCurlResult($response, $this->curlHandle);
if ($response['status'] === self::STATUS_CURLERROR) {
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
*
* @param RequestInterface $request
* @return array
*/
protected function createCurlSettingsArray(RequestInterface $request) {
$settings = $this->curlSettings;
switch ($request->getMethod()) {
case 'HEAD' :
$settings[CURLOPT_NOBODY] = true;
$settings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
$settings[CURLOPT_POSTFIELDS] = '';
$settings[CURLOPT_PUT] = false;
break;
case 'GET' :
$settings[CURLOPT_CUSTOMREQUEST] = 'GET';
$settings[CURLOPT_POSTFIELDS] = '';
$settings[CURLOPT_PUT] = false;
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;
/**
* 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 string $response
* @param resource $curlHandle
* @return Response
*/
protected function parseCurlResult($response, $curlHandle) {
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']) ?: null;
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);
$response = new Response();
$response->setStatus($curlInfo['http_code']);
foreach ($headerBlob as $header) {
$parts = explode(':', $header, 2);
if (count($parts) == 2) {
$response->addHeader(trim($parts[0]), trim($parts[1]));
}
}
$response->setBody($responseBody);
$httpCode = intval($response->getStatus());
return [
'status' => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS,
'response' => $response,
'http_code' => $httpCode,
];
}
/**
* 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().
*
* @param RequestInterface $request
* @param callable $success
* @param callable $error
* @param int $retryCount
*/
protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, $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);
$this->curlMultiMap[intval($curl)] = [
$request,
$success,
$error,
$retryCount
];
}
// @codeCoverageIgnoreStart
/**
* Calls curl_exec
*
* This method exists so it can easily be overridden and mocked.
*
* @param resource $curlHandle
* @return string
*/
protected function curlExec($curlHandle) {
return curl_exec($curlHandle);
}
/**
* Returns a bunch of information about a curl request.
*
* This method exists so it can easily be overridden and mocked.
*
* @param resource $curlHandle
* @return array
*/
protected function curlStuff($curlHandle) {
return [
curl_getinfo($curlHandle),
curl_errno($curlHandle),
curl_error($curlHandle),
];
}
// @codeCoverageIgnoreEnd
}

View File

@@ -0,0 +1,15 @@
<?php
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,58 @@
<?php
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
*
* @param ResponseInterface $response
*/
function __construct(ResponseInterface $response) {
$this->response = $response;
parent::__construct($response->getStatusText(), $response->getStatus());
}
/**
* The http status code for the error.
*
* @return int
*/
function getHttpStatus() {
return $this->response->getStatus();
}
/**
* Returns the full response object.
*
* @return ResponseInterface
*/
function getResponse() {
return $this->response;
}
}

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

@@ -0,0 +1,30 @@
<?php
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
*/
function getHttpStatus();
}

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

@@ -0,0 +1,314 @@
<?php
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
*
* @var resource
*/
protected $body;
/**
* Contains the list of HTTP headers
*
* @var array
*/
protected $headers = [];
/**
* HTTP message version (1.0 or 1.1)
*
* @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
*/
function getBodyAsStream() {
$body = $this->getBody();
if (is_string($body) || is_null($body)) {
$stream = fopen('php://temp', 'r+');
fwrite($stream, $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.
*
* @return string
*/
function getBodyAsString() {
$body = $this->getBody();
if (is_string($body)) {
return $body;
}
if (is_null($body)) {
return '';
}
$contentLength = $this->getHeader('Content-Length');
if (null === $contentLength) {
return stream_get_contents($body);
} else {
return stream_get_contents($body, $contentLength);
}
}
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string or a stream.
*
* @return resource|string
*/
function getBody() {
return $this->body;
}
/**
* Replaces the body resource with a new stream or string.
*
* @param resource|string $body
*/
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.
*
* @return array
*/
function getHeaders() {
$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.
*
* @param string $name
* @return bool
*/
function hasHeader($name) {
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().
*
* @param string $name
* @return string|null
*/
function getHeader($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.
*
* @param string $name
* @return string[]
*/
function getHeaderAsArray($name) {
$name = strtolower($name);
if (isset($this->headers[$name])) {
return $this->headers[$name][1];
}
return [];
}
/**
* 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 $name
* @param string|string[] $value
* @return void
*/
function setHeader($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.
*
* @param array $headers
* @return void
*/
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 $name
* @param string $value
* @return void
*/
function addHeader($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.
*
* @param array $headers
* @return void
*/
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-insenstive.
* This method should return true if the header was successfully deleted,
* and false if the header did not exist.
*
* @return bool
*/
function removeHeader($name) {
$name = strtolower($name);
if (!isset($this->headers[$name])) {
return false;
}
unset($this->headers[$name]);
return true;
}
/**
* Sets the HTTP version.
*
* Should be 1.0 or 1.1.
*
* @param string $version
* @return void
*/
function setHttpVersion($version) {
$this->httpVersion = $version;
}
/**
* Returns the HTTP version.
*
* @return string
*/
function getHttpVersion() {
return $this->httpVersion;
}
}

View File

@@ -0,0 +1,250 @@
<?php
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
*/
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.
*
* @return string
*/
function getBodyAsString() {
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
*/
function getBody() {
return $this->inner->getBody();
}
/**
* Updates the body resource with a new stream.
*
* @param resource $body
* @return void
*/
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.
*
* @return array
*/
function getHeaders() {
return $this->inner->getHeaders();
}
/**
* Will return true or false, depending on if a HTTP header exists.
*
* @param string $name
* @return bool
*/
function hasHeader($name) {
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().
*
* @param string $name
* @return string|null
*/
function getHeader($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.
*
* @param string $name
* @return string[]
*/
function getHeaderAsArray($name) {
return $this->inner->getHeaderAsArray($name);
}
/**
* 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 $name
* @param string|string[] $value
* @return void
*/
function setHeader($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.
*
* @param array $headers
* @return void
*/
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 $name
* @param string $value
* @return void
*/
function addHeader($name, $value) {
$this->inner->addHeader($name, $value);
}
/**
* Adds a new set of HTTP headers.
*
* Any existing headers will not be overwritten.
*
* @param array $headers
* @return void
*/
function addHeaders(array $headers) {
$this->inner->addHeaders($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.
*
* @return bool
*/
function removeHeader($name) {
$this->inner->removeHeader($name);
}
/**
* Sets the HTTP version.
*
* Should be 1.0 or 1.1.
*
* @param string $version
* @return void
*/
function setHttpVersion($version) {
$this->inner->setHttpVersion($version);
}
/**
* Returns the HTTP version.
*
* @return string
*/
function getHttpVersion() {
return $this->inner->getHttpVersion();
}
}

View File

@@ -0,0 +1,177 @@
<?php
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
*/
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.
*
* @return string
*/
function getBodyAsString();
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string or a stream.
*
* @return resource|string
*/
function getBody();
/**
* Updates the body resource with a new stream.
*
* @param resource $body
* @return void
*/
function setBody($body);
/**
* Returns all the HTTP headers as an array.
*
* Every header is returned as an array, with one or more values.
*
* @return array
*/
function getHeaders();
/**
* Will return true or false, depending on if a HTTP header exists.
*
* @param string $name
* @return bool
*/
function 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().
*
* @param string $name
* @return string|null
*/
function 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.
*
* @param string $name
* @return string[]
*/
function getHeaderAsArray($name);
/**
* 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 $name
* @param string|string[] $value
* @return void
*/
function 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.
*
* @param array $headers
* @return void
*/
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 $name
* @param string $value
* @return void
*/
function addHeader($name, $value);
/**
* Adds a new set of HTTP headers.
*
* Any existing headers will not be overwritten.
*
* @param array $headers
* @return void
*/
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.
*
* @return bool
*/
function removeHeader($name);
/**
* Sets the HTTP version.
*
* Should be 1.0 or 1.1.
*
* @param string $version
* @return void
*/
function setHttpVersion($version);
/**
* Returns the HTTP version.
*
* @return string
*/
function getHttpVersion();
}

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

@@ -0,0 +1,316 @@
<?php
namespace Sabre\HTTP;
use InvalidArgumentException;
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 string $method
* @param string $url
* @param array $headers
* @param resource $body
*/
function __construct($method = null, $url = null, array $headers = null, $body = null) {
if (is_array($method)) {
throw new InvalidArgumentException('The first argument for this constructor should be a string or null, not an array. Did you upgrade from sabre/http 1.0 to 2.0?');
}
if (!is_null($method)) $this->setMethod($method);
if (!is_null($url)) $this->setUrl($url);
if (!is_null($headers)) $this->setHeaders($headers);
if (!is_null($body)) $this->setBody($body);
}
/**
* Returns the current HTTP method
*
* @return string
*/
function getMethod() {
return $this->method;
}
/**
* Sets the HTTP method
*
* @param string $method
* @return void
*/
function setMethod($method) {
$this->method = $method;
}
/**
* Returns the request url.
*
* @return string
*/
function getUrl() {
return $this->url;
}
/**
* Sets the request url.
*
* @param string $url
* @return void
*/
function setUrl($url) {
$this->url = $url;
}
/**
* Returns the list of query parameters.
*
* This is equivalent to PHP's $_GET superglobal.
*
* @return array
*/
function getQueryParameters() {
$url = $this->getUrl();
if (($index = strpos($url, '?')) === false) {
return [];
} else {
parse_str(substr($url, $index + 1), $queryParams);
return $queryParams;
}
}
/**
* Sets the absolute url.
*
* @param string $url
* @return void
*/
function setAbsoluteUrl($url) {
$this->absoluteUrl = $url;
}
/**
* Returns the absolute url.
*
* @return string
*/
function getAbsoluteUrl() {
return $this->absoluteUrl;
}
/**
* Base url
*
* @var string
*/
protected $baseUrl = '/';
/**
* Sets a base url.
*
* This url is used for relative path calculations.
*
* @param string $url
* @return void
*/
function setBaseUrl($url) {
$this->baseUrl = $url;
}
/**
* Returns the current base url.
*
* @return string
*/
function getBaseUrl() {
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.
*
* @return string
*/
function getPath() {
// Removing duplicated slashes.
$uri = str_replace('//', '/', $this->getUrl());
$uri = Uri\normalize($uri);
$baseUri = Uri\normalize($this->getBaseUrl());
if (strpos($uri, $baseUri) === 0) {
// We're not interested in the query part (everything after the ?).
list($uri) = explode('?', $uri);
return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/');
}
// A special case, if the baseUri was accessed without a trailing
// slash, we'll accept it as well.
elseif ($uri . '/' === $baseUri) {
return '';
}
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.
*
* @param array $postData
* @return void
*/
function setPostData(array $postData) {
$this->postData = $postData;
}
/**
* Returns the POST data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* @return array
*/
function getPostData() {
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.
*
* @param string $valueName
* @return string|null
*/
function getRawServerValue($valueName) {
if (isset($this->rawServerData[$valueName])) {
return $this->rawServerData[$valueName];
}
}
/**
* Sets the _SERVER array.
*
* @param array $data
* @return void
*/
function setRawServerData(array $data) {
$this->rawServerData = $data;
}
/**
* Serializes the request object as a string.
*
* This is useful for debugging purposes.
*
* @return string
*/
function __toString() {
$out = $this->getMethod() . ' ' . $this->getUrl() . ' HTTP/' . $this->getHTTPVersion() . "\r\n";
foreach ($this->getHeaders() as $key => $value) {
foreach ($value as $v) {
if ($key === 'Authorization') {
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,231 @@
<?php
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.
*
* @param RequestInterface $inner
*/
function __construct(RequestInterface $inner) {
$this->inner = $inner;
}
/**
* Returns the current HTTP method
*
* @return string
*/
function getMethod() {
return $this->inner->getMethod();
}
/**
* Sets the HTTP method
*
* @param string $method
* @return void
*/
function setMethod($method) {
$this->inner->setMethod($method);
}
/**
* Returns the request url.
*
* @return string
*/
function getUrl() {
return $this->inner->getUrl();
}
/**
* Sets the request url.
*
* @param string $url
* @return void
*/
function setUrl($url) {
$this->inner->setUrl($url);
}
/**
* Returns the absolute url.
*
* @return string
*/
function getAbsoluteUrl() {
return $this->inner->getAbsoluteUrl();
}
/**
* Sets the absolute url.
*
* @param string $url
* @return void
*/
function setAbsoluteUrl($url) {
$this->inner->setAbsoluteUrl($url);
}
/**
* Returns the current base url.
*
* @return string
*/
function getBaseUrl() {
return $this->inner->getBaseUrl();
}
/**
* Sets a base url.
*
* This url is used for relative path calculations.
*
* The base url should default to /
*
* @param string $url
* @return void
*/
function setBaseUrl($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.
*
* @return string
*/
function getPath() {
return $this->inner->getPath();
}
/**
* Returns the list of query parameters.
*
* This is equivalent to PHP's $_GET superglobal.
*
* @return array
*/
function getQueryParameters() {
return $this->inner->getQueryParameters();
}
/**
* Returns the POST data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* @return array
*/
function getPostData() {
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.
*
* @param array $postData
* @return void
*/
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.
*
* @param string $valueName
* @return string|null
*/
function getRawServerValue($valueName) {
return $this->inner->getRawServerValue($valueName);
}
/**
* Sets the _SERVER array.
*
* @param array $data
* @return void
*/
function setRawServerData(array $data) {
$this->inner->setRawServerData($data);
}
/**
* Serializes the request object as a string.
*
* This is useful for debugging purposes.
*
* @return string
*/
function __toString() {
return $this->inner->__toString();
}
}

View File

@@ -0,0 +1,147 @@
<?php
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
*
* @return string
*/
function getMethod();
/**
* Sets the HTTP method
*
* @param string $method
* @return void
*/
function setMethod($method);
/**
* Returns the request url.
*
* @return string
*/
function getUrl();
/**
* Sets the request url.
*
* @param string $url
* @return void
*/
function setUrl($url);
/**
* Returns the absolute url.
*
* @return string
*/
function getAbsoluteUrl();
/**
* Sets the absolute url.
*
* @param string $url
* @return void
*/
function setAbsoluteUrl($url);
/**
* Returns the current base url.
*
* @return string
*/
function getBaseUrl();
/**
* Sets a base url.
*
* This url is used for relative path calculations.
*
* The base url should default to /
*
* @param string $url
* @return void
*/
function 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.
*
* @return string
*/
function getPath();
/**
* Returns the list of query parameters.
*
* This is equivalent to PHP's $_GET superglobal.
*
* @return array
*/
function getQueryParameters();
/**
* Returns the POST data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* @return array
*/
function 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.
*
* @param array $postData
* @return void
*/
function setPostData(array $postData);
/**
* Returns an item from the _SERVER array.
*
* If the value does not exist in the array, null is returned.
*
* @param string $valueName
* @return string|null
*/
function getRawServerValue($valueName);
/**
* Sets the _SERVER array.
*
* @param array $data
* @return void
*/
function setRawServerData(array $data);
}

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

@@ -0,0 +1,194 @@
<?php
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
*/
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
* @return void
*/
function __construct($status = null, array $headers = null, $body = null) {
if (!is_null($status)) $this->setStatus($status);
if (!is_null($headers)) $this->setHeaders($headers);
if (!is_null($body)) $this->setBody($body);
}
/**
* Returns the current HTTP status code.
*
* @return int
*/
function getStatus() {
return $this->status;
}
/**
* Returns the human-readable status string.
*
* In the case of a 200, this may for example be 'OK'.
*
* @return string
*/
function getStatusText() {
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 \InvalidArgumentExeption
* @return void
*/
function setStatus($status) {
if (ctype_digit($status) || is_int($status)) {
$statusCode = $status;
$statusText = isset(self::$statusCodes[$status]) ? self::$statusCodes[$status] : 'Unknown';
} else {
list(
$statusCode,
$statusText
) = explode(' ', $status, 2);
}
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.
*
* @return string
*/
function __toString() {
$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,84 @@
<?php
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.
*
* @param ResponseInterface $inner
*/
function __construct(ResponseInterface $inner) {
$this->inner = $inner;
}
/**
* Returns the current HTTP status code.
*
* @return int
*/
function getStatus() {
return $this->inner->getStatus();
}
/**
* Returns the human-readable status string.
*
* In the case of a 200, this may for example be 'OK'.
*
* @return string
*/
function getStatusText() {
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
* @return void
*/
function setStatus($status) {
$this->inner->setStatus($status);
}
/**
* Serializes the request object as a string.
*
* This is useful for debugging purposes.
*
* @return string
*/
function __toString() {
return $this->inner->__toString();
}
}

View File

@@ -0,0 +1,45 @@
<?php
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.
*
* @return int
*/
function getStatus();
/**
* Returns the human-readable status string.
*
* In the case of a 200, this may for example be 'OK'.
*
* @return string
*/
function 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
* @throws \InvalidArgumentExeption
* @return void
*/
function setStatus($status);
}

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

@@ -0,0 +1,194 @@
<?php
namespace Sabre\HTTP;
/**
* 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.
*
* @return Request
*/
static function getRequest() {
$r = self::createFromServerArray($_SERVER);
$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.
*
* @param ResponseInterface $response
* @return void
*/
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 ($k === 0) {
header($key . ': ' . $v);
} else {
header($key . ': ' . $v, false);
}
}
}
$body = $response->getBody();
if (is_null($body)) return;
$contentLength = $response->getHeader('Content-Length');
if ($contentLength !== null) {
$output = fopen('php://output', 'wb');
if (is_resource($body) && get_resource_type($body) == 'stream') {
stream_copy_to_stream($body, $output, $contentLength);
} else {
fwrite($output, $body, $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.
*
* @param array $serverArray
* @return Request
*/
static function createFromServerArray(array $serverArray) {
$headers = [];
$method = null;
$url = null;
$httpVersion = '1.1';
$protocol = 'http';
$hostName = 'localhost';
foreach ($serverArray as $key => $value) {
switch ($key) {
case 'SERVER_PROTOCOL' :
if ($value === 'HTTP/1.0') {
$httpVersion = '1.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) && $value !== 'off') {
$protocol = 'https';
}
break;
default :
if (substr($key, 0, 5) === 'HTTP_') {
// 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;
}
}
$r = new Request($method, $url, $headers);
$r->setHttpVersion($httpVersion);
$r->setRawServerData($serverArray);
$r->setAbsoluteUrl($protocol . '://' . $hostName . $url);
return $r;
}
}

103
vendor/sabre/http/lib/URLUtil.php vendored Normal file
View File

@@ -0,0 +1,103 @@
<?php
namespace Sabre\HTTP;
use Sabre\URI;
/**
* URL utility class
*
* Note: this class is deprecated. All its functionality moved to functions.php
* or sabre\uri.
*
* @deprectated
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class URLUtil {
/**
* Encodes the path of a url.
*
* slashes (/) are treated as path-separators.
*
* @deprecated use \Sabre\HTTP\encodePath()
* @param string $path
* @return string
*/
static function encodePath($path) {
return encodePath($path);
}
/**
* Encodes a 1 segment of a path
*
* Slashes are considered part of the name, and are encoded as %2f
*
* @deprecated use \Sabre\HTTP\encodePathSegment()
* @param string $pathSegment
* @return string
*/
static function encodePathSegment($pathSegment) {
return encodePathSegment($pathSegment);
}
/**
* Decodes a url-encoded path
*
* @deprecated use \Sabre\HTTP\decodePath
* @param string $path
* @return string
*/
static function decodePath($path) {
return decodePath($path);
}
/**
* Decodes a url-encoded path segment
*
* @deprecated use \Sabre\HTTP\decodePathSegment()
* @param string $path
* @return string
*/
static function decodePathSegment($path) {
return decodePathSegment($path);
}
/**
* Returns the 'dirname' and 'basename' for a path.
*
* @deprecated Use Sabre\Uri\split().
* @param string $path
* @return array
*/
static function splitPath($path) {
return Uri\split($path);
}
/**
* Resolves relative urls, like a browser would.
*
* @deprecated Use Sabre\Uri\resolve().
* @param string $basePath
* @param string $newPath
* @return string
*/
static function resolve($basePath, $newPath) {
return Uri\resolve($basePath, $newPath);
}
}

74
vendor/sabre/http/lib/Util.php vendored Normal file
View File

@@ -0,0 +1,74 @@
<?php
namespace Sabre\HTTP;
/**
* HTTP utility methods
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @author Paul Voegler
* @deprecated All these functions moved to functions.php
* @license http://sabre.io/license/ Modified BSD License
*/
class Util {
/**
* Content negotiation
*
* @deprecated Use \Sabre\HTTP\negotiateContentType
* @param string|null $acceptHeaderValue
* @param array $availableOptions
* @return string|null
*/
static function negotiateContentType($acceptHeaderValue, array $availableOptions) {
return negotiateContentType($acceptHeaderValue, $availableOptions);
}
/**
* Deprecated! Use negotiateContentType.
*
* @deprecated Use \Sabre\HTTP\NegotiateContentType
* @param string|null $acceptHeader
* @param array $availableOptions
* @return string|null
*/
static function negotiate($acceptHeaderValue, array $availableOptions) {
return negotiateContentType($acceptHeaderValue, $availableOptions);
}
/**
* Parses a RFC2616-compatible date string
*
* This method returns false if the date is invalid
*
* @deprecated Use parseDate
* @param string $dateHeader
* @return bool|DateTime
*/
static function parseHTTPDate($dateHeader) {
return parseDate($dateHeader);
}
/**
* Transforms a DateTime object to HTTP's most common date format.
*
* We're serializing it as the RFC 1123 date, which, for HTTP must be
* specified as GMT.
*
* @deprecated Use toDate
* @param \DateTime $dateTime
* @return string
*/
static function toHTTPDate(\DateTime $dateTime) {
return toDate($dateTime);
}
}

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

@@ -0,0 +1,19 @@
<?php
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 = '4.2.1';
}

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

@@ -0,0 +1,445 @@
<?php
namespace Sabre\HTTP;
use DateTime;
/**
* 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
*
* @param string $dateString
* @return bool|DateTime
*/
function parseDate($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 (strpos($dateString, ' GMT') === false)
$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
*
* @param DateTime $dateTime
* @return string
*/
function toDate(DateTime $dateTime) {
// 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
* @param array $availableOptions
* @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 (is_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[] $header
* @return array
*/
function parsePrefer($input) {
$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
* @return string[]
*/
function getHeaderValues($values, $values2 = null) {
$values = (array)$values;
if ($values2) {
$values = array_merge($values, (array)$values2);
}
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
*
* @param string $str
* @return array
*/
function parseMimeType($str) {
$parameters = [];
// If no q= parameter appears, then quality = 1.
$quality = 1;
$parts = explode(';', $str);
// The first part is the mime-type.
$mimeType = array_shift($parts);
$mimeType = explode('/', trim($mimeType));
if (count($mimeType) !== 2) {
// Illegal value
return null;
}
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 ($partName !== 'q') {
$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.
*
* @param string $path
* @return string
*/
function encodePath($path) {
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
*
* @param string $pathSegment
* @return string
*/
function encodePathSegment($pathSegment) {
return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\):@])/', function($match) {
return '%' . sprintf('%02x', ord($match[0]));
}, $pathSegment);
}
/**
* Decodes a url-encoded path
*
* @param string $path
* @return string
*/
function decodePath($path) {
return decodePathSegment($path);
}
/**
* Decodes a url-encoded path segment
*
* @param string $path
* @return string
*/
function decodePathSegment($path) {
$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;
}