comparison vendor/zendframework/zend-feed/src/PubSubHubbub/Subscriber/Callback.php @ 0:c75dbcec494b

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