From 0117e7f9bfd55db1ca4f6a5616aaaa37375d38de Mon Sep 17 00:00:00 2001 From: "J. King" Date: Tue, 27 Oct 2020 10:49:54 -0400 Subject: [PATCH] Relax Fever's HTTP correctness for client compat - Unread on iOS appears to send all API requests as GETs - Newsflash on Linux sends multipart/form-data input --- lib/REST/Fever/API.php | 9 +++++---- tests/cases/REST/Fever/TestAPI.php | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/REST/Fever/API.php b/lib/REST/Fever/API.php index 9a5779b..1901397 100644 --- a/lib/REST/Fever/API.php +++ b/lib/REST/Fever/API.php @@ -22,7 +22,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { public const LEVEL = 3; protected const GENERIC_ICON_TYPE = "image/png;base64"; protected const GENERIC_ICON_DATA = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg=="; - protected const ACCEPTED_TYPE = "application/x-www-form-urlencoded"; + protected const ACCEPTED_TYPES = ["application/x-www-form-urlencoded", "multipart/form-data"]; // GET parameters for which we only check presence: these will be converted to booleans protected const PARAM_BOOL = ["groups", "feeds", "items", "favicons", "links", "unread_item_ids", "saved_item_ids"]; @@ -68,11 +68,12 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { case "OPTIONS": return new EmptyResponse(204, [ 'Allow' => "POST", - 'Accept' => self::ACCEPTED_TYPE, + 'Accept' => implode(", ", self::ACCEPTED_TYPES), ]); + case "GET": // HTTP violation required for client "Unread" on iOS case "POST": - if (!HTTP::matchType($req, self::ACCEPTED_TYPE, "")) { - return new EmptyResponse(415, ['Accept' => self::ACCEPTED_TYPE]); + if (!HTTP::matchType($req, "", ...self::ACCEPTED_TYPES)) { + return new EmptyResponse(415, ['Accept' => implode(", ", self::ACCEPTED_TYPES)]); } $out = [ 'api_version' => self::LEVEL, diff --git a/tests/cases/REST/Fever/TestAPI.php b/tests/cases/REST/Fever/TestAPI.php index d89a17f..d0632c9 100644 --- a/tests/cases/REST/Fever/TestAPI.php +++ b/tests/cases/REST/Fever/TestAPI.php @@ -427,9 +427,11 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { public function provideInvalidRequests(): iterable { return [ - 'Not an API request' => [$this->req(""), new EmptyResponse(404)], - 'Wrong method' => [$this->req("api", "", "GET"), new EmptyResponse(405, ['Allow' => "OPTIONS,POST"])], - 'Wrong content type' => [$this->req("api", '{"api_key":"validToken"}', "POST", "application/json"), new EmptyResponse(415, ['Accept' => "application/x-www-form-urlencoded"])], + 'Not an API request' => [$this->req(""), new EmptyResponse(404)], + 'Wrong method' => [$this->req("api", "", "PUT"), new EmptyResponse(405, ['Allow' => "OPTIONS,POST"])], + 'Non-standard method' => [$this->req("api", "", "GET"), new JsonResponse([])], + 'Wrong content type' => [$this->req("api", '{"api_key":"validToken"}', "POST", "application/json"), new EmptyResponse(415, ['Accept' => "application/x-www-form-urlencoded, multipart/form-data"])], + 'Non-standard content type' => [$this->req("api", '{"api_key":"validToken"}', "POST", "multipart/form-data; boundary=33b68964f0de4c1f-5144aa6caaa6e4a8-18bfaf416a1786c8-5c5053a45f221bc1"), new JsonResponse([])], ]; } @@ -499,7 +501,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { $act = $this->h->dispatch($this->req("api", "", "OPTIONS")); $exp = new EmptyResponse(204, [ 'Allow' => "POST", - 'Accept' => "application/x-www-form-urlencoded", + 'Accept' => "application/x-www-form-urlencoded, multipart/form-data", ]); $this->assertMessage($exp, $act); }