Update core 8.3.0

This commit is contained in:
Rob Davies 2017-04-13 15:53:35 +01:00
parent da7a7918f8
commit cd7a898e66
6144 changed files with 132297 additions and 87747 deletions

View file

@ -6,11 +6,11 @@ php:
- 5.5
- 5.6
- 7.0
- 7.1
- hhvm
before_script:
- curl --version
- composer self-update
- composer install --no-interaction --prefer-source --dev
- ~/.nvm/nvm.sh install v0.6.14
- ~/.nvm/nvm.sh run v0.6.14

View file

@ -1,5 +1,16 @@
# CHANGELOG
## 6.2.3 - 2017-02-28
* Fix deprecations with guzzle/psr7 version 1.4
## 6.2.2 - 2016-10-08
* Allow to pass nullable Response to delay callable
* Only add scheme when host is present
* Fix drain case where content-length is the literal string zero
* Obfuscate in-URL credentials in exceptions
## 6.2.1 - 2016-07-18
* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
@ -746,7 +757,7 @@ interfaces.
* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
to help with refcount based garbage collection of resources created by sending a request
* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it'sdeprecated). Use the
* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
HistoryPlugin for a history.
* Added a `responseBody` alias for the `response_body` location
* Refactored internals to no longer rely on Response::getRequest()

View file

@ -132,7 +132,7 @@ $handler = GuzzleHttp\HandlerStack::create();
$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
// Notice that we have to return a request object
return $request->withHeader('X-Foo', 'Bar');
});
}));
// Inject the handler into the client
$client = new GuzzleHttp\Client(['handler' => $handler]);
```
@ -600,7 +600,7 @@ these if needed):
The following plugins are not part of the core Guzzle package, but are provided
in separate repositories:
- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be muchs simpler
- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
to build custom retry policies using simple functions rather than various
chained classes. See: https://github.com/guzzle/retry-subscriber
- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
@ -664,8 +664,8 @@ that contain additional metadata accessible via `getMetadata()`.
The entire concept of the StreamRequestFactory has been removed. The way this
was used in Guzzle 3 broke the actual interface of sending streaming requests
(instead of getting back a Response, you got a StreamInterface). Streeaming
PHP requests are now implemented throught the `GuzzleHttp\Adapter\StreamAdapter`.
(instead of getting back a Response, you got a StreamInterface). Streaming
PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.
3.6 to 3.7
----------

View file

@ -14,7 +14,7 @@
],
"require": {
"php": ">=5.5",
"guzzlehttp/psr7": "^1.3.1",
"guzzlehttp/psr7": "^1.4",
"guzzlehttp/promises": "^1.0"
},
"require-dev": {

View file

@ -9,18 +9,18 @@ use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* @method ResponseInterface get($uri, array $options = [])
* @method ResponseInterface head($uri, array $options = [])
* @method ResponseInterface put($uri, array $options = [])
* @method ResponseInterface post($uri, array $options = [])
* @method ResponseInterface patch($uri, array $options = [])
* @method ResponseInterface delete($uri, array $options = [])
* @method Promise\PromiseInterface getAsync($uri, array $options = [])
* @method Promise\PromiseInterface headAsync($uri, array $options = [])
* @method Promise\PromiseInterface putAsync($uri, array $options = [])
* @method Promise\PromiseInterface postAsync($uri, array $options = [])
* @method Promise\PromiseInterface patchAsync($uri, array $options = [])
* @method Promise\PromiseInterface deleteAsync($uri, array $options = [])
* @method ResponseInterface get(string|UriInterface $uri, array $options = [])
* @method ResponseInterface head(string|UriInterface $uri, array $options = [])
* @method ResponseInterface put(string|UriInterface $uri, array $options = [])
* @method ResponseInterface post(string|UriInterface $uri, array $options = [])
* @method ResponseInterface patch(string|UriInterface $uri, array $options = [])
* @method ResponseInterface delete(string|UriInterface $uri, array $options = [])
* @method Promise\PromiseInterface getAsync(string|UriInterface $uri, array $options = [])
* @method Promise\PromiseInterface headAsync(string|UriInterface $uri, array $options = [])
* @method Promise\PromiseInterface putAsync(string|UriInterface $uri, array $options = [])
* @method Promise\PromiseInterface postAsync(string|UriInterface $uri, array $options = [])
* @method Promise\PromiseInterface patchAsync(string|UriInterface $uri, array $options = [])
* @method Promise\PromiseInterface deleteAsync(string|UriInterface $uri, array $options = [])
*/
class Client implements ClientInterface
{
@ -142,10 +142,10 @@ class Client implements ClientInterface
$uri = Psr7\uri_for($uri === null ? '' : $uri);
if (isset($config['base_uri'])) {
$uri = Psr7\Uri::resolve(Psr7\uri_for($config['base_uri']), $uri);
$uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
}
return $uri->getScheme() === '' ? $uri->withScheme('http') : $uri;
return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
}
/**

View file

@ -4,6 +4,7 @@ namespace GuzzleHttp\Exception;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\UriInterface;
/**
* HTTP Request exception
@ -89,12 +90,15 @@ class RequestException extends TransferException
$className = __CLASS__;
}
$uri = $request->getUri();
$uri = static::obfuscateUri($uri);
// Server Error: `GET /` resulted in a `404 Not Found` response:
// <html> ... (truncated)
$message = sprintf(
'%s: `%s` resulted in a `%s` response',
$label,
$request->getMethod() . ' ' . $request->getUri(),
$request->getMethod() . ' ' . $uri,
$response->getStatusCode() . ' ' . $response->getReasonPhrase()
);
@ -141,6 +145,24 @@ class RequestException extends TransferException
return $summary;
}
/**
* Obfuscates URI if there is an username and a password present
*
* @param UriInterface $uri
*
* @return UriInterface
*/
private static function obfuscateUri($uri)
{
$userInfo = $uri->getUserInfo();
if (false !== ($pos = strpos($userInfo, ':'))) {
return $uri->withUserInfo(substr($userInfo, 0, $pos), '***');
}
return $uri;
}
/**
* Get the request that caused the exception
*

View file

@ -210,7 +210,7 @@ class StreamHandler
Psr7\copy_to_stream(
$source,
$sink,
strlen($contentLength) > 0 ? (int) $contentLength : -1
(strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
);
$sink->seek(0);

View file

@ -208,9 +208,9 @@ class RedirectMiddleware
ResponseInterface $response,
array $protocols
) {
$location = Psr7\Uri::resolve(
$location = Psr7\UriResolver::resolve(
$request->getUri(),
$response->getHeaderLine('Location')
new Psr7\Uri($response->getHeaderLine('Location'))
);
// Ensure that the redirect URI is allowed based on the protocols.

View file

@ -43,8 +43,8 @@ final class RequestOptions
const AUTH = 'auth';
/**
* body: (string|null|callable|iterator|object) Body to send in the
* request.
* body: (resource|string|null|int|float|StreamInterface|callable|\Iterator)
* Body to send in the request.
*/
const BODY = 'body';

View file

@ -5,6 +5,7 @@ use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Middleware that retries requests based on the boolean result of
@ -25,8 +26,8 @@ class RetryMiddleware
* retried.
* @param callable $nextHandler Next handler to invoke.
* @param callable $delay Function that accepts the number of retries
* and returns the number of milliseconds to
* delay.
* and [response] and returns the number of
* milliseconds to delay.
*/
public function __construct(
callable $decider,
@ -82,7 +83,7 @@ class RetryMiddleware
)) {
return $value;
}
return $this->doRetry($req, $options);
return $this->doRetry($req, $options, $value);
};
}
@ -102,9 +103,9 @@ class RetryMiddleware
};
}
private function doRetry(RequestInterface $request, array $options)
private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null)
{
$options['delay'] = call_user_func($this->delay, ++$options['retries']);
$options['delay'] = call_user_func($this->delay, ++$options['retries'], $response);
return $this($request, $options);
}

View file

@ -1,11 +0,0 @@
phpunit.xml
composer.phar
composer.lock
composer-test.lock
vendor/
build/artifacts/
artifacts/
docs/_build
docs/*.pyc
.idea
.DS_STORE

View file

@ -1,19 +0,0 @@
language: php
php:
- 5.5
- 5.6
- 7.0
- hhvm
sudo: false
install:
- travis_retry composer install --no-interaction --prefer-source
script: make test
matrix:
allow_failures:
- php: hhvm
fast_finish: true

View file

@ -1,31 +1,65 @@
# CHANGELOG
## 1.3.1 - 2016-12-20
### Fixed
- `wait()` foreign promise compatibility
## 1.3.0 - 2016-11-18
### Added
- Adds support for custom task queues.
### Fixed
- Fixed coroutine promise memory leak.
## 1.2.0 - 2016-05-18
* Update to now catch `\Throwable` on PHP 7+
### Changed
- Update to now catch `\Throwable` on PHP 7+
## 1.1.0 - 2016-03-07
* Update EachPromise to prevent recurring on a iterator when advancing, as this
### Changed
- Update EachPromise to prevent recurring on a iterator when advancing, as this
could trigger fatal generator errors.
* Update Promise to allow recursive waiting without unwrapping exceptions.
- Update Promise to allow recursive waiting without unwrapping exceptions.
## 1.0.3 - 2015-10-15
* Update EachPromise to immediately resolve when the underlying promise iterator
### Changed
- Update EachPromise to immediately resolve when the underlying promise iterator
is empty. Previously, such a promise would throw an exception when its `wait`
function was called.
## 1.0.2 - 2015-05-15
* Conditionally require functions.php.
### Changed
- Conditionally require functions.php.
## 1.0.1 - 2015-06-24
* Updating EachPromise to call next on the underlying promise iterator as late
### Changed
- Updating EachPromise to call next on the underlying promise iterator as late
as possible to ensure that generators that generate new requests based on
callbacks are not iterated until after callbacks are invoked.
## 1.0.0 - 2015-05-12
* Initial release
- Initial release

View file

@ -1,4 +1,4 @@
Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Copyright (c) 2015-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -96,7 +96,7 @@ $promise->resolve('reader.');
## Promise forwarding
Promises can be chained one after the other. Each then in the chain is a new
promise. The return value of of a promise is what's forwarded to the next
promise. The return value of a promise is what's forwarded to the next
promise in the chain. Returning a promise in a `then` callback will cause the
subsequent promises in the chain to only be fulfilled when the returned promise
has been fulfilled. The next promise in the chain will be invoked with the
@ -315,8 +315,11 @@ A promise has the following methods:
- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
Creates a new promise that is fulfilled or rejected when the promise is
resolved.
Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
- `otherwise(callable $onRejected) : PromiseInterface`
Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
- `wait($unwrap = true) : mixed`

View file

@ -1,6 +1,5 @@
{
"name": "guzzlehttp/promises",
"type": "library",
"description": "Guzzle promises library",
"keywords": ["promise"],
"license": "MIT",
@ -15,7 +14,7 @@
"php": ">=5.5.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "^4.0"
},
"autoload": {
"psr-4": {
@ -23,9 +22,13 @@
},
"files": ["src/functions_include.php"]
},
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text"
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.4-dev"
}
}
}

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
colors="true">
<testsuites>
<testsuite>
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
<exclude>
<directory suffix="Interface.php">src/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View file

@ -0,0 +1,151 @@
<?php
namespace GuzzleHttp\Promise;
use Exception;
use Generator;
use Throwable;
/**
* Creates a promise that is resolved using a generator that yields values or
* promises (somewhat similar to C#'s async keyword).
*
* When called, the coroutine function will start an instance of the generator
* and returns a promise that is fulfilled with its final yielded value.
*
* Control is returned back to the generator when the yielded promise settles.
* This can lead to less verbose code when doing lots of sequential async calls
* with minimal processing in between.
*
* use GuzzleHttp\Promise;
*
* function createPromise($value) {
* return new Promise\FulfilledPromise($value);
* }
*
* $promise = Promise\coroutine(function () {
* $value = (yield createPromise('a'));
* try {
* $value = (yield createPromise($value . 'b'));
* } catch (\Exception $e) {
* // The promise was rejected.
* }
* yield $value . 'c';
* });
*
* // Outputs "abc"
* $promise->then(function ($v) { echo $v; });
*
* @param callable $generatorFn Generator function to wrap into a promise.
*
* @return Promise
* @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
*/
final class Coroutine implements PromiseInterface
{
/**
* @var PromiseInterface|null
*/
private $currentPromise;
/**
* @var Generator
*/
private $generator;
/**
* @var Promise
*/
private $result;
public function __construct(callable $generatorFn)
{
$this->generator = $generatorFn();
$this->result = new Promise(function () {
while (isset($this->currentPromise)) {
$this->currentPromise->wait();
}
});
$this->nextCoroutine($this->generator->current());
}
public function then(
callable $onFulfilled = null,
callable $onRejected = null
) {
return $this->result->then($onFulfilled, $onRejected);
}
public function otherwise(callable $onRejected)
{
return $this->result->otherwise($onRejected);
}
public function wait($unwrap = true)
{
return $this->result->wait($unwrap);
}
public function getState()
{
return $this->result->getState();
}
public function resolve($value)
{
$this->result->resolve($value);
}
public function reject($reason)
{
$this->result->reject($reason);
}
public function cancel()
{
$this->currentPromise->cancel();
$this->result->cancel();
}
private function nextCoroutine($yielded)
{
$this->currentPromise = promise_for($yielded)
->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
}
/**
* @internal
*/
public function _handleSuccess($value)
{
unset($this->currentPromise);
try {
$next = $this->generator->send($value);
if ($this->generator->valid()) {
$this->nextCoroutine($next);
} else {
$this->result->resolve($value);
}
} catch (Exception $exception) {
$this->result->reject($exception);
} catch (Throwable $throwable) {
$this->result->reject($throwable);
}
}
/**
* @internal
*/
public function _handleFailure($reason)
{
unset($this->currentPromise);
try {
$nextYield = $this->generator->throw(exception_for($reason));
// The throw was caught, so keep iterating on the coroutine
$this->nextCoroutine($nextYield);
} catch (Exception $exception) {
$this->result->reject($exception);
} catch (Throwable $throwable) {
$this->result->reject($throwable);
}
}
}

View file

@ -263,10 +263,17 @@ class Promise implements PromiseInterface
$this->waitList = null;
foreach ($waitList as $result) {
$result->waitIfPending();
while ($result->result instanceof Promise) {
$result = $result->result;
while (true) {
$result->waitIfPending();
if ($result->result instanceof Promise) {
$result = $result->result;
} else {
if ($result->result instanceof PromiseInterface) {
$result->result->wait(false);
}
break;
}
}
}
}

View file

@ -10,7 +10,7 @@ namespace GuzzleHttp\Promise;
*
* GuzzleHttp\Promise\queue()->run();
*/
class TaskQueue
class TaskQueue implements TaskQueueInterface
{
private $enableShutdown = true;
private $queue = [];
@ -30,30 +30,16 @@ class TaskQueue
}
}
/**
* Returns true if the queue is empty.
*
* @return bool
*/
public function isEmpty()
{
return !$this->queue;
}
/**
* Adds a task to the queue that will be executed the next time run is
* called.
*
* @param callable $task
*/
public function add(callable $task)
{
$this->queue[] = $task;
}
/**
* Execute all of the pending task in the queue.
*/
public function run()
{
/** @var callable $task */

View file

@ -0,0 +1,25 @@
<?php
namespace GuzzleHttp\Promise;
interface TaskQueueInterface
{
/**
* Returns true if the queue is empty.
*
* @return bool
*/
public function isEmpty();
/**
* Adds a task to the queue that will be executed the next time run is
* called.
*
* @param callable $task
*/
public function add(callable $task);
/**
* Execute all of the pending task in the queue.
*/
public function run();
}

View file

@ -14,13 +14,17 @@ namespace GuzzleHttp\Promise;
* }
* </code>
*
* @return TaskQueue
* @param TaskQueueInterface $assign Optionally specify a new queue instance.
*
* @return TaskQueueInterface
*/
function queue()
function queue(TaskQueueInterface $assign = null)
{
static $queue;
if (!$queue) {
if ($assign) {
$queue = $assign;
} elseif (!$queue) {
$queue = new TaskQueue();
}
@ -210,7 +214,7 @@ function unwrap($promises)
*
* @param mixed $promises Promises or values.
*
* @return Promise
* @return PromiseInterface
*/
function all($promises)
{
@ -243,7 +247,7 @@ function all($promises)
* @param int $count Total number of promises.
* @param mixed $promises Promises or values.
*
* @return Promise
* @return PromiseInterface
*/
function some($count, $promises)
{
@ -299,7 +303,7 @@ function any($promises)
*
* @param mixed $promises Promises or values.
*
* @return Promise
* @return PromiseInterface
* @see GuzzleHttp\Promise\inspect for the inspection state array format.
*/
function settle($promises)
@ -337,7 +341,7 @@ function settle($promises)
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return Promise
* @return PromiseInterface
*/
function each(
$iterable,
@ -363,7 +367,7 @@ function each(
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return mixed
* @return PromiseInterface
*/
function each_limit(
$iterable,
@ -387,7 +391,7 @@ function each_limit(
* @param int|callable $concurrency
* @param callable $onFulfilled
*
* @return mixed
* @return PromiseInterface
*/
function each_limit_all(
$iterable,
@ -441,60 +445,13 @@ function is_settled(PromiseInterface $promise)
}
/**
* Creates a promise that is resolved using a generator that yields values or
* promises (somewhat similar to C#'s async keyword).
* @see Coroutine
*
* When called, the coroutine function will start an instance of the generator
* and returns a promise that is fulfilled with its final yielded value.
* @param callable $generatorFn
*
* Control is returned back to the generator when the yielded promise settles.
* This can lead to less verbose code when doing lots of sequential async calls
* with minimal processing in between.
*
* use GuzzleHttp\Promise;
*
* function createPromise($value) {
* return new Promise\FulfilledPromise($value);
* }
*
* $promise = Promise\coroutine(function () {
* $value = (yield createPromise('a'));
* try {
* $value = (yield createPromise($value . 'b'));
* } catch (\Exception $e) {
* // The promise was rejected.
* }
* yield $value . 'c';
* });
*
* // Outputs "abc"
* $promise->then(function ($v) { echo $v; });
*
* @param callable $generatorFn Generator function to wrap into a promise.
*
* @return Promise
* @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
* @return PromiseInterface
*/
function coroutine(callable $generatorFn)
{
$generator = $generatorFn();
return __next_coroutine($generator->current(), $generator)->then();
}
/** @internal */
function __next_coroutine($yielded, \Generator $generator)
{
return promise_for($yielded)->then(
function ($value) use ($generator) {
$nextYield = $generator->send($value);
return $generator->valid()
? __next_coroutine($nextYield, $generator)
: $value;
},
function ($reason) use ($generator) {
$nextYield = $generator->throw(exception_for($reason));
// The throw was caught, so keep iterating on the coroutine
return __next_coroutine($nextYield, $generator);
}
);
return new Coroutine($generatorFn);
}

View file

@ -1,11 +0,0 @@
phpunit.xml
composer.phar
composer.lock
composer-test.lock
vendor/
build/artifacts/
artifacts/
docs/_build
docs/*.pyc
.idea
.DS_STORE

View file

@ -1,20 +0,0 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
sudo: false
install:
- travis_retry composer install --no-interaction --prefer-source
script: make test
matrix:
allow_failures:
- php: hhvm
fast_finish: true

View file

@ -1,5 +1,45 @@
# CHANGELOG
## 1.4.2 - 2017-03-20
* Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing
calls to `trigger_error` when deprecated methods are invoked.
## 1.4.1 - 2017-02-27
* Reverted BC break by reintroducing behavior to automagically fix a URI with a
relative path and an authority by adding a leading slash to the path. It's only
deprecated now.
* Added triggering of silenced deprecation warnings.
## 1.4.0 - 2017-02-21
* Fix `Stream::read` when length parameter <= 0.
* `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
* Fix `ServerRequest::getUriFromGlobals` when `Host` header contains port.
* Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form.
* Allow `parse_response` to parse a response without delimiting space and reason.
* Ensure each URI modification results in a valid URI according to PSR-7 discussions.
Invalid modifications will throw an exception instead of returning a wrong URI or
doing some magic.
- `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception
because the path of a URI with an authority must start with a slash "/" or be empty
- `(new Uri())->withScheme('http')` will return `'http://localhost'`
* Fix compatibility of URIs with `file` scheme and empty host.
* Added common URI utility methods based on RFC 3986 (see documentation in the readme):
- `Uri::isDefaultPort`
- `Uri::isAbsolute`
- `Uri::isNetworkPathReference`
- `Uri::isAbsolutePathReference`
- `Uri::isRelativePathReference`
- `Uri::isSameDocumentReference`
- `Uri::composeComponents`
- `UriNormalizer::normalize`
- `UriNormalizer::isEquivalent`
- `UriResolver::relativize`
* Deprecated `Uri::resolve` in favor of `UriResolver::resolve`
* Deprecated `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
## 1.3.1 - 2016-06-25
* Fix `Uri::__toString` for network path references, e.g. `//example.org`.

View file

@ -1,29 +0,0 @@
all: clean test
test:
vendor/bin/phpunit $(TEST)
coverage:
vendor/bin/phpunit --coverage-html=artifacts/coverage $(TEST)
view-coverage:
open artifacts/coverage/index.html
check-tag:
$(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1"))
tag: check-tag
@echo Tagging $(TAG)
chag update $(TAG)
git commit -a -m '$(TAG) release'
chag tag
@echo "Release has been created. Push using 'make release'"
@echo "Changes made in the release commit"
git diff HEAD~1 HEAD
release: check-tag
git push origin master
git push origin $(TAG)
clean:
rm -rf artifacts/*

View file

@ -519,51 +519,221 @@ Determines the mimetype of a file by looking at its extension.
Maps a file extensions to a mimetype.
# Static URI methods
# Additional URI Methods
The `GuzzleHttp\Psr7\Uri` class has several static methods to manipulate URIs.
Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
this library also provides additional functionality when working with URIs as static methods.
## URI Types
## `GuzzleHttp\Psr7\Uri::removeDotSegments`
An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
the base URI. Relative references can be divided into several forms according to
[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2):
`public static function removeDotSegments(string $path): string`
- network-path references, e.g. `//example.com/path`
- absolute-path references, e.g. `/path`
- relative-path references, e.g. `subpath`
Removes dot segments from a path and returns the new path.
The following methods can be used to identify the type of the URI.
See http://tools.ietf.org/html/rfc3986#section-5.2.4
### `GuzzleHttp\Psr7\Uri::isAbsolute`
`public static function isAbsolute(UriInterface $uri): bool`
## `GuzzleHttp\Psr7\Uri::resolve`
Whether the URI is absolute, i.e. it has a scheme.
`public static function resolve(UriInterface $base, $rel): UriInterface`
### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
Resolve a base URI with a relative URI and return a new URI.
`public static function isNetworkPathReference(UriInterface $uri): bool`
See http://tools.ietf.org/html/rfc3986#section-5
Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
termed an network-path reference.
### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
## `GuzzleHttp\Psr7\Uri::withQueryValue`
`public static function isAbsolutePathReference(UriInterface $uri): bool`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
termed an absolute-path reference.
Create a new URI with a specific query string value.
### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
Any existing query string values that exactly match the provided key are
removed and replaced with the given key value pair.
`public static function isRelativePathReference(UriInterface $uri): bool`
Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
termed a relative-path reference.
## `GuzzleHttp\Psr7\Uri::withoutQueryValue`
### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
Create a new URI with a specific query string value removed.
Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
(apart from its fragment) is considered a same-document reference.
Any existing query string values that exactly match the provided key are
removed.
## URI Components
Additional methods to work with URI components.
## `GuzzleHttp\Psr7\Uri::fromParts`
### `GuzzleHttp\Psr7\Uri::isDefaultPort`
`public static function isDefaultPort(UriInterface $uri): bool`
Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
or the standard port. This method can be used independently of the implementation.
### `GuzzleHttp\Psr7\Uri::composeComponents`
`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
Composes a URI reference string from its various components according to
[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
### `GuzzleHttp\Psr7\Uri::fromParts`
`public static function fromParts(array $parts): UriInterface`
Create a `GuzzleHttp\Psr7\Uri` object from a hash of `parse_url` parts.
Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components.
### `GuzzleHttp\Psr7\Uri::withQueryValue`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
Creates a new URI with a specific query string value. Any existing query string values that exactly match the
provided key are removed and replaced with the given key value pair. A value of null will set the query string
key without a value, e.g. "key" instead of "key=value".
### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
provided key are removed.
## Reference Resolution
`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
do when resolving a link in a website based on the current request URI.
### `GuzzleHttp\Psr7\UriResolver::resolve`
`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
Converts the relative URI into a new URI that is resolved against the base URI.
### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
`public static function removeDotSegments(string $path): string`
Removes dot segments from a path and returns the new path according to
[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
### `GuzzleHttp\Psr7\UriResolver::relativize`
`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
```php
(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
```
One use-case is to use the current request URI as base URI and then generate relative links in your documents
to reduce the document size or offer self-contained downloadable document archives.
```php
$base = new Uri('http://example.com/a/b/');
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
```
## Normalization and Comparison
`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
### `GuzzleHttp\Psr7\UriNormalizer::normalize`
`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
of normalizations to apply. The following normalizations are available:
- `UriNormalizer::PRESERVING_NORMALIZATIONS`
Default normalizations which only include the ones that preserve semantics.
- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
Example: `http://example.org/a%c2%b1b``http://example.org/a%C2%B1b`
- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
ALPHA (%41%5A and %61%7A), DIGIT (%30%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
characters by URI normalizers.
Example: `http://example.org/%7Eusern%61me/``http://example.org/~username/`
- `UriNormalizer::CONVERT_EMPTY_PATH`
Converts the empty path to "/" for http and https URIs.
Example: `http://example.org``http://example.org/`
- `UriNormalizer::REMOVE_DEFAULT_HOST`
Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
"localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
RFC 3986.
Example: `file://localhost/myfile``file:///myfile`
- `UriNormalizer::REMOVE_DEFAULT_PORT`
Removes the default port of the given URI scheme from the URI.
Example: `http://example.org:80/``http://example.org/`
- `UriNormalizer::REMOVE_DOT_SEGMENTS`
Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
change the semantics of the URI reference.
Example: `http://example.org/../a/b/../c/./d.html``http://example.org/a/c/d.html`
- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
may change the semantics. Encoded slashes (%2F) are not removed.
Example: `http://example.org//foo///bar.html``http://example.org/foo/bar.html`
- `UriNormalizer::SORT_QUERY_PARAMETERS`
Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
of the URI.
Example: `?lang=en&article=fred``?article=fred&lang=en`
### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
equivalence or difference of relative references does not mean anything.

View file

@ -1,14 +1,18 @@
{
"name": "guzzlehttp/psr7",
"type": "library",
"description": "PSR-7 message implementation",
"keywords": ["message", "stream", "http", "uri"],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": ["request", "response", "message", "stream", "http", "uri", "url"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"require": {

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
colors="true">
<testsuites>
<testsuite>
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
<exclude>
<directory suffix="Interface.php">src/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View file

@ -21,7 +21,7 @@ class LimitStream implements StreamInterface
* @param StreamInterface $stream Stream to wrap
* @param int $limit Total number of bytes to allow to be read
* from the stream. Pass -1 for no limit.
* @param int|null $offset Position to seek to before reading (only
* @param int $offset Position to seek to before reading (only
* works on seekable streams).
*/
public function __construct(

View file

@ -27,7 +27,7 @@ class MultipartStream implements StreamInterface
*/
public function __construct(array $elements = [], $boundary = null)
{
$this->boundary = $boundary ?: uniqid();
$this->boundary = $boundary ?: sha1(uniqid('', true));
$this->stream = $this->createStream($elements);
}
@ -108,7 +108,7 @@ class MultipartStream implements StreamInterface
/**
* @return array
*/
private function createElement($name, $stream, $filename, array $headers)
private function createElement($name, StreamInterface $stream, $filename, array $headers)
{
// Set a default content-disposition header if one was no provided
$disposition = $this->getHeader($headers, 'content-disposition');

View file

@ -19,7 +19,7 @@ class Request implements RequestInterface
/** @var null|string */
private $requestTarget;
/** @var null|UriInterface */
/** @var UriInterface */
private $uri;
/**

View file

@ -2,6 +2,7 @@
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/**
* PSR-7 response implementation.
@ -100,7 +101,7 @@ class Response implements ResponseInterface
$this->setHeaders($headers);
if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
$this->reasonPhrase = self::$phrases[$status];
$this->reasonPhrase = self::$phrases[$this->statusCode];
} else {
$this->reasonPhrase = (string) $reason;
}

View file

@ -188,25 +188,37 @@ class ServerRequest extends Request implements ServerRequestInterface
public static function getUriFromGlobals() {
$uri = new Uri('');
if (isset($_SERVER['HTTPS'])) {
$uri = $uri->withScheme($_SERVER['HTTPS'] == 'on' ? 'https' : 'http');
}
$uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
$hasPort = false;
if (isset($_SERVER['HTTP_HOST'])) {
$uri = $uri->withHost($_SERVER['HTTP_HOST']);
$hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']);
$uri = $uri->withHost($hostHeaderParts[0]);
if (isset($hostHeaderParts[1])) {
$hasPort = true;
$uri = $uri->withPort($hostHeaderParts[1]);
}
} elseif (isset($_SERVER['SERVER_NAME'])) {
$uri = $uri->withHost($_SERVER['SERVER_NAME']);
} elseif (isset($_SERVER['SERVER_ADDR'])) {
$uri = $uri->withHost($_SERVER['SERVER_ADDR']);
}
if (isset($_SERVER['SERVER_PORT'])) {
if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
$uri = $uri->withPort($_SERVER['SERVER_PORT']);
}
$hasQuery = false;
if (isset($_SERVER['REQUEST_URI'])) {
$uri = $uri->withPath(current(explode('?', $_SERVER['REQUEST_URI'])));
$requestUriParts = explode('?', $_SERVER['REQUEST_URI']);
$uri = $uri->withPath($requestUriParts[0]);
if (isset($requestUriParts[1])) {
$hasQuery = true;
$uri = $uri->withQuery($requestUriParts[1]);
}
}
if (isset($_SERVER['QUERY_STRING'])) {
if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
$uri = $uri->withQuery($_SERVER['QUERY_STRING']);
}

View file

@ -207,8 +207,20 @@ class Stream implements StreamInterface
if (!$this->readable) {
throw new \RuntimeException('Cannot read from non-readable stream');
}
if ($length < 0) {
throw new \RuntimeException('Length parameter cannot be negative');
}
return fread($this->stream, $length);
if (0 === $length) {
return '';
}
$string = fread($this->stream, $length);
if (false === $string) {
throw new \RuntimeException('Unable to read from stream');
}
return $string;
}
public function write($string)

View file

@ -12,9 +12,26 @@ use Psr\Http\Message\UriInterface;
*/
class Uri implements UriInterface
{
private static $schemes = [
/**
* Absolute http and https URIs require a host per RFC 7230 Section 2.7
* but in generic URIs the host can be empty. So for http(s) URIs
* we apply this default host when no host is given yet to form a
* valid URI.
*/
const HTTP_DEFAULT_HOST = 'localhost';
private static $defaultPorts = [
'http' => 80,
'https' => 443,
'ftp' => 21,
'gopher' => 70,
'nntp' => 119,
'news' => 119,
'telnet' => 23,
'tn3270' => 23,
'imap' => 143,
'pop' => 110,
'ldap' => 389,
];
private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
@ -47,6 +64,7 @@ class Uri implements UriInterface
*/
public function __construct($uri = '')
{
// weak type check to also accept null until we can add scalar type hints
if ($uri != '') {
$parts = parse_url($uri);
if ($parts === false) {
@ -58,7 +76,7 @@ class Uri implements UriInterface
public function __toString()
{
return self::createUriString(
return self::composeComponents(
$this->scheme,
$this->getAuthority(),
$this->path,
@ -67,57 +85,199 @@ class Uri implements UriInterface
);
}
/**
* Composes a URI reference string from its various components.
*
* Usually this method does not need to be called manually but instead is used indirectly via
* `Psr\Http\Message\UriInterface::__toString`.
*
* PSR-7 UriInterface treats an empty component the same as a missing component as
* getQuery(), getFragment() etc. always return a string. This explains the slight
* difference to RFC 3986 Section 5.3.
*
* Another adjustment is that the authority separator is added even when the authority is missing/empty
* for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
* `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
* `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
* that format).
*
* @param string $scheme
* @param string $authority
* @param string $path
* @param string $query
* @param string $fragment
*
* @return string
*
* @link https://tools.ietf.org/html/rfc3986#section-5.3
*/
public static function composeComponents($scheme, $authority, $path, $query, $fragment)
{
$uri = '';
// weak type checks to also accept null until we can add scalar type hints
if ($scheme != '') {
$uri .= $scheme . ':';
}
if ($authority != ''|| $scheme === 'file') {
$uri .= '//' . $authority;
}
$uri .= $path;
if ($query != '') {
$uri .= '?' . $query;
}
if ($fragment != '') {
$uri .= '#' . $fragment;
}
return $uri;
}
/**
* Whether the URI has the default port of the current scheme.
*
* `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
* independently of the implementation.
*
* @param UriInterface $uri
*
* @return bool
*/
public static function isDefaultPort(UriInterface $uri)
{
return $uri->getPort() === null
|| (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
}
/**
* Whether the URI is absolute, i.e. it has a scheme.
*
* An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
* if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
* to another URI, the base URI. Relative references can be divided into several forms:
* - network-path references, e.g. '//example.com/path'
* - absolute-path references, e.g. '/path'
* - relative-path references, e.g. 'subpath'
*
* @param UriInterface $uri
*
* @return bool
* @see Uri::isNetworkPathReference
* @see Uri::isAbsolutePathReference
* @see Uri::isRelativePathReference
* @link https://tools.ietf.org/html/rfc3986#section-4
*/
public static function isAbsolute(UriInterface $uri)
{
return $uri->getScheme() !== '';
}
/**
* Whether the URI is a network-path reference.
*
* A relative reference that begins with two slash characters is termed an network-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isNetworkPathReference(UriInterface $uri)
{
return $uri->getScheme() === '' && $uri->getAuthority() !== '';
}
/**
* Whether the URI is a absolute-path reference.
*
* A relative reference that begins with a single slash character is termed an absolute-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isAbsolutePathReference(UriInterface $uri)
{
return $uri->getScheme() === ''
&& $uri->getAuthority() === ''
&& isset($uri->getPath()[0])
&& $uri->getPath()[0] === '/';
}
/**
* Whether the URI is a relative-path reference.
*
* A relative reference that does not begin with a slash character is termed a relative-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isRelativePathReference(UriInterface $uri)
{
return $uri->getScheme() === ''
&& $uri->getAuthority() === ''
&& (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
}
/**
* Whether the URI is a same-document reference.
*
* A same-document reference refers to a URI that is, aside from its fragment
* component, identical to the base URI. When no base URI is given, only an empty
* URI reference (apart from its fragment) is considered a same-document reference.
*
* @param UriInterface $uri The URI to check
* @param UriInterface|null $base An optional base URI to compare against
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.4
*/
public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
{
if ($base !== null) {
$uri = UriResolver::resolve($base, $uri);
return ($uri->getScheme() === $base->getScheme())
&& ($uri->getAuthority() === $base->getAuthority())
&& ($uri->getPath() === $base->getPath())
&& ($uri->getQuery() === $base->getQuery());
}
return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
}
/**
* Removes dot segments from a path and returns the new path.
*
* @param string $path
*
* @return string
* @link http://tools.ietf.org/html/rfc3986#section-5.2.4
*
* @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
* @see UriResolver::removeDotSegments
*/
public static function removeDotSegments($path)
{
static $noopPaths = ['' => true, '/' => true, '*' => true];
static $ignoreSegments = ['.' => true, '..' => true];
if (isset($noopPaths[$path])) {
return $path;
}
$results = [];
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment === '..') {
array_pop($results);
} elseif (!isset($ignoreSegments[$segment])) {
$results[] = $segment;
}
}
$newPath = implode('/', $results);
// Add the leading slash if necessary
if (substr($path, 0, 1) === '/' &&
substr($newPath, 0, 1) !== '/'
) {
$newPath = '/' . $newPath;
}
// Add the trailing slash if necessary
if ($newPath !== '/' && isset($ignoreSegments[end($segments)])) {
$newPath .= '/';
}
return $newPath;
return UriResolver::removeDotSegments($path);
}
/**
* Resolve a base URI with a relative URI and return a new URI.
* Converts the relative URI into a new URI that is resolved against the base URI.
*
* @param UriInterface $base Base URI
* @param string|UriInterface $rel Relative URI
*
* @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*
* @deprecated since version 1.4. Use UriResolver::resolve instead.
* @see UriResolver::resolve
*/
public static function resolve(UriInterface $base, $rel)
{
@ -125,55 +285,11 @@ class Uri implements UriInterface
$rel = new self($rel);
}
if ((string) $rel === '') {
// we can simply return the same base URI instance for this same-document reference
return $base;
}
if ($rel->getScheme() != '') {
return $rel->withPath(self::removeDotSegments($rel->getPath()));
}
if ($rel->getAuthority() != '') {
$targetAuthority = $rel->getAuthority();
$targetPath = self::removeDotSegments($rel->getPath());
$targetQuery = $rel->getQuery();
} else {
$targetAuthority = $base->getAuthority();
if ($rel->getPath() === '') {
$targetPath = $base->getPath();
$targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
} else {
if ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
if ($targetAuthority != '' && $base->getPath() === '') {
$targetPath = '/' . $rel->getPath();
} else {
$lastSlashPos = strrpos($base->getPath(), '/');
if ($lastSlashPos === false) {
$targetPath = $rel->getPath();
} else {
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
}
}
}
$targetPath = self::removeDotSegments($targetPath);
$targetQuery = $rel->getQuery();
}
}
return new self(self::createUriString(
$base->getScheme(),
$targetAuthority,
$targetPath,
$targetQuery,
$rel->getFragment()
));
return UriResolver::resolve($base, $rel);
}
/**
* Create a new URI with a specific query string value removed.
* Creates a new URI with a specific query string value removed.
*
* Any existing query string values that exactly match the provided key are
* removed.
@ -186,7 +302,7 @@ class Uri implements UriInterface
public static function withoutQueryValue(UriInterface $uri, $key)
{
$current = $uri->getQuery();
if ($current == '') {
if ($current === '') {
return $uri;
}
@ -199,7 +315,7 @@ class Uri implements UriInterface
}
/**
* Create a new URI with a specific query string value.
* Creates a new URI with a specific query string value.
*
* Any existing query string values that exactly match the provided key are
* removed and replaced with the given key value pair.
@ -217,7 +333,7 @@ class Uri implements UriInterface
{
$current = $uri->getQuery();
if ($current == '') {
if ($current === '') {
$result = [];
} else {
$decodedKey = rawurldecode($key);
@ -241,16 +357,21 @@ class Uri implements UriInterface
}
/**
* Create a URI from a hash of parse_url parts.
* Creates a URI from a hash of `parse_url` components.
*
* @param array $parts
*
* @return self
* @return UriInterface
* @link http://php.net/manual/en/function.parse-url.php
*
* @throws \InvalidArgumentException If the components do not form a valid URI.
*/
public static function fromParts(array $parts)
{
$uri = new self();
$uri->applyParts($parts);
$uri->validateState();
return $uri;
}
@ -261,12 +382,8 @@ class Uri implements UriInterface
public function getAuthority()
{
if ($this->host == '') {
return '';
}
$authority = $this->host;
if ($this->userInfo != '') {
if ($this->userInfo !== '') {
$authority = $this->userInfo . '@' . $authority;
}
@ -317,7 +434,9 @@ class Uri implements UriInterface
$new = clone $this;
$new->scheme = $scheme;
$new->port = $new->filterPort($new->port);
$new->removeDefaultPort();
$new->validateState();
return $new;
}
@ -334,6 +453,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->userInfo = $info;
$new->validateState();
return $new;
}
@ -347,6 +468,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->host = $host;
$new->validateState();
return $new;
}
@ -360,6 +483,9 @@ class Uri implements UriInterface
$new = clone $this;
$new->port = $port;
$new->removeDefaultPort();
$new->validateState();
return $new;
}
@ -373,6 +499,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->path = $path;
$new->validateState();
return $new;
}
@ -386,6 +514,7 @@ class Uri implements UriInterface
$new = clone $this;
$new->query = $query;
return $new;
}
@ -399,6 +528,7 @@ class Uri implements UriInterface
$new = clone $this;
$new->fragment = $fragment;
return $new;
}
@ -431,69 +561,8 @@ class Uri implements UriInterface
if (isset($parts['pass'])) {
$this->userInfo .= ':' . $parts['pass'];
}
}
/**
* Create a URI string from its various parts
*
* @param string $scheme
* @param string $authority
* @param string $path
* @param string $query
* @param string $fragment
* @return string
*/
private static function createUriString($scheme, $authority, $path, $query, $fragment)
{
$uri = '';
if ($scheme != '') {
$uri .= $scheme . ':';
}
if ($authority != '') {
$uri .= '//' . $authority;
}
if ($path != '') {
if ($path[0] !== '/') {
if ($authority != '') {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$path = '/' . $path;
}
} elseif (isset($path[1]) && $path[1] === '/') {
if ($authority == '') {
// If the path is starting with more than one "/" and no authority is present, the
// starting slashes MUST be reduced to one.
$path = '/' . ltrim($path, '/');
}
}
$uri .= $path;
}
if ($query != '') {
$uri .= '?' . $query;
}
if ($fragment != '') {
$uri .= '#' . $fragment;
}
return $uri;
}
/**
* Is a given port non-standard for the current scheme?
*
* @param string $scheme
* @param int $port
*
* @return bool
*/
private static function isNonStandardPort($scheme, $port)
{
return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
$this->removeDefaultPort();
}
/**
@ -548,7 +617,14 @@ class Uri implements UriInterface
);
}
return self::isNonStandardPort($this->scheme, $port) ? $port : null;
return $port;
}
private function removeDefaultPort()
{
if ($this->port !== null && self::isDefaultPort($this)) {
$this->port = null;
}
}
/**
@ -599,4 +675,28 @@ class Uri implements UriInterface
{
return rawurlencode($match[0]);
}
private function validateState()
{
if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
$this->host = self::HTTP_DEFAULT_HOST;
}
if ($this->getAuthority() === '') {
if (0 === strpos($this->path, '//')) {
throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
}
if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
}
} elseif (isset($this->path[0]) && $this->path[0] !== '/') {
@trigger_error(
'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
E_USER_DEPRECATED
);
$this->path = '/'. $this->path;
//throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
}
}
}

View file

@ -0,0 +1,216 @@
<?php
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
/**
* Provides methods to normalize and compare URIs.
*
* @author Tobias Schultze
*
* @link https://tools.ietf.org/html/rfc3986#section-6
*/
final class UriNormalizer
{
/**
* Default normalizations which only include the ones that preserve semantics.
*
* self::CAPITALIZE_PERCENT_ENCODING | self::DECODE_UNRESERVED_CHARACTERS | self::CONVERT_EMPTY_PATH |
* self::REMOVE_DEFAULT_HOST | self::REMOVE_DEFAULT_PORT | self::REMOVE_DOT_SEGMENTS
*/
const PRESERVING_NORMALIZATIONS = 63;
/**
* All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
*
* Example: http://example.org/a%c2%b1b http://example.org/a%C2%B1b
*/
const CAPITALIZE_PERCENT_ENCODING = 1;
/**
* Decodes percent-encoded octets of unreserved characters.
*
* For consistency, percent-encoded octets in the ranges of ALPHA (%41%5A and %61%7A), DIGIT (%30%39),
* hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and,
* when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers.
*
* Example: http://example.org/%7Eusern%61me/ http://example.org/~username/
*/
const DECODE_UNRESERVED_CHARACTERS = 2;
/**
* Converts the empty path to "/" for http and https URIs.
*
* Example: http://example.org http://example.org/
*/
const CONVERT_EMPTY_PATH = 4;
/**
* Removes the default host of the given URI scheme from the URI.
*
* Only the "file" scheme defines the default host "localhost".
* All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile`
* are equivalent according to RFC 3986. The first format is not accepted
* by PHPs stream functions and thus already normalized implicitly to the
* second format in the Uri class. See `GuzzleHttp\Psr7\Uri::composeComponents`.
*
* Example: file://localhost/myfile file:///myfile
*/
const REMOVE_DEFAULT_HOST = 8;
/**
* Removes the default port of the given URI scheme from the URI.
*
* Example: http://example.org:80/ http://example.org/
*/
const REMOVE_DEFAULT_PORT = 16;
/**
* Removes unnecessary dot-segments.
*
* Dot-segments in relative-path references are not removed as it would
* change the semantics of the URI reference.
*
* Example: http://example.org/../a/b/../c/./d.html http://example.org/a/c/d.html
*/
const REMOVE_DOT_SEGMENTS = 32;
/**
* Paths which include two or more adjacent slashes are converted to one.
*
* Webservers usually ignore duplicate slashes and treat those URIs equivalent.
* But in theory those URIs do not need to be equivalent. So this normalization
* may change the semantics. Encoded slashes (%2F) are not removed.
*
* Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html
*/
const REMOVE_DUPLICATE_SLASHES = 64;
/**
* Sort query parameters with their values in alphabetical order.
*
* However, the order of parameters in a URI may be significant (this is not defined by the standard).
* So this normalization is not safe and may change the semantics of the URI.
*
* Example: ?lang=en&article=fred ?article=fred&lang=en
*
* Note: The sorting is neither locale nor Unicode aware (the URI query does not get decoded at all) as the
* purpose is to be able to compare URIs in a reproducible way, not to have the params sorted perfectly.
*/
const SORT_QUERY_PARAMETERS = 128;
/**
* Returns a normalized URI.
*
* The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
* This methods adds additional normalizations that can be configured with the $flags parameter.
*
* PSR-7 UriInterface cannot distinguish between an empty component and a missing component as
* getQuery(), getFragment() etc. always return a string. This means the URIs "/?#" and "/" are
* treated equivalent which is not necessarily true according to RFC 3986. But that difference
* is highly uncommon in reality. So this potential normalization is implied in PSR-7 as well.
*
* @param UriInterface $uri The URI to normalize
* @param int $flags A bitmask of normalizations to apply, see constants
*
* @return UriInterface The normalized URI
* @link https://tools.ietf.org/html/rfc3986#section-6.2
*/
public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS)
{
if ($flags & self::CAPITALIZE_PERCENT_ENCODING) {
$uri = self::capitalizePercentEncoding($uri);
}
if ($flags & self::DECODE_UNRESERVED_CHARACTERS) {
$uri = self::decodeUnreservedCharacters($uri);
}
if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === '' &&
($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
) {
$uri = $uri->withPath('/');
}
if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
$uri = $uri->withHost('');
}
if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
$uri = $uri->withPort(null);
}
if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
$uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
}
if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
$uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
}
if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
$queryKeyValues = explode('&', $uri->getQuery());
sort($queryKeyValues);
$uri = $uri->withQuery(implode('&', $queryKeyValues));
}
return $uri;
}
/**
* Whether two URIs can be considered equivalent.
*
* Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
* accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
* resolved against the same base URI. If this is not the case, determination of equivalence or difference of
* relative references does not mean anything.
*
* @param UriInterface $uri1 An URI to compare
* @param UriInterface $uri2 An URI to compare
* @param int $normalizations A bitmask of normalizations to apply, see constants
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-6.1
*/
public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
{
return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
}
private static function capitalizePercentEncoding(UriInterface $uri)
{
$regex = '/(?:%[A-Fa-f0-9]{2})++/';
$callback = function (array $match) {
return strtoupper($match[0]);
};
return
$uri->withPath(
preg_replace_callback($regex, $callback, $uri->getPath())
)->withQuery(
preg_replace_callback($regex, $callback, $uri->getQuery())
);
}
private static function decodeUnreservedCharacters(UriInterface $uri)
{
$regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
$callback = function (array $match) {
return rawurldecode($match[0]);
};
return
$uri->withPath(
preg_replace_callback($regex, $callback, $uri->getPath())
)->withQuery(
preg_replace_callback($regex, $callback, $uri->getQuery())
);
}
private function __construct()
{
// cannot be instantiated
}
}

View file

@ -0,0 +1,219 @@
<?php
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
/**
* Resolves a URI reference in the context of a base URI and the opposite way.
*
* @author Tobias Schultze
*
* @link https://tools.ietf.org/html/rfc3986#section-5
*/
final class UriResolver
{
/**
* Removes dot segments from a path and returns the new path.
*
* @param string $path
*
* @return string
* @link http://tools.ietf.org/html/rfc3986#section-5.2.4
*/
public static function removeDotSegments($path)
{
if ($path === '' || $path === '/') {
return $path;
}
$results = [];
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment === '..') {
array_pop($results);
} elseif ($segment !== '.') {
$results[] = $segment;
}
}
$newPath = implode('/', $results);
if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {
// Re-add the leading slash if necessary for cases like "/.."
$newPath = '/' . $newPath;
} elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {
// Add the trailing slash if necessary
// If newPath is not empty, then $segment must be set and is the last segment from the foreach
$newPath .= '/';
}
return $newPath;
}
/**
* Converts the relative URI into a new URI that is resolved against the base URI.
*
* @param UriInterface $base Base URI
* @param UriInterface $rel Relative URI
*
* @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*/
public static function resolve(UriInterface $base, UriInterface $rel)
{
if ((string) $rel === '') {
// we can simply return the same base URI instance for this same-document reference
return $base;
}
if ($rel->getScheme() != '') {
return $rel->withPath(self::removeDotSegments($rel->getPath()));
}
if ($rel->getAuthority() != '') {
$targetAuthority = $rel->getAuthority();
$targetPath = self::removeDotSegments($rel->getPath());
$targetQuery = $rel->getQuery();
} else {
$targetAuthority = $base->getAuthority();
if ($rel->getPath() === '') {
$targetPath = $base->getPath();
$targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
} else {
if ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
if ($targetAuthority != '' && $base->getPath() === '') {
$targetPath = '/' . $rel->getPath();
} else {
$lastSlashPos = strrpos($base->getPath(), '/');
if ($lastSlashPos === false) {
$targetPath = $rel->getPath();
} else {
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
}
}
}
$targetPath = self::removeDotSegments($targetPath);
$targetQuery = $rel->getQuery();
}
}
return new Uri(Uri::composeComponents(
$base->getScheme(),
$targetAuthority,
$targetPath,
$targetQuery,
$rel->getFragment()
));
}
/**
* Returns the target URI as a relative reference from the base URI.
*
* This method is the counterpart to resolve():
*
* (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
*
* One use-case is to use the current request URI as base URI and then generate relative links in your documents
* to reduce the document size or offer self-contained downloadable document archives.
*
* $base = new Uri('http://example.com/a/b/');
* echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
* echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
* echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
* echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
*
* This method also accepts a target that is already relative and will try to relativize it further. Only a
* relative-path reference will be returned as-is.
*
* echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
*
* @param UriInterface $base Base URI
* @param UriInterface $target Target URI
*
* @return UriInterface The relative URI reference
*/
public static function relativize(UriInterface $base, UriInterface $target)
{
if ($target->getScheme() !== '' &&
($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
) {
return $target;
}
if (Uri::isRelativePathReference($target)) {
// As the target is already highly relative we return it as-is. It would be possible to resolve
// the target with `$target = self::resolve($base, $target);` and then try make it more relative
// by removing a duplicate query. But let's not do that automatically.
return $target;
}
if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
return $target->withScheme('');
}
// We must remove the path before removing the authority because if the path starts with two slashes, the URI
// would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
// invalid.
$emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
if ($base->getPath() !== $target->getPath()) {
return $emptyPathUri->withPath(self::getRelativePath($base, $target));
}
if ($base->getQuery() === $target->getQuery()) {
// Only the target fragment is left. And it must be returned even if base and target fragment are the same.
return $emptyPathUri->withQuery('');
}
// If the base URI has a query but the target has none, we cannot return an empty path reference as it would
// inherit the base query component when resolving.
if ($target->getQuery() === '') {
$segments = explode('/', $target->getPath());
$lastSegment = end($segments);
return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
}
return $emptyPathUri;
}
private static function getRelativePath(UriInterface $base, UriInterface $target)
{
$sourceSegments = explode('/', $base->getPath());
$targetSegments = explode('/', $target->getPath());
array_pop($sourceSegments);
$targetLastSegment = array_pop($targetSegments);
foreach ($sourceSegments as $i => $segment) {
if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
unset($sourceSegments[$i], $targetSegments[$i]);
} else {
break;
}
}
$targetSegments[] = $targetLastSegment;
$relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
// A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
// This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
// as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
$relativePath = "./$relativePath";
} elseif ('/' === $relativePath[0]) {
if ($base->getAuthority() != '' && $base->getPath() === '') {
// In this case an extra slash is added by resolve() automatically. So we must not add one here.
$relativePath = ".$relativePath";
} else {
$relativePath = "./$relativePath";
}
}
return $relativePath;
}
private function __construct()
{
// cannot be instantiated
}
}

View file

@ -371,25 +371,24 @@ function copy_to_stream(
StreamInterface $dest,
$maxLen = -1
) {
$bufferSize = 8192;
if ($maxLen === -1) {
while (!$source->eof()) {
if (!$dest->write($source->read(1048576))) {
if (!$dest->write($source->read($bufferSize))) {
break;
}
}
return;
}
$bytes = 0;
while (!$source->eof()) {
$buf = $source->read($maxLen - $bytes);
if (!($len = strlen($buf))) {
break;
}
$bytes += $len;
$dest->write($buf);
if ($bytes == $maxLen) {
break;
} else {
$remaining = $maxLen;
while ($remaining > 0 && !$source->eof()) {
$buf = $source->read(min($bufferSize, $remaining));
$len = strlen($buf);
if (!$len) {
break;
}
$remaining -= $len;
$dest->write($buf);
}
}
}
@ -492,7 +491,10 @@ function parse_request($message)
function parse_response($message)
{
$data = _parse_message($message);
if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) {
// According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
// between status-code and reason-phrase is required. But browsers accept
// responses without space and reason as well.
if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
throw new \InvalidArgumentException('Invalid response string');
}
$parts = explode(' ', $data['start-line'], 3);