Browse Source

CLI for OPML import, and proper exceptions

microsub
J. King 5 years ago
parent
commit
b9821d925a
  1. 6
      lib/AbstractException.php
  2. 11
      lib/CLI.php
  3. 19
      lib/ImportExport/OPML.php
  4. 18
      locale/en.php

6
lib/AbstractException.php

@ -86,8 +86,14 @@ abstract class AbstractException extends \Exception {
"Feed/Exception.xmlEntity" => 10512, "Feed/Exception.xmlEntity" => 10512,
"Feed/Exception.subscriptionNotFound" => 10521, "Feed/Exception.subscriptionNotFound" => 10521,
"Feed/Exception.unsupportedFeedFormat" => 10522, "Feed/Exception.unsupportedFeedFormat" => 10522,
"ImportExport/Exception.fileMissing" => 10601,
"ImportExport/Exception.fileUnreadable" => 10603,
"ImportExport/Exception.fileUnwritable" => 10604, "ImportExport/Exception.fileUnwritable" => 10604,
"ImportExport/Exception.fileUncreatable" => 10605, "ImportExport/Exception.fileUncreatable" => 10605,
"ImportExport/Exception.invalidSyntax" => 10611,
"ImportExport/Exception.invalidSemantics" => 10612,
"ImportExport/Exception.invalidFolderName" => 10613,
"ImportExport/Exception.invalidFolderCopy" => 10614,
]; ];
public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) { public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {

11
lib/CLI.php

@ -24,7 +24,10 @@ Usage:
arsse.php user unset-pass <username> arsse.php user unset-pass <username>
[--oldpass=<pass>] [--fever] [--oldpass=<pass>] [--fever]
arsse.php user auth <username> <password> [--fever] arsse.php user auth <username> <password> [--fever]
arsse.php export <username> [<file>] [-f | --flat] arsse.php export <username> [<file>]
[-f | --flat]
arsse.php import <username> [<file>]
[-f | --flat] [-r | --replace]
arsse.php --version arsse.php --version
arsse.php --help | -h arsse.php --help | -h
@ -70,7 +73,7 @@ USAGE_TEXT;
'help' => false, 'help' => false,
]); ]);
try { try {
$cmd = $this->command(["--help", "--version", "daemon", "feed refresh", "feed refresh-all", "conf save-defaults", "user", "export"], $args); $cmd = $this->command(["--help", "--version", "daemon", "feed refresh", "feed refresh-all", "conf save-defaults", "user", "export", "import"], $args);
if ($cmd && !in_array($cmd, ["--help", "--version", "conf save-defaults"])) { if ($cmd && !in_array($cmd, ["--help", "--version", "conf save-defaults"])) {
// only certain commands don't require configuration to be loaded // only certain commands don't require configuration to be loaded
$this->loadConf(); $this->loadConf();
@ -99,6 +102,10 @@ USAGE_TEXT;
$u = $args['<username>']; $u = $args['<username>'];
$file = $this->resolveFile($args['<file>'], "w"); $file = $this->resolveFile($args['<file>'], "w");
return (int) !$this->getInstance(OPML::class)->exportFile($file, $u, $args['--flat']); return (int) !$this->getInstance(OPML::class)->exportFile($file, $u, $args['--flat']);
case "import":
$u = $args['<username>'];
$file = $this->resolveFile($args['<file>'], "w");
return (int) !$this->getInstance(OPML::class)->importFile($file, $u, $args['--flat'], $args['--replace']);
} }
} catch (AbstractException $e) { } catch (AbstractException $e) {
$this->logError($e->getMessage()); $this->logError($e->getMessage());

19
lib/ImportExport/OPML.php

@ -19,14 +19,14 @@ class OPML {
foreach ($folders as $f) { foreach ($folders as $f) {
// check to make sure folder names are all valid // check to make sure folder names are all valid
if (!strlen(trim($f['name']))) { if (!strlen(trim($f['name']))) {
throw new \Exception; throw new Exception("invalidFolderName");
} }
// check for duplicates // check for duplicates
if (!isset($folderMap[$f['parent']])) { if (!isset($folderMap[$f['parent']])) {
$folderMap[$f['parent']] = []; $folderMap[$f['parent']] = [];
} }
if (isset($folderMap[$f['parent']][$f['name']])) { if (isset($folderMap[$f['parent']][$f['name']])) {
throw new \Exception; throw new Exception("invalidFolderCopy");
} else { } else {
$folderMap[$f['parent']][$f['name']] = true; $folderMap[$f['parent']][$f['name']] = true;
} }
@ -142,12 +142,13 @@ class OPML {
$d = new \DOMDocument; $d = new \DOMDocument;
if (!@$d->loadXML($opml)) { if (!@$d->loadXML($opml)) {
// not a valid XML document // not a valid XML document
throw new \Exception; $err = libxml_get_last_error();
throw new Exception("invalidSyntax", ['line' => $err->line, 'column' => $err->column]);
} }
$body = $d->getElementsByTagName("body"); $body = $d->getElementsByTagName("body");
if ($d->documentElement->nodeName !== "opml" || !$body->length || $body->item(0)->parentNode != $d->documentElement) { if ($d->documentElement->nodeName !== "opml" || !$body->length || $body->item(0)->parentNode != $d->documentElement) {
// not a valid OPML document // not a valid OPML document
throw new \Exception; throw new Exception("invalidSemantics", ['type' => "OPML"]);
} }
$body = $body->item(0); $body = $body->item(0);
$folders = []; $folders = [];
@ -268,4 +269,14 @@ class OPML {
} }
return true; return true;
} }
public function imortFile(string $file, string $user, bool $flat = false, bool $replace): bool {
$data = @file_get_contents($file);
if ($data === false) {
// if it fails throw an exception
$err = file_exists($file) ? "fileUnreadable" : "fileMissing";
throw new Exception($err, ['file' => $file, 'format' => str_replace(__NAMESPACE__."\\", "", __CLASS__)]);
}
return $this->import($user, $data, $flat, $replace);
}
} }

18
locale/en.php

@ -155,14 +155,12 @@ return [
'Exception.JKingWeb/Arsse/Feed/Exception.xmlEntity' => 'Refused to parse feed "{url}" because it contains an XXE attack', 'Exception.JKingWeb/Arsse/Feed/Exception.xmlEntity' => 'Refused to parse feed "{url}" because it contains an XXE attack',
'Exception.JKingWeb/Arsse/Feed/Exception.subscriptionNotFound' => 'Unable to find a feed at location "{url}"', 'Exception.JKingWeb/Arsse/Feed/Exception.subscriptionNotFound' => 'Unable to find a feed at location "{url}"',
'Exception.JKingWeb/Arsse/Feed/Exception.unsupportedFeedFormat' => 'Feed "{url}" is of an unsupported format', 'Exception.JKingWeb/Arsse/Feed/Exception.unsupportedFeedFormat' => 'Feed "{url}" is of an unsupported format',
'Exception.JKingWeb/Arsse/ImportExport/Exception.fileUncreatable' => 'Exception.JKingWeb/Arsse/ImportExport/Exception.fileMissing' => 'Import {type} file "{file}" does not exist',
'Insufficient permissions to write {type, select, 'Exception.JKingWeb/Arsse/ImportExport/Exception.fileUnreadable' => 'Insufficient permissions to read {type} file "{file}" for import',
OPML {OPML} 'Exception.JKingWeb/Arsse/ImportExport/Exception.fileUncreatable' => 'Insufficient permissions to write {type} export to file "{file}"',
other {"{type}"} 'Exception.JKingWeb/Arsse/ImportExport/Exception.fileUnwritable' => 'Insufficient permissions to write {type} export to existing file "{file}"',
} export to file "{file}"', 'Exception.JKingWeb/Arsse/ImportExport/Exception.invalidSyntax' => 'Input data syntax error at line {line}, column {column}',
'Exception.JKingWeb/Arsse/ImportExport/Exception.fileUnwritable' => 'Exception.JKingWeb/Arsse/ImportExport/Exception.invalidSemantics' => 'Input data is not valid {type} data',
'Insufficient permissions to write {type, select, 'Exception.JKingWeb/Arsse/ImportExport/Exception.invalidFolderName' => 'Input data contains an invalid folder name',
OPML {OPML} 'Exception.JKingWeb/Arsse/ImportExport/Exception.invalidFolderCopy' => 'Input data contains multiple folders of the same name under the same parent',
other {"{type}"}
} export to existing file "{file}"',
]; ];

Loading…
Cancel
Save