]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OStatus/actions/pushhub.php
OStatus: fix up some recent regressions in subscription setup; fix state checks and...
[quix0rs-gnu-social.git] / plugins / OStatus / actions / pushhub.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2010, StatusNet, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /**
21  * Integrated PuSH hub; lets us only ping them what need it.
22  * @package Hub
23  * @maintainer Brion Vibber <brion@status.net>
24  */
25
26 /**
27
28
29 Things to consider...
30 * should we purge incomplete subscriptions that never get a verification pingback?
31 * when can we send subscription renewal checks?
32     - at next send time probably ok
33 * when can we handle trimming of subscriptions?
34     - at next send time probably ok
35 * should we keep a fail count?
36
37 */
38
39
40 class PushHubAction extends Action
41 {
42     function arg($arg, $def=null)
43     {
44         // PHP converts '.'s in incoming var names to '_'s.
45         // It also merges multiple values, which'll break hub.verify and hub.topic for publishing
46         // @fixme handle multiple args
47         $arg = str_replace('hub.', 'hub_', $arg);
48         return parent::arg($arg, $def);
49     }
50
51     function prepare($args)
52     {
53         StatusNet::setApi(true); // reduce exception reports to aid in debugging
54         return parent::prepare($args);
55     }
56
57     function handle()
58     {
59         $mode = $this->trimmed('hub.mode');
60         switch ($mode) {
61         case "subscribe":
62             $this->subscribe();
63             break;
64         case "unsubscribe":
65             $this->unsubscribe();
66             break;
67         case "publish":
68             throw new ServerException("Publishing outside feeds not supported.", 400);
69         default:
70             throw new ServerException("Unrecognized mode '$mode'.", 400);
71         }
72     }
73
74     /**
75      * Process a PuSH feed subscription request.
76      *
77      * HTTP return codes:
78      *   202 Accepted - request saved and awaiting verification
79      *   204 No Content - already subscribed
80      *   403 Forbidden - rejecting this (not specifically spec'd)
81      */
82     function subscribe()
83     {
84         $feed = $this->argUrl('hub.topic');
85         $callback = $this->argUrl('hub.callback');
86
87         common_log(LOG_DEBUG, __METHOD__ . ": checking sub'd to $feed $callback");
88         if ($this->getSub($feed, $callback)) {
89             // Already subscribed; return 204 per spec.
90             header('HTTP/1.1 204 No Content');
91             common_log(LOG_DEBUG, __METHOD__ . ': already subscribed');
92             return;
93         }
94
95         common_log(LOG_DEBUG, __METHOD__ . ': setting up');
96         $sub = new HubSub();
97         $sub->topic = $feed;
98         $sub->callback = $callback;
99         $sub->verify_token = $this->arg('hub.verify_token', null);
100         $sub->secret = $this->arg('hub.secret', null);
101         if (strlen($sub->secret) > 200) {
102             throw new ClientException("hub.secret must be no longer than 200 chars", 400);
103         }
104         $sub->setLease(intval($this->arg('hub.lease_seconds')));
105
106         // @fixme check for feeds we don't manage
107         // @fixme check the verification mode, might want a return immediately?
108
109         common_log(LOG_DEBUG, __METHOD__ . ': inserting');
110         $ok = $sub->insert();
111         
112         if (!$ok) {
113             throw new ServerException("Failed to save subscription record", 500);
114         }
115
116         // @fixme check errors ;)
117
118         $data = array('sub' => $sub, 'mode' => 'subscribe');
119         $qm = QueueManager::get();
120         $qm->enqueue($data, 'hubverify');
121         
122         header('HTTP/1.1 202 Accepted');
123         common_log(LOG_DEBUG, __METHOD__ . ': done');
124     }
125
126     /**
127      * Process a PuSH feed unsubscription request.
128      *
129      * HTTP return codes:
130      *   202 Accepted - request saved and awaiting verification
131      *   204 No Content - already subscribed
132      *   400 Bad Request - invalid params or rejected feed
133      */
134     function unsubscribe()
135     {
136         $feed = $this->argUrl('hub.topic');
137         $callback = $this->argUrl('hub.callback');
138         $sub = $this->getSub($feed, $callback);
139         
140         if ($sub) {
141             if ($sub->verify('unsubscribe')) {
142                 $sub->delete();
143                 common_log(LOG_INFO, "PuSH unsubscribed $feed for $callback");
144             } else {
145                 throw new ServerException("Failed PuSH unsubscription: verification failed! $feed for $callback");
146             }
147         } else {
148             throw new ServerException("Failed PuSH unsubscription: not subscribed! $feed for $callback");
149         }
150     }
151
152     /**
153      * Grab and validate a URL from POST parameters.
154      * @throws ServerException for malformed or non-http/https URLs
155      */
156     protected function argUrl($arg)
157     {
158         $url = $this->arg($arg);
159         $params = array('domain_check' => false, // otherwise breaks my local tests :P
160                         'allowed_schemes' => array('http', 'https'));
161         if (Validate::uri($url, $params)) {
162             return $url;
163         } else {
164             throw new ServerException("Invalid URL passed for $arg: '$url'", 400);
165         }
166     }
167
168     /**
169      * Get HubSub subscription record for a given feed & subscriber.
170      *
171      * @param string $feed
172      * @param string $callback
173      * @return mixed HubSub or false
174      */
175     protected function getSub($feed, $callback)
176     {
177         return HubSub::staticGet($feed, $callback);
178     }
179 }
180