PageRenderTime 67ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/system/expressionengine/third_party/ce_img_aws/libraries/Ce_s3.php

https://bitbucket.org/studiobreakfast/sync
PHP | 2053 lines | 1401 code | 189 blank | 463 comment | 232 complexity | 3136460885b5017a42183e5e702c4f8d MD5 | raw file

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

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

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