/libraries/s3.php

https://github.com/SixPonyHitch/CodeIgniter-Amazon-S3-SDK · PHP · 2052 lines · 1269 code · 236 blank · 547 comment · 275 complexity · e64bf91284f27d4c0fbc3f105bbcdebe MD5 · raw file

Large files are truncated click here to view the full file

  1. <?php
  2. /**
  3. * $Id$
  4. *
  5. * Copyright (c) 2011, Donovan Schönknecht. All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. *
  10. * - Redistributions of source code must retain the above copyright notice,
  11. * this list of conditions and the following disclaimer.
  12. * - Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. * POSSIBILITY OF SUCH DAMAGE.
  27. *
  28. * Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
  29. */
  30. /**
  31. * Amazon S3 PHP class
  32. *
  33. * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
  34. * @version 0.5.0-dev
  35. */
  36. class S3
  37. {
  38. // ACL flags
  39. const ACL_PRIVATE = 'private';
  40. const ACL_PUBLIC_READ = 'public-read';
  41. const ACL_PUBLIC_READ_WRITE = 'public-read-write';
  42. const ACL_AUTHENTICATED_READ = 'authenticated-read';
  43. const STORAGE_CLASS_STANDARD = 'STANDARD';
  44. const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
  45. private static $__accessKey = null; // AWS Access key
  46. private static $__secretKey = null; // AWS Secret key
  47. private static $__sslKey = null;
  48. public static $endpoint = 's3.amazonaws.com';
  49. public static $proxy = null;
  50. public static $useSSL = false;
  51. public static $useSSLValidation = true;
  52. public static $useExceptions = false;
  53. // SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration
  54. public static $sslKey = null;
  55. public static $sslCert = null;
  56. public static $sslCACert = null;
  57. private static $__signingKeyPairId = null; // AWS Key Pair ID
  58. private static $__signingKeyResource = false; // Key resource, freeSigningKey() must be called to clear it from memory
  59. /**
  60. * Constructor - if you're not using the class statically
  61. *
  62. * @param array configuration
  63. * @return void
  64. */
  65. public function __construct($config = array())
  66. {
  67. if (empty($config)) {
  68. get_instance()->config->load('s3', TRUE);
  69. $config = get_instance()->config->item('s3');
  70. }
  71. $this->initialize($config);
  72. }
  73. /**
  74. * CodeIgniter support
  75. *
  76. * @param array configuration
  77. * @return void
  78. */
  79. public function initialize($config) {
  80. foreach ($config as $key => $val) {
  81. if(!in_array($key, array('accessKey', 'secretKey'))) {
  82. self::$key = $val;
  83. }
  84. }
  85. if (isset($config["accessKey"]) && isset($config["secretKey"]))
  86. self::setAuth($this->accessKey, $this->secretKey);
  87. }
  88. /**
  89. * Set the sertvice endpoint
  90. *
  91. * @param string $host Hostname
  92. * @return void
  93. */
  94. public function setEndpoint($host)
  95. {
  96. self::$endpoint = $host;
  97. }
  98. /**
  99. * Set AWS access key and secret key
  100. *
  101. * @param string $accessKey Access key
  102. * @param string $secretKey Secret key
  103. * @return void
  104. */
  105. public static function setAuth($accessKey, $secretKey)
  106. {
  107. self::$__accessKey = $accessKey;
  108. self::$__secretKey = $secretKey;
  109. }
  110. /**
  111. * Check if AWS keys have been set
  112. *
  113. * @return boolean
  114. */
  115. public static function hasAuth() {
  116. return (self::$__accessKey !== null && self::$__secretKey !== null);
  117. }
  118. /**
  119. * Set SSL on or off
  120. *
  121. * @param boolean $enabled SSL enabled
  122. * @param boolean $validate SSL certificate validation
  123. * @return void
  124. */
  125. public static function setSSL($enabled, $validate = true)
  126. {
  127. self::$useSSL = $enabled;
  128. self::$useSSLValidation = $validate;
  129. }
  130. /**
  131. * Set SSL client certificates (experimental)
  132. *
  133. * @param string $sslCert SSL client certificate
  134. * @param string $sslKey SSL client key
  135. * @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
  136. * @return void
  137. */
  138. public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null)
  139. {
  140. self::$sslCert = $sslCert;
  141. self::$sslKey = $sslKey;
  142. self::$sslCACert = $sslCACert;
  143. }
  144. /**
  145. * Set proxy information
  146. *
  147. * @param string $host Proxy hostname and port (localhost:1234)
  148. * @param string $user Proxy username
  149. * @param string $pass Proxy password
  150. * @param constant $type CURL proxy type
  151. * @return void
  152. */
  153. public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
  154. {
  155. self::$proxy = array('host' => $host, 'type' => $type, 'user' => null, 'pass' => 'null');
  156. }
  157. /**
  158. * Set the error mode to exceptions
  159. *
  160. * @param boolean $enabled Enable exceptions
  161. * @return void
  162. */
  163. public static function setExceptions($enabled = true)
  164. {
  165. self::$useExceptions = $enabled;
  166. }
  167. /**
  168. * Set signing key
  169. *
  170. * @param string $keyPairId AWS Key Pair ID
  171. * @param string $signingKey Private Key
  172. * @param boolean $isFile Load private key from file, set to false to load string
  173. * @return boolean
  174. */
  175. public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
  176. {
  177. self::$__signingKeyPairId = $keyPairId;
  178. if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
  179. file_get_contents($signingKey) : $signingKey)) !== false) return true;
  180. self::__triggerError('S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
  181. return false;
  182. }
  183. /**
  184. * Free signing key from memory, MUST be called if you are using setSigningKey()
  185. *
  186. * @return void
  187. */
  188. public static function freeSigningKey()
  189. {
  190. if (self::$__signingKeyResource !== false)
  191. openssl_free_key(self::$__signingKeyResource);
  192. }
  193. /**
  194. * Internal error handler
  195. *
  196. * @internal Internal error handler
  197. * @param string $message Error message
  198. * @param string $file Filename
  199. * @param integer $line Line number
  200. * @param integer $code Error code
  201. * @return void
  202. */
  203. private static function __triggerError($message, $file, $line, $code = 0)
  204. {
  205. if (self::$useExceptions)
  206. throw new S3Exception($message, $file, $line, $code);
  207. else
  208. trigger_error($message, E_USER_WARNING);
  209. }
  210. /**
  211. * Get a list of buckets
  212. *
  213. * @param boolean $detailed Returns detailed bucket list when true
  214. * @return array | false
  215. */
  216. public static function listBuckets($detailed = false)
  217. {
  218. $rest = new S3Request('GET', '', '', self::$endpoint);
  219. $rest = $rest->getResponse();
  220. if ($rest->error === false && $rest->code !== 200)
  221. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  222. if ($rest->error !== false)
  223. {
  224. self::__triggerError(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'],
  225. $rest->error['message']), __FILE__, __LINE__);
  226. return false;
  227. }
  228. $results = array();
  229. if (!isset($rest->body->Buckets)) return $results;
  230. if ($detailed)
  231. {
  232. if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
  233. $results['owner'] = array(
  234. 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
  235. );
  236. $results['buckets'] = array();
  237. foreach ($rest->body->Buckets->Bucket as $b)
  238. $results['buckets'][] = array(
  239. 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
  240. );
  241. } else
  242. foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
  243. return $results;
  244. }
  245. /*
  246. * Get contents for a bucket
  247. *
  248. * If maxKeys is null this method will loop through truncated result sets
  249. *
  250. * @param string $bucket Bucket name
  251. * @param string $prefix Prefix
  252. * @param string $marker Marker (last file listed)
  253. * @param string $maxKeys Max keys (maximum number of keys to return)
  254. * @param string $delimiter Delimiter
  255. * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
  256. * @return array | false
  257. */
  258. public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
  259. {
  260. $rest = new S3Request('GET', $bucket, '', self::$endpoint);
  261. if ($maxKeys == 0) $maxKeys = null;
  262. if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
  263. if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
  264. if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
  265. if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
  266. $response = $rest->getResponse();
  267. if ($response->error === false && $response->code !== 200)
  268. $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
  269. if ($response->error !== false)
  270. {
  271. self::__triggerError(sprintf("S3::getBucket(): [%s] %s",
  272. $response->error['code'], $response->error['message']), __FILE__, __LINE__);
  273. return false;
  274. }
  275. $results = array();
  276. $nextMarker = null;
  277. if (isset($response->body, $response->body->Contents))
  278. foreach ($response->body->Contents as $c)
  279. {
  280. $results[(string)$c->Key] = array(
  281. 'name' => (string)$c->Key,
  282. 'time' => strtotime((string)$c->LastModified),
  283. 'size' => (int)$c->Size,
  284. 'hash' => substr((string)$c->ETag, 1, -1)
  285. );
  286. $nextMarker = (string)$c->Key;
  287. }
  288. if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
  289. foreach ($response->body->CommonPrefixes as $c)
  290. $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
  291. if (isset($response->body, $response->body->IsTruncated) &&
  292. (string)$response->body->IsTruncated == 'false') return $results;
  293. if (isset($response->body, $response->body->NextMarker))
  294. $nextMarker = (string)$response->body->NextMarker;
  295. // Loop through truncated results if maxKeys isn't specified
  296. if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
  297. do
  298. {
  299. $rest = new S3Request('GET', $bucket, '', self::$endpoint);
  300. if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
  301. $rest->setParameter('marker', $nextMarker);
  302. if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
  303. if (($response = $rest->getResponse()) == false || $response->code !== 200) break;
  304. if (isset($response->body, $response->body->Contents))
  305. foreach ($response->body->Contents as $c)
  306. {
  307. $results[(string)$c->Key] = array(
  308. 'name' => (string)$c->Key,
  309. 'time' => strtotime((string)$c->LastModified),
  310. 'size' => (int)$c->Size,
  311. 'hash' => substr((string)$c->ETag, 1, -1)
  312. );
  313. $nextMarker = (string)$c->Key;
  314. }
  315. if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
  316. foreach ($response->body->CommonPrefixes as $c)
  317. $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
  318. if (isset($response->body, $response->body->NextMarker))
  319. $nextMarker = (string)$response->body->NextMarker;
  320. } while ($response !== false && (string)$response->body->IsTruncated == 'true');
  321. return $results;
  322. }
  323. /**
  324. * Put a bucket
  325. *
  326. * @param string $bucket Bucket name
  327. * @param constant $acl ACL flag
  328. * @param string $location Set as "EU" to create buckets hosted in Europe
  329. * @return boolean
  330. */
  331. public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
  332. {
  333. $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
  334. $rest->setAmzHeader('x-amz-acl', $acl);
  335. if ($location !== false)
  336. {
  337. $dom = new DOMDocument;
  338. $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
  339. $locationConstraint = $dom->createElement('LocationConstraint', $location);
  340. $createBucketConfiguration->appendChild($locationConstraint);
  341. $dom->appendChild($createBucketConfiguration);
  342. $rest->data = $dom->saveXML();
  343. $rest->size = strlen($rest->data);
  344. $rest->setHeader('Content-Type', 'application/xml');
  345. }
  346. $rest = $rest->getResponse();
  347. if ($rest->error === false && $rest->code !== 200)
  348. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  349. if ($rest->error !== false)
  350. {
  351. self::__triggerError(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
  352. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  353. return false;
  354. }
  355. return true;
  356. }
  357. /**
  358. * Delete an empty bucket
  359. *
  360. * @param string $bucket Bucket name
  361. * @return boolean
  362. */
  363. public static function deleteBucket($bucket)
  364. {
  365. $rest = new S3Request('DELETE', $bucket, '', self::$endpoint);
  366. $rest = $rest->getResponse();
  367. if ($rest->error === false && $rest->code !== 204)
  368. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  369. if ($rest->error !== false)
  370. {
  371. self::__triggerError(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
  372. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  373. return false;
  374. }
  375. return true;
  376. }
  377. /**
  378. * Create input info array for putObject()
  379. *
  380. * @param string $file Input file
  381. * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
  382. * @return array | false
  383. */
  384. public static function inputFile($file, $md5sum = true)
  385. {
  386. if (!file_exists($file) || !is_file($file) || !is_readable($file))
  387. {
  388. self::__triggerError('S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
  389. return false;
  390. }
  391. return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
  392. (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
  393. }
  394. /**
  395. * Create input array info for putObject() with a resource
  396. *
  397. * @param string $resource Input resource to read from
  398. * @param integer $bufferSize Input byte size
  399. * @param string $md5sum MD5 hash to send (optional)
  400. * @return array | false
  401. */
  402. public static function inputResource(&$resource, $bufferSize, $md5sum = '')
  403. {
  404. if (!is_resource($resource) || $bufferSize < 0)
  405. {
  406. self::__triggerError('S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
  407. return false;
  408. }
  409. $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
  410. $input['fp'] =& $resource;
  411. return $input;
  412. }
  413. /**
  414. * Put an object
  415. *
  416. * @param mixed $input Input data
  417. * @param string $bucket Bucket name
  418. * @param string $uri Object URI
  419. * @param constant $acl ACL constant
  420. * @param array $metaHeaders Array of x-amz-meta-* headers
  421. * @param array $requestHeaders Array of request headers or content type as a string
  422. * @param constant $storageClass Storage class constant
  423. * @return boolean
  424. */
  425. public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
  426. {
  427. if ($input === false) return false;
  428. $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
  429. if (!is_array($input)) $input = array(
  430. 'data' => $input, 'size' => strlen($input),
  431. 'md5sum' => base64_encode(md5($input, true))
  432. );
  433. // Data
  434. if (isset($input['fp']))
  435. $rest->fp =& $input['fp'];
  436. elseif (isset($input['file']))
  437. $rest->fp = @fopen($input['file'], 'rb');
  438. elseif (isset($input['data']))
  439. $rest->data = $input['data'];
  440. // Content-Length (required)
  441. if (isset($input['size']) && $input['size'] >= 0)
  442. $rest->size = $input['size'];
  443. else {
  444. if (isset($input['file']))
  445. $rest->size = filesize($input['file']);
  446. elseif (isset($input['data']))
  447. $rest->size = strlen($input['data']);
  448. }
  449. // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
  450. if (is_array($requestHeaders))
  451. foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
  452. elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
  453. $input['type'] = $requestHeaders;
  454. // Content-Type
  455. if (!isset($input['type']))
  456. {
  457. if (isset($requestHeaders['Content-Type']))
  458. $input['type'] =& $requestHeaders['Content-Type'];
  459. elseif (isset($input['file']))
  460. $input['type'] = self::__getMimeType($input['file']);
  461. else
  462. $input['type'] = 'application/octet-stream';
  463. }
  464. if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
  465. $rest->setAmzHeader('x-amz-storage-class', $storageClass);
  466. // We need to post with Content-Length and Content-Type, MD5 is optional
  467. if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
  468. {
  469. $rest->setHeader('Content-Type', $input['type']);
  470. if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
  471. $rest->setAmzHeader('x-amz-acl', $acl);
  472. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  473. $rest->getResponse();
  474. } else
  475. $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
  476. if ($rest->response->error === false && $rest->response->code !== 200)
  477. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  478. if ($rest->response->error !== false)
  479. {
  480. self::__triggerError(sprintf("S3::putObject(): [%s] %s",
  481. $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
  482. return false;
  483. }
  484. return true;
  485. }
  486. /**
  487. * Put an object from a file (legacy function)
  488. *
  489. * @param string $file Input file path
  490. * @param string $bucket Bucket name
  491. * @param string $uri Object URI
  492. * @param constant $acl ACL constant
  493. * @param array $metaHeaders Array of x-amz-meta-* headers
  494. * @param string $contentType Content type
  495. * @return boolean
  496. */
  497. public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
  498. {
  499. return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
  500. }
  501. /**
  502. * Put an object from a string (legacy function)
  503. *
  504. * @param string $string Input data
  505. * @param string $bucket Bucket name
  506. * @param string $uri Object URI
  507. * @param constant $acl ACL constant
  508. * @param array $metaHeaders Array of x-amz-meta-* headers
  509. * @param string $contentType Content type
  510. * @return boolean
  511. */
  512. public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
  513. {
  514. return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
  515. }
  516. /**
  517. * Get an object
  518. *
  519. * @param string $bucket Bucket name
  520. * @param string $uri Object URI
  521. * @param mixed $saveTo Filename or resource to write to
  522. * @return mixed
  523. */
  524. public static function getObject($bucket, $uri, $saveTo = false)
  525. {
  526. $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
  527. if ($saveTo !== false)
  528. {
  529. if (is_resource($saveTo))
  530. $rest->fp =& $saveTo;
  531. else
  532. if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
  533. $rest->file = realpath($saveTo);
  534. else
  535. $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
  536. }
  537. if ($rest->response->error === false) $rest->getResponse();
  538. if ($rest->response->error === false && $rest->response->code !== 200)
  539. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  540. if ($rest->response->error !== false)
  541. {
  542. self::__triggerError(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
  543. $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
  544. return false;
  545. }
  546. return $rest->response;
  547. }
  548. /**
  549. * Get object information
  550. *
  551. * @param string $bucket Bucket name
  552. * @param string $uri Object URI
  553. * @param boolean $returnInfo Return response information
  554. * @return mixed | false
  555. */
  556. public static function getObjectInfo($bucket, $uri, $returnInfo = true)
  557. {
  558. $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint);
  559. $rest = $rest->getResponse();
  560. if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
  561. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  562. if ($rest->error !== false)
  563. {
  564. self::__triggerError(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
  565. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  566. return false;
  567. }
  568. return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
  569. }
  570. /**
  571. * Copy an object
  572. *
  573. * @param string $bucket Source bucket name
  574. * @param string $uri Source object URI
  575. * @param string $bucket Destination bucket name
  576. * @param string $uri Destination object URI
  577. * @param constant $acl ACL constant
  578. * @param array $metaHeaders Optional array of x-amz-meta-* headers
  579. * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
  580. * @param constant $storageClass Storage class constant
  581. * @return mixed | false
  582. */
  583. public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
  584. {
  585. $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
  586. $rest->setHeader('Content-Length', 0);
  587. foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
  588. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  589. if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
  590. $rest->setAmzHeader('x-amz-storage-class', $storageClass);
  591. $rest->setAmzHeader('x-amz-acl', $acl);
  592. $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
  593. if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
  594. $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
  595. $rest = $rest->getResponse();
  596. if ($rest->error === false && $rest->code !== 200)
  597. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  598. if ($rest->error !== false)
  599. {
  600. self::__triggerError(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
  601. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  602. return false;
  603. }
  604. return isset($rest->body->LastModified, $rest->body->ETag) ? array(
  605. 'time' => strtotime((string)$rest->body->LastModified),
  606. 'hash' => substr((string)$rest->body->ETag, 1, -1)
  607. ) : false;
  608. }
  609. /**
  610. * Set logging for a bucket
  611. *
  612. * @param string $bucket Bucket name
  613. * @param string $targetBucket Target bucket (where logs are stored)
  614. * @param string $targetPrefix Log prefix (e,g; domain.com-)
  615. * @return boolean
  616. */
  617. public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
  618. {
  619. // The S3 log delivery group has to be added to the target bucket's ACP
  620. if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
  621. {
  622. // Only add permissions to the target bucket when they do not exist
  623. $aclWriteSet = false;
  624. $aclReadSet = false;
  625. foreach ($acp['acl'] as $acl)
  626. if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
  627. {
  628. if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
  629. elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
  630. }
  631. if (!$aclWriteSet) $acp['acl'][] = array(
  632. 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
  633. );
  634. if (!$aclReadSet) $acp['acl'][] = array(
  635. 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
  636. );
  637. if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
  638. }
  639. $dom = new DOMDocument;
  640. $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
  641. $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
  642. if ($targetBucket !== null)
  643. {
  644. if ($targetPrefix == null) $targetPrefix = $bucket . '-';
  645. $loggingEnabled = $dom->createElement('LoggingEnabled');
  646. $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
  647. $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
  648. // TODO: Add TargetGrants?
  649. $bucketLoggingStatus->appendChild($loggingEnabled);
  650. }
  651. $dom->appendChild($bucketLoggingStatus);
  652. $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
  653. $rest->setParameter('logging', null);
  654. $rest->data = $dom->saveXML();
  655. $rest->size = strlen($rest->data);
  656. $rest->setHeader('Content-Type', 'application/xml');
  657. $rest = $rest->getResponse();
  658. if ($rest->error === false && $rest->code !== 200)
  659. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  660. if ($rest->error !== false)
  661. {
  662. self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
  663. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  664. return false;
  665. }
  666. return true;
  667. }
  668. /**
  669. * Get logging status for a bucket
  670. *
  671. * This will return false if logging is not enabled.
  672. * Note: To enable logging, you also need to grant write access to the log group
  673. *
  674. * @param string $bucket Bucket name
  675. * @return array | false
  676. */
  677. public static function getBucketLogging($bucket)
  678. {
  679. $rest = new S3Request('GET', $bucket, '', self::$endpoint);
  680. $rest->setParameter('logging', null);
  681. $rest = $rest->getResponse();
  682. if ($rest->error === false && $rest->code !== 200)
  683. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  684. if ($rest->error !== false)
  685. {
  686. self::__triggerError(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
  687. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  688. return false;
  689. }
  690. if (!isset($rest->body->LoggingEnabled)) return false; // No logging
  691. return array(
  692. 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
  693. 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
  694. );
  695. }
  696. /**
  697. * Disable bucket logging
  698. *
  699. * @param string $bucket Bucket name
  700. * @return boolean
  701. */
  702. public static function disableBucketLogging($bucket)
  703. {
  704. return self::setBucketLogging($bucket, null);
  705. }
  706. /**
  707. * Get a bucket's location
  708. *
  709. * @param string $bucket Bucket name
  710. * @return string | false
  711. */
  712. public static function getBucketLocation($bucket)
  713. {
  714. $rest = new S3Request('GET', $bucket, '', self::$endpoint);
  715. $rest->setParameter('location', null);
  716. $rest = $rest->getResponse();
  717. if ($rest->error === false && $rest->code !== 200)
  718. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  719. if ($rest->error !== false)
  720. {
  721. self::__triggerError(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
  722. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  723. return false;
  724. }
  725. return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
  726. }
  727. /**
  728. * Set object or bucket Access Control Policy
  729. *
  730. * @param string $bucket Bucket name
  731. * @param string $uri Object URI
  732. * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
  733. * @return boolean
  734. */
  735. public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
  736. {
  737. $dom = new DOMDocument;
  738. $dom->formatOutput = true;
  739. $accessControlPolicy = $dom->createElement('AccessControlPolicy');
  740. $accessControlList = $dom->createElement('AccessControlList');
  741. // It seems the owner has to be passed along too
  742. $owner = $dom->createElement('Owner');
  743. $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
  744. $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
  745. $accessControlPolicy->appendChild($owner);
  746. foreach ($acp['acl'] as $g)
  747. {
  748. $grant = $dom->createElement('Grant');
  749. $grantee = $dom->createElement('Grantee');
  750. $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
  751. if (isset($g['id']))
  752. { // CanonicalUser (DisplayName is omitted)
  753. $grantee->setAttribute('xsi:type', 'CanonicalUser');
  754. $grantee->appendChild($dom->createElement('ID', $g['id']));
  755. }
  756. elseif (isset($g['email']))
  757. { // AmazonCustomerByEmail
  758. $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
  759. $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
  760. }
  761. elseif ($g['type'] == 'Group')
  762. { // Group
  763. $grantee->setAttribute('xsi:type', 'Group');
  764. $grantee->appendChild($dom->createElement('URI', $g['uri']));
  765. }
  766. $grant->appendChild($grantee);
  767. $grant->appendChild($dom->createElement('Permission', $g['permission']));
  768. $accessControlList->appendChild($grant);
  769. }
  770. $accessControlPolicy->appendChild($accessControlList);
  771. $dom->appendChild($accessControlPolicy);
  772. $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
  773. $rest->setParameter('acl', null);
  774. $rest->data = $dom->saveXML();
  775. $rest->size = strlen($rest->data);
  776. $rest->setHeader('Content-Type', 'application/xml');
  777. $rest = $rest->getResponse();
  778. if ($rest->error === false && $rest->code !== 200)
  779. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  780. if ($rest->error !== false)
  781. {
  782. self::__triggerError(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
  783. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  784. return false;
  785. }
  786. return true;
  787. }
  788. /**
  789. * Get object or bucket Access Control Policy
  790. *
  791. * @param string $bucket Bucket name
  792. * @param string $uri Object URI
  793. * @return mixed | false
  794. */
  795. public static function getAccessControlPolicy($bucket, $uri = '')
  796. {
  797. $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
  798. $rest->setParameter('acl', null);
  799. $rest = $rest->getResponse();
  800. if ($rest->error === false && $rest->code !== 200)
  801. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  802. if ($rest->error !== false)
  803. {
  804. self::__triggerError(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
  805. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  806. return false;
  807. }
  808. $acp = array();
  809. if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
  810. $acp['owner'] = array(
  811. 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
  812. );
  813. if (isset($rest->body->AccessControlList))
  814. {
  815. $acp['acl'] = array();
  816. foreach ($rest->body->AccessControlList->Grant as $grant)
  817. {
  818. foreach ($grant->Grantee as $grantee)
  819. {
  820. if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
  821. $acp['acl'][] = array(
  822. 'type' => 'CanonicalUser',
  823. 'id' => (string)$grantee->ID,
  824. 'name' => (string)$grantee->DisplayName,
  825. 'permission' => (string)$grant->Permission
  826. );
  827. elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
  828. $acp['acl'][] = array(
  829. 'type' => 'AmazonCustomerByEmail',
  830. 'email' => (string)$grantee->EmailAddress,
  831. 'permission' => (string)$grant->Permission
  832. );
  833. elseif (isset($grantee->URI)) // Group
  834. $acp['acl'][] = array(
  835. 'type' => 'Group',
  836. 'uri' => (string)$grantee->URI,
  837. 'permission' => (string)$grant->Permission
  838. );
  839. else continue;
  840. }
  841. }
  842. }
  843. return $acp;
  844. }
  845. /**
  846. * Delete an object
  847. *
  848. * @param string $bucket Bucket name
  849. * @param string $uri Object URI
  850. * @return boolean
  851. */
  852. public static function deleteObject($bucket, $uri)
  853. {
  854. $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
  855. $rest = $rest->getResponse();
  856. if ($rest->error === false && $rest->code !== 204)
  857. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  858. if ($rest->error !== false)
  859. {
  860. self::__triggerError(sprintf("S3::deleteObject(): [%s] %s",
  861. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  862. return false;
  863. }
  864. return true;
  865. }
  866. /**
  867. * Get a query string authenticated URL
  868. *
  869. * @param string $bucket Bucket name
  870. * @param string $uri Object URI
  871. * @param integer $lifetime Lifetime in seconds
  872. * @param boolean $hostBucket Use the bucket name as the hostname
  873. * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
  874. * @return string
  875. */
  876. public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
  877. {
  878. $expires = time() + $lifetime;
  879. $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
  880. return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
  881. // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
  882. $hostBucket ? $bucket : 's3.amazonaws.com/'.$bucket, $uri, self::$__accessKey, $expires,
  883. urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
  884. }
  885. /**
  886. * Get a CloudFront signed policy URL
  887. *
  888. * @param array $policy Policy
  889. * @return string
  890. */
  891. public static function getSignedPolicyURL($policy)
  892. {
  893. $data = json_encode($policy);
  894. $signature = '';
  895. if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
  896. $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
  897. $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
  898. $url = $policy['Statement'][0]['Resource'] . '?';
  899. foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
  900. $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
  901. return substr($url, 0, -1);
  902. }
  903. /**
  904. * Get a CloudFront canned policy URL
  905. *
  906. * @param string $string URL to sign
  907. * @param integer $lifetime URL lifetime
  908. * @return string
  909. */
  910. public static function getSignedCannedURL($url, $lifetime)
  911. {
  912. return self::getSignedPolicyURL(array(
  913. 'Statement' => array(
  914. array('Resource' => $url, 'Condition' => array(
  915. 'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
  916. ))
  917. )
  918. ));
  919. }
  920. /**
  921. * Get upload POST parameters for form uploads
  922. *
  923. * @param string $bucket Bucket name
  924. * @param string $uriPrefix Object URI prefix
  925. * @param constant $acl ACL constant
  926. * @param integer $lifetime Lifetime in seconds
  927. * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
  928. * @param string $successRedirect Redirect URL or 200 / 201 status code
  929. * @param array $amzHeaders Array of x-amz-meta-* headers
  930. * @param array $headers Array of request headers or content type as a string
  931. * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
  932. * @return object
  933. */
  934. public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
  935. $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
  936. {
  937. // Create policy object
  938. $policy = new stdClass;
  939. $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
  940. $policy->conditions = array();
  941. $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
  942. $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
  943. $obj = new stdClass; // 200 for non-redirect uploads
  944. if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
  945. $obj->success_action_status = (string)$successRedirect;
  946. else // URL
  947. $obj->success_action_redirect = $successRedirect;
  948. array_push($policy->conditions, $obj);
  949. if ($acl !== self::ACL_PUBLIC_READ)
  950. array_push($policy->conditions, array('eq', '$acl', $acl));
  951. array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
  952. if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
  953. foreach (array_keys($headers) as $headerKey)
  954. array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
  955. foreach ($amzHeaders as $headerKey => $headerVal)
  956. {
  957. $obj = new stdClass;
  958. $obj->{$headerKey} = (string)$headerVal;
  959. array_push($policy->conditions, $obj);
  960. }
  961. array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
  962. $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
  963. // Create parameters
  964. $params = new stdClass;
  965. $params->AWSAccessKeyId = self::$__accessKey;
  966. $params->key = $uriPrefix.'${filename}';
  967. $params->acl = $acl;
  968. $params->policy = $policy; unset($policy);
  969. $params->signature = self::__getHash($params->policy);
  970. if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
  971. $params->success_action_status = (string)$successRedirect;
  972. else
  973. $params->success_action_redirect = $successRedirect;
  974. foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
  975. foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
  976. return $params;
  977. }
  978. /**
  979. * Create a CloudFront distribution
  980. *
  981. * @param string $bucket Bucket name
  982. * @param boolean $enabled Enabled (true/false)
  983. * @param array $cnames Array containing CNAME aliases
  984. * @param string $comment Use the bucket name as the hostname
  985. * @param string $defaultRootObject Default root object
  986. * @param string $originAccessIdentity Origin access identity
  987. * @param array $trustedSigners Array of trusted signers
  988. * @return array | false
  989. */
  990. public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
  991. {
  992. if (!extension_loaded('openssl'))
  993. {
  994. self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
  995. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  996. return false;
  997. }
  998. $useSSL = self::$useSSL;
  999. self::$useSSL = true; // CloudFront requires SSL
  1000. $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
  1001. $rest->data = self::__getCloudFrontDistributionConfigXML(
  1002. $bucket.'.s3.amazonaws.com',
  1003. $enabled,
  1004. (string)$comment,
  1005. (string)microtime(true),
  1006. $cnames,
  1007. $defaultRootObject,
  1008. $originAccessIdentity,
  1009. $trustedSigners
  1010. );
  1011. $rest->size = strlen($rest->data);
  1012. $rest->setHeader('Content-Type', 'application/xml');
  1013. $rest = self::__getCloudFrontResponse($rest);
  1014. self::$useSSL = $useSSL;
  1015. if ($rest->error === false && $rest->code !== 201)
  1016. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1017. if ($rest->error !== false)
  1018. {
  1019. self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
  1020. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1021. return false;
  1022. } elseif ($rest->body instanceof SimpleXMLElement)
  1023. return self::__parseCloudFrontDistributionConfig($rest->body);
  1024. return false;
  1025. }
  1026. /**
  1027. * Get CloudFront distribution info
  1028. *
  1029. * @param string $distributionId Distribution ID from listDistributions()
  1030. * @return array | false
  1031. */
  1032. public static function getDistribution($distributionId)
  1033. {
  1034. if (!extension_loaded('openssl'))
  1035. {
  1036. self::__triggerError(sprintf("S3::getDistribution($distributionId): %s",
  1037. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1038. return false;
  1039. }
  1040. $useSSL = self::$useSSL;
  1041. self::$useSSL = true; // CloudFront requires SSL
  1042. $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
  1043. $rest = self::__getCloudFrontResponse($rest);
  1044. self::$useSSL = $useSSL;
  1045. if ($rest->error === false && $rest->code !== 200)
  1046. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1047. if ($rest->error !== false)
  1048. {
  1049. self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s",
  1050. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1051. return false;
  1052. }
  1053. elseif ($rest->body instanceof SimpleXMLElement)
  1054. {
  1055. $dist = self::__parseCloudFrontDistributionConfig($rest->body);
  1056. $dist['hash'] = $rest->headers['hash'];
  1057. $dist['id'] = $distributionId;
  1058. return $dist;
  1059. }
  1060. return false;
  1061. }
  1062. /**
  1063. * Update a CloudFront distribution
  1064. *
  1065. * @param array $dist Distribution array info identical to output of getDistribution()
  1066. * @return array | false
  1067. */
  1068. public static function updateDistribution($dist)
  1069. {
  1070. if (!extension_loaded('openssl'))
  1071. {
  1072. self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s",
  1073. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1074. return false;
  1075. }
  1076. $useSSL = self::$useSSL;
  1077. self::$useSSL = true; // CloudFront requires SSL
  1078. $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
  1079. $rest->data = self::__getCloudFrontDistributionConfigXML(
  1080. $dist['origin'],
  1081. $dist['enabled'],
  1082. $dist['comment'],
  1083. $dist['callerReference'],
  1084. $dist['cnames'],
  1085. $dist['defaultRootObject'],
  1086. $dist['originAccessIdentity'],
  1087. $dist['trustedSigners']
  1088. );
  1089. $rest->size = strlen($rest->data);
  1090. $rest->setHeader('If-Match', $dist['hash']);
  1091. $rest = self::__getCloudFrontResponse($rest);
  1092. self::$useSSL = $useSSL;
  1093. if ($rest->error === false && $rest->code !== 200)
  1094. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1095. if ($rest->error !== false)
  1096. {
  1097. self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s",
  1098. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1099. return false;
  1100. } else {
  1101. $dist = self::__parseCloudFrontDistributionConfig($rest->body);
  1102. $dist['hash'] = $rest->headers['hash'];
  1103. return $dist;
  1104. }
  1105. return false;
  1106. }
  1107. /**
  1108. * Delete a CloudFront distribution
  1109. *
  1110. * @param array $dist Distribution array info identical to output of getDistribution()
  1111. * @return boolean
  1112. */
  1113. public static function deleteDistribution($dist)
  1114. {
  1115. if (!extension_loaded('openssl'))
  1116. {
  1117. self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s",
  1118. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1119. return false;
  1120. }
  1121. $useSSL = self::$useSSL;
  1122. self::$useSSL = true; // CloudFront requires SSL
  1123. $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
  1124. $rest->setHeader('If-Match', $dist['hash']);
  1125. $rest = self::__getCloudFrontResponse($rest);
  1126. self::$useSSL = $useSSL;
  1127. if ($rest->error === false && $rest->code !== 204)
  1128. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1129. if ($rest->error !== false)
  1130. {
  1131. self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
  1132. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1133. return false;
  1134. }
  1135. return true;
  1136. }
  1137. /**
  1138. * Get a list of CloudFront distributions
  1139. *
  1140. * @return array
  1141. */
  1142. public static function listDistributions()
  1143. {
  1144. if (!extension_loaded('openssl'))
  1145. {
  1146. self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
  1147. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1148. return false;
  1149. }
  1150. $useSSL = self::$useSSL;
  1151. self::$useSSL = true; // CloudFront requires SSL
  1152. $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
  1153. $rest = self::__getCloudFrontResponse($rest);
  1154. self::$useSSL = $useSSL;
  1155. if ($rest->error === false && $rest->code !== 200)
  1156. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1157. if ($rest->error !== false)
  1158. {
  1159. self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
  1160. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1161. return false;
  1162. }
  1163. elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
  1164. {
  1165. $list = array();
  1166. if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
  1167. {
  1168. //$info['marker'] = (string)$rest->body->Marker;
  1169. //$info['maxItems'] = (int)$rest->body->MaxItems;
  1170. //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
  1171. }
  1172. foreach ($rest->body->DistributionSummary as $summary)
  1173. $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
  1174. return $list;
  1175. }
  1176. return array();
  1177. }
  1178. /**
  1179. * List CloudFront Origin Access Identities
  1180. *
  1181. * @return array
  1182. */
  1183. public static function listOriginAccessIdentities()
  1184. {
  1185. if (!extension_loaded('openssl'))
  1186. {
  1187. self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
  1188. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1189. return false;
  1190. }
  1191. self::$useSSL = true; // CloudFront requires SSL
  1192. $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
  1193. $rest = self::__getCloudFrontResponse($rest);
  1194. $useSSL = self::$useSSL;
  1195. if ($rest->error === false && $rest->code !== 200)
  1196. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1197. if ($rest->error !== false)
  1198. {
  1199. trigger_error(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
  1200. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  1201. return false;
  1202. }
  1203. if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
  1204. {
  1205. $identities = array();
  1206. foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
  1207. if (isset($identity->S3CanonicalUserId))
  1208. $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
  1209. return $identities;
  1210. }
  1211. return false;
  1212. }
  1213. /**
  1214. * Invalidate objects in a CloudFront distribution
  1215. *
  1216. * Thanks to Martin Lindkvist for S3::invalidateDistribution()
  1217. *
  1218. * @param string $distributionId Distribution ID from listDistributions()
  1219. * @param array $paths Array of object paths to invalidate
  1220. * @return boolean
  1221. */
  1222. public static function invalidateDistribution($distributionId, $paths)
  1223. {
  1224. if (!extension_loaded('openssl'))
  1225. {
  1226. self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s",
  1227. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1228. return false;
  1229. }
  1230. $useSSL = self::$useSSL;
  1231. self::$useSSL = true; // CloudFront requires SSL
  1232. $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
  1233. $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
  1234. $rest->size = strlen($rest->data);
  1235. $rest = self::__getCloudFrontResponse($rest);
  1236. self::$useSSL = $useSSL;
  1237. if ($rest->error === false && $rest->code !== 201)
  1238. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1239. if ($rest->error !== false)
  1240. {
  1241. trigger_error(sprintf("S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
  1242. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  1243. return false;
  1244. }
  1245. return true;
  1246. }
  1247. /**
  1248. * Get a InvalidationBatch DOMDocument
  1249. *
  1250. * @internal Used to create XML in invalidateDistribution()
  1251. * @param array $paths Paths to objects to invalidateDistribution
  1252. * @return string
  1253. */
  1254. private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') {
  1255. $dom = new DOMDocument('1.0', 'UTF-8');
  1256. $dom->formatOutput = true;
  1257. $invalidationBatch = $dom->createElement('InvalidationBatch');
  1258. foreach ($paths as $path)
  1259. $invalidationBatch->appendChild($dom->createElement('Path', $path));
  1260. $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
  1261. $dom->appendChild($invalidationBatch);
  1262. return $dom->saveXML();
  1263. }
  1264. /**
  1265. * List your invalidation batches for invalidateDistribution() in a CloudFront distribution
  1266. *
  1267. * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html
  1268. * returned array looks like this:
  1269. * Array
  1270. * (
  1271. * [I31TWB0CN9V6XD] => InProgress
  1272. * [IT3TFE31M0IHZ] => Completed
  1273. * [I12HK7MPO1UQDA] => Completed
  1274. * [I1IA7R6JKTC3L2] => Completed
  1275. * )
  1276. *
  1277. * @param string $distributionId Distribution ID from listDistributions()
  1278. * @return array
  1279. */
  1280. public static function getDistributionInvalidationList($distributionId)
  1281. {
  1282. if (!extension_loaded('openssl'))
  1283. {
  1284. self::__triggerError(sprintf("S3::getDistributionInvalidationList(): [%s] %s",
  1285. "CloudFront functionality requires S…