diff options
| author | 2015-05-15 15:34:51 +0200 | |
|---|---|---|
| committer | 2015-05-15 15:34:51 +0200 | |
| commit | c472569b3861541c322c850c4ff8ca3471572f40 (patch) | |
| tree | ae2ecf23892c289dbffb18079db6b5781293631a | |
| parent | 256c8613a4046931dcd28ab22b6aebe8752a98c2 (diff) | |
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.
| -rwxr-xr-x | app/Controllers/feedController.php | 31 | ||||
| -rw-r--r-- | app/Models/Feed.php | 53 | ||||
| -rw-r--r-- | data/PubSubHubbub/feeds/README.md | 11 | ||||
| -rw-r--r-- | data/PubSubHubbub/keys/.gitignore (renamed from data/PubSubHubbub/secrets/.gitignore) | 0 | ||||
| -rw-r--r-- | data/PubSubHubbub/keys/README.md (renamed from data/PubSubHubbub/secrets/README.md) | 2 | ||||
| -rw-r--r-- | p/api/pshb.php | 55 |
6 files changed, 98 insertions, 54 deletions
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 { //<PubSubHubbub> 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 index 2211df63d..2211df63d 100644 --- a/data/PubSubHubbub/secrets/.gitignore +++ b/data/PubSubHubbub/keys/.gitignore diff --git a/data/PubSubHubbub/secrets/README.md b/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; } |
