]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OStatus/actions/pushhub.php
Merge branch '0.9.x' into twitstream
[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 if (!defined('STATUSNET')) {
27     exit(1);
28 }
29
30 /**
31
32
33 Things to consider...
34 * should we purge incomplete subscriptions that never get a verification pingback?
35 * when can we send subscription renewal checks?
36     - at next send time probably ok
37 * when can we handle trimming of subscriptions?
38     - at next send time probably ok
39 * should we keep a fail count?
40
41 */
42
43 class PushHubAction extends Action
44 {
45     function arg($arg, $def=null)
46     {
47         // PHP converts '.'s in incoming var names to '_'s.
48         // It also merges multiple values, which'll break hub.verify and hub.topic for publishing
49         // @fixme handle multiple args
50         $arg = str_replace('hub.', 'hub_', $arg);
51         return parent::arg($arg, $def);
52     }
53
54     function prepare($args)
55     {
56         StatusNet::setApi(true); // reduce exception reports to aid in debugging
57         return parent::prepare($args);
58     }
59
60     function handle()
61     {
62         $mode = $this->trimmed('hub.mode');
63         switch ($mode) {
64         case "subscribe":
65         case "unsubscribe":
66             $this->subunsub($mode);
67             break;
68         case "publish":
69             // TRANS: Client exception.
70             throw new ClientException(_m('Publishing outside feeds not supported.'), 400);
71         default:
72             // TRANS: Client exception. %s is a mode.
73             throw new ClientException(sprintf(_m('Unrecognized mode "%s".'),$mode), 400);
74         }
75     }
76
77     /**
78      * Process a request for a new or modified PuSH feed subscription.
79      * If asynchronous verification is requested, updates won't be saved immediately.
80      *
81      * HTTP return codes:
82      *   202 Accepted - request saved and awaiting verification
83      *   204 No Content - already subscribed
84      *   400 Bad Request - rejecting this (not specifically spec'd)
85      */
86     function subunsub($mode)
87     {
88         $callback = $this->argUrl('hub.callback');
89
90         $topic = $this->argUrl('hub.topic');
91         if (!$this->recognizedFeed($topic)) {
92             // TRANS: Client exception. %s is a topic.
93             throw new ClientException(sprintf(_m('Unsupported hub.topic %s this hub only serves local user and group Atom feeds.'),$topic));
94         }
95
96         $verify = $this->arg('hub.verify'); // @fixme may be multiple
97         if ($verify != 'sync' && $verify != 'async') {
98             // TRANS: Client exception.
99             throw new ClientException(sprintf(_m('Invalid hub.verify "%s". It must be sync or async.'),$verify));
100         }
101
102         $lease = $this->arg('hub.lease_seconds', null);
103         if ($mode == 'subscribe' && $lease != '' && !preg_match('/^\d+$/', $lease)) {
104             // TRANS: Client exception.
105             throw new ClientException(sprintf(_m('Invalid hub.lease "%s". It must be empty or positive integer.'),$lease));
106         }
107
108         $token = $this->arg('hub.verify_token', null);
109
110         $secret = $this->arg('hub.secret', null);
111         if ($secret != '' && strlen($secret) >= 200) {
112             // TRANS: Client exception.
113             throw new ClientException(sprintf(_m('Invalid hub.secret "%s". It must be under 200 bytes.'),$secret));
114         }
115
116         $sub = HubSub::staticGet($topic, $callback);
117         if (!$sub) {
118             // Creating a new one!
119             $sub = new HubSub();
120             $sub->topic = $topic;
121             $sub->callback = $callback;
122         }
123         if ($mode == 'subscribe') {
124             if ($secret) {
125                 $sub->secret = $secret;
126             }
127             if ($lease) {
128                 $sub->setLease(intval($lease));
129             }
130         }
131
132         if (!common_config('queue', 'enabled')) {
133             // Won't be able to background it.
134             $verify = 'sync';
135         }
136         if ($verify == 'async') {
137             $sub->scheduleVerify($mode, $token);
138             header('HTTP/1.1 202 Accepted');
139         } else {
140             $sub->verify($mode, $token);
141             header('HTTP/1.1 204 No Content');
142         }
143     }
144
145     /**
146      * Check whether the given URL represents one of our canonical
147      * user or group Atom feeds.
148      *
149      * @param string $feed URL
150      * @return boolean true if it matches
151      */
152     function recognizedFeed($feed)
153     {
154         $matches = array();
155         if (preg_match('!/(\d+)\.atom$!', $feed, $matches)) {
156             $id = $matches[1];
157             $params = array('id' => $id, 'format' => 'atom');
158             $userFeed = common_local_url('ApiTimelineUser', $params);
159             $groupFeed = common_local_url('ApiTimelineGroup', $params);
160
161             if ($feed == $userFeed) {
162                 $user = User::staticGet('id', $id);
163                 if (!$user) {
164                     // TRANS: Client exception.
165                     throw new ClientException(sprintt(_m('Invalid hub.topic "%s". User doesn\'t exist.'),$feed));
166                 } else {
167                     return true;
168                 }
169             }
170             if ($feed == $groupFeed) {
171                 $user = User_group::staticGet('id', $id);
172                 if (!$user) {
173                     // TRANS: Client exception.
174                     throw new ClientException(sprintf(_m('Invalid hub.topic "%s". Group doesn\'t exist.'),$feed));
175                 } else {
176                     return true;
177                 }
178             }
179             common_log(LOG_DEBUG, "Not a user or group feed? $feed $userFeed $groupFeed");
180         }
181         common_log(LOG_DEBUG, "LOST $feed");
182         return false;
183     }
184
185     /**
186      * Grab and validate a URL from POST parameters.
187      * @throws ClientException for malformed or non-http/https URLs
188      */
189     protected function argUrl($arg)
190     {
191         $url = $this->arg($arg);
192         $params = array('domain_check' => false, // otherwise breaks my local tests :P
193                         'allowed_schemes' => array('http', 'https'));
194         if (Validate::uri($url, $params)) {
195             return $url;
196         } else {
197             // TRANS: Client exception.
198             // TRANS: %1$s is this argument to the method this exception occurs in, %2$s is a URL.
199             throw new ClientException(sprintf(_m('Invalid URL passed for %1$s: "%2$s"'),$arg,$url));
200         }
201     }
202
203     /**
204      * Get HubSub subscription record for a given feed & subscriber.
205      *
206      * @param string $feed
207      * @param string $callback
208      * @return mixed HubSub or false
209      */
210     protected function getSub($feed, $callback)
211     {
212         return HubSub::staticGet($feed, $callback);
213     }
214 }