PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/w3-total-cache/lib/S3.php

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