diff --git a/lib/REST.php b/lib/REST.php index 39899c1..1ad740d 100644 --- a/lib/REST.php +++ b/lib/REST.php @@ -16,14 +16,12 @@ use Zend\Diactoros\Response\EmptyResponse; class REST { const API_LIST = [ - // NextCloud News version enumerator - 'ncn' => [ + 'ncn' => [ // NextCloud News version enumerator 'match' => '/index.php/apps/news/api', 'strip' => '/index.php/apps/news/api', 'class' => REST\NextCloudNews\Versions::class, ], - // NextCloud News v1-2 https://github.com/nextcloud/news/blob/master/docs/externalapi/Legacy.md - 'ncn_v1-2' => [ + 'ncn_v1-2' => [ // NextCloud News v1-2 https://github.com/nextcloud/news/blob/master/docs/externalapi/Legacy.md 'match' => '/index.php/apps/news/api/v1-2/', 'strip' => '/index.php/apps/news/api/v1-2', 'class' => REST\NextCloudNews\V1_2::class, @@ -38,9 +36,13 @@ class REST { 'strip' => '/tt-rss/feed-icons/', 'class' => REST\TinyTinyRSS\Icon::class, ], + 'fever' => [ // Fever https://web.archive.org/web/20161217042229/https://feedafever.com/api + 'match' => '/fever/', + 'strip' => '/fever/', + 'class' => REST\Fever\API::class, + ], // Other candidates: // Google Reader http://feedhq.readthedocs.io/en/latest/api/index.html - // Fever https://web.archive.org/web/20161217042229/https://feedafever.com/api // Feedbin v2 https://github.com/feedbin/feedbin-api // CommaFeed https://www.commafeed.com/api/ // Selfoss https://github.com/SSilence/selfoss/wiki/Restful-API-for-Apps-or-any-other-external-access diff --git a/lib/REST/Fever/API.php b/lib/REST/Fever/API.php new file mode 100644 index 0000000..b9fd5ad --- /dev/null +++ b/lib/REST/Fever/API.php @@ -0,0 +1,98 @@ +getQueryParams(); + if (!array_key_exists("api")) { + // the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404 + return new EmptyResponse(404); + } + $xml = $inR['api'] === "xml"; + switch ($req->getMethod()) { + case "OPTIONS": + // do stuff + break; + 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"]); + } + $inW = $req->getParsedBody(); + $out = [ + 'api_version' => self::LEVEL, + 'auth' => 0, + ]; + // check that the user specified credentials + if ($this->logIn(strtolower($inW['api_key'] ?? ""))) { + $out['auth'] = 1; + } else { + return $this->formatResponse($out, $xml); + } + // handle each possible parameter + # do stuff + // return the result + return $this->formatResponse($out, $xml); + break; + default: + return new EmptyResponse(405, ['Allow' => "OPTIONS,POST"]); + } + } + + protected function formatResponse(array $data, bool $xml): ResponseInterface { + if ($xml) { + throw \Exception("Not implemented yet"); + } else { + return new JsonResponse($data, 200, [], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + } + } + + protected function logIn(string $hash): bool { + // if HTTP authentication was successful and sessions are not enforced, proceed unconditionally + if (isset(Arsse::$user->id) && !Arsse::$conf->userSessionEnforced) { + return true; + } + try { + // verify the supplied hash is valid + $s = Arsse::$db->TokenLookup($id, "fever.login"); + } catch (\JKingWeb\Arsse\Db\ExceptionInput $e) { + return false; + } + // set the user name + Arsse::$user->id = $s['user']; + return true; + } + + public static function registerUser(string $user, string $password = null): string { + $password = $password ?? Arsse::$user->generatePassword(); + $hash = md5("$user:$password"); + Arsse::$db->tokenCreate($user, "fever.login", $hash); + return $password; + } +} diff --git a/lib/User.php b/lib/User.php index d7aae1c..82e8d3d 100644 --- a/lib/User.php +++ b/lib/User.php @@ -114,7 +114,7 @@ class User { return $out; } - protected function generatePassword(): string { + public function generatePassword(): string { return (new PassGen)->length(Arsse::$conf->userTempPasswordLength)->get(); } }