From 469d601bcbe83eb800552ca6327dac7732763c75 Mon Sep 17 00:00:00 2001 From: Frans de Jonge Date: Fri, 22 Dec 2017 12:02:06 +0100 Subject: [doc] Editing for better style (#1736) * Also removed references to Persona authentication. * Changed code comment about Persona because it's for HTTP auth in general. See https://github.com/FreshRSS/FreshRSS/commit/3d876091e1268e3ccd5036449a4deb5134936206 and https://github.com/FreshRSS/FreshRSS/issues/358#issuecomment-31931484 --- docs/fr/users/03_Main_view.md | 2 +- docs/fr/users/07_Frequently_Asked_Questions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/fr') diff --git a/docs/fr/users/03_Main_view.md b/docs/fr/users/03_Main_view.md index 744141b7d..ebf782136 100644 --- a/docs/fr/users/03_Main_view.md +++ b/docs/fr/users/03_Main_view.md @@ -45,7 +45,7 @@ C’est le cas le plus simple, puisque votre instance est publique, vous n’ave 0 * * * * curl 'https://votre.serveur.net/FreshRSS/p/i/?c=feed&a=actualize' ``` -##### Authentification par formulaire ou Persona +##### Authentification par formulaire Dans ces cas-là, si vous avez autorisé la lecture anonyme des articles, vous pouvez aussi permettre à n’importe qui de rafraîchir vos flux (« Autoriser le rafraîchissement anonyme des flux »). diff --git a/docs/fr/users/07_Frequently_Asked_Questions.md b/docs/fr/users/07_Frequently_Asked_Questions.md index 9dc80b2e4..1f4d059ce 100644 --- a/docs/fr/users/07_Frequently_Asked_Questions.md +++ b/docs/fr/users/07_Frequently_Asked_Questions.md @@ -4,7 +4,7 @@ Il est possible que nous n'ayons pas répondu à toutes vos questions dans les p Bien entendu, le ```/i``` n'est pas là pour faire joli ! Il s'agit d'une question de performances et de praticité : -* Cela permet de servir les icônes, images, styles, scripts sans cookie. Sans cela, ces fichiers seraient souvent re-téléchargés, en particulier lorsque Persona ou le formulaire de connexion sont utilisés. De plus, les requêtes vers ces ressources seraient plus lourdes. +* Cela permet de servir les icônes, images, styles, scripts sans cookie. Sans cela, ces fichiers seraient souvent re-téléchargés, en particulier lorsque le formulaire de connexion est utilisé. De plus, les requêtes vers ces ressources seraient plus lourdes. * La racine publique ```./p/``` peut être servie sans restriction d'accès HTTP (qui peut avantageusement être mise en place dans ```./p/i/```). * Cela permet d'éviter des problèmes pour des fichiers qui doivent être publics pour bien fonctionner, comme ```favicon.ico```, ```robots.txt```, etc. * Cela permet aussi d'avoir un logo FreshRSS plutôt qu'une page blanche pour accueillir l'utilisateur par exemple dans le cas de la restriction d'accès HTTP ou lors de l'attente du chargement plus lourd du reste de l'interface. -- cgit v1.2.3 From 79f8b440d1303a0cd377cabe18750a2a552919e3 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 8 Feb 2018 20:11:05 +0100 Subject: API /reader/api/0/stream/items/contents (#1774) * API /reader/api/0/stream/items/contents For FeedMe * Fix continuation * Continuation in stream/items/ids * Fix multiple continuations * Allow empty POST tokens For FeedMe. This token is not used by e.g. The Old Reader API. There is the Authorization header anyway. TODO: Check security consequences * API compatibility FeedMe: add/remove feed FeedMe uses GET for some parameters typically given by POST * A bit of sanitization * Links to FeedMe * API favicons more robust when base_url is not set * Changelog FeedMe --- CHANGELOG.md | 4 +- README.fr.md | 1 + README.md | 1 + app/Models/EntryDAO.php | 23 ++++- docs/en/users/06_Mobile_access.md | 1 + docs/fr/users/06_Mobile_access.md | 1 + p/api/greader.php | 179 ++++++++++++++++++++++++++------------ 7 files changed, 149 insertions(+), 61 deletions(-) (limited to 'docs/fr') diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ed835205..2908cd616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # FreshRSS changelog -## 2018-XX-XX FreshRSS 1.9.1-dev +## 2018-02-XX FreshRSS 1.9.1-dev +* API + * Add compatibility with FeedMe 3.5.3+ on Android [#1774](https://github.com/FreshRSS/FreshRSS/pull/1774) * Features * Ability to pause feeds, and to hide them from categories [#1750](https://github.com/FreshRSS/FreshRSS/pull/1750) * Security diff --git a/README.fr.md b/README.fr.md index b888f7738..4421f92f0 100644 --- a/README.fr.md +++ b/README.fr.md @@ -177,6 +177,7 @@ Tout client supportant une API de type Google Reader. Sélection : * Android * [News+](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus) avec [News+ Google Reader extension](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader) (Propriétaire) + * [FeedMe 3.5.3+](https://play.google.com/store/apps/details?id=com.seazon.feedme) (Propriétaire) * [EasyRSS](https://github.com/Alkarex/EasyRSS) (Libre, [F-Droid](https://f-droid.org/fr/packages/org.freshrss.easyrss/)) * GNU/Linux * [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Libre) diff --git a/README.md b/README.md index c971675fa..a95593651 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ Any client supporting a Google Reader-like API. Selection: * Android * [News+](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus) with [News+ Google Reader extension](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader) (Closed source) + * [FeedMe 3.5.3+](https://play.google.com/store/apps/details?id=com.seazon.feedme) (Closed source) * [EasyRSS](https://github.com/Alkarex/EasyRSS) (Open source, [F-Droid](https://f-droid.org/packages/org.freshrss.easyrss/)) * GNU/Linux * [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Open source) diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 9e291f4ed..70135e7a0 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -628,10 +628,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $firstId = $order === 'DESC' ? '9000000000'. '000000' : '0'; }*/ if ($firstId !== '') { - $search .= 'AND ' . $alias . 'id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; + $search .= 'AND ' . $alias . 'id ' . ($order === 'DESC' ? '<=' : '>=') . ' ? '; + $values[] = $firstId; } if ($date_min > 0) { - $search .= 'AND ' . $alias . 'id >= ' . $date_min . '000000 '; + $search .= 'AND ' . $alias . 'id >= ? '; + $values[] = $date_min . '000000'; } if ($filter) { if ($filter->getMinDate()) { @@ -781,6 +783,23 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC)); } + public function listByIds($ids, $order = 'DESC') { + if (count($ids) < 1) { + return array(); + } + + $sql = 'SELECT id, guid, title, author, ' + . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') + . ', link, date, is_read, is_favorite, id_feed, tags ' + . 'FROM `' . $this->prefix . 'entry` ' + . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?) ' + . 'ORDER BY id ' . $order; + + $stm = $this->bd->prepare($sql); + $stm->execute($ids); + return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC)); + } + public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { //For API list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min); diff --git a/docs/en/users/06_Mobile_access.md b/docs/en/users/06_Mobile_access.md index 3472172b0..166985585 100644 --- a/docs/en/users/06_Mobile_access.md +++ b/docs/en/users/06_Mobile_access.md @@ -46,6 +46,7 @@ This page assumes you have completed the [server setup](../admins/02_Installatio 7. Pick a client supporting a Google Reader-like API. Selection: * Android * [News+](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus) with [News+ Google Reader extension](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader) (Closed source) + * [FeedMe 3.5.3+](https://play.google.com/store/apps/details?id=com.seazon.feedme) (Closed source) * [EasyRSS](https://github.com/Alkarex/EasyRSS) (Open source, [F-Droid](https://f-droid.org/packages/org.freshrss.easyrss/)) * Linux * [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Open source) diff --git a/docs/fr/users/06_Mobile_access.md b/docs/fr/users/06_Mobile_access.md index 185c94098..8ef3d038a 100644 --- a/docs/fr/users/06_Mobile_access.md +++ b/docs/fr/users/06_Mobile_access.md @@ -44,6 +44,7 @@ Tout client supportant une API de type Google Reader. Sélection : * Android * [News+](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus) avec [News+ Google Reader extension](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader) (Propriétaire) + * [FeedMe 3.5.3+](https://play.google.com/store/apps/details?id=com.seazon.feedme) (Propriétaire) * [EasyRSS](https://github.com/Alkarex/EasyRSS) (Libre, F-Droid) * Linux * [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Libre) diff --git a/p/api/greader.php b/p/api/greader.php index 72f886190..4add26bd8 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -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; -- cgit v1.2.3