PageRenderTime 58ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/lib/S3.php

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