miniflux-legacy/fever/index.php
Mathias Kresin df4f3b2a52 Fever API: Allow to select the database
Utilize apaches mod_rewrite to append a database parameter to the fever
api script based on the requested URL. Basically everything after
/fever/ is treated as the desired database. E.g. using
https://www.miniflux.net/fever/custom.sqlite/ as server url selects this
database as sync source/target.

The user supplied database parameter is compared with the output of
Model\Database\get_all(), which ensures that only files within the
DATA_DIRECTORY can be referenced (prevents File Inclusion/Directory
Traversal vulnerabilities).

I've tested it against Reeder on iOS. Note that Reeder does not allow
the same user name to be used multiple times within the same domain
name, even if the used URLs are different.
2014-12-26 23:01:05 +01:00

313 lines
7.3 KiB
PHP

<?php
require '../common.php';
use Model\Feed;
use PicoDb\Database;
// Route handler
function route($name, Closure $callback = null)
{
static $routes = array();
if ($callback !== null) {
$routes[$name] = $callback;
}
else if (isset($routes[$name])) {
$routes[$name]();
}
}
// Serialize the payload in Json (XML is not supported)
function response(array $response)
{
header('Content-Type: application/json');
echo json_encode($response);
exit;
}
// Fever authentication
function auth()
{
if (!empty($_GET['database'])) {
Model\Database\select($_GET['database']);
}
$credentials = Database::get('db')->table('config')
->columns('username', 'fever_token')
->findOne();
$api_key = md5($credentials['username'].':'.$credentials['fever_token']);
$response = array(
'api_version' => 3,
'auth' => (int) (@$_POST['api_key'] === $api_key),
'last_refreshed_on_time' => time(),
);
return $response;
}
// Call: ?api&groups
route('groups', function() {
$response = auth();
if ($response['auth']) {
$feed_ids = Database::get('db')
->table('feeds')
->findAllByColumn('id');
$response['groups'] = array(
array(
'id' => 1,
'title' => t('All'),
)
);
$response['feeds_groups'] = array(
array(
'group_id' => 1,
'feed_ids' => implode(',', $feed_ids),
)
);
}
response($response);
});
// Call: ?api&feeds
route('feeds', function() {
$response = auth();
if ($response['auth']) {
$response['feeds'] = array();
$feeds = Feed\get_all();
$feed_ids = array();
foreach ($feeds as $feed) {
$response['feeds'][] = array(
'id' => (int) $feed['id'],
'favicon_id' => 1,
'title' => $feed['title'],
'url' => $feed['feed_url'],
'site_url' => $feed['site_url'],
'is_spark' => 0,
'last_updated_on_time' => $feed['last_checked'] ?: time(),
);
$feed_ids[] = $feed['id'];
}
$response['feeds_groups'] = array(
array(
'group_id' => 1,
'feed_ids' => implode(',', $feed_ids),
)
);
}
response($response);
});
// Call: ?api&favicons
route('favicons', function() {
$response = auth();
if ($response['auth']) {
$response['favicons'] = array();
}
response($response);
});
// Call: ?api&items
route('items', function() {
$response = auth();
if ($response['auth']) {
$query = Database::get('db')
->table('items')
->limit(50)
->columns(
'rowid',
'feed_id',
'title',
'author',
'content',
'url',
'updated',
'status',
'bookmark'
);
if (isset($_GET['since_id']) && is_numeric($_GET['since_id'])) {
$items = $query->gt('rowid', $_GET['since_id'])
->asc('rowid');
}
else if (! empty($_GET['with_ids'])) {
$query->in('rowid', explode(',', $_GET['with_ids']));
}
$items = $query->findAll();
$response['items'] = array();
foreach ($items as $item) {
$response['items'][] = array(
'id' => (int) $item['rowid'],
'feed_id' => (int) $item['feed_id'],
'title' => $item['title'],
'author' => $item['author'],
'html' => $item['content'],
'url' => $item['url'],
'is_saved' => (int) $item['bookmark'],
'is_read' => $item['status'] == 'read' ? 1 : 0,
'created_on_time' => $item['updated'],
);
}
$response['total_items'] = Database::get('db')
->table('items')
->neq('status', 'removed')
->count();
}
response($response);
});
// Call: ?api&links
route('links', function() {
$response = auth();
if ($response['auth']) {
$response['links'] = array();
}
response($response);
});
// Call: ?api&unread_item_ids
route('unread_item_ids', function() {
$response = auth();
if ($response['auth']) {
$item_ids = Database::get('db')
->table('items')
->eq('status', 'unread')
->findAllByColumn('rowid');
$response['unread_item_ids'] = implode(',', $item_ids);
}
response($response);
});
// Call: ?api&saved_item_ids
route('saved_item_ids', function() {
$response = auth();
if ($response['auth']) {
$item_ids = Database::get('db')
->table('items')
->eq('bookmark', 1)
->findAllByColumn('rowid');
$response['saved_item_ids'] = implode(',', $item_ids);
}
response($response);
});
// handle write items
route('write_items', function() {
$response = auth();
if ($response['auth']) {
$query = Database::get('db')
->table('items')
->eq('rowid', $_POST['id']);
if ($_POST['as'] === 'saved') {
$query->update(array('bookmark' => 1));
}
else if ($_POST['as'] === 'unsaved') {
$query->update(array('bookmark' => 0));
}
else if ($_POST['as'] === 'read') {
$query->update(array('status' => 'read'));
}
else if ($_POST['as'] === 'unread') {
$query->update(array('status' => 'unread'));
}
}
response($response);
});
// handle write feeds
route('write_feeds', function() {
$response = auth();
if ($response['auth']) {
Database::get('db')
->table('items')
->eq('feed_id', $_POST['id'])
->lte('updated', $_POST['before'])
->update(array('status' => $_POST['as'] === 'read' ? 'read' : 'unread'));
}
response($response);
});
// handle write groups
route('write_groups', function() {
$response = auth();
if ($response['auth']) {
Database::get('db')
->table('items')
->lte('updated', $_POST['before'])
->update(array('status' => $_POST['as'] === 'read' ? 'read' : 'unread'));
}
response($response);
});
foreach (array_keys($_GET) as $action) {
route($action);
}
if (! empty($_POST['mark']) && ! empty($_POST['as'])
&& ! is_null(filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT, array('options' => array('default' => NULL,'min_range' => -1)))) ){
if ($_POST['mark'] === 'item') {
route('write_items');
}
else if ($_POST['mark'] === 'feed' && ! empty($_POST['before'])) {
route('write_feeds');
}
else if ($_POST['mark'] === 'group' && ! empty($_POST['before'])) {
route('write_groups');
}
}
response(auth());