Browse Source

Normalize Fever input consistently

Two parameters are undocumented, but other implementations consistently
accept them from clients
microsub
J. King 5 years ago
parent
commit
5d994f3dad
  1. 80
      lib/REST/Fever/API.php

80
lib/REST/Fever/API.php

@ -11,7 +11,7 @@ use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\User; use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Service; use JKingWeb\Arsse\Service;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Misc\ValueInfo; use JKingWeb\Arsse\Misc\ValueInfo as V;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\AbstractException; use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
@ -26,17 +26,40 @@ use Zend\Diactoros\Response\EmptyResponse;
class API extends \JKingWeb\Arsse\REST\AbstractHandler { class API extends \JKingWeb\Arsse\REST\AbstractHandler {
const LEVEL = 3; const LEVEL = 3;
// 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"];
// GET parameters which contain meaningful values
const PARAM_GET = [
'api' => V::T_STRING, // this parameter requires special handling
'page' => V::T_INT, // parameter for hot links
'range' => V::T_INT, // parameter for hot links
'offset' => V::T_INT, // parameter for hot links
'since_id' => V::T_INT,
'max_id' => V::T_INT,
'with_ids' => V::T_STRING,
'group_ids' => V::T_STRING, // undocumented parameter for 'items' lookup
'feed_ids' => V::T_STRING, // undocumented parameter for 'items' lookup
];
// POST parameters, all of which contain meaningful values
const PARAM_POST = [
'api_key' => V::T_STRING,
'mark' => V::T_STRING,
'as' => V::T_STRING,
'id' => V::T_INT,
'before' => V::T_DATE,
'unread_recently_read' => V::T_BOOL,
];
public function __construct() { public function __construct() {
} }
public function dispatch(ServerRequestInterface $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
$inR = $req->getQueryParams() ?? []; $G = $this->normalizeInputGet($req->getQueryParams() ?? []);
$inW = $req->getParsedBody() ?? []; $P = $this->normalizeInputPost($req->getParsedBody() ?? []);
if (!array_key_exists("api", $inR)) { if (!isset($G['api'])) {
// the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404 // the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404
return new EmptyResponse(404); return new EmptyResponse(404);
} }
$xml = $inR['api'] === "xml";
switch ($req->getMethod()) { switch ($req->getMethod()) {
case "OPTIONS": case "OPTIONS":
// do stuff // do stuff
@ -58,32 +81,63 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
return new EmptyResponse(401); return new EmptyResponse(401);
} }
// produce a full response if authenticated or a basic response otherwise // produce a full response if authenticated or a basic response otherwise
if ($this->logIn(strtolower($inW['api_key'] ?? ""))) { if ($this->logIn(strtolower($P['api_key'] ?? ""))) {
$out = $this->processRequest($this->baseResponse(true), $inR, $inW); $out = $this->processRequest($this->baseResponse(true), $G, $P);
} else { } else {
$out = $this->baseResponse(false); $out = $this->baseResponse(false);
} }
// return the result, possibly formatted as XML // return the result, possibly formatted as XML
return $this->formatResponse($out, $xml); return $this->formatResponse($out, ($G['api'] === "xml"));
break;
default: default:
return new EmptyResponse(405, ['Allow' => "OPTIONS,POST"]); return new EmptyResponse(405, ['Allow' => "OPTIONS,POST"]);
} }
} }
protected function normalizeInputGet(array $data): array {
$out = [];
if (array_key_exists("api", $data)) {
// the "api" parameter must be handled specially as it a string, but null has special meaning
$data['api'] = $data['api'] ?? "json";
}
foreach (self::PARAM_BOOL as $p) {
// first handle all the boolean parameters
$out[$p] = array_key_exists($p, $data);
}
foreach (self::PARAM_GET as $p => $t) {
$out[$p] = V::normalize($data[$p] ?? null, $t | V::M_DROP, "unix");
}
return $out;
}
protected function normalizeInputPost(array $data): array {
$out = [];
foreach (self::PARAM_POST as $p => $t) {
$out[$p] = V::normalize($data[$p] ?? null, $t | V::M_DROP, "unix");
}
return $out;
}
protected function processRequest(array $out, array $G, array $P): array { protected function processRequest(array $out, array $G, array $P): array {
if (array_key_exists("feeds", $G) || array_key_exists("groups", $G)) { if ($G['feeds'] || $G['groups']) {
if (array_key_exists("groups", $G)) { if ($G['groups']) {
$out['groups'] = $this->getGroups(); $out['groups'] = $this->getGroups();
} }
if (array_key_exists("feeds", $G)) { if ($G['feeds']) {
$out['feeds'] = $this->getFeeds(); $out['feeds'] = $this->getFeeds();
} }
$out['feeds_groups'] = $this->getRelationships(); $out['feeds_groups'] = $this->getRelationships();
} }
if (array_key_exists("favicons", $G)) { if ($G['favicons']) {
# deal with favicons # deal with favicons
} }
if ($G['items']) {
$out['items'] = $this->getItems($G);
$out['total_items'] = Arsse::$db->articleCount(Arsse::$user->id);
}
if ($G['links']) {
// TODO: implement hot links
$out['inks'] = [];
}
return $out; return $out;
} }

Loading…
Cancel
Save