diff --git a/composer.json b/composer.json index 0fef64b7..6d05da07 100644 --- a/composer.json +++ b/composer.json @@ -17,13 +17,19 @@ } ], + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/JKingweb/picoFeed-1" + } + ], "require": { "php": "^7.1", "ext-intl": "*", "ext-json": "*", "ext-hash": "*", "ext-dom": "*", - "p3k/picofeed": "0.1.*", + "nicolus/picofeed": "dev-fixed", "hosteurope/password-generator": "1.*", "docopt/docopt": "1.*", "jkingweb/druuid": "3.*", diff --git a/composer.lock b/composer.lock index bc0f82a0..f61e035b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "356cebec1e1e689b4249b661f787b7d9", + "content-hash": "9710e0638278222dc8d6207d4141e529", "packages": [ { "name": "docopt/docopt", @@ -52,6 +52,195 @@ ], "time": "2019-12-03T02:48:46+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2019-12-23T11:57:10+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2019-07-01T23:21:34+00:00" + }, { "name": "hosteurope/password-generator", "version": "v1.0.1", @@ -137,6 +326,81 @@ ], "time": "2017-02-09T14:17:01+00:00" }, + { + "name": "kevinrob/guzzle-cache-middleware", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Kevinrob/guzzle-cache-middleware.git", + "reference": "6952064f7747756b0be7b4c234c0fd7535ea4c8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Kevinrob/guzzle-cache-middleware/zipball/6952064f7747756b0be7b4c234c0fd7535ea4c8c", + "reference": "6952064f7747756b0be7b4c234c0fd7535ea4c8c", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "cache/array-adapter": "^0.4", + "doctrine/cache": "^1.0", + "guzzlehttp/guzzle": "^6.0", + "illuminate/cache": "^5.0", + "league/flysystem": "^1.0", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/cache": "^1.0" + }, + "suggest": { + "doctrine/cache": "This library have a lot of ready-to-use cache storage (to be use with Kevinrob\\GuzzleCache\\Storage\\DoctrineCacheStorage)", + "guzzlehttp/guzzle": "For using this library. It was created for Guzzle6. (but you can use it with any PSR-7 HTTP Client)", + "laravel/framework": "To be use with Kevinrob\\GuzzleCache\\Storage\\LaravelCacheStorage", + "league/flysystem": "To be use with Kevinrob\\GuzzleCache\\Storage\\FlysystemStorage", + "psr/cache": "To be use with Kevinrob\\GuzzleCache\\Storage\\Psr6CacheStorage" + }, + "type": "library", + "autoload": { + "psr-4": { + "Kevinrob\\GuzzleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Robatel", + "email": "kevinrob2@gmail.com", + "homepage": "https://github.com/Kevinrob" + } + ], + "description": "A HTTP/1.1 Cache for Guzzle 6. It's a simple Middleware to be added in the HandlerStack. (RFC 7234)", + "homepage": "https://github.com/Kevinrob/guzzle-cache-middleware", + "keywords": [ + "Etag", + "Flysystem", + "Guzzle", + "cache", + "cache-control", + "doctrine", + "expiration", + "guzzle6", + "handler", + "http", + "http 1.1", + "middleware", + "performance", + "php", + "promise", + "psr6", + "psr7", + "rfc7234", + "validation" + ], + "time": "2017-08-17T12:23:43+00:00" + }, { "name": "laminas/laminas-diactoros", "version": "2.2.2", @@ -280,6 +544,56 @@ ], "time": "2019-12-31T17:06:16+00:00" }, + { + "name": "laminas/laminas-xml", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-xml.git", + "reference": "879cc66d1bba6a37705e98074f52a6960c220020" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-xml/zipball/879cc66d1bba6a37705e98074f52a6960c220020", + "reference": "879cc66d1bba6a37705e98074f52a6960c220020", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zendxml": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev", + "dev-develop": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Xml\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Utility library for XML usage, best practices, and security in PHP", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "security", + "xml" + ], + "time": "2019-12-31T18:05:42+00:00" + }, { "name": "laminas/laminas-zendframework-bridge", "version": "1.0.1", @@ -333,17 +647,17 @@ "time": "2020-01-07T22:58:31+00:00" }, { - "name": "p3k/picofeed", - "version": "v0.1.35", + "name": "nicolus/picofeed", + "version": "dev-fixed", "source": { "type": "git", - "url": "https://github.com/aaronpk/picofeed.git", - "reference": "b2a48acb026df91d2e21dfc8b4edbc1fc48f01f1" + "url": "https://github.com/JKingweb/picoFeed-1.git", + "reference": "419bc85afb18a84e43274029cf8e198cc5785425" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aaronpk/picofeed/zipball/b2a48acb026df91d2e21dfc8b4edbc1fc48f01f1", - "reference": "b2a48acb026df91d2e21dfc8b4edbc1fc48f01f1", + "url": "https://api.github.com/repos/JKingweb/picoFeed-1/zipball/419bc85afb18a84e43274029cf8e198cc5785425", + "reference": "419bc85afb18a84e43274029cf8e198cc5785425", "shasum": "" }, "require": { @@ -352,16 +666,22 @@ "ext-libxml": "*", "ext-simplexml": "*", "ext-xml": "*", - "php": ">=5.3.0", - "zendframework/zendxml": "^1.0" + "guzzlehttp/guzzle": "~6.0", + "kevinrob/guzzle-cache-middleware": "^2.1", + "laminas/laminas-xml": "^1.0", + "php": ">=7.1", + "psr/log": "^1.0" }, "require-dev": { + "blastcloud/guzzler": "^1.5", + "monolog/monolog": "^1.23", + "php-coveralls/php-coveralls": "^2.1", "phpdocumentor/reflection-docblock": "2.0.4", - "phpunit/phpunit": "4.8.26", - "symfony/yaml": "2.8.7" + "phpunit/phpunit": "^7.0.0" }, "suggest": { - "ext-curl": "PicoFeed will use cURL if present" + "ext-curl": "PicoFeed will use cURL if present", + "monolog/monolog": "You can set a monolog Logger to get debug information from PicoFeed" }, "bin": [ "picofeed" @@ -372,18 +692,23 @@ "PicoFeed": "lib/" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Frédéric Guillot" + }, + { + "name": "Nicolas Bailly" } ], - "description": "Modern library to handle RSS/Atom feeds", - "homepage": "https://github.com/aaronpk/picoFeed", - "time": "2019-01-17T17:26:41+00:00" + "description": "RSS/Atom parsing library", + "homepage": "https://github.com/nicolus/picoFeed", + "support": { + "source": "https://github.com/JKingweb/picoFeed-1/tree/fixed" + }, + "time": "2020-01-21T00:09:22+00:00" }, { "name": "psr/http-factory", @@ -541,51 +866,91 @@ "time": "2018-10-30T16:46:14+00:00" }, { - "name": "zendframework/zendxml", - "version": "1.2.0", + "name": "psr/log", + "version": "1.1.2", "source": { "type": "git", - "url": "https://github.com/zendframework/ZendXml.git", - "reference": "eceab37a591c9e140772a1470338258857339e00" + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/ZendXml/zipball/eceab37a591c9e140772a1470338258857339e00", - "reference": "eceab37a591c9e140772a1470338258857339e00", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev", - "dev-develop": "1.3.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { - "ZendXml\\": "src/" + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Utility library for XML usage, best practices, and security in PHP", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "ZendFramework", - "security", - "xml", - "zf" + "log", + "psr", + "psr-3" ], - "abandoned": "laminas/laminas-xml", - "time": "2019-01-22T19:42:14+00:00" + "time": "2019-11-01T11:05:21+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" } ], "packages-dev": [ @@ -631,7 +996,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "nicolus/picofeed": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/lib/AbstractException.php b/lib/AbstractException.php index 0165d464..4c0496a6 100644 --- a/lib/AbstractException.php +++ b/lib/AbstractException.php @@ -77,11 +77,12 @@ abstract class AbstractException extends \Exception { "User/ExceptionSession.invalid" => 10431, "Feed/Exception.invalidCertificate" => 10501, "Feed/Exception.invalidUrl" => 10502, - "Feed/Exception.maxRedirect" => 10503, + "Feed/Exception.tooManyRedirects" => 10503, "Feed/Exception.maxSize" => 10504, "Feed/Exception.timeout" => 10505, "Feed/Exception.forbidden" => 10506, "Feed/Exception.unauthorized" => 10507, + "Feed/Exception.transmissionError" => 10508, "Feed/Exception.malformedXml" => 10511, "Feed/Exception.xmlEntity" => 10512, "Feed/Exception.subscriptionNotFound" => 10521, diff --git a/lib/Feed.php b/lib/Feed.php index 87e6016f..71f30da0 100644 --- a/lib/Feed.php +++ b/lib/Feed.php @@ -101,6 +101,8 @@ class Feed { return $client; } catch (PicoFeedException $e) { throw new Feed\Exception($url, $e); + } catch (\GuzzleHttp\Exception\GuzzleException $e) { + throw new Feed\Exception($url, $e); } } @@ -121,6 +123,10 @@ class Feed { // work around a PicoFeed memory leak libxml_use_internal_errors(false); throw new Feed\Exception($this->resource->getUrl(), $e); + } catch (\GuzzleHttp\Exception\GuzzleException $e) { + // work around a PicoFeed memory leak + libxml_use_internal_errors(false); + throw new Feed\Exception($this->resource->getUrl(), $e); } // PicoFeed does not provide valid ids when there is no id element. Its solution diff --git a/lib/Feed/Exception.php b/lib/Feed/Exception.php index 91fe085c..8e7bbab4 100644 --- a/lib/Feed/Exception.php +++ b/lib/Feed/Exception.php @@ -6,13 +6,39 @@ declare(strict_types=1); namespace JKingWeb\Arsse\Feed; +use GuzzleHttp\Exception\GuzzleException; + class Exception extends \JKingWeb\Arsse\AbstractException { public function __construct($url, \Throwable $e) { - $className = get_class($e); - // Convert the exception thrown by PicoFeed to the one to be thrown here. - $msgID = preg_replace('/^PicoFeed\\\(?:Client|Parser|Reader)\\\([A-Za-z]+)Exception$/', '$1', $className); - // If the message ID doesn't change then it's unknown. - $msgID = ($msgID !== $className) ? lcfirst($msgID) : ''; + if ($e instanceof GuzzleException) { + switch ($e->getCode()) { + case 401: + $msgID = "unauthorized"; + break; + case 403: + $msgID = "forbidden"; + break; + case 404: + case 410: + $msgID = "invalidUrl"; + break; + case 508: + $msgID = "tooManyRedirects"; + break; + default: + $c = $e->getCode(); + if ($c >= 400 && $c < 600) { + $msgID = "transmissionError"; + } + } + } + if (!($msgID ?? "")) { + $className = get_class($e); + // Convert the exception thrown by PicoFeed to the one to be thrown here. + $msgID = preg_replace('/^(?:PicoFeed\\\(?:Client|Parser|Reader)|GuzzleHttp\\\Exception)\\\([A-Za-z]+)Exception$/', '$1', $className); + // If the message ID doesn't change then it's unknown. + $msgID = ($msgID !== $className) ? lcfirst($msgID) : ''; + } parent::__construct($msgID, ['url' => $url], $e); } } diff --git a/locale/en.php b/locale/en.php index e095db8a..ff75bc8b 100644 --- a/locale/en.php +++ b/locale/en.php @@ -146,11 +146,12 @@ return [ 'Exception.JKingWeb/Arsse/User/ExceptionSession.invalid' => 'Session with ID {0} does not exist', 'Exception.JKingWeb/Arsse/Feed/Exception.invalidCertificate' => 'Could not download feed "{url}" because its server is serving an invalid SSL certificate', 'Exception.JKingWeb/Arsse/Feed/Exception.invalidUrl' => 'Feed URL "{url}" is invalid', - 'Exception.JKingWeb/Arsse/Feed/Exception.maxRedirect' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections', + 'Exception.JKingWeb/Arsse/Feed/Exception.tooManyRedirects' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections', 'Exception.JKingWeb/Arsse/Feed/Exception.maxSize' => 'Could not download feed "{url}" because its size exceeds the maximum allowed on its server', 'Exception.JKingWeb/Arsse/Feed/Exception.timeout' => 'Could not download feed "{url}" because its server timed out', 'Exception.JKingWeb/Arsse/Feed/Exception.forbidden' => 'Could not download feed "{url}" because you do not have permission to access it', 'Exception.JKingWeb/Arsse/Feed/Exception.unauthorized' => 'Could not download feed "{url}" because you provided insufficient or invalid credentials', + 'Exception.JKingWeb/Arsse/Feed/Exception.transmissionError' => 'Could not download feed "{url}" because of a network error', 'Exception.JKingWeb/Arsse/Feed/Exception.malformedXml' => 'Could not parse feed "{url}" because it is malformed', 'Exception.JKingWeb/Arsse/Feed/Exception.xmlEntity' => 'Refused to parse feed "{url}" because it contains an XXE attack', 'Exception.JKingWeb/Arsse/Feed/Exception.subscriptionNotFound' => 'Unable to find a feed at location "{url}"', diff --git a/tests/cases/Feed/TestFetching.php b/tests/cases/Feed/TestFetching.php index 573c7e96..c83f269c 100644 --- a/tests/cases/Feed/TestFetching.php +++ b/tests/cases/Feed/TestFetching.php @@ -28,7 +28,7 @@ class TestFetching extends \JKingWeb\Arsse\Test\AbstractTest { } public function testHandle400(): void { - $this->assertException("unsupportedFeedFormat", "Feed"); + $this->assertException("transmissionError", "Feed"); new Feed(null, $this->base."Fetching/Error?code=400"); } @@ -48,12 +48,12 @@ class TestFetching extends \JKingWeb\Arsse\Test\AbstractTest { } public function testHandle500(): void { - $this->assertException("unsupportedFeedFormat", "Feed"); + $this->assertException("transmissionError", "Feed"); new Feed(null, $this->base."Fetching/Error?code=500"); } public function testHandleARedirectLoop(): void { - $this->assertException("maxRedirect", "Feed"); + $this->assertException("tooManyRedirects", "Feed"); new Feed(null, $this->base."Fetching/EndlessLoop?i=0"); }