Browse Source

Clean up use of subscriptionFavicon

rpm
J. King 4 years ago
parent
commit
424b14d2b4
  1. 4
      lib/Database.php
  2. 11
      lib/REST/TinyTinyRSS/Icon.php
  3. 41
      tests/cases/Database/SeriesSubscription.php
  4. 24
      tests/cases/REST/TinyTinyRSS/TestIcon.php

4
lib/Database.php

@ -926,7 +926,7 @@ class Database {
*/ */
public function subscriptionIcon(?string $user, int $id, bool $includeData = true): array { public function subscriptionIcon(?string $user, int $id, bool $includeData = true): array {
$data = $includeData ? "i.data" : "null as data"; $data = $includeData ? "i.data" : "null as data";
$q = new Query("SELECT i.id, i.url, i.type, $data from arsse_icons as i join arsse_feeds as f on i.id = f.icon join arsse_subscriptions as s on s.feed = f.id"); $q = new Query("SELECT i.id, i.url, i.type, $data from arsse_subscriptions as s join arsse_feeds as f on s.feed = f.id left join arsse_icons as i on f.icon = i.id");
$q->setWhere("s.id = ?", "int", $id); $q->setWhere("s.id = ?", "int", $id);
if (isset($user)) { if (isset($user)) {
if (!Arsse::$user->authorize($user, __FUNCTION__)) { if (!Arsse::$user->authorize($user, __FUNCTION__)) {
@ -936,7 +936,7 @@ class Database {
} }
$out = $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getRow(); $out = $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getRow();
if (!$out) { if (!$out) {
throw new Db\ExceptionInput("idMissing", ["action" => __FUNCTION__, "field" => "subscription", 'id' => $id]); throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "subscription", 'id' => $id]);
} }
return $out; return $out;
} }

11
lib/REST/TinyTinyRSS/Icon.php

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST\TinyTinyRSS; namespace JKingWeb\Arsse\REST\TinyTinyRSS;
use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Db\ExceptionInput;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse as Response; use Laminas\Diactoros\Response\EmptyResponse as Response;
@ -29,14 +30,16 @@ class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
} elseif (!preg_match("<^(\d+)\.ico$>", $req->getRequestTarget(), $match) || !((int) $match[1])) { } elseif (!preg_match("<^(\d+)\.ico$>", $req->getRequestTarget(), $match) || !((int) $match[1])) {
return new Response(404); return new Response(404);
} }
$url = Arsse::$db->subscriptionFavicon((int) $match[1], Arsse::$user->id ?? null); try {
if ($url) { $url = Arsse::$db->subscriptionIcon(Arsse::$user->id ?? null, (int) $match[1], false)['url'];
// strip out anything after literal line-end characters; this is to mitigate a potential header (e.g. cookie) injection from the URL if (!$url) {
return new Response(404);
}
if (($pos = strpos($url, "\r")) !== false || ($pos = strpos($url, "\n")) !== false) { if (($pos = strpos($url, "\r")) !== false || ($pos = strpos($url, "\n")) !== false) {
$url = substr($url, 0, $pos); $url = substr($url, 0, $pos);
} }
return new Response(301, ['Location' => $url]); return new Response(301, ['Location' => $url]);
} else { } catch (ExceptionInput $e) {
return new Response(404); return new Response(404);
} }
} }

41
tests/cases/Database/SeriesSubscription.php

@ -462,32 +462,35 @@ trait SeriesSubscription {
public function testRetrieveTheFaviconOfASubscription(): void { public function testRetrieveTheFaviconOfASubscription(): void {
$exp = "http://example.com/favicon.ico"; $exp = "http://example.com/favicon.ico";
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(1)); $this->assertSame($exp, Arsse::$db->subscriptionIcon(null, 1)['url']);
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(2)); $this->assertSame($exp, Arsse::$db->subscriptionIcon(null, 2)['url']);
$this->assertSame('', Arsse::$db->subscriptionFavicon(3)); $this->assertSame(null, Arsse::$db->subscriptionIcon(null, 3)['url']);
$this->assertSame('', Arsse::$db->subscriptionFavicon(4));
// authorization shouldn't have any bearing on this function // authorization shouldn't have any bearing on this function
\Phake::when(Arsse::$user)->authorize->thenReturn(false); \Phake::when(Arsse::$user)->authorize->thenReturn(false);
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(1)); $this->assertSame($exp, Arsse::$db->subscriptionIcon(null, 1)['url']);
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(2)); $this->assertSame($exp, Arsse::$db->subscriptionIcon(null, 2)['url']);
$this->assertSame('', Arsse::$db->subscriptionFavicon(3)); $this->assertSame(null, Arsse::$db->subscriptionIcon(null, 3)['url']);
$this->assertSame('', Arsse::$db->subscriptionFavicon(4)); }
// invalid IDs should simply return an empty string
$this->assertSame('', Arsse::$db->subscriptionFavicon(-2112)); public function testRetrieveTheFaviconOfAMissingSubscription(): void {
$this->assertException("subjectMissing", "Db", "ExceptionInput");
Arsse::$db->subscriptionIcon(null, -2112);
} }
public function testRetrieveTheFaviconOfASubscriptionWithUser(): void { public function testRetrieveTheFaviconOfASubscriptionWithUser(): void {
$exp = "http://example.com/favicon.ico"; $exp = "http://example.com/favicon.ico";
$user = "john.doe@example.com"; $user = "john.doe@example.com";
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(1, $user)); $this->assertSame($exp, Arsse::$db->subscriptionIcon($user, 1)['url']);
$this->assertSame('', Arsse::$db->subscriptionFavicon(2, $user)); $this->assertSame(null, Arsse::$db->subscriptionIcon($user, 3)['url']);
$this->assertSame('', Arsse::$db->subscriptionFavicon(3, $user));
$this->assertSame('', Arsse::$db->subscriptionFavicon(4, $user));
$user = "jane.doe@example.com"; $user = "jane.doe@example.com";
$this->assertSame('', Arsse::$db->subscriptionFavicon(1, $user)); $this->assertSame($exp, Arsse::$db->subscriptionIcon($user, 2)['url']);
$this->assertSame($exp, Arsse::$db->subscriptionFavicon(2, $user)); }
$this->assertSame('', Arsse::$db->subscriptionFavicon(3, $user));
$this->assertSame('', Arsse::$db->subscriptionFavicon(4, $user)); public function testRetrieveTheFaviconOfASubscriptionOfTheWrongUser(): void {
$exp = "http://example.com/favicon.ico";
$user = "john.doe@example.com";
$this->assertException("subjectMissing", "Db", "ExceptionInput");
$this->assertSame(null, Arsse::$db->subscriptionIcon($user, 2)['url']);
} }
public function testRetrieveTheFaviconOfASubscriptionWithUserWithoutAuthority(): void { public function testRetrieveTheFaviconOfASubscriptionWithUserWithoutAuthority(): void {
@ -495,7 +498,7 @@ trait SeriesSubscription {
$user = "john.doe@example.com"; $user = "john.doe@example.com";
\Phake::when(Arsse::$user)->authorize->thenReturn(false); \Phake::when(Arsse::$user)->authorize->thenReturn(false);
$this->assertException("notAuthorized", "User", "ExceptionAuthz"); $this->assertException("notAuthorized", "User", "ExceptionAuthz");
Arsse::$db->subscriptionFavicon(-2112, $user); Arsse::$db->subscriptionIcon($user, -2112);
} }
public function testListTheTagsOfASubscription(): void { public function testListTheTagsOfASubscription(): void {

24
tests/cases/REST/TinyTinyRSS/TestIcon.php

@ -48,10 +48,10 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testRetrieveFavion(): void { public function testRetrieveFavion(): void {
\Phake::when(Arsse::$db)->subscriptionFavicon->thenReturn(""); \Phake::when(Arsse::$db)->subscriptionIcon->thenReturn(['url' => null]);
\Phake::when(Arsse::$db)->subscriptionFavicon(42, $this->anything())->thenReturn("http://example.com/favicon.ico"); \Phake::when(Arsse::$db)->subscriptionIcon($this->anything(), 42, false)->thenReturn(['url' => "http://example.com/favicon.ico"]);
\Phake::when(Arsse::$db)->subscriptionFavicon(2112, $this->anything())->thenReturn("http://example.net/logo.png"); \Phake::when(Arsse::$db)->subscriptionIcon($this->anything(), 2112, false)->thenReturn(['url' => "http://example.net/logo.png"]);
\Phake::when(Arsse::$db)->subscriptionFavicon(1337, $this->anything())->thenReturn("http://example.org/icon.gif\r\nLocation: http://bad.example.com/"); \Phake::when(Arsse::$db)->subscriptionIcon($this->anything(), 1337, false)->thenReturn(['url' => "http://example.org/icon.gif\r\nLocation: http://bad.example.com/"]);
// these requests should succeed // these requests should succeed
$exp = new Response(301, ['Location' => "http://example.com/favicon.ico"]); $exp = new Response(301, ['Location' => "http://example.com/favicon.ico"]);
$this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("42.ico"));
@ -71,14 +71,14 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testRetrieveFavionWithHttpAuthentication(): void { public function testRetrieveFavionWithHttpAuthentication(): void {
$url = "http://example.org/icon.gif\r\nLocation: http://bad.example.com/"; $url = ['url' => "http://example.org/icon.gif\r\nLocation: http://bad.example.com/"];
\Phake::when(Arsse::$db)->subscriptionFavicon->thenReturn(""); \Phake::when(Arsse::$db)->subscriptionIcon->thenReturn(['url' => null]);
\Phake::when(Arsse::$db)->subscriptionFavicon(42, $this->user)->thenReturn($url); \Phake::when(Arsse::$db)->subscriptionIcon($this->user, 42, false)->thenReturn($url);
\Phake::when(Arsse::$db)->subscriptionFavicon(2112, "jane.doe")->thenReturn($url); \Phake::when(Arsse::$db)->subscriptionIcon("jane.doe", 2112, false)->thenReturn($url);
\Phake::when(Arsse::$db)->subscriptionFavicon(1337, $this->user)->thenReturn($url); \Phake::when(Arsse::$db)->subscriptionIcon($this->user, 1337, false)->thenReturn($url);
\Phake::when(Arsse::$db)->subscriptionFavicon(42, null)->thenReturn($url); \Phake::when(Arsse::$db)->subscriptionIcon(null, 42, false)->thenReturn($url);
\Phake::when(Arsse::$db)->subscriptionFavicon(2112, null)->thenReturn($url); \Phake::when(Arsse::$db)->subscriptionIcon(null, 2112, false)->thenReturn($url);
\Phake::when(Arsse::$db)->subscriptionFavicon(1337, null)->thenReturn($url); \Phake::when(Arsse::$db)->subscriptionIcon(null, 1337, false)->thenReturn($url);
// these requests should succeed // these requests should succeed
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); $exp = new Response(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("42.ico"));

Loading…
Cancel
Save