PageRenderTime 1ms CodeModel.GetById 31ms app.highlight 4ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Feed/Pubsubhubbub/Subscriber/Callback.php

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