From 256c8613a4046931dcd28ab22b6aebe8752a98c2 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 15 May 2015 03:21:36 +0200 Subject: [PATCH 01/10] First draft of PubSubHubbub https://github.com/FreshRSS/FreshRSS/issues/312 Requires setting base_url in config.php. Currently using the filesystem (no change to the database) --- app/Controllers/feedController.php | 55 ++++++++----- app/Models/Feed.php | 69 +++++++++++++++- constants.php | 1 + data/PubSubHubbub/feeds/.gitignore | 1 + data/PubSubHubbub/feeds/README.md | 12 +++ data/PubSubHubbub/secrets/.gitignore | 1 + data/PubSubHubbub/secrets/README.md | 4 + data/config.default.php | 8 +- lib/lib_rss.php | 9 +++ p/api/pshb.php | 116 +++++++++++++++++++++++++++ 10 files changed, 252 insertions(+), 24 deletions(-) create mode 100644 data/PubSubHubbub/feeds/.gitignore create mode 100644 data/PubSubHubbub/feeds/README.md create mode 100644 data/PubSubHubbub/secrets/.gitignore create mode 100644 data/PubSubHubbub/secrets/README.md create mode 100644 p/api/pshb.php diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 0443b4159..9117da639 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -168,6 +168,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { // Ok, feed has been added in database. Now we have to refresh entries. $feed->_id($id); $feed->faviconPrepare(); + $feed->pubSubHubbubPrepare(); $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; @@ -261,12 +262,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController { * This action actualizes entries from one or several feeds. * * Parameters are: - * - id (default: false) + * - id (default: false): Feed ID + * - url (default: false): Feed URL * - force (default: false) - * If id is not specified, all the feeds are actualized. But if force is + * If id and url are not specified, all the feeds are actualized. But if force is * false, process stops at 10 feeds to avoid time execution problem. */ - public function actualizeAction() { + public function actualizeAction($simplePie = null) { @set_time_limit(300); $feedDAO = FreshRSS_Factory::createFeedDao(); @@ -274,14 +276,15 @@ class FreshRSS_feed_Controller extends Minz_ActionController { Minz_Session::_param('actualize_feeds', false); $id = Minz_Request::param('id'); + $url = Minz_Request::param('url'); $force = Minz_Request::param('force'); // Create a list of feeds to actualize. // If id is set and valid, corresponding feed is added to the list but // alone in order to automatize further process. $feeds = array(); - if ($id) { - $feed = $feedDAO->searchById($id); + if ($id || $url) { + $feed = $id ? $feedDAO->searchById($id) : $feedDAO->searchByUrl($url); if ($feed) { $feeds[] = $feed; } @@ -302,8 +305,11 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } try { - // Load entries - $feed->load(false); + if ($simplePie) { + $feed->loadEntries($simplePie); //Used by PubSubHubbub + } else { + $feed->load(false); + } } catch (FreshRSS_Feed_Exception $e) { Minz_Log::notice($e->getMessage()); $feedDAO->updateLastUpdate($feed->id(), true); @@ -404,7 +410,16 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); } - $feed->faviconPrepare(); + if ($simplePie === null) { + $feed->faviconPrepare(); + if ($feed->url() === 'http://push-pub.appspot.com/feed') { + $secret = $feed->pubSubHubbubPrepare(); + if ($secret != '') { + Minz_Log::debug('PubSubHubbub subscribe ' . $feed->url()); + $feed->pubSubHubbubSubscribe(true, $secret); + } + } + } $feed->unlock(); $updated_feeds++; unset($feed); @@ -427,20 +442,20 @@ class FreshRSS_feed_Controller extends Minz_ActionController { Minz_Session::_param('notification', $notif); // No layout in ajax request. $this->view->_useLayout(false); - return; - } - - // Redirect to the main page with correct notification. - if ($updated_feeds === 1) { - $feed = reset($feeds); - Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array( - 'params' => array('get' => 'f_' . $feed->id()) - )); - } elseif ($updated_feeds > 1) { - Minz_Request::good(_t('feedback.sub.feed.n_actualized', $updated_feeds), array()); } else { - Minz_Request::good(_t('feedback.sub.feed.no_refresh'), array()); + // Redirect to the main page with correct notification. + if ($updated_feeds === 1) { + $feed = reset($feeds); + Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array( + 'params' => array('get' => 'f_' . $feed->id()) + )); + } elseif ($updated_feeds > 1) { + Minz_Request::good(_t('feedback.sub.feed.n_actualized', $updated_feeds), array()); + } else { + Minz_Request::good(_t('feedback.sub.feed.no_refresh'), array()); + } } + return $updated_feeds; } /** diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 85fb173ec..dcf083ea8 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -19,6 +19,8 @@ class FreshRSS_Feed extends Minz_Model { private $ttl = -2; private $hash = null; private $lockPath = ''; + private $hubUrl = ''; + private $selfUrl = ''; public function __construct($url, $validate=true) { if ($validate) { @@ -226,6 +228,11 @@ class FreshRSS_Feed extends Minz_Model { throw new FreshRSS_Feed_Exception(($errorMessage == '' ? 'Feed error' : $errorMessage) . ' [' . $url . ']'); } + $links = $feed->get_links('self'); + $this->selfUrl = isset($links[0]) ? $links[0] : null; + $links = $feed->get_links('hub'); + $this->hubUrl = isset($links[0]) ? $links[0] : null; + if ($loadDetails) { // si on a utilisé l'auto-discover, notre url va avoir changé $subscribe_url = $feed->subscribe_url(false); @@ -259,7 +266,7 @@ class FreshRSS_Feed extends Minz_Model { } } - private function loadEntries($feed) { + public function loadEntries($feed) { $entries = array(); foreach ($feed->get_items() as $item) { @@ -333,4 +340,64 @@ class FreshRSS_Feed extends Minz_Model { function unlock() { @unlink($this->lockPath); } + + // + + function pubSubHubbubPrepare() { + $secret = ''; + if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { + $path = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl); + if (!file_exists($path . '/hub.txt')) { + @mkdir($path, 0777, true); + file_put_contents($path . '/hub.txt', $this->hubUrl); + $secret = sha1(FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true)); + file_put_contents($path . '/secret.txt', $secret); + @mkdir(PSHB_PATH . '/secrets/'); + file_put_contents(PSHB_PATH . '/secrets/' . $secret . '.txt', base64url_encode($this->selfUrl)); + Minz_Log::notice('PubSubHubbub prepared for ' . $this->url); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . + 'PubSubHubbub prepared for ' . $this->url . "\n", FILE_APPEND); + } + $path .= '/' . base64url_encode($this->url); + $currentUser = Minz_Session::param('currentUser'); + if (ctype_alnum($currentUser) && !file_exists($path . '/' . $currentUser . '.txt')) { + @mkdir($path, 0777, true); + touch($path . '/' . $currentUser . '.txt'); + } + } + return $secret; + } + + //Parameter true to subscribe, false to unsubscribe. + function pubSubHubbubSubscribe($state, $secret = '') { + if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { + $callbackUrl = checkUrl(FreshRSS_Context::$system_conf->base_url . 'api/pshb.php?s=' . $secret); + if ($callbackUrl == '') { + return false; + } + + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $this->hubUrl, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_USERAGENT => _t('gen.freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')', + CURLOPT_POSTFIELDS => 'hub.verify=sync' + . '&hub.mode=' . ($state ? 'subscribe' : 'unsubscribe') + . '&hub.topic=' . urlencode($this->selfUrl) + . '&hub.callback=' . urlencode($callbackUrl) + ) + ); + $response = curl_exec($ch); + $info = curl_getinfo($ch); + + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . + 'PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $this->selfUrl . + ' with callback ' . $callbackUrl . ': ' . $info['http_code'] . ' ' . $response . "\n", FILE_APPEND); + return substr($info['http_code'], 0, 1) == '2'; + } + return false; + } + + // } diff --git a/constants.php b/constants.php index b20bf0710..5bb410e29 100644 --- a/constants.php +++ b/constants.php @@ -18,6 +18,7 @@ define('FRESHRSS_PATH', dirname(__FILE__)); define('UPDATE_FILENAME', DATA_PATH . '/update.php'); define('USERS_PATH', DATA_PATH . '/users'); define('CACHE_PATH', DATA_PATH . '/cache'); + define('PSHB_PATH', DATA_PATH . '/PubSubHubbub'); define('LIB_PATH', FRESHRSS_PATH . '/lib'); define('APP_PATH', FRESHRSS_PATH . '/app'); diff --git a/data/PubSubHubbub/feeds/.gitignore b/data/PubSubHubbub/feeds/.gitignore new file mode 100644 index 000000000..150f68c80 --- /dev/null +++ b/data/PubSubHubbub/feeds/.gitignore @@ -0,0 +1 @@ +*/* diff --git a/data/PubSubHubbub/feeds/README.md b/data/PubSubHubbub/feeds/README.md new file mode 100644 index 000000000..15fa8e521 --- /dev/null +++ b/data/PubSubHubbub/feeds/README.md @@ -0,0 +1,12 @@ +List of canonical URLS of the various feeds users have subscribed to. +Several feeds can share the same canonical URL (rel="self"). +Several users can have subscribed to the same feed. + +* ./base64url(canonicalUrl)/ + * ./secret.txt + * ./base64url(feedUrl1)/ + * ./user1.txt + * ./user2.txt + * ./base64url(feedUrl2)/ + * ./user3.txt + * ./user4.txt diff --git a/data/PubSubHubbub/secrets/.gitignore b/data/PubSubHubbub/secrets/.gitignore new file mode 100644 index 000000000..2211df63d --- /dev/null +++ b/data/PubSubHubbub/secrets/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/data/PubSubHubbub/secrets/README.md b/data/PubSubHubbub/secrets/README.md new file mode 100644 index 000000000..ad8158839 --- /dev/null +++ b/data/PubSubHubbub/secrets/README.md @@ -0,0 +1,4 @@ +List of secrets given to PubSubHubbub hubs + +* ./sha1(random + salt).txt + * base64url(canonicalUrl) diff --git a/data/config.default.php b/data/config.default.php index 8be203d36..80d331df7 100644 --- a/data/config.default.php +++ b/data/config.default.php @@ -11,9 +11,11 @@ return array( # Used to make crypto more unique. Generated during install. 'salt' => '', - # Leave empty for most cases. - # Ability to override the address of the FreshRSS instance, - # used when building absolute URLs. + # Specify address of the FreshRSS instance, + # used when building absolute URLs, e.g. for PubSubHubbub. + # Examples: + # https://example.net/FreshRSS/p/ + # https://freshrss.example.net/ 'base_url' => '', # Natural language of the user interface, e.g. `en`, `fr`. diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 6342011c8..191a58f35 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -446,3 +446,12 @@ function array_push_unique(&$array, $value) { function array_remove(&$array, $value) { $array = array_diff($array, array($value)); } + +//RFC 4648 +function base64url_encode($data) { + return strtr(rtrim(base64_encode($data), '='), '+/', '-_'); +} +//RFC 4648 +function base64url_decode($data) { + return base64_decode(strtr($data, '-_', '+/')); +} diff --git a/p/api/pshb.php b/p/api/pshb.php new file mode 100644 index 000000000..bcb8341b1 --- /dev/null +++ b/p/api/pshb.php @@ -0,0 +1,116 @@ + $_GET, '_POST' => $_POST, 'INPUT' => $ORIGINAL_INPUT), true)); + +$secret = isset($_GET['s']) ? substr($_GET['s'], 0, 128) : ''; +if (!ctype_xdigit($secret)) { + header('HTTP/1.1 422 Unprocessable Entity'); + die('Invalid feed secret format!'); +} +chdir(PSHB_PATH); +$canonical64 = @file_get_contents('secrets/' . $secret . '.txt'); +if ($canonical64 === false) { + header('HTTP/1.1 404 Not Found'); + logMe('Feed secret not found!: ' . $secret); + die('Feed secret not found!'); +} +$canonical64 = trim($canonical64); +if (!preg_match('/^[A-Za-z0-9_-]+$/D', $canonical64)) { + header('HTTP/1.1 500 Internal Server Error'); + logMe('Invalid secret reference!: ' . $canonical64); + die('Invalid secret reference!'); +} +$secret2 = @file_get_contents('feeds/' . $canonical64 . '/secret.txt'); +if ($secret2 === false) { + header('HTTP/1.1 404 Not Found'); + //@unlink('secrets/' . $secret . '.txt'); + logMe('Feed reverse secret not found!: ' . $canonical64); + die('Feed reverse secret not found!'); +} +if ($secret !== $secret2) { + header('HTTP/1.1 500 Internal Server Error'); + logMe('Invalid secret cross-check!: ' . $secret); + die('Invalid secret cross-check!'); +} +chdir('feeds/' . $canonical64); +$users = glob('*/*.txt', GLOB_NOSORT); +if (empty($users)) { + header('HTTP/1.1 410 Gone'); + logMe('Nobody is subscribed to this feed anymore!: ' . $canonical64); + die('Nobody is subscribed to this feed anymore!'); +} + +if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] === 'subscribe') { + //TODO: hub_lease_seconds + exit(isset($_REQUEST['hub_challenge']) ? $_REQUEST['hub_challenge'] : ''); +} + +Minz_Configuration::register('system', DATA_PATH . '/config.php', DATA_PATH . '/config.default.php'); +$system_conf = Minz_Configuration::get('system'); +$system_conf->auth_type = 'none'; // avoid necessity to be logged in (not saved!) +Minz_Translate::init('en'); +Minz_Request::_param('ajax', true); +$feedController = new FreshRSS_feed_Controller(); + +$simplePie = customSimplePie(); +$simplePie->set_raw_data($ORIGINAL_INPUT); +$simplePie->init(); +unset($ORIGINAL_INPUT); + +$links = $simplePie->get_links('self'); +$self = isset($links[0]) ? $links[0] : null; + +if ($self !== base64url_decode($canonical64)) { + header('HTTP/1.1 422 Unprocessable Entity'); + logMe('Self URL does not match registered canonical URL!: ' . $self); + die('Self URL does not match registered canonical URL!'); +} +Minz_Request::_param('url', $self); + +$nb = 0; +foreach ($users as $userLine) { + $userLine = strtr($userLine, '\\', '/'); + $userInfos = explode('/', $userLine); + $feedUrl = isset($userInfos[0]) ? base64url_decode($userInfos[0]) : ''; + $username = isset($userInfos[1]) ? basename($userInfos[1], '.txt') : ''; + if (!file_exists(USERS_PATH . '/' . $username . '/config.php')) { + break; + } + + try { + Minz_Session::_param('currentUser', $username); + Minz_Configuration::register('user', + join_path(USERS_PATH, $username, 'config.php'), + join_path(USERS_PATH, '_', 'config.default.php')); + FreshRSS_Context::init(); + if ($feedController->actualizeAction($simplePie) > 0) { + $nb++; + } + } catch (Exception $e) { + logMe($e->getMessage()); + } +} + +$simplePie->__destruct(); +unset($simplePie); + +if ($nb === 0) { + header('HTTP/1.1 410 Gone'); + logMe('Nobody is subscribed to this feed anymore after all!: ' . $self); + die('Nobody is subscribed to this feed anymore after all!'); +} + +logMe($self . ' done: ' . $nb); +exit('Done: ' . $nb . "\n"); From c472569b3861541c322c850c4ff8ca3471572f40 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 15 May 2015 15:34:51 +0200 Subject: [PATCH 02/10] First alpha of PubSubHubbub https://github.com/FreshRSS/FreshRSS/issues/312 Using a white list limited to http://push-pub.appspot.com/feed for alpha testing. --- app/Controllers/feedController.php | 31 ++++++++--- app/Models/Feed.php | 53 +++++++++++++----- data/PubSubHubbub/feeds/README.md | 11 +--- .../PubSubHubbub/{secrets => keys}/.gitignore | 0 data/PubSubHubbub/{secrets => keys}/README.md | 2 +- p/api/pshb.php | 55 +++++++++++-------- 6 files changed, 98 insertions(+), 54 deletions(-) rename data/PubSubHubbub/{secrets => keys}/.gitignore (100%) rename data/PubSubHubbub/{secrets => keys}/README.md (56%) diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 9117da639..0fb4bdf03 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -304,6 +304,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { continue; } + $url = $feed->url(); //For detection of HTTP 301 try { if ($simplePie) { $feed->loadEntries($simplePie); //Used by PubSubHubbub @@ -317,7 +318,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { continue; } - $url = $feed->url(); $feed_history = $feed->keepHistory(); if ($feed_history == -2) { // TODO: -2 must be a constant! @@ -404,19 +404,34 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $entryDAO->commit(); } - if ($feed->url() !== $url) { - // HTTP 301 Moved Permanently + if ($feed->hubUrl() && $feed->selfUrl()) { //selfUrl has priority for PubSubHubbub + if ($feed->selfUrl() !== $url) { //https://code.google.com/p/pubsubhubbub/wiki/MovingFeedsOrChangingHubs + $selfUrl = checkUrl($feed->selfUrl()); + if ($selfUrl) { + Minz_Log::debug('PubSubHubbub unsubscribe ' . $feed->url()); + if (!$feed->pubSubHubbubSubscribe(false)) { //Unsubscribe + Minz_Log::warning('Error while PubSubHubbub unsubscribing from ' . $feed->url()); + } + $feed->_url($selfUrl, false); + Minz_Log::notice('Feed ' . $url . ' canonical address moved to ' . $feed->url()); + $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); + } + } + } + elseif ($feed->url() !== $url) { // HTTP 301 Moved Permanently Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url()); $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); } if ($simplePie === null) { $feed->faviconPrepare(); - if ($feed->url() === 'http://push-pub.appspot.com/feed') { - $secret = $feed->pubSubHubbubPrepare(); - if ($secret != '') { - Minz_Log::debug('PubSubHubbub subscribe ' . $feed->url()); - $feed->pubSubHubbubSubscribe(true, $secret); + if (in_array($feed->url(), array('http://push-pub.appspot.com/feed'))) { //TODO: Remove white-list after testing + Minz_Log::debug('PubSubHubbub match ' . $feed->url()); + if ($feed->pubSubHubbubPrepare()) { + Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url()); + if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe + Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url()); + } } } } diff --git a/app/Models/Feed.php b/app/Models/Feed.php index dcf083ea8..a17cf415d 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -51,6 +51,12 @@ class FreshRSS_Feed extends Minz_Model { public function url() { return $this->url; } + public function selfUrl() { + return $this->selfUrl; + } + public function hubUrl() { + return $this->hubUrl; + } public function category() { return $this->category; } @@ -344,38 +350,59 @@ class FreshRSS_Feed extends Minz_Model { // function pubSubHubbubPrepare() { - $secret = ''; + $key = ''; if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { $path = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl); - if (!file_exists($path . '/hub.txt')) { + if ($hubFile = @file_get_contents($path . '/!hub.json')) { + $hubJson = json_decode($hubFile, true); + if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) { + Minz_Log::warning('Invalid JSON for PubSubHubbub: ' . $this->url); + return false; + } + if (empty($hubJson['lease_end']) || $hubJson['lease_end'] <= time()) { + Minz_Log::warning('PubSubHubbub lease expired: ' . $this->url); + $key = $hubJson['key']; //To renew our lease + } + } else { @mkdir($path, 0777, true); - file_put_contents($path . '/hub.txt', $this->hubUrl); - $secret = sha1(FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true)); - file_put_contents($path . '/secret.txt', $secret); - @mkdir(PSHB_PATH . '/secrets/'); - file_put_contents(PSHB_PATH . '/secrets/' . $secret . '.txt', base64url_encode($this->selfUrl)); + $key = sha1(FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true)); + $hubJson = array( + 'hub' => $this->hubUrl, + 'key' => $key, + ); + file_put_contents($path . '/!hub.json', json_encode($hubJson)); + @mkdir(PSHB_PATH . '/keys/'); + file_put_contents(PSHB_PATH . '/keys/' . $key . '.txt', base64url_encode($this->selfUrl)); Minz_Log::notice('PubSubHubbub prepared for ' . $this->url); file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . 'PubSubHubbub prepared for ' . $this->url . "\n", FILE_APPEND); } - $path .= '/' . base64url_encode($this->url); $currentUser = Minz_Session::param('currentUser'); if (ctype_alnum($currentUser) && !file_exists($path . '/' . $currentUser . '.txt')) { - @mkdir($path, 0777, true); touch($path . '/' . $currentUser . '.txt'); } } - return $secret; + return $key; } //Parameter true to subscribe, false to unsubscribe. - function pubSubHubbubSubscribe($state, $secret = '') { + function pubSubHubbubSubscribe($state) { if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { - $callbackUrl = checkUrl(FreshRSS_Context::$system_conf->base_url . 'api/pshb.php?s=' . $secret); + $hubFile = @file_get_contents(PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl) . '/!hub.json'); + if ($hubFile === false) { + Minz_Log::warning('JSON not found for PubSubHubbub: ' . $this->url); + return false; + } + $hubJson = json_decode($hubFile, true); + if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) { + Minz_Log::warning('Invalid JSON for PubSubHubbub: ' . $this->url); + return false; + } + $callbackUrl = checkUrl(FreshRSS_Context::$system_conf->base_url . 'api/pshb.php?k=' . $hubJson['key']); if ($callbackUrl == '') { + Minz_Log::warning('Invalid callback for PubSubHubbub: ' . $this->url); return false; } - $ch = curl_init(); curl_setopt_array($ch, array( CURLOPT_URL => $this->hubUrl, diff --git a/data/PubSubHubbub/feeds/README.md b/data/PubSubHubbub/feeds/README.md index 15fa8e521..a01a3197f 100644 --- a/data/PubSubHubbub/feeds/README.md +++ b/data/PubSubHubbub/feeds/README.md @@ -1,12 +1,7 @@ List of canonical URLS of the various feeds users have subscribed to. -Several feeds can share the same canonical URL (rel="self"). Several users can have subscribed to the same feed. * ./base64url(canonicalUrl)/ - * ./secret.txt - * ./base64url(feedUrl1)/ - * ./user1.txt - * ./user2.txt - * ./base64url(feedUrl2)/ - * ./user3.txt - * ./user4.txt + * ./!hub.json + * ./user1.txt + * ./user2.txt diff --git a/data/PubSubHubbub/secrets/.gitignore b/data/PubSubHubbub/keys/.gitignore similarity index 100% rename from data/PubSubHubbub/secrets/.gitignore rename to data/PubSubHubbub/keys/.gitignore diff --git a/data/PubSubHubbub/secrets/README.md b/data/PubSubHubbub/keys/README.md similarity index 56% rename from data/PubSubHubbub/secrets/README.md rename to data/PubSubHubbub/keys/README.md index ad8158839..bb1e57cd4 100644 --- a/data/PubSubHubbub/secrets/README.md +++ b/data/PubSubHubbub/keys/README.md @@ -1,4 +1,4 @@ -List of secrets given to PubSubHubbub hubs +List of keys given to PubSubHubbub hubs * ./sha1(random + salt).txt * base64url(canonicalUrl) diff --git a/p/api/pshb.php b/p/api/pshb.php index bcb8341b1..90d4c52bb 100644 --- a/p/api/pshb.php +++ b/p/api/pshb.php @@ -12,40 +12,41 @@ function logMe($text) { $ORIGINAL_INPUT = file_get_contents('php://input', false, null, -1, MAX_PAYLOAD); -logMe(print_r(array('_GET' => $_GET, '_POST' => $_POST, 'INPUT' => $ORIGINAL_INPUT), true)); +logMe(print_r(array('_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, 'INPUT' => $ORIGINAL_INPUT), true)); -$secret = isset($_GET['s']) ? substr($_GET['s'], 0, 128) : ''; -if (!ctype_xdigit($secret)) { +$key = isset($_GET['k']) ? substr($_GET['k'], 0, 128) : ''; +if (!ctype_xdigit($key)) { header('HTTP/1.1 422 Unprocessable Entity'); - die('Invalid feed secret format!'); + die('Invalid feed key format!'); } chdir(PSHB_PATH); -$canonical64 = @file_get_contents('secrets/' . $secret . '.txt'); +$canonical64 = @file_get_contents('keys/' . $key . '.txt'); if ($canonical64 === false) { header('HTTP/1.1 404 Not Found'); - logMe('Feed secret not found!: ' . $secret); - die('Feed secret not found!'); + logMe('Feed key not found!: ' . $key); + die('Feed key not found!'); } $canonical64 = trim($canonical64); if (!preg_match('/^[A-Za-z0-9_-]+$/D', $canonical64)) { header('HTTP/1.1 500 Internal Server Error'); - logMe('Invalid secret reference!: ' . $canonical64); - die('Invalid secret reference!'); + logMe('Invalid key reference!: ' . $canonical64); + die('Invalid key reference!'); } -$secret2 = @file_get_contents('feeds/' . $canonical64 . '/secret.txt'); -if ($secret2 === false) { +$hubFile = @file_get_contents('feeds/' . $canonical64 . '/!hub.json'); +if ($hubFile === false) { header('HTTP/1.1 404 Not Found'); - //@unlink('secrets/' . $secret . '.txt'); - logMe('Feed reverse secret not found!: ' . $canonical64); - die('Feed reverse secret not found!'); + //@unlink('keys/' . $key . '.txt'); + logMe('Feed info not found!: ' . $canonical64); + die('Feed info not found!'); } -if ($secret !== $secret2) { +$hubJson = json_decode($hubFile, true); +if (!$hubJson || empty($hubJson['key']) || $hubJson['key'] !== $key) { header('HTTP/1.1 500 Internal Server Error'); - logMe('Invalid secret cross-check!: ' . $secret); - die('Invalid secret cross-check!'); + logMe('Invalid key cross-check!: ' . $key); + die('Invalid key cross-check!'); } chdir('feeds/' . $canonical64); -$users = glob('*/*.txt', GLOB_NOSORT); +$users = glob('*.txt', GLOB_NOSORT); if (empty($users)) { header('HTTP/1.1 410 Gone'); logMe('Nobody is subscribed to this feed anymore!: ' . $canonical64); @@ -53,10 +54,19 @@ if (empty($users)) { } if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] === 'subscribe') { - //TODO: hub_lease_seconds + $leaseSeconds = empty($_REQUEST['hub_lease_seconds']) ? 0 : intval($_REQUEST['hub_lease_seconds']); + if ($leaseSeconds > 60) { + $hubJson['lease_end'] = time() + $leaseSeconds; + file_put_contents('./!hub.json', json_encode($hubJson)); + } exit(isset($_REQUEST['hub_challenge']) ? $_REQUEST['hub_challenge'] : ''); } +if ($ORIGINAL_INPUT == '') { + header('HTTP/1.1 422 Unprocessable Entity'); + die('Missing XML payload!'); +} + Minz_Configuration::register('system', DATA_PATH . '/config.php', DATA_PATH . '/config.default.php'); $system_conf = Minz_Configuration::get('system'); $system_conf->auth_type = 'none'; // avoid necessity to be logged in (not saved!) @@ -80,11 +90,8 @@ if ($self !== base64url_decode($canonical64)) { Minz_Request::_param('url', $self); $nb = 0; -foreach ($users as $userLine) { - $userLine = strtr($userLine, '\\', '/'); - $userInfos = explode('/', $userLine); - $feedUrl = isset($userInfos[0]) ? base64url_decode($userInfos[0]) : ''; - $username = isset($userInfos[1]) ? basename($userInfos[1], '.txt') : ''; +foreach ($users as $userFilename) { + $username = basename($userFilename, '.txt'); if (!file_exists(USERS_PATH . '/' . $username . '/config.php')) { break; } From 18831a89efadf8a05fcc3285fa6af0051e41df2b Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 15 May 2015 15:46:51 +0200 Subject: [PATCH 03/10] PubSubHubbub active only when refreshing, not adding a feed https://github.com/FreshRSS/FreshRSS/issues/312 --- app/Controllers/feedController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 0fb4bdf03..ab73879d0 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -168,7 +168,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { // Ok, feed has been added in database. Now we have to refresh entries. $feed->_id($id); $feed->faviconPrepare(); - $feed->pubSubHubbubPrepare(); + //$feed->pubSubHubbubPrepare(); //TODO: prepare PubSubHubbub already when adding the feed $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; From 0163564b9e02bc399c26d3083048f38d3374cbd7 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 15 May 2015 17:58:56 +0200 Subject: [PATCH 04/10] Change some error messages --- app/Models/Feed.php | 2 +- p/api/pshb.php | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/Models/Feed.php b/app/Models/Feed.php index a17cf415d..d2b552265 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -373,7 +373,7 @@ class FreshRSS_Feed extends Minz_Model { file_put_contents($path . '/!hub.json', json_encode($hubJson)); @mkdir(PSHB_PATH . '/keys/'); file_put_contents(PSHB_PATH . '/keys/' . $key . '.txt', base64url_encode($this->selfUrl)); - Minz_Log::notice('PubSubHubbub prepared for ' . $this->url); + Minz_Log::debug('PubSubHubbub prepared for ' . $this->url); file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . 'PubSubHubbub prepared for ' . $this->url . "\n", FILE_APPEND); } diff --git a/p/api/pshb.php b/p/api/pshb.php index 90d4c52bb..6280c04ac 100644 --- a/p/api/pshb.php +++ b/p/api/pshb.php @@ -12,7 +12,7 @@ function logMe($text) { $ORIGINAL_INPUT = file_get_contents('php://input', false, null, -1, MAX_PAYLOAD); -logMe(print_r(array('_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, 'INPUT' => $ORIGINAL_INPUT), true)); +//logMe(print_r(array('_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, 'INPUT' => $ORIGINAL_INPUT), true)); $key = isset($_GET['k']) ? substr($_GET['k'], 0, 128) : ''; if (!ctype_xdigit($key)) { @@ -23,33 +23,33 @@ chdir(PSHB_PATH); $canonical64 = @file_get_contents('keys/' . $key . '.txt'); if ($canonical64 === false) { header('HTTP/1.1 404 Not Found'); - logMe('Feed key not found!: ' . $key); + logMe('Error: Feed key not found!: ' . $key); die('Feed key not found!'); } $canonical64 = trim($canonical64); if (!preg_match('/^[A-Za-z0-9_-]+$/D', $canonical64)) { header('HTTP/1.1 500 Internal Server Error'); - logMe('Invalid key reference!: ' . $canonical64); + logMe('Error: Invalid key reference!: ' . $canonical64); die('Invalid key reference!'); } $hubFile = @file_get_contents('feeds/' . $canonical64 . '/!hub.json'); if ($hubFile === false) { header('HTTP/1.1 404 Not Found'); //@unlink('keys/' . $key . '.txt'); - logMe('Feed info not found!: ' . $canonical64); + logMe('Error: Feed info not found!: ' . $canonical64); die('Feed info not found!'); } $hubJson = json_decode($hubFile, true); if (!$hubJson || empty($hubJson['key']) || $hubJson['key'] !== $key) { header('HTTP/1.1 500 Internal Server Error'); - logMe('Invalid key cross-check!: ' . $key); + logMe('Error: Invalid key cross-check!: ' . $key); die('Invalid key cross-check!'); } chdir('feeds/' . $canonical64); $users = glob('*.txt', GLOB_NOSORT); if (empty($users)) { header('HTTP/1.1 410 Gone'); - logMe('Nobody is subscribed to this feed anymore!: ' . $canonical64); + logMe('Error: Nobody is subscribed to this feed anymore!: ' . $canonical64); die('Nobody is subscribed to this feed anymore!'); } @@ -83,9 +83,10 @@ $links = $simplePie->get_links('self'); $self = isset($links[0]) ? $links[0] : null; if ($self !== base64url_decode($canonical64)) { - header('HTTP/1.1 422 Unprocessable Entity'); - logMe('Self URL does not match registered canonical URL!: ' . $self); - die('Self URL does not match registered canonical URL!'); + //header('HTTP/1.1 422 Unprocessable Entity'); + logMe('Warning: Self URL ' . $self . ' does not match registered canonical URL!: ' . base64url_decode($canonical64)); + //die('Self URL does not match registered canonical URL!'); + $self = base64url_decode($canonical64); } Minz_Request::_param('url', $self); @@ -106,7 +107,7 @@ foreach ($users as $userFilename) { $nb++; } } catch (Exception $e) { - logMe($e->getMessage()); + logMe('Error: ' . $e->getMessage()); } } @@ -115,7 +116,7 @@ unset($simplePie); if ($nb === 0) { header('HTTP/1.1 410 Gone'); - logMe('Nobody is subscribed to this feed anymore after all!: ' . $self); + logMe('Error: Nobody is subscribed to this feed anymore after all!: ' . $self); die('Nobody is subscribed to this feed anymore after all!'); } From 3adab4b70fab858048bd68ed72e71676c4d5badf Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 16 May 2015 13:05:43 +0200 Subject: [PATCH 05/10] More PubSubHubbub https://github.com/FreshRSS/FreshRSS/issues/312 Show whether PubSubHubbub is enabled in the Web interface of feed configuration. When PubSubHubbub is used, do not pull refresh so often (hard-coded to max once per 24h for now). Improved logic for lease renewal, and some detection of lease problems. Updated read-me and changelog. --- CHANGELOG | 9 +++++ README.fr.md | 22 +++++------ README.md | 34 ++++++++--------- app/Controllers/feedController.php | 36 ++++++++++++------ app/Models/Feed.php | 59 ++++++++++++++++++++++++----- app/i18n/cz/conf.php | 1 + app/i18n/cz/sub.php | 1 + app/i18n/de/sub.php | 1 + app/i18n/en/sub.php | 1 + app/i18n/fr/sub.php | 1 + app/views/helpers/feed/update.phtml | 8 ++++ data/users/_/config.default.php | 2 +- p/api/pshb.php | 8 ++-- 13 files changed, 128 insertions(+), 55 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d1b49d339..f3559ccc4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,15 @@ ## 2015-xx-xx FreshRSS 1.1.1 (beta) +* Features + * Support for PubSubHubbub for instant notifications from compatible Web sites. + * New option to detect and mark updated articles as unread. + * Support for internationalized domain name (IDN). +* Misc. + * Improved logic for automatic deletion of old articles. + * Attempt to better handle encoded titles. + + ## 2015-01-31 FreshRSS 1.0.0 / 1.1.0 (beta) * UI diff --git a/README.fr.md b/README.fr.md index 6c77ccf51..1110eb8e5 100644 --- a/README.fr.md +++ b/README.fr.md @@ -6,6 +6,7 @@ FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed] Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable. Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme. +Il supporte [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) pour des notifications instantanées depuis les sites compatibles. * Site officiel : http://freshrss.org * Démo : http://demo.freshrss.org/ @@ -14,28 +15,25 @@ Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture an ![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png) # Note sur les branches -**Ce logiciel est encore en développement !** Veuillez vous assurer d'utiliser la branche qui vous correspond : +**Ce logiciel est en développement permanent !** Veuillez vous assurer d'utiliser la branche qui vous correspond : * Utilisez [la branche master](https://github.com/FreshRSS/FreshRSS/tree/master/) si vous visez la stabilité. * [La branche beta](https://github.com/FreshRSS/FreshRSS/tree/beta) est celle par défaut : les nouveautés y sont ajoutées environ tous les mois. -* Pour les développeurs et ceux qui savent ce qu'ils font, [la branche dev](https://github.com/FreshRSS/FreshRSS/tree/dev) vous ouvre les bras ! +* Pour les développeurs et ceux qui veulent aider à tester les toutes dernières fonctionnalités, [la branche dev](https://github.com/FreshRSS/FreshRSS/tree/dev) vous ouvre les bras ! # Disclaimer -Cette application a été développée pour s’adapter à des besoins personnels et non professionnels. -Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement. -Je m’engage néanmoins à répondre dans la mesure du possible aux demandes d’évolution si celles-ci me semblent justifiées. -Privilégiez pour cela des demandes sur GitHub -(https://github.com/FreshRSS/FreshRSS/issues). +Cette application a été développée pour s’adapter principalement à des besoins personnels, et aucune garantie n'est fournie. +Les demandes de fonctionnalités, rapports de bugs, et autres contributions sont les bienvenues. Privilégiez pour cela des [demandes sur GitHub](https://github.com/FreshRSS/FreshRSS/issues). -# Pré-requis +# Prérequis * Serveur modeste, par exemple sous Linux ou Windows * Fonctionne même sur un Raspberry Pi avec des temps de réponse < 1s (testé sur 150 flux, 22k articles, soit 32Mo de données partiellement compressées) * Serveur Web Apache2 (recommandé), ou nginx, lighttpd (non testé sur les autres) * PHP 5.2.1+ (PHP 5.3.7+ recommandé) - * Requis : [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (pour accès API sur platformes < 64 bits), [IDN](http://php.net/intl.idn) (pour les noms de domaines internationalisés) + * Requis : [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](http://php.net/intl.idn) (pour les noms de domaines internationalisés) * Recommandés : [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip) * MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+ -* Un navigateur Web récent tel Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+ +* Un navigateur Web récent tel Firefox, Chrome, Opera, Safari. [Internet Explorer ne fonctionne plus, mais ce sera corrigé](https://github.com/FreshRSS/FreshRSS/issues/772). * Fonctionne aussi sur mobile ![Capture d’écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png) @@ -63,7 +61,7 @@ C’est une bonne idée d’utiliser le même utilisateur que votre serveur Web Par exemple, pour exécuter le script toutes les heures : ``` -7 * * * * php /chemin/vers/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 +7 * * * * php /votre-chemin/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 ``` # Conseils @@ -75,7 +73,7 @@ Par exemple, pour exécuter le script toutes les heures : # Sauvegarde * Il faut conserver vos fichiers `./data/config.php` ainsi que `./data/*_user.php` et éventuellement `./data/persona/` * Vous pouvez exporter votre liste de flux depuis FreshRSS au format OPML -* Pour sauvegarder les articles eux-même, vous pouvez utiliser [phpMyAdmin](http://www.phpmyadmin.net) ou les outils de MySQL : +* Pour sauvegarder les articles eux-mêmes, vous pouvez utiliser [phpMyAdmin](http://www.phpmyadmin.net) ou les outils de MySQL : ```bash mysqldump -u utilisateur -p --databases freshrss > freshrss.sql diff --git a/README.md b/README.md index 089bbd780..4430560fe 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ * [Version française](README.fr.md) # FreshRSS -FreshRSS is a self-hosted RSS feed agregator like [Leed](http://projet.idleman.fr/leed/) or [Kriss Feed](http://tontof.net/kriss/feed/). +FreshRSS is a self-hosted RSS feed aggregator such as [Leed](http://projet.idleman.fr/leed/) or [Kriss Feed](http://tontof.net/kriss/feed/). -It is at the same time light-weight, easy to work with, powerful and customizable. +It is at the same time lightweight, easy to work with, powerful and customizable. It is a multi-user application with an anonymous reading mode. +It supports [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) for instant notifications from compatible Web sites. * Official website: http://freshrss.org * Demo: http://demo.freshrss.org/ @@ -14,28 +15,25 @@ It is a multi-user application with an anonymous reading mode. ![FreshRSS logo](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png) # Note on branches -**This application is still in development!** Please use the branch that suits your needs: +**This application is under continuous development!** Please use the branch that suits your needs: * Use [the master branch](https://github.com/FreshRSS/FreshRSS/tree/master/) if you need a stable version. * [The beta branch](https://github.com/FreshRSS/FreshRSS/tree/beta) is the default branch: new features are added on a monthly basis. -* For developers and tech savvy persons, [the dev branch](https://github.com/FreshRSS/FreshRSS/tree/dev) is waiting for you! +* For developers and tech savvy persons willing to help testing the latest features, [the dev branch](https://github.com/FreshRSS/FreshRSS/tree/dev) is waiting for you! # Disclaimer -This application was developed to fulfill personal needs not professional needs. -There is no guarantee neither on its security nor its proper functioning. -If there is feature requests which I think are good for the project, I'll do my best to include them. -The best way is to open issues on GitHub -(https://github.com/FreshRSS/FreshRSS/issues). +This application was developed to fulfil personal needs primarily, and comes with absolutely no warranty. +Feature requests, bug reports, and other contributions are welcome. The best way is to [open issues on GitHub](https://github.com/FreshRSS/FreshRSS/issues). # Requirements * Light server running Linux or Windows * It even works on Raspberry Pi with response time under a second (tested with 150 feeds, 22k articles, or 32Mo of compressed data) -* A web server: Apache2 (recommanded), nginx, lighttpd (not tested on others) -* PHP 5.2.1+ (PHP 5.3.7+ recommanded) +* A web server: Apache2 (recommended), nginx, lighttpd (not tested on others) +* PHP 5.2.1+ (PHP 5.3.7+ recommended) * Required extensions: [PDO_MySQL](http://php.net/pdo-mysql) or [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (for API access on platforms < 64 bits), [IDN](http://php.net/intl.idn) (for Internationalized Domain Names) - * Recommanded extensions : [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip) -* MySQL 5.0.3+ (recommanded) or SQLite 3.7.4+ -* A recent browser like Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+ + * Recommended extensions: [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip) +* MySQL 5.0.3+ (recommended) or SQLite 3.7.4+ +* A recent browser like Firefox, Chrome, Opera, Safari. [Internet Explorer currently not supported, but support will come back](https://github.com/FreshRSS/FreshRSS/issues/772). * Works on mobile ![FreshRSS screenshot](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png) @@ -45,7 +43,7 @@ The best way is to open issues on GitHub 2. Dump the application on your server (expose only the `./p/` folder) 3. Add write access on `./data/` folder to the webserver user 4. Access FreshRSS with your browser and follow the installation process -5. Every thing should be working :) If you encounter any problem, feel free to contact me. +5. Everything should be working :) If you encounter any problem, feel free to contact me. 6. Advanced configuration settings can be seen in [config.php](./data/config.default.php). # Access control @@ -59,18 +57,18 @@ It is needed for the multi-user mode to limit access to FreshRSS. You can: # Automatic feed update * You can add a Cron job to launch the update script. Check the Cron documentation related to your distribution ([Debian/Ubuntu](https://help.ubuntu.com/community/CronHowto), [Red Hat/Fedora](https://fedoraproject.org/wiki/Administration_Guide_Draft/Cron), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](https://wiki.gentoo.org/wiki/Cron), [Arch Linux](https://wiki.archlinux.org/index.php/Cron)…). -It’s a good idea to use the web server user . +It’s a good idea to use the Web server user. For example, if you want to run the script every hour: ``` -7 * * * * php /chemin/vers/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 +7 * * * * php /your-path/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 ``` # Advices * For a better security, expose only the `./p/` folder on the web. * Be aware that the `./data/` folder contains all personal data, so it is a bad idea to expose it. * The `./constants.php` file defines access to application folder. If you want to customize your installation, every thing happens here. -* If you encounter any problem, logs are accessibles from the interface or manually in `./data/log/*.log` files. +* If you encounter any problem, logs are accessible from the interface or manually in `./data/log/*.log` files. # Backup * You need to keep `./data/config.php`, `./data/*_user.php` and `./data/persona/` files diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index ab73879d0..dfdf0dc16 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -268,7 +268,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { * If id and url are not specified, all the feeds are actualized. But if force is * false, process stops at 10 feeds to avoid time execution problem. */ - public function actualizeAction($simplePie = null) { + public function actualizeAction($simplePiePush = null) { @set_time_limit(300); $feedDAO = FreshRSS_Factory::createFeedDao(); @@ -295,10 +295,16 @@ class FreshRSS_feed_Controller extends Minz_ActionController { // Calculate date of oldest entries we accept in DB. $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1); $date_min = time() - (3600 * 24 * 30 * $nb_month_old); + $pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration. $updated_feeds = 0; $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; foreach ($feeds as $feed) { + $pubSubHubbubEnabled = $feed->pubSubHubbubEnabled(); + if ((!$simplePiePush) && (!$id) && (!$force) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) { + continue; //When PubSubHubbub is used, do not pull refresh so often + } + if (!$feed->lock()) { Minz_Log::notice('Feed already being actualized: ' . $feed->url()); continue; @@ -306,8 +312,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $url = $feed->url(); //For detection of HTTP 301 try { - if ($simplePie) { - $feed->loadEntries($simplePie); //Used by PubSubHubbub + if ($simplePiePush) { + $feed->loadEntries($simplePiePush); //Used by PubSubHubbub } else { $feed->load(false); } @@ -374,6 +380,14 @@ class FreshRSS_feed_Controller extends Minz_ActionController { continue; } + if ($pubSubHubbubEnabled && !$simplePiePush) { //We use push, but have discovered an article by pull! + $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url . ' GUID ' . $entry->guid(); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); + Minz_Log::warning($text); + $pubSubHubbubEnabled = false; + $feed->pubSubHubbubEnabled(false); //To force the renewal of our lease + } + if (!$entryDAO->hasTransaction()) { $entryDAO->beginTransaction(); } @@ -423,15 +437,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); } - if ($simplePie === null) { - $feed->faviconPrepare(); - if (in_array($feed->url(), array('http://push-pub.appspot.com/feed'))) { //TODO: Remove white-list after testing - Minz_Log::debug('PubSubHubbub match ' . $feed->url()); - if ($feed->pubSubHubbubPrepare()) { - Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url()); - if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe - Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url()); - } + $feed->faviconPrepare(); + if (in_array($feed->url(), array('http://push-pub.appspot.com/feed'))) { //TODO: Remove white-list after testing + Minz_Log::debug('PubSubHubbub match ' . $feed->url()); + if ($feed->pubSubHubbubPrepare()) { + Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url()); + if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe + Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url()); } } } diff --git a/app/Models/Feed.php b/app/Models/Feed.php index d2b552265..7bc60dfc9 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -104,6 +104,16 @@ class FreshRSS_Feed extends Minz_Model { public function ttl() { return $this->ttl; } + // public function ttlExpire() { + // $ttl = $this->ttl; + // if ($ttl == -2) { //Default + // $ttl = FreshRSS_Context::$user_conf->ttl_default; + // } + // if ($ttl == -1) { //Never + // $ttl = 64000000; //~2 years. Good enough for PubSubHubbub logic + // } + // return $this->lastUpdate + $ttl; + // } public function nbEntries() { if ($this->nbEntries < 0) { $feedDAO = FreshRSS_Factory::createFeedDao(); @@ -349,18 +359,42 @@ class FreshRSS_Feed extends Minz_Model { // + function pubSubHubbubEnabled($keep = true) { + $url = $this->selfUrl ? $this->selfUrl : $this->url; + $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json'; + if ($hubFile = @file_get_contents($hubFilename)) { + $hubJson = json_decode($hubFile, true); + if (!$keep) { + $hubJson['lease_end'] = time() - 60; + file_put_contents($hubFilename, json_encode($hubJson)); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" + . 'Force expire lease for ' . $url . "\n", FILE_APPEND); + } elseif ($hubJson && (empty($hubJson['lease_end']) || $hubJson['lease_end'] > time())) { + return true; + } + } + return false; + } + function pubSubHubbubPrepare() { $key = ''; if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { $path = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl); - if ($hubFile = @file_get_contents($path . '/!hub.json')) { + $hubFilename = $path . '/!hub.json'; + if ($hubFile = @file_get_contents($hubFilename)) { $hubJson = json_decode($hubFile, true); if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) { - Minz_Log::warning('Invalid JSON for PubSubHubbub: ' . $this->url); + $text = 'Invalid JSON for PubSubHubbub: ' . $this->url; + Minz_Log::warning($text); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); return false; } - if (empty($hubJson['lease_end']) || $hubJson['lease_end'] <= time()) { - Minz_Log::warning('PubSubHubbub lease expired: ' . $this->url); + if (empty($hubJson['lease_end']) || ($hubJson['lease_end'] <= (time() + (3600 * 24)))) { //TODO: Make a better policy + $text = 'PubSubHubbub lease ends at ' + . date('c', empty($hubJson['lease_end']) ? time() : $hubJson['lease_end']) + . ' and needs renewal: ' . $this->url; + Minz_Log::warning($text); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); $key = $hubJson['key']; //To renew our lease } } else { @@ -370,12 +404,12 @@ class FreshRSS_Feed extends Minz_Model { 'hub' => $this->hubUrl, 'key' => $key, ); - file_put_contents($path . '/!hub.json', json_encode($hubJson)); + file_put_contents($hubFilename, json_encode($hubJson)); @mkdir(PSHB_PATH . '/keys/'); file_put_contents(PSHB_PATH . '/keys/' . $key . '.txt', base64url_encode($this->selfUrl)); - Minz_Log::debug('PubSubHubbub prepared for ' . $this->url); - file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . - 'PubSubHubbub prepared for ' . $this->url . "\n", FILE_APPEND); + $text = 'PubSubHubbub prepared for ' . $this->url; + Minz_Log::debug($text); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); } $currentUser = Minz_Session::param('currentUser'); if (ctype_alnum($currentUser) && !file_exists($path . '/' . $currentUser . '.txt')) { @@ -388,7 +422,8 @@ class FreshRSS_Feed extends Minz_Model { //Parameter true to subscribe, false to unsubscribe. function pubSubHubbubSubscribe($state) { if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { - $hubFile = @file_get_contents(PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl) . '/!hub.json'); + $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl) . '/!hub.json'; + $hubFile = @file_get_contents($hubFilename); if ($hubFile === false) { Minz_Log::warning('JSON not found for PubSubHubbub: ' . $this->url); return false; @@ -421,6 +456,12 @@ class FreshRSS_Feed extends Minz_Model { file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . 'PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $this->selfUrl . ' with callback ' . $callbackUrl . ': ' . $info['http_code'] . ' ' . $response . "\n", FILE_APPEND); + + if (!$state) { //unsubscribe + $hubJson['lease_end'] = time() - 60; + file_put_contents($hubFilename, json_encode($hubJson)); + } + return substr($info['http_code'], 0, 1) == '2'; } return false; diff --git a/app/i18n/cz/conf.php b/app/i18n/cz/conf.php index 29fb1e4d4..9518df66d 100644 --- a/app/i18n/cz/conf.php +++ b/app/i18n/cz/conf.php @@ -84,6 +84,7 @@ return array( 'articles_per_page' => 'Počet článků na stranu', 'auto_load_more' => 'Načítat další články dole na stránce', 'auto_remove_article' => 'Po přečtení články schovat', + 'mark_updated_article_unread' => 'Označte aktualizované položky jako nepřečtené', 'confirm_enabled' => 'Vyžadovat potvrzení pro akci “označit vše jako přečtené”', 'display_articles_unfolded' => 'Ve výchozím stavu zobrazovat články otevřené', 'display_categories_unfolded' => 'Ve výchozím stavu zobrazovat kategorie zavřené', diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php index 78712506c..cea0541e3 100644 --- a/app/i18n/cz/sub.php +++ b/app/i18n/cz/sub.php @@ -37,6 +37,7 @@ return array( 'url' => 'URL kanálu', 'validator' => 'Zkontrolovat platnost kanálu', 'website' => 'URL webové stránky', + 'pubsubhubbub' => 'Okamžité oznámení s PubSubHubbub', ), 'import_export' => array( 'export' => 'Export', diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php index 0479b8f46..7433bd61c 100644 --- a/app/i18n/de/sub.php +++ b/app/i18n/de/sub.php @@ -37,6 +37,7 @@ return array( 'url' => 'Feed-URL', 'validator' => 'Überprüfen Sie die Gültigkeit des Feeds', 'website' => 'Webseiten-URL', + 'pubsubhubbub' => 'Sofortige Benachrichtigung mit PubSubHubbub', ), 'import_export' => array( 'export' => 'Exportieren', diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php index 2b62e4775..d8b5ced04 100644 --- a/app/i18n/en/sub.php +++ b/app/i18n/en/sub.php @@ -37,6 +37,7 @@ return array( 'url' => 'Feed URL', 'validator' => 'Check the validity of the feed', 'website' => 'Website URL', + 'pubsubhubbub' => 'Instant notification with PubSubHubbub', ), 'import_export' => array( 'export' => 'Export', diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php index a3f7c4d6d..0a1a03e41 100644 --- a/app/i18n/fr/sub.php +++ b/app/i18n/fr/sub.php @@ -37,6 +37,7 @@ return array( 'url' => 'URL du flux', 'validator' => 'Vérifier la valididé du flux', 'website' => 'URL du site', + 'pubsubhubbub' => 'Notification instantanée par PubSubHubbub', ), 'import_export' => array( 'export' => 'Exporter', diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml index 0b08d036c..b2cf9f93c 100644 --- a/app/views/helpers/feed/update.phtml +++ b/app/views/helpers/feed/update.phtml @@ -126,6 +126,14 @@ ?> +
+ +
+ +
+
diff --git a/data/users/_/config.default.php b/data/users/_/config.default.php index bf74ca1de..8f8ff528c 100644 --- a/data/users/_/config.default.php +++ b/data/users/_/config.default.php @@ -25,7 +25,7 @@ return array ( # In the case an article has changed (e.g. updated content): # Set to `true` to mark it unread, or `false` to leave it as-is. - 'mark_updated_article_unread' => false, + 'mark_updated_article_unread' => false, //TODO: -1 => ignore, 0 => update, 1 => update and mark as unread 'sort_order' => 'DESC', 'anon_access' => false, diff --git a/p/api/pshb.php b/p/api/pshb.php index 6280c04ac..2f7f48cd8 100644 --- a/p/api/pshb.php +++ b/p/api/pshb.php @@ -57,8 +57,10 @@ if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] === 'subscribe') { $leaseSeconds = empty($_REQUEST['hub_lease_seconds']) ? 0 : intval($_REQUEST['hub_lease_seconds']); if ($leaseSeconds > 60) { $hubJson['lease_end'] = time() + $leaseSeconds; - file_put_contents('./!hub.json', json_encode($hubJson)); + } else { + unset($hubJson['lease_end']); } + file_put_contents('./!hub.json', json_encode($hubJson)); exit(isset($_REQUEST['hub_challenge']) ? $_REQUEST['hub_challenge'] : ''); } @@ -84,7 +86,7 @@ $self = isset($links[0]) ? $links[0] : null; if ($self !== base64url_decode($canonical64)) { //header('HTTP/1.1 422 Unprocessable Entity'); - logMe('Warning: Self URL ' . $self . ' does not match registered canonical URL!: ' . base64url_decode($canonical64)); + logMe('Warning: Self URL [' . $self . '] does not match registered canonical URL!: ' . base64url_decode($canonical64)); //die('Self URL does not match registered canonical URL!'); $self = base64url_decode($canonical64); } @@ -120,5 +122,5 @@ if ($nb === 0) { die('Nobody is subscribed to this feed anymore after all!'); } -logMe($self . ' done: ' . $nb); +logMe('PubSubHubbub ' . $self . ' done: ' . $nb); exit('Done: ' . $nb . "\n"); From d3af903301ce2b59d2720e94727945cf9ff31a40 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 16 May 2015 21:47:26 +0200 Subject: [PATCH 06/10] i18n: German PubSubHubbub https://github.com/FreshRSS/FreshRSS/pull/831/files#r30463016 https://github.com/FreshRSS/FreshRSS/issues/312 --- app/i18n/de/sub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php index 7433bd61c..0f05a5635 100644 --- a/app/i18n/de/sub.php +++ b/app/i18n/de/sub.php @@ -37,7 +37,7 @@ return array( 'url' => 'Feed-URL', 'validator' => 'Überprüfen Sie die Gültigkeit des Feeds', 'website' => 'Webseiten-URL', - 'pubsubhubbub' => 'Sofortige Benachrichtigung mit PubSubHubbub', + 'pubsubhubbub' => 'Sofortbenachrichtigung mit PubSubHubbub', ), 'import_export' => array( 'export' => 'Exportieren', From 99dd1d37e021733f27f6e09129a7133e6db5e584 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 16 May 2015 21:52:57 +0200 Subject: [PATCH 07/10] PubSubHubbub changelog Planned for version beta 1.1.2 https://github.com/FreshRSS/FreshRSS/issues/312 --- CHANGELOG | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f3559ccc4..ed1f655f9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,14 @@ # Changelog -## 2015-xx-xx FreshRSS 1.1.1 (beta) +## 2015-xx-xx FreshRSS 1.1.2 (beta) * Features * Support for PubSubHubbub for instant notifications from compatible Web sites. + + +## 2015-xx-xx FreshRSS 1.1.1 (beta) + +* Features * New option to detect and mark updated articles as unread. * Support for internationalized domain name (IDN). * Misc. From 84ea636d2d8c55e04d0c2d07688b66d7730b6c7c Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 16 May 2015 23:44:36 +0200 Subject: [PATCH 08/10] PubSubHubbub bug skip pull Do not pull refresh feeds that are PubSubHubbub too often during cron refresh. And more debugging info during the test phase. https://github.com/FreshRSS/FreshRSS/pull/831 --- app/Controllers/feedController.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index dfdf0dc16..5e845027f 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -300,8 +300,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $updated_feeds = 0; $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; foreach ($feeds as $feed) { + $url = $feed->url(); //For detection of HTTP 301 + $pubSubHubbubEnabled = $feed->pubSubHubbubEnabled(); - if ((!$simplePiePush) && (!$id) && (!$force) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) { + if ((!$simplePiePush) && (!$id) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) { + $text = 'Skip pull of feed using PubSubHubbub: ' . $url; + Minz_Log::debug($text); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); continue; //When PubSubHubbub is used, do not pull refresh so often } @@ -310,7 +315,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { continue; } - $url = $feed->url(); //For detection of HTTP 301 try { if ($simplePiePush) { $feed->loadEntries($simplePiePush); //Used by PubSubHubbub From 001c713f030d51b74a860e20014153c6b4d9661f Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 17 May 2015 22:06:11 +0200 Subject: [PATCH 09/10] PubSubHubbub better gestion of errors Do not assume that PubSubHubbub works until the first successul push https://github.com/FreshRSS/FreshRSS/issues/312#issuecomment-102706500 --- app/Controllers/feedController.php | 4 ++-- app/Models/Feed.php | 31 +++++++++++++++++++++--------- p/api/pshb.php | 7 +++++++ 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 5e845027f..3d8a6deb7 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -305,7 +305,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $pubSubHubbubEnabled = $feed->pubSubHubbubEnabled(); if ((!$simplePiePush) && (!$id) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) { $text = 'Skip pull of feed using PubSubHubbub: ' . $url; - Minz_Log::debug($text); + //Minz_Log::debug($text); file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); continue; //When PubSubHubbub is used, do not pull refresh so often } @@ -389,7 +389,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); Minz_Log::warning($text); $pubSubHubbubEnabled = false; - $feed->pubSubHubbubEnabled(false); //To force the renewal of our lease + $feed->pubSubHubbubError(true); } if (!$entryDAO->hasTransaction()) { diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 7bc60dfc9..ed0a862c3 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -359,23 +359,33 @@ class FreshRSS_Feed extends Minz_Model { // - function pubSubHubbubEnabled($keep = true) { + function pubSubHubbubEnabled() { $url = $this->selfUrl ? $this->selfUrl : $this->url; $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json'; if ($hubFile = @file_get_contents($hubFilename)) { $hubJson = json_decode($hubFile, true); - if (!$keep) { - $hubJson['lease_end'] = time() - 60; - file_put_contents($hubFilename, json_encode($hubJson)); - file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" - . 'Force expire lease for ' . $url . "\n", FILE_APPEND); - } elseif ($hubJson && (empty($hubJson['lease_end']) || $hubJson['lease_end'] > time())) { + if ($hubJson && empty($hubJson['error']) && + (empty($hubJson['lease_end']) || $hubJson['lease_end'] > time())) { return true; } } return false; } + function pubSubHubbubError($error = true) { + $url = $this->selfUrl ? $this->selfUrl : $this->url; + $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json'; + $hubFile = @file_get_contents($hubFilename); + $hubJson = $hubFile ? json_decode($hubFile, true) : array(); + if (!isset($hubJson['error']) || $hubJson['error'] !== (bool)$error) { + $hubJson['error'] = (bool)$error; + file_put_contents($hubFilename, json_encode($hubJson)); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" + . 'Set error to ' . ($error ? 1 : 0) . ' for ' . $url . "\n", FILE_APPEND); + } + return false; + } + function pubSubHubbubPrepare() { $key = ''; if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { @@ -389,17 +399,20 @@ class FreshRSS_Feed extends Minz_Model { file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); return false; } - if (empty($hubJson['lease_end']) || ($hubJson['lease_end'] <= (time() + (3600 * 24)))) { //TODO: Make a better policy + if ((!empty($hubJson['lease_end'])) && ($hubJson['lease_end'] < (time() + (3600 * 23)))) { //TODO: Make a better policy $text = 'PubSubHubbub lease ends at ' . date('c', empty($hubJson['lease_end']) ? time() : $hubJson['lease_end']) . ' and needs renewal: ' . $this->url; Minz_Log::warning($text); file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); $key = $hubJson['key']; //To renew our lease + } elseif (((!empty($hubJson['error'])) || empty($hubJson['lease_end'])) && + (empty($hubJson['lease_start']) || $hubJson['lease_start'] < time() - (3600 * 23))) { //Do not renew too often + $key = $hubJson['key']; //To renew our lease } } else { @mkdir($path, 0777, true); - $key = sha1(FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true)); + $key = sha1($path . FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true)); $hubJson = array( 'hub' => $this->hubUrl, 'key' => $key, diff --git a/p/api/pshb.php b/p/api/pshb.php index 2f7f48cd8..4bb4694b3 100644 --- a/p/api/pshb.php +++ b/p/api/pshb.php @@ -60,6 +60,10 @@ if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] === 'subscribe') { } else { unset($hubJson['lease_end']); } + $hubJson['lease_start'] = time(); + if (!isset($hubJson['error'])) { + $hubJson['error'] = true; //Do not assume that PubSubHubbub works until the first successul push + } file_put_contents('./!hub.json', json_encode($hubJson)); exit(isset($_REQUEST['hub_challenge']) ? $_REQUEST['hub_challenge'] : ''); } @@ -120,6 +124,9 @@ if ($nb === 0) { header('HTTP/1.1 410 Gone'); logMe('Error: Nobody is subscribed to this feed anymore after all!: ' . $self); die('Nobody is subscribed to this feed anymore after all!'); +} elseif (!empty($hubJson['error'])) { + $hubJson['error'] = false; + file_put_contents('./!hub.json', json_encode($hubJson)); } logMe('PubSubHubbub ' . $self . ' done: ' . $nb); From 694dfa1f8b90d8f693ef39c7099c0e8f23c5c777 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 23 May 2015 16:37:08 +0200 Subject: [PATCH 10/10] PubSubHubbub: remove white list The tests so far are good. Ready to test more broadly. https://github.com/FreshRSS/FreshRSS/pull/831 https://github.com/FreshRSS/FreshRSS/issues/312 --- app/Controllers/feedController.php | 11 ++++------- app/Models/Feed.php | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 3d8a6deb7..957a809cd 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -442,13 +442,10 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } $feed->faviconPrepare(); - if (in_array($feed->url(), array('http://push-pub.appspot.com/feed'))) { //TODO: Remove white-list after testing - Minz_Log::debug('PubSubHubbub match ' . $feed->url()); - if ($feed->pubSubHubbubPrepare()) { - Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url()); - if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe - Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url()); - } + if ($feed->pubSubHubbubPrepare()) { + Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url()); + if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe + Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url()); } } $feed->unlock(); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index ed0a862c3..bf7ed3967 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -388,7 +388,7 @@ class FreshRSS_Feed extends Minz_Model { function pubSubHubbubPrepare() { $key = ''; - if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { + if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl && @is_dir(PSHB_PATH)) { $path = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl); $hubFilename = $path . '/!hub.json'; if ($hubFile = @file_get_contents($hubFilename)) {