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 }