]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OStatus/actions/pushhub.php
19599d815f51b2ab1344f6a965976e7f28574ec5
[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         $token = $this->arg('hub.verify_token', null);
87
88         common_log(LOG_DEBUG, __METHOD__ . ": checking sub'd to $feed $callback");
89         if ($this->getSub($feed, $callback)) {
90             // Already subscribed; return 204 per spec.
91             header('HTTP/1.1 204 No Content');
92             common_log(LOG_DEBUG, __METHOD__ . ': already subscribed');
93             return;
94         }
95
96         common_log(LOG_DEBUG, __METHOD__ . ': setting up');
97         $sub = new HubSub();
98         $sub->topic = $feed;
99         $sub->callback = $callback;
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', 'token' => $token);
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      * @fixme background this
135      */
136     function unsubscribe()
137     {
138         $feed = $this->argUrl('hub.topic');
139         $callback = $this->argUrl('hub.callback');
140         $sub = $this->getSub($feed, $callback);
141         
142         if ($sub) {
143             $token = $this->arg('hub.verify_token', null);
144             if ($sub->verify('unsubscribe', $token)) {
145                 $sub->delete();
146                 common_log(LOG_INFO, "PuSH unsubscribed $feed for $callback");
147             } else {
148                 throw new ServerException("Failed PuSH unsubscription: verification failed! $feed for $callback");
149             }
150         } else {
151             throw new ServerException("Failed PuSH unsubscription: not subscribed! $feed for $callback");
152         }
153     }
154
155     /**
156      * Grab and validate a URL from POST parameters.
157      * @throws ServerException for malformed or non-http/https URLs
158      */
159     protected function argUrl($arg)
160     {
161         $url = $this->arg($arg);
162         $params = array('domain_check' => false, // otherwise breaks my local tests :P
163                         'allowed_schemes' => array('http', 'https'));
164         if (Validate::uri($url, $params)) {
165             return $url;
166         } else {
167             throw new ServerException("Invalid URL passed for $arg: '$url'", 400);
168         }
169     }
170
171     /**
172      * Get HubSub subscription record for a given feed & subscriber.
173      *
174      * @param string $feed
175      * @param string $callback
176      * @return mixed HubSub or false
177      */
178     protected function getSub($feed, $callback)
179     {
180         return HubSub::staticGet($feed, $callback);
181     }
182 }
183