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": {
"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",

2
lib/AbstractException.php

@ -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,

2
lib/Feed.php

@ -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();

27
lib/Feed/Exception.php

@ -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);
}

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/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',

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);
}
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;

3
tests/cases/Feed/TestFetching.php

@ -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

@ -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,
'cache' => false,
'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,
'cache' => false,
'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);
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…
Cancel
Save