Browse Source

Fix problems with nicolus/picofeed

This involved multiple fixes to Picofeed itself, not all of which have
been merged upstream yet
rpm
J. King 4 years ago
parent
commit
49d003082d
  1. 8
      composer.lock
  2. 2
      lib/AbstractException.php
  3. 2
      lib/Feed.php
  4. 27
      lib/Feed/Exception.php
  5. 2
      locale/en.php
  6. 28
      tests/cases/Feed/TestFeed.php
  7. 3
      tests/cases/Feed/TestFetching.php
  8. 23
      tests/docroot/Feed/Caching/304Conditional.php
  9. 2
      tests/docroot/Feed/Caching/304ETagOnly.php
  10. 2
      tests/docroot/Feed/Caching/304LastModOnly.php
  11. 2
      tests/server.php

8
composer.lock

@ -652,12 +652,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/JKingweb/picoFeed-1.git", "url": "https://github.com/JKingweb/picoFeed-1.git",
"reference": "419bc85afb18a84e43274029cf8e198cc5785425" "reference": "009250e98ff0975335176f74e5fea95104058d09"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/JKingweb/picoFeed-1/zipball/419bc85afb18a84e43274029cf8e198cc5785425", "url": "https://api.github.com/repos/JKingweb/picoFeed-1/zipball/009250e98ff0975335176f74e5fea95104058d09",
"reference": "419bc85afb18a84e43274029cf8e198cc5785425", "reference": "009250e98ff0975335176f74e5fea95104058d09",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -708,7 +708,7 @@
"support": { "support": {
"source": "https://github.com/JKingweb/picoFeed-1/tree/fixed" "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", "name": "psr/http-factory",

2
lib/AbstractException.php

@ -77,7 +77,7 @@ abstract class AbstractException extends \Exception {
"User/ExceptionSession.invalid" => 10431, "User/ExceptionSession.invalid" => 10431,
"Feed/Exception.invalidCertificate" => 10501, "Feed/Exception.invalidCertificate" => 10501,
"Feed/Exception.invalidUrl" => 10502, "Feed/Exception.invalidUrl" => 10502,
"Feed/Exception.tooManyRedirects" => 10503, "Feed/Exception.maxRedirect" => 10503,
"Feed/Exception.maxSize" => 10504, "Feed/Exception.maxSize" => 10504,
"Feed/Exception.timeout" => 10505, "Feed/Exception.timeout" => 10505,
"Feed/Exception.forbidden" => 10506, "Feed/Exception.forbidden" => 10506,

2
lib/Feed.php

@ -50,7 +50,7 @@ class Feed {
$this->resource = self::download($url, $lastModified, $etag, $username, $password); $this->resource = self::download($url, $lastModified, $etag, $username, $password);
// format the HTTP Last-Modified date returned // format the HTTP Last-Modified date returned
$lastMod = $this->resource->getLastModified(); $lastMod = $this->resource->getLastModified();
if (strlen($lastMod)) { if (strlen($lastMod ?? "")) {
$this->lastModified = Date::normalize($lastMod, "http"); $this->lastModified = Date::normalize($lastMod, "http");
} }
$this->modified = $this->resource->isModified(); $this->modified = $this->resource->isModified();

27
lib/Feed/Exception.php

@ -6,11 +6,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\Feed; namespace JKingWeb\Arsse\Feed;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\TooManyRedirectsException;
use PicoFeed\PicoFeedException;
class Exception extends \JKingWeb\Arsse\AbstractException { class Exception extends \JKingWeb\Arsse\AbstractException {
public function __construct($url, \Throwable $e) { public function __construct($url, \Throwable $e) {
if ($e instanceof GuzzleException) { if ($e instanceof BadResponseException) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 401: case 401:
$msgID = "unauthorized"; $msgID = "unauthorized";
@ -31,13 +34,29 @@ class Exception extends \JKingWeb\Arsse\AbstractException {
$msgID = "transmissionError"; $msgID = "transmissionError";
} }
} }
} } elseif ($e instanceof TooManyRedirectsException) {
if (!($msgID ?? "")) { $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); $className = get_class($e);
// Convert the exception thrown by PicoFeed to the one to be thrown here. // 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. // If the message ID doesn't change then it's unknown.
$msgID = ($msgID !== $className) ? lcfirst($msgID) : ''; $msgID = ($msgID !== $className) ? lcfirst($msgID) : '';
} else {
$msgID = get_class($e);
} }
parent::__construct($msgID, ['url' => $url], $e); parent::__construct($msgID, ['url' => $url], $e);
} }

2
locale/en.php

@ -146,7 +146,7 @@ return [
'Exception.JKingWeb/Arsse/User/ExceptionSession.invalid' => 'Session with ID {0} does not exist', '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.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.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.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.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.forbidden' => 'Could not download feed "{url}" because you do not have permission to access it',

28
tests/cases/Feed/TestFeed.php

@ -198,24 +198,26 @@ class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest {
$this->assertSame("http://example.com/1", $f->newItems[0]->url); $this->assertSame("http://example.com/1", $f->newItems[0]->url);
} }
public function testHandleCacheHeadersOn304(): void { /** @dataProvider provide304ResponseURLs */
// upon 304, the client should re-use the caching header values it supplied the server public function testHandleCacheHeadersOn304(string $url): void {
$t = time(); // 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"; $e = "78567a";
$f = new Feed(null, $this->base."Caching/304Random", 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());
$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);
$this->assertTime($t, $f->lastModified); $this->assertTime($t, $f->lastModified);
$this->assertSame($e, $f->resource->getETag()); $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 { public function testHandleCacheHeadersOn200(): void {
// these tests should trust the server-returned time, even in cases of obviously incorrect results // these tests should trust the server-returned time, even in cases of obviously incorrect results
$t = time() - 2000; $t = time() - 2000;

3
tests/cases/Feed/TestFetching.php

@ -53,11 +53,12 @@ class TestFetching extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testHandleARedirectLoop(): void { public function testHandleARedirectLoop(): void {
$this->assertException("tooManyRedirects", "Feed"); $this->assertException("maxRedirect", "Feed");
new Feed(null, $this->base."Fetching/EndlessLoop?i=0"); new Feed(null, $this->base."Fetching/EndlessLoop?i=0");
} }
public function testHandleAnOverlyLargeFeed(): void { public function testHandleAnOverlyLargeFeed(): void {
$this->markTestIncomplete();
Arsse::$conf->fetchSizeLimit = 512; Arsse::$conf->fetchSizeLimit = 512;
$this->assertException("maxSize", "Feed"); $this->assertException("maxSize", "Feed");
new Feed(null, $this->base."Fetching/TooLarge"); new Feed(null, $this->base."Fetching/TooLarge");

23
tests/docroot/Feed/Caching/304Conditional.php

@ -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
tests/docroot/Feed/Caching/304ETagOnly.php

@ -2,6 +2,6 @@
'code' => 304, 'code' => 304,
'cache' => false, 'cache' => false,
'fields' => [ 'fields' => [
"ETag: ".$_SERVER['HTTP_IF_NONE_MATCH'], "ETag: ".($_SERVER['HTTP_IF_NONE_MATCH'] ?? "No ETag supplied"),
], ],
]; ];

2
tests/docroot/Feed/Caching/304LastModOnly.php

@ -2,6 +2,6 @@
'code' => 304, 'code' => 304,
'cache' => false, 'cache' => false,
'fields' => [ 'fields' => [
'Last-Modified: '.$_SERVER['HTTP_IF_MODIFIED_SINCE'], 'Last-Modified: '.($_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? "No timestamp supplied"),
], ],
]; ];

2
tests/server.php

@ -31,6 +31,7 @@ which include the following data:
ignore_user_abort(false); ignore_user_abort(false);
ob_start();
$defaults = [ // default values for response $defaults = [ // default values for response
'code' => 200, 'code' => 200,
'content' => "", 'content' => "",
@ -74,3 +75,4 @@ foreach ($response['fields'] as $h) {
} }
// send the content // send the content
echo $response['content']; echo $response['content'];
ob_end_flush();

Loading…
Cancel
Save