3 * Zend Framework (http://framework.zend.com/)
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
10 namespace Zend\Feed\PubSubHubbub\Subscriber;
12 use Zend\Feed\PubSubHubbub;
13 use Zend\Feed\PubSubHubbub\Exception;
16 class Callback extends PubSubHubbub\AbstractCallback
19 * Contains the content of any feeds sent as updates to the Callback URL
23 protected $feedUpdate = null;
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.
33 protected $subscriptionKey = null;
36 * After verification, this is set to the verified subscription's data.
40 protected $currentSubscriptionData = null;
43 * Set a subscription key to use for the current callback request manually.
44 * Required if usePathParameter is enabled for the Subscriber.
47 * @return \Zend\Feed\PubSubHubbub\Subscriber\Callback
49 public function setSubscriptionKey($key)
51 $this->subscriptionKey = $key;
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.
60 * @param array $httpGetData GET data if available and not in $_GET
61 * @param bool $sendResponseNow Whether to send response now or when asked
64 public function handle(array $httpGetData = null, $sendResponseNow = false)
66 if ($httpGetData === null) {
71 * Handle any feed updates (sorry for the mess :P)
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.
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)
86 $this->setFeedUpdate($this->_getRawBody());
87 $this->getHttpResponse()->setHeader('X-Hub-On-Behalf-Of', $this->getSubscriberCount());
89 * Handle any (un)subscribe confirmation requests
91 } elseif ($this->isValidHubVerification($httpGetData)) {
92 $this->getHttpResponse()->setContent($httpGetData['hub_challenge']);
94 switch (strtolower($httpGetData['hub_mode'])) {
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'];
101 $this->getStorage()->setSubscription($data);
104 $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData);
105 $this->getStorage()->deleteSubscription($verifyTokenKey);
108 throw new Exception\RuntimeException(sprintf(
109 'Invalid hub_mode ("%s") provided',
110 $httpGetData['hub_mode']
114 * Hey, C'mon! We tried everything else!
117 $this->getHttpResponse()->setStatusCode(404);
120 if ($sendResponseNow) {
121 $this->sendResponse();
126 * Checks validity of the request simply by making a quick pass and
127 * confirming the presence of all REQUIRED parameters.
129 * @param array $httpGetData
132 public function isValidHubVerification(array $httpGetData)
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.
140 if (strtolower($_SERVER['REQUEST_METHOD']) !== 'get') {
149 foreach ($required as $key) {
150 if (! array_key_exists($key, $httpGetData)) {
154 if ($httpGetData['hub_mode'] !== 'subscribe'
155 && $httpGetData['hub_mode'] !== 'unsubscribe'
159 if ($httpGetData['hub_mode'] == 'subscribe'
160 && ! array_key_exists('hub_lease_seconds', $httpGetData)
164 if (! Uri::factory($httpGetData['hub_topic'])->isValid()) {
169 * Attempt to retrieve any Verification Token Key attached to Callback
170 * URL's path by our Subscriber implementation
172 if (! $this->_hasValidVerifyToken($httpGetData)) {
179 * Sets a newly received feed (Atom/RSS) sent by a Hub as an update to a
180 * Topic we've subscribed to.
182 * @param string $feed
183 * @return \Zend\Feed\PubSubHubbub\Subscriber\Callback
185 public function setFeedUpdate($feed)
187 $this->feedUpdate = $feed;
192 * Check if any newly received feed (Atom/RSS) update was received
196 public function hasFeedUpdate()
198 if ($this->feedUpdate === null) {
205 * Gets a newly received feed (Atom/RSS) sent by a Hub as an update to a
206 * Topic we've subscribed to.
210 public function getFeedUpdate()
212 return $this->feedUpdate;
216 * Check for a valid verify_token. By default attempts to compare values
217 * with that sent from Hub, otherwise merely ascertains its existence.
219 * @param array $httpGetData
220 * @param bool $checkValue
223 // @codingStandardsIgnoreStart
224 protected function _hasValidVerifyToken(array $httpGetData = null, $checkValue = true)
226 // @codingStandardsIgnoreEnd
227 $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData);
228 if (empty($verifyTokenKey)) {
231 $verifyTokenExists = $this->getStorage()->hasSubscription($verifyTokenKey);
232 if (! $verifyTokenExists) {
236 $data = $this->getStorage()->getSubscription($verifyTokenKey);
237 $verifyToken = $data['verify_token'];
238 if ($verifyToken !== hash('sha256', $httpGetData['hub_verify_token'])) {
241 $this->currentSubscriptionData = $data;
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).
252 * @param null|array $httpGetData
253 * @return false|string
255 // @codingStandardsIgnoreStart
256 protected function _detectVerifyTokenKey(array $httpGetData = null)
258 // @codingStandardsIgnoreEnd
260 * Available when sub keys encoding in Callback URL path
262 if (isset($this->subscriptionKey)) {
263 return $this->subscriptionKey;
267 * Available only if allowed by PuSH 0.2 Hubs
269 if (is_array($httpGetData)
270 && isset($httpGetData['xhub_subscription'])
272 return $httpGetData['xhub_subscription'];
276 * Available (possibly) if corrupted in transit and not part of $_GET
278 $params = $this->_parseQueryString();
279 if (isset($params['xhub.subscription'])) {
280 return rawurldecode($params['xhub.subscription']);
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.
293 // @codingStandardsIgnoreStart
294 protected function _parseQueryString()
296 // @codingStandardsIgnoreEnd
299 if (isset($_SERVER['QUERY_STRING'])) {
300 $queryString = $_SERVER['QUERY_STRING'];
302 if (empty($queryString)) {
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;
314 $params[$key] = [$params[$key], $value];
317 $params[$key] = $value;