if($this->db->prepare("DELETE from arsse_users where id is ?", "str")->run($user)->changes() <1)thrownewUser\Exception("doesNotExist",["action"=> __FUNCTION__, "user" => $user]);
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
if($this->db->prepare("DELETE from arsse_users where id is ?", "str")->run($user)->changes() <1){
throw new User\Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
}
return true;
return true;
}
}
public function userList(string $domain = null): array {
public function userList(string $domain = null): array {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
// check to see if the feed exists
// check to see if the feed exists
$feedID = $this->db->prepare("SELECT id from arsse_feeds where url is ? and username is ? and password is ?", "str", "str", "str")->run($url, $fetchUser, $fetchPassword)->getValue();
$feedID = $this->db->prepare("SELECT id from arsse_feeds where url is ? and username is ? and password is ?", "str", "str", "str")->run($url, $fetchUser, $fetchPassword)->getValue();
if(is_null($feedID)) {
if(is_null($feedID)) {
@ -331,7 +394,9 @@ class Database {
}
}
public function subscriptionList(string $user, int $folder = null, int $id = null): Db\Result {
public function subscriptionList(string $user, int $folder = null, int $id = null): Db\Result {
$f = $this->db->prepare("SELECT url, username, password, modified, etag, err_count, scrape FROM arsse_feeds where id is ?", "int")->run($feedID)->getRow();
$f = $this->db->prepare("SELECT url, username, password, modified, etag, err_count, scrape FROM arsse_feeds where id is ?", "int")->run($feedID)->getRow();
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
if(!$context) {
$context = new Context;
}
$q = new Query("SELECT max(arsse_editions.id) from arsse_editions left join arsse_articles on article is arsse_articles.id left join arsse_feeds on arsse_articles.feed is arsse_feeds.id");
$q = new Query("SELECT max(arsse_editions.id) from arsse_editions left join arsse_articles on article is arsse_articles.id left join arsse_feeds on arsse_articles.feed is arsse_feeds.id");
if($context->subscription()) {
if($context->subscription()) {
// if a subscription is specified, make sure it exists
// if a subscription is specified, make sure it exists
@ -592,8 +682,12 @@ class Database {
}
}
public function articleList(string $user, Context $context = null): Db\Result {
public function articleList(string $user, Context $context = null): Db\Result {
// if multiple specific editions have been requested, prepare a CTE to list them and their articles
// if multiple specific editions have been requested, prepare a CTE to list them and their articles
if(!$context->editions) throw new Db\ExceptionInput("tooShort", ['field' => "editions", 'action' => __FUNCTION__, 'min' => 1]); // must have at least one array element
if(!$context->editions) {
if(sizeof($context->editions) > 50) throw new Db\ExceptionInput("tooLong", ['field' => "editions", 'action' => __FUNCTION__, 'max' => 50]); // must not have more than 50 array elements
throw new Db\ExceptionInput("tooShort", ['field' => "editions", 'action' => __FUNCTION__, 'min' => 1]); // must have at least one array element
} else if(sizeof($context->editions) > 50) {
throw new Db\ExceptionInput("tooLong", ['field' => "editions", 'action' => __FUNCTION__, 'max' => 50]); // must not have more than 50 array elements
"SELECT article,id as edition from arsse_editions where edition in ($inParams)",
"SELECT article,id as edition from arsse_editions where edition in ($inParams)",
@ -742,8 +857,11 @@ class Database {
$q->setWhere("arsse_articles.id in (select id from requested_articles)");
$q->setWhere("arsse_articles.id in (select id from requested_articles)");
} else if($context->articles()) {
} else if($context->articles()) {
// if multiple specific articles have been requested, prepare a CTE to list them and their articles
// if multiple specific articles have been requested, prepare a CTE to list them and their articles
if(!$context->articles) throw new Db\ExceptionInput("tooShort", ['field' => "articles", 'action' => __FUNCTION__, 'min' => 1]); // must have at least one array element
if(!$context->articles) {
if(sizeof($context->articles) > 50) throw new Db\ExceptionInput("tooLong", ['field' => "articles", 'action' => __FUNCTION__, 'max' => 50]); // must not have more than 50 array elements
throw new Db\ExceptionInput("tooShort", ['field' => "articles", 'action' => __FUNCTION__, 'min' => 1]); // must have at least one array element
} else if(sizeof($context->articles) > 50) {
throw new Db\ExceptionInput("tooLong", ['field' => "articles", 'action' => __FUNCTION__, 'max' => 50]); // must not have more than 50 array elements
"SELECT id,(select max(id) from arsse_editions where article is arsse_articles.id) as edition from arsse_articles where arsse_articles.id in ($inParams)",
"SELECT id,(select max(id) from arsse_editions where article is arsse_articles.id) as edition from arsse_articles where arsse_articles.id in ($inParams)",
@ -756,11 +874,19 @@ class Database {
$q->setCTE("requested_articles(id,edition)", "SELECT 'empty','table' where 1 is 0");
$q->setCTE("requested_articles(id,edition)", "SELECT 'empty','table' where 1 is 0");
// if requesting the same locale as already wanted, just return (but load first if we've requested an immediate load)
// if requesting the same locale as already wanted, just return (but load first if we've requested an immediate load)
if($locale==$this->wanted) {
if($locale==$this->wanted) {
if($immediate && !$this->synched) $this->load();
if($immediate && !$this->synched) {
$this->load();
}
return $locale;
return $locale;
}
}
// if we've requested a locale other than the null locale, fetch the list of available files and find the closest match e.g. en_ca_somedialect -> en_ca
// if we've requested a locale other than the null locale, fetch the list of available files and find the closest match e.g. en_ca_somedialect -> en_ca
if($locale != "") {
if($locale != "") {
$list = $this->listFiles();
$list = $this->listFiles();
// if the default locale is unavailable, this is (for now) an error
// if the default locale is unavailable, this is (for now) an error
if(!in_array(self::DEFAULT, $list)) throw new Lang\Exception("defaultFileMissing", self::DEFAULT);
if(!in_array(self::DEFAULT, $list)) {
throw new Lang\Exception("defaultFileMissing", self::DEFAULT);
}
$this->wanted = $this->match($locale, $list);
$this->wanted = $this->match($locale, $list);
} else {
} else {
$this->wanted = "";
$this->wanted = "";
}
}
$this->synched = false;
$this->synched = false;
// load right now if asked to, otherwise load later when actually required
// load right now if asked to, otherwise load later when actually required
if($immediate) $this->load();
if($immediate) {
$this->load();
}
return $this->wanted;
return $this->wanted;
}
}
@ -74,7 +82,9 @@ class Lang {
}
}
// if the requested message is not present in any of the currently loaded language files, throw an exception
// if the requested message is not present in any of the currently loaded language files, throw an exception
// note that this is indicative of a programming error since the default locale should have all strings
// note that this is indicative of a programming error since the default locale should have all strings
return new Response(415, "", "", ['Accept: application/json']);
}
$data = @json_decode($req->body, true);
$data = @json_decode($req->body, true);
if(json_last_error() != \JSON_ERROR_NONE) {
if(json_last_error() != \JSON_ERROR_NONE) {
// if the body could not be parsed as JSON, return "400 Bad Request"
// if the body could not be parsed as JSON, return "400 Bad Request"
@ -68,7 +70,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch(Exception405 $e) {
} catch(Exception405 $e) {
return new Response(405, "", "", ["Allow: ".$e->getMessage()]);
return new Response(405, "", "", ["Allow: ".$e->getMessage()]);
}
}
if(!method_exists($this, $func)) return new Response(501);
if(!method_exists($this, $func)) {
return new Response(501);
}
// dispatch
// dispatch
try {
try {
return $this->$func($req->paths, $data);
return $this->$func($req->paths, $data);
@ -129,12 +133,16 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
$scope = $url[0];
$scope = $url[0];
// any URL components which are only digits should be replaced with "0", for easier comparison (integer segments are IDs, and we don't care about the specific ID)
// any URL components which are only digits should be replaced with "0", for easier comparison (integer segments are IDs, and we don't care about the specific ID)
for($a = 0; $a <sizeof($url);$a++){
for($a = 0; $a <sizeof($url);$a++){
if($this->validateInt($url[$a])) $url[$a] = "0";
if($this->validateInt($url[$a])) {
$url[$a] = "0";
}
}
}
// normalize the HTTP method to uppercase
// normalize the HTTP method to uppercase
$method = strtoupper($method);
$method = strtoupper($method);
// if the scope is not supported, return 501
// if the scope is not supported, return 501
if(!array_key_exists($scope, $choices)) throw new Exception501();
if(!array_key_exists($scope, $choices)) {
throw new Exception501();
}
// we now evaluate the supplied URL against every supported path for the selected scope
// we now evaluate the supplied URL against every supported path for the selected scope
// the URL is evaluated as an array so as to avoid decoded escapes turning invalid URLs into valid ones
// the URL is evaluated as an array so as to avoid decoded escapes turning invalid URLs into valid ones
foreach($choices[$scope] as $path => $funcs) {
foreach($choices[$scope] as $path => $funcs) {
@ -251,7 +259,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// rename a folder (also supports moving nesting folders, but this is not a feature of the API)
// rename a folder (also supports moving nesting folders, but this is not a feature of the API)
protected function folderRename(array $url, array $data): Response {
protected function folderRename(array $url, array $data): Response {