PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/S3.old.php

https://bitbucket.org/ardydedase/web-build-optimizer
PHP | 1922 lines | 1191 code | 223 blank | 508 comment | 255 complexity | 2af8375bae7d2ef3eff8ae1800c7b36d MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause

Large files files are truncated, but you can 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 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', strtoupper($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}, {$uri}): [%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 a query string authenticated URL
  854. *
  855. * @param string $bucket Bucket name
  856. * @param string $uri Object URI
  857. * @param integer $lifetime Lifetime in seconds
  858. * @param boolean $hostBucket Use the bucket name as the hostname
  859. * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
  860. * @return string
  861. */
  862. public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
  863. {
  864. $expires = time() + $lifetime;
  865. $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
  866. return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
  867. $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
  868. urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
  869. }
  870. /**
  871. * Get a CloudFront signed policy URL
  872. *
  873. * @param array $policy Policy
  874. * @return string
  875. */
  876. public static function getSignedPolicyURL($policy)
  877. {
  878. $data = json_encode($policy);
  879. $signature = '';
  880. if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
  881. $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
  882. $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
  883. $url = $policy['Statement'][0]['Resource'] . '?';
  884. foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
  885. $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
  886. return substr($url, 0, -1);
  887. }
  888. /**
  889. * Get a CloudFront canned policy URL
  890. *
  891. * @param string $string URL to sign
  892. * @param integer $lifetime URL lifetime
  893. * @return string
  894. */
  895. public static function getSignedCannedURL($url, $lifetime)
  896. {
  897. return self::getSignedPolicyURL(array(
  898. 'Statement' => array(
  899. array('Resource' => $url, 'Condition' => array(
  900. 'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
  901. ))
  902. )
  903. ));
  904. }
  905. /**
  906. * Get upload POST parameters for form uploads
  907. *
  908. * @param string $bucket Bucket name
  909. * @param string $uriPrefix Object URI prefix
  910. * @param constant $acl ACL constant
  911. * @param integer $lifetime Lifetime in seconds
  912. * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
  913. * @param string $successRedirect Redirect URL or 200 / 201 status code
  914. * @param array $amzHeaders Array of x-amz-meta-* headers
  915. * @param array $headers Array of request headers or content type as a string
  916. * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
  917. * @return object
  918. */
  919. public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
  920. $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
  921. {
  922. // Create policy object
  923. $policy = new stdClass;
  924. $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
  925. $policy->conditions = array();
  926. $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
  927. $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
  928. $obj = new stdClass; // 200 for non-redirect uploads
  929. if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
  930. $obj->success_action_status = (string)$successRedirect;
  931. else // URL
  932. $obj->success_action_redirect = $successRedirect;
  933. array_push($policy->conditions, $obj);
  934. if ($acl !== self::ACL_PUBLIC_READ)
  935. array_push($policy->conditions, array('eq', '$acl', $acl));
  936. array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
  937. if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
  938. foreach (array_keys($headers) as $headerKey)
  939. array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
  940. foreach ($amzHeaders as $headerKey => $headerVal)
  941. {
  942. $obj = new stdClass;
  943. $obj->{$headerKey} = (string)$headerVal;
  944. array_push($policy->conditions, $obj);
  945. }
  946. array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
  947. $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
  948. // Create parameters
  949. $params = new stdClass;
  950. $params->AWSAccessKeyId = self::$__accessKey;
  951. $params->key = $uriPrefix.'${filename}';
  952. $params->acl = $acl;
  953. $params->policy = $policy; unset($policy);
  954. $params->signature = self::__getHash($params->policy);
  955. if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
  956. $params->success_action_status = (string)$successRedirect;
  957. else
  958. $params->success_action_redirect = $successRedirect;
  959. foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
  960. foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
  961. return $params;
  962. }
  963. /**
  964. * Create a CloudFront distribution
  965. *
  966. * @param string $bucket Bucket name
  967. * @param boolean $enabled Enabled (true/false)
  968. * @param array $cnames Array containing CNAME aliases
  969. * @param string $comment Use the bucket name as the hostname
  970. * @param string $defaultRootObject Default root object
  971. * @param string $originAccessIdentity Origin access identity
  972. * @param array $trustedSigners Array of trusted signers
  973. * @return array | false
  974. */
  975. public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
  976. {
  977. if (!extension_loaded('openssl'))
  978. {
  979. self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
  980. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  981. return false;
  982. }
  983. $useSSL = self::$useSSL;
  984. self::$useSSL = true; // CloudFront requires SSL
  985. $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
  986. $rest->data = self::__getCloudFrontDistributionConfigXML(
  987. $bucket.'.s3.amazonaws.com',
  988. $enabled,
  989. (string)$comment,
  990. (string)microtime(true),
  991. $cnames,
  992. $defaultRootObject,
  993. $originAccessIdentity,
  994. $trustedSigners
  995. );
  996. $rest->size = strlen($rest->data);
  997. $rest->setHeader('Content-Type', 'application/xml');
  998. $rest = self::__getCloudFrontResponse($rest);
  999. self::$useSSL = $useSSL;
  1000. if ($rest->error === false && $rest->code !== 201)
  1001. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1002. if ($rest->error !== false)
  1003. {
  1004. self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
  1005. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1006. return false;
  1007. } elseif ($rest->body instanceof SimpleXMLElement)
  1008. return self::__parseCloudFrontDistributionConfig($rest->body);
  1009. return false;
  1010. }
  1011. /**
  1012. * Get CloudFront distribution info
  1013. *
  1014. * @param string $distributionId Distribution ID from listDistributions()
  1015. * @return array | false
  1016. */
  1017. public static function getDistribution($distributionId)
  1018. {
  1019. if (!extension_loaded('openssl'))
  1020. {
  1021. self::__triggerError(sprintf("S3::getDistribution($distributionId): %s",
  1022. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1023. return false;
  1024. }
  1025. $useSSL = self::$useSSL;
  1026. self::$useSSL = true; // CloudFront requires SSL
  1027. $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
  1028. $rest = self::__getCloudFrontResponse($rest);
  1029. self::$useSSL = $useSSL;
  1030. if ($rest->error === false && $rest->code !== 200)
  1031. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1032. if ($rest->error !== false)
  1033. {
  1034. self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s",
  1035. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1036. return false;
  1037. }
  1038. elseif ($rest->body instanceof SimpleXMLElement)
  1039. {
  1040. $dist = self::__parseCloudFrontDistributionConfig($rest->body);
  1041. $dist['hash'] = $rest->headers['hash'];
  1042. $dist['id'] = $distributionId;
  1043. return $dist;
  1044. }
  1045. return false;
  1046. }
  1047. /**
  1048. * Update a CloudFront distribution
  1049. *
  1050. * @param array $dist Distribution array info identical to output of getDistribution()
  1051. * @return array | false
  1052. */
  1053. public static function updateDistribution($dist)
  1054. {
  1055. if (!extension_loaded('openssl'))
  1056. {
  1057. self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s",
  1058. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1059. return false;
  1060. }
  1061. $useSSL = self::$useSSL;
  1062. self::$useSSL = true; // CloudFront requires SSL
  1063. $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
  1064. $rest->data = self::__getCloudFrontDistributionConfigXML(
  1065. $dist['origin'],
  1066. $dist['enabled'],
  1067. $dist['comment'],
  1068. $dist['callerReference'],
  1069. $dist['cnames'],
  1070. $dist['defaultRootObject'],
  1071. $dist['originAccessIdentity'],
  1072. $dist['trustedSigners']
  1073. );
  1074. $rest->size = strlen($rest->data);
  1075. $rest->setHeader('If-Match', $dist['hash']);
  1076. $rest = self::__getCloudFrontResponse($rest);
  1077. self::$useSSL = $useSSL;
  1078. if ($rest->error === false && $rest->code !== 200)
  1079. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1080. if ($rest->error !== false)
  1081. {
  1082. self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s",
  1083. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1084. return false;
  1085. } else {
  1086. $dist = self::__parseCloudFrontDistributionConfig($rest->body);
  1087. $dist['hash'] = $rest->headers['hash'];
  1088. return $dist;
  1089. }
  1090. return false;
  1091. }
  1092. /**
  1093. * Delete a CloudFront distribution
  1094. *
  1095. * @param array $dist Distribution array info identical to output of getDistribution()
  1096. * @return boolean
  1097. */
  1098. public static function deleteDistribution($dist)
  1099. {
  1100. if (!extension_loaded('openssl'))
  1101. {
  1102. self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s",
  1103. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1104. return false;
  1105. }
  1106. $useSSL = self::$useSSL;
  1107. self::$useSSL = true; // CloudFront requires SSL
  1108. $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
  1109. $rest->setHeader('If-Match', $dist['hash']);
  1110. $rest = self::__getCloudFrontResponse($rest);
  1111. self::$useSSL = $useSSL;
  1112. if ($rest->error === false && $rest->code !== 204)
  1113. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1114. if ($rest->error !== false)
  1115. {
  1116. self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
  1117. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1118. return false;
  1119. }
  1120. return true;
  1121. }
  1122. /**
  1123. * Get a list of CloudFront distributions
  1124. *
  1125. * @return array
  1126. */
  1127. public static function listDistributions()
  1128. {
  1129. if (!extension_loaded('openssl'))
  1130. {
  1131. self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
  1132. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1133. return false;
  1134. }
  1135. $useSSL = self::$useSSL;
  1136. self::$useSSL = true; // CloudFront requires SSL
  1137. $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
  1138. $rest = self::__getCloudFrontResponse($rest);
  1139. self::$useSSL = $useSSL;
  1140. if ($rest->error === false && $rest->code !== 200)
  1141. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1142. if ($rest->error !== false)
  1143. {
  1144. self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
  1145. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1146. return false;
  1147. }
  1148. elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
  1149. {
  1150. $list = array();
  1151. if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
  1152. {
  1153. //$info['marker'] = (string)$rest->body->Marker;
  1154. //$info['maxItems'] = (int)$rest->body->MaxItems;
  1155. //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
  1156. }
  1157. foreach ($rest->body->DistributionSummary as $summary)
  1158. $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
  1159. return $list;
  1160. }
  1161. return array();
  1162. }
  1163. /**
  1164. * List CloudFront Origin Access Identities
  1165. *
  1166. * @return array
  1167. */
  1168. public static function listOriginAccessIdentities()
  1169. {
  1170. if (!extension_loaded('openssl'))
  1171. {
  1172. self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
  1173. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1174. return false;
  1175. }
  1176. self::$useSSL = true; // CloudFront requires SSL
  1177. $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
  1178. $rest = self::__getCloudFrontResponse($rest);
  1179. $useSSL = self::$useSSL;
  1180. if ($rest->error === false && $rest->code !== 200)
  1181. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1182. if ($rest->error !== false)
  1183. {
  1184. trigger_error(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
  1185. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  1186. return false;
  1187. }
  1188. if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
  1189. {
  1190. $identities = array();
  1191. foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
  1192. if (isset($identity->S3CanonicalUserId))
  1193. $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
  1194. return $identities;
  1195. }
  1196. return false;
  1197. }
  1198. /**
  1199. * Invalidate objects in a CloudFront distribution
  1200. *
  1201. * Thanks to Martin Lindkvist for S3::invalidateDistribution()
  1202. *
  1203. * @param string $distributionId Distribution ID from listDistributions()
  1204. * @param array $paths Array of object paths to invalidate
  1205. * @return boolean
  1206. */
  1207. public static function invalidateDistribution($distributionId, $paths)
  1208. {
  1209. if (!extension_loaded('openssl'))
  1210. {
  1211. self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s",
  1212. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1213. return false;
  1214. }
  1215. $useSSL = self::$useSSL;
  1216. self::$useSSL = true; // CloudFront requires SSL
  1217. $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
  1218. $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
  1219. $rest->size = strlen($rest->data);
  1220. $rest = self::__getCloudFrontResponse($rest);
  1221. self::$useSSL = $useSSL;
  1222. if ($rest->error === false && $rest->code !== 201)
  1223. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1224. if ($rest->error !== false)
  1225. {
  1226. trigger_error(sprintf("S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
  1227. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  1228. return false;
  1229. }
  1230. return true;
  1231. }
  1232. /**
  1233. * Get a InvalidationBatch DOMDocument
  1234. *
  1235. * @internal Used to create XML in invalidateDistribution()
  1236. * @param array $paths Paths to objects to invalidateDistribution
  1237. * @return string
  1238. */
  1239. private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') {
  1240. $dom = new DOMDocument('1.0', 'UTF-8');
  1241. $dom->formatOutput = true;
  1242. $invalidationBatch = $dom->createElement('InvalidationBatch');
  1243. foreach ($paths as $path)
  1244. $invalidationBatch->appendChild($dom->createElement('Path', $path));
  1245. $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
  1246. $dom->appendChild($invalidationBatch);
  1247. return $dom->saveXML();
  1248. }
  1249. /**
  1250. * Get a DistributionConfig DOMDocument
  1251. *
  1252. * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
  1253. *
  1254. * @internal Used to create XML in createDistribution() and updateDistribution()
  1255. * @param string $bucket S3 Origin bucket
  1256. * @param boolean $enabled Enabled (true/false)
  1257. * @param string $comment Comment to append
  1258. * @param string $callerReference Caller reference
  1259. * @param array $cnames Array of CNAME aliases
  1260. * @param string $defaultRootObject Default root object
  1261. * @param string $originAccessIdentity Origin access identity
  1262. * @param array $trustedSigners Array of trusted signers
  1263. * @return string
  1264. */
  1265. private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
  1266. {
  1267. $dom = new DOMDocument('1.0', 'UTF-8');
  1268. $dom->formatOutput = true;
  1269. $distributionConfig = $dom->createElement('DistributionConfig');
  1270. $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
  1271. $origin = $dom->createElement('S3Origin');
  1272. $origin->appendChild($dom->createElement('DNSName', $bucket));
  1273. if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
  1274. $distributionConfig->appendChild($origin);
  1275. if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
  1276. $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
  1277. foreach ($cnames as $cname)
  1278. $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
  1279. if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
  1280. $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
  1281. $trusted = $dom->createElement('TrustedSigners');
  1282. foreach ($trustedSigners as $id => $type)
  1283. $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
  1284. $distributionConfig->appendChild($trusted);
  1285. $dom->appendChild($distributionConfig);
  1286. //var_dump($dom->saveXML());
  1287. return $dom->saveXML();
  1288. }
  1289. /**
  1290. * Parse a CloudFront distribution config
  1291. *
  1292. * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistributio…

Large files files are truncated, but you can click here to view the full file