From 7b9adbf52e8b34a4008eed17d3ea821238f64b90 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Mon, 15 Jan 2018 14:43:15 -0500 Subject: [PATCH] Update copyright year --- app/bootstrap.php | 2 +- build/header_comment.txt | 2 +- index.php | 2 +- src/API/APIRequestBuilder.php | 2 +- src/API/AbstractListItem.php | 2 +- src/API/CacheTrait.php | 2 +- src/API/Enum/AnimeWatchingStatus/Kitsu.php | 2 +- src/API/Enum/AnimeWatchingStatus/MAL.php | 2 +- src/API/Enum/AnimeWatchingStatus/Route.php | 2 +- src/API/Enum/AnimeWatchingStatus/Title.php | 2 +- src/API/Enum/MangaReadingStatus/Kitsu.php | 2 +- src/API/Enum/MangaReadingStatus/MAL.php | 2 +- src/API/Enum/MangaReadingStatus/Route.php | 2 +- src/API/Enum/MangaReadingStatus/Title.php | 2 +- src/API/FailedResponseException.php | 2 +- src/API/HummingbirdClient.php | 2347 +++++++++-------- src/API/JsonAPI.php | 2 +- src/API/Kitsu.php | 2 +- src/API/Kitsu/Auth.php | 3 +- src/API/Kitsu/Enum/AnimeAiringStatus.php | 2 +- src/API/Kitsu/KitsuRequestBuilder.php | 2 +- src/API/Kitsu/KitsuTrait.php | 2 +- src/API/Kitsu/ListItem.php | 2 +- src/API/Kitsu/Model.php | 2 +- .../Transformer/AnimeListTransformer.php | 2 +- .../Kitsu/Transformer/AnimeTransformer.php | 2 +- .../Transformer/MangaListTransformer.php | 2 +- .../Kitsu/Transformer/MangaTransformer.php | 2 +- src/API/ListItemInterface.php | 2 +- src/API/MAL.php | 15 +- src/API/MAL/ListItem.php | 2 +- src/API/MAL/MALRequestBuilder.php | 2 +- src/API/MAL/MALTrait.php | 2 +- src/API/MAL/Model.php | 2 +- .../MAL/Transformer/AnimeListTransformer.php | 2 +- .../MAL/Transformer/MangaListTransformer.php | 2 +- src/API/Mapping/AnimeWatchingStatus.php | 2 +- src/API/Mapping/MangaReadingStatus.php | 2 +- src/API/ParallelAPIRequest.php | 2 +- src/API/XML.php | 2 +- src/AnimeClient.php | 2 +- src/Command/BaseCommand.php | 2 +- src/Command/CacheClear.php | 2 +- src/Command/CachePrime.php | 2 +- src/Command/SyncKitsuWithMal.php | 2 +- src/Controller.php | 2 +- src/Controller/Anime.php | 2 +- src/Controller/AnimeCollection.php | 2 +- src/Controller/Character.php | 2 +- src/Controller/Index.php | 2 +- src/Controller/Manga.php | 2 +- src/Controller/MangaCollection.php | 2 +- src/Dispatcher.php | 10 +- src/Helper/Menu.php | 2 +- src/MenuGenerator.php | 2 +- src/Model/API.php | 2 +- src/Model/AbstractModel.php | 2 +- src/Model/Anime.php | 2 +- src/Model/AnimeCollection.php | 2 +- src/Model/Collection.php | 2 +- src/Model/DB.php | 2 +- src/Model/Manga.php | 2 +- src/Model/MangaCollection.php | 2 +- src/RoutingBase.php | 2 +- src/UrlGenerator.php | 2 +- src/Util.php | 2 +- tests/API/APIRequestBuilderTest.php | 2 +- tests/API/CacheTraitTest.php | 2 +- tests/API/JsonAPITest.php | 2 +- .../Transformer/AnimeListTransformerTest.php | 2 +- .../Transformer/AnimeTransformerTest.php | 2 +- .../Transformer/MangaListTransformerTest.php | 2 +- .../Transformer/MangaTransformerTest.php | 2 +- tests/API/KitsuTest.php | 2 +- tests/API/MAL/ListItemTest.php | 2 +- tests/API/MAL/MALTraitTest.php | 2 +- tests/API/MAL/ModelTest.php | 2 +- .../Transformer/AnimeListTransformerTest.php | 2 +- .../Transformer/MangaListTransformerTest.php | 2 +- tests/API/ParallelAPIRequestTest.php | 2 +- tests/API/XMLTest.php | 2 +- tests/AnimeClientTestCase.php | 12 +- tests/Command/BaseCommandTest.php | 2 +- tests/ControllerTest.php | 2 +- tests/DispatcherTest.php | 2 +- tests/Helper/MenuHelperTest.php | 2 +- tests/MenuGeneratorTest.php | 2 +- tests/RequirementsTest.php | 2 +- tests/RoutingBaseTest.php | 2 +- tests/TestSessionHandler.php | 2 +- tests/UrlGeneratorTest.php | 2 +- tests/UtilTest.php | 2 +- 92 files changed, 1296 insertions(+), 1265 deletions(-) diff --git a/app/bootstrap.php b/app/bootstrap.php index 507f3a3b..8b0e1db4 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/build/header_comment.txt b/build/header_comment.txt index b6e10e3d..5ca5be22 100644 --- a/build/header_comment.txt +++ b/build/header_comment.txt @@ -7,7 +7,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/index.php b/index.php index d59043cf..fb774bc7 100644 --- a/index.php +++ b/index.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/APIRequestBuilder.php b/src/API/APIRequestBuilder.php index fa975b0c..5a0fe9b1 100644 --- a/src/API/APIRequestBuilder.php +++ b/src/API/APIRequestBuilder.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/AbstractListItem.php b/src/API/AbstractListItem.php index 4dbc59d8..c4739e8c 100644 --- a/src/API/AbstractListItem.php +++ b/src/API/AbstractListItem.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/CacheTrait.php b/src/API/CacheTrait.php index 58933809..6a00270d 100644 --- a/src/API/CacheTrait.php +++ b/src/API/CacheTrait.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/AnimeWatchingStatus/Kitsu.php b/src/API/Enum/AnimeWatchingStatus/Kitsu.php index b3447c29..db3afcca 100644 --- a/src/API/Enum/AnimeWatchingStatus/Kitsu.php +++ b/src/API/Enum/AnimeWatchingStatus/Kitsu.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/AnimeWatchingStatus/MAL.php b/src/API/Enum/AnimeWatchingStatus/MAL.php index 35209eaf..b9d17c53 100644 --- a/src/API/Enum/AnimeWatchingStatus/MAL.php +++ b/src/API/Enum/AnimeWatchingStatus/MAL.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/AnimeWatchingStatus/Route.php b/src/API/Enum/AnimeWatchingStatus/Route.php index 0c2b4a38..4f834c55 100644 --- a/src/API/Enum/AnimeWatchingStatus/Route.php +++ b/src/API/Enum/AnimeWatchingStatus/Route.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/AnimeWatchingStatus/Title.php b/src/API/Enum/AnimeWatchingStatus/Title.php index 5efdf4fb..ef3241cb 100644 --- a/src/API/Enum/AnimeWatchingStatus/Title.php +++ b/src/API/Enum/AnimeWatchingStatus/Title.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/MangaReadingStatus/Kitsu.php b/src/API/Enum/MangaReadingStatus/Kitsu.php index d8b7ccd0..2c5f3d74 100644 --- a/src/API/Enum/MangaReadingStatus/Kitsu.php +++ b/src/API/Enum/MangaReadingStatus/Kitsu.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/MangaReadingStatus/MAL.php b/src/API/Enum/MangaReadingStatus/MAL.php index 4da8b04a..9fe1f767 100644 --- a/src/API/Enum/MangaReadingStatus/MAL.php +++ b/src/API/Enum/MangaReadingStatus/MAL.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/MangaReadingStatus/Route.php b/src/API/Enum/MangaReadingStatus/Route.php index cc31f426..f33f7ad1 100644 --- a/src/API/Enum/MangaReadingStatus/Route.php +++ b/src/API/Enum/MangaReadingStatus/Route.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/MangaReadingStatus/Title.php b/src/API/Enum/MangaReadingStatus/Title.php index c623c9c4..292da0da 100644 --- a/src/API/Enum/MangaReadingStatus/Title.php +++ b/src/API/Enum/MangaReadingStatus/Title.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/FailedResponseException.php b/src/API/FailedResponseException.php index 79047e84..b64ed202 100644 --- a/src/API/FailedResponseException.php +++ b/src/API/FailedResponseException.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/HummingbirdClient.php b/src/API/HummingbirdClient.php index 01b8b8f1..0c545611 100644 --- a/src/API/HummingbirdClient.php +++ b/src/API/HummingbirdClient.php @@ -1,1155 +1,1192 @@ - true, - self::OP_TRANSFER_TIMEOUT => 15000, - self::OP_MAX_REDIRECTS => 5, - self::OP_AUTO_REFERER => true, - self::OP_DISCARD_BODY => false, - self::OP_DEFAULT_HEADERS => [], - self::OP_MAX_HEADER_BYTES => Parser::DEFAULT_MAX_HEADER_BYTES, - self::OP_MAX_BODY_BYTES => Parser::DEFAULT_MAX_BODY_BYTES, - ]; - - public function __construct( - CookieJar $cookieJar = null, - HttpSocketPool $socketPool = null, - ClientTlsContext $tlsContext = null - ) - { - $this->cookieJar = $cookieJar ?? new NullCookieJar; - $this->tlsContext = $tlsContext ?? new ClientTlsContext; - $this->socketPool = $socketPool ?? new HttpSocketPool; - $this->hasZlib = extension_loaded('zlib'); - } - - /** @inheritdoc */ - public function request($uriOrRequest, array $options = [], CancellationToken $cancellation = null): Promise - { - return call(function () use ($uriOrRequest, $options, $cancellation) { - $cancellation = $cancellation ?? new NullCancellationToken; - - foreach ($options as $option => $value) { - $this->validateOption($option, $value); - } - - /** @var Request $request */ - list($request, $uri) = $this->generateRequestFromUri($uriOrRequest); - $options = $options ? array_merge($this->options, $options) : $this->options; - - foreach ($this->options[self::OP_DEFAULT_HEADERS] as $name => $header) { - if (!$request->hasHeader($name)) { - $request = $request->withHeaders([$name => $header]); - } - } - - /** @var array $headers */ - $headers = yield $request->getBody()->getHeaders(); - foreach ($headers as $name => $header) { - if (!$request->hasHeader($name)) { - $request = $request->withHeaders([$name => $header]); - } - } - - $originalUri = $uri; - $previousResponse = null; - - $maxRedirects = $options[self::OP_MAX_REDIRECTS]; - $requestNr = 1; - - do { - /** @var Request $request */ - $request = yield from $this->normalizeRequestBodyHeaders($request); - $request = $this->normalizeRequestHeaders($request, $uri, $options); - - // Always normalize this as last item, because we need to strip sensitive headers - $request = $this->normalizeTraceRequest($request); - - /** @var Response $response */ - $response = yield $this->doRequest($request, $uri, $options, $previousResponse, $cancellation); - - // Explicit $maxRedirects !== 0 check to not consume redirect bodies if redirect following is disabled - if ($maxRedirects !== 0 && $redirectUri = $this->getRedirectUri($response)) { - // Discard response body of redirect responses - $body = $response->getBody(); - while (null !== yield $body->read()) ; - - /** - * If this is a 302/303 we need to follow the location with a GET if the original request wasn't - * GET. Otherwise we need to send the body again. - * - * We won't resend the body nor any headers on redirects to other hosts for security reasons. - * - * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3 - */ - $method = $request->getMethod(); - $status = $response->getStatus(); - $isSameHost = $redirectUri->getAuthority(false) === $originalUri->getAuthority(false); - - if ($isSameHost) { - $request = $request->withUri($redirectUri); - - if ($status >= 300 && $status <= 303 && $method !== 'GET') { - $request = $request->withMethod('GET'); - $request = $request->withoutHeader('Transfer-Encoding'); - $request = $request->withoutHeader('Content-Length'); - $request = $request->withoutHeader('Content-Type'); - $request = $request->withBody(null); - } - } else { - // We ALWAYS follow with a GET and without any set headers or body for redirects to other hosts. - $optionsWithoutHeaders = $options; - unset($optionsWithoutHeaders[self::OP_DEFAULT_HEADERS]); - - $request = new Request((string)$redirectUri); - $request = $this->normalizeRequestHeaders($request, $redirectUri, $optionsWithoutHeaders); - } - - if ($options[self::OP_AUTO_REFERER]) { - $request = $this->assignRedirectRefererHeader($request, $originalUri, $redirectUri); - } - - $previousResponse = $response; - $originalUri = $redirectUri; - $uri = $redirectUri; - } else { - break; - } - } while (++$requestNr <= $maxRedirects + 1); - - if ($maxRedirects !== 0 && $redirectUri = $this->getRedirectUri($response)) { - throw new TooManyRedirectsException($response); - } - - return $response; - }); - } - - private function validateOption(string $option, $value) - { - switch ($option) { - case self::OP_AUTO_ENCODING: - if (!\is_bool($value)) { - throw new \TypeError("Invalid value for OP_AUTO_ENCODING, bool expected"); - } - - break; - - case self::OP_TRANSFER_TIMEOUT: - if (!\is_int($value) || $value < 0) { - throw new \Error("Invalid value for OP_TRANSFER_TIMEOUT, int >= 0 expected"); - } - - break; - - case self::OP_MAX_REDIRECTS: - if (!\is_int($value) || $value < 0) { - throw new \Error("Invalid value for OP_MAX_REDIRECTS, int >= 0 expected"); - } - - break; - - case self::OP_AUTO_REFERER: - if (!\is_bool($value)) { - throw new \TypeError("Invalid value for OP_AUTO_REFERER, bool expected"); - } - - break; - - case self::OP_DISCARD_BODY: - if (!\is_bool($value)) { - throw new \TypeError("Invalid value for OP_DISCARD_BODY, bool expected"); - } - - break; - - case self::OP_DEFAULT_HEADERS: - // We attempt to set the headers here, because they're automatically validated then. - (new Request("https://example.com/"))->withHeaders($value); - - break; - - case self::OP_MAX_HEADER_BYTES: - if (!\is_int($value) || $value < 0) { - throw new \Error("Invalid value for OP_MAX_HEADER_BYTES, int >= 0 expected"); - } - - break; - - case self::OP_MAX_BODY_BYTES: - if (!\is_int($value) || $value < 0) { - throw new \Error("Invalid value for OP_MAX_BODY_BYTES, int >= 0 expected"); - } - - break; - - default: - throw new \Error( - sprintf("Unknown option: %s", $option) - ); - } - } - - private function generateRequestFromUri($uriOrRequest) - { - if (is_string($uriOrRequest)) { - $uri = $this->buildUriFromString($uriOrRequest); - $request = new Request($uri); - } elseif ($uriOrRequest instanceof Request) { - $uri = $this->buildUriFromString($uriOrRequest->getUri()); - $request = $uriOrRequest; - } else { - throw new HttpException( - 'Request must be a valid HTTP URI or Amp\Artax\Request instance' - ); - } - - return [$request, $uri]; - } - - private function buildUriFromString($str): Uri - { - try { - $uri = new Uri($str); - $scheme = $uri->getScheme(); - - if (($scheme === "http" || $scheme === "https") && $uri->getHost()) { - return $uri; - } - - throw new HttpException("Request must specify a valid HTTP URI"); - } catch (InvalidUriException $e) { - throw new HttpException("Request must specify a valid HTTP URI", 0, $e); - } - } - - private function normalizeRequestBodyHeaders(Request $request): \Generator - { - if ($request->hasHeader("Transfer-Encoding")) { - return $request->withoutHeader("Content-Length"); - } - - if ($request->hasHeader("Content-Length")) { - return $request; - } - - /** @var RequestBody $body */ - $body = $request->getBody(); - $bodyLength = yield $body->getBodyLength(); - - if ($bodyLength === 0) { - $request = $request->withHeader('Content-Length', '0'); - $request = $request->withoutHeader('Transfer-Encoding'); - } else { - if ($bodyLength > 0) { - $request = $request->withHeader("Content-Length", $bodyLength); - $request = $request->withoutHeader("Transfer-Encoding"); - } else { - $request = $request->withHeader("Transfer-Encoding", "chunked"); - } - } - - return $request; - } - - private function normalizeRequestHeaders($request, $uri, $options) - { - $request = $this->normalizeRequestEncodingHeaderForZlib($request, $options); - $request = $this->normalizeRequestHostHeader($request, $uri); - $request = $this->normalizeRequestUserAgent($request); - $request = $this->normalizeRequestAcceptHeader($request); - $request = $this->assignApplicableRequestCookies($request); - - return $request; - } - - private function normalizeRequestEncodingHeaderForZlib(Request $request, array $options): Request - { - $autoEncoding = $options[self::OP_AUTO_ENCODING]; - - if (!$autoEncoding) { - return $request; - } - - if ($this->hasZlib) { - return $request->withHeader('Accept-Encoding', 'gzip, deflate, identity'); - } - - return $request->withoutHeader('Accept-Encoding'); - } - - private function normalizeRequestHostHeader(Request $request, Uri $uri): Request - { - if ($request->hasHeader('Host')) { - return $request; - } - - $authority = $this->generateAuthorityFromUri($uri); - $request = $request->withHeader('Host', $this->normalizeHostHeader($authority)); - - return $request; - } - - private function generateAuthorityFromUri(Uri $uri): string - { - $host = $uri->getHost(); - $port = $uri->getPort(); - - return "{$host}:{$port}"; - } - - private function normalizeHostHeader(string $host): string - { - // Though servers are supposed to be able to handle standard port names on the end of the - // Host header some fail to do this correctly. As a result, we strip the port from the end - // if it's a standard 80 or 443 - if (strpos($host, ':80') === strlen($host) - 3) { - return substr($host, 0, -3); - } elseif (strpos($host, ':443') === strlen($host) - 4) { - return substr($host, 0, -4); - } - - return $host; - } - - private function normalizeRequestUserAgent(Request $request): Request - { - if ($request->hasHeader('User-Agent')) { - return $request; - } - - return $request->withHeader('User-Agent', self::DEFAULT_USER_AGENT); - } - - private function normalizeRequestAcceptHeader(Request $request): Request - { - if ($request->hasHeader('Accept')) { - return $request; - } - - return $request->withHeader('Accept', '*/*'); - } - - private function assignApplicableRequestCookies(Request $request): Request - { - $uri = new Uri($request->getUri()); - - $domain = $uri->getHost(); - $path = $uri->getPath(); - - if (!$applicableCookies = $this->cookieJar->get($domain, $path)) { - // No cookies matched our request; we're finished. - return $request->withoutHeader("Cookie"); - } - - $isRequestSecure = strcasecmp($uri->getScheme(), "https") === 0; - $cookiePairs = []; - - /** @var Cookie $cookie */ - foreach ($applicableCookies as $cookie) { - if (!$cookie->isSecure() || $isRequestSecure) { - $cookiePairs[] = $cookie->getName() . "=" . $cookie->getValue(); - } - } - - if ($cookiePairs) { - return $request->withHeader("Cookie", \implode("; ", $cookiePairs)); - } - - return $request->withoutHeader("Cookie"); - } - - private function normalizeTraceRequest(Request $request): Request - { - $method = $request->getMethod(); - - if ($method !== 'TRACE') { - return $request; - } - - // https://tools.ietf.org/html/rfc7231#section-4.3.8 - /** @var Request $request */ - $request = $request->withBody(null); - - // Remove all body and sensitive headers - $request = $request->withHeaders([ - "Transfer-Encoding" => [], - "Content-Length" => [], - "Authorization" => [], - "Proxy-Authorization" => [], - "Cookie" => [], - ]); - - return $request; - } - - private function doRequest(Request $request, Uri $uri, array $options, Response $previousResponse = null, CancellationToken $cancellation): Promise - { - $deferred = new Deferred; - - $requestCycle = new RequestCycle; - $requestCycle->request = $request; - $requestCycle->uri = $uri; - $requestCycle->options = $options; - $requestCycle->previousResponse = $previousResponse; - $requestCycle->deferred = $deferred; - $requestCycle->bodyDeferred = new Deferred; - $requestCycle->body = new Emitter; - $requestCycle->cancellation = $cancellation; - - $protocolVersions = $request->getProtocolVersions(); - - if (\in_array("1.1", $protocolVersions, true)) { - $requestCycle->protocolVersion = "1.1"; - } elseif (\in_array("1.0", $protocolVersions, true)) { - $requestCycle->protocolVersion = "1.0"; - } else { - return new Failure(new HttpException( - "None of the requested protocol versions are supported: " . \implode(", ", $protocolVersions) - )); - } - - asyncCall(function () use ($requestCycle) { - try { - yield from $this->doWrite($requestCycle); - } catch (\Throwable $e) { - $this->fail($requestCycle, $e); - } - }); - - return $deferred->promise(); - } - - private function doWrite(RequestCycle $requestCycle) - { - $timeout = $requestCycle->options[self::OP_TRANSFER_TIMEOUT]; - $timeoutToken = new NullCancellationToken; - - if ($timeout > 0) { - $transferTimeoutWatcher = Loop::delay($timeout, function () use ($requestCycle, $timeout) { - $this->fail($requestCycle, new TimeoutException( - sprintf('Allowed transfer timeout exceeded: %d ms', $timeout) - )); - }); - - $requestCycle->bodyDeferred->promise()->onResolve(static function () use ($transferTimeoutWatcher) { - Loop::cancel($transferTimeoutWatcher); - }); - - $timeoutToken = new TimeoutCancellationToken($timeout); - } - - $authority = $this->generateAuthorityFromUri($requestCycle->uri); - $socketCheckoutUri = $requestCycle->uri->getScheme() . "://{$authority}"; - $connectTimeoutToken = new CombinedCancellationToken($requestCycle->cancellation, $timeoutToken); - - try { - /** @var ClientSocket $socket */ - $socket = yield $this->socketPool->checkout($socketCheckoutUri, $connectTimeoutToken); - $requestCycle->socket = $socket; - } catch (ResolutionException $dnsException) { - throw new DnsException(\sprintf("Resolving the specified domain failed: '%s'", $requestCycle->uri->getHost()), 0, $dnsException); - } catch (ConnectException $e) { - throw new SocketException(\sprintf("Connection to '%s' failed", $authority), 0, $e); - } catch (CancelledException $e) { - // In case of a user cancellation request, throw the expected exception - $requestCycle->cancellation->throwIfRequested(); - - // Otherwise we ran into a timeout of our TimeoutCancellationToken - throw new SocketException(\sprintf("Connection to '%s' timed out", $authority), 0, $e); - } - - $cancellation = $requestCycle->cancellation->subscribe(function ($error) use ($requestCycle) { - $this->fail($requestCycle, $error); - }); - - try { - if ($requestCycle->uri->getScheme() === 'https') { - $tlsContext = $this->tlsContext - ->withPeerName($requestCycle->uri->getHost()) - ->withPeerCapturing(); - - yield $socket->enableCrypto($tlsContext); - } - - // Collect this here, because it fails in case the remote closes the connection directly. - $connectionInfo = $this->collectConnectionInfo($socket); - - $rawHeaders = $this->generateRawRequestHeaders($requestCycle->request, $requestCycle->protocolVersion); - yield $socket->write($rawHeaders); - - $body = $requestCycle->request->getBody()->createBodyStream(); - $chunking = $requestCycle->request->getHeader("transfer-encoding") === "chunked"; - $remainingBytes = $requestCycle->request->getHeader("content-length"); - - if ($chunking && $requestCycle->protocolVersion === "1.0") { - throw new HttpException("Can't send chunked bodies over HTTP/1.0"); - } - - // We always buffer the last chunk to make sure we don't write $contentLength bytes if the body is too long. - $buffer = ""; - - while (null !== $chunk = yield $body->read()) { - $requestCycle->cancellation->throwIfRequested(); - - if ($chunk === "") { - continue; - } - - if ($chunking) { - $chunk = \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n"; - }/* elseif ($remainingBytes !== null) { - $remainingBytes -= \strlen($chunk); - - if ($remainingBytes < 0) { - throw new HttpException("Body contained more bytes than specified in Content-Length, aborting request"); - } - }*/ - - yield $socket->write($buffer); - $buffer = $chunk; - } - - // Flush last buffered chunk. - yield $socket->write($buffer); - - if ($chunking) { - yield $socket->write("0\r\n\r\n"); - }/* elseif ($remainingBytes !== null && $remainingBytes > 0) { - throw new HttpException("Body contained fewer bytes than specified in Content-Length, aborting request"); - }*/ - - yield from $this->doRead($requestCycle, $socket, $connectionInfo); - } finally { - $requestCycle->cancellation->unsubscribe($cancellation); - } - } - - private function fail(RequestCycle $requestCycle, \Throwable $error) - { - $toFails = []; - $socket = null; - - if ($requestCycle->deferred) { - $toFails[] = $requestCycle->deferred; - $requestCycle->deferred = null; - } - - if ($requestCycle->body) { - $toFails[] = $requestCycle->body; - $requestCycle->body = null; - } - - if ($requestCycle->bodyDeferred) { - $toFails[] = $requestCycle->bodyDeferred; - $requestCycle->bodyDeferred = null; - } - - if ($requestCycle->socket) { - $this->socketPool->clear($requestCycle->socket); - $socket = $requestCycle->socket; - $requestCycle->socket = null; - $socket->close(); - } - - foreach ($toFails as $toFail) { - $toFail->fail($error); - } - } - - private function collectConnectionInfo(ClientSocket $socket): ConnectionInfo - { - $crypto = \stream_get_meta_data($socket->getResource())["crypto"] ?? null; - - return new ConnectionInfo( - $socket->getLocalAddress(), - $socket->getRemoteAddress(), - $crypto ? TlsInfo::fromMetaData($crypto, \stream_context_get_options($socket->getResource())["ssl"]) : null - ); - } - - /** - * @param Request $request - * @param string $protocolVersion - * - * @return string - * - * @TODO Send absolute URIs in the request line when using a proxy server - * Right now this doesn't matter because all proxy requests use a CONNECT - * tunnel but this likely will not always be the case. - */ - private function generateRawRequestHeaders(Request $request, string $protocolVersion): string - { - $uri = $request->getUri(); - $uri = new Uri($uri); - - $requestUri = $uri->getPath() ?: '/'; - - if ($query = $uri->getQuery()) { - $requestUri .= '?' . $query; - } - - $head = $request->getMethod() . ' ' . $requestUri . ' HTTP/' . $protocolVersion . "\r\n"; - - $headers = $request->getHeaders(true); - /*$newHeaders = []; - - foreach($headers as $key => $val) - { - if ($key !== 'Content-Length') - { - $newHeaders[$key] = $val; - } - }*/ - - // Curse you Kitsu, for this stupid work-around because the login API endpoint doesn't allow for a Content-Length header! - //unset($headers['Content-Length']); - - foreach ($headers as $field => $values) { - if (\strcspn($field, "\r\n") !== \strlen($field)) { - throw new HttpException("Blocked header injection attempt for header '{$field}'"); - } - - foreach ($values as $value) { - if (\strcspn($value, "\r\n") !== \strlen($value)) { - throw new HttpException("Blocked header injection attempt for header '{$field}' with value '{$value}'"); - } - - $head .= "{$field}: {$value}\r\n"; - } - } - - $head .= "\r\n"; - - return $head; - } - - private function doRead(RequestCycle $requestCycle, ClientSocket $socket, ConnectionInfo $connectionInfo): \Generator - { - try { - $backpressure = new Success; - $bodyCallback = $requestCycle->options[self::OP_DISCARD_BODY] - ? null - : static function ($data) use ($requestCycle, &$backpressure) { - $backpressure = $requestCycle->body->emit($data); - }; - - $parser = new Parser($bodyCallback); - - $parser->enqueueResponseMethodMatch($requestCycle->request->getMethod()); - $parser->setAllOptions([ - Parser::OP_MAX_HEADER_BYTES => $requestCycle->options[self::OP_MAX_HEADER_BYTES], - Parser::OP_MAX_BODY_BYTES => $requestCycle->options[self::OP_MAX_BODY_BYTES], - ]); - - while (null !== $chunk = yield $socket->read()) { - $requestCycle->cancellation->throwIfRequested(); - - $parseResult = $parser->parse($chunk); - - if (!$parseResult) { - continue; - } - - $parseResult["headers"] = \array_change_key_case($parseResult["headers"], \CASE_LOWER); - - $response = $this->finalizeResponse($requestCycle, $parseResult, $connectionInfo); - $shouldCloseSocketAfterResponse = $this->shouldCloseSocketAfterResponse($response); - $ignoreIncompleteBodyCheck = false; - $responseHeaders = $response->getHeaders(); - - if ($requestCycle->deferred) { - $deferred = $requestCycle->deferred; - $requestCycle->deferred = null; - $deferred->resolve($response); - $response = null; // clear references - $deferred = null; // there's also a reference in the deferred - } else { - return; - } - - // Required, otherwise responses without body hang - if ($parseResult["headersOnly"]) { - // Directly parse again in case we already have the full body but aborted parsing - // to resolve promise with headers. - $chunk = null; - - do { - try { - $parseResult = $parser->parse($chunk); - } catch (ParseException $e) { - $this->fail($requestCycle, $e); - throw $e; - } - - if ($parseResult) { - break; - } - - if (!$backpressure instanceof Success) { - yield $this->withCancellation($backpressure, $requestCycle->cancellation); - } - - if ($requestCycle->bodyTooLarge) { - throw new HttpException("Response body exceeded the specified size limit"); - } - } while (null !== $chunk = yield $socket->read()); - - $parserState = $parser->getState(); - if ($parserState !== Parser::AWAITING_HEADERS) { - // Ignore check if neither content-length nor chunked encoding are given. - $ignoreIncompleteBodyCheck = $parserState === Parser::BODY_IDENTITY_EOF && - !isset($responseHeaders["content-length"]) && - strcasecmp('identity', $responseHeaders['transfer-encoding'][0] ?? ""); - - if (!$ignoreIncompleteBodyCheck) { - throw new SocketException(sprintf( - 'Socket disconnected prior to response completion (Parser state: %s)', - $parserState - )); - } - } - } - - if ($shouldCloseSocketAfterResponse || $ignoreIncompleteBodyCheck) { - $this->socketPool->clear($socket); - $socket->close(); - } else { - $this->socketPool->checkin($socket); - } - - $requestCycle->socket = null; - - // Complete body AFTER socket checkin, so the socket can be reused for a potential redirect - $body = $requestCycle->body; - $requestCycle->body = null; - - $bodyDeferred = $requestCycle->bodyDeferred; - $requestCycle->bodyDeferred = null; - - $body->complete(); - $bodyDeferred->resolve(); - - return; - } - } catch (\Throwable $e) { - $this->fail($requestCycle, $e); - - return; - } - - if ($socket->getResource() !== null) { - $requestCycle->socket = null; - $this->socketPool->clear($socket); - $socket->close(); - } - - // Required, because if the write fails, the read() call immediately resolves. - yield new Delayed(0); - - if ($requestCycle->deferred === null) { - return; - } - - $parserState = $parser->getState(); - - if ($parserState === Parser::AWAITING_HEADERS && $requestCycle->retryCount < 1) { - $requestCycle->retryCount++; - yield from $this->doWrite($requestCycle); - } else { - $this->fail($requestCycle, new SocketException(sprintf( - 'Socket disconnected prior to response completion (Parser state: %s)', - $parserState - ))); - } - } - - private function finalizeResponse(RequestCycle $requestCycle, array $parserResult, ConnectionInfo $connectionInfo) - { - $body = new IteratorStream($requestCycle->body->iterate()); - - if ($encoding = $this->determineCompressionEncoding($parserResult["headers"])) { - $body = new ZlibInputStream($body, $encoding); - } - - // Wrap the input stream so we can discard the body in case it's destructed but hasn't been consumed. - // This allows reusing the connection for further requests. It's important to have __destruct in InputStream and - // not in Message, because an InputStream might be pulled out of Message and used separately. - $body = new class($body, $requestCycle, $this->socketPool) implements InputStream - { - private $body; - private $bodySize = 0; - private $requestCycle; - private $socketPool; - private $successfulEnd = false; - - public function __construct(InputStream $body, RequestCycle $requestCycle, HttpSocketPool $socketPool) - { - $this->body = $body; - $this->requestCycle = $requestCycle; - $this->socketPool = $socketPool; - } - - public function read(): Promise - { - $promise = $this->body->read(); - $promise->onResolve(function ($error, $value) { - if ($value !== null) { - $this->bodySize += \strlen($value); - $maxBytes = $this->requestCycle->options[Client::OP_MAX_BODY_BYTES]; - if ($maxBytes !== 0 && $this->bodySize >= $maxBytes) { - $this->requestCycle->bodyTooLarge = true; - } - } elseif ($error === null) { - $this->successfulEnd = true; - } - }); - - return $promise; - } - - public function __destruct() - { - if (!$this->successfulEnd && $this->requestCycle->socket) { - $this->socketPool->clear($this->requestCycle->socket); - $socket = $this->requestCycle->socket; - $this->requestCycle->socket = null; - $socket->close(); - } - } - }; - - $response = new class($parserResult["protocol"], $parserResult["status"], $parserResult["reason"], $parserResult["headers"], $body, $requestCycle->request, $requestCycle->previousResponse, new MetaInfo($connectionInfo)) implements Response - { - private $protocolVersion; - private $status; - private $reason; - private $request; - private $previousResponse; - private $headers; - private $body; - private $metaInfo; - - public function __construct( - string $protocolVersion, - int $status, - string $reason, - array $headers, - InputStream $body, - Request $request, - Response $previousResponse = null, - MetaInfo $metaInfo - ) - { - $this->protocolVersion = $protocolVersion; - $this->status = $status; - $this->reason = $reason; - $this->headers = $headers; - $this->body = new Message($body); - $this->request = $request; - $this->previousResponse = $previousResponse; - $this->metaInfo = $metaInfo; - } - - public function getProtocolVersion(): string - { - return $this->protocolVersion; - } - - public function getStatus(): int - { - return $this->status; - } - - public function getReason(): string - { - return $this->reason; - } - - public function getRequest(): Request - { - return $this->request; - } - - public function getOriginalRequest(): Request - { - if (empty($this->previousResponse)) { - return $this->request; - } - - return $this->previousResponse->getOriginalRequest(); - } - - public function getPreviousResponse() - { - return $this->previousResponse; - } - - public function hasHeader(string $field): bool - { - return isset($this->headers[\strtolower($field)]); - } - - public function getHeader(string $field) - { - return $this->headers[\strtolower($field)][0] ?? null; - } - - public function getHeaderArray(string $field): array - { - return $this->headers[\strtolower($field)] ?? []; - } - - public function getHeaders(): array - { - return $this->headers; - } - - public function getBody(): Message - { - return $this->body; - } - - public function getMetaInfo(): MetaInfo - { - return $this->metaInfo; - } - }; - - if ($response->hasHeader('Set-Cookie')) { - $requestDomain = $requestCycle->uri->getHost(); - $cookies = $response->getHeaderArray('Set-Cookie'); - - foreach ($cookies as $rawCookieStr) { - $this->storeResponseCookie($requestDomain, $rawCookieStr); - } - } - - return $response; - } - - private function determineCompressionEncoding(array $responseHeaders): int - { - if (!$this->hasZlib) { - return 0; - } - - if (!isset($responseHeaders["content-encoding"])) { - return 0; - } - - $contentEncodingHeader = \trim(\current($responseHeaders["content-encoding"])); - - if (strcasecmp($contentEncodingHeader, 'gzip') === 0) { - return \ZLIB_ENCODING_GZIP; - } - - if (strcasecmp($contentEncodingHeader, 'deflate') === 0) { - return \ZLIB_ENCODING_DEFLATE; - } - - return 0; - } - - private function storeResponseCookie(string $requestDomain, string $rawCookieStr) - { - try { - $cookie = Cookie::fromString($rawCookieStr); - - if (!$cookie->getDomain()) { - $cookie = $cookie->withDomain($requestDomain); - } else { - // https://tools.ietf.org/html/rfc6265#section-4.1.2.3 - $cookieDomain = $cookie->getDomain(); - - // If a domain is set, left dots are ignored and it's always a wildcard - $cookieDomain = \ltrim($cookieDomain, "."); - - if ($cookieDomain !== $requestDomain) { - // ignore cookies on domains that are public suffixes - if (PublicSuffixList::isPublicSuffix($cookieDomain)) { - return; - } - - // cookie origin would not be included when sending the cookie - if (\substr($requestDomain, 0, -\strlen($cookieDomain) - 1) . "." . $cookieDomain !== $requestDomain) { - return; - } - } - - // always add the dot, it's used internally for wildcard matching when an explicit domain is sent - $cookie = $cookie->withDomain("." . $cookieDomain); - } - - $this->cookieJar->store($cookie); - } catch (CookieFormatException $e) { - // Ignore malformed Set-Cookie headers - } - } - - private function shouldCloseSocketAfterResponse(Response $response) - { - $request = $response->getRequest(); - - $requestConnHeader = $request->getHeader('Connection'); - $responseConnHeader = $response->getHeader('Connection'); - - if ($requestConnHeader && !strcasecmp($requestConnHeader, 'close')) { - return true; - } elseif ($responseConnHeader && !strcasecmp($responseConnHeader, 'close')) { - return true; - } elseif ($response->getProtocolVersion() === '1.0' && !$responseConnHeader) { - return true; - } - - return false; - } - - private function withCancellation(Promise $promise, CancellationToken $cancellationToken): Promise - { - $deferred = new Deferred; - $newPromise = $deferred->promise(); - - $promise->onResolve(function ($error, $value) use (&$deferred) { - if ($deferred) { - if ($error) { - $deferred->fail($error); - $deferred = null; - } else { - $deferred->resolve($value); - $deferred = null; - } - } - }); - - $cancellationSubscription = $cancellationToken->subscribe(function ($e) use (&$deferred) { - if ($deferred) { - $deferred->fail($e); - $deferred = null; - } - }); - - $newPromise->onResolve(function () use ($cancellationToken, $cancellationSubscription) { - $cancellationToken->unsubscribe($cancellationSubscription); - }); - - return $newPromise; - } - - private function getRedirectUri(Response $response) - { - if (!$response->hasHeader('Location')) { - return null; - } - - $request = $response->getRequest(); - - $status = $response->getStatus(); - $method = $request->getMethod(); - - if ($status < 300 || $status > 399 || $method === 'HEAD') { - return null; - } - - $requestUri = new Uri($request->getUri()); - $redirectLocation = $response->getHeader('Location'); - - try { - return $requestUri->resolve($redirectLocation); - } catch (InvalidUriException $e) { - return null; - } - } - - /** - * Clients must not add a Referer header when leaving an unencrypted resource and redirecting to an encrypted - * resource. - * - * @param Request $request - * @param string $refererUri - * @param string $newUri - * - * @return Request - * - * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3 - */ - private function assignRedirectRefererHeader(Request $request, string $refererUri, string $newUri): Request - { - $refererIsEncrypted = (\stripos($refererUri, 'https') === 0); - $destinationIsEncrypted = (\stripos($newUri, 'https') === 0); - - if (!$refererIsEncrypted || $destinationIsEncrypted) { - return $request->withHeader('Referer', $refererUri); - } - - return $request->withoutHeader('Referer'); - } - - /** - * Set multiple options at once. - * - * @param array $options An array of the form [OP_CONSTANT => $value] - * - * @throws \Error On unknown option key or invalid value. - */ - public function setOptions(array $options) - { - foreach ($options as $option => $value) { - $this->setOption($option, $value); - } - } - - /** - * Set an option. - * - * @param string $option A Client option constant - * @param mixed $value The option value to assign - * - * @throws \Error On unknown option key or invalid value. - */ - public function setOption(string $option, $value) - { - $this->validateOption($option, $value); - $this->options[$option] = $value; - } -} + + * @copyright 2015 - 2018 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.0 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\API; + +use Amp\{ + CancellationToken, + CancelledException, + Deferred, + Delayed, + Emitter, + Failure, + Loop, + NullCancellationToken, + Promise, + Success, + TimeoutCancellationToken +}; +use Amp\Artax\{ + ConnectionInfo, + Client, + HttpException, + HttpSocketPool, + MetaInfo, + Response, + Request, + TimeoutException, + TlsInfo +}; +use Amp\Artax\Cookie\{ + Cookie, + CookieFormatException, + CookieJar, + NullCookieJar +}; +use Amp\Artax\Internal\{ + CombinedCancellationToken, Parser, PublicSuffixList, RequestCycle +}; +use Amp\ByteStream\{ + InputStream, IteratorStream, Message, ZlibInputStream +}; +use Amp\Dns\ResolutionException; +use Amp\Socket\{ + ClientSocket, ClientTlsContext, ConnectException +}; +use Amp\Uri\{ + InvalidUriException, Uri +}; +use function Amp\{ + asyncCall, call +}; + +/** + * Standard client implementation. + * + * Use the `Client` interface for your type declarations so people can use composition to add layers like caching. + * + * @see Client + */ +final class HummingbirdClient implements Client { + const DEFAULT_USER_AGENT = 'Mozilla/5.0 (compatible; Artax)'; + + private $cookieJar; + private $socketPool; + private $tlsContext; + private $hasZlib; + private $options = [ + self::OP_AUTO_ENCODING => true, + self::OP_TRANSFER_TIMEOUT => 15000, + self::OP_MAX_REDIRECTS => 5, + self::OP_AUTO_REFERER => true, + self::OP_DISCARD_BODY => false, + self::OP_DEFAULT_HEADERS => [], + self::OP_MAX_HEADER_BYTES => Parser::DEFAULT_MAX_HEADER_BYTES, + self::OP_MAX_BODY_BYTES => Parser::DEFAULT_MAX_BODY_BYTES, + ]; + + public function __construct( + CookieJar $cookieJar = null, + HttpSocketPool $socketPool = null, + ClientTlsContext $tlsContext = null + ) + { + $this->cookieJar = $cookieJar ?? new NullCookieJar; + $this->tlsContext = $tlsContext ?? new ClientTlsContext; + $this->socketPool = $socketPool ?? new HttpSocketPool; + $this->hasZlib = extension_loaded('zlib'); + } + + /** @inheritdoc */ + public function request($uriOrRequest, array $options = [], CancellationToken $cancellation = null): Promise + { + return call(function () use ($uriOrRequest, $options, $cancellation) { + $cancellation = $cancellation ?? new NullCancellationToken; + + foreach ($options as $option => $value) { + $this->validateOption($option, $value); + } + + /** @var Request $request */ + list($request, $uri) = $this->generateRequestFromUri($uriOrRequest); + $options = $options ? array_merge($this->options, $options) : $this->options; + + foreach ($this->options[self::OP_DEFAULT_HEADERS] as $name => $header) { + if (!$request->hasHeader($name)) { + $request = $request->withHeaders([$name => $header]); + } + } + + /** @var array $headers */ + $headers = yield $request->getBody()->getHeaders(); + foreach ($headers as $name => $header) { + if (!$request->hasHeader($name)) { + $request = $request->withHeaders([$name => $header]); + } + } + + $originalUri = $uri; + $previousResponse = null; + + $maxRedirects = $options[self::OP_MAX_REDIRECTS]; + $requestNr = 1; + + do { + /** @var Request $request */ + $request = yield from $this->normalizeRequestBodyHeaders($request); + $request = $this->normalizeRequestHeaders($request, $uri, $options); + + // Always normalize this as last item, because we need to strip sensitive headers + $request = $this->normalizeTraceRequest($request); + + /** @var Response $response */ + $response = yield $this->doRequest($request, $uri, $options, $previousResponse, $cancellation); + + // Explicit $maxRedirects !== 0 check to not consume redirect bodies if redirect following is disabled + if ($maxRedirects !== 0 && $redirectUri = $this->getRedirectUri($response)) { + // Discard response body of redirect responses + $body = $response->getBody(); + while (null !== yield $body->read()) ; + + /** + * If this is a 302/303 we need to follow the location with a GET if the original request wasn't + * GET. Otherwise we need to send the body again. + * + * We won't resend the body nor any headers on redirects to other hosts for security reasons. + * + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3 + */ + $method = $request->getMethod(); + $status = $response->getStatus(); + $isSameHost = $redirectUri->getAuthority(false) === $originalUri->getAuthority(false); + + if ($isSameHost) { + $request = $request->withUri($redirectUri); + + if ($status >= 300 && $status <= 303 && $method !== 'GET') { + $request = $request->withMethod('GET'); + $request = $request->withoutHeader('Transfer-Encoding'); + $request = $request->withoutHeader('Content-Length'); + $request = $request->withoutHeader('Content-Type'); + $request = $request->withBody(null); + } + } else { + // We ALWAYS follow with a GET and without any set headers or body for redirects to other hosts. + $optionsWithoutHeaders = $options; + unset($optionsWithoutHeaders[self::OP_DEFAULT_HEADERS]); + + $request = new Request((string)$redirectUri); + $request = $this->normalizeRequestHeaders($request, $redirectUri, $optionsWithoutHeaders); + } + + if ($options[self::OP_AUTO_REFERER]) { + $request = $this->assignRedirectRefererHeader($request, $originalUri, $redirectUri); + } + + $previousResponse = $response; + $originalUri = $redirectUri; + $uri = $redirectUri; + } else { + break; + } + } while (++$requestNr <= $maxRedirects + 1); + + if ($maxRedirects !== 0 && $redirectUri = $this->getRedirectUri($response)) { + throw new TooManyRedirectsException($response); + } + + return $response; + }); + } + + private function validateOption(string $option, $value) + { + switch ($option) { + case self::OP_AUTO_ENCODING: + if (!\is_bool($value)) { + throw new \TypeError("Invalid value for OP_AUTO_ENCODING, bool expected"); + } + + break; + + case self::OP_TRANSFER_TIMEOUT: + if (!\is_int($value) || $value < 0) { + throw new \Error("Invalid value for OP_TRANSFER_TIMEOUT, int >= 0 expected"); + } + + break; + + case self::OP_MAX_REDIRECTS: + if (!\is_int($value) || $value < 0) { + throw new \Error("Invalid value for OP_MAX_REDIRECTS, int >= 0 expected"); + } + + break; + + case self::OP_AUTO_REFERER: + if (!\is_bool($value)) { + throw new \TypeError("Invalid value for OP_AUTO_REFERER, bool expected"); + } + + break; + + case self::OP_DISCARD_BODY: + if (!\is_bool($value)) { + throw new \TypeError("Invalid value for OP_DISCARD_BODY, bool expected"); + } + + break; + + case self::OP_DEFAULT_HEADERS: + // We attempt to set the headers here, because they're automatically validated then. + (new Request("https://example.com/"))->withHeaders($value); + + break; + + case self::OP_MAX_HEADER_BYTES: + if (!\is_int($value) || $value < 0) { + throw new \Error("Invalid value for OP_MAX_HEADER_BYTES, int >= 0 expected"); + } + + break; + + case self::OP_MAX_BODY_BYTES: + if (!\is_int($value) || $value < 0) { + throw new \Error("Invalid value for OP_MAX_BODY_BYTES, int >= 0 expected"); + } + + break; + + default: + throw new \Error( + sprintf("Unknown option: %s", $option) + ); + } + } + + private function generateRequestFromUri($uriOrRequest) + { + if (is_string($uriOrRequest)) { + $uri = $this->buildUriFromString($uriOrRequest); + $request = new Request($uri); + } elseif ($uriOrRequest instanceof Request) { + $uri = $this->buildUriFromString($uriOrRequest->getUri()); + $request = $uriOrRequest; + } else { + throw new HttpException( + 'Request must be a valid HTTP URI or Amp\Artax\Request instance' + ); + } + + return [$request, $uri]; + } + + private function buildUriFromString($str): Uri + { + try { + $uri = new Uri($str); + $scheme = $uri->getScheme(); + + if (($scheme === "http" || $scheme === "https") && $uri->getHost()) { + return $uri; + } + + throw new HttpException("Request must specify a valid HTTP URI"); + } catch (InvalidUriException $e) { + throw new HttpException("Request must specify a valid HTTP URI", 0, $e); + } + } + + private function normalizeRequestBodyHeaders(Request $request): \Generator + { + if ($request->hasHeader("Transfer-Encoding")) { + return $request->withoutHeader("Content-Length"); + } + + if ($request->hasHeader("Content-Length")) { + return $request; + } + + /** @var RequestBody $body */ + $body = $request->getBody(); + $bodyLength = yield $body->getBodyLength(); + + if ($bodyLength === 0) { + $request = $request->withHeader('Content-Length', '0'); + $request = $request->withoutHeader('Transfer-Encoding'); + } else { + if ($bodyLength > 0) { + $request = $request->withHeader("Content-Length", $bodyLength); + $request = $request->withoutHeader("Transfer-Encoding"); + } else { + $request = $request->withHeader("Transfer-Encoding", "chunked"); + } + } + + return $request; + } + + private function normalizeRequestHeaders($request, $uri, $options) + { + $request = $this->normalizeRequestEncodingHeaderForZlib($request, $options); + $request = $this->normalizeRequestHostHeader($request, $uri); + $request = $this->normalizeRequestUserAgent($request); + $request = $this->normalizeRequestAcceptHeader($request); + $request = $this->assignApplicableRequestCookies($request); + + return $request; + } + + private function normalizeRequestEncodingHeaderForZlib(Request $request, array $options): Request + { + $autoEncoding = $options[self::OP_AUTO_ENCODING]; + + if (!$autoEncoding) { + return $request; + } + + if ($this->hasZlib) { + return $request->withHeader('Accept-Encoding', 'gzip, deflate, identity'); + } + + return $request->withoutHeader('Accept-Encoding'); + } + + private function normalizeRequestHostHeader(Request $request, Uri $uri): Request + { + if ($request->hasHeader('Host')) { + return $request; + } + + $authority = $this->generateAuthorityFromUri($uri); + $request = $request->withHeader('Host', $this->normalizeHostHeader($authority)); + + return $request; + } + + private function generateAuthorityFromUri(Uri $uri): string + { + $host = $uri->getHost(); + $port = $uri->getPort(); + + return "{$host}:{$port}"; + } + + private function normalizeHostHeader(string $host): string + { + // Though servers are supposed to be able to handle standard port names on the end of the + // Host header some fail to do this correctly. As a result, we strip the port from the end + // if it's a standard 80 or 443 + if (strpos($host, ':80') === strlen($host) - 3) { + return substr($host, 0, -3); + } elseif (strpos($host, ':443') === strlen($host) - 4) { + return substr($host, 0, -4); + } + + return $host; + } + + private function normalizeRequestUserAgent(Request $request): Request + { + if ($request->hasHeader('User-Agent')) { + return $request; + } + + return $request->withHeader('User-Agent', self::DEFAULT_USER_AGENT); + } + + private function normalizeRequestAcceptHeader(Request $request): Request + { + if ($request->hasHeader('Accept')) { + return $request; + } + + return $request->withHeader('Accept', '*/*'); + } + + private function assignApplicableRequestCookies(Request $request): Request + { + $uri = new Uri($request->getUri()); + + $domain = $uri->getHost(); + $path = $uri->getPath(); + + if (!$applicableCookies = $this->cookieJar->get($domain, $path)) { + // No cookies matched our request; we're finished. + return $request->withoutHeader("Cookie"); + } + + $isRequestSecure = strcasecmp($uri->getScheme(), "https") === 0; + $cookiePairs = []; + + /** @var Cookie $cookie */ + foreach ($applicableCookies as $cookie) { + if (!$cookie->isSecure() || $isRequestSecure) { + $cookiePairs[] = $cookie->getName() . "=" . $cookie->getValue(); + } + } + + if ($cookiePairs) { + return $request->withHeader("Cookie", \implode("; ", $cookiePairs)); + } + + return $request->withoutHeader("Cookie"); + } + + private function normalizeTraceRequest(Request $request): Request + { + $method = $request->getMethod(); + + if ($method !== 'TRACE') { + return $request; + } + + // https://tools.ietf.org/html/rfc7231#section-4.3.8 + /** @var Request $request */ + $request = $request->withBody(null); + + // Remove all body and sensitive headers + $request = $request->withHeaders([ + "Transfer-Encoding" => [], + "Content-Length" => [], + "Authorization" => [], + "Proxy-Authorization" => [], + "Cookie" => [], + ]); + + return $request; + } + + private function doRequest(Request $request, Uri $uri, array $options, Response $previousResponse = null, CancellationToken $cancellation): Promise + { + $deferred = new Deferred; + + $requestCycle = new RequestCycle; + $requestCycle->request = $request; + $requestCycle->uri = $uri; + $requestCycle->options = $options; + $requestCycle->previousResponse = $previousResponse; + $requestCycle->deferred = $deferred; + $requestCycle->bodyDeferred = new Deferred; + $requestCycle->body = new Emitter; + $requestCycle->cancellation = $cancellation; + + $protocolVersions = $request->getProtocolVersions(); + + if (\in_array("1.1", $protocolVersions, true)) { + $requestCycle->protocolVersion = "1.1"; + } elseif (\in_array("1.0", $protocolVersions, true)) { + $requestCycle->protocolVersion = "1.0"; + } else { + return new Failure(new HttpException( + "None of the requested protocol versions are supported: " . \implode(", ", $protocolVersions) + )); + } + + asyncCall(function () use ($requestCycle) { + try { + yield from $this->doWrite($requestCycle); + } catch (\Throwable $e) { + $this->fail($requestCycle, $e); + } + }); + + return $deferred->promise(); + } + + private function doWrite(RequestCycle $requestCycle) + { + $timeout = $requestCycle->options[self::OP_TRANSFER_TIMEOUT]; + $timeoutToken = new NullCancellationToken; + + if ($timeout > 0) { + $transferTimeoutWatcher = Loop::delay($timeout, function () use ($requestCycle, $timeout) { + $this->fail($requestCycle, new TimeoutException( + sprintf('Allowed transfer timeout exceeded: %d ms', $timeout) + )); + }); + + $requestCycle->bodyDeferred->promise()->onResolve(static function () use ($transferTimeoutWatcher) { + Loop::cancel($transferTimeoutWatcher); + }); + + $timeoutToken = new TimeoutCancellationToken($timeout); + } + + $authority = $this->generateAuthorityFromUri($requestCycle->uri); + $socketCheckoutUri = $requestCycle->uri->getScheme() . "://{$authority}"; + $connectTimeoutToken = new CombinedCancellationToken($requestCycle->cancellation, $timeoutToken); + + try { + /** @var ClientSocket $socket */ + $socket = yield $this->socketPool->checkout($socketCheckoutUri, $connectTimeoutToken); + $requestCycle->socket = $socket; + } catch (ResolutionException $dnsException) { + throw new DnsException(\sprintf("Resolving the specified domain failed: '%s'", $requestCycle->uri->getHost()), 0, $dnsException); + } catch (ConnectException $e) { + throw new SocketException(\sprintf("Connection to '%s' failed", $authority), 0, $e); + } catch (CancelledException $e) { + // In case of a user cancellation request, throw the expected exception + $requestCycle->cancellation->throwIfRequested(); + + // Otherwise we ran into a timeout of our TimeoutCancellationToken + throw new SocketException(\sprintf("Connection to '%s' timed out", $authority), 0, $e); + } + + $cancellation = $requestCycle->cancellation->subscribe(function ($error) use ($requestCycle) { + $this->fail($requestCycle, $error); + }); + + try { + if ($requestCycle->uri->getScheme() === 'https') { + $tlsContext = $this->tlsContext + ->withPeerName($requestCycle->uri->getHost()) + ->withPeerCapturing(); + + yield $socket->enableCrypto($tlsContext); + } + + // Collect this here, because it fails in case the remote closes the connection directly. + $connectionInfo = $this->collectConnectionInfo($socket); + + $rawHeaders = $this->generateRawRequestHeaders($requestCycle->request, $requestCycle->protocolVersion); + yield $socket->write($rawHeaders); + + $body = $requestCycle->request->getBody()->createBodyStream(); + $chunking = $requestCycle->request->getHeader("transfer-encoding") === "chunked"; + $remainingBytes = $requestCycle->request->getHeader("content-length"); + + if ($chunking && $requestCycle->protocolVersion === "1.0") { + throw new HttpException("Can't send chunked bodies over HTTP/1.0"); + } + + // We always buffer the last chunk to make sure we don't write $contentLength bytes if the body is too long. + $buffer = ""; + + while (null !== $chunk = yield $body->read()) { + $requestCycle->cancellation->throwIfRequested(); + + if ($chunk === "") { + continue; + } + + if ($chunking) { + $chunk = \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n"; + }/* elseif ($remainingBytes !== null) { + $remainingBytes -= \strlen($chunk); + + if ($remainingBytes < 0) { + throw new HttpException("Body contained more bytes than specified in Content-Length, aborting request"); + } + }*/ + + yield $socket->write($buffer); + $buffer = $chunk; + } + + // Flush last buffered chunk. + yield $socket->write($buffer); + + if ($chunking) { + yield $socket->write("0\r\n\r\n"); + }/* elseif ($remainingBytes !== null && $remainingBytes > 0) { + throw new HttpException("Body contained fewer bytes than specified in Content-Length, aborting request"); + }*/ + + yield from $this->doRead($requestCycle, $socket, $connectionInfo); + } finally { + $requestCycle->cancellation->unsubscribe($cancellation); + } + } + + private function fail(RequestCycle $requestCycle, \Throwable $error) + { + $toFails = []; + $socket = null; + + if ($requestCycle->deferred) { + $toFails[] = $requestCycle->deferred; + $requestCycle->deferred = null; + } + + if ($requestCycle->body) { + $toFails[] = $requestCycle->body; + $requestCycle->body = null; + } + + if ($requestCycle->bodyDeferred) { + $toFails[] = $requestCycle->bodyDeferred; + $requestCycle->bodyDeferred = null; + } + + if ($requestCycle->socket) { + $this->socketPool->clear($requestCycle->socket); + $socket = $requestCycle->socket; + $requestCycle->socket = null; + $socket->close(); + } + + foreach ($toFails as $toFail) { + $toFail->fail($error); + } + } + + private function collectConnectionInfo(ClientSocket $socket): ConnectionInfo + { + $crypto = \stream_get_meta_data($socket->getResource())["crypto"] ?? null; + + return new ConnectionInfo( + $socket->getLocalAddress(), + $socket->getRemoteAddress(), + $crypto ? TlsInfo::fromMetaData($crypto, \stream_context_get_options($socket->getResource())["ssl"]) : null + ); + } + + /** + * @param Request $request + * @param string $protocolVersion + * + * @return string + * + * @TODO Send absolute URIs in the request line when using a proxy server + * Right now this doesn't matter because all proxy requests use a CONNECT + * tunnel but this likely will not always be the case. + */ + private function generateRawRequestHeaders(Request $request, string $protocolVersion): string + { + $uri = $request->getUri(); + $uri = new Uri($uri); + + $requestUri = $uri->getPath() ?: '/'; + + if ($query = $uri->getQuery()) { + $requestUri .= '?' . $query; + } + + $head = $request->getMethod() . ' ' . $requestUri . ' HTTP/' . $protocolVersion . "\r\n"; + + $headers = $request->getHeaders(true); + /*$newHeaders = []; + + foreach($headers as $key => $val) + { + if ($key !== 'Content-Length') + { + $newHeaders[$key] = $val; + } + }*/ + + // Curse you Kitsu, for this stupid work-around because the login API endpoint doesn't allow for a Content-Length header! + //unset($headers['Content-Length']); + + foreach ($headers as $field => $values) { + if (\strcspn($field, "\r\n") !== \strlen($field)) { + throw new HttpException("Blocked header injection attempt for header '{$field}'"); + } + + foreach ($values as $value) { + if (\strcspn($value, "\r\n") !== \strlen($value)) { + throw new HttpException("Blocked header injection attempt for header '{$field}' with value '{$value}'"); + } + + $head .= "{$field}: {$value}\r\n"; + } + } + + $head .= "\r\n"; + + return $head; + } + + private function doRead(RequestCycle $requestCycle, ClientSocket $socket, ConnectionInfo $connectionInfo): \Generator + { + try { + $backpressure = new Success; + $bodyCallback = $requestCycle->options[self::OP_DISCARD_BODY] + ? null + : static function ($data) use ($requestCycle, &$backpressure) { + $backpressure = $requestCycle->body->emit($data); + }; + + $parser = new Parser($bodyCallback); + + $parser->enqueueResponseMethodMatch($requestCycle->request->getMethod()); + $parser->setAllOptions([ + Parser::OP_MAX_HEADER_BYTES => $requestCycle->options[self::OP_MAX_HEADER_BYTES], + Parser::OP_MAX_BODY_BYTES => $requestCycle->options[self::OP_MAX_BODY_BYTES], + ]); + + while (null !== $chunk = yield $socket->read()) { + $requestCycle->cancellation->throwIfRequested(); + + $parseResult = $parser->parse($chunk); + + if (!$parseResult) { + continue; + } + + $parseResult["headers"] = \array_change_key_case($parseResult["headers"], \CASE_LOWER); + + $response = $this->finalizeResponse($requestCycle, $parseResult, $connectionInfo); + $shouldCloseSocketAfterResponse = $this->shouldCloseSocketAfterResponse($response); + $ignoreIncompleteBodyCheck = false; + $responseHeaders = $response->getHeaders(); + + if ($requestCycle->deferred) { + $deferred = $requestCycle->deferred; + $requestCycle->deferred = null; + $deferred->resolve($response); + $response = null; // clear references + $deferred = null; // there's also a reference in the deferred + } else { + return; + } + + // Required, otherwise responses without body hang + if ($parseResult["headersOnly"]) { + // Directly parse again in case we already have the full body but aborted parsing + // to resolve promise with headers. + $chunk = null; + + do { + try { + $parseResult = $parser->parse($chunk); + } catch (ParseException $e) { + $this->fail($requestCycle, $e); + throw $e; + } + + if ($parseResult) { + break; + } + + if (!$backpressure instanceof Success) { + yield $this->withCancellation($backpressure, $requestCycle->cancellation); + } + + if ($requestCycle->bodyTooLarge) { + throw new HttpException("Response body exceeded the specified size limit"); + } + } while (null !== $chunk = yield $socket->read()); + + $parserState = $parser->getState(); + if ($parserState !== Parser::AWAITING_HEADERS) { + // Ignore check if neither content-length nor chunked encoding are given. + $ignoreIncompleteBodyCheck = $parserState === Parser::BODY_IDENTITY_EOF && + !isset($responseHeaders["content-length"]) && + strcasecmp('identity', $responseHeaders['transfer-encoding'][0] ?? ""); + + if (!$ignoreIncompleteBodyCheck) { + throw new SocketException(sprintf( + 'Socket disconnected prior to response completion (Parser state: %s)', + $parserState + )); + } + } + } + + if ($shouldCloseSocketAfterResponse || $ignoreIncompleteBodyCheck) { + $this->socketPool->clear($socket); + $socket->close(); + } else { + $this->socketPool->checkin($socket); + } + + $requestCycle->socket = null; + + // Complete body AFTER socket checkin, so the socket can be reused for a potential redirect + $body = $requestCycle->body; + $requestCycle->body = null; + + $bodyDeferred = $requestCycle->bodyDeferred; + $requestCycle->bodyDeferred = null; + + $body->complete(); + $bodyDeferred->resolve(); + + return; + } + } catch (\Throwable $e) { + $this->fail($requestCycle, $e); + + return; + } + + if ($socket->getResource() !== null) { + $requestCycle->socket = null; + $this->socketPool->clear($socket); + $socket->close(); + } + + // Required, because if the write fails, the read() call immediately resolves. + yield new Delayed(0); + + if ($requestCycle->deferred === null) { + return; + } + + $parserState = $parser->getState(); + + if ($parserState === Parser::AWAITING_HEADERS && $requestCycle->retryCount < 1) { + $requestCycle->retryCount++; + yield from $this->doWrite($requestCycle); + } else { + $this->fail($requestCycle, new SocketException(sprintf( + 'Socket disconnected prior to response completion (Parser state: %s)', + $parserState + ))); + } + } + + private function finalizeResponse(RequestCycle $requestCycle, array $parserResult, ConnectionInfo $connectionInfo) + { + $body = new IteratorStream($requestCycle->body->iterate()); + + if ($encoding = $this->determineCompressionEncoding($parserResult["headers"])) { + $body = new ZlibInputStream($body, $encoding); + } + + // Wrap the input stream so we can discard the body in case it's destructed but hasn't been consumed. + // This allows reusing the connection for further requests. It's important to have __destruct in InputStream and + // not in Message, because an InputStream might be pulled out of Message and used separately. + $body = new class($body, $requestCycle, $this->socketPool) implements InputStream + { + private $body; + private $bodySize = 0; + private $requestCycle; + private $socketPool; + private $successfulEnd = false; + + public function __construct(InputStream $body, RequestCycle $requestCycle, HttpSocketPool $socketPool) + { + $this->body = $body; + $this->requestCycle = $requestCycle; + $this->socketPool = $socketPool; + } + + public function read(): Promise + { + $promise = $this->body->read(); + $promise->onResolve(function ($error, $value) { + if ($value !== null) { + $this->bodySize += \strlen($value); + $maxBytes = $this->requestCycle->options[Client::OP_MAX_BODY_BYTES]; + if ($maxBytes !== 0 && $this->bodySize >= $maxBytes) { + $this->requestCycle->bodyTooLarge = true; + } + } elseif ($error === null) { + $this->successfulEnd = true; + } + }); + + return $promise; + } + + public function __destruct() + { + if (!$this->successfulEnd && $this->requestCycle->socket) { + $this->socketPool->clear($this->requestCycle->socket); + $socket = $this->requestCycle->socket; + $this->requestCycle->socket = null; + $socket->close(); + } + } + }; + + $response = new class($parserResult["protocol"], $parserResult["status"], $parserResult["reason"], $parserResult["headers"], $body, $requestCycle->request, $requestCycle->previousResponse, new MetaInfo($connectionInfo)) implements Response + { + private $protocolVersion; + private $status; + private $reason; + private $request; + private $previousResponse; + private $headers; + private $body; + private $metaInfo; + + public function __construct( + string $protocolVersion, + int $status, + string $reason, + array $headers, + InputStream $body, + Request $request, + Response $previousResponse = null, + MetaInfo $metaInfo + ) + { + $this->protocolVersion = $protocolVersion; + $this->status = $status; + $this->reason = $reason; + $this->headers = $headers; + $this->body = new Message($body); + $this->request = $request; + $this->previousResponse = $previousResponse; + $this->metaInfo = $metaInfo; + } + + public function getProtocolVersion(): string + { + return $this->protocolVersion; + } + + public function getStatus(): int + { + return $this->status; + } + + public function getReason(): string + { + return $this->reason; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getOriginalRequest(): Request + { + if (empty($this->previousResponse)) { + return $this->request; + } + + return $this->previousResponse->getOriginalRequest(); + } + + public function getPreviousResponse() + { + return $this->previousResponse; + } + + public function hasHeader(string $field): bool + { + return isset($this->headers[\strtolower($field)]); + } + + public function getHeader(string $field) + { + return $this->headers[\strtolower($field)][0] ?? null; + } + + public function getHeaderArray(string $field): array + { + return $this->headers[\strtolower($field)] ?? []; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function getBody(): Message + { + return $this->body; + } + + public function getMetaInfo(): MetaInfo + { + return $this->metaInfo; + } + }; + + if ($response->hasHeader('Set-Cookie')) { + $requestDomain = $requestCycle->uri->getHost(); + $cookies = $response->getHeaderArray('Set-Cookie'); + + foreach ($cookies as $rawCookieStr) { + $this->storeResponseCookie($requestDomain, $rawCookieStr); + } + } + + return $response; + } + + private function determineCompressionEncoding(array $responseHeaders): int + { + if (!$this->hasZlib) { + return 0; + } + + if (!isset($responseHeaders["content-encoding"])) { + return 0; + } + + $contentEncodingHeader = \trim(\current($responseHeaders["content-encoding"])); + + if (strcasecmp($contentEncodingHeader, 'gzip') === 0) { + return \ZLIB_ENCODING_GZIP; + } + + if (strcasecmp($contentEncodingHeader, 'deflate') === 0) { + return \ZLIB_ENCODING_DEFLATE; + } + + return 0; + } + + private function storeResponseCookie(string $requestDomain, string $rawCookieStr) + { + try { + $cookie = Cookie::fromString($rawCookieStr); + + if (!$cookie->getDomain()) { + $cookie = $cookie->withDomain($requestDomain); + } else { + // https://tools.ietf.org/html/rfc6265#section-4.1.2.3 + $cookieDomain = $cookie->getDomain(); + + // If a domain is set, left dots are ignored and it's always a wildcard + $cookieDomain = \ltrim($cookieDomain, "."); + + if ($cookieDomain !== $requestDomain) { + // ignore cookies on domains that are public suffixes + if (PublicSuffixList::isPublicSuffix($cookieDomain)) { + return; + } + + // cookie origin would not be included when sending the cookie + if (\substr($requestDomain, 0, -\strlen($cookieDomain) - 1) . "." . $cookieDomain !== $requestDomain) { + return; + } + } + + // always add the dot, it's used internally for wildcard matching when an explicit domain is sent + $cookie = $cookie->withDomain("." . $cookieDomain); + } + + $this->cookieJar->store($cookie); + } catch (CookieFormatException $e) { + // Ignore malformed Set-Cookie headers + } + } + + private function shouldCloseSocketAfterResponse(Response $response) + { + $request = $response->getRequest(); + + $requestConnHeader = $request->getHeader('Connection'); + $responseConnHeader = $response->getHeader('Connection'); + + if ($requestConnHeader && !strcasecmp($requestConnHeader, 'close')) { + return true; + } elseif ($responseConnHeader && !strcasecmp($responseConnHeader, 'close')) { + return true; + } elseif ($response->getProtocolVersion() === '1.0' && !$responseConnHeader) { + return true; + } + + return false; + } + + private function withCancellation(Promise $promise, CancellationToken $cancellationToken): Promise + { + $deferred = new Deferred; + $newPromise = $deferred->promise(); + + $promise->onResolve(function ($error, $value) use (&$deferred) { + if ($deferred) { + if ($error) { + $deferred->fail($error); + $deferred = null; + } else { + $deferred->resolve($value); + $deferred = null; + } + } + }); + + $cancellationSubscription = $cancellationToken->subscribe(function ($e) use (&$deferred) { + if ($deferred) { + $deferred->fail($e); + $deferred = null; + } + }); + + $newPromise->onResolve(function () use ($cancellationToken, $cancellationSubscription) { + $cancellationToken->unsubscribe($cancellationSubscription); + }); + + return $newPromise; + } + + private function getRedirectUri(Response $response) + { + if (!$response->hasHeader('Location')) { + return null; + } + + $request = $response->getRequest(); + + $status = $response->getStatus(); + $method = $request->getMethod(); + + if ($status < 300 || $status > 399 || $method === 'HEAD') { + return null; + } + + $requestUri = new Uri($request->getUri()); + $redirectLocation = $response->getHeader('Location'); + + try { + return $requestUri->resolve($redirectLocation); + } catch (InvalidUriException $e) { + return null; + } + } + + /** + * Clients must not add a Referer header when leaving an unencrypted resource and redirecting to an encrypted + * resource. + * + * @param Request $request + * @param string $refererUri + * @param string $newUri + * + * @return Request + * + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3 + */ + private function assignRedirectRefererHeader(Request $request, string $refererUri, string $newUri): Request + { + $refererIsEncrypted = (\stripos($refererUri, 'https') === 0); + $destinationIsEncrypted = (\stripos($newUri, 'https') === 0); + + if (!$refererIsEncrypted || $destinationIsEncrypted) { + return $request->withHeader('Referer', $refererUri); + } + + return $request->withoutHeader('Referer'); + } + + /** + * Set multiple options at once. + * + * @param array $options An array of the form [OP_CONSTANT => $value] + * + * @throws \Error On unknown option key or invalid value. + */ + public function setOptions(array $options) + { + foreach ($options as $option => $value) { + $this->setOption($option, $value); + } + } + + /** + * Set an option. + * + * @param string $option A Client option constant + * @param mixed $value The option value to assign + * + * @throws \Error On unknown option key or invalid value. + */ + public function setOption(string $option, $value) + { + $this->validateOption($option, $value); + $this->options[$option] = $value; + } +} diff --git a/src/API/JsonAPI.php b/src/API/JsonAPI.php index 0bd1fe75..638d7ba1 100644 --- a/src/API/JsonAPI.php +++ b/src/API/JsonAPI.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu.php b/src/API/Kitsu.php index bbebf196..9443fbe6 100644 --- a/src/API/Kitsu.php +++ b/src/API/Kitsu.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Auth.php b/src/API/Kitsu/Auth.php index 7ff53ad3..39a52362 100644 --- a/src/API/Kitsu/Auth.php +++ b/src/API/Kitsu/Auth.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -18,7 +18,6 @@ namespace Aviat\AnimeClient\API\Kitsu; use const Aviat\AnimeClient\SESSION_SEGMENT; -use Aviat\AnimeClient\AnimeClient; use Aviat\AnimeClient\API\{ CacheTrait, Kitsu as K diff --git a/src/API/Kitsu/Enum/AnimeAiringStatus.php b/src/API/Kitsu/Enum/AnimeAiringStatus.php index b658d7d2..93577a5d 100644 --- a/src/API/Kitsu/Enum/AnimeAiringStatus.php +++ b/src/API/Kitsu/Enum/AnimeAiringStatus.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/KitsuRequestBuilder.php b/src/API/Kitsu/KitsuRequestBuilder.php index 46e7bf97..e36ee6f3 100644 --- a/src/API/Kitsu/KitsuRequestBuilder.php +++ b/src/API/Kitsu/KitsuRequestBuilder.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/KitsuTrait.php b/src/API/Kitsu/KitsuTrait.php index 215c9bcc..3c0bf48c 100644 --- a/src/API/Kitsu/KitsuTrait.php +++ b/src/API/Kitsu/KitsuTrait.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/ListItem.php b/src/API/Kitsu/ListItem.php index 71bef41a..2f01be8a 100644 --- a/src/API/Kitsu/ListItem.php +++ b/src/API/Kitsu/ListItem.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Model.php b/src/API/Kitsu/Model.php index f25e9be2..149ca9bd 100644 --- a/src/API/Kitsu/Model.php +++ b/src/API/Kitsu/Model.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Transformer/AnimeListTransformer.php b/src/API/Kitsu/Transformer/AnimeListTransformer.php index 5cd72d6e..539f01b4 100644 --- a/src/API/Kitsu/Transformer/AnimeListTransformer.php +++ b/src/API/Kitsu/Transformer/AnimeListTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Transformer/AnimeTransformer.php b/src/API/Kitsu/Transformer/AnimeTransformer.php index f50ac656..0b850f83 100644 --- a/src/API/Kitsu/Transformer/AnimeTransformer.php +++ b/src/API/Kitsu/Transformer/AnimeTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Transformer/MangaListTransformer.php b/src/API/Kitsu/Transformer/MangaListTransformer.php index 54106f63..0e143632 100644 --- a/src/API/Kitsu/Transformer/MangaListTransformer.php +++ b/src/API/Kitsu/Transformer/MangaListTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Transformer/MangaTransformer.php b/src/API/Kitsu/Transformer/MangaTransformer.php index 2b466c1b..53465603 100644 --- a/src/API/Kitsu/Transformer/MangaTransformer.php +++ b/src/API/Kitsu/Transformer/MangaTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/ListItemInterface.php b/src/API/ListItemInterface.php index 1ed294e2..7f4f209b 100644 --- a/src/API/ListItemInterface.php +++ b/src/API/ListItemInterface.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL.php b/src/API/MAL.php index e477ab24..12343a28 100644 --- a/src/API/MAL.php +++ b/src/API/MAL.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,11 +16,14 @@ namespace Aviat\AnimeClient\API; -use Aviat\AnimeClient\API\Kitsu\Enum\{ - AnimeWatchingStatus as KAWS, - MangaReadingStatus as KMRS +use Aviat\AnimeClient\API\Enum\{ + AnimeWatchingStatus\Kitsu as KAWS, + MangaReadingStatus\Kitsu as KMRS +}; +use Aviat\AnimeClient\API\Enum\{ + AnimeWatchingStatus\MAL as AnimeWatchingStatus, + MangaReadingStatus\MAL as MangaReadingStatus }; -use Aviat\AnimeClient\API\MAL\Enum\{AnimeWatchingStatus, MangaReadingStatus}; /** * Constants and mappings for the My Anime List API @@ -36,7 +39,7 @@ class MAL { KAWS::DROPPED => AnimeWatchingStatus::DROPPED, KAWS::PLAN_TO_WATCH => AnimeWatchingStatus::PLAN_TO_WATCH ]; - + const MAL_KITSU_WATCHING_STATUS_MAP = [ 1 => KAWS::WATCHING, 2 => KAWS::COMPLETED, diff --git a/src/API/MAL/ListItem.php b/src/API/MAL/ListItem.php index 27de85b3..0c81844a 100644 --- a/src/API/MAL/ListItem.php +++ b/src/API/MAL/ListItem.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/MALRequestBuilder.php b/src/API/MAL/MALRequestBuilder.php index 4b92fcba..cc8899a5 100644 --- a/src/API/MAL/MALRequestBuilder.php +++ b/src/API/MAL/MALRequestBuilder.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/MALTrait.php b/src/API/MAL/MALTrait.php index b82ffa2c..c10775bd 100644 --- a/src/API/MAL/MALTrait.php +++ b/src/API/MAL/MALTrait.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/Model.php b/src/API/MAL/Model.php index 35c849ef..006813e8 100644 --- a/src/API/MAL/Model.php +++ b/src/API/MAL/Model.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/Transformer/AnimeListTransformer.php b/src/API/MAL/Transformer/AnimeListTransformer.php index 2e74dceb..25d18c2c 100644 --- a/src/API/MAL/Transformer/AnimeListTransformer.php +++ b/src/API/MAL/Transformer/AnimeListTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/Transformer/MangaListTransformer.php b/src/API/MAL/Transformer/MangaListTransformer.php index 04896ec7..2c04db77 100644 --- a/src/API/MAL/Transformer/MangaListTransformer.php +++ b/src/API/MAL/Transformer/MangaListTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Mapping/AnimeWatchingStatus.php b/src/API/Mapping/AnimeWatchingStatus.php index 0017775c..8bc37712 100644 --- a/src/API/Mapping/AnimeWatchingStatus.php +++ b/src/API/Mapping/AnimeWatchingStatus.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Mapping/MangaReadingStatus.php b/src/API/Mapping/MangaReadingStatus.php index c509a0bb..77d76609 100644 --- a/src/API/Mapping/MangaReadingStatus.php +++ b/src/API/Mapping/MangaReadingStatus.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/ParallelAPIRequest.php b/src/API/ParallelAPIRequest.php index b7c1dc95..42a3a0b4 100644 --- a/src/API/ParallelAPIRequest.php +++ b/src/API/ParallelAPIRequest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/XML.php b/src/API/XML.php index ff9fe997..f84d512c 100644 --- a/src/API/XML.php +++ b/src/API/XML.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/AnimeClient.php b/src/AnimeClient.php index 226de822..f1e7bfcd 100644 --- a/src/AnimeClient.php +++ b/src/AnimeClient.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index 5327ffb5..d1ae0e6c 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Command/CacheClear.php b/src/Command/CacheClear.php index 6029e72c..77754da2 100644 --- a/src/Command/CacheClear.php +++ b/src/Command/CacheClear.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Command/CachePrime.php b/src/Command/CachePrime.php index 3f06c229..660f43f8 100644 --- a/src/Command/CachePrime.php +++ b/src/Command/CachePrime.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Command/SyncKitsuWithMal.php b/src/Command/SyncKitsuWithMal.php index 966aef52..a4142133 100644 --- a/src/Command/SyncKitsuWithMal.php +++ b/src/Command/SyncKitsuWithMal.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller.php b/src/Controller.php index 24324a71..b3f09b65 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller/Anime.php b/src/Controller/Anime.php index 90cbac2c..845cc0d7 100644 --- a/src/Controller/Anime.php +++ b/src/Controller/Anime.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller/AnimeCollection.php b/src/Controller/AnimeCollection.php index 0bb096a8..e83ce631 100644 --- a/src/Controller/AnimeCollection.php +++ b/src/Controller/AnimeCollection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller/Character.php b/src/Controller/Character.php index 25977ade..316db69a 100644 --- a/src/Controller/Character.php +++ b/src/Controller/Character.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller/Index.php b/src/Controller/Index.php index 439522c5..358c6d60 100644 --- a/src/Controller/Index.php +++ b/src/Controller/Index.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller/Manga.php b/src/Controller/Manga.php index 84c4a55e..0d54718a 100644 --- a/src/Controller/Manga.php +++ b/src/Controller/Manga.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller/MangaCollection.php b/src/Controller/MangaCollection.php index b5bfcc94..f4c59dae 100644 --- a/src/Controller/MangaCollection.php +++ b/src/Controller/MangaCollection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 92e31d9a..75609ed1 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,14 +16,6 @@ namespace Aviat\AnimeClient; -use const Aviat\AnimeClient\{ - DEFAULT_CONTROLLER, - DEFAULT_CONTROLLER_NAMESPACE, - ERROR_MESSAGE_METHOD, - NOT_FOUND_METHOD, - SRC_DIR -}; - use function Aviat\Ion\_dir; use Aviat\AnimeClient\API\FailedResponseException; diff --git a/src/Helper/Menu.php b/src/Helper/Menu.php index b726538b..17a5e380 100644 --- a/src/Helper/Menu.php +++ b/src/Helper/Menu.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/MenuGenerator.php b/src/MenuGenerator.php index 89199ddd..845207c5 100644 --- a/src/MenuGenerator.php +++ b/src/MenuGenerator.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/API.php b/src/Model/API.php index e13e0ee4..f08e0a9f 100644 --- a/src/Model/API.php +++ b/src/Model/API.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/AbstractModel.php b/src/Model/AbstractModel.php index 8a2e92d7..8df1220e 100644 --- a/src/Model/AbstractModel.php +++ b/src/Model/AbstractModel.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/Anime.php b/src/Model/Anime.php index 43c18d35..e56522c5 100644 --- a/src/Model/Anime.php +++ b/src/Model/Anime.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/AnimeCollection.php b/src/Model/AnimeCollection.php index cf2ce5fb..7fac6835 100644 --- a/src/Model/AnimeCollection.php +++ b/src/Model/AnimeCollection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/Collection.php b/src/Model/Collection.php index 50003973..9d859f80 100644 --- a/src/Model/Collection.php +++ b/src/Model/Collection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/DB.php b/src/Model/DB.php index f047c030..c4776485 100644 --- a/src/Model/DB.php +++ b/src/Model/DB.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/Manga.php b/src/Model/Manga.php index dbd5bc89..0ca1a732 100644 --- a/src/Model/Manga.php +++ b/src/Model/Manga.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/MangaCollection.php b/src/Model/MangaCollection.php index 37ae42e6..c403cbc9 100644 --- a/src/Model/MangaCollection.php +++ b/src/Model/MangaCollection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/RoutingBase.php b/src/RoutingBase.php index eaf247ed..d4269cc3 100644 --- a/src/RoutingBase.php +++ b/src/RoutingBase.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/UrlGenerator.php b/src/UrlGenerator.php index b26399b7..39862ca6 100644 --- a/src/UrlGenerator.php +++ b/src/UrlGenerator.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Util.php b/src/Util.php index 1dca7425..dd87758f 100644 --- a/src/Util.php +++ b/src/Util.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/APIRequestBuilderTest.php b/tests/API/APIRequestBuilderTest.php index 1f0020dc..0dffe0dc 100644 --- a/tests/API/APIRequestBuilderTest.php +++ b/tests/API/APIRequestBuilderTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/CacheTraitTest.php b/tests/API/CacheTraitTest.php index 6817448d..75ed7add 100644 --- a/tests/API/CacheTraitTest.php +++ b/tests/API/CacheTraitTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/JsonAPITest.php b/tests/API/JsonAPITest.php index 22e65696..e72dd09f 100644 --- a/tests/API/JsonAPITest.php +++ b/tests/API/JsonAPITest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php b/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php index e8a20e63..2f062339 100644 --- a/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php +++ b/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/Kitsu/Transformer/AnimeTransformerTest.php b/tests/API/Kitsu/Transformer/AnimeTransformerTest.php index 04b74fda..fc418842 100644 --- a/tests/API/Kitsu/Transformer/AnimeTransformerTest.php +++ b/tests/API/Kitsu/Transformer/AnimeTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/Kitsu/Transformer/MangaListTransformerTest.php b/tests/API/Kitsu/Transformer/MangaListTransformerTest.php index dd1cc8ca..b68db9d7 100644 --- a/tests/API/Kitsu/Transformer/MangaListTransformerTest.php +++ b/tests/API/Kitsu/Transformer/MangaListTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/Kitsu/Transformer/MangaTransformerTest.php b/tests/API/Kitsu/Transformer/MangaTransformerTest.php index 0c31fcc8..6185d6e4 100644 --- a/tests/API/Kitsu/Transformer/MangaTransformerTest.php +++ b/tests/API/Kitsu/Transformer/MangaTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/KitsuTest.php b/tests/API/KitsuTest.php index ebbca7da..b78e439d 100644 --- a/tests/API/KitsuTest.php +++ b/tests/API/KitsuTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/ListItemTest.php b/tests/API/MAL/ListItemTest.php index 510010fb..6586ecf6 100644 --- a/tests/API/MAL/ListItemTest.php +++ b/tests/API/MAL/ListItemTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/MALTraitTest.php b/tests/API/MAL/MALTraitTest.php index 5c239eb9..132cca59 100644 --- a/tests/API/MAL/MALTraitTest.php +++ b/tests/API/MAL/MALTraitTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/ModelTest.php b/tests/API/MAL/ModelTest.php index b626bb07..0d430b5a 100644 --- a/tests/API/MAL/ModelTest.php +++ b/tests/API/MAL/ModelTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/Transformer/AnimeListTransformerTest.php b/tests/API/MAL/Transformer/AnimeListTransformerTest.php index bc5007ea..5b03dc2a 100644 --- a/tests/API/MAL/Transformer/AnimeListTransformerTest.php +++ b/tests/API/MAL/Transformer/AnimeListTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/Transformer/MangaListTransformerTest.php b/tests/API/MAL/Transformer/MangaListTransformerTest.php index 6a6a6196..35327fca 100644 --- a/tests/API/MAL/Transformer/MangaListTransformerTest.php +++ b/tests/API/MAL/Transformer/MangaListTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/ParallelAPIRequestTest.php b/tests/API/ParallelAPIRequestTest.php index c3555e52..56943e75 100644 --- a/tests/API/ParallelAPIRequestTest.php +++ b/tests/API/ParallelAPIRequestTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/XMLTest.php b/tests/API/XMLTest.php index c51b6cda..db17f53b 100644 --- a/tests/API/XMLTest.php +++ b/tests/API/XMLTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/AnimeClientTestCase.php b/tests/AnimeClientTestCase.php index 4418e787..bbe323aa 100644 --- a/tests/AnimeClientTestCase.php +++ b/tests/AnimeClientTestCase.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -29,9 +29,9 @@ use Zend\Diactoros\{ ServerRequestFactory }; -define('ROOT_DIR', __DIR__ . '/../'); -define('TEST_DATA_DIR', __DIR__ . '/test_data'); -define('TEST_VIEW_DIR', __DIR__ . '/test_views'); +\define('ROOT_DIR', __DIR__ . '/../'); +\define('TEST_DATA_DIR', __DIR__ . '/test_data'); +\define('TEST_VIEW_DIR', __DIR__ . '/test_views'); /** * Base class for TestCases @@ -141,8 +141,8 @@ class AnimeClientTestCase extends TestCase { '_FILES' => $_FILES ]; - $request = call_user_func_array( - ['Zend\Diactoros\ServerRequestFactory', 'fromGlobals'], + $request = \call_user_func_array( + [ServerRequestFactory::class, 'fromGlobals'], array_merge($default, $supers) ); $this->container->setInstance('request', $request); diff --git a/tests/Command/BaseCommandTest.php b/tests/Command/BaseCommandTest.php index 22252a62..c019035b 100644 --- a/tests/Command/BaseCommandTest.php +++ b/tests/Command/BaseCommandTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index 8333cc62..12a0f5fb 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index c64ec0f6..09d968ff 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/Helper/MenuHelperTest.php b/tests/Helper/MenuHelperTest.php index cf0471cf..352aa607 100644 --- a/tests/Helper/MenuHelperTest.php +++ b/tests/Helper/MenuHelperTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/MenuGeneratorTest.php b/tests/MenuGeneratorTest.php index d6c2a806..9d4ec482 100644 --- a/tests/MenuGeneratorTest.php +++ b/tests/MenuGeneratorTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/RequirementsTest.php b/tests/RequirementsTest.php index ec5c65cd..45416d4a 100644 --- a/tests/RequirementsTest.php +++ b/tests/RequirementsTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/RoutingBaseTest.php b/tests/RoutingBaseTest.php index 6e3e5893..9b6934fe 100644 --- a/tests/RoutingBaseTest.php +++ b/tests/RoutingBaseTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/TestSessionHandler.php b/tests/TestSessionHandler.php index 1f4c3262..ee7d235a 100644 --- a/tests/TestSessionHandler.php +++ b/tests/TestSessionHandler.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/UrlGeneratorTest.php b/tests/UrlGeneratorTest.php index 48d91cfd..a7fedf21 100644 --- a/tests/UrlGeneratorTest.php +++ b/tests/UrlGeneratorTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/UtilTest.php b/tests/UtilTest.php index d5a9470a..226cdd13 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient