File indexing completed on 2024-12-22 05:37:05

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_Service_WindowsAzure
0017  * @subpackage Storage
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * @see Zend_Http_Client
0025  */
0026 // require_once 'Zend/Http/Client.php';
0027 
0028 /**
0029  * @see Zend_Service_WindowsAzure_Credentials_SharedKey
0030  */
0031 // require_once 'Zend/Service/WindowsAzure/Credentials/SharedKey.php';
0032 
0033 /**
0034  * @see Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract
0035  */
0036 // require_once 'Zend/Service/WindowsAzure/RetryPolicy/RetryPolicyAbstract.php';
0037 
0038 /** @see Zend_Xml_Security */
0039 // require_once 'Zend/Xml/Security.php';
0040 
0041 /**
0042  * @category   Zend
0043  * @package    Zend_Service_WindowsAzure
0044  * @subpackage Storage
0045  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0046  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0047  */
0048 class Zend_Service_WindowsAzure_Storage
0049 {
0050   /**
0051    * Development storage URLS
0052    */
0053   const URL_DEV_BLOB      = "127.0.0.1:10000";
0054   const URL_DEV_QUEUE     = "127.0.0.1:10001";
0055   const URL_DEV_TABLE     = "127.0.0.1:10002";
0056   
0057   /**
0058    * Live storage URLS
0059    */
0060   const URL_CLOUD_BLOB    = "blob.core.windows.net";
0061   const URL_CLOUD_QUEUE   = "queue.core.windows.net";
0062   const URL_CLOUD_TABLE   = "table.core.windows.net";
0063   
0064   /**
0065    * Resource types
0066    */
0067   const RESOURCE_UNKNOWN     = "unknown";
0068   const RESOURCE_CONTAINER   = "c";
0069   const RESOURCE_BLOB        = "b";
0070   const RESOURCE_TABLE       = "t";
0071   const RESOURCE_ENTITY      = "e";
0072   const RESOURCE_QUEUE       = "q";
0073   
0074   /**
0075    * HTTP header prefixes
0076    */
0077   const PREFIX_PROPERTIES      = "x-ms-prop-";
0078   const PREFIX_METADATA        = "x-ms-meta-";
0079   const PREFIX_STORAGE_HEADER  = "x-ms-";
0080   
0081   /**
0082    * Current API version
0083    * 
0084    * @var string
0085    */
0086   protected $_apiVersion = '2009-09-19';
0087   
0088   /**
0089    * Storage host name
0090    *
0091    * @var string
0092    */
0093   protected $_host = '';
0094   
0095   /**
0096    * Account name for Windows Azure
0097    *
0098    * @var string
0099    */
0100   protected $_accountName = '';
0101   
0102   /**
0103    * Account key for Windows Azure
0104    *
0105    * @var string
0106    */
0107   protected $_accountKey = '';
0108   
0109   /**
0110    * Use path-style URI's
0111    *
0112    * @var boolean
0113    */
0114   protected $_usePathStyleUri = false;
0115   
0116   /**
0117    * Zend_Service_WindowsAzure_Credentials_CredentialsAbstract instance
0118    *
0119    * @var Zend_Service_WindowsAzure_Credentials_CredentialsAbstract
0120    */
0121   protected $_credentials = null;
0122   
0123   /**
0124    * Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract instance
0125    * 
0126    * @var Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract
0127    */
0128   protected $_retryPolicy = null;
0129   
0130   /**
0131    * Zend_Http_Client channel used for communication with REST services
0132    * 
0133    * @var Zend_Http_Client
0134    */
0135   protected $_httpClientChannel = null;
0136   
0137   /**
0138    * Use proxy?
0139    * 
0140    * @var boolean
0141    */
0142   protected $_useProxy = false;
0143   
0144   /**
0145    * Proxy url
0146    * 
0147    * @var string
0148    */
0149   protected $_proxyUrl = '';
0150   
0151   /**
0152    * Proxy port
0153    * 
0154    * @var int
0155    */
0156   protected $_proxyPort = 80;
0157   
0158   /**
0159    * Proxy credentials
0160    * 
0161    * @var string
0162    */
0163   protected $_proxyCredentials = '';
0164   
0165   /**
0166    * Creates a new Zend_Service_WindowsAzure_Storage instance
0167    *
0168    * @param string $host Storage host name
0169    * @param string $accountName Account name for Windows Azure
0170    * @param string $accountKey Account key for Windows Azure
0171    * @param boolean $usePathStyleUri Use path-style URI's
0172    * @param Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests
0173    */
0174   public function __construct(
0175     $host = self::URL_DEV_BLOB,
0176     $accountName = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_ACCOUNT,
0177     $accountKey = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_KEY,
0178     $usePathStyleUri = false,
0179     Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy = null
0180   ) {
0181     $this->_host = $host;
0182     $this->_accountName = $accountName;
0183     $this->_accountKey = $accountKey;
0184     $this->_usePathStyleUri = $usePathStyleUri;
0185     
0186     // Using local storage?
0187     if (!$this->_usePathStyleUri
0188       && ($this->_host == self::URL_DEV_BLOB
0189         || $this->_host == self::URL_DEV_QUEUE
0190         || $this->_host == self::URL_DEV_TABLE)
0191     ) {
0192       // Local storage
0193       $this->_usePathStyleUri = true;
0194     }
0195     
0196     if (is_null($this->_credentials)) {
0197         $this->_credentials = new Zend_Service_WindowsAzure_Credentials_SharedKey(
0198           $this->_accountName, $this->_accountKey, $this->_usePathStyleUri);
0199     }
0200     
0201     $this->_retryPolicy = $retryPolicy;
0202     if (is_null($this->_retryPolicy)) {
0203         $this->_retryPolicy = Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract::noRetry();
0204     }
0205     
0206     // Setup default Zend_Http_Client channel
0207     $options = array(
0208       'adapter' => 'Zend_Http_Client_Adapter_Proxy'
0209     );
0210     if (function_exists('curl_init')) {
0211       // Set cURL options if cURL is used afterwards
0212       $options['curloptions'] = array(
0213           CURLOPT_FOLLOWLOCATION => true,
0214           CURLOPT_TIMEOUT => 120,
0215       );
0216     }
0217     $this->_httpClientChannel = new Zend_Http_Client(null, $options);
0218   }
0219   
0220   /**
0221    * Set the HTTP client channel to use
0222    * 
0223    * @param Zend_Http_Client_Adapter_Interface|string $adapterInstance Adapter instance or adapter class name.
0224    */
0225   public function setHttpClientChannel($adapterInstance = 'Zend_Http_Client_Adapter_Proxy')
0226   {
0227     $this->_httpClientChannel->setAdapter($adapterInstance);
0228   }
0229   
0230     /**
0231      * Retrieve HTTP client channel
0232      * 
0233      * @return Zend_Http_Client_Adapter_Interface
0234      */
0235     public function getHttpClientChannel()
0236     {
0237         return $this->_httpClientChannel;
0238     }
0239   
0240   /**
0241    * Set retry policy to use when making requests
0242    *
0243    * @param Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests
0244    */
0245   public function setRetryPolicy(Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy = null)
0246   {
0247     $this->_retryPolicy = $retryPolicy;
0248     if (is_null($this->_retryPolicy)) {
0249         $this->_retryPolicy = Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract::noRetry();
0250     }
0251   }
0252   
0253   /**
0254    * Set proxy
0255    * 
0256    * @param boolean $useProxy         Use proxy?
0257    * @param string  $proxyUrl         Proxy URL
0258    * @param int     $proxyPort        Proxy port
0259    * @param string  $proxyCredentials Proxy credentials
0260    */
0261   public function setProxy($useProxy = false, $proxyUrl = '', $proxyPort = 80, $proxyCredentials = '')
0262   {
0263       $this->_useProxy         = $useProxy;
0264       $this->_proxyUrl         = $proxyUrl;
0265       $this->_proxyPort        = $proxyPort;
0266       $this->_proxyCredentials = $proxyCredentials;
0267       
0268       if ($this->_useProxy) {
0269         $credentials = explode(':', $this->_proxyCredentials);
0270         
0271         $this->_httpClientChannel->setConfig(array(
0272         'proxy_host' => $this->_proxyUrl,
0273           'proxy_port' => $this->_proxyPort,
0274           'proxy_user' => $credentials[0],
0275           'proxy_pass' => $credentials[1],
0276         ));
0277       } else {
0278       $this->_httpClientChannel->setConfig(array(
0279         'proxy_host' => '',
0280           'proxy_port' => 8080,
0281           'proxy_user' => '',
0282           'proxy_pass' => '',
0283         ));
0284       }
0285   }
0286   
0287   /**
0288    * Returns the Windows Azure account name
0289    * 
0290    * @return string
0291    */
0292   public function getAccountName()
0293   {
0294     return $this->_accountName;
0295   }
0296   
0297   /**
0298    * Get base URL for creating requests
0299    *
0300    * @return string
0301    */
0302   public function getBaseUrl()
0303   {
0304     if ($this->_usePathStyleUri) {
0305       return 'http://' . $this->_host . '/' . $this->_accountName;
0306     } else {
0307       return 'http://' . $this->_accountName . '.' . $this->_host;
0308     }
0309   }
0310   
0311   /**
0312    * Set Zend_Service_WindowsAzure_Credentials_CredentialsAbstract instance
0313    * 
0314    * @param Zend_Service_WindowsAzure_Credentials_CredentialsAbstract $credentials Zend_Service_WindowsAzure_Credentials_CredentialsAbstract instance to use for request signing.
0315    */
0316   public function setCredentials(Zend_Service_WindowsAzure_Credentials_CredentialsAbstract $credentials)
0317   {
0318       $this->_credentials = $credentials;
0319       $this->_credentials->setAccountName($this->_accountName);
0320       $this->_credentials->setAccountkey($this->_accountKey);
0321       $this->_credentials->setUsePathStyleUri($this->_usePathStyleUri);
0322   }
0323   
0324   /**
0325    * Get Zend_Service_WindowsAzure_Credentials_CredentialsAbstract instance
0326    * 
0327    * @return Zend_Service_WindowsAzure_Credentials_CredentialsAbstract
0328    */
0329   public function getCredentials()
0330   {
0331       return $this->_credentials;
0332   }
0333   
0334   /**
0335    * Perform request using Zend_Http_Client channel
0336    *
0337    * @param string $path Path
0338    * @param string $queryString Query string
0339    * @param string $httpVerb HTTP verb the request will use
0340    * @param array $headers x-ms headers to add
0341    * @param boolean $forTableStorage Is the request for table storage?
0342    * @param mixed $rawData Optional RAW HTTP data to be sent over the wire
0343    * @param string $resourceType Resource type
0344    * @param string $requiredPermission Required permission
0345    * @return Zend_Http_Response
0346    */
0347   protected function _performRequest(
0348     $path = '/',
0349     $queryString = '',
0350     $httpVerb = Zend_Http_Client::GET,
0351     $headers = array(),
0352     $forTableStorage = false,
0353     $rawData = null,
0354     $resourceType = Zend_Service_WindowsAzure_Storage::RESOURCE_UNKNOWN,
0355     $requiredPermission = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::PERMISSION_READ
0356   ) {
0357       // Clean path
0358     if (strpos($path, '/') !== 0) {
0359       $path = '/' . $path;
0360     }
0361       
0362     // Clean headers
0363     if (is_null($headers)) {
0364         $headers = array();
0365     }
0366     
0367     // Ensure cUrl will also work correctly:
0368     //  - disable Content-Type if required
0369     //  - disable Expect: 100 Continue
0370     if (!isset($headers["Content-Type"])) {
0371       $headers["Content-Type"] = '';
0372     }
0373     $headers["Expect"]= '';
0374 
0375     // Add version header
0376     $headers['x-ms-version'] = $this->_apiVersion;
0377         
0378     // URL encoding
0379     $path           = self::urlencode($path);
0380     $queryString    = self::urlencode($queryString);
0381 
0382     // Generate URL and sign request
0383     $requestUrl     = $this->_credentials
0384               ->signRequestUrl($this->getBaseUrl() . $path . $queryString, $resourceType, $requiredPermission);
0385     $requestHeaders = $this->_credentials
0386               ->signRequestHeaders($httpVerb, $path, $queryString, $headers, $forTableStorage, $resourceType, $requiredPermission, $rawData);
0387 
0388     // Prepare request 
0389     $this->_httpClientChannel->resetParameters(true);
0390     $this->_httpClientChannel->setUri($requestUrl);
0391     $this->_httpClientChannel->setHeaders($requestHeaders);
0392     $this->_httpClientChannel->setRawData($rawData);
0393         
0394     // Execute request
0395     $response = $this->_retryPolicy->execute(
0396         array($this->_httpClientChannel, 'request'),
0397         array($httpVerb)
0398     );
0399     
0400     return $response;
0401   }
0402   
0403   /** 
0404    * Parse result from Zend_Http_Response
0405    *
0406    * @param Zend_Http_Response $response Response from HTTP call
0407    * @return object
0408    * @throws Zend_Service_WindowsAzure_Exception
0409    */
0410   protected function _parseResponse(Zend_Http_Response $response = null)
0411   {
0412     if (is_null($response)) {
0413       // require_once 'Zend/Service/WindowsAzure/Exception.php';
0414       throw new Zend_Service_WindowsAzure_Exception('Response should not be null.');
0415     }
0416     
0417         $xml = Zend_Xml_Security::scan($response->getBody());
0418         
0419         if ($xml !== false) {
0420             // Fetch all namespaces 
0421             $namespaces = array_merge($xml->getNamespaces(true), $xml->getDocNamespaces(true)); 
0422             
0423             // Register all namespace prefixes
0424             foreach ($namespaces as $prefix => $ns) { 
0425                 if ($prefix != '') {
0426                     $xml->registerXPathNamespace($prefix, $ns);
0427                 } 
0428             } 
0429         }
0430         
0431         return $xml;
0432   }
0433   
0434   /**
0435    * Generate metadata headers
0436    * 
0437    * @param array $metadata
0438    * @return HTTP headers containing metadata
0439    */
0440   protected function _generateMetadataHeaders($metadata = array())
0441   {
0442     // Validate
0443     if (!is_array($metadata)) {
0444       return array();
0445     }
0446     
0447     // Return headers
0448     $headers = array();
0449     foreach ($metadata as $key => $value) {
0450       if (strpos($value, "\r") !== false || strpos($value, "\n") !== false) {
0451                             // require_once 'Zend/Service/WindowsAzure/Exception.php';
0452                             throw new Zend_Service_WindowsAzure_Exception('Metadata cannot contain newline characters.');
0453       }
0454       
0455       if (!self::isValidMetadataName($key)) {
0456                             // require_once 'Zend/Service/WindowsAzure/Exception.php';
0457                             throw new Zend_Service_WindowsAzure_Exception('Metadata name does not adhere to metadata naming conventions. See http://msdn.microsoft.com/en-us/library/aa664670(VS.71).aspx for more information.');
0458       }
0459       
0460         $headers["x-ms-meta-" . strtolower($key)] = $value;
0461     }
0462     return $headers;
0463   }
0464   
0465   /**
0466    * Parse metadata headers
0467    * 
0468    * @param array $headers HTTP headers containing metadata
0469    * @return array
0470    */
0471   protected function _parseMetadataHeaders($headers = array())
0472   {
0473     // Validate
0474     if (!is_array($headers)) {
0475       return array();
0476     }
0477     
0478     // Return metadata
0479     $metadata = array();
0480     foreach ($headers as $key => $value) {
0481         if (substr(strtolower($key), 0, 10) == "x-ms-meta-") {
0482             $metadata[str_replace("x-ms-meta-", '', strtolower($key))] = $value;
0483         }
0484     }
0485     return $metadata;
0486   }
0487   
0488   /**
0489    * Parse metadata XML
0490    * 
0491    * @param SimpleXMLElement $parentElement Element containing the Metadata element.
0492    * @return array
0493    */
0494   protected function _parseMetadataElement($element = null)
0495   {
0496     // Metadata present?
0497     if (!is_null($element) && isset($element->Metadata) && !is_null($element->Metadata)) {
0498       return get_object_vars($element->Metadata);
0499     }
0500 
0501     return array();
0502   }
0503   
0504   /**
0505    * Generate ISO 8601 compliant date string in UTC time zone
0506    * 
0507    * @param int $timestamp
0508    * @return string
0509    */
0510   public function isoDate($timestamp = null) 
0511   {        
0512       $tz = @date_default_timezone_get();
0513       @date_default_timezone_set('UTC');
0514       
0515       if (is_null($timestamp)) {
0516           $timestamp = time();
0517       }
0518           
0519       $returnValue = str_replace('+00:00', '.0000000Z', @date('c', $timestamp));
0520       @date_default_timezone_set($tz);
0521       return $returnValue;
0522   }
0523   
0524   /**
0525    * URL encode function
0526    * 
0527    * @param  string $value Value to encode
0528    * @return string        Encoded value
0529    */
0530   public static function urlencode($value)
0531   {
0532       return str_replace(' ', '%20', $value);
0533   }
0534   
0535   /**
0536    * Is valid metadata name?
0537    *
0538    * @param string $metadataName Metadata name
0539    * @return boolean
0540    */
0541     public static function isValidMetadataName($metadataName = '')
0542     {
0543         if (preg_match("/^[a-zA-Z0-9_@][a-zA-Z0-9_]*$/", $metadataName) === 0) {
0544             return false;
0545         }
0546     
0547         if ($metadataName == '') {
0548             return false;
0549         }
0550 
0551         return true;
0552     }
0553     
0554     /**
0555      * Builds a query string from an array of elements
0556      * 
0557      * @param array     Array of elements
0558      * @return string   Assembled query string
0559      */
0560     public static function createQueryStringFromArray($queryString)
0561     {
0562       return count($queryString) > 0 ? '?' . implode('&', $queryString) : '';
0563     } 
0564 }