]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - plugins/OStatus/actions/pushhub.php
Minor function definitions so they match Action parent
[quix0rs-gnu-social.git] / plugins / OStatus / actions / pushhub.php
index 13ec09d52871634b3bb864742bc038994923b564..c883647e681ef8768b58694cf9458ca49b81e894 100644 (file)
  * @maintainer Brion Vibber <brion@status.net>
  */
 
-/**
-
-
-Things to consider...
-* should we purge incomplete subscriptions that never get a verification pingback?
-* when can we send subscription renewal checks?
-    - at next send time probably ok
-* when can we handle trimming of subscriptions?
-    - at next send time probably ok
-* should we keep a fail count?
-
-*/
-
+if (!defined('STATUSNET')) {
+    exit(1);
+}
 
+/**
+ * Things to consider...
+ * should we purge incomplete subscriptions that never get a verification pingback?
+ * when can we send subscription renewal checks?
+ *    - at next send time probably ok
+ * when can we handle trimming of subscriptions?
+ *    - at next send time probably ok
+ * should we keep a fail count?
+ */
 class PushHubAction extends Action
 {
     function arg($arg, $def=null)
@@ -48,120 +47,169 @@ class PushHubAction extends Action
         return parent::arg($arg, $def);
     }
 
-    function prepare($args)
+    protected function prepare($args)
     {
         StatusNet::setApi(true); // reduce exception reports to aid in debugging
         return parent::prepare($args);
     }
 
-    function handle()
+    protected function handle()
     {
         $mode = $this->trimmed('hub.mode');
         switch ($mode) {
         case "subscribe":
-            $this->subscribe();
-            break;
         case "unsubscribe":
-            $this->unsubscribe();
+            $this->subunsub($mode);
             break;
         case "publish":
-            throw new ServerException("Publishing outside feeds not supported.", 400);
+            // TRANS: Client exception.
+            throw new ClientException(_m('Publishing outside feeds not supported.'), 400);
         default:
-            throw new ServerException("Unrecognized mode '$mode'.", 400);
+            // TRANS: Client exception. %s is a mode.
+            throw new ClientException(sprintf(_m('Unrecognized mode "%s".'),$mode), 400);
         }
     }
 
     /**
-     * Process a PuSH feed subscription request.
+     * Process a request for a new or modified PuSH feed subscription.
+     * If asynchronous verification is requested, updates won't be saved immediately.
      *
      * HTTP return codes:
      *   202 Accepted - request saved and awaiting verification
      *   204 No Content - already subscribed
-     *   403 Forbidden - rejecting this (not specifically spec'd)
+     *   400 Bad Request - rejecting this (not specifically spec'd)
      */
-    function subscribe()
+    function subunsub($mode)
     {
-        $feed = $this->argUrl('hub.topic');
         $callback = $this->argUrl('hub.callback');
 
-        common_log(LOG_DEBUG, __METHOD__ . ": checking sub'd to $feed $callback");
-        if ($this->getSub($feed, $callback)) {
-            // Already subscribed; return 204 per spec.
-            header('HTTP/1.1 204 No Content');
-            common_log(LOG_DEBUG, __METHOD__ . ': already subscribed');
-            return;
+        $topic = $this->argUrl('hub.topic');
+        if (!$this->recognizedFeed($topic)) {
+            // TRANS: Client exception. %s is a topic.
+            throw new ClientException(sprintf(_m('Unsupported hub.topic %s this hub only serves local user and group Atom feeds.'),$topic));
         }
 
-        common_log(LOG_DEBUG, __METHOD__ . ': setting up');
-        $sub = new HubSub();
-        $sub->topic = $feed;
-        $sub->callback = $callback;
-        $sub->verify_token = $this->arg('hub.verify_token', null);
-        $sub->secret = $this->arg('hub.secret', null);
-        if (strlen($sub->secret) > 200) {
-            throw new ClientException("hub.secret must be no longer than 200 chars", 400);
+        $verify = $this->arg('hub.verify'); // @fixme may be multiple
+        if ($verify != 'sync' && $verify != 'async') {
+            // TRANS: Client exception. %s is sync or async.
+            throw new ClientException(sprintf(_m('Invalid hub.verify "%s". It must be sync or async.'),$verify));
         }
-        $sub->setLease(intval($this->arg('hub.lease_seconds')));
 
-        // @fixme check for feeds we don't manage
-        // @fixme check the verification mode, might want a return immediately?
+        $lease = $this->arg('hub.lease_seconds', null);
+        if ($mode == 'subscribe' && $lease != '' && !preg_match('/^\d+$/', $lease)) {
+            // TRANS: Client exception. %s is the invalid lease value.
+            throw new ClientException(sprintf(_m('Invalid hub.lease "%s". It must be empty or positive integer.'),$lease));
+        }
 
-        common_log(LOG_DEBUG, __METHOD__ . ': inserting');
-        $ok = $sub->insert();
-        
-        if (!$ok) {
-            throw new ServerException("Failed to save subscription record", 500);
+        $token = $this->arg('hub.verify_token', null);
+
+        $secret = $this->arg('hub.secret', null);
+        if ($secret != '' && strlen($secret) >= 200) {
+            // TRANS: Client exception. %s is the invalid hub secret.
+            throw new ClientException(sprintf(_m('Invalid hub.secret "%s". It must be under 200 bytes.'),$secret));
         }
 
-        // @fixme check errors ;)
+        $sub = HubSub::getByHashkey($topic, $callback);
+        if (!$sub) {
+            // Creating a new one!
+            $sub = new HubSub();
+            $sub->topic = $topic;
+            $sub->callback = $callback;
+        }
+        if ($mode == 'subscribe') {
+            if ($secret) {
+                $sub->secret = $secret;
+            }
+            if ($lease) {
+                $sub->setLease(intval($lease));
+            }
+        }
 
-        $data = array('sub' => $sub, 'mode' => 'subscribe');
-        $qm = QueueManager::get();
-        $qm->enqueue($data, 'hubverify');
-        
-        header('HTTP/1.1 202 Accepted');
-        common_log(LOG_DEBUG, __METHOD__ . ': done');
+        if (!common_config('queue', 'enabled')) {
+            // Won't be able to background it.
+            $verify = 'sync';
+        }
+        if ($verify == 'async') {
+            $sub->scheduleVerify($mode, $token);
+            header('HTTP/1.1 202 Accepted');
+        } else {
+            $sub->verify($mode, $token);
+            header('HTTP/1.1 204 No Content');
+        }
     }
 
     /**
-     * Process a PuSH feed unsubscription request.
+     * Check whether the given URL represents one of our canonical
+     * user or group Atom feeds.
      *
-     * HTTP return codes:
-     *   202 Accepted - request saved and awaiting verification
-     *   204 No Content - already subscribed
-     *   400 Bad Request - invalid params or rejected feed
+     * @param string $feed URL
+     * @return boolean true if it matches
      */
-    function unsubscribe()
+    function recognizedFeed($feed)
     {
-        $feed = $this->argUrl('hub.topic');
-        $callback = $this->argUrl('hub.callback');
-        $sub = $this->getSub($feed, $callback);
-        
-        if ($sub) {
-            if ($sub->verify('unsubscribe')) {
-                $sub->delete();
-                common_log(LOG_INFO, "PuSH unsubscribed $feed for $callback");
-            } else {
-                throw new ServerException("Failed PuSH unsubscription: verification failed! $feed for $callback");
+        $matches = array();
+        if (preg_match('!/(\d+)\.atom$!', $feed, $matches)) {
+            $id = $matches[1];
+            $params = array('id' => $id, 'format' => 'atom');
+            $userFeed = common_local_url('ApiTimelineUser', $params);
+            $groupFeed = common_local_url('ApiTimelineGroup', $params);
+
+            if ($feed == $userFeed) {
+                $user = User::getKV('id', $id);
+                if (!$user) {
+                    // TRANS: Client exception. %s is a feed URL.
+                    throw new ClientException(sprintt(_m('Invalid hub.topic "%s". User does not exist.'),$feed));
+                } else {
+                    return true;
+                }
             }
-        } else {
-            throw new ServerException("Failed PuSH unsubscription: not subscribed! $feed for $callback");
+            if ($feed == $groupFeed) {
+                $user = User_group::getKV('id', $id);
+                if (!$user) {
+                    // TRANS: Client exception. %s is a feed URL.
+                    throw new ClientException(sprintf(_m('Invalid hub.topic "%s". Group does not exist.'),$feed));
+                } else {
+                    return true;
+                }
+            }
+        } else if (preg_match('!/(\d+)/lists/(\d+)/statuses\.atom$!', $feed, $matches)) {
+            $user = $matches[1];
+            $id = $matches[2];
+            $params = array('user' => $user, 'id' => $id, 'format' => 'atom');
+            $listFeed = common_local_url('ApiTimelineList', $params);
+
+            if ($feed == $listFeed) {
+                $list = Profile_list::getKV('id', $id);
+                $user = User::getKV('id', $user);
+                if (!$list || !$user || $list->tagger != $user->id) {
+                    // TRANS: Client exception. %s is a feed URL.
+                    throw new ClientException(sprintf(_m('Invalid hub.topic %s; list does not exist.'),$feed));
+                } else {
+                    return true;
+                }
+            }
+            common_log(LOG_DEBUG, "Not a user, group or people tag feed? $feed $userFeed $groupFeed $listFeed");
         }
+        common_log(LOG_DEBUG, "LOST $feed");
+        return false;
     }
 
     /**
      * Grab and validate a URL from POST parameters.
-     * @throws ServerException for malformed or non-http/https URLs
+     * @throws ClientException for malformed or non-http/https URLs
      */
     protected function argUrl($arg)
     {
         $url = $this->arg($arg);
         $params = array('domain_check' => false, // otherwise breaks my local tests :P
                         'allowed_schemes' => array('http', 'https'));
-        if (Validate::uri($url, $params)) {
+        $validate = new Validate;
+        if ($validate->uri($url, $params)) {
             return $url;
         } else {
-            throw new ServerException("Invalid URL passed for $arg: '$url'", 400);
+            // TRANS: Client exception.
+            // TRANS: %1$s is this argument to the method this exception occurs in, %2$s is a URL.
+            throw new ClientException(sprintf(_m('Invalid URL passed for %1$s: "%2$s"'),$arg,$url));
         }
     }
 
@@ -174,7 +222,6 @@ class PushHubAction extends Action
      */
     protected function getSub($feed, $callback)
     {
-        return HubSub::staticGet($feed, $callback);
+        return HubSub::getByHashkey($feed, $callback);
     }
 }
-