]> git.mxchange.org Git - friendica.git/commitdiff
Initial implementation of internal PuSH server in Friendica. It has been tested with...
authorMats Sjöberg <mats@sjoberg.fi>
Sun, 17 Nov 2013 14:16:25 +0000 (16:16 +0200)
committerMats Sjöberg <mats@sjoberg.fi>
Sun, 17 Nov 2013 15:13:38 +0000 (17:13 +0200)
boot.php
include/notifier.php
include/queue.php
include/text.php
mod/pubsubhubbub.php [new file with mode: 0644]
update.php

index d0f6e9d444fbc52ae0c21a74fa22e2f308789c02..892ee1272096b33112429cbe6690360a0fb7c471 100644 (file)
--- a/boot.php
+++ b/boot.php
@@ -14,7 +14,7 @@ require_once('include/features.php');
 define ( 'FRIENDICA_PLATFORM',     'Friendica');
 define ( 'FRIENDICA_VERSION',      '3.2.1745' );
 define ( 'DFRN_PROTOCOL_VERSION',  '2.23'    );
-define ( 'DB_UPDATE_VERSION',      1165      );
+define ( 'DB_UPDATE_VERSION',      1166      );
 define ( 'EOL',                    "<br />\r\n"     );
 define ( 'ATOM_TIME',              'Y-m-d\TH:i:s\Z' );
 
index ad0167e34bc8f2443899d62f2b60f22f02850575..8e9764315f4631ee7239de8feccc8152c7a73baf 100644 (file)
@@ -966,9 +966,18 @@ function notifier_run(&$argv, &$argc){
                                        $h = trim($h);
                                        if(! strlen($h))
                                                continue;
-                                       $params = 'hub.mode=publish&hub.url=' . urlencode($a->get_baseurl() . '/dfrn_poll/' . $owner['nickname'] );
-                                       post_url($h,$params);
-                                       logger('pubsub: publish: ' . $h . ' ' . $params . ' returned ' . $a->get_curl_code());
+
+                                       if ($h === '[internal]') {
+                                               // Set push flag for PuSH subscribers to this topic,
+                                               // they will be notified in queue.php
+                                               q("UPDATE `push_subscriber` SET `push` = 1 " . 
+                                                 "WHERE `nickname` = '%s'", dbesc($owner['nickname']));
+                                       } else {
+
+                                               $params = 'hub.mode=publish&hub.url=' . urlencode( $a->get_baseurl() . '/dfrn_poll/' . $owner['nickname'] );
+                                               post_url($h,$params);
+                                               logger('pubsub: publish: ' . $h . ' ' . $params . ' returned ' . $a->get_curl_code());
+                                       }
                                        if(count($hubs) > 1)
                                                sleep(7);                               // try and avoid multiple hubs responding at precisely the same time
                                }
index 64cccad21efe4f796a50e39b480da75415da7d12..5d30ab138dfc807622274cbbf6c0b4ff56419080 100644 (file)
@@ -2,6 +2,51 @@
 require_once("boot.php");
 require_once('include/queue_fn.php');
 
+function handle_pubsubhubbub() {
+       global $a, $db;
+
+       logger('queue [pubsubhubbub]: start');
+
+       // We'll push to each subscriber that has the push flag set,
+       // i.e. there has been an update (set in notifier.php).
+
+       $r = q("SELECT * FROM `push_subscriber` WHERE `push` = 1");
+
+       foreach($r as $rr) {
+               $params = get_feed_for($a, '', $rr['nickname'], $rr['last_update']);
+               $hmac_sig = hash_hmac("sha1", $params, $rr['secret']);
+
+               $headers = array("Content-type: application/atom+xml",
+                                                sprintf("Link: <%s>;rel=hub," .
+                                                                "<%s>;rel=self",
+                                                                $a->get_baseurl() . '/pubsubhubbub',
+                                                                $rr['topic']),
+                                                "X-Hub-Signature: sha1=" . $hmac_sig);
+
+               logger('queue [pubsubhubbub]: POST', $headers);
+
+               post_url($rr['callback_url'], $params, $headers);
+               $ret = $a->get_curl_code();
+
+               if ($ret >= 200 && $ret <= 299) {
+                       logger('queue [pubsubhubbub]: successfully pushed to ' .
+                                  $rr['callback_url']);
+                       // here we should set push = 0 and update last_update to 'now'
+                       $date_now = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
+                       q("UPDATE `push_subscriber` SET `push` = 0, last_update = '%s' " .
+                         "WHERE id = %d",
+                         dbesc($date_now),
+                         intval($rr['id']));
+               } else {
+                       logger('queue [pubsubhubbub]: error when pushing to ' .
+                                  $rr['callback_url'] . 'HTTP: ', $ret);
+                       // here we should set update some retry counter
+                       // or cancel if counter is too high, remove subscription?
+               }
+       }
+}
+
+
 function queue_run(&$argv, &$argc){
        global $a, $db;
 
@@ -38,6 +83,8 @@ function queue_run(&$argv, &$argc){
 
        logger('queue: start');
 
+       handle_pubsubhubbub();
+
        $interval = ((get_config('system','delivery_interval') === false) ? 2 : intval(get_config('system','delivery_interval')));
 
        $r = q("select * from deliverq where 1");
index c0b716afb238d0aa5ae5cef47135ecf99e6c4164..ea36a2a01699d6b9794a611a6a2743721ddc49de 100644 (file)
@@ -1554,7 +1554,7 @@ if(! function_exists('feed_hublinks')) {
  * @return string hub link xml elements
  */
 function feed_hublinks() {
-
+       $a = get_app();
        $hub = get_config('system','huburl');
 
        $hubxml = '';
@@ -1565,6 +1565,8 @@ function feed_hublinks() {
                                $h = trim($h);
                                if(! strlen($h))
                                        continue;
+                               if ($h === '[internal]')
+                                       $h = $a->get_baseurl() . '/pubsubhubbub';
                                $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
                        }
                }
diff --git a/mod/pubsubhubbub.php b/mod/pubsubhubbub.php
new file mode 100644 (file)
index 0000000..d6b9bf1
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+
+function post_var($name) {
+       return (x($_POST, $name)) ? notags(trim($_POST[$name])) : '';
+}
+
+function pubsubhubbub_init(&$a) {
+       // PuSH subscription must be considered "public" so just block it
+       // if public access isn't enabled.
+       if (get_config('system', 'block_public')) {
+               http_status_exit(403);
+       }
+
+       // Subscription request from subscriber
+       // https://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#anchor4
+       // Example from GNU Social:
+    // [hub_mode] => subscribe
+    // [hub_callback] => http://status.local/main/push/callback/1
+    // [hub_verify] => sync
+    // [hub_verify_token] => af11...
+    // [hub_secret] => af11...
+    // [hub_topic] => http://friendica.local/dfrn_poll/sazius
+
+       if($_SERVER['REQUEST_METHOD'] === 'POST') {
+               $hub_mode = post_var('hub_mode');
+               $hub_callback = post_var('hub_callback');
+               $hub_verify = post_var('hub_verify');
+               $hub_verify_token = post_var('hub_verify_token');
+               $hub_secret = post_var('hub_secret');
+               $hub_topic = post_var('hub_topic');
+
+               // check for valid hub_mode
+               if ($hub_mode === 'subscribe') {
+                       $subscribe = 1;
+               } else if ($hub_mode === 'unsubscribe') {
+                       $subscribe = 0;
+               } else {
+                       logger("pubsubhubbub: invalid hub_mode=$hub_mode, ignoring.");
+                       http_status_exit(404);
+               }
+
+               logger("pubsubhubbub: $hub_mode request from " . 
+                          $_SERVER['REMOTE_ADDR']);
+
+               // get the nick name from the topic, a bit hacky but needed
+               $nick = substr(strrchr($hub_topic, "/"), 1);
+
+               if (!$nick) {
+                       logger('pubsubhubbub: bad hub_topic=$hub_topic, ignoring.');
+                       http_status_exit(404);
+               }
+
+               // fetch user from database given the nickname
+               $r = q("SELECT * FROM `user` WHERE `nickname` = '%s'" .
+                          " AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", 
+                          dbesc($nick));
+               
+               if(!count($r)) {
+                       logger('pubsubhubbub: local account not found: ' . $nick);
+                       http_status_exit(404);
+               }
+
+               $owner = $r[0];
+
+               // abort if user's wall is supposed to be private
+               if ($r[0]['hidewall']) {
+                       logger('pubsubhubbub: local user ' . $nick .
+                                  'has chosen to hide wall, ignoring.');
+                       http_status_exit(403);
+               }
+
+               // get corresponding row from contact table
+               $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `blocked` = 0" .
+                          " AND `pending` = 0 LIMIT 1",
+                          intval($owner['uid']));
+               if(!count($r)) {
+                       logger('pubsubhubbub: contact not found.');
+                       http_status_exit(404);
+               }
+
+               $contact = $r[0];
+
+               // sanity check that topic URLs are the same
+               if(!link_compare($hub_topic, $contact['poll'])) {
+                       logger('pubsubhubbub: hub topic ' . $hub_topic . ' != ' . 
+                                  $contact['poll']);
+                       http_status_exit(404);
+               }
+
+               // do subscriber verification according to the PuSH protocol
+               $hub_challenge = random_string(40);
+               $params = 'hub.mode=' . 
+                       ($subscribe == 1 ? 'subscribe' : 'unsubscribe') .
+                       '&hub.topic=' . urlencode($hub_topic) .
+                       '&hub.challenge=' . $hub_challenge .
+                       '&hub.lease_seconds=604800' .
+                       '&hub.verify_token=' . $hub_verify_token;
+               
+               // lease time is hard coded to one week (in seconds)
+               // we don't actually enforce the lease time because GNU
+               // Social/StatusNet doesn't honour it (yet)
+
+               $body = fetch_url($hub_callback . "?" . $params);
+               $ret = $a->get_curl_code();
+
+               // give up if the HTTP return code wasn't a success (2xx)
+               if ($ret < 200 || $ret > 299) {
+                       logger("pubsubhubbub: subscriber verification at $hub_callback ".
+                                  "returned $ret, ignoring.");
+                       http_status_exit(404);
+               }
+
+               // check that the correct hub_challenge code was echoed back
+               if (trim($body) !== $hub_challenge) {
+                       logger("pubsubhubbub: subscriber did not echo back ".
+                                  "hub.challenge, ignoring.");
+                       logger("\"$hub_challenge\" != \"".trim($body)."\"");
+                       http_status_exit(404);
+               }
+
+               // fetch the old subscription if it exists
+               $r = q("SELECT * FROM `push_subscriber` WHERE `callback_url` = '%s'",
+                 dbesc($hub_callback));
+
+               // delete old subscription if it exists
+               q("DELETE FROM `push_subscriber` WHERE `callback_url` = '%s'",
+                 dbesc($hub_callback));
+
+               if ($subscribe) {
+                       $last_update = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
+                       $push_flag = 0;
+
+                       // if we are just updating an old subscription, keep the
+                       // old values for push and last_update
+                       if (count($r)) {
+                               $last_update = $r[0]['last_update'];
+                               $push_flag = $r[0]['push'];
+                       }
+
+                       // subscribe means adding the row to the table
+                       q("INSERT INTO `push_subscriber` (`uid`, `callback_url`, " .
+                         "`topic`, `nickname`, `push`, `last_update`, `secret`) values " .
+                         "(%d, '%s', '%s', '%s', %d, '%s', '%s')",
+                         intval($owner['uid']),
+                         dbesc($hub_callback),
+                         dbesc($hub_topic),
+                         dbesc($nick),
+                         intval($push_flag),
+                         dbesc($last_update),
+                         dbesc($hub_secret));
+                       logger("pubsubhubbub: successfully subscribed [$hub_callback].");
+               } else {
+                       logger("pubsubhubbub: successfully unsubscribed [$hub_callback].");
+                       // we do nothing here, since the row was already deleted
+               }
+               http_status_exit(202);
+       }
+
+       killme();
+}
+
+?>
index 2d2d2d178b7c5bcfe320bfc8f5a796ca31a596a8..aa4d9b7a39f16cb94193d5178e8e41161298b7d8 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 
-define( 'UPDATE_VERSION' , 1165 );
+define( 'UPDATE_VERSION' , 1166 );
 
 /**
  *
@@ -1509,3 +1509,20 @@ function update_1164() {
 
        return UPDATE_SUCCESS;
 }
+
+function update_1165() {
+       $r = q("CREATE TABLE IF NOT EXISTS `push_subscriber` (
+                       `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+                   `uid` INT NOT NULL,
+               `callback_url` CHAR( 255 ) NOT NULL,
+            `topic` CHAR( 255 ) NOT NULL,
+            `nickname` CHAR( 255 ) NOT NULL,
+            `push` INT NOT NULL,
+            `last_update` DATETIME NOT NULL,
+            `secret` CHAR( 255 ) NOT NULL
+                 ) ENGINE = MYISAM DEFAULT CHARSET=utf8 ");
+       if (!$r)
+               return UPDATE_FAILED;
+
+       return UPDATE_SUCCESS;
+}