annotate vendor/zendframework/zend-feed/src/PubSubHubbub/Subscriber/Callback.php @ 19:fa3358dc1485 tip

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