Ver Fonte

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.
Alexandre Alapetite há 11 anos atrás
pai
commit
c472569b38

+ 23 - 8
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());
+						}
 					}
 				}
 			}

+ 40 - 13
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,

+ 3 - 8
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

+ 0 - 0
data/PubSubHubbub/secrets/.gitignore → data/PubSubHubbub/keys/.gitignore


+ 1 - 1
data/PubSubHubbub/secrets/README.md → 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)

+ 31 - 24
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;
 	}