Version 1
[yaffs-website] / vendor / zendframework / zend-feed / src / PubSubHubbub / Subscriber / Callback.php
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     // @codingStandardsIgnoreStart
224     protected function _hasValidVerifyToken(array $httpGetData = null, $checkValue = true)
225     {
226         // @codingStandardsIgnoreEnd
227         $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData);
228         if (empty($verifyTokenKey)) {
229             return false;
230         }
231         $verifyTokenExists = $this->getStorage()->hasSubscription($verifyTokenKey);
232         if (! $verifyTokenExists) {
233             return false;
234         }
235         if ($checkValue) {
236             $data = $this->getStorage()->getSubscription($verifyTokenKey);
237             $verifyToken = $data['verify_token'];
238             if ($verifyToken !== hash('sha256', $httpGetData['hub_verify_token'])) {
239                 return false;
240             }
241             $this->currentSubscriptionData = $data;
242             return true;
243         }
244         return true;
245     }
246
247     /**
248      * Attempt to detect the verification token key. This would be passed in
249      * the Callback URL (which we are handling with this class!) as a URI
250      * path part (the last part by convention).
251      *
252      * @param  null|array $httpGetData
253      * @return false|string
254      */
255     // @codingStandardsIgnoreStart
256     protected function _detectVerifyTokenKey(array $httpGetData = null)
257     {
258         // @codingStandardsIgnoreEnd
259         /**
260          * Available when sub keys encoding in Callback URL path
261          */
262         if (isset($this->subscriptionKey)) {
263             return $this->subscriptionKey;
264         }
265
266         /**
267          * Available only if allowed by PuSH 0.2 Hubs
268          */
269         if (is_array($httpGetData)
270             && isset($httpGetData['xhub_subscription'])
271         ) {
272             return $httpGetData['xhub_subscription'];
273         }
274
275         /**
276          * Available (possibly) if corrupted in transit and not part of $_GET
277          */
278         $params = $this->_parseQueryString();
279         if (isset($params['xhub.subscription'])) {
280             return rawurldecode($params['xhub.subscription']);
281         }
282
283         return false;
284     }
285
286     /**
287      * Build an array of Query String parameters.
288      * This bypasses $_GET which munges parameter names and cannot accept
289      * multiple parameters with the same key.
290      *
291      * @return array|void
292      */
293     // @codingStandardsIgnoreStart
294     protected function _parseQueryString()
295     {
296         // @codingStandardsIgnoreEnd
297         $params      = [];
298         $queryString = '';
299         if (isset($_SERVER['QUERY_STRING'])) {
300             $queryString = $_SERVER['QUERY_STRING'];
301         }
302         if (empty($queryString)) {
303             return [];
304         }
305         $parts = explode('&', $queryString);
306         foreach ($parts as $kvpair) {
307             $pair  = explode('=', $kvpair);
308             $key   = rawurldecode($pair[0]);
309             $value = rawurldecode($pair[1]);
310             if (isset($params[$key])) {
311                 if (is_array($params[$key])) {
312                     $params[$key][] = $value;
313                 } else {
314                     $params[$key] = [$params[$key], $value];
315                 }
316             } else {
317                 $params[$key] = $value;
318             }
319         }
320         return $params;
321     }
322 }