|
|
@@ -216,9 +216,13 @@ function token($conf) {
|
|
|
function checkToken($conf, $token) {
|
|
|
//http://code.google.com/p/google-reader-api/wiki/ActionToken
|
|
|
$user = Minz_Session::param('currentUser', '_');
|
|
|
+ if ($user !== '_' && $token == '') {
|
|
|
+ return true; //FeedMe //TODO: Check security consequences
|
|
|
+ }
|
|
|
if ($token === str_pad(sha1(FreshRSS_Context::$system_conf->salt . $user . $conf->apiPasswordHash), 57, 'Z')) {
|
|
|
return true;
|
|
|
}
|
|
|
+ Minz_Log::warning('Invalid POST token: ' . $token, API_LOG);
|
|
|
unauthorized();
|
|
|
}
|
|
|
|
|
|
@@ -266,6 +270,8 @@ function subscriptionList() {
|
|
|
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
|
|
$salt = FreshRSS_Context::$system_conf->salt;
|
|
|
+ $faviconsUrl = Minz_Url::display('/f.php?', '', true);
|
|
|
+ $faviconsUrl = str_replace('/api/greader.php/reader/api/0/subscription', '', $faviconsUrl); //Security if base_url is not set properly
|
|
|
$subscriptions = array();
|
|
|
|
|
|
foreach ($res as $line) {
|
|
|
@@ -282,7 +288,7 @@ function subscriptionList() {
|
|
|
//'firstitemmsec' => 0,
|
|
|
'url' => $line['url'],
|
|
|
'htmlUrl' => $line['website'],
|
|
|
- 'iconUrl' => Minz_Url::display('/f.php?' . hash('crc32b', $salt . $line['url']), '', true),
|
|
|
+ 'iconUrl' => $faviconsUrl . hash('crc32b', $salt . $line['url']),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -324,6 +330,9 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = '
|
|
|
$addCatId = 1; //Default category
|
|
|
}
|
|
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
|
|
+ if (!is_array($streamNames) || count($streamNames) < 1) {
|
|
|
+ badRequest();
|
|
|
+ }
|
|
|
for ($i = count($streamNames) - 1; $i >= 0; $i--) {
|
|
|
$streamName = $streamNames[$i]; //feed/http://example.net/sample.xml ; feed/338
|
|
|
if (strpos($streamName, 'feed/') === 0) {
|
|
|
@@ -435,6 +444,51 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl
|
|
|
exit();
|
|
|
}
|
|
|
|
|
|
+function entriesToArray($entries) {
|
|
|
+ $items = array();
|
|
|
+ foreach ($entries as $entry) {
|
|
|
+ $f_id = $entry->feed();
|
|
|
+ if (isset($arrayFeedCategoryNames[$f_id])) {
|
|
|
+ $c_name = $arrayFeedCategoryNames[$f_id]['c_name'];
|
|
|
+ $f_name = $arrayFeedCategoryNames[$f_id]['name'];
|
|
|
+ } else {
|
|
|
+ $c_name = '_';
|
|
|
+ $f_name = '_';
|
|
|
+ }
|
|
|
+ $item = array(
|
|
|
+ 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId
|
|
|
+ 'crawlTimeMsec' => substr($entry->id(), 0, -3),
|
|
|
+ 'timestampUsec' => '' . $entry->id(), //EasyRSS
|
|
|
+ 'published' => $entry->date(true),
|
|
|
+ 'title' => $entry->title(),
|
|
|
+ 'summary' => array('content' => $entry->content()),
|
|
|
+ 'alternate' => array(
|
|
|
+ array('href' => htmlspecialchars_decode($entry->link(), ENT_QUOTES)),
|
|
|
+ ),
|
|
|
+ 'categories' => array(
|
|
|
+ 'user/-/state/com.google/reading-list',
|
|
|
+ 'user/-/label/' . $c_name,
|
|
|
+ ),
|
|
|
+ 'origin' => array(
|
|
|
+ 'streamId' => 'feed/' . $f_id,
|
|
|
+ 'title' => $f_name, //EasyRSS
|
|
|
+ //'htmlUrl' => $line['f_website'],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ if ($entry->author() != '') {
|
|
|
+ $item['author'] = $entry->author();
|
|
|
+ }
|
|
|
+ if ($entry->isRead()) {
|
|
|
+ $item['categories'][] = 'user/-/state/com.google/read';
|
|
|
+ }
|
|
|
+ if ($entry->isFavorite()) {
|
|
|
+ $item['categories'][] = 'user/-/state/com.google/starred';
|
|
|
+ }
|
|
|
+ $items[] = $item;
|
|
|
+ }
|
|
|
+ return $items;
|
|
|
+}
|
|
|
+
|
|
|
function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) {
|
|
|
//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI
|
|
|
//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed
|
|
|
@@ -476,57 +530,18 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- if (!empty($continuation)) {
|
|
|
+ if ($continuation != '') {
|
|
|
$count++; //Shift by one element
|
|
|
}
|
|
|
|
|
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
|
|
$entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, new FreshRSS_Search(''), $start_time);
|
|
|
|
|
|
- $items = array();
|
|
|
- foreach ($entries as $entry) {
|
|
|
- $f_id = $entry->feed();
|
|
|
- if (isset($arrayFeedCategoryNames[$f_id])) {
|
|
|
- $c_name = $arrayFeedCategoryNames[$f_id]['c_name'];
|
|
|
- $f_name = $arrayFeedCategoryNames[$f_id]['name'];
|
|
|
- } else {
|
|
|
- $c_name = '_';
|
|
|
- $f_name = '_';
|
|
|
- }
|
|
|
- $item = array(
|
|
|
- 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId
|
|
|
- 'crawlTimeMsec' => substr($entry->id(), 0, -3),
|
|
|
- 'timestampUsec' => '' . $entry->id(), //EasyRSS
|
|
|
- 'published' => $entry->date(true),
|
|
|
- 'title' => $entry->title(),
|
|
|
- 'summary' => array('content' => $entry->content()),
|
|
|
- 'alternate' => array(
|
|
|
- array('href' => htmlspecialchars_decode($entry->link(), ENT_QUOTES)),
|
|
|
- ),
|
|
|
- 'categories' => array(
|
|
|
- 'user/-/state/com.google/reading-list',
|
|
|
- 'user/-/label/' . $c_name,
|
|
|
- ),
|
|
|
- 'origin' => array(
|
|
|
- 'streamId' => 'feed/' . $f_id,
|
|
|
- 'title' => $f_name, //EasyRSS
|
|
|
- //'htmlUrl' => $line['f_website'],
|
|
|
- ),
|
|
|
- );
|
|
|
- if ($entry->author() != '') {
|
|
|
- $item['author'] = $entry->author();
|
|
|
- }
|
|
|
- if ($entry->isRead()) {
|
|
|
- $item['categories'][] = 'user/-/state/com.google/read';
|
|
|
- }
|
|
|
- if ($entry->isFavorite()) {
|
|
|
- $item['categories'][] = 'user/-/state/com.google/starred';
|
|
|
- }
|
|
|
- $items[] = $item;
|
|
|
- }
|
|
|
+ $items = entriesToArray($entries);
|
|
|
|
|
|
- if (!empty($continuation)) {
|
|
|
+ if ($continuation != '') {
|
|
|
array_shift($items); //Discard first element that was already sent in the previous response
|
|
|
+ $count--;
|
|
|
}
|
|
|
|
|
|
$response = array(
|
|
|
@@ -534,15 +549,18 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex
|
|
|
'updated' => time(),
|
|
|
'items' => $items,
|
|
|
);
|
|
|
- if ((count($entries) >= $count) && (!empty($entry))) {
|
|
|
- $response['continuation'] = $entry->id();
|
|
|
+ if (count($entries) >= $count) {
|
|
|
+ $entry = end($entries);
|
|
|
+ if ($entry != false) {
|
|
|
+ $response['continuation'] = $entry->id();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
echo json_encode($response), "\n";
|
|
|
exit();
|
|
|
}
|
|
|
|
|
|
-function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target) {
|
|
|
+function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target, $continuation) {
|
|
|
//http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds
|
|
|
//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI
|
|
|
//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed
|
|
|
@@ -572,8 +590,17 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
+ if ($continuation != '') {
|
|
|
+ $count++; //Shift by one element
|
|
|
+ }
|
|
|
+
|
|
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
|
|
- $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', new FreshRSS_Search(''), $start_time);
|
|
|
+ $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, new FreshRSS_Search(''), $start_time);
|
|
|
+
|
|
|
+ if ($continuation != '') {
|
|
|
+ array_shift($ids); //Discard first element that was already sent in the previous response
|
|
|
+ $count--;
|
|
|
+ }
|
|
|
|
|
|
if (empty($ids)) { //For News+ bug https://github.com/noinnion/newsplus/issues/84#issuecomment-57834632
|
|
|
$ids[] = 0;
|
|
|
@@ -585,9 +612,39 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- echo json_encode(array(
|
|
|
+ $response = array(
|
|
|
'itemRefs' => $itemRefs,
|
|
|
- )), "\n";
|
|
|
+ );
|
|
|
+ if (count($ids) >= $count) {
|
|
|
+ $id = end($ids);
|
|
|
+ if ($id != false) {
|
|
|
+ $response['continuation'] = $id;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ echo json_encode($response), "\n";
|
|
|
+ exit();
|
|
|
+}
|
|
|
+
|
|
|
+function streamContentsItems($e_ids, $order) {
|
|
|
+ header('Content-Type: application/json; charset=UTF-8');
|
|
|
+
|
|
|
+ foreach ($e_ids as $i => $e_id) {
|
|
|
+ $e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/'
|
|
|
+ }
|
|
|
+
|
|
|
+ $entryDAO = FreshRSS_Factory::createEntryDao();
|
|
|
+ $entries = $entryDAO->listByIds($e_ids, $order === 'o' ? 'ASC' : 'DESC');
|
|
|
+
|
|
|
+ $items = entriesToArray($entries);
|
|
|
+
|
|
|
+ $response = array(
|
|
|
+ 'id' => 'user/-/state/com.google/reading-list',
|
|
|
+ 'updated' => time(),
|
|
|
+ 'items' => $items,
|
|
|
+ );
|
|
|
+
|
|
|
+ echo json_encode($response), "\n";
|
|
|
exit();
|
|
|
}
|
|
|
|
|
|
@@ -726,7 +783,10 @@ if (count($pathInfos) < 3) {
|
|
|
* all items in a timestamp range, it will have a continuation attribute.
|
|
|
* The same request can be re-issued with the value of that attribute put
|
|
|
* in this parameter to get more items */
|
|
|
- $continuation = isset($_GET['c']) ? $_GET['c'] : '';
|
|
|
+ $continuation = isset($_GET['c']) ? trim($_GET['c']) : '';
|
|
|
+ if (!ctype_digit($continuation)) {
|
|
|
+ $continuation = '';
|
|
|
+ }
|
|
|
if (isset($pathInfos[5]) && $pathInfos[5] === 'contents' && isset($pathInfos[6])) {
|
|
|
if (isset($pathInfos[7])) {
|
|
|
if ($pathInfos[6] === 'feed') {
|
|
|
@@ -755,7 +815,10 @@ if (count($pathInfos) < 3) {
|
|
|
* be repeated to fetch the item IDs from multiple streams at once
|
|
|
* (more efficient from a backend perspective than multiple requests). */
|
|
|
$streamId = $_GET['s'];
|
|
|
- streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target);
|
|
|
+ streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target, $continuation);
|
|
|
+ } else if ($pathInfos[6] === 'contents' && isset($_POST['i'])) { //FeedMe
|
|
|
+ $e_ids = multiplePosts('i'); //item IDs
|
|
|
+ streamContentsItems($e_ids, $order);
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
@@ -775,16 +838,16 @@ if (count($pathInfos) < 3) {
|
|
|
subscriptionList($_GET['output']);
|
|
|
break;
|
|
|
case 'edit':
|
|
|
- if (isset($_POST['s']) && isset($_POST['ac'])) {
|
|
|
+ if (isset($_REQUEST['s']) && isset($_REQUEST['ac'])) {
|
|
|
//StreamId to operate on. The parameter may be repeated to edit multiple subscriptions at once
|
|
|
- $streamNames = multiplePosts('s');
|
|
|
+ $streamNames = empty($_POST['s']) && isset($_GET['s']) ? array($_GET['s']) : multiplePosts('s');
|
|
|
/* Title to use for the subscription. For the `subscribe` action,
|
|
|
* if not specified then the feed's current title will be used. Can
|
|
|
* be used with the `edit` action to rename a subscription */
|
|
|
- $titles = multiplePosts('t');
|
|
|
- $action = $_POST['ac']; //Action to perform on the given StreamId. Possible values are `subscribe`, `unsubscribe` and `edit`
|
|
|
- $add = isset($_POST['a']) ? $_POST['a'] : ''; //StreamId to add the subscription to (generally a user label)
|
|
|
- $remove = isset($_POST['r']) ? $_POST['r'] : ''; //StreamId to remove the subscription from (generally a user label)
|
|
|
+ $titles = empty($_POST['t']) && isset($_GET['t']) ? array($_GET['t']) : multiplePosts('t');
|
|
|
+ $action = $_REQUEST['ac']; //Action to perform on the given StreamId. Possible values are `subscribe`, `unsubscribe` and `edit`
|
|
|
+ $add = isset($_REQUEST['a']) ? $_REQUEST['a'] : ''; //StreamId to add the subscription to (generally a user label)
|
|
|
+ $remove = isset($_REQUEST['r']) ? $_REQUEST['r'] : ''; //StreamId to remove the subscription from (generally a user label)
|
|
|
subscriptionEdit($streamNames, $titles, $action, $add, $remove);
|
|
|
}
|
|
|
break;
|