web/lib/Zend/Service/WindowsAzure/Storage.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Service_WindowsAzure
       
    17  * @subpackage Storage
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  * @version    $Id: Storage.php 23167 2010-10-19 17:53:31Z mabe $
       
    21  */
       
    22 
       
    23 /**
       
    24  * @see Zend_Service_WindowsAzure_Credentials_CredentialsAbstract
       
    25  */
       
    26 require_once 'Zend/Service/WindowsAzure/Credentials/CredentialsAbstract.php';
       
    27 
       
    28 /**
       
    29  * @see Zend_Service_WindowsAzure_Credentials_SharedKey
       
    30  */
       
    31 require_once 'Zend/Service/WindowsAzure/Credentials/SharedKey.php';
       
    32 
       
    33 /**
       
    34  * @see Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract
       
    35  */
       
    36 require_once 'Zend/Service/WindowsAzure/RetryPolicy/RetryPolicyAbstract.php';
       
    37 
       
    38 /**
       
    39  * @see Zend_Service_WindowsAzure_Exception
       
    40  */
       
    41 require_once 'Zend/Service/WindowsAzure/Exception.php';
       
    42 
       
    43 /**
       
    44  * @see Zend_Http_Client
       
    45  */
       
    46 require_once 'Zend/Http/Client.php';
       
    47 
       
    48 /**
       
    49  * @see Zend_Http_Response
       
    50  */
       
    51 require_once 'Zend/Http/Response.php';
       
    52 
       
    53 /**
       
    54  * @category   Zend
       
    55  * @package    Zend_Service_WindowsAzure
       
    56  * @subpackage Storage
       
    57  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    58  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    59  */
       
    60 class Zend_Service_WindowsAzure_Storage
       
    61 {
       
    62 	/**
       
    63 	 * Development storage URLS
       
    64 	 */
       
    65 	const URL_DEV_BLOB      = "127.0.0.1:10000";
       
    66 	const URL_DEV_QUEUE     = "127.0.0.1:10001";
       
    67 	const URL_DEV_TABLE     = "127.0.0.1:10002";
       
    68 	
       
    69 	/**
       
    70 	 * Live storage URLS
       
    71 	 */
       
    72 	const URL_CLOUD_BLOB    = "blob.core.windows.net";
       
    73 	const URL_CLOUD_QUEUE   = "queue.core.windows.net";
       
    74 	const URL_CLOUD_TABLE   = "table.core.windows.net";
       
    75 	
       
    76 	/**
       
    77 	 * Resource types
       
    78 	 */
       
    79 	const RESOURCE_UNKNOWN     = "unknown";
       
    80 	const RESOURCE_CONTAINER   = "c";
       
    81 	const RESOURCE_BLOB        = "b";
       
    82 	const RESOURCE_TABLE       = "t";
       
    83 	const RESOURCE_ENTITY      = "e";
       
    84 	const RESOURCE_QUEUE       = "q";
       
    85 	
       
    86 	/**
       
    87 	 * HTTP header prefixes
       
    88 	 */
       
    89 	const PREFIX_PROPERTIES      = "x-ms-prop-";
       
    90 	const PREFIX_METADATA        = "x-ms-meta-";
       
    91 	const PREFIX_STORAGE_HEADER  = "x-ms-";
       
    92 	
       
    93 	/**
       
    94 	 * Current API version
       
    95 	 * 
       
    96 	 * @var string
       
    97 	 */
       
    98 	protected $_apiVersion = '2009-09-19';
       
    99 	
       
   100 	/**
       
   101 	 * Storage host name
       
   102 	 *
       
   103 	 * @var string
       
   104 	 */
       
   105 	protected $_host = '';
       
   106 	
       
   107 	/**
       
   108 	 * Account name for Windows Azure
       
   109 	 *
       
   110 	 * @var string
       
   111 	 */
       
   112 	protected $_accountName = '';
       
   113 	
       
   114 	/**
       
   115 	 * Account key for Windows Azure
       
   116 	 *
       
   117 	 * @var string
       
   118 	 */
       
   119 	protected $_accountKey = '';
       
   120 	
       
   121 	/**
       
   122 	 * Use path-style URI's
       
   123 	 *
       
   124 	 * @var boolean
       
   125 	 */
       
   126 	protected $_usePathStyleUri = false;
       
   127 	
       
   128 	/**
       
   129 	 * Zend_Service_WindowsAzure_Credentials_CredentialsAbstract instance
       
   130 	 *
       
   131 	 * @var Zend_Service_WindowsAzure_Credentials_CredentialsAbstract
       
   132 	 */
       
   133 	protected $_credentials = null;
       
   134 	
       
   135 	/**
       
   136 	 * Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract instance
       
   137 	 * 
       
   138 	 * @var Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract
       
   139 	 */
       
   140 	protected $_retryPolicy = null;
       
   141 	
       
   142 	/**
       
   143 	 * Zend_Http_Client channel used for communication with REST services
       
   144 	 * 
       
   145 	 * @var Zend_Http_Client
       
   146 	 */
       
   147 	protected $_httpClientChannel = null;
       
   148 	
       
   149 	/**
       
   150 	 * Use proxy?
       
   151 	 * 
       
   152 	 * @var boolean
       
   153 	 */
       
   154 	protected $_useProxy = false;
       
   155 	
       
   156 	/**
       
   157 	 * Proxy url
       
   158 	 * 
       
   159 	 * @var string
       
   160 	 */
       
   161 	protected $_proxyUrl = '';
       
   162 	
       
   163 	/**
       
   164 	 * Proxy port
       
   165 	 * 
       
   166 	 * @var int
       
   167 	 */
       
   168 	protected $_proxyPort = 80;
       
   169 	
       
   170 	/**
       
   171 	 * Proxy credentials
       
   172 	 * 
       
   173 	 * @var string
       
   174 	 */
       
   175 	protected $_proxyCredentials = '';
       
   176 	
       
   177 	/**
       
   178 	 * Creates a new Zend_Service_WindowsAzure_Storage instance
       
   179 	 *
       
   180 	 * @param string $host Storage host name
       
   181 	 * @param string $accountName Account name for Windows Azure
       
   182 	 * @param string $accountKey Account key for Windows Azure
       
   183 	 * @param boolean $usePathStyleUri Use path-style URI's
       
   184 	 * @param Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests
       
   185 	 */
       
   186 	public function __construct(
       
   187 		$host = self::URL_DEV_BLOB,
       
   188 		$accountName = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_ACCOUNT,
       
   189 		$accountKey = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_KEY,
       
   190 		$usePathStyleUri = false,
       
   191 		Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy = null
       
   192 	) {
       
   193 		$this->_host = $host;
       
   194 		$this->_accountName = $accountName;
       
   195 		$this->_accountKey = $accountKey;
       
   196 		$this->_usePathStyleUri = $usePathStyleUri;
       
   197 		
       
   198 		// Using local storage?
       
   199 		if (!$this->_usePathStyleUri
       
   200 			&& ($this->_host == self::URL_DEV_BLOB
       
   201 				|| $this->_host == self::URL_DEV_QUEUE
       
   202 				|| $this->_host == self::URL_DEV_TABLE)
       
   203 		) {
       
   204 			// Local storage
       
   205 			$this->_usePathStyleUri = true;
       
   206 		}
       
   207 		
       
   208 		if ($this->_credentials === null) {
       
   209 		    $this->_credentials = new Zend_Service_WindowsAzure_Credentials_SharedKey(
       
   210 		    	$this->_accountName, $this->_accountKey, $this->_usePathStyleUri);
       
   211 		}
       
   212 		
       
   213 		$this->_retryPolicy = $retryPolicy;
       
   214 		if ($this->_retryPolicy === null) {
       
   215 		    $this->_retryPolicy = Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract::noRetry();
       
   216 		}
       
   217 		
       
   218 		// Setup default Zend_Http_Client channel
       
   219 		$options = array(
       
   220 			'adapter' => 'Zend_Http_Client_Adapter_Proxy'
       
   221 		);
       
   222 		if (function_exists('curl_init')) {
       
   223 			// Set cURL options if cURL is used afterwards
       
   224 			$options['curloptions'] = array(
       
   225 					CURLOPT_FOLLOWLOCATION => true,
       
   226 					CURLOPT_TIMEOUT => 120,
       
   227 			);
       
   228 		}
       
   229 		$this->_httpClientChannel = new Zend_Http_Client(null, $options);
       
   230 	}
       
   231 	
       
   232 	/**
       
   233 	 * Set the HTTP client channel to use
       
   234 	 * 
       
   235 	 * @param Zend_Http_Client_Adapter_Interface|string $adapterInstance Adapter instance or adapter class name.
       
   236 	 */
       
   237 	public function setHttpClientChannel($adapterInstance = 'Zend_Http_Client_Adapter_Proxy')
       
   238 	{
       
   239 		$this->_httpClientChannel->setAdapter($adapterInstance);
       
   240 	}
       
   241 
       
   242     /**
       
   243      * Retrieve HTTP client channel
       
   244      * 
       
   245      * @return Zend_Http_Client_Adapter_Interface
       
   246      */
       
   247     public function getHttpClientChannel()
       
   248     {
       
   249         return $this->_httpClientChannel;
       
   250     }
       
   251 	
       
   252 	/**
       
   253 	 * Set retry policy to use when making requests
       
   254 	 *
       
   255 	 * @param Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests
       
   256 	 */
       
   257 	public function setRetryPolicy(Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy = null)
       
   258 	{
       
   259 		$this->_retryPolicy = $retryPolicy;
       
   260 		if ($this->_retryPolicy === null) {
       
   261 		    $this->_retryPolicy = Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract::noRetry();
       
   262 		}
       
   263 	}
       
   264 	
       
   265 	/**
       
   266 	 * Set proxy
       
   267 	 * 
       
   268 	 * @param boolean $useProxy         Use proxy?
       
   269 	 * @param string  $proxyUrl         Proxy URL
       
   270 	 * @param int     $proxyPort        Proxy port
       
   271 	 * @param string  $proxyCredentials Proxy credentials
       
   272 	 */
       
   273 	public function setProxy($useProxy = false, $proxyUrl = '', $proxyPort = 80, $proxyCredentials = '')
       
   274 	{
       
   275 	    $this->_useProxy         = $useProxy;
       
   276 	    $this->_proxyUrl         = $proxyUrl;
       
   277 	    $this->_proxyPort        = $proxyPort;
       
   278 	    $this->_proxyCredentials = $proxyCredentials;
       
   279 	    
       
   280 	    if ($this->_useProxy) {
       
   281 	    	$credentials = explode(':', $this->_proxyCredentials);
       
   282 	    	
       
   283 	    	$this->_httpClientChannel->setConfig(array(
       
   284 				'proxy_host' => $this->_proxyUrl,
       
   285 	    		'proxy_port' => $this->_proxyPort,
       
   286 	    		'proxy_user' => $credentials[0],
       
   287 	    		'proxy_pass' => $credentials[1],
       
   288 	    	));
       
   289 	    } else {
       
   290 			$this->_httpClientChannel->setConfig(array(
       
   291 				'proxy_host' => '',
       
   292 	    		'proxy_port' => 8080,
       
   293 	    		'proxy_user' => '',
       
   294 	    		'proxy_pass' => '',
       
   295 	    	));
       
   296 	    }
       
   297 	}
       
   298 	
       
   299 	/**
       
   300 	 * Returns the Windows Azure account name
       
   301 	 * 
       
   302 	 * @return string
       
   303 	 */
       
   304 	public function getAccountName()
       
   305 	{
       
   306 		return $this->_accountName;
       
   307 	}
       
   308 	
       
   309 	/**
       
   310 	 * Get base URL for creating requests
       
   311 	 *
       
   312 	 * @return string
       
   313 	 */
       
   314 	public function getBaseUrl()
       
   315 	{
       
   316 		if ($this->_usePathStyleUri) {
       
   317 			return 'http://' . $this->_host . '/' . $this->_accountName;
       
   318 		} else {
       
   319 			return 'http://' . $this->_accountName . '.' . $this->_host;
       
   320 		}
       
   321 	}
       
   322 	
       
   323 	/**
       
   324 	 * Set Zend_Service_WindowsAzure_Credentials_CredentialsAbstract instance
       
   325 	 * 
       
   326 	 * @param Zend_Service_WindowsAzure_Credentials_CredentialsAbstract $credentials Zend_Service_WindowsAzure_Credentials_CredentialsAbstract instance to use for request signing.
       
   327 	 */
       
   328 	public function setCredentials(Zend_Service_WindowsAzure_Credentials_CredentialsAbstract $credentials)
       
   329 	{
       
   330 	    $this->_credentials = $credentials;
       
   331 	    $this->_credentials->setAccountName($this->_accountName);
       
   332 	    $this->_credentials->setAccountkey($this->_accountKey);
       
   333 	    $this->_credentials->setUsePathStyleUri($this->_usePathStyleUri);
       
   334 	}
       
   335 	
       
   336 	/**
       
   337 	 * Get Zend_Service_WindowsAzure_Credentials_CredentialsAbstract instance
       
   338 	 * 
       
   339 	 * @return Zend_Service_WindowsAzure_Credentials_CredentialsAbstract
       
   340 	 */
       
   341 	public function getCredentials()
       
   342 	{
       
   343 	    return $this->_credentials;
       
   344 	}
       
   345 	
       
   346 	/**
       
   347 	 * Perform request using Zend_Http_Client channel
       
   348 	 *
       
   349 	 * @param string $path Path
       
   350 	 * @param string $queryString Query string
       
   351 	 * @param string $httpVerb HTTP verb the request will use
       
   352 	 * @param array $headers x-ms headers to add
       
   353 	 * @param boolean $forTableStorage Is the request for table storage?
       
   354 	 * @param mixed $rawData Optional RAW HTTP data to be sent over the wire
       
   355 	 * @param string $resourceType Resource type
       
   356 	 * @param string $requiredPermission Required permission
       
   357 	 * @return Zend_Http_Response
       
   358 	 */
       
   359 	protected function _performRequest(
       
   360 		$path = '/',
       
   361 		$queryString = '',
       
   362 		$httpVerb = Zend_Http_Client::GET,
       
   363 		$headers = array(),
       
   364 		$forTableStorage = false,
       
   365 		$rawData = null,
       
   366 		$resourceType = Zend_Service_WindowsAzure_Storage::RESOURCE_UNKNOWN,
       
   367 		$requiredPermission = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::PERMISSION_READ
       
   368 	) {
       
   369 	    // Clean path
       
   370 		if (strpos($path, '/') !== 0) {
       
   371 			$path = '/' . $path;
       
   372 		}
       
   373 			
       
   374 		// Clean headers
       
   375 		if ($headers === null) {
       
   376 		    $headers = array();
       
   377 		}
       
   378 		
       
   379 		// Ensure cUrl will also work correctly:
       
   380 		//  - disable Content-Type if required
       
   381 		//  - disable Expect: 100 Continue
       
   382 		if (!isset($headers["Content-Type"])) {
       
   383 			$headers["Content-Type"] = '';
       
   384 		}
       
   385 		$headers["Expect"]= '';
       
   386 
       
   387 		// Add version header
       
   388 		$headers['x-ms-version'] = $this->_apiVersion;
       
   389 		    
       
   390 		// URL encoding
       
   391 		$path           = self::urlencode($path);
       
   392 		$queryString    = self::urlencode($queryString);
       
   393 
       
   394 		// Generate URL and sign request
       
   395 		$requestUrl     = $this->_credentials
       
   396 						  ->signRequestUrl($this->getBaseUrl() . $path . $queryString, $resourceType, $requiredPermission);
       
   397 		$requestHeaders = $this->_credentials
       
   398 						  ->signRequestHeaders($httpVerb, $path, $queryString, $headers, $forTableStorage, $resourceType, $requiredPermission, $rawData);
       
   399 
       
   400 		// Prepare request
       
   401 		$this->_httpClientChannel->resetParameters(true);
       
   402 		$this->_httpClientChannel->setUri($requestUrl);
       
   403 		$this->_httpClientChannel->setHeaders($requestHeaders);
       
   404 		$this->_httpClientChannel->setRawData($rawData);
       
   405 				
       
   406 		// Execute request
       
   407 		$response = $this->_retryPolicy->execute(
       
   408 		    array($this->_httpClientChannel, 'request'),
       
   409 		    array($httpVerb)
       
   410 		);
       
   411 		
       
   412 		return $response;
       
   413 	}
       
   414 	
       
   415 	/** 
       
   416 	 * Parse result from Zend_Http_Response
       
   417 	 *
       
   418 	 * @param Zend_Http_Response $response Response from HTTP call
       
   419 	 * @return object
       
   420 	 * @throws Zend_Service_WindowsAzure_Exception
       
   421 	 */
       
   422 	protected function _parseResponse(Zend_Http_Response $response = null)
       
   423 	{
       
   424 		if ($response === null) {
       
   425 			throw new Zend_Service_WindowsAzure_Exception('Response should not be null.');
       
   426 		}
       
   427 		
       
   428         $xml = @simplexml_load_string($response->getBody());
       
   429         
       
   430         if ($xml !== false) {
       
   431             // Fetch all namespaces 
       
   432             $namespaces = array_merge($xml->getNamespaces(true), $xml->getDocNamespaces(true)); 
       
   433             
       
   434             // Register all namespace prefixes
       
   435             foreach ($namespaces as $prefix => $ns) { 
       
   436                 if ($prefix != '') {
       
   437                     $xml->registerXPathNamespace($prefix, $ns);
       
   438                 } 
       
   439             } 
       
   440         }
       
   441         
       
   442         return $xml;
       
   443 	}
       
   444 	
       
   445 	/**
       
   446 	 * Generate metadata headers
       
   447 	 * 
       
   448 	 * @param array $metadata
       
   449 	 * @return HTTP headers containing metadata
       
   450 	 */
       
   451 	protected function _generateMetadataHeaders($metadata = array())
       
   452 	{
       
   453 		// Validate
       
   454 		if (!is_array($metadata)) {
       
   455 			return array();
       
   456 		}
       
   457 		
       
   458 		// Return headers
       
   459 		$headers = array();
       
   460 		foreach ($metadata as $key => $value) {
       
   461 			if (strpos($value, "\r") !== false || strpos($value, "\n") !== false) {
       
   462 				throw new Zend_Service_WindowsAzure_Exception('Metadata cannot contain newline characters.');
       
   463 			}
       
   464 			
       
   465 			if (!self::isValidMetadataName($key)) {
       
   466 		    	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.');
       
   467 			}
       
   468 			
       
   469 		    $headers["x-ms-meta-" . strtolower($key)] = $value;
       
   470 		}
       
   471 		return $headers;
       
   472 	}
       
   473 	
       
   474 	/**
       
   475 	 * Parse metadata headers
       
   476 	 * 
       
   477 	 * @param array $headers HTTP headers containing metadata
       
   478 	 * @return array
       
   479 	 */
       
   480 	protected function _parseMetadataHeaders($headers = array())
       
   481 	{
       
   482 		// Validate
       
   483 		if (!is_array($headers)) {
       
   484 			return array();
       
   485 		}
       
   486 		
       
   487 		// Return metadata
       
   488 		$metadata = array();
       
   489 		foreach ($headers as $key => $value) {
       
   490 		    if (substr(strtolower($key), 0, 10) == "x-ms-meta-") {
       
   491 		        $metadata[str_replace("x-ms-meta-", '', strtolower($key))] = $value;
       
   492 		    }
       
   493 		}
       
   494 		return $metadata;
       
   495 	}
       
   496 	
       
   497 	/**
       
   498 	 * Parse metadata XML
       
   499 	 * 
       
   500 	 * @param SimpleXMLElement $parentElement Element containing the Metadata element.
       
   501 	 * @return array
       
   502 	 */
       
   503 	protected function _parseMetadataElement($element = null)
       
   504 	{
       
   505 		// Metadata present?
       
   506 		if ($element !== null && isset($element->Metadata) && $element->Metadata !== null) {
       
   507 			return get_object_vars($element->Metadata);
       
   508 		}
       
   509 
       
   510 		return array();
       
   511 	}
       
   512 	
       
   513 	/**
       
   514 	 * Generate ISO 8601 compliant date string in UTC time zone
       
   515 	 * 
       
   516 	 * @param int $timestamp
       
   517 	 * @return string
       
   518 	 */
       
   519 	public function isoDate($timestamp = null) 
       
   520 	{        
       
   521 	    $tz = @date_default_timezone_get();
       
   522 	    @date_default_timezone_set('UTC');
       
   523 	    
       
   524 	    if ($timestamp === null) {
       
   525 	        $timestamp = time();
       
   526 	    }
       
   527 	        
       
   528 	    $returnValue = str_replace('+00:00', '.0000000Z', @date('c', $timestamp));
       
   529 	    @date_default_timezone_set($tz);
       
   530 	    return $returnValue;
       
   531 	}
       
   532 	
       
   533 	/**
       
   534 	 * URL encode function
       
   535 	 * 
       
   536 	 * @param  string $value Value to encode
       
   537 	 * @return string        Encoded value
       
   538 	 */
       
   539 	public static function urlencode($value)
       
   540 	{
       
   541 	    return str_replace(' ', '%20', $value);
       
   542 	}
       
   543 	
       
   544 	/**
       
   545 	 * Is valid metadata name?
       
   546 	 *
       
   547 	 * @param string $metadataName Metadata name
       
   548 	 * @return boolean
       
   549 	 */
       
   550     public static function isValidMetadataName($metadataName = '')
       
   551     {
       
   552         if (preg_match("/^[a-zA-Z0-9_@][a-zA-Z0-9_]*$/", $metadataName) === 0) {
       
   553             return false;
       
   554         }
       
   555     
       
   556         if ($metadataName == '') {
       
   557             return false;
       
   558         }
       
   559 
       
   560         return true;
       
   561     }
       
   562     
       
   563     /**
       
   564      * Builds a query string from an array of elements
       
   565      * 
       
   566      * @param array     Array of elements
       
   567      * @return string   Assembled query string
       
   568      */
       
   569     public static function createQueryStringFromArray($queryString)
       
   570     {
       
   571     	return count($queryString) > 0 ? '?' . implode('&', $queryString) : '';
       
   572     }	
       
   573 }