pax_global_header00006660000000000000000000000064125241450050014510gustar00rootroot0000000000000052 comment=6b06c03376219b3d608e1f878514ec105ed1b577 sabre-http-3.0.5/000077500000000000000000000000001252414500500135665ustar00rootroot00000000000000sabre-http-3.0.5/.gitignore000066400000000000000000000001561252414500500155600ustar00rootroot00000000000000# Composer vendor/ composer.lock # Tests tests/cov/ # Composer binaries bin/phpunit bin/phpcs # Vim .*.swp sabre-http-3.0.5/.travis.yml000066400000000000000000000005321252414500500156770ustar00rootroot00000000000000language: php php: - 5.4 - 5.5 - 5.6 - hhvm 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/phpcs -p --standard=tests/phpcs/ruleset.xml lib/ sabre-http-3.0.5/ChangeLog.md000066400000000000000000000137361252414500500157510ustar00rootroot00000000000000ChangeLog ========= 3.0.5 (2015-05-11) ------------------ * #47: 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 sabre-http-3.0.5/LICENSE000066400000000000000000000030411252414500500145710ustar00rootroot00000000000000Copyright (C) 2009-2015 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. sabre-http-3.0.5/README.md000066400000000000000000000372411252414500500150540ustar00rootroot00000000000000sabre/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, mainly do to superglobals and the CGI standard. 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.png?branch=master)](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 sabre-http-3.0.5/bin/000077500000000000000000000000001252414500500143365ustar00rootroot00000000000000sabre-http-3.0.5/bin/.empty000066400000000000000000000000001252414500500154630ustar00rootroot00000000000000sabre-http-3.0.5/composer.json000066400000000000000000000021031252414500500163040ustar00rootroot00000000000000{ "name": "sabre/http", "description" : "The sabre/http library provides utilities for dealing with http requests and responses. ", "keywords" : [ "HTTP" ], "homepage" : "https://github.com/fruux/sabre-http", "license" : "BSD-3-Clause", "require" : { "php" : ">=5.4", "ext-mbstring" : "*", "sabre/event" : ">=1.0.0,<3.0.0" }, "require-dev" : { "phpunit/phpunit" : "~4.3", "squizlabs/php_codesniffer": "~1.5.3" }, "suggest" : { "ext-curl" : " to make http requests with the Client class" }, "authors" : [ { "name" : "Evert Pot", "email" : "me@evertpot.com", "homepage" : "http://evertpot.com/", "role" : "Developer" } ], "support" : { "forum" : "https://groups.google.com/group/sabredav-discuss", "source" : "https://github.com/fruux/sabre-http" }, "autoload" : { "psr-4" : { "Sabre\\HTTP\\" : "lib/" } }, "config" : { "bin-dir" : "bin/" } } sabre-http-3.0.5/examples/000077500000000000000000000000001252414500500154045ustar00rootroot00000000000000sabre-http-3.0.5/examples/asyncclient.php000066400000000000000000000035461252414500500204410ustar00rootroot00000000000000sendAsync( $request, // This is the 'success' callback function($response) use ($i) { echo "$i -> " . $response->getStatus() . "\n"; }, // This is the 'error' callback. It is called for general connection // problems (such as not being able to connect to a host, dns errors, // etc.) and also cases where a response was returned, but it had a // status code of 400 or higher. function($error) use ($i) { if ($error['status'] === Client::STATUS_CURLERROR) { // Curl errors echo "$i -> curl error: " . $error['curl_errmsg'] . "\n"; } else { // HTTP errors echo "$i -> " . $error['response']->getStatus() . "\n"; } } ); } // After everything is done, we call 'wait'. This causes the client to wait for // all outstanding http requests to complete. $client->wait(); sabre-http-3.0.5/examples/basicauth.php000066400000000000000000000022631252414500500200630ustar00rootroot00000000000000 "password", "user2" => "password", ]; use Sabre\HTTP\Sapi, Sabre\HTTP\Response, Sabre\HTTP\Auth; // Find the autoloader $paths = [ __DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php', __DIR__ . '/vendor/autoload.php', ]; foreach($paths as $path) { if (file_exists($path)) { include $path; break; } } $request = Sapi::getRequest(); $response = new Response(); $basicAuth = new Auth\Basic("Locked down area", $request, $response); if (!$userPass = $basicAuth->getCredentials()) { // No username or password given $basicAuth->requireLogin(); } elseif (!isset($userList[$userPass[0]]) || $userList[$userPass[0]] !== $userPass[1]) { // Username or password are incorrect $basicAuth->requireLogin(); } else { // Success ! $response->setBody('You are logged in!'); } // Sending the response Sapi::sendResponse($response); sabre-http-3.0.5/examples/client.php000066400000000000000000000015161252414500500173760ustar00rootroot00000000000000addCurlSetting(CURLOPT_PROXY,'localhost:8888'); $response = $client->send($request); echo "Response:\n"; echo (string)$response; sabre-http-3.0.5/examples/reverseproxy.php000066400000000000000000000021641252414500500206750ustar00rootroot00000000000000setBaseUrl($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); sabre-http-3.0.5/examples/stringify.php000066400000000000000000000021121252414500500201270ustar00rootroot00000000000000setHeaders([ 'Host' => 'example.org', 'Content-Type' => 'application/json' ]); $request->setBody(json_encode(['foo' => 'bar'])); echo $request; echo "\r\n\r\n"; $response = new Response(424); $response->setHeaders([ 'Content-Type' => 'text/plain', 'Connection' => 'close', ]); $response->setBody("ABORT! ABORT!"); echo $response; echo "\r\n"; sabre-http-3.0.5/lib/000077500000000000000000000000001252414500500143345ustar00rootroot00000000000000sabre-http-3.0.5/lib/Auth/000077500000000000000000000000001252414500500152355ustar00rootroot00000000000000sabre-http-3.0.5/lib/Auth/AWS.php000066400000000000000000000133271252414500500164060ustar00rootroot00000000000000request->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(true); $this->request->setBody($body,true); 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; } } sabre-http-3.0.5/lib/Auth/AbstractAuth.php000066400000000000000000000025371252414500500203420ustar00rootroot00000000000000realm = $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; } } sabre-http-3.0.5/lib/Auth/Basic.php000066400000000000000000000025571252414500500170000ustar00rootroot00000000000000request->getHeader('Authorization'); if (!$auth) { return null; } if (strtolower(substr($auth,0,6))!=='basic ') { return null; } return explode(':',base64_decode(substr($auth, 6)), 2); } /** * 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); } } sabre-http-3.0.5/lib/Auth/Digest.php000066400000000000000000000141621252414500500171710ustar00rootroot00000000000000nonce = 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; } } sabre-http-3.0.5/lib/Client.php000066400000000000000000000426771252414500500163030ustar00rootroot00000000000000curlSettings = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_NOBODY => false, ]; } /** * 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(URLUtil::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 $stillRunning; } /** * 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 a asynchrous 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 } sabre-http-3.0.5/lib/ClientException.php000066400000000000000000000005641252414500500201470ustar00rootroot00000000000000response = $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; } } sabre-http-3.0.5/lib/HttpException.php000066400000000000000000000014001252414500500176360ustar00rootroot00000000000000getBody(); 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 ''; } return stream_get_contents($body); } /** * 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 $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; } } sabre-http-3.0.5/lib/MessageDecoratorTrait.php000066400000000000000000000125571252414500500213120ustar00rootroot00000000000000inner->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(); } } sabre-http-3.0.5/lib/MessageInterface.php000066400000000000000000000104611252414500500202540ustar00rootroot00000000000000setMethod($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()); if (strpos($uri,$this->getBaseUrl())===0) { // We're not interested in the query part (everything after the ?). list($uri) = explode('?', $uri); return trim(URLUtil::decodePath(substr($uri,strlen($this->getBaseUrl()))),'/'); } // A special case, if the baseUri was accessed without a trailing // slash, we'll accept it as well. elseif ($uri.'/' === $this->getBaseUrl()) { 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; } } sabre-http-3.0.5/lib/RequestDecorator.php000066400000000000000000000103401252414500500203360ustar00rootroot00000000000000inner = $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(); } } sabre-http-3.0.5/lib/RequestInterface.php000066400000000000000000000060121252414500500203150ustar00rootroot00000000000000 '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 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; } } sabre-http-3.0.5/lib/ResponseDecorator.php000066400000000000000000000032051252414500500205060ustar00rootroot00000000000000inner = $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(); } } sabre-http-3.0.5/lib/ResponseInterface.php000066400000000000000000000020331252414500500204620ustar00rootroot00000000000000setBody(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); } } } file_put_contents('php://output', $response->getBody()); } /** * 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 should 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; } } sabre-http-3.0.5/lib/URLUtil.php000066400000000000000000000131571252414500500163540ustar00rootroot00000000000000= 0) return new \DateTime('@' . $realDate, new \DateTimeZone('UTC')); } /** * 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. * * @param \DateTime $dateTime * @return string */ static function toHTTPDate(\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 method 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 */ static function negotiateContentType($acceptHeaderValue, array $availableOptions) { if (!$acceptHeaderValue) { // Grabbing the first in the list. return reset($availableOptions); } $proposals = array_map( ['self', 'parseMimeType'], explode(',', $acceptHeaderValue) ); // Ensuring array keys are reset. $availableOptions = array_values($availableOptions); $options = array_map( ['self', '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 a mime-type and splits it into: * * 1. type * 2. subtype * 3. quality * 4. parameters * * @param string $str * @return array */ private static 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, ]; } /** * Deprecated! Use negotiateContentType. * * @deprecated * @param string|null $acceptHeader * @param array $availableOptions * @return string|null */ static function negotiate($acceptHeaderValue, array $availableOptions) { return self::negotiateContentType($acceptHeaderValue, $availableOptions); } } sabre-http-3.0.5/lib/Version.php000066400000000000000000000005611252414500500164740ustar00rootroot00000000000000response = new Response(); $this->request = new Request(); $this->auth = new AWS(self::REALM, $this->request, $this->response); } public function testNoHeader() { $this->request->setMethod('GET'); $result = $this->auth->init(); $this->assertFalse($result,'No AWS Authorization header was supplied, so we should have gotten false'); $this->assertEquals(AWS::ERR_NOAWSHEADER,$this->auth->errorCode); } public function testIncorrectContentMD5() { $accessKey = 'accessKey'; $secretKey = 'secretKey'; $this->request->setMethod('GET'); $this->request->setHeaders([ 'Authorization' => "AWS $accessKey:sig", 'Content-MD5' => 'garbage', ]); $this->request->setUrl('/'); $this->auth->init(); $result = $this->auth->validate($secretKey); $this->assertFalse($result); $this->assertEquals(AWS::ERR_MD5CHECKSUMWRONG,$this->auth->errorCode); } public function testNoDate() { $accessKey = 'accessKey'; $secretKey = 'secretKey'; $content = 'thisisthebody'; $contentMD5 = base64_encode(md5($content,true)); $this->request->setMethod('POST'); $this->request->setHeaders([ 'Authorization' => "AWS $accessKey:sig", 'Content-MD5' => $contentMD5, ]); $this->request->setUrl('/'); $this->request->setBody($content); $this->auth->init(); $result = $this->auth->validate($secretKey); $this->assertFalse($result); $this->assertEquals(AWS::ERR_INVALIDDATEFORMAT,$this->auth->errorCode); } public function testFutureDate() { $accessKey = 'accessKey'; $secretKey = 'secretKey'; $content = 'thisisthebody'; $contentMD5 = base64_encode(md5($content,true)); $date = new \DateTime('@' . (time() + (60*20))); $date->setTimeZone(new \DateTimeZone('GMT')); $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); $this->request->setMethod('POST'); $this->request->setHeaders([ 'Authorization' => "AWS $accessKey:sig", 'Content-MD5' => $contentMD5, 'Date' => $date, ]); $this->request->setBody($content); $this->auth->init(); $result = $this->auth->validate($secretKey); $this->assertFalse($result); $this->assertEquals(AWS::ERR_REQUESTTIMESKEWED,$this->auth->errorCode); } public function testPastDate() { $accessKey = 'accessKey'; $secretKey = 'secretKey'; $content = 'thisisthebody'; $contentMD5 = base64_encode(md5($content,true)); $date = new \DateTime('@' . (time() - (60*20))); $date->setTimeZone(new \DateTimeZone('GMT')); $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); $this->request->setMethod('POST'); $this->request->setHeaders([ 'Authorization' => "AWS $accessKey:sig", 'Content-MD5' => $contentMD5, 'Date' => $date, ]); $this->request->setBody($content); $this->auth->init(); $result = $this->auth->validate($secretKey); $this->assertFalse($result); $this->assertEquals(AWS::ERR_REQUESTTIMESKEWED,$this->auth->errorCode); } public function testIncorrectSignature() { $accessKey = 'accessKey'; $secretKey = 'secretKey'; $content = 'thisisthebody'; $contentMD5 = base64_encode(md5($content,true)); $date = new \DateTime('now'); $date->setTimeZone(new \DateTimeZone('GMT')); $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); $this->request->setUrl('/'); $this->request->setMethod('POST'); $this->request->setHeaders([ 'Authorization' => "AWS $accessKey:sig", 'Content-MD5' => $contentMD5, 'X-amz-date' => $date, ]); $this->request->setBody($content); $this->request->setBody($content); $this->auth->init(); $result = $this->auth->validate($secretKey); $this->assertFalse($result); $this->assertEquals(AWS::ERR_INVALIDSIGNATURE,$this->auth->errorCode); } public function testValidRequest() { $accessKey = 'accessKey'; $secretKey = 'secretKey'; $content = 'thisisthebody'; $contentMD5 = base64_encode(md5($content,true)); $date = new \DateTime('now'); $date->setTimeZone(new \DateTimeZone('GMT')); $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); $sig = base64_encode($this->hmacsha1($secretKey, "POST\n$contentMD5\n\n$date\nx-amz-date:$date\n/evert" )); $this->request->setUrl('/evert'); $this->request->setMethod('POST'); $this->request->setHeaders([ 'Authorization' => "AWS $accessKey:$sig", 'Content-MD5' => $contentMD5, 'X-amz-date' => $date, ]); $this->request->setBody($content); $this->auth->init(); $result = $this->auth->validate($secretKey); $this->assertTrue($result,'Signature did not validate, got errorcode ' . $this->auth->errorCode); $this->assertEquals($accessKey,$this->auth->getAccessKey()); } public function test401() { $this->auth->requireLogin(); $test = preg_match('/^AWS$/',$this->response->getHeader('WWW-Authenticate'),$matches); $this->assertTrue($test==true,'The WWW-Authenticate response didn\'t match our pattern'); } /** * Generates an HMAC-SHA1 signature * * @param string $key * @param string $message * @return string */ private function hmacsha1($key, $message) { $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; } } sabre-http-3.0.5/tests/HTTP/Auth/BasicTest.php000066400000000000000000000025501252414500500210040ustar00rootroot00000000000000 'Basic ' . base64_encode('user:pass:bla') )); $basic = new Basic('Dagger', $request, new Response()); $this->assertEquals(array( 'user', 'pass:bla', ), $basic->getCredentials($request)); } function testGetCredentialsNoheader() { $request = new Request('GET','/',array()); $basic = new Basic('Dagger', $request, new Response()); $this->assertNull($basic->getCredentials($request)); } function testGetCredentialsNotBasic() { $request = new Request('GET','/',array( 'Authorization' => 'QBasic ' . base64_encode('user:pass:bla') )); $basic = new Basic('Dagger', $request, new Response()); $this->assertNull($basic->getCredentials($request)); } function testRequireLogin() { $response = new Response(); $basic = new Basic('Dagger', new Request(), $response); $basic->requireLogin(); $this->assertEquals('Basic realm="Dagger"', $response->getHeader('WWW-Authenticate')); $this->assertEquals(401, $response->getStatus()); } } sabre-http-3.0.5/tests/HTTP/Auth/DigestTest.php000066400000000000000000000140711252414500500212030ustar00rootroot00000000000000response = new Response(); $this->request = new Request(); $this->auth = new Digest(self::REALM, $this->request, $this->response); } public function testDigest() { list($nonce,$opaque) = $this->getServerTokens(); $username = 'admin'; $password = 12345; $nc = '00002'; $cnonce = uniqid(); $digestHash = md5( md5($username . ':' . self::REALM . ':' . $password) . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . 'auth:' . md5('GET' . ':' . '/') ); $this->request->setMethod('GET'); $this->request->setHeader('Authorization','Digest username="'.$username.'", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth,nc='.$nc.',cnonce="' . $cnonce . '"'); $this->auth->init(); $this->assertEquals($username,$this->auth->getUserName()); $this->assertEquals(self::REALM,$this->auth->getRealm()); $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)),'Authentication is deemed invalid through validateA1'); $this->assertTrue($this->auth->validatePassword($password),'Authentication is deemed invalid through validatePassword'); } public function testInvalidDigest() { list($nonce,$opaque) = $this->getServerTokens(); $username = 'admin'; $password = 12345; $nc = '00002'; $cnonce = uniqid(); $digestHash = md5( md5($username . ':' . self::REALM . ':' . $password) . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . 'auth:' . md5('GET' . ':' . '/') ); $this->request->setMethod('GET'); $this->request->setHeader('Authorization', 'Digest username="'.$username.'", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth,nc='.$nc.',cnonce="' . $cnonce . '"'); $this->auth->init(); $this->assertFalse($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . ($password . 'randomness'))),'Authentication is deemed invalid through validateA1'); } public function testInvalidDigest2() { $this->request->setMethod('GET'); $this->request->setHeader('Authorization','basic blablabla'); $this->auth->init(); $this->assertFalse($this->auth->validateA1(md5('user:realm:password'))); } public function testDigestAuthInt() { $this->auth->setQOP(Digest::QOP_AUTHINT); list($nonce,$opaque) = $this->getServerTokens(Digest::QOP_AUTHINT); $username = 'admin'; $password = 12345; $nc = '00003'; $cnonce = uniqid(); $digestHash = md5( md5($username . ':' . self::REALM . ':' . $password) . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . 'auth-int:' . md5('POST' . ':' . '/' . ':' . md5('body')) ); $this->request->setMethod('POST'); $this->request->setHeader('Authorization','Digest username="'.$username.'", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc='.$nc.',cnonce="' . $cnonce . '"'); $this->request->setBody('body'); $this->auth->init(); $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)),'Authentication is deemed invalid through validateA1'); } public function testDigestAuthBoth() { $this->auth->setQOP(Digest::QOP_AUTHINT | Digest::QOP_AUTH); list($nonce,$opaque) = $this->getServerTokens(Digest::QOP_AUTHINT| Digest::QOP_AUTH); $username = 'admin'; $password = 12345; $nc = '00003'; $cnonce = uniqid(); $digestHash = md5( md5($username . ':' . self::REALM . ':' . $password) . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . 'auth-int:' . md5('POST' . ':' . '/' . ':' . md5('body')) ); $this->request->setMethod('POST'); $this->request->setHeader('Authorization','Digest username="'.$username.'", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc='.$nc.',cnonce="' . $cnonce . '"'); $this->request->setBody('body'); $this->auth->init(); $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)),'Authentication is deemed invalid through validateA1'); } private function getServerTokens($qop = Digest::QOP_AUTH) { $this->auth->requireLogin(); switch($qop) { case Digest::QOP_AUTH : $qopstr='auth'; break; case Digest::QOP_AUTHINT : $qopstr='auth-int'; break; default : $qopstr='auth,auth-int'; break; } $test = preg_match('/Digest realm="'.self::REALM.'",qop="'.$qopstr.'",nonce="([0-9a-f]*)",opaque="([0-9a-f]*)"/', $this->response->getHeader('WWW-Authenticate'),$matches); $this->assertTrue($test==true,'The WWW-Authenticate response didn\'t match our pattern. We received: ' . $this->response->getHeader('WWW-Authenticate')); $nonce = $matches[1]; $opaque = $matches[2]; // Reset our environment $this->setUp(); $this->auth->setQOP($qop); return array($nonce,$opaque); } } sabre-http-3.0.5/tests/HTTP/ClientTest.php000066400000000000000000000340151252414500500203010ustar00rootroot00000000000000addCurlSetting(CURLOPT_POSTREDIR, 0); $request = new Request('GET','http://example.org/', ['X-Foo' => 'bar']); $settings = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_POSTREDIR => 0, CURLOPT_HTTPHEADER => ['X-Foo: bar'], CURLOPT_NOBODY => false, CURLOPT_URL => 'http://example.org/', CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_POSTFIELDS => null, CURLOPT_PUT => false, ]; // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM // at least if this unit test fails in the future we know it is :) if(defined('HHVM_VERSION') === false) { $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } $this->assertEquals($settings, $client->createCurlSettingsArray($request)); } function testCreateCurlSettingsArrayHEAD() { $client = new ClientMock(); $request = new Request('HEAD','http://example.org/', ['X-Foo' => 'bar']); $settings = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_NOBODY => true, CURLOPT_CUSTOMREQUEST => 'HEAD', CURLOPT_HTTPHEADER => ['X-Foo: bar'], CURLOPT_URL => 'http://example.org/', CURLOPT_POSTFIELDS => '', CURLOPT_PUT => false, ]; // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM // at least if this unit test fails in the future we know it is :) if(defined('HHVM_VERSION') === false) { $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } $this->assertEquals($settings, $client->createCurlSettingsArray($request)); } function testCreateCurlSettingsArrayGETAfterHEAD() { $client = new ClientMock(); $request = new Request('HEAD','http://example.org/', ['X-Foo' => 'bar']); // Parsing the settings for this method, and discarding the result. // This will cause the client to automatically persist previous // settings and will help us detect problems. $client->createCurlSettingsArray($request); // This is the real request. $request = new Request('GET','http://example.org/', ['X-Foo' => 'bar']); $settings = [ CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_HTTPHEADER => ['X-Foo: bar'], CURLOPT_NOBODY => false, CURLOPT_URL => 'http://example.org/', CURLOPT_POSTFIELDS => '', CURLOPT_PUT => false, ]; // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM // at least if this unit test fails in the future we know it is :) if(defined('HHVM_VERSION') === false) { $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } $this->assertEquals($settings, $client->createCurlSettingsArray($request)); } function testCreateCurlSettingsArrayPUTStream() { $client = new ClientMock(); $h = fopen('php://memory', 'r+'); fwrite($h, 'booh'); $request = new Request('PUT','http://example.org/', ['X-Foo' => 'bar'], $h); $settings = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_PUT => true, CURLOPT_INFILE => $h, CURLOPT_NOBODY => false, CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_HTTPHEADER => ['X-Foo: bar'], CURLOPT_URL => 'http://example.org/', ]; // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM // at least if this unit test fails in the future we know it is :) if(defined('HHVM_VERSION') === false) { $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } $this->assertEquals($settings, $client->createCurlSettingsArray($request)); } function testCreateCurlSettingsArrayPUTString() { $client = new ClientMock(); $request = new Request('PUT','http://example.org/', ['X-Foo' => 'bar'], 'boo'); $settings = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_NOBODY => false, CURLOPT_POSTFIELDS => 'boo', CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_HTTPHEADER => ['X-Foo: bar'], CURLOPT_URL => 'http://example.org/', ]; // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM // at least if this unit test fails in the future we know it is :) if(defined('HHVM_VERSION') === false) { $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } $this->assertEquals($settings, $client->createCurlSettingsArray($request)); } function testSend() { $client = new ClientMock(); $request = new Request('GET', 'http://example.org/'); $client->on('doRequest', function($request, &$response) { $response = new Response(200); }); $response = $client->send($request); $this->assertEquals(200, $response->getStatus()); } function testSendClientError() { $client = new ClientMock(); $request = new Request('GET', 'http://example.org/'); $client->on('doRequest', function($request, &$response) { throw new ClientException('aaah',1); }); $called = false; $client->on('exception', function() use (&$called) { $called = true; }); try { $client->send($request); $this->fail('send() should have thrown an exception'); } catch (ClientException $e) { } $this->assertTrue($called); } function testSendHttpError() { $client = new ClientMock(); $request = new Request('GET', 'http://example.org/'); $client->on('doRequest', function($request, &$response) { $response = new Response(404); }); $called = 0; $client->on('error', function() use (&$called) { $called++; }); $client->on('error:404', function() use (&$called) { $called++; }); $client->send($request); $this->assertEquals(2,$called); } function testSendRetry() { $client = new ClientMock(); $request = new Request('GET', 'http://example.org/'); $called = 0; $client->on('doRequest', function($request, &$response) use (&$called) { $called++; if ($called < 3) { $response = new Response(404); } else { $response = new Response(200); } }); $errorCalled = 0; $client->on('error', function($request, $response, &$retry, $retryCount) use (&$errorCalled) { $errorCalled++; $retry = true; }); $response = $client->send($request); $this->assertEquals(3,$called); $this->assertEquals(2,$errorCalled); $this->assertEquals(200, $response->getStatus()); } function testHttpErrorException() { $client = new ClientMock(); $client->setThrowExceptions(true); $request = new Request('GET', 'http://example.org/'); $client->on('doRequest', function($request, &$response) { $response = new Response(404); }); try { $client->send($request); $this->fail('An exception should have been thrown'); } catch (ClientHttpException $e) { $this->assertEquals(404, $e->getHttpStatus()); $this->assertInstanceOf('Sabre\HTTP\Response', $e->getResponse()); } } function testParseCurlResult() { $client = new ClientMock(); $client->on('curlStuff', function(&$return) { $return = [ [ 'header_size' => 33, 'http_code' => 200, ], 0, '', ]; }); $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; $result = $client->parseCurlResult($body, 'foobar'); $this->assertEquals(Client::STATUS_SUCCESS, $result['status']); $this->assertEquals(200, $result['http_code']); $this->assertEquals(200, $result['response']->getStatus()); $this->assertEquals(['Header1' => ['Val1']], $result['response']->getHeaders()); $this->assertEquals('Foo', $result['response']->getBodyAsString()); } function testParseCurlError() { $client = new ClientMock(); $client->on('curlStuff', function(&$return) { $return = [ [], 1, 'Curl error', ]; }); $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; $result = $client->parseCurlResult($body, 'foobar'); $this->assertEquals(Client::STATUS_CURLERROR, $result['status']); $this->assertEquals(1, $result['curl_errno']); $this->assertEquals('Curl error', $result['curl_errmsg']); } function testDoRequest() { $client = new ClientMock(); $request = new Request('GET', 'http://example.org/'); $client->on('curlExec', function(&$return) { $return = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; }); $client->on('curlStuff', function(&$return) { $return = [ [ 'header_size' => 33, 'http_code' => 200, ], 0, '', ]; }); $response = $client->doRequest($request); $this->assertEquals(200, $response->getStatus()); $this->assertEquals(['Header1' => ['Val1']], $response->getHeaders()); $this->assertEquals('Foo', $response->getBodyAsString()); } function testDoRequestCurlError() { $client = new ClientMock(); $request = new Request('GET', 'http://example.org/'); $client->on('curlExec', function(&$return) { $return = ""; }); $client->on('curlStuff', function(&$return) { $return = [ [], 1, 'Curl error', ]; }); try { $response = $client->doRequest($request); $this->fail('This should have thrown an exception'); } catch (ClientException $e) { $this->assertEquals(1, $e->getCode()); $this->assertEquals('Curl error', $e->getMessage()); } } } class ClientMock extends Client { protected $persistedSettings = []; /** * Making this method public. * * We are also going to persist all settings this method generates. While * the underlying object doesn't behave exactly the same, it helps us * simulate what curl does internally, and helps us identify problems with * settings that are set by _some_ methods and not correctly reset by other * methods after subsequent use. * forces */ public function createCurlSettingsArray(RequestInterface $request) { $settings = parent::createCurlSettingsArray($request); $settings = $settings + $this->persistedSettings; $this->persistedSettings = $settings; return $settings; } /** * Making this method public. */ public function parseCurlResult($response, $curlHandle) { return parent::parseCurlResult($response, $curlHandle); } /** * This method is responsible for performing a single request. * * @param RequestInterface $request * @return ResponseInterface */ public function doRequest(RequestInterface $request) { $response = null; $this->emit('doRequest', [$request, &$response]); // If nothing modified $response, we're using the default behavior. if (is_null($response)) { return parent::doRequest($request); } else { return $response; } } /** * 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 = null; $this->emit('curlStuff', [&$return]); // If nothing modified $return, we're using the default behavior. if (is_null($return)) { return parent::curlStuff($curlHandle); } else { return $return; } } /** * Calls curl_exec * * This method exists so it can easily be overridden and mocked. * * @param resource $curlHandle * @return string */ protected function curlExec($curlHandle) { $return = null; $this->emit('curlExec', [&$return]); // If nothing modified $return, we're using the default behavior. if (is_null($return)) { return parent::curlExec($curlHandle); } else { return $return; } } } sabre-http-3.0.5/tests/HTTP/MessageDecoratorTest.php000066400000000000000000000051371252414500500223150ustar00rootroot00000000000000inner = new Request(); $this->outer = new RequestDecorator($this->inner); } function testBody() { $this->outer->setBody('foo'); $this->assertEquals('foo', stream_get_contents($this->inner->getBodyAsStream())); $this->assertEquals('foo', stream_get_contents($this->outer->getBodyAsStream())); $this->assertEquals('foo', $this->inner->getBodyAsString()); $this->assertEquals('foo', $this->outer->getBodyAsString()); $this->assertEquals('foo', $this->inner->getBody()); $this->assertEquals('foo', $this->outer->getBody()); } function testHeaders() { $this->outer->setHeaders([ 'a' => 'b', ]); $this->assertEquals(['a' => ['b']], $this->inner->getHeaders()); $this->assertEquals(['a' => ['b']], $this->outer->getHeaders()); $this->outer->setHeaders([ 'c' => 'd', ]); $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->inner->getHeaders()); $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->outer->getHeaders()); $this->outer->addHeaders([ 'e' => 'f', ]); $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->inner->getHeaders()); $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->outer->getHeaders()); } function testHeader() { $this->assertFalse($this->outer->hasHeader('a')); $this->assertFalse($this->inner->hasHeader('a')); $this->outer->setHeader('a', 'c'); $this->assertTrue($this->outer->hasHeader('a')); $this->assertTrue($this->inner->hasHeader('a')); $this->assertEquals('c', $this->inner->getHeader('A')); $this->assertEquals('c', $this->outer->getHeader('A')); $this->outer->addHeader('A','d'); $this->assertEquals( ['c','d'], $this->inner->getHeaderAsArray('A') ); $this->assertEquals( ['c','d'], $this->outer->getHeaderAsArray('A') ); $this->outer->removeHeader('a'); $this->assertNull($this->inner->getHeader('A')); $this->assertNull($this->outer->getHeader('A')); } function testHttpVersion() { $this->outer->setHttpVersion('1.0'); $this->assertEquals('1.0', $this->inner->getHttpVersion()); $this->assertEquals('1.0', $this->outer->getHttpVersion()); } } sabre-http-3.0.5/tests/HTTP/MessageTest.php000066400000000000000000000104441252414500500204470ustar00rootroot00000000000000assertInstanceOf('Sabre\HTTP\Message', $message); } function testStreamBody() { $body = 'foo'; $h = fopen('php://memory', 'r+'); fwrite($h, $body); rewind($h); $message = new MessageMock(); $message->setBody($h); $this->assertEquals($body, $message->getBodyAsString()); rewind($h); $this->assertEquals($body, stream_get_contents($message->getBodyAsStream())); rewind($h); $this->assertEquals($body, stream_get_contents($message->getBody())); } function testStringBody() { $body = 'foo'; $message = new MessageMock(); $message->setBody($body); $this->assertEquals($body, $message->getBodyAsString()); $this->assertEquals($body, stream_get_contents($message->getBodyAsStream())); $this->assertEquals($body, $message->getBody()); } function testGetEmptyBodyStream() { $message = new MessageMock(); $body = $message->getBodyAsStream(); $this->assertEquals('', stream_get_contents($body)); } function testGetEmptyBodyString() { $message = new MessageMock(); $body = $message->getBodyAsString(); $this->assertEquals('', $body); } function testHeaders() { $message = new MessageMock(); $message->setHeader('X-Foo', 'bar'); // Testing caselessness $this->assertEquals('bar', $message->getHeader('X-Foo')); $this->assertEquals('bar', $message->getHeader('x-fOO')); $this->assertTrue( $message->removeHeader('X-FOO') ); $this->assertNull($message->getHeader('X-Foo')); $this->assertFalse( $message->removeHeader('X-FOO') ); } function testSetHeaders() { $message = new MessageMock(); $headers = [ 'X-Foo' => ['1'], 'X-Bar' => ['2'], ]; $message->setHeaders($headers); $this->assertEquals($headers, $message->getHeaders()); $message->setHeaders([ 'X-Foo' => ['3','4'], 'X-Bar' => '5', ]); $expected = [ 'X-Foo' => ['3','4'], 'X-Bar' => ['5'], ]; $this->assertEquals($expected, $message->getHeaders()); } function testAddHeaders() { $message = new MessageMock(); $headers = [ 'X-Foo' => ['1'], 'X-Bar' => ['2'], ]; $message->addHeaders($headers); $this->assertEquals($headers, $message->getHeaders()); $message->addHeaders([ 'X-Foo' => ['3','4'], 'X-Bar' => '5', ]); $expected = [ 'X-Foo' => ['1','3','4'], 'X-Bar' => ['2','5'], ]; $this->assertEquals($expected, $message->getHeaders()); } function testSendBody() { $message = new MessageMock(); // String $message->setBody('foo'); // Stream $h = fopen('php://memory','r+'); fwrite($h,'bar'); rewind($h); $message->setBody($h); $body = $message->getBody(); rewind($body); $this->assertEquals('bar', stream_get_contents($body)); } function testMultipleHeaders() { $message = new MessageMock(); $message->setHeader('a', '1'); $message->addHeader('A', '2'); $this->assertEquals( "1,2", $message->getHeader('A') ); $this->assertEquals( "1,2", $message->getHeader('a') ); $this->assertEquals( ['1','2'], $message->getHeaderAsArray('a') ); $this->assertEquals( ['1','2'], $message->getHeaderAsArray('A') ); $this->assertEquals( [], $message->getHeaderAsArray('B') ); } function testHasHeaders() { $message = new MessageMock(); $this->assertFalse($message->hasHeader('X-Foo')); $message->setHeader('X-Foo', 'Bar'); $this->assertTrue($message->hasHeader('X-Foo')); } } class MessageMock extends Message { } sabre-http-3.0.5/tests/HTTP/RequestDecoratorTest.php000066400000000000000000000053701252414500500223600ustar00rootroot00000000000000inner = new Request(); $this->outer = new RequestDecorator($this->inner); } function testMethod() { $this->outer->setMethod('FOO'); $this->assertEquals('FOO', $this->inner->getMethod()); $this->assertEquals('FOO', $this->outer->getMethod()); } function testUrl() { $this->outer->setUrl('/foo'); $this->assertEquals('/foo', $this->inner->getUrl()); $this->assertEquals('/foo', $this->outer->getUrl()); } function testAbsoluteUrl() { $this->outer->setAbsoluteUrl('http://example.org/foo'); $this->assertEquals('http://example.org/foo', $this->inner->getAbsoluteUrl()); $this->assertEquals('http://example.org/foo', $this->outer->getAbsoluteUrl()); } function testBaseUrl() { $this->outer->setBaseUrl('/foo'); $this->assertEquals('/foo', $this->inner->getBaseUrl()); $this->assertEquals('/foo', $this->outer->getBaseUrl()); } function testPath() { $this->outer->setBaseUrl('/foo'); $this->outer->setUrl('/foo/bar'); $this->assertEquals('bar', $this->inner->getPath()); $this->assertEquals('bar', $this->outer->getPath()); } function testQueryParams() { $this->outer->setUrl('/foo?a=b&c=d&e'); $expected = [ 'a' => 'b', 'c' => 'd', 'e' => null, ]; $this->assertEquals($expected, $this->inner->getQueryParameters()); $this->assertEquals($expected, $this->outer->getQueryParameters()); } function testPostData() { $postData = [ 'a' => 'b', 'c' => 'd', 'e' => null, ]; $this->outer->setPostData($postData); $this->assertEquals($postData, $this->inner->getPostData()); $this->assertEquals($postData, $this->outer->getPostData()); } function testServerData() { $serverData = [ 'HTTPS' => 'On', ]; $this->outer->setRawServerData($serverData); $this->assertEquals('On', $this->inner->getRawServerValue('HTTPS')); $this->assertEquals('On', $this->outer->getRawServerValue('HTTPS')); $this->assertNull($this->inner->getRawServerValue('FOO')); $this->assertNull($this->outer->getRawServerValue('FOO')); } function testToString() { $this->inner->setMethod('POST'); $this->inner->setUrl('/foo/bar/'); $this->inner->setBody('foo'); $this->inner->setHeader('foo','bar'); $this->assertEquals((string)$this->inner, (string)$this->outer); } } sabre-http-3.0.5/tests/HTTP/RequestTest.php000066400000000000000000000070231252414500500205120ustar00rootroot00000000000000 'Evert', )); $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( 'User-Agent' => ['Evert'], ), $request->getHeaders()); } function testGetQueryParameters() { $request = new Request('GET', '/foo?a=b&c&d=e'); $this->assertEquals([ 'a' => 'b', 'c' => null, 'd' => 'e', ], $request->getQueryParameters()); } function testGetQueryParametersNoData() { $request = new Request('GET', '/foo'); $this->assertEquals([], $request->getQueryParameters()); } /** * @backupGlobals */ function testCreateFromPHPRequest() { $_SERVER['REQUEST_METHOD'] = 'PUT'; $request = Sapi::getRequest(); $this->assertEquals('PUT', $request->getMethod()); } function testGetAbsoluteUrl() { $s = [ 'HTTP_HOST' => 'sabredav.org', 'REQUEST_URI' => '/foo' ]; $r = Sapi::createFromServerArray($s); $this->assertEquals('http://sabredav.org/foo', $r->getAbsoluteUrl()); $s = [ 'HTTP_HOST' => 'sabredav.org', 'REQUEST_URI' => '/foo', 'HTTPS' => 'on', ]; $r = Sapi::createFromServerArray($s); $this->assertEquals('https://sabredav.org/foo', $r->getAbsoluteUrl()); } function testGetPostData() { $post = [ 'bla' => 'foo', ]; $r = new Request(); $r->setPostData($post); $this->assertEquals($post, $r->getPostData()); } function testGetPath() { $request = new Request(); $request->setBaseUrl('/foo'); $request->setUrl('/foo/bar/'); $this->assertEquals('bar', $request->getPath()); } function testGetPathStrippedQuery() { $request = new Request(); $request->setBaseUrl('/foo'); $request->setUrl('/foo/bar/?a=b'); $this->assertEquals('bar', $request->getPath()); } function testGetPathMissingSlash() { $request = new Request(); $request->setBaseUrl('/foo/'); $request->setUrl('/foo'); $this->assertEquals('', $request->getPath()); } /** * @expectedException \LogicException */ function testGetPathOutsideBaseUrl() { $request = new Request(); $request->setBaseUrl('/foo/'); $request->setUrl('/bar/'); $request->getPath(); } function testToString() { $request = new Request('PUT', '/foo/bar', ['Content-Type' => 'text/xml']); $request->setBody('foo'); $expected = <<assertEquals($expected, (string)$request); } function testToStringAuthorization() { $request = new Request('PUT', '/foo/bar', ['Content-Type' => 'text/xml', 'Authorization' => 'Basic foobar']); $request->setBody('foo'); $expected = <<assertEquals($expected, (string)$request); } /** * @expectedException \InvalidArgumentException */ function testConstructorWithArray() { $request = new Request(array()); } } sabre-http-3.0.5/tests/HTTP/ResponseDecoratorTest.php000066400000000000000000000015211252414500500225200ustar00rootroot00000000000000inner = new Response(); $this->outer = new ResponseDecorator($this->inner); } function testStatus() { $this->outer->setStatus(201); $this->assertEquals(201, $this->inner->getStatus()); $this->assertEquals(201, $this->outer->getStatus()); $this->assertEquals('Created', $this->inner->getStatusText()); $this->assertEquals('Created', $this->outer->getStatusText()); } function testToString() { $this->inner->setStatus(201); $this->inner->setBody('foo'); $this->inner->setHeader('foo','bar'); $this->assertEquals((string)$this->inner, (string)$this->outer); } } sabre-http-3.0.5/tests/HTTP/ResponseTest.php000066400000000000000000000020031252414500500206510ustar00rootroot00000000000000 'text/xml']); $this->assertEquals(200, $response->getStatus()); $this->assertEquals('OK', $response->getStatusText()); } function testSetStatus() { $response = new Response(); $response->setStatus('402 Where\'s my money?'); $this->assertEquals(402, $response->getStatus()); $this->assertEquals('Where\'s my money?', $response->getStatusText()); } /** * @expectedException InvalidArgumentException */ function testInvalidStatus() { $response = new Response(1000); } function testToString() { $response = new Response(200, ['Content-Type' => 'text/xml']); $response->setBody('foo'); $expected = <<assertEquals($expected, (string)$response); } } sabre-http-3.0.5/tests/HTTP/SapiTest.php000066400000000000000000000066711252414500500177660ustar00rootroot00000000000000 '/foo', 'REQUEST_METHOD' => 'GET', 'HTTP_USER_AGENT' => 'Evert', 'CONTENT_TYPE' => 'text/xml', 'CONTENT_LENGTH' => '400', 'SERVER_PROTOCOL' => 'HTTP/1.0', )); $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( 'User-Agent' => ['Evert'], 'Content-Type' => ['text/xml'], 'Content-Length' => ['400'], ), $request->getHeaders()); $this->assertEquals('1.0', $request->getHttpVersion()); $this->assertEquals('400', $request->getRawServerValue('CONTENT_LENGTH')); $this->assertNull($request->getRawServerValue('FOO')); } function testConstructPHPAuth() { $request = Sapi::createFromServerArray(array( 'REQUEST_URI' => '/foo', 'REQUEST_METHOD' => 'GET', 'PHP_AUTH_USER' => 'user', 'PHP_AUTH_PW' => 'pass', )); $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( 'Authorization' => ['Basic ' . base64_encode('user:pass')], ), $request->getHeaders()); } function testConstructPHPAuthDigest() { $request = Sapi::createFromServerArray(array( 'REQUEST_URI' => '/foo', 'REQUEST_METHOD' => 'GET', 'PHP_AUTH_DIGEST' => 'blabla', )); $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( 'Authorization' => ['Digest blabla'], ), $request->getHeaders()); } function testConstructRedirectAuth() { $request = Sapi::createFromServerArray(array( 'REQUEST_URI' => '/foo', 'REQUEST_METHOD' => 'GET', 'REDIRECT_HTTP_AUTHORIZATION' => 'Basic bla', )); $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( 'Authorization' => ['Basic bla'], ), $request->getHeaders()); } /** * @runInSeparateProcess * * Unfortunately we have no way of testing if the HTTP response code got * changed. */ function testSend() { if (!function_exists('xdebug_get_headers')) { $this->markTestSkipped('XDebug needs to be installed for this test to run'); } $response = new Response(204, ['Content-Type' => 'text/xml;charset=UTF-8']); // Second Content-Type header. Normally this doesn't make sense. $response->addHeader('Content-Type', 'application/xml'); $response->setBody('foo'); ob_start(); Sapi::sendResponse($response); $headers = xdebug_get_headers(); $result = ob_get_clean(); header_remove(); $this->assertEquals( [ "Content-Type: text/xml;charset=UTF-8", "Content-Type: application/xml", ], $headers ); $this->assertEquals('foo', $result); } } sabre-http-3.0.5/tests/HTTP/URLUtilTest.php000066400000000000000000000124321252414500500203620ustar00rootroot00000000000000assertEquals( '%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f'. '%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f'. '%20%21%22%23%24%25%26%27()%2a%2b%2c-./'. '0123456789:%3b%3c%3d%3e%3f'. '@ABCDEFGHIJKLMNO' . 'PQRSTUVWXYZ%5b%5c%5d%5e_' . '%60abcdefghijklmno' . 'pqrstuvwxyz%7b%7c%7d~%7f', $newStr); $this->assertEquals($str,URLUtil::decodePath($newStr)); } function testEncodePathSegment() { $str = ''; for($i=0;$i<128;$i++) $str.=chr($i); $newStr = URLUtil::encodePathSegment($str); // Note: almost exactly the same as the last test, with the // exception of the encoding of / (ascii code 2f) $this->assertEquals( '%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f'. '%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f'. '%20%21%22%23%24%25%26%27()%2a%2b%2c-.%2f'. '0123456789:%3b%3c%3d%3e%3f'. '@ABCDEFGHIJKLMNO' . 'PQRSTUVWXYZ%5b%5c%5d%5e_' . '%60abcdefghijklmno' . 'pqrstuvwxyz%7b%7c%7d~%7f', $newStr); $this->assertEquals($str,URLUtil::decodePathSegment($newStr)); } function testDecode() { $str = 'Hello%20Test+Test2.txt'; $newStr = URLUtil::decodePath($str); $this->assertEquals('Hello Test+Test2.txt',$newStr); } /** * @depends testDecode */ function testDecodeUmlaut() { $str = 'Hello%C3%BC.txt'; $newStr = URLUtil::decodePath($str); $this->assertEquals("Hello\xC3\xBC.txt",$newStr); } /** * @depends testDecodeUmlaut */ function testDecodeUmlautLatin1() { $str = 'Hello%FC.txt'; $newStr = URLUtil::decodePath($str); $this->assertEquals("Hello\xC3\xBC.txt",$newStr); } /** * This testcase was sent by a bug reporter * * @depends testDecode */ function testDecodeAccentsWindows7() { $str = '/webdav/%C3%A0fo%C3%B3'; $newStr = URLUtil::decodePath($str); $this->assertEquals(strtolower($str),URLUtil::encodePath($newStr)); } function testSplitPath() { $strings = array( // input // expected result '/foo/bar' => array('/foo','bar'), '/foo/bar/' => array('/foo','bar'), 'foo/bar/' => array('foo','bar'), 'foo/bar' => array('foo','bar'), 'foo/bar/baz' => array('foo/bar','baz'), 'foo/bar/baz/' => array('foo/bar','baz'), 'foo' => array('','foo'), 'foo/' => array('','foo'), '/foo/' => array('','foo'), '/foo' => array('','foo'), '' => array(null,null), // UTF-8 "/\xC3\xA0fo\xC3\xB3/bar" => array("/\xC3\xA0fo\xC3\xB3",'bar'), "/\xC3\xA0foo/b\xC3\xBCr/" => array("/\xC3\xA0foo","b\xC3\xBCr"), "foo/\xC3\xA0\xC3\xBCr" => array("foo","\xC3\xA0\xC3\xBCr"), ); foreach($strings as $input => $expected) { $output = URLUtil::splitPath($input); $this->assertEquals($expected, $output, 'The expected output for \'' . $input . '\' was incorrect'); } } /** * @dataProvider resolveData */ function testResolve($base, $update, $expected) { $this->assertEquals( $expected, URLUtil::resolve($base, $update) ); } function resolveData() { return [ [ 'http://example.org/foo/baz', '/bar', 'http://example.org/bar', ], [ 'https://example.org/foo', '//example.net/', 'https://example.net/', ], [ 'https://example.org/foo', '?a=b', 'https://example.org/foo?a=b', ], [ '//example.org/foo', '?a=b', '//example.org/foo?a=b', ], // Ports and fragments [ 'https://example.org:81/foo#hey', '?a=b#c=d', 'https://example.org:81/foo?a=b#c=d', ], // Relative.. in-directory paths [ 'http://example.org/foo/bar', 'bar2', 'http://example.org/foo/bar2', ], // Now the base path ended with a slash [ 'http://example.org/foo/bar/', 'bar2/bar3', 'http://example.org/foo/bar/bar2/bar3', ], // .. and . [ 'http://example.org/foo/bar/', '../bar2/.././/bar3/', 'http://example.org/foo/bar3/', ], ]; } } sabre-http-3.0.5/tests/HTTP/UtilTest.php000066400000000000000000000144671252414500500200110ustar00rootroot00000000000000assertEquals($expected, $result->format('U')); } $result = Util::parseHTTPDate('Wed Oct 6 10:26:00 2010'); $this->assertEquals(1286360760, $result->format('U')); } function testParseHTTPDateFail() { $times = array( //random string 'NOW', // not-GMT timezone 'Wednesday, 13-Oct-10 10:26:00 UTC', // No space before the 6 'Wed Oct 6 10:26:00 2010', // Invalid day 'Wed Oct 0 10:26:00 2010', 'Wed Oct 32 10:26:00 2010', 'Wed, 0 Oct 2010 10:26:00 GMT', 'Wed, 32 Oct 2010 10:26:00 GMT', 'Wednesday, 32-Oct-10 10:26:00 GMT', // Invalid hour 'Wed, 13 Oct 2010 24:26:00 GMT', 'Wednesday, 13-Oct-10 24:26:00 GMT', 'Wed Oct 13 24:26:00 2010', ); foreach($times as $time) { $this->assertFalse(Util::parseHTTPDate($time), 'We used the string: ' . $time); } } function testTimezones() { $default = date_default_timezone_get(); date_default_timezone_set('Europe/Amsterdam'); $this->testParseHTTPDate(); date_default_timezone_set($default); } function testToHTTPDate() { $dt = new \DateTime('2011-12-10 12:00:00 +0200'); $this->assertEquals( 'Sat, 10 Dec 2011 10:00:00 GMT', Util::toHTTPDate($dt) ); } function testStrtotimeFail() { // Strtotime may return -1 when the date cannot be parsed. // We are simulating this situation by testing a date that actually // results in -1. (because I have found no other way to break this // code) $time = 'Wed, 13 Oct 1960 10:26:00 GMT'; $this->assertNull(Util::parseHTTPDate($time)); } /** * @dataProvider negotiateData */ function testNegotiate($acceptHeader, $available, $expected) { $this->assertEquals( $expected, Util::negotiate($acceptHeader, $available) ); } function negotiateData() { return [ [ // simple 'application/xml', ['application/xml'], 'application/xml', ], [ // no header null, ['application/xml'], 'application/xml', ], [ // 2 options 'application/json', ['application/xml', 'application/json'], 'application/json', ], [ // 2 choices 'application/json, application/xml', ['application/xml'], 'application/xml', ], [ // quality 'application/xml;q=0.2, application/json', ['application/xml', 'application/json'], 'application/json', ], [ // wildcard 'image/jpeg, image/png, */*', ['application/xml', 'application/json'], 'application/xml', ], [ // wildcard + quality 'image/jpeg, image/png; q=0.5, */*', ['application/xml', 'application/json', 'image/png'], 'application/xml', ], [ // no match 'image/jpeg', ['application/xml'], null, ], [ // This is used in sabre/dav 'text/vcard; version=4.0', [ // Most often used mime-type. Version 3 'text/x-vcard', // The correct standard mime-type. Defaults to version 3 as // well. 'text/vcard', // vCard 4 'text/vcard; version=4.0', // vCard 3 'text/vcard; version=3.0', // jCard 'application/vcard+json', ], 'text/vcard; version=4.0', ], [ // rfc7231 example 1 'audio/*; q=0.2, audio/basic', [ 'audio/pcm', 'audio/basic', ], 'audio/basic', ], [ // Lower quality after 'audio/pcm; q=0.2, audio/basic; q=0.1', [ 'audio/pcm', 'audio/basic', ], 'audio/pcm', ], [ // Random parameter, should be ignored 'audio/pcm; hello; q=0.2, audio/basic; q=0.1', [ 'audio/pcm', 'audio/basic', ], 'audio/pcm', ], [ // No whitepace after type, should pick the one that is the most specific. 'text/vcard;version=3.0, text/vcard', [ 'text/vcard', 'text/vcard; version=3.0' ], 'text/vcard; version=3.0', ], [ // Same as last one, but order is different 'text/vcard, text/vcard;version=3.0', [ 'text/vcard; version=3.0', 'text/vcard', ], 'text/vcard; version=3.0', ], [ // Charset should be ignored here. 'text/vcard; charset=utf-8; version=3.0, text/vcard', [ 'text/vcard', 'text/vcard; version=3.0' ], 'text/vcard; version=3.0', ], [ // Undefined offset issue. 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2', ['application/xml', 'application/json', 'image/png'], 'application/xml', ], ]; } } sabre-http-3.0.5/tests/bootstrap.php000066400000000000000000000002551252414500500174600ustar00rootroot00000000000000 sabre.io codesniffer ruleset sabre-http-3.0.5/tests/phpunit.xml000066400000000000000000000006451252414500500171460ustar00rootroot00000000000000 HTTP/ ../lib/