summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2015-05-15 15:34:51 +0200
committerGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2015-05-15 15:34:51 +0200
commitc472569b3861541c322c850c4ff8ca3471572f40 (patch)
treeae2ecf23892c289dbffb18079db6b5781293631a
parent256c8613a4046931dcd28ab22b6aebe8752a98c2 (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-xapp/Controllers/feedController.php31
-rw-r--r--app/Models/Feed.php53
-rw-r--r--data/PubSubHubbub/feeds/README.md11
-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.php55
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;
}