PageRenderTime 293ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 1ms

/repository/s3/S3.php

https://bitbucket.org/moodle/moodle
PHP | 2388 lines | 1344 code | 271 blank | 773 comment | 290 complexity | c194473c668a0d7f75d30ce92eb1d5ed MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0

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

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

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