subscriptionKey = $key; return $this; } /** * Handle any callback from a Hub Server responding to a subscription or * unsubscription request. This should be the Hub Server confirming the * the request prior to taking action on it. * * @param array $httpGetData GET data if available and not in $_GET * @param bool $sendResponseNow Whether to send response now or when asked * @return void */ public function handle(array $httpGetData = null, $sendResponseNow = false) { if ($httpGetData === null) { $httpGetData = $_GET; } /** * Handle any feed updates (sorry for the mess :P) * * This DOES NOT attempt to process a feed update. Feed updates * SHOULD be validated/processed by an asynchronous process so as * to avoid holding up responses to the Hub. */ $contentType = $this->_getHeader('Content-Type'); if (strtolower($_SERVER['REQUEST_METHOD']) == 'post' && $this->_hasValidVerifyToken(null, false) && (stripos($contentType, 'application/atom+xml') === 0 || stripos($contentType, 'application/rss+xml') === 0 || stripos($contentType, 'application/xml') === 0 || stripos($contentType, 'text/xml') === 0 || stripos($contentType, 'application/rdf+xml') === 0) ) { $this->setFeedUpdate($this->_getRawBody()); $this->getHttpResponse()->setHeader('X-Hub-On-Behalf-Of', $this->getSubscriberCount()); /** * Handle any (un)subscribe confirmation requests */ } elseif ($this->isValidHubVerification($httpGetData)) { $this->getHttpResponse()->setContent($httpGetData['hub_challenge']); switch (strtolower($httpGetData['hub_mode'])) { case 'subscribe': $data = $this->currentSubscriptionData; $data['subscription_state'] = PubSubHubbub\PubSubHubbub::SUBSCRIPTION_VERIFIED; if (isset($httpGetData['hub_lease_seconds'])) { $data['lease_seconds'] = $httpGetData['hub_lease_seconds']; } $this->getStorage()->setSubscription($data); break; case 'unsubscribe': $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData); $this->getStorage()->deleteSubscription($verifyTokenKey); break; default: throw new Exception\RuntimeException(sprintf( 'Invalid hub_mode ("%s") provided', $httpGetData['hub_mode'] )); } /** * Hey, C'mon! We tried everything else! */ } else { $this->getHttpResponse()->setStatusCode(404); } if ($sendResponseNow) { $this->sendResponse(); } } /** * Checks validity of the request simply by making a quick pass and * confirming the presence of all REQUIRED parameters. * * @param array $httpGetData * @return bool */ public function isValidHubVerification(array $httpGetData) { /** * As per the specification, the hub.verify_token is OPTIONAL. This * implementation of Pubsubhubbub considers it REQUIRED and will * always send a hub.verify_token parameter to be echoed back * by the Hub Server. Therefore, its absence is considered invalid. */ if (strtolower($_SERVER['REQUEST_METHOD']) !== 'get') { return false; } $required = [ 'hub_mode', 'hub_topic', 'hub_challenge', 'hub_verify_token', ]; foreach ($required as $key) { if (! array_key_exists($key, $httpGetData)) { return false; } } if ($httpGetData['hub_mode'] !== 'subscribe' && $httpGetData['hub_mode'] !== 'unsubscribe' ) { return false; } if ($httpGetData['hub_mode'] == 'subscribe' && ! array_key_exists('hub_lease_seconds', $httpGetData) ) { return false; } if (! Uri::factory($httpGetData['hub_topic'])->isValid()) { return false; } /** * Attempt to retrieve any Verification Token Key attached to Callback * URL's path by our Subscriber implementation */ if (! $this->_hasValidVerifyToken($httpGetData)) { return false; } return true; } /** * Sets a newly received feed (Atom/RSS) sent by a Hub as an update to a * Topic we've subscribed to. * * @param string $feed * @return \Zend\Feed\PubSubHubbub\Subscriber\Callback */ public function setFeedUpdate($feed) { $this->feedUpdate = $feed; return $this; } /** * Check if any newly received feed (Atom/RSS) update was received * * @return bool */ public function hasFeedUpdate() { if ($this->feedUpdate === null) { return false; } return true; } /** * Gets a newly received feed (Atom/RSS) sent by a Hub as an update to a * Topic we've subscribed to. * * @return string */ public function getFeedUpdate() { return $this->feedUpdate; } /** * Check for a valid verify_token. By default attempts to compare values * with that sent from Hub, otherwise merely ascertains its existence. * * @param array $httpGetData * @param bool $checkValue * @return bool */ // @codingStandardsIgnoreStart protected function _hasValidVerifyToken(array $httpGetData = null, $checkValue = true) { // @codingStandardsIgnoreEnd $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData); if (empty($verifyTokenKey)) { return false; } $verifyTokenExists = $this->getStorage()->hasSubscription($verifyTokenKey); if (! $verifyTokenExists) { return false; } if ($checkValue) { $data = $this->getStorage()->getSubscription($verifyTokenKey); $verifyToken = $data['verify_token']; if ($verifyToken !== hash('sha256', $httpGetData['hub_verify_token'])) { return false; } $this->currentSubscriptionData = $data; return true; } return true; } /** * Attempt to detect the verification token key. This would be passed in * the Callback URL (which we are handling with this class!) as a URI * path part (the last part by convention). * * @param null|array $httpGetData * @return false|string */ // @codingStandardsIgnoreStart protected function _detectVerifyTokenKey(array $httpGetData = null) { // @codingStandardsIgnoreEnd /** * Available when sub keys encoding in Callback URL path */ if (isset($this->subscriptionKey)) { return $this->subscriptionKey; } /** * Available only if allowed by PuSH 0.2 Hubs */ if (is_array($httpGetData) && isset($httpGetData['xhub_subscription']) ) { return $httpGetData['xhub_subscription']; } /** * Available (possibly) if corrupted in transit and not part of $_GET */ $params = $this->_parseQueryString(); if (isset($params['xhub.subscription'])) { return rawurldecode($params['xhub.subscription']); } return false; } /** * Build an array of Query String parameters. * This bypasses $_GET which munges parameter names and cannot accept * multiple parameters with the same key. * * @return array|void */ // @codingStandardsIgnoreStart protected function _parseQueryString() { // @codingStandardsIgnoreEnd $params = []; $queryString = ''; if (isset($_SERVER['QUERY_STRING'])) { $queryString = $_SERVER['QUERY_STRING']; } if (empty($queryString)) { return []; } $parts = explode('&', $queryString); foreach ($parts as $kvpair) { $pair = explode('=', $kvpair); $key = rawurldecode($pair[0]); $value = rawurldecode($pair[1]); if (isset($params[$key])) { if (is_array($params[$key])) { $params[$key][] = $value; } else { $params[$key] = [$params[$key], $value]; } } else { $params[$key] = $value; } } return $params; } }