PageRenderTime 59ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/p_includes/S3.php

https://github.com/amanelis/rathersaurus
PHP | 889 lines | 549 code | 104 blank | 236 comment | 143 complexity | 7f67238728272ff53c711d07396e929c MD5 | raw file
  1. <?php
  2. function random_letters ($numofletters) {
  3. if (!isset($numofletters)) $numofletters = 10; // if $numofletters is not specified sets to 10 letters
  4. $literki = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'W');
  5. $ilosc_literek = count($literki);
  6. for ($licz = 0; $licz < $numofletters; $licz++) {
  7. $rand = rand(0, $ilosc_literek-1);
  8. $vercode = $vercode.$literki[$rand];
  9. }
  10. return $vercode;
  11. }
  12. class S3 {
  13. // ACL flags
  14. const ACL_PRIVATE = 'private';
  15. const ACL_PUBLIC_READ = 'public-read';
  16. const ACL_PUBLIC_READ_WRITE = 'public-read-write';
  17. private static $__accessKey; // AWS Access key
  18. private static $__secretKey; // AWS Secret key
  19. /**
  20. * Constructor, used if you're not calling the class statically
  21. *
  22. * @param string $accessKey Access key
  23. * @param string $secretKey Secret key
  24. * @return void
  25. */
  26. public function __construct($accessKey = null, $secretKey = null) {
  27. if ($accessKey !== null && $secretKey !== null)
  28. self::setAuth($accessKey, $secretKey);
  29. }
  30. /**
  31. * Set access information
  32. *
  33. * @param string $accessKey Access key
  34. * @param string $secretKey Secret key
  35. * @return void
  36. */
  37. public static function setAuth($accessKey, $secretKey) {
  38. self::$__accessKey = $accessKey;
  39. self::$__secretKey = $secretKey;
  40. }
  41. /**
  42. * Get a list of buckets
  43. *
  44. * @param boolean $detailed Returns detailed bucket list when true
  45. * @return array | false
  46. */
  47. public static function listBuckets($detailed = false) {
  48. $rest = new S3Request('GET', '', '');
  49. $rest = $rest->getResponse();
  50. if ($rest->error === false && $rest->code !== 200)
  51. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  52. if ($rest->error !== false) {
  53. trigger_error(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  54. return false;
  55. }
  56. $results = array(); //var_dump($rest->body);
  57. if (!isset($rest->body->Buckets)) return $results;
  58. if ($detailed) {
  59. if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
  60. $results['owner'] = array(
  61. 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
  62. );
  63. $results['buckets'] = array();
  64. foreach ($rest->body->Buckets->Bucket as $b)
  65. $results['buckets'][] = array(
  66. 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
  67. );
  68. } else
  69. foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
  70. return $results;
  71. }
  72. /*
  73. * Get contents for a bucket
  74. *
  75. * If maxKeys is null this method will loop through truncated result sets
  76. *
  77. * @param string $bucket Bucket name
  78. * @param string $prefix Prefix
  79. * @param string $marker Marker (last file listed)
  80. * @param string $maxKeys Max keys (maximum number of keys to return)
  81. * @return array | false
  82. */
  83. public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null) {
  84. $rest = new S3Request('GET', $bucket, '');
  85. if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
  86. if ($marker !== null && $prefix !== '') $rest->setParameter('marker', $marker);
  87. if ($maxKeys !== null && $prefix !== '') $rest->setParameter('max-keys', $maxKeys);
  88. $response = $rest->getResponse();
  89. if ($response->error === false && $response->code !== 200)
  90. $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
  91. if ($response->error !== false) {
  92. trigger_error(sprintf("S3::getBucket(): [%s] %s", $response->error['code'], $response->error['message']), E_USER_WARNING);
  93. return false;
  94. }
  95. $results = array();
  96. $lastMarker = null;
  97. if (isset($response->body, $response->body->Contents))
  98. foreach ($response->body->Contents as $c) {
  99. $results[(string)$c->Key] = array(
  100. 'name' => (string)$c->Key,
  101. 'time' => strToTime((string)$c->LastModified),
  102. 'size' => (int)$c->Size,
  103. 'hash' => substr((string)$c->ETag, 1, -1)
  104. );
  105. $lastMarker = (string)$c->Key;
  106. //$response->body->IsTruncated = 'true'; break;
  107. }
  108. if (isset($response->body->IsTruncated) &&
  109. (string)$response->body->IsTruncated == 'false') return $results;
  110. // Loop through truncated results if maxKeys isn't specified
  111. if ($maxKeys == null && $lastMarker !== null && (string)$response->body->IsTruncated == 'true')
  112. do {
  113. $rest = new S3Request('GET', $bucket, '');
  114. if ($prefix !== null) $rest->setParameter('prefix', $prefix);
  115. $rest->setParameter('marker', $lastMarker);
  116. if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break;
  117. if (isset($response->body, $response->body->Contents))
  118. foreach ($response->body->Contents as $c) {
  119. $results[(string)$c->Key] = array(
  120. 'name' => (string)$c->Key,
  121. 'time' => strToTime((string)$c->LastModified),
  122. 'size' => (int)$c->Size,
  123. 'hash' => substr((string)$c->ETag, 1, -1)
  124. );
  125. $lastMarker = (string)$c->Key;
  126. }
  127. } while ($response !== false && (string)$response->body->IsTruncated == 'true');
  128. return $results;
  129. }
  130. /**
  131. * Put a bucket
  132. *
  133. * @param string $bucket Bucket name
  134. * @param constant $acl ACL flag
  135. * @return boolean
  136. */
  137. public function putBucket($bucket, $acl = self::ACL_PRIVATE) {
  138. $rest = new S3Request('PUT', $bucket, '');
  139. $rest->setAmzHeader('x-amz-acl', $acl);
  140. $rest = $rest->getResponse();
  141. if ($rest->error === false && $rest->code !== 200)
  142. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  143. if ($rest->error !== false) {
  144. trigger_error(sprintf("S3::putBucket({$bucket}): [%s] %s",
  145. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  146. return false;
  147. }
  148. return true;
  149. }
  150. /**
  151. * Delete an empty bucket
  152. *
  153. * @param string $bucket Bucket name
  154. * @return boolean
  155. */
  156. public function deleteBucket($bucket = '') {
  157. $rest = new S3Request('DELETE', $bucket);
  158. $rest = $rest->getResponse();
  159. if ($rest->error === false && $rest->code !== 204)
  160. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  161. if ($rest->error !== false) {
  162. trigger_error(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
  163. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  164. return false;
  165. }
  166. return true;
  167. }
  168. /**
  169. * Create input info array for putObject()
  170. *
  171. * @param string $file Input file
  172. * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
  173. * @return array | false
  174. */
  175. public static function inputFile($file, $md5sum = true) {
  176. if (!file_exists($file) || !is_file($file) || !is_readable($file)) {
  177. trigger_error('S3::inputFile(): Unable to open input file: '.$file, E_USER_WARNING);
  178. return false;
  179. }
  180. return array('file' => $file, 'size' => filesize($file),
  181. 'md5sum' => $md5sum !== false ? (is_string($md5sum) ? $md5sum :
  182. base64_encode(md5_file($file, true))) : '');
  183. }
  184. /**
  185. * Use a resource for input
  186. *
  187. * @param string $file Input file
  188. * @param integer $bufferSize Input byte size
  189. * @param string $md5sum MD5 hash to send (optional)
  190. * @return array | false
  191. */
  192. public static function inputResource(&$resource, $bufferSize, $md5sum = '') {
  193. if (!is_resource($resource) || $bufferSize <= 0) {
  194. trigger_error('S3::inputResource(): Invalid resource or buffer size', E_USER_WARNING);
  195. return false;
  196. }
  197. $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
  198. $input['fp'] =& $resource;
  199. return $input;
  200. }
  201. /**
  202. * Put an object
  203. *
  204. * @param mixed $input Input data
  205. * @param string $bucket Bucket name
  206. * @param string $uri Object URI
  207. * @param constant $acl ACL constant
  208. * @param array $metaHeaders Array of x-amz-meta-* headers
  209. * @param string $contentType Content type
  210. * @return boolean
  211. */
  212. public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) {
  213. if ($input == false) return false;
  214. $rest = new S3Request('PUT', $bucket, $uri);
  215. if (is_string($input)) $input = array(
  216. 'data' => $input, 'size' => strlen($input),
  217. 'md5sum' => base64_encode(md5($input, true))
  218. );
  219. // Data
  220. if (isset($input['fp']))
  221. $rest->fp =& $input['fp'];
  222. elseif (isset($input['file']))
  223. $rest->fp = @fopen($input['file'], 'rb');
  224. elseif (isset($input['data']))
  225. $rest->data = $input['data'];
  226. // Content-Length (required)
  227. if (isset($input['size']) && $input['size'] > 0)
  228. $rest->size = $input['size'];
  229. else {
  230. if (isset($input['file']))
  231. $rest->size = filesize($input['file']);
  232. elseif (isset($input['data']))
  233. $rest->size = strlen($input['data']);
  234. }
  235. // Content-Type
  236. if ($contentType !== null)
  237. $input['type'] = $contentType;
  238. elseif (!isset($input['type']) && isset($input['file']))
  239. $input['type'] = self::__getMimeType($input['file']);
  240. else
  241. $input['type'] = 'application/octet-stream';
  242. // We need to post with the content-length and content-type, MD5 is optional
  243. if ($rest->size > 0 && ($rest->fp !== false || $rest->data !== false)) {
  244. $rest->setHeader('Content-Type', $input['type']);
  245. if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
  246. $rest->setAmzHeader('x-amz-acl', $acl);
  247. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  248. $rest->getResponse();
  249. } else
  250. $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
  251. if ($rest->response->error === false && $rest->response->code !== 200)
  252. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  253. if ($rest->response->error !== false) {
  254. trigger_error(sprintf("S3::putObject(): [%s] %s", $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING);
  255. return false;
  256. }
  257. return true;
  258. }
  259. /**
  260. * Puts an object from a file (legacy function)
  261. *
  262. * @param string $file Input file path
  263. * @param string $bucket Bucket name
  264. * @param string $uri Object URI
  265. * @param constant $acl ACL constant
  266. * @param array $metaHeaders Array of x-amz-meta-* headers
  267. * @param string $contentType Content type
  268. * @return boolean
  269. */
  270. public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) {
  271. return self::putObject(S3::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
  272. }
  273. /**
  274. * Put an object from a string (legacy function)
  275. *
  276. * @param string $string 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 string $contentType Content type
  282. * @return boolean
  283. */
  284. public function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain') {
  285. return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
  286. }
  287. /**
  288. * Get an object
  289. *
  290. * @param string $bucket Bucket name
  291. * @param string $uri Object URI
  292. * @param mixed &$saveTo Filename or resource to write to
  293. * @return mixed
  294. */
  295. public static function getObject($bucket = '', $uri = '', $saveTo = false) {
  296. $rest = new S3Request('GET', $bucket, $uri);
  297. if ($saveTo !== false) {
  298. if (is_resource($saveTo))
  299. $rest->fp =& $saveTo;
  300. else
  301. if (($rest->fp = @fopen($saveTo, 'wb')) == false)
  302. $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
  303. }
  304. if ($rest->response->error === false) $rest->getResponse();
  305. if ($rest->response->error === false && $rest->response->code !== 200)
  306. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  307. if ($rest->response->error !== false) {
  308. trigger_error(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
  309. $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING);
  310. return false;
  311. }
  312. $rest->file = realpath($saveTo);
  313. return $rest->response;
  314. }
  315. /**
  316. * Get object information
  317. *
  318. * @param string $bucket Bucket name
  319. * @param string $uri Object URI
  320. * @param boolean $returnInfo Return response information
  321. * @return mixed | false
  322. */
  323. public static function getObjectInfo($bucket = '', $uri = '', $returnInfo = true) {
  324. $rest = new S3Request('HEAD', $bucket, $uri);
  325. $rest = $rest->getResponse();
  326. if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
  327. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  328. if ($rest->error !== false) {
  329. trigger_error(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
  330. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  331. return false;
  332. }
  333. return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
  334. }
  335. /**
  336. * Set logging for a bucket
  337. *
  338. * @param string $bucket Bucket name
  339. * @param string $targetBucket Target bucket (where logs are stored)
  340. * @param string $targetPrefix Log prefix (e,g; domain.com-)
  341. * @return boolean
  342. */
  343. public static function setBucketLogging($bucket, $targetBucket, $targetPrefix) {
  344. $dom = new DOMDocument;
  345. $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
  346. $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
  347. $loggingEnabled = $dom->createElement('LoggingEnabled');
  348. $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
  349. $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
  350. // TODO: Add TargetGrants
  351. $bucketLoggingStatus->appendChild($loggingEnabled);
  352. $dom->appendChild($bucketLoggingStatus);
  353. $rest = new S3Request('PUT', $bucket, '');
  354. $rest->setParameter('logging', null);
  355. $rest->data = $dom->saveXML();
  356. $rest->size = strlen($rest->data);
  357. $rest->setHeader('Content-Type', 'application/xml');
  358. $rest = $rest->getResponse();
  359. if ($rest->error === false && $rest->code !== 200)
  360. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  361. if ($rest->error !== false) {
  362. trigger_error(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s",
  363. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  364. return false;
  365. }
  366. return true;
  367. }
  368. /**
  369. * Get logging status for a bucket
  370. *
  371. * This will return false if logging is not enabled.
  372. * Note: To enable logging, you also need to grant write access to the log group
  373. *
  374. * @param string $bucket Bucket name
  375. * @return array | false
  376. */
  377. public static function getBucketLogging($bucket = '') {
  378. $rest = new S3Request('GET', $bucket, '');
  379. $rest->setParameter('logging', null);
  380. $rest = $rest->getResponse();
  381. if ($rest->error === false && $rest->code !== 200)
  382. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  383. if ($rest->error !== false) {
  384. trigger_error(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
  385. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  386. return false;
  387. }
  388. if (!isset($rest->body->LoggingEnabled)) return false; // No logging
  389. return array(
  390. 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
  391. 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
  392. );
  393. }
  394. /**
  395. * Set object or bucket Access Control Policy
  396. *
  397. * @param string $bucket Bucket name
  398. * @param string $uri Object URI
  399. * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
  400. * @return boolean
  401. */
  402. public static function setAccessControlPolicy($bucket, $uri = '', $acp = array()) {
  403. $dom = new DOMDocument;
  404. $dom->formatOutput = true;
  405. $accessControlPolicy = $dom->createElement('AccessControlPolicy');
  406. $accessControlList = $dom->createElement('AccessControlList');
  407. // It seems the owner has to be passed along too
  408. $owner = $dom->createElement('Owner');
  409. $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
  410. $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
  411. $accessControlPolicy->appendChild($owner);
  412. foreach ($acp['acl'] as $g) {
  413. $grant = $dom->createElement('Grant');
  414. $grantee = $dom->createElement('Grantee');
  415. $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
  416. if (isset($g['id'])) { // CanonicalUser (DisplayName is omitted)
  417. $grantee->setAttribute('xsi:type', 'CanonicalUser');
  418. $grantee->appendChild($dom->createElement('ID', $g['id']));
  419. } elseif (isset($g['email'])) { // AmazonCustomerByEmail
  420. $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
  421. $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
  422. } elseif ($g['type'] == 'Group') { // Group
  423. $grantee->setAttribute('xsi:type', 'Group');
  424. $grantee->appendChild($dom->createElement('URI', $g['uri']));
  425. }
  426. $grant->appendChild($grantee);
  427. $grant->appendChild($dom->createElement('Permission', $g['permission']));
  428. $accessControlList->appendChild($grant);
  429. }
  430. $accessControlPolicy->appendChild($accessControlList);
  431. $dom->appendChild($accessControlPolicy);
  432. $rest = new S3Request('PUT', $bucket, '');
  433. $rest->setParameter('acl', null);
  434. $rest->data = $dom->saveXML();
  435. $rest->size = strlen($rest->data);
  436. $rest->setHeader('Content-Type', 'application/xml');
  437. $rest = $rest->getResponse();
  438. if ($rest->error === false && $rest->code !== 200)
  439. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  440. if ($rest->error !== false) {
  441. trigger_error(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
  442. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  443. return false;
  444. }
  445. return true;
  446. }
  447. /**
  448. * Get object or bucket Access Control Policy
  449. *
  450. * Currently this will trigger an error if there is no ACL on an object (will fix soon)
  451. *
  452. * @param string $bucket Bucket name
  453. * @param string $uri Object URI
  454. * @return mixed | false
  455. */
  456. public static function getAccessControlPolicy($bucket, $uri = '') {
  457. $rest = new S3Request('GET', $bucket, $uri);
  458. $rest->setParameter('acl', null);
  459. $rest = $rest->getResponse();
  460. if ($rest->error === false && $rest->code !== 200)
  461. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  462. if ($rest->error !== false) {
  463. trigger_error(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
  464. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  465. return false;
  466. }
  467. $acp = array();
  468. if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) {
  469. $acp['owner'] = array(
  470. 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
  471. );
  472. }
  473. if (isset($rest->body->AccessControlList)) {
  474. $acp['acl'] = array();
  475. foreach ($rest->body->AccessControlList->Grant as $grant) {
  476. foreach ($grant->Grantee as $grantee) {
  477. if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
  478. $acp['acl'][] = array(
  479. 'type' => 'CanonicalUser',
  480. 'id' => (string)$grantee->ID,
  481. 'name' => (string)$grantee->DisplayName,
  482. 'permission' => (string)$grant->Permission
  483. );
  484. elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
  485. $acp['acl'][] = array(
  486. 'type' => 'AmazonCustomerByEmail',
  487. 'email' => (string)$grantee->EmailAddress,
  488. 'permission' => (string)$grant->Permission
  489. );
  490. elseif (isset($grantee->URI)) // Group
  491. $acp['acl'][] = array(
  492. 'type' => 'Group',
  493. 'uri' => (string)$grantee->URI,
  494. 'permission' => (string)$grant->Permission
  495. );
  496. else continue;
  497. }
  498. }
  499. }
  500. return $acp;
  501. }
  502. /**
  503. * Delete an object
  504. *
  505. * @param string $bucket Bucket name
  506. * @param string $uri Object URI
  507. * @return mixed
  508. */
  509. public static function deleteObject($bucket = '', $uri = '') {
  510. $rest = new S3Request('DELETE', $bucket, $uri);
  511. $rest = $rest->getResponse();
  512. if ($rest->error === false && $rest->code !== 204)
  513. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  514. if ($rest->error !== false) {
  515. trigger_error(sprintf("S3::deleteObject(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  516. return false;
  517. }
  518. return true;
  519. }
  520. /**
  521. * Get MIME type for file
  522. *
  523. * @internal Used to get mime types
  524. * @param string &$file File path
  525. * @return string
  526. */
  527. public static function __getMimeType(&$file) {
  528. $type = false;
  529. // Fileinfo documentation says fileinfo_open() will use the
  530. // MAGIC env var for the magic file
  531. if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
  532. ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) {
  533. if (($type = finfo_file($finfo, $file)) !== false) {
  534. // Remove the charset and grab the last content-type
  535. $type = explode(' ', str_replace('; charset=', ';charset=', $type));
  536. $type = array_pop($type);
  537. $type = explode(';', $type);
  538. $type = array_shift($type);
  539. }
  540. finfo_close($finfo);
  541. // If anyone is still using mime_content_type()
  542. } elseif (function_exists('mime_content_type'))
  543. $type = mime_content_type($file);
  544. if ($type !== false && strlen($type) > 0) return $type;
  545. // Otherwise do it the old fashioned way
  546. static $exts = array(
  547. 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
  548. 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
  549. 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
  550. 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
  551. 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
  552. 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
  553. 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
  554. 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
  555. 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
  556. 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
  557. 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
  558. );
  559. $ext = strToLower(pathInfo($file, PATHINFO_EXTENSION));
  560. return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
  561. }
  562. /**
  563. * Generate the auth string: "AWS AccessKey:Signature"
  564. *
  565. * This uses the hash extension if loaded
  566. *
  567. * @internal Signs the request
  568. * @param string $string String to sign
  569. * @return string
  570. */
  571. public static function __getSignature($string) {
  572. return 'AWS '.self::$__accessKey.':'.base64_encode(extension_loaded('hash') ?
  573. hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
  574. (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
  575. pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
  576. (str_repeat(chr(0x36), 64))) . $string)))));
  577. }
  578. }
  579. final class S3Request {
  580. private $verb, $bucket, $uri, $resource = '', $parameters = array(),
  581. $amzHeaders = array(), $headers = array(
  582. 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
  583. );
  584. public $fp = false, $size = 0, $data = false, $response;
  585. /**
  586. * Constructor
  587. *
  588. * @param string $verb Verb
  589. * @param string $bucket Bucket name
  590. * @param string $uri Object URI
  591. * @return mixed
  592. */
  593. function __construct($verb, $bucket = '', $uri = '') {
  594. $this->verb = $verb;
  595. $this->bucket = strtolower($bucket);
  596. $this->uri = $uri !== '' ? '/'.$uri : '/';
  597. if ($this->bucket !== '') {
  598. $this->bucket = explode('/', $this->bucket);
  599. $this->resource = '/'.$this->bucket[0].$this->uri;
  600. $this->headers['Host'] = $this->bucket[0].'.s3.amazonaws.com';
  601. $this->bucket = implode('/', $this->bucket);
  602. } else {
  603. $this->headers['Host'] = 's3.amazonaws.com';
  604. if (strlen($this->uri) > 1)
  605. $this->resource = '/'.$this->bucket.$this->uri;
  606. else $this->resource = $this->uri;
  607. }
  608. $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
  609. $this->response = new STDClass;
  610. $this->response->error = false;
  611. }
  612. /**
  613. * Set request parameter
  614. *
  615. * @param string $key Key
  616. * @param string $value Value
  617. * @return void
  618. */
  619. public function setParameter($key, $value) {
  620. $this->parameters[$key] = $value;
  621. }
  622. /**
  623. * Set request header
  624. *
  625. * @param string $key Key
  626. * @param string $value Value
  627. * @return void
  628. */
  629. public function setHeader($key, $value) {
  630. $this->headers[$key] = $value;
  631. }
  632. /**
  633. * Set x-amz-meta-* header
  634. *
  635. * @param string $key Key
  636. * @param string $value Value
  637. * @return void
  638. */
  639. public function setAmzHeader($key, $value) {
  640. $this->amzHeaders[$key] = $value;
  641. }
  642. /**
  643. * Get the S3 response
  644. *
  645. * @return object | false
  646. */
  647. public function getResponse() {
  648. $query = '';
  649. if (sizeof($this->parameters) > 0) {
  650. $query = substr($this->uri, -1) !== '?' ? '?' : '&';
  651. foreach ($this->parameters as $var => $value)
  652. if ($value == null || $value == '') $query .= $var.'&';
  653. else $query .= $var.'='.$value.'&';
  654. $query = substr($query, 0, -1);
  655. $this->uri .= $query;
  656. if (isset($this->parameters['acl']) || !isset($this->parameters['logging']))
  657. $this->resource .= $query;
  658. }
  659. $url = (extension_loaded('openssl')?'https://':'http://').$this->headers['Host'].$this->uri;
  660. //var_dump($this->bucket, $this->uri, $this->resource, $url);
  661. // Basic setup
  662. $curl = curl_init();
  663. curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
  664. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
  665. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
  666. curl_setopt($curl, CURLOPT_URL, $url);
  667. // Headers
  668. $headers = array(); $amz = array();
  669. foreach ($this->amzHeaders as $header => $value)
  670. if (strlen($value) > 0) $headers[] = $header.': '.$value;
  671. foreach ($this->headers as $header => $value)
  672. if (strlen($value) > 0) $headers[] = $header.': '.$value;
  673. foreach ($this->amzHeaders as $header => $value)
  674. if (strlen($value) > 0) $amz[] = strToLower($header).':'.$value;
  675. $amz = (sizeof($amz) > 0) ? "\n".implode("\n", $amz) : '';
  676. // Authorization string
  677. $headers[] = 'Authorization: ' . S3::__getSignature(
  678. $this->verb."\n".
  679. $this->headers['Content-MD5']."\n".
  680. $this->headers['Content-Type']."\n".
  681. $this->headers['Date'].$amz."\n".$this->resource
  682. );
  683. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  684. curl_setopt($curl, CURLOPT_HEADER, false);
  685. curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
  686. curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
  687. curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
  688. // Request types
  689. switch ($this->verb) {
  690. case 'GET': break;
  691. case 'PUT':
  692. if ($this->fp !== false) {
  693. curl_setopt($curl, CURLOPT_PUT, true);
  694. curl_setopt($curl, CURLOPT_INFILE, $this->fp);
  695. if ($this->size > 0)
  696. curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
  697. } elseif ($this->data !== false) {
  698. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
  699. curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
  700. if ($this->size > 0)
  701. curl_setopt($curl, CURLOPT_BUFFERSIZE, $this->size);
  702. } else
  703. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
  704. break;
  705. case 'HEAD':
  706. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
  707. curl_setopt($curl, CURLOPT_NOBODY, true);
  708. break;
  709. case 'DELETE':
  710. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
  711. break;
  712. default: break;
  713. }
  714. // Execute, grab errors
  715. if (curl_exec($curl))
  716. $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  717. else
  718. $this->response->error = array(
  719. 'code' => curl_errno($curl),
  720. 'message' => curl_error($curl),
  721. 'resource' => $this->resource
  722. );
  723. @curl_close($curl);
  724. // Parse body into XML
  725. if ($this->response->error === false && isset($this->response->headers['type']) &&
  726. $this->response->headers['type'] == 'application/xml' && isset($this->response->body)) {
  727. $this->response->body = simplexml_load_string($this->response->body);
  728. // Grab S3 errors
  729. if (!in_array($this->response->code, array(200, 204)) &&
  730. isset($this->response->body->Code, $this->response->body->Message)) {
  731. $this->response->error = array(
  732. 'code' => (string)$this->response->body->Code,
  733. 'message' => (string)$this->response->body->Message
  734. );
  735. if (isset($this->response->body->Resource))
  736. $this->response->error['resource'] = (string)$this->response->body->Resource;
  737. unset($this->response->body);
  738. }
  739. }
  740. // Clean up file resources
  741. if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
  742. return $this->response;
  743. }
  744. /**
  745. * CURL write callback
  746. *
  747. * @param resource &$curl CURL resource
  748. * @param string &$data Data
  749. * @return integer
  750. */
  751. private function __responseWriteCallback(&$curl, &$data) {
  752. if ($this->response->code == 200 && $this->fp !== false)
  753. return fwrite($this->fp, $data);
  754. else
  755. $this->response->body .= $data;
  756. return strlen($data);
  757. }
  758. /**
  759. * CURL header callback
  760. *
  761. * @param resource &$curl CURL resource
  762. * @param string &$data Data
  763. * @return integer
  764. */
  765. private function __responseHeaderCallback(&$curl, &$data) {
  766. if (($strlen = strlen($data)) <= 2) return $strlen;
  767. if (substr($data, 0, 4) == 'HTTP')
  768. $this->response->code = (int)substr($data, 9, 3);
  769. else {
  770. list($header, $value) = explode(': ', trim($data));
  771. if ($header == 'Last-Modified')
  772. $this->response->headers['time'] = strtotime($value);
  773. elseif ($header == 'Content-Length')
  774. $this->response->headers['size'] = (int)$value;
  775. elseif ($header == 'Content-Type')
  776. $this->response->headers['type'] = $value;
  777. elseif ($header == 'ETag')
  778. $this->response->headers['hash'] = substr($value, 1, -1);
  779. elseif (preg_match('/^x-amz-meta-.*$/', $header))
  780. $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value;
  781. }
  782. return $strlen;
  783. }
  784. }
  785. ?>