Fix problems with nicolus/picofeed
This involved multiple fixes to Picofeed itself, not all of which have been merged upstream yet
This commit is contained in:
parent
e583ffea67
commit
49d003082d
11 changed files with 74 additions and 27 deletions
8
composer.lock
generated
8
composer.lock
generated
|
@ -652,12 +652,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/JKingweb/picoFeed-1.git",
|
||||
"reference": "419bc85afb18a84e43274029cf8e198cc5785425"
|
||||
"reference": "009250e98ff0975335176f74e5fea95104058d09"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/JKingweb/picoFeed-1/zipball/419bc85afb18a84e43274029cf8e198cc5785425",
|
||||
"reference": "419bc85afb18a84e43274029cf8e198cc5785425",
|
||||
"url": "https://api.github.com/repos/JKingweb/picoFeed-1/zipball/009250e98ff0975335176f74e5fea95104058d09",
|
||||
"reference": "009250e98ff0975335176f74e5fea95104058d09",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -708,7 +708,7 @@
|
|||
"support": {
|
||||
"source": "https://github.com/JKingweb/picoFeed-1/tree/fixed"
|
||||
},
|
||||
"time": "2020-01-21T00:09:22+00:00"
|
||||
"time": "2020-01-23T22:03:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
|
|
|
@ -77,7 +77,7 @@ abstract class AbstractException extends \Exception {
|
|||
"User/ExceptionSession.invalid" => 10431,
|
||||
"Feed/Exception.invalidCertificate" => 10501,
|
||||
"Feed/Exception.invalidUrl" => 10502,
|
||||
"Feed/Exception.tooManyRedirects" => 10503,
|
||||
"Feed/Exception.maxRedirect" => 10503,
|
||||
"Feed/Exception.maxSize" => 10504,
|
||||
"Feed/Exception.timeout" => 10505,
|
||||
"Feed/Exception.forbidden" => 10506,
|
||||
|
|
|
@ -50,7 +50,7 @@ class Feed {
|
|||
$this->resource = self::download($url, $lastModified, $etag, $username, $password);
|
||||
// format the HTTP Last-Modified date returned
|
||||
$lastMod = $this->resource->getLastModified();
|
||||
if (strlen($lastMod)) {
|
||||
if (strlen($lastMod ?? "")) {
|
||||
$this->lastModified = Date::normalize($lastMod, "http");
|
||||
}
|
||||
$this->modified = $this->resource->isModified();
|
||||
|
|
|
@ -6,11 +6,14 @@
|
|||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Feed;
|
||||
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\TooManyRedirectsException;
|
||||
use PicoFeed\PicoFeedException;
|
||||
|
||||
class Exception extends \JKingWeb\Arsse\AbstractException {
|
||||
public function __construct($url, \Throwable $e) {
|
||||
if ($e instanceof GuzzleException) {
|
||||
if ($e instanceof BadResponseException) {
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
$msgID = "unauthorized";
|
||||
|
@ -31,13 +34,29 @@ class Exception extends \JKingWeb\Arsse\AbstractException {
|
|||
$msgID = "transmissionError";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!($msgID ?? "")) {
|
||||
} elseif ($e instanceof TooManyRedirectsException) {
|
||||
$msgID = "maxRedirect";
|
||||
} elseif ($e instanceof GuzzleException) {
|
||||
$m = $e->getMessage();
|
||||
if (preg_match("/^Error creating resource:/", $m)) {
|
||||
// PHP stream error; the class of error is ambiguous
|
||||
$msgID = "transmissionError"; // @codeCoverageIgnore
|
||||
} elseif (preg_match("/^cURL error 35:/", $m)) {
|
||||
$msgID = "invalidCertificate";
|
||||
} elseif (preg_match("/^cURL error 28:/", $m)) {
|
||||
$msgID = "timeout";
|
||||
} else {
|
||||
var_export($m);
|
||||
exit;
|
||||
}
|
||||
} elseif ($e instanceof PicoFeedException) {
|
||||
$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);
|
||||
$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) : '';
|
||||
} else {
|
||||
$msgID = get_class($e);
|
||||
}
|
||||
parent::__construct($msgID, ['url' => $url], $e);
|
||||
}
|
||||
|
|
|
@ -146,7 +146,7 @@ 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.tooManyRedirects' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections',
|
||||
'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.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',
|
||||
|
|
|
@ -198,24 +198,26 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
$this->assertSame("http://example.com/1", $f->newItems[0]->url);
|
||||
}
|
||||
|
||||
public function testHandleCacheHeadersOn304(): void {
|
||||
// upon 304, the client should re-use the caching header values it supplied the server
|
||||
$t = time();
|
||||
/** @dataProvider provide304ResponseURLs */
|
||||
public function testHandleCacheHeadersOn304(string $url): void {
|
||||
// upon 304, the client should re-use the caching header values it supplied to the server
|
||||
$t = Date::transform("2010-01-01T00:00:00Z", "unix");
|
||||
$e = "78567a";
|
||||
$f = new Feed(null, $this->base."Caching/304Random", Date::transform($t, "http"), $e);
|
||||
$this->assertTime($t, $f->lastModified);
|
||||
$this->assertSame($e, $f->resource->getETag());
|
||||
$f = new Feed(null, $this->base."Caching/304ETagOnly", Date::transform($t, "http"), $e);
|
||||
$this->assertTime($t, $f->lastModified);
|
||||
$this->assertSame($e, $f->resource->getETag());
|
||||
$f = new Feed(null, $this->base."Caching/304LastModOnly", Date::transform($t, "http"), $e);
|
||||
$this->assertTime($t, $f->lastModified);
|
||||
$this->assertSame($e, $f->resource->getETag());
|
||||
$f = new Feed(null, $this->base."Caching/304None", Date::transform($t, "http"), $e);
|
||||
$f = new Feed(null, $this->base.$url."?t=$t&e=$e", Date::transform($t, "http"), $e);
|
||||
$this->assertTime($t, $f->lastModified);
|
||||
$this->assertSame($e, $f->resource->getETag());
|
||||
}
|
||||
|
||||
public function provide304ResponseURLs() {
|
||||
return [
|
||||
'Control' => ["Caching/304Conditional"],
|
||||
'Random last-mod and ETag' => ["Caching/304Random"],
|
||||
'ETag only' => ["Caching/304ETagOnly"],
|
||||
'Last-mod only' => ["Caching/304LastModOnly"],
|
||||
'Neither last-mod nor ETag' => ["Caching/304None"],
|
||||
];
|
||||
}
|
||||
|
||||
public function testHandleCacheHeadersOn200(): void {
|
||||
// these tests should trust the server-returned time, even in cases of obviously incorrect results
|
||||
$t = time() - 2000;
|
||||
|
|
|
@ -53,11 +53,12 @@ class TestFetching extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
}
|
||||
|
||||
public function testHandleARedirectLoop(): void {
|
||||
$this->assertException("tooManyRedirects", "Feed");
|
||||
$this->assertException("maxRedirect", "Feed");
|
||||
new Feed(null, $this->base."Fetching/EndlessLoop?i=0");
|
||||
}
|
||||
|
||||
public function testHandleAnOverlyLargeFeed(): void {
|
||||
$this->markTestIncomplete();
|
||||
Arsse::$conf->fetchSizeLimit = 512;
|
||||
$this->assertException("maxSize", "Feed");
|
||||
new Feed(null, $this->base."Fetching/TooLarge");
|
||||
|
|
23
tests/docroot/Feed/Caching/304Conditional.php
Normal file
23
tests/docroot/Feed/Caching/304Conditional.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
// this test returns 400 rather than 304 if the values of If-Modified-Since
|
||||
// and If-None-Match doesn't match $G_GET['t'] and $_GET['e'] respectively, or
|
||||
// if the $_GET members are missing
|
||||
if (
|
||||
!($_GET['t'] ?? "") ||
|
||||
!($_GET['e'] ?? "") ||
|
||||
($_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? "") !== gmdate("D, d M Y H:i:s \G\M\T", (int) $_GET['t']) ||
|
||||
($_SERVER['HTTP_IF_NONE_MATCH'] ?? "") !== $_GET['e']
|
||||
) {
|
||||
return [
|
||||
'code' => 400,
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'code' => 304,
|
||||
'lastMod' => random_int(0, 2^31),
|
||||
'fields' => [
|
||||
"ETag: ".bin2hex(random_bytes(8)),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
@ -2,6 +2,6 @@
|
|||
'code' => 304,
|
||||
'cache' => false,
|
||||
'fields' => [
|
||||
"ETag: ".$_SERVER['HTTP_IF_NONE_MATCH'],
|
||||
"ETag: ".($_SERVER['HTTP_IF_NONE_MATCH'] ?? "No ETag supplied"),
|
||||
],
|
||||
];
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
'code' => 304,
|
||||
'cache' => false,
|
||||
'fields' => [
|
||||
'Last-Modified: '.$_SERVER['HTTP_IF_MODIFIED_SINCE'],
|
||||
'Last-Modified: '.($_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? "No timestamp supplied"),
|
||||
],
|
||||
];
|
||||
|
|
|
@ -31,6 +31,7 @@ which include the following data:
|
|||
|
||||
|
||||
ignore_user_abort(false);
|
||||
ob_start();
|
||||
$defaults = [ // default values for response
|
||||
'code' => 200,
|
||||
'content' => "",
|
||||
|
@ -74,3 +75,4 @@ foreach ($response['fields'] as $h) {
|
|||
}
|
||||
// send the content
|
||||
echo $response['content'];
|
||||
ob_end_flush();
|
||||
|
|
Loading…
Reference in a new issue