2
0

Storage.php 15 KB

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