File indexing completed on 2024-12-29 05:27:36
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Feed_Pubsubhubbub 0017 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0018 * @license http://framework.zend.com/license/new-bsd New BSD License 0019 * @version $Id$ 0020 */ 0021 0022 /** 0023 * @see Zend_Feed_Pubsubhubbub 0024 */ 0025 // require_once 'Zend/Feed/Pubsubhubbub.php'; 0026 0027 /** 0028 * @see Zend_Feed_Pubsubhubbub 0029 */ 0030 // require_once 'Zend/Feed/Pubsubhubbub/CallbackAbstract.php'; 0031 0032 /** 0033 * @see Zend_Feed_Reader 0034 */ 0035 // require_once 'Zend/Feed/Reader.php'; 0036 0037 /** 0038 * @category Zend 0039 * @package Zend_Feed_Pubsubhubbub 0040 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0041 * @license http://framework.zend.com/license/new-bsd New BSD License 0042 */ 0043 class Zend_Feed_Pubsubhubbub_Subscriber_Callback 0044 extends Zend_Feed_Pubsubhubbub_CallbackAbstract 0045 { 0046 /** 0047 * Contains the content of any feeds sent as updates to the Callback URL 0048 * 0049 * @var string 0050 */ 0051 protected $_feedUpdate = null; 0052 0053 /** 0054 * Holds a manually set subscription key (i.e. identifies a unique 0055 * subscription) which is typical when it is not passed in the query string 0056 * but is part of the Callback URL path, requiring manual retrieval e.g. 0057 * using a route and the Zend_Controller_Action::_getParam() method. 0058 * 0059 * @var string 0060 */ 0061 protected $_subscriptionKey = null; 0062 0063 /** 0064 * After verification, this is set to the verified subscription's data. 0065 * 0066 * @var array 0067 */ 0068 protected $_currentSubscriptionData = null; 0069 0070 /** 0071 * Set a subscription key to use for the current callback request manually. 0072 * Required if usePathParameter is enabled for the Subscriber. 0073 * 0074 * @param string $key 0075 * @return Zend_Feed_Pubsubhubbub_Subscriber_Callback 0076 */ 0077 public function setSubscriptionKey($key) 0078 { 0079 $this->_subscriptionKey = $key; 0080 return $this; 0081 } 0082 0083 /** 0084 * Handle any callback from a Hub Server responding to a subscription or 0085 * unsubscription request. This should be the Hub Server confirming the 0086 * the request prior to taking action on it. 0087 * 0088 * @param array $httpGetData GET data if available and not in $_GET 0089 * @param bool $sendResponseNow Whether to send response now or when asked 0090 * @return void 0091 */ 0092 public function handle(array $httpGetData = null, $sendResponseNow = false) 0093 { 0094 if ($httpGetData === null) { 0095 $httpGetData = $_GET; 0096 } 0097 0098 /** 0099 * Handle any feed updates (sorry for the mess :P) 0100 * 0101 * This DOES NOT attempt to process a feed update. Feed updates 0102 * SHOULD be validated/processed by an asynchronous process so as 0103 * to avoid holding up responses to the Hub. 0104 */ 0105 $contentType = $this->_getHeader('Content-Type'); 0106 if (strtolower($_SERVER['REQUEST_METHOD']) == 'post' 0107 && $this->_hasValidVerifyToken(null, false) 0108 && (stripos($contentType, 'application/atom+xml') === 0 0109 || stripos($contentType, 'application/rss+xml') === 0 0110 || stripos($contentType, 'application/xml') === 0 0111 || stripos($contentType, 'text/xml') === 0 0112 || stripos($contentType, 'application/rdf+xml') === 0) 0113 ) { 0114 $this->setFeedUpdate($this->_getRawBody()); 0115 $this->getHttpResponse() 0116 ->setHeader('X-Hub-On-Behalf-Of', $this->getSubscriberCount()); 0117 /** 0118 * Handle any (un)subscribe confirmation requests 0119 */ 0120 } elseif ($this->isValidHubVerification($httpGetData)) { 0121 $data = $this->_currentSubscriptionData; 0122 $this->getHttpResponse()->setBody($httpGetData['hub_challenge']); 0123 $data['subscription_state'] = Zend_Feed_Pubsubhubbub::SUBSCRIPTION_VERIFIED; 0124 if (isset($httpGetData['hub_lease_seconds'])) { 0125 $data['lease_seconds'] = $httpGetData['hub_lease_seconds']; 0126 } 0127 $this->getStorage()->setSubscription($data); 0128 /** 0129 * Hey, C'mon! We tried everything else! 0130 */ 0131 } else { 0132 $this->getHttpResponse()->setHttpResponseCode(404); 0133 } 0134 if ($sendResponseNow) { 0135 $this->sendResponse(); 0136 } 0137 } 0138 0139 /** 0140 * Checks validity of the request simply by making a quick pass and 0141 * confirming the presence of all REQUIRED parameters. 0142 * 0143 * @param array $httpGetData 0144 * @return bool 0145 */ 0146 public function isValidHubVerification(array $httpGetData) 0147 { 0148 /** 0149 * As per the specification, the hub.verify_token is OPTIONAL. This 0150 * implementation of Pubsubhubbub considers it REQUIRED and will 0151 * always send a hub.verify_token parameter to be echoed back 0152 * by the Hub Server. Therefore, its absence is considered invalid. 0153 */ 0154 if (strtolower($_SERVER['REQUEST_METHOD']) !== 'get') { 0155 return false; 0156 } 0157 $required = array( 0158 'hub_mode', 0159 'hub_topic', 0160 'hub_challenge', 0161 'hub_verify_token', 0162 ); 0163 foreach ($required as $key) { 0164 if (!array_key_exists($key, $httpGetData)) { 0165 return false; 0166 } 0167 } 0168 if ($httpGetData['hub_mode'] !== 'subscribe' 0169 && $httpGetData['hub_mode'] !== 'unsubscribe' 0170 ) { 0171 return false; 0172 } 0173 if ($httpGetData['hub_mode'] == 'subscribe' 0174 && !array_key_exists('hub_lease_seconds', $httpGetData) 0175 ) { 0176 return false; 0177 } 0178 if (!Zend_Uri::check($httpGetData['hub_topic'])) { 0179 return false; 0180 } 0181 0182 /** 0183 * Attempt to retrieve any Verification Token Key attached to Callback 0184 * URL's path by our Subscriber implementation 0185 */ 0186 if (!$this->_hasValidVerifyToken($httpGetData)) { 0187 return false; 0188 } 0189 return true; 0190 } 0191 0192 /** 0193 * Sets a newly received feed (Atom/RSS) sent by a Hub as an update to a 0194 * Topic we've subscribed to. 0195 * 0196 * @param string $feed 0197 * @return Zend_Feed_Pubsubhubbub_Subscriber_Callback 0198 */ 0199 public function setFeedUpdate($feed) 0200 { 0201 $this->_feedUpdate = $feed; 0202 return $this; 0203 } 0204 0205 /** 0206 * Check if any newly received feed (Atom/RSS) update was received 0207 * 0208 * @return bool 0209 */ 0210 public function hasFeedUpdate() 0211 { 0212 if ($this->_feedUpdate === null) { 0213 return false; 0214 } 0215 return true; 0216 } 0217 0218 /** 0219 * Gets a newly received feed (Atom/RSS) sent by a Hub as an update to a 0220 * Topic we've subscribed to. 0221 * 0222 * @return string 0223 */ 0224 public function getFeedUpdate() 0225 { 0226 return $this->_feedUpdate; 0227 } 0228 0229 /** 0230 * Check for a valid verify_token. By default attempts to compare values 0231 * with that sent from Hub, otherwise merely ascertains its existence. 0232 * 0233 * @param array $httpGetData 0234 * @param bool $checkValue 0235 * @return bool 0236 */ 0237 protected function _hasValidVerifyToken(array $httpGetData = null, $checkValue = true) 0238 { 0239 $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData); 0240 if (empty($verifyTokenKey)) { 0241 return false; 0242 } 0243 $verifyTokenExists = $this->getStorage()->hasSubscription($verifyTokenKey); 0244 if (!$verifyTokenExists) { 0245 return false; 0246 } 0247 if ($checkValue) { 0248 $data = $this->getStorage()->getSubscription($verifyTokenKey); 0249 $verifyToken = $data['verify_token']; 0250 if ($verifyToken !== hash('sha256', $httpGetData['hub_verify_token'])) { 0251 return false; 0252 } 0253 $this->_currentSubscriptionData = $data; 0254 return true; 0255 } 0256 return true; 0257 } 0258 0259 /** 0260 * Attempt to detect the verification token key. This would be passed in 0261 * the Callback URL (which we are handling with this class!) as a URI 0262 * path part (the last part by convention). 0263 * 0264 * @param null|array $httpGetData 0265 * @return false|string 0266 */ 0267 protected function _detectVerifyTokenKey(array $httpGetData = null) 0268 { 0269 /** 0270 * Available when sub keys encoding in Callback URL path 0271 */ 0272 if (isset($this->_subscriptionKey)) { 0273 return $this->_subscriptionKey; 0274 } 0275 0276 /** 0277 * Available only if allowed by PuSH 0.2 Hubs 0278 */ 0279 if (is_array($httpGetData) 0280 && isset($httpGetData['xhub_subscription']) 0281 ) { 0282 return $httpGetData['xhub_subscription']; 0283 } 0284 0285 /** 0286 * Available (possibly) if corrupted in transit and not part of $_GET 0287 */ 0288 $params = $this->_parseQueryString(); 0289 if (isset($params['xhub.subscription'])) { 0290 return rawurldecode($params['xhub.subscription']); 0291 } 0292 0293 return false; 0294 } 0295 0296 /** 0297 * Build an array of Query String parameters. 0298 * This bypasses $_GET which munges parameter names and cannot accept 0299 * multiple parameters with the same key. 0300 * 0301 * @return array|void 0302 */ 0303 protected function _parseQueryString() 0304 { 0305 $params = array(); 0306 $queryString = ''; 0307 if (isset($_SERVER['QUERY_STRING'])) { 0308 $queryString = $_SERVER['QUERY_STRING']; 0309 } 0310 if (empty($queryString)) { 0311 return array(); 0312 } 0313 $parts = explode('&', $queryString); 0314 foreach ($parts as $kvpair) { 0315 $pair = explode('=', $kvpair); 0316 $key = rawurldecode($pair[0]); 0317 $value = rawurldecode($pair[1]); 0318 if (isset($params[$key])) { 0319 if (is_array($params[$key])) { 0320 $params[$key][] = $value; 0321 } else { 0322 $params[$key] = array($params[$key], $value); 0323 } 0324 } else { 0325 $params[$key] = $value; 0326 } 0327 } 0328 return $params; 0329 } 0330 }