Chris@0
|
1 <?php
|
Chris@0
|
2 /**
|
Chris@0
|
3 * Zend Framework (http://framework.zend.com/)
|
Chris@0
|
4 *
|
Chris@0
|
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
|
Chris@0
|
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
|
Chris@0
|
7 * @license http://framework.zend.com/license/new-bsd New BSD License
|
Chris@0
|
8 */
|
Chris@0
|
9
|
Chris@0
|
10 namespace Zend\Feed\PubSubHubbub\Subscriber;
|
Chris@0
|
11
|
Chris@0
|
12 use Zend\Feed\PubSubHubbub;
|
Chris@0
|
13 use Zend\Feed\PubSubHubbub\Exception;
|
Chris@0
|
14 use Zend\Feed\Uri;
|
Chris@0
|
15
|
Chris@0
|
16 class Callback extends PubSubHubbub\AbstractCallback
|
Chris@0
|
17 {
|
Chris@0
|
18 /**
|
Chris@0
|
19 * Contains the content of any feeds sent as updates to the Callback URL
|
Chris@0
|
20 *
|
Chris@0
|
21 * @var string
|
Chris@0
|
22 */
|
Chris@0
|
23 protected $feedUpdate = null;
|
Chris@0
|
24
|
Chris@0
|
25 /**
|
Chris@0
|
26 * Holds a manually set subscription key (i.e. identifies a unique
|
Chris@0
|
27 * subscription) which is typical when it is not passed in the query string
|
Chris@0
|
28 * but is part of the Callback URL path, requiring manual retrieval e.g.
|
Chris@0
|
29 * using a route and the \Zend\Mvc\Router\RouteMatch::getParam() method.
|
Chris@0
|
30 *
|
Chris@0
|
31 * @var string
|
Chris@0
|
32 */
|
Chris@0
|
33 protected $subscriptionKey = null;
|
Chris@0
|
34
|
Chris@0
|
35 /**
|
Chris@0
|
36 * After verification, this is set to the verified subscription's data.
|
Chris@0
|
37 *
|
Chris@0
|
38 * @var array
|
Chris@0
|
39 */
|
Chris@0
|
40 protected $currentSubscriptionData = null;
|
Chris@0
|
41
|
Chris@0
|
42 /**
|
Chris@0
|
43 * Set a subscription key to use for the current callback request manually.
|
Chris@0
|
44 * Required if usePathParameter is enabled for the Subscriber.
|
Chris@0
|
45 *
|
Chris@0
|
46 * @param string $key
|
Chris@0
|
47 * @return \Zend\Feed\PubSubHubbub\Subscriber\Callback
|
Chris@0
|
48 */
|
Chris@0
|
49 public function setSubscriptionKey($key)
|
Chris@0
|
50 {
|
Chris@0
|
51 $this->subscriptionKey = $key;
|
Chris@0
|
52 return $this;
|
Chris@0
|
53 }
|
Chris@0
|
54
|
Chris@0
|
55 /**
|
Chris@0
|
56 * Handle any callback from a Hub Server responding to a subscription or
|
Chris@0
|
57 * unsubscription request. This should be the Hub Server confirming the
|
Chris@0
|
58 * the request prior to taking action on it.
|
Chris@0
|
59 *
|
Chris@0
|
60 * @param array $httpGetData GET data if available and not in $_GET
|
Chris@0
|
61 * @param bool $sendResponseNow Whether to send response now or when asked
|
Chris@0
|
62 * @return void
|
Chris@0
|
63 */
|
Chris@0
|
64 public function handle(array $httpGetData = null, $sendResponseNow = false)
|
Chris@0
|
65 {
|
Chris@0
|
66 if ($httpGetData === null) {
|
Chris@0
|
67 $httpGetData = $_GET;
|
Chris@0
|
68 }
|
Chris@0
|
69
|
Chris@0
|
70 /**
|
Chris@0
|
71 * Handle any feed updates (sorry for the mess :P)
|
Chris@0
|
72 *
|
Chris@0
|
73 * This DOES NOT attempt to process a feed update. Feed updates
|
Chris@0
|
74 * SHOULD be validated/processed by an asynchronous process so as
|
Chris@0
|
75 * to avoid holding up responses to the Hub.
|
Chris@0
|
76 */
|
Chris@0
|
77 $contentType = $this->_getHeader('Content-Type');
|
Chris@0
|
78 if (strtolower($_SERVER['REQUEST_METHOD']) == 'post'
|
Chris@0
|
79 && $this->_hasValidVerifyToken(null, false)
|
Chris@0
|
80 && (stripos($contentType, 'application/atom+xml') === 0
|
Chris@0
|
81 || stripos($contentType, 'application/rss+xml') === 0
|
Chris@0
|
82 || stripos($contentType, 'application/xml') === 0
|
Chris@0
|
83 || stripos($contentType, 'text/xml') === 0
|
Chris@0
|
84 || stripos($contentType, 'application/rdf+xml') === 0)
|
Chris@0
|
85 ) {
|
Chris@0
|
86 $this->setFeedUpdate($this->_getRawBody());
|
Chris@0
|
87 $this->getHttpResponse()->setHeader('X-Hub-On-Behalf-Of', $this->getSubscriberCount());
|
Chris@0
|
88 /**
|
Chris@0
|
89 * Handle any (un)subscribe confirmation requests
|
Chris@0
|
90 */
|
Chris@0
|
91 } elseif ($this->isValidHubVerification($httpGetData)) {
|
Chris@0
|
92 $this->getHttpResponse()->setContent($httpGetData['hub_challenge']);
|
Chris@0
|
93
|
Chris@0
|
94 switch (strtolower($httpGetData['hub_mode'])) {
|
Chris@0
|
95 case 'subscribe':
|
Chris@0
|
96 $data = $this->currentSubscriptionData;
|
Chris@0
|
97 $data['subscription_state'] = PubSubHubbub\PubSubHubbub::SUBSCRIPTION_VERIFIED;
|
Chris@0
|
98 if (isset($httpGetData['hub_lease_seconds'])) {
|
Chris@0
|
99 $data['lease_seconds'] = $httpGetData['hub_lease_seconds'];
|
Chris@0
|
100 }
|
Chris@0
|
101 $this->getStorage()->setSubscription($data);
|
Chris@0
|
102 break;
|
Chris@0
|
103 case 'unsubscribe':
|
Chris@0
|
104 $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData);
|
Chris@0
|
105 $this->getStorage()->deleteSubscription($verifyTokenKey);
|
Chris@0
|
106 break;
|
Chris@0
|
107 default:
|
Chris@0
|
108 throw new Exception\RuntimeException(sprintf(
|
Chris@0
|
109 'Invalid hub_mode ("%s") provided',
|
Chris@0
|
110 $httpGetData['hub_mode']
|
Chris@0
|
111 ));
|
Chris@0
|
112 }
|
Chris@0
|
113 /**
|
Chris@0
|
114 * Hey, C'mon! We tried everything else!
|
Chris@0
|
115 */
|
Chris@0
|
116 } else {
|
Chris@0
|
117 $this->getHttpResponse()->setStatusCode(404);
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120 if ($sendResponseNow) {
|
Chris@0
|
121 $this->sendResponse();
|
Chris@0
|
122 }
|
Chris@0
|
123 }
|
Chris@0
|
124
|
Chris@0
|
125 /**
|
Chris@0
|
126 * Checks validity of the request simply by making a quick pass and
|
Chris@0
|
127 * confirming the presence of all REQUIRED parameters.
|
Chris@0
|
128 *
|
Chris@0
|
129 * @param array $httpGetData
|
Chris@0
|
130 * @return bool
|
Chris@0
|
131 */
|
Chris@0
|
132 public function isValidHubVerification(array $httpGetData)
|
Chris@0
|
133 {
|
Chris@0
|
134 /**
|
Chris@0
|
135 * As per the specification, the hub.verify_token is OPTIONAL. This
|
Chris@0
|
136 * implementation of Pubsubhubbub considers it REQUIRED and will
|
Chris@0
|
137 * always send a hub.verify_token parameter to be echoed back
|
Chris@0
|
138 * by the Hub Server. Therefore, its absence is considered invalid.
|
Chris@0
|
139 */
|
Chris@0
|
140 if (strtolower($_SERVER['REQUEST_METHOD']) !== 'get') {
|
Chris@0
|
141 return false;
|
Chris@0
|
142 }
|
Chris@0
|
143 $required = [
|
Chris@0
|
144 'hub_mode',
|
Chris@0
|
145 'hub_topic',
|
Chris@0
|
146 'hub_challenge',
|
Chris@0
|
147 'hub_verify_token',
|
Chris@0
|
148 ];
|
Chris@0
|
149 foreach ($required as $key) {
|
Chris@12
|
150 if (! array_key_exists($key, $httpGetData)) {
|
Chris@0
|
151 return false;
|
Chris@0
|
152 }
|
Chris@0
|
153 }
|
Chris@0
|
154 if ($httpGetData['hub_mode'] !== 'subscribe'
|
Chris@0
|
155 && $httpGetData['hub_mode'] !== 'unsubscribe'
|
Chris@0
|
156 ) {
|
Chris@0
|
157 return false;
|
Chris@0
|
158 }
|
Chris@0
|
159 if ($httpGetData['hub_mode'] == 'subscribe'
|
Chris@12
|
160 && ! array_key_exists('hub_lease_seconds', $httpGetData)
|
Chris@0
|
161 ) {
|
Chris@0
|
162 return false;
|
Chris@0
|
163 }
|
Chris@12
|
164 if (! Uri::factory($httpGetData['hub_topic'])->isValid()) {
|
Chris@0
|
165 return false;
|
Chris@0
|
166 }
|
Chris@0
|
167
|
Chris@0
|
168 /**
|
Chris@0
|
169 * Attempt to retrieve any Verification Token Key attached to Callback
|
Chris@0
|
170 * URL's path by our Subscriber implementation
|
Chris@0
|
171 */
|
Chris@12
|
172 if (! $this->_hasValidVerifyToken($httpGetData)) {
|
Chris@0
|
173 return false;
|
Chris@0
|
174 }
|
Chris@0
|
175 return true;
|
Chris@0
|
176 }
|
Chris@0
|
177
|
Chris@0
|
178 /**
|
Chris@0
|
179 * Sets a newly received feed (Atom/RSS) sent by a Hub as an update to a
|
Chris@0
|
180 * Topic we've subscribed to.
|
Chris@0
|
181 *
|
Chris@0
|
182 * @param string $feed
|
Chris@0
|
183 * @return \Zend\Feed\PubSubHubbub\Subscriber\Callback
|
Chris@0
|
184 */
|
Chris@0
|
185 public function setFeedUpdate($feed)
|
Chris@0
|
186 {
|
Chris@0
|
187 $this->feedUpdate = $feed;
|
Chris@0
|
188 return $this;
|
Chris@0
|
189 }
|
Chris@0
|
190
|
Chris@0
|
191 /**
|
Chris@0
|
192 * Check if any newly received feed (Atom/RSS) update was received
|
Chris@0
|
193 *
|
Chris@0
|
194 * @return bool
|
Chris@0
|
195 */
|
Chris@0
|
196 public function hasFeedUpdate()
|
Chris@0
|
197 {
|
Chris@0
|
198 if ($this->feedUpdate === null) {
|
Chris@0
|
199 return false;
|
Chris@0
|
200 }
|
Chris@0
|
201 return true;
|
Chris@0
|
202 }
|
Chris@0
|
203
|
Chris@0
|
204 /**
|
Chris@0
|
205 * Gets a newly received feed (Atom/RSS) sent by a Hub as an update to a
|
Chris@0
|
206 * Topic we've subscribed to.
|
Chris@0
|
207 *
|
Chris@0
|
208 * @return string
|
Chris@0
|
209 */
|
Chris@0
|
210 public function getFeedUpdate()
|
Chris@0
|
211 {
|
Chris@0
|
212 return $this->feedUpdate;
|
Chris@0
|
213 }
|
Chris@0
|
214
|
Chris@0
|
215 /**
|
Chris@0
|
216 * Check for a valid verify_token. By default attempts to compare values
|
Chris@0
|
217 * with that sent from Hub, otherwise merely ascertains its existence.
|
Chris@0
|
218 *
|
Chris@0
|
219 * @param array $httpGetData
|
Chris@0
|
220 * @param bool $checkValue
|
Chris@0
|
221 * @return bool
|
Chris@0
|
222 */
|
Chris@12
|
223 // @codingStandardsIgnoreStart
|
Chris@0
|
224 protected function _hasValidVerifyToken(array $httpGetData = null, $checkValue = true)
|
Chris@0
|
225 {
|
Chris@12
|
226 // @codingStandardsIgnoreEnd
|
Chris@0
|
227 $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData);
|
Chris@0
|
228 if (empty($verifyTokenKey)) {
|
Chris@0
|
229 return false;
|
Chris@0
|
230 }
|
Chris@0
|
231 $verifyTokenExists = $this->getStorage()->hasSubscription($verifyTokenKey);
|
Chris@12
|
232 if (! $verifyTokenExists) {
|
Chris@0
|
233 return false;
|
Chris@0
|
234 }
|
Chris@0
|
235 if ($checkValue) {
|
Chris@0
|
236 $data = $this->getStorage()->getSubscription($verifyTokenKey);
|
Chris@0
|
237 $verifyToken = $data['verify_token'];
|
Chris@0
|
238 if ($verifyToken !== hash('sha256', $httpGetData['hub_verify_token'])) {
|
Chris@0
|
239 return false;
|
Chris@0
|
240 }
|
Chris@0
|
241 $this->currentSubscriptionData = $data;
|
Chris@0
|
242 return true;
|
Chris@0
|
243 }
|
Chris@0
|
244 return true;
|
Chris@0
|
245 }
|
Chris@0
|
246
|
Chris@0
|
247 /**
|
Chris@0
|
248 * Attempt to detect the verification token key. This would be passed in
|
Chris@0
|
249 * the Callback URL (which we are handling with this class!) as a URI
|
Chris@0
|
250 * path part (the last part by convention).
|
Chris@0
|
251 *
|
Chris@0
|
252 * @param null|array $httpGetData
|
Chris@0
|
253 * @return false|string
|
Chris@0
|
254 */
|
Chris@12
|
255 // @codingStandardsIgnoreStart
|
Chris@0
|
256 protected function _detectVerifyTokenKey(array $httpGetData = null)
|
Chris@0
|
257 {
|
Chris@12
|
258 // @codingStandardsIgnoreEnd
|
Chris@0
|
259 /**
|
Chris@0
|
260 * Available when sub keys encoding in Callback URL path
|
Chris@0
|
261 */
|
Chris@0
|
262 if (isset($this->subscriptionKey)) {
|
Chris@0
|
263 return $this->subscriptionKey;
|
Chris@0
|
264 }
|
Chris@0
|
265
|
Chris@0
|
266 /**
|
Chris@0
|
267 * Available only if allowed by PuSH 0.2 Hubs
|
Chris@0
|
268 */
|
Chris@0
|
269 if (is_array($httpGetData)
|
Chris@0
|
270 && isset($httpGetData['xhub_subscription'])
|
Chris@0
|
271 ) {
|
Chris@0
|
272 return $httpGetData['xhub_subscription'];
|
Chris@0
|
273 }
|
Chris@0
|
274
|
Chris@0
|
275 /**
|
Chris@0
|
276 * Available (possibly) if corrupted in transit and not part of $_GET
|
Chris@0
|
277 */
|
Chris@0
|
278 $params = $this->_parseQueryString();
|
Chris@0
|
279 if (isset($params['xhub.subscription'])) {
|
Chris@0
|
280 return rawurldecode($params['xhub.subscription']);
|
Chris@0
|
281 }
|
Chris@0
|
282
|
Chris@0
|
283 return false;
|
Chris@0
|
284 }
|
Chris@0
|
285
|
Chris@0
|
286 /**
|
Chris@0
|
287 * Build an array of Query String parameters.
|
Chris@0
|
288 * This bypasses $_GET which munges parameter names and cannot accept
|
Chris@0
|
289 * multiple parameters with the same key.
|
Chris@0
|
290 *
|
Chris@0
|
291 * @return array|void
|
Chris@0
|
292 */
|
Chris@12
|
293 // @codingStandardsIgnoreStart
|
Chris@0
|
294 protected function _parseQueryString()
|
Chris@0
|
295 {
|
Chris@12
|
296 // @codingStandardsIgnoreEnd
|
Chris@0
|
297 $params = [];
|
Chris@0
|
298 $queryString = '';
|
Chris@0
|
299 if (isset($_SERVER['QUERY_STRING'])) {
|
Chris@0
|
300 $queryString = $_SERVER['QUERY_STRING'];
|
Chris@0
|
301 }
|
Chris@0
|
302 if (empty($queryString)) {
|
Chris@0
|
303 return [];
|
Chris@0
|
304 }
|
Chris@0
|
305 $parts = explode('&', $queryString);
|
Chris@0
|
306 foreach ($parts as $kvpair) {
|
Chris@0
|
307 $pair = explode('=', $kvpair);
|
Chris@0
|
308 $key = rawurldecode($pair[0]);
|
Chris@0
|
309 $value = rawurldecode($pair[1]);
|
Chris@0
|
310 if (isset($params[$key])) {
|
Chris@0
|
311 if (is_array($params[$key])) {
|
Chris@0
|
312 $params[$key][] = $value;
|
Chris@0
|
313 } else {
|
Chris@0
|
314 $params[$key] = [$params[$key], $value];
|
Chris@0
|
315 }
|
Chris@0
|
316 } else {
|
Chris@0
|
317 $params[$key] = $value;
|
Chris@0
|
318 }
|
Chris@0
|
319 }
|
Chris@0
|
320 return $params;
|
Chris@0
|
321 }
|
Chris@0
|
322 }
|