From 5f993187ea4239fd0ffdf63619e554683b979979 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Fri, 27 Sep 2019 17:16:34 -0400 Subject: [PATCH 1/2] Be explicit with HTTP challenge character encoding --- CHANGELOG | 1 + lib/REST.php | 2 +- tests/cases/REST/TestREST.php | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 153dfeb5..3e48ece9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ Bug fixes: - Don't fail adding a feed which collides with another via redirection - Don't fail on very long text-search queries containing question marks when using PostgreSQL or MySQL +- Specify HTTP authentication encoding as UTF-8 Changes: - Include a user manual diff --git a/lib/REST.php b/lib/REST.php index 989205f4..9212326d 100644 --- a/lib/REST.php +++ b/lib/REST.php @@ -150,7 +150,7 @@ class REST { public function challenge(ResponseInterface $res, string $realm = null): ResponseInterface { $realm = $realm ?? Arsse::$conf->httpRealm; - return $res->withAddedHeader("WWW-Authenticate", 'Basic realm="'.$realm.'"'); + return $res->withAddedHeader("WWW-Authenticate", 'Basic realm="'.$realm.'", charset="UTF-8"'); } public function normalizeResponse(ResponseInterface $res, RequestInterface $req = null): ResponseInterface { diff --git a/tests/cases/REST/TestREST.php b/tests/cases/REST/TestREST.php index 994d6ac5..fb0dc2f7 100644 --- a/tests/cases/REST/TestREST.php +++ b/tests/cases/REST/TestREST.php @@ -97,10 +97,10 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { self::setConf(); $r = new REST(); $in = new EmptyResponse(401); - $exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK"'); + $exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK", charset="UTF-8"'); $act = $r->challenge($in, "OOK"); $this->assertMessage($exp, $act); - $exp = $in->withHeader("WWW-Authenticate", 'Basic realm="'.Arsse::$conf->httpRealm.'"'); + $exp = $in->withHeader("WWW-Authenticate", 'Basic realm="'.Arsse::$conf->httpRealm.'", charset="UTF-8"'); $act = $r->challenge($in); $this->assertMessage($exp, $act); } From 4f5a8e3180bd84b10a1e2dd21b03bad16c08b249 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Fri, 27 Sep 2019 22:38:03 -0400 Subject: [PATCH 2/2] Make media type checking more robust --- lib/Misc/HTTP.php | 22 ++++++++++++++++++++++ lib/REST/Fever/API.php | 8 +++++--- lib/REST/NextCloudNews/V1_2.php | 13 +++++-------- lib/REST/TinyTinyRSS/API.php | 3 ++- tests/cases/Misc/TestHTTP.php | 32 ++++++++++++++++++++++++++++++++ tests/phpunit.dist.xml | 1 + 6 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 lib/Misc/HTTP.php create mode 100644 tests/cases/Misc/TestHTTP.php diff --git a/lib/Misc/HTTP.php b/lib/Misc/HTTP.php new file mode 100644 index 00000000..78a817b7 --- /dev/null +++ b/lib/Misc/HTTP.php @@ -0,0 +1,22 @@ +getHeaderLine("Content-Type") ?? ""; + foreach ($type as $t) { + $pattern = "/^".preg_quote(trim($t), "/")."($|;|,)/i"; + if (preg_match($pattern, $header)) { + return true; + } + } + return false; + } +} diff --git a/lib/REST/Fever/API.php b/lib/REST/Fever/API.php index 1401d63a..a83b3e1a 100644 --- a/lib/REST/Fever/API.php +++ b/lib/REST/Fever/API.php @@ -11,6 +11,7 @@ use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Db\ExceptionInput; +use JKingWeb\Arsse\Misc\HTTP; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response\JsonResponse; @@ -21,6 +22,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { const LEVEL = 3; const GENERIC_ICON_TYPE = "image/png;base64"; const GENERIC_ICON_DATA = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg=="; + const ACCEPTED_TYPE = "application/x-www-form-urlencoded"; // GET parameters for which we only check presence: these will be converted to booleans const PARAM_BOOL = ["groups", "feeds", "items", "favicons", "links", "unread_item_ids", "saved_item_ids"]; @@ -66,11 +68,11 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { case "OPTIONS": return new EmptyResponse(204, [ 'Allow' => "POST", - 'Accept' => "application/x-www-form-urlencoded", + 'Accept' => self::ACCEPTED_TYPE, ]); case "POST": - if (strlen($req->getHeaderLine("Content-Type")) && $req->getHeaderLine("Content-Type") !== "application/x-www-form-urlencoded") { - return new EmptyResponse(415, ['Accept' => "application/x-www-form-urlencoded"]); + if (!HTTP::matchType($req, self::ACCEPTED_TYPE, "")) { + return new EmptyResponse(415, ['Accept' => self::ACCEPTED_TYPE]); } $out = [ 'api_version' => self::LEVEL, diff --git a/lib/REST/NextCloudNews/V1_2.php b/lib/REST/NextCloudNews/V1_2.php index f64d9cb2..29652d17 100644 --- a/lib/REST/NextCloudNews/V1_2.php +++ b/lib/REST/NextCloudNews/V1_2.php @@ -13,6 +13,7 @@ use JKingWeb\Arsse\Misc\ValueInfo; use JKingWeb\Arsse\AbstractException; use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Feed\Exception as FeedException; +use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\REST\Exception404; use JKingWeb\Arsse\REST\Exception405; use Psr\Http\Message\ServerRequestInterface; @@ -23,6 +24,7 @@ use Zend\Diactoros\Response\EmptyResponse; class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { const REALM = "NextCloud News API v1-2"; const VERSION = "11.0.5"; + const ACCEPTED_TYPE = "application/json"; protected $dateFormat = "unix"; @@ -90,15 +92,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { } // normalize the input $data = (string) $req->getBody(); - $type = ""; - if ($req->hasHeader("Content-Type")) { - $type = $req->getHeader("Content-Type"); - $type = array_pop($type); - } if ($data) { // if the entity body is not JSON according to content type, return "415 Unsupported Media Type" - if (!preg_match("<^application/json\b|^$>", $type)) { - return new EmptyResponse(415, ['Accept' => "application/json"]); + if (!HTTP::matchType($req, "", self::ACCEPTED_TYPE)) { + return new EmptyResponse(415, ['Accept' => self::ACCEPTED_TYPE]); } $data = @json_decode($data, true); if (json_last_error() !== \JSON_ERROR_NONE) { @@ -269,7 +266,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { } return new EmptyResponse(204, [ 'Allow' => implode(",", $allowed), - 'Accept' => "application/json", + 'Accept' => self::ACCEPTED_TYPE, ]); } else { // if the path is not supported, return 404 diff --git a/lib/REST/TinyTinyRSS/API.php b/lib/REST/TinyTinyRSS/API.php index 045710cf..56a24374 100644 --- a/lib/REST/TinyTinyRSS/API.php +++ b/lib/REST/TinyTinyRSS/API.php @@ -43,6 +43,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { const CAT_NOT_SPECIAL = -3; const CAT_ALL = -4; // valid input + const ACCEPTED_TYPES = ["application/json", "text/json"]; const VALID_INPUT = [ 'op' => ValueInfo::T_STRING, // the function ("operation") to perform 'sid' => ValueInfo::T_STRING, // session ID @@ -99,7 +100,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { // respond to OPTIONS rquests; the response is a fib, as we technically accept any type or method return new EmptyResponse(204, [ 'Allow' => "POST", - 'Accept' => "application/json, text/json", + 'Accept' => implode(", ", self::ACCEPTED_TYPES), ]); } $data = (string) $req->getBody(); diff --git a/tests/cases/Misc/TestHTTP.php b/tests/cases/Misc/TestHTTP.php new file mode 100644 index 00000000..5d6f4656 --- /dev/null +++ b/tests/cases/Misc/TestHTTP.php @@ -0,0 +1,32 @@ +withHeader("Content-Type", $header); + $this->assertSame($exp, HTTP::matchType($msg, ...$types)); + $msg = (new \Zend\Diactoros\Response)->withHeader("Content-Type", $header); + $this->assertSame($exp, HTTP::matchType($msg, ...$types)); + } + + public function provideMediaTypes() { + return [ + ["application/json", ["application/json"], true], + ["APPLICATION/JSON", ["application/json"], true], + ["text/JSON", ["application/json", "text/json"], true], + ["text/json; charset=utf-8", ["application/json", "text/json"], true], + ["", ["application/json"], false], + ["", ["application/json", ""], true], + ]; + } +} diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml index 5489f948..28d2f899 100644 --- a/tests/phpunit.dist.xml +++ b/tests/phpunit.dist.xml @@ -47,6 +47,7 @@ cases/Misc/TestDate.php cases/Misc/TestContext.php cases/Misc/TestURL.php + cases/Misc/TestHTTP.php cases/User/TestInternal.php