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