PageRenderTime 56ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/updraftplus/includes/S3.php

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

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