/zf/library/Zend/Service/WindowsAzure/Storage.php
PHP | 573 lines | 254 code | 67 blank | 252 comment | 36 complexity | 0f2507a9060468ecc43fac09b98d3118 MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, Apache-2.0, LGPL-2.1, LGPL-3.0, BSD-2-Clause
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-2011 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 23775 2011-03-01 17:25:24Z ralph $ 21 */ 22 23/** 24 * @see Zend_Service_WindowsAzure_Credentials_CredentialsAbstract 25 */ 26require_once 'Zend/Service/WindowsAzure/Credentials/CredentialsAbstract.php'; 27 28/** 29 * @see Zend_Service_WindowsAzure_Credentials_SharedKey 30 */ 31require_once 'Zend/Service/WindowsAzure/Credentials/SharedKey.php'; 32 33/** 34 * @see Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract 35 */ 36require_once 'Zend/Service/WindowsAzure/RetryPolicy/RetryPolicyAbstract.php'; 37 38/** 39 * @see Zend_Service_WindowsAzure_Exception 40 */ 41require_once 'Zend/Service/WindowsAzure/Exception.php'; 42 43/** 44 * @see Zend_Http_Client 45 */ 46require_once 'Zend/Http/Client.php'; 47 48/** 49 * @see Zend_Http_Response 50 */ 51require_once 'Zend/Http/Response.php'; 52 53/** 54 * @category Zend 55 * @package Zend_Service_WindowsAzure 56 * @subpackage Storage 57 * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) 58 * @license http://framework.zend.com/license/new-bsd New BSD License 59 */ 60class 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}