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 }