S3.php 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389
  1. <?php
  2. /**
  3. * $Id$
  4. *
  5. * Copyright (c) 2013, Donovan Schönknecht. All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. *
  10. * - Redistributions of source code must retain the above copyright notice,
  11. * this list of conditions and the following disclaimer.
  12. * - Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. * POSSIBILITY OF SUCH DAMAGE.
  27. *
  28. * Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
  29. */
  30. /**
  31. * Amazon S3 PHP class
  32. *
  33. * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
  34. * @version 0.5.1
  35. */
  36. class S3
  37. {
  38. // ACL flags
  39. const ACL_PRIVATE = 'private';
  40. const ACL_PUBLIC_READ = 'public-read';
  41. const ACL_PUBLIC_READ_WRITE = 'public-read-write';
  42. const ACL_AUTHENTICATED_READ = 'authenticated-read';
  43. const STORAGE_CLASS_STANDARD = 'STANDARD';
  44. const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
  45. const STORAGE_CLASS_STANDARD_IA = 'STANDARD_IA';
  46. const SSE_NONE = '';
  47. const SSE_AES256 = 'AES256';
  48. /**
  49. * The AWS Access key
  50. *
  51. * @var string
  52. * @access private
  53. * @static
  54. */
  55. private static $__accessKey = null;
  56. /**
  57. * AWS Secret Key
  58. *
  59. * @var string
  60. * @access private
  61. * @static
  62. */
  63. private static $__secretKey = null;
  64. /**
  65. * SSL Client key
  66. *
  67. * @var string
  68. * @access private
  69. * @static
  70. */
  71. private static $__sslKey = null;
  72. /**
  73. * Default delimiter to be used, for example while getBucket().
  74. * @var string
  75. * @access public
  76. * @static
  77. */
  78. public static $defDelimiter = null;
  79. /**
  80. * AWS URI
  81. *
  82. * @var string
  83. * @acess public
  84. * @static
  85. */
  86. public static $endpoint = 's3.amazonaws.com';
  87. /**
  88. * Proxy information
  89. *
  90. * @var null|array
  91. * @access public
  92. * @static
  93. */
  94. public static $proxy = null;
  95. /**
  96. * Connect using SSL?
  97. *
  98. * @var bool
  99. * @access public
  100. * @static
  101. */
  102. public static $useSSL = false;
  103. /**
  104. * Use SSL validation?
  105. *
  106. * @var bool
  107. * @access public
  108. * @static
  109. */
  110. public static $useSSLValidation = true;
  111. /**
  112. * Use SSL version
  113. *
  114. * @var const
  115. * @access public
  116. * @static
  117. */
  118. public static $useSSLVersion = CURL_SSLVERSION_TLSv1;
  119. /**
  120. * Use PHP exceptions?
  121. *
  122. * @var bool
  123. * @access public
  124. * @static
  125. */
  126. public static $useExceptions = false;
  127. /**
  128. * Time offset applied to time()
  129. * @access private
  130. * @static
  131. */
  132. private static $__timeOffset = 0;
  133. /**
  134. * SSL client key
  135. *
  136. * @var bool
  137. * @access public
  138. * @static
  139. */
  140. public static $sslKey = null;
  141. /**
  142. * SSL client certfificate
  143. *
  144. * @var string
  145. * @acess public
  146. * @static
  147. */
  148. public static $sslCert = null;
  149. /**
  150. * SSL CA cert (only required if you are having problems with your system CA cert)
  151. *
  152. * @var string
  153. * @access public
  154. * @static
  155. */
  156. public static $sslCACert = null;
  157. /**
  158. * AWS Key Pair ID
  159. *
  160. * @var string
  161. * @access private
  162. * @static
  163. */
  164. private static $__signingKeyPairId = null;
  165. /**
  166. * Key resource, freeSigningKey() must be called to clear it from memory
  167. *
  168. * @var bool
  169. * @access private
  170. * @static
  171. */
  172. private static $__signingKeyResource = false;
  173. /**
  174. * Constructor - if you're not using the class statically
  175. *
  176. * @param string $accessKey Access key
  177. * @param string $secretKey Secret key
  178. * @param boolean $useSSL Enable SSL
  179. * @param string $endpoint Amazon URI
  180. * @return void
  181. */
  182. public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com')
  183. {
  184. if ($accessKey !== null && $secretKey !== null)
  185. self::setAuth($accessKey, $secretKey);
  186. self::$useSSL = $useSSL;
  187. self::$endpoint = $endpoint;
  188. }
  189. /**
  190. * Set the service endpoint
  191. *
  192. * @param string $host Hostname
  193. * @return void
  194. */
  195. public function setEndpoint($host)
  196. {
  197. self::$endpoint = $host;
  198. }
  199. /**
  200. * Set AWS access key and secret key
  201. *
  202. * @param string $accessKey Access key
  203. * @param string $secretKey Secret key
  204. * @return void
  205. */
  206. public static function setAuth($accessKey, $secretKey)
  207. {
  208. self::$__accessKey = $accessKey;
  209. self::$__secretKey = $secretKey;
  210. }
  211. /**
  212. * Check if AWS keys have been set
  213. *
  214. * @return boolean
  215. */
  216. public static function hasAuth() {
  217. return (self::$__accessKey !== null && self::$__secretKey !== null);
  218. }
  219. /**
  220. * Set SSL on or off
  221. *
  222. * @param boolean $enabled SSL enabled
  223. * @param boolean $validate SSL certificate validation
  224. * @return void
  225. */
  226. public static function setSSL($enabled, $validate = true)
  227. {
  228. self::$useSSL = $enabled;
  229. self::$useSSLValidation = $validate;
  230. }
  231. /**
  232. * Set SSL client certificates (experimental)
  233. *
  234. * @param string $sslCert SSL client certificate
  235. * @param string $sslKey SSL client key
  236. * @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
  237. * @return void
  238. */
  239. public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null)
  240. {
  241. self::$sslCert = $sslCert;
  242. self::$sslKey = $sslKey;
  243. self::$sslCACert = $sslCACert;
  244. }
  245. /**
  246. * Set proxy information
  247. *
  248. * @param string $host Proxy hostname and port (localhost:1234)
  249. * @param string $user Proxy username
  250. * @param string $pass Proxy password
  251. * @param constant $type CURL proxy type
  252. * @return void
  253. */
  254. public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
  255. {
  256. self::$proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass);
  257. }
  258. /**
  259. * Set the error mode to exceptions
  260. *
  261. * @param boolean $enabled Enable exceptions
  262. * @return void
  263. */
  264. public static function setExceptions($enabled = true)
  265. {
  266. self::$useExceptions = $enabled;
  267. }
  268. /**
  269. * Set AWS time correction offset (use carefully)
  270. *
  271. * This can be used when an inaccurate system time is generating
  272. * invalid request signatures. It should only be used as a last
  273. * resort when the system time cannot be changed.
  274. *
  275. * @param string $offset Time offset (set to zero to use AWS server time)
  276. * @return void
  277. */
  278. public static function setTimeCorrectionOffset($offset = 0)
  279. {
  280. if ($offset == 0)
  281. {
  282. $rest = new S3Request('HEAD');
  283. $rest = $rest->getResponse();
  284. $awstime = $rest->headers['date'];
  285. $systime = time();
  286. $offset = $systime > $awstime ? -($systime - $awstime) : ($awstime - $systime);
  287. }
  288. self::$__timeOffset = $offset;
  289. }
  290. /**
  291. * Set signing key
  292. *
  293. * @param string $keyPairId AWS Key Pair ID
  294. * @param string $signingKey Private Key
  295. * @param boolean $isFile Load private key from file, set to false to load string
  296. * @return boolean
  297. */
  298. public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
  299. {
  300. self::$__signingKeyPairId = $keyPairId;
  301. if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
  302. file_get_contents($signingKey) : $signingKey)) !== false) return true;
  303. self::__triggerError('S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
  304. return false;
  305. }
  306. /**
  307. * Free signing key from memory, MUST be called if you are using setSigningKey()
  308. *
  309. * @return void
  310. */
  311. public static function freeSigningKey()
  312. {
  313. if (self::$__signingKeyResource !== false)
  314. openssl_free_key(self::$__signingKeyResource);
  315. }
  316. /**
  317. * Internal error handler
  318. *
  319. * @internal Internal error handler
  320. * @param string $message Error message
  321. * @param string $file Filename
  322. * @param integer $line Line number
  323. * @param integer $code Error code
  324. * @return void
  325. */
  326. private static function __triggerError($message, $file, $line, $code = 0)
  327. {
  328. if (self::$useExceptions)
  329. throw new S3Exception($message, $file, $line, $code);
  330. else
  331. trigger_error($message, E_USER_WARNING);
  332. }
  333. /**
  334. * Get a list of buckets
  335. *
  336. * @param boolean $detailed Returns detailed bucket list when true
  337. * @return array | false
  338. */
  339. public static function listBuckets($detailed = false)
  340. {
  341. $rest = new S3Request('GET', '', '', self::$endpoint);
  342. $rest = $rest->getResponse();
  343. if ($rest->error === false && $rest->code !== 200)
  344. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  345. if ($rest->error !== false)
  346. {
  347. self::__triggerError(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'],
  348. $rest->error['message']), __FILE__, __LINE__);
  349. return false;
  350. }
  351. $results = array();
  352. if (!isset($rest->body->Buckets)) return $results;
  353. if ($detailed)
  354. {
  355. if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
  356. $results['owner'] = array(
  357. 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
  358. );
  359. $results['buckets'] = array();
  360. foreach ($rest->body->Buckets->Bucket as $b)
  361. $results['buckets'][] = array(
  362. 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
  363. );
  364. } else
  365. foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
  366. return $results;
  367. }
  368. /**
  369. * Get contents for a bucket
  370. *
  371. * If maxKeys is null this method will loop through truncated result sets
  372. *
  373. * @param string $bucket Bucket name
  374. * @param string $prefix Prefix
  375. * @param string $marker Marker (last file listed)
  376. * @param string $maxKeys Max keys (maximum number of keys to return)
  377. * @param string $delimiter Delimiter
  378. * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
  379. * @return array | false
  380. */
  381. public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
  382. {
  383. $rest = new S3Request('GET', $bucket, '', self::$endpoint);
  384. if ($maxKeys == 0) $maxKeys = null;
  385. if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
  386. if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
  387. if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
  388. if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
  389. else if (!empty(self::$defDelimiter)) $rest->setParameter('delimiter', self::$defDelimiter);
  390. $response = $rest->getResponse();
  391. if ($response->error === false && $response->code !== 200)
  392. $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
  393. if ($response->error !== false)
  394. {
  395. self::__triggerError(sprintf("S3::getBucket(): [%s] %s",
  396. $response->error['code'], $response->error['message']), __FILE__, __LINE__);
  397. return false;
  398. }
  399. $results = array();
  400. $nextMarker = null;
  401. if (isset($response->body, $response->body->Contents))
  402. foreach ($response->body->Contents as $c)
  403. {
  404. $results[(string)$c->Key] = array(
  405. 'name' => (string)$c->Key,
  406. 'time' => strtotime((string)$c->LastModified),
  407. 'size' => (int)$c->Size,
  408. 'hash' => substr((string)$c->ETag, 1, -1)
  409. );
  410. $nextMarker = (string)$c->Key;
  411. }
  412. if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
  413. foreach ($response->body->CommonPrefixes as $c)
  414. $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
  415. if (isset($response->body, $response->body->IsTruncated) &&
  416. (string)$response->body->IsTruncated == 'false') return $results;
  417. if (isset($response->body, $response->body->NextMarker))
  418. $nextMarker = (string)$response->body->NextMarker;
  419. // Loop through truncated results if maxKeys isn't specified
  420. if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
  421. do
  422. {
  423. $rest = new S3Request('GET', $bucket, '', self::$endpoint);
  424. if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
  425. $rest->setParameter('marker', $nextMarker);
  426. if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
  427. if (($response = $rest->getResponse()) == false || $response->code !== 200) break;
  428. if (isset($response->body, $response->body->Contents))
  429. foreach ($response->body->Contents as $c)
  430. {
  431. $results[(string)$c->Key] = array(
  432. 'name' => (string)$c->Key,
  433. 'time' => strtotime((string)$c->LastModified),
  434. 'size' => (int)$c->Size,
  435. 'hash' => substr((string)$c->ETag, 1, -1)
  436. );
  437. $nextMarker = (string)$c->Key;
  438. }
  439. if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
  440. foreach ($response->body->CommonPrefixes as $c)
  441. $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
  442. if (isset($response->body, $response->body->NextMarker))
  443. $nextMarker = (string)$response->body->NextMarker;
  444. } while ($response !== false && (string)$response->body->IsTruncated == 'true');
  445. return $results;
  446. }
  447. /**
  448. * Put a bucket
  449. *
  450. * @param string $bucket Bucket name
  451. * @param constant $acl ACL flag
  452. * @param string $location Set as "EU" to create buckets hosted in Europe
  453. * @return boolean
  454. */
  455. public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
  456. {
  457. $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
  458. $rest->setAmzHeader('x-amz-acl', $acl);
  459. if ($location !== false)
  460. {
  461. $dom = new DOMDocument;
  462. $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
  463. $locationConstraint = $dom->createElement('LocationConstraint', $location);
  464. $createBucketConfiguration->appendChild($locationConstraint);
  465. $dom->appendChild($createBucketConfiguration);
  466. $rest->data = $dom->saveXML();
  467. $rest->size = strlen($rest->data);
  468. $rest->setHeader('Content-Type', 'application/xml');
  469. }
  470. $rest = $rest->getResponse();
  471. if ($rest->error === false && $rest->code !== 200)
  472. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  473. if ($rest->error !== false)
  474. {
  475. self::__triggerError(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
  476. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  477. return false;
  478. }
  479. return true;
  480. }
  481. /**
  482. * Delete an empty bucket
  483. *
  484. * @param string $bucket Bucket name
  485. * @return boolean
  486. */
  487. public static function deleteBucket($bucket)
  488. {
  489. $rest = new S3Request('DELETE', $bucket, '', self::$endpoint);
  490. $rest = $rest->getResponse();
  491. if ($rest->error === false && $rest->code !== 204)
  492. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  493. if ($rest->error !== false)
  494. {
  495. self::__triggerError(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
  496. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  497. return false;
  498. }
  499. return true;
  500. }
  501. /**
  502. * Create input info array for putObject()
  503. *
  504. * @param string $file Input file
  505. * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
  506. * @return array | false
  507. */
  508. public static function inputFile($file, $md5sum = true)
  509. {
  510. if (!file_exists($file) || !is_file($file) || !is_readable($file))
  511. {
  512. self::__triggerError('S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
  513. return false;
  514. }
  515. clearstatcache(false, $file);
  516. return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
  517. (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
  518. }
  519. /**
  520. * Create input array info for putObject() with a resource
  521. *
  522. * @param string $resource Input resource to read from
  523. * @param integer $bufferSize Input byte size
  524. * @param string $md5sum MD5 hash to send (optional)
  525. * @return array | false
  526. */
  527. public static function inputResource(&$resource, $bufferSize = false, $md5sum = '')
  528. {
  529. if (!is_resource($resource) || (int)$bufferSize < 0)
  530. {
  531. self::__triggerError('S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
  532. return false;
  533. }
  534. // Try to figure out the bytesize
  535. if ($bufferSize === false)
  536. {
  537. if (fseek($resource, 0, SEEK_END) < 0 || ($bufferSize = ftell($resource)) === false)
  538. {
  539. self::__triggerError('S3::inputResource(): Unable to obtain resource size', __FILE__, __LINE__);
  540. return false;
  541. }
  542. fseek($resource, 0);
  543. }
  544. $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
  545. $input['fp'] =& $resource;
  546. return $input;
  547. }
  548. /**
  549. * Put an object
  550. *
  551. * @param mixed $input Input data
  552. * @param string $bucket Bucket name
  553. * @param string $uri Object URI
  554. * @param constant $acl ACL constant
  555. * @param array $metaHeaders Array of x-amz-meta-* headers
  556. * @param array $requestHeaders Array of request headers or content type as a string
  557. * @param constant $storageClass Storage class constant
  558. * @param constant $serverSideEncryption Server-side encryption
  559. * @return boolean
  560. */
  561. public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE)
  562. {
  563. if ($input === false) return false;
  564. $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
  565. if (!is_array($input)) $input = array(
  566. 'data' => $input, 'size' => strlen($input),
  567. 'md5sum' => base64_encode(md5($input, true))
  568. );
  569. // Data
  570. if (isset($input['fp']))
  571. $rest->fp =& $input['fp'];
  572. elseif (isset($input['file']))
  573. $rest->fp = @fopen($input['file'], 'rb');
  574. elseif (isset($input['data']))
  575. $rest->data = $input['data'];
  576. // Content-Length (required)
  577. if (isset($input['size']) && $input['size'] >= 0)
  578. $rest->size = $input['size'];
  579. else {
  580. if (isset($input['file'])) {
  581. clearstatcache(false, $input['file']);
  582. $rest->size = filesize($input['file']);
  583. }
  584. elseif (isset($input['data']))
  585. $rest->size = strlen($input['data']);
  586. }
  587. // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
  588. if (is_array($requestHeaders))
  589. foreach ($requestHeaders as $h => $v)
  590. strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v);
  591. elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
  592. $input['type'] = $requestHeaders;
  593. // Content-Type
  594. if (!isset($input['type']))
  595. {
  596. if (isset($requestHeaders['Content-Type']))
  597. $input['type'] =& $requestHeaders['Content-Type'];
  598. elseif (isset($input['file']))
  599. $input['type'] = self::__getMIMEType($input['file']);
  600. else
  601. $input['type'] = 'application/octet-stream';
  602. }
  603. if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
  604. $rest->setAmzHeader('x-amz-storage-class', $storageClass);
  605. if ($serverSideEncryption !== self::SSE_NONE) // Server-side encryption
  606. $rest->setAmzHeader('x-amz-server-side-encryption', $serverSideEncryption);
  607. // We need to post with Content-Length and Content-Type, MD5 is optional
  608. if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
  609. {
  610. $rest->setHeader('Content-Type', $input['type']);
  611. if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
  612. $rest->setAmzHeader('x-amz-acl', $acl);
  613. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  614. $rest->getResponse();
  615. } else
  616. $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
  617. if ($rest->response->error === false && $rest->response->code !== 200)
  618. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  619. if ($rest->response->error !== false)
  620. {
  621. self::__triggerError(sprintf("S3::putObject(): [%s] %s",
  622. $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
  623. return false;
  624. }
  625. return true;
  626. }
  627. /**
  628. * Put an object from a file (legacy function)
  629. *
  630. * @param string $file Input file path
  631. * @param string $bucket Bucket name
  632. * @param string $uri Object URI
  633. * @param constant $acl ACL constant
  634. * @param array $metaHeaders Array of x-amz-meta-* headers
  635. * @param string $contentType Content type
  636. * @return boolean
  637. */
  638. public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
  639. {
  640. return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
  641. }
  642. /**
  643. * Put an object from a string (legacy function)
  644. *
  645. * @param string $string Input data
  646. * @param string $bucket Bucket name
  647. * @param string $uri Object URI
  648. * @param constant $acl ACL constant
  649. * @param array $metaHeaders Array of x-amz-meta-* headers
  650. * @param string $contentType Content type
  651. * @return boolean
  652. */
  653. public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
  654. {
  655. return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
  656. }
  657. /**
  658. * Get an object
  659. *
  660. * @param string $bucket Bucket name
  661. * @param string $uri Object URI
  662. * @param mixed $saveTo Filename or resource to write to
  663. * @return mixed
  664. */
  665. public static function getObject($bucket, $uri, $saveTo = false)
  666. {
  667. $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
  668. if ($saveTo !== false)
  669. {
  670. if (is_resource($saveTo))
  671. $rest->fp =& $saveTo;
  672. else
  673. if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
  674. $rest->file = realpath($saveTo);
  675. else
  676. $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
  677. }
  678. if ($rest->response->error === false) $rest->getResponse();
  679. if ($rest->response->error === false && $rest->response->code !== 200)
  680. $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
  681. if ($rest->response->error !== false)
  682. {
  683. self::__triggerError(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
  684. $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
  685. return false;
  686. }
  687. return $rest->response;
  688. }
  689. /**
  690. * Get object information
  691. *
  692. * @param string $bucket Bucket name
  693. * @param string $uri Object URI
  694. * @param boolean $returnInfo Return response information
  695. * @return mixed | false
  696. */
  697. public static function getObjectInfo($bucket, $uri, $returnInfo = true)
  698. {
  699. $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint);
  700. $rest = $rest->getResponse();
  701. if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
  702. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  703. if ($rest->error !== false)
  704. {
  705. self::__triggerError(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
  706. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  707. return false;
  708. }
  709. return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
  710. }
  711. /**
  712. * Copy an object
  713. *
  714. * @param string $srcBucket Source bucket name
  715. * @param string $srcUri Source object URI
  716. * @param string $bucket Destination bucket name
  717. * @param string $uri Destination object URI
  718. * @param constant $acl ACL constant
  719. * @param array $metaHeaders Optional array of x-amz-meta-* headers
  720. * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
  721. * @param constant $storageClass Storage class constant
  722. * @return mixed | false
  723. */
  724. public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
  725. {
  726. $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
  727. $rest->setHeader('Content-Length', 0);
  728. foreach ($requestHeaders as $h => $v)
  729. strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v);
  730. foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
  731. if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
  732. $rest->setAmzHeader('x-amz-storage-class', $storageClass);
  733. $rest->setAmzHeader('x-amz-acl', $acl);
  734. $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
  735. if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
  736. $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
  737. $rest = $rest->getResponse();
  738. if ($rest->error === false && $rest->code !== 200)
  739. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  740. if ($rest->error !== false)
  741. {
  742. self::__triggerError(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
  743. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  744. return false;
  745. }
  746. return isset($rest->body->LastModified, $rest->body->ETag) ? array(
  747. 'time' => strtotime((string)$rest->body->LastModified),
  748. 'hash' => substr((string)$rest->body->ETag, 1, -1)
  749. ) : false;
  750. }
  751. /**
  752. * Set up a bucket redirection
  753. *
  754. * @param string $bucket Bucket name
  755. * @param string $location Target host name
  756. * @return boolean
  757. */
  758. public static function setBucketRedirect($bucket = NULL, $location = NULL)
  759. {
  760. $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
  761. if( empty($bucket) || empty($location) ) {
  762. self::__triggerError("S3::setBucketRedirect({$bucket}, {$location}): Empty parameter.", __FILE__, __LINE__);
  763. return false;
  764. }
  765. $dom = new DOMDocument;
  766. $websiteConfiguration = $dom->createElement('WebsiteConfiguration');
  767. $redirectAllRequestsTo = $dom->createElement('RedirectAllRequestsTo');
  768. $hostName = $dom->createElement('HostName', $location);
  769. $redirectAllRequestsTo->appendChild($hostName);
  770. $websiteConfiguration->appendChild($redirectAllRequestsTo);
  771. $dom->appendChild($websiteConfiguration);
  772. $rest->setParameter('website', null);
  773. $rest->data = $dom->saveXML();
  774. $rest->size = strlen($rest->data);
  775. $rest->setHeader('Content-Type', 'application/xml');
  776. $rest = $rest->getResponse();
  777. if ($rest->error === false && $rest->code !== 200)
  778. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  779. if ($rest->error !== false)
  780. {
  781. self::__triggerError(sprintf("S3::setBucketRedirect({$bucket}, {$location}): [%s] %s",
  782. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  783. return false;
  784. }
  785. return true;
  786. }
  787. /**
  788. * Set logging for a bucket
  789. *
  790. * @param string $bucket Bucket name
  791. * @param string $targetBucket Target bucket (where logs are stored)
  792. * @param string $targetPrefix Log prefix (e,g; domain.com-)
  793. * @return boolean
  794. */
  795. public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
  796. {
  797. // The S3 log delivery group has to be added to the target bucket's ACP
  798. if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
  799. {
  800. // Only add permissions to the target bucket when they do not exist
  801. $aclWriteSet = false;
  802. $aclReadSet = false;
  803. foreach ($acp['acl'] as $acl)
  804. if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
  805. {
  806. if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
  807. elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
  808. }
  809. if (!$aclWriteSet) $acp['acl'][] = array(
  810. 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
  811. );
  812. if (!$aclReadSet) $acp['acl'][] = array(
  813. 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
  814. );
  815. if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
  816. }
  817. $dom = new DOMDocument;
  818. $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
  819. $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
  820. if ($targetBucket !== null)
  821. {
  822. if ($targetPrefix == null) $targetPrefix = $bucket . '-';
  823. $loggingEnabled = $dom->createElement('LoggingEnabled');
  824. $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
  825. $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
  826. // TODO: Add TargetGrants?
  827. $bucketLoggingStatus->appendChild($loggingEnabled);
  828. }
  829. $dom->appendChild($bucketLoggingStatus);
  830. $rest = new S3Request('PUT', $bucket, '', self::$endpoint);
  831. $rest->setParameter('logging', null);
  832. $rest->data = $dom->saveXML();
  833. $rest->size = strlen($rest->data);
  834. $rest->setHeader('Content-Type', 'application/xml');
  835. $rest = $rest->getResponse();
  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. {
  840. self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
  841. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  842. return false;
  843. }
  844. return true;
  845. }
  846. /**
  847. * Get logging status for a bucket
  848. *
  849. * This will return false if logging is not enabled.
  850. * Note: To enable logging, you also need to grant write access to the log group
  851. *
  852. * @param string $bucket Bucket name
  853. * @return array | false
  854. */
  855. public static function getBucketLogging($bucket)
  856. {
  857. $rest = new S3Request('GET', $bucket, '', self::$endpoint);
  858. $rest->setParameter('logging', null);
  859. $rest = $rest->getResponse();
  860. if ($rest->error === false && $rest->code !== 200)
  861. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  862. if ($rest->error !== false)
  863. {
  864. self::__triggerError(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
  865. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  866. return false;
  867. }
  868. if (!isset($rest->body->LoggingEnabled)) return false; // No logging
  869. return array(
  870. 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
  871. 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
  872. );
  873. }
  874. /**
  875. * Disable bucket logging
  876. *
  877. * @param string $bucket Bucket name
  878. * @return boolean
  879. */
  880. public static function disableBucketLogging($bucket)
  881. {
  882. return self::setBucketLogging($bucket, null);
  883. }
  884. /**
  885. * Get a bucket's location
  886. *
  887. * @param string $bucket Bucket name
  888. * @return string | false
  889. */
  890. public static function getBucketLocation($bucket)
  891. {
  892. $rest = new S3Request('GET', $bucket, '', self::$endpoint);
  893. $rest->setParameter('location', null);
  894. $rest = $rest->getResponse();
  895. if ($rest->error === false && $rest->code !== 200)
  896. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  897. if ($rest->error !== false)
  898. {
  899. self::__triggerError(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
  900. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  901. return false;
  902. }
  903. return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
  904. }
  905. /**
  906. * Set object or bucket Access Control Policy
  907. *
  908. * @param string $bucket Bucket name
  909. * @param string $uri Object URI
  910. * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
  911. * @return boolean
  912. */
  913. public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
  914. {
  915. $dom = new DOMDocument;
  916. $dom->formatOutput = true;
  917. $accessControlPolicy = $dom->createElement('AccessControlPolicy');
  918. $accessControlList = $dom->createElement('AccessControlList');
  919. // It seems the owner has to be passed along too
  920. $owner = $dom->createElement('Owner');
  921. $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
  922. $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
  923. $accessControlPolicy->appendChild($owner);
  924. foreach ($acp['acl'] as $g)
  925. {
  926. $grant = $dom->createElement('Grant');
  927. $grantee = $dom->createElement('Grantee');
  928. $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
  929. if (isset($g['id']))
  930. { // CanonicalUser (DisplayName is omitted)
  931. $grantee->setAttribute('xsi:type', 'CanonicalUser');
  932. $grantee->appendChild($dom->createElement('ID', $g['id']));
  933. }
  934. elseif (isset($g['email']))
  935. { // AmazonCustomerByEmail
  936. $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
  937. $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
  938. }
  939. elseif ($g['type'] == 'Group')
  940. { // Group
  941. $grantee->setAttribute('xsi:type', 'Group');
  942. $grantee->appendChild($dom->createElement('URI', $g['uri']));
  943. }
  944. $grant->appendChild($grantee);
  945. $grant->appendChild($dom->createElement('Permission', $g['permission']));
  946. $accessControlList->appendChild($grant);
  947. }
  948. $accessControlPolicy->appendChild($accessControlList);
  949. $dom->appendChild($accessControlPolicy);
  950. $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
  951. $rest->setParameter('acl', null);
  952. $rest->data = $dom->saveXML();
  953. $rest->size = strlen($rest->data);
  954. $rest->setHeader('Content-Type', 'application/xml');
  955. $rest = $rest->getResponse();
  956. if ($rest->error === false && $rest->code !== 200)
  957. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  958. if ($rest->error !== false)
  959. {
  960. self::__triggerError(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
  961. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  962. return false;
  963. }
  964. return true;
  965. }
  966. /**
  967. * Get object or bucket Access Control Policy
  968. *
  969. * @param string $bucket Bucket name
  970. * @param string $uri Object URI
  971. * @return mixed | false
  972. */
  973. public static function getAccessControlPolicy($bucket, $uri = '')
  974. {
  975. $rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
  976. $rest->setParameter('acl', null);
  977. $rest = $rest->getResponse();
  978. if ($rest->error === false && $rest->code !== 200)
  979. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  980. if ($rest->error !== false)
  981. {
  982. self::__triggerError(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
  983. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  984. return false;
  985. }
  986. $acp = array();
  987. if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
  988. $acp['owner'] = array(
  989. 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
  990. );
  991. if (isset($rest->body->AccessControlList))
  992. {
  993. $acp['acl'] = array();
  994. foreach ($rest->body->AccessControlList->Grant as $grant)
  995. {
  996. foreach ($grant->Grantee as $grantee)
  997. {
  998. if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
  999. $acp['acl'][] = array(
  1000. 'type' => 'CanonicalUser',
  1001. 'id' => (string)$grantee->ID,
  1002. 'name' => (string)$grantee->DisplayName,
  1003. 'permission' => (string)$grant->Permission
  1004. );
  1005. elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
  1006. $acp['acl'][] = array(
  1007. 'type' => 'AmazonCustomerByEmail',
  1008. 'email' => (string)$grantee->EmailAddress,
  1009. 'permission' => (string)$grant->Permission
  1010. );
  1011. elseif (isset($grantee->URI)) // Group
  1012. $acp['acl'][] = array(
  1013. 'type' => 'Group',
  1014. 'uri' => (string)$grantee->URI,
  1015. 'permission' => (string)$grant->Permission
  1016. );
  1017. else continue;
  1018. }
  1019. }
  1020. }
  1021. return $acp;
  1022. }
  1023. /**
  1024. * Delete an object
  1025. *
  1026. * @param string $bucket Bucket name
  1027. * @param string $uri Object URI
  1028. * @return boolean
  1029. */
  1030. public static function deleteObject($bucket, $uri)
  1031. {
  1032. $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
  1033. $rest = $rest->getResponse();
  1034. if ($rest->error === false && $rest->code !== 204)
  1035. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1036. if ($rest->error !== false)
  1037. {
  1038. self::__triggerError(sprintf("S3::deleteObject(): [%s] %s",
  1039. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1040. return false;
  1041. }
  1042. return true;
  1043. }
  1044. /**
  1045. * Get a query string authenticated URL
  1046. *
  1047. * @param string $bucket Bucket name
  1048. * @param string $uri Object URI
  1049. * @param integer $lifetime Lifetime in seconds
  1050. * @param boolean $hostBucket Use the bucket name as the hostname
  1051. * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
  1052. * @return string
  1053. */
  1054. public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
  1055. {
  1056. $expires = self::__getTime() + $lifetime;
  1057. $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
  1058. return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
  1059. // $hostBucket ? $bucket : self::$endpoint.'/'.$bucket, $uri, self::$__accessKey, $expires,
  1060. $hostBucket ? $bucket : $bucket . '.' . self::$endpoint, $uri, self::$__accessKey, $expires,
  1061. urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
  1062. }
  1063. /**
  1064. * Get a CloudFront signed policy URL
  1065. *
  1066. * @param array $policy Policy
  1067. * @return string
  1068. */
  1069. public static function getSignedPolicyURL($policy)
  1070. {
  1071. $data = json_encode($policy);
  1072. $signature = '';
  1073. if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
  1074. $encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
  1075. $signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
  1076. $url = $policy['Statement'][0]['Resource'] . '?';
  1077. foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
  1078. $url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
  1079. return substr($url, 0, -1);
  1080. }
  1081. /**
  1082. * Get a CloudFront canned policy URL
  1083. *
  1084. * @param string $url URL to sign
  1085. * @param integer $lifetime URL lifetime
  1086. * @return string
  1087. */
  1088. public static function getSignedCannedURL($url, $lifetime)
  1089. {
  1090. return self::getSignedPolicyURL(array(
  1091. 'Statement' => array(
  1092. array('Resource' => $url, 'Condition' => array(
  1093. 'DateLessThan' => array('AWS:EpochTime' => self::__getTime() + $lifetime)
  1094. ))
  1095. )
  1096. ));
  1097. }
  1098. /**
  1099. * Get upload POST parameters for form uploads
  1100. *
  1101. * @param string $bucket Bucket name
  1102. * @param string $uriPrefix Object URI prefix
  1103. * @param constant $acl ACL constant
  1104. * @param integer $lifetime Lifetime in seconds
  1105. * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
  1106. * @param string $successRedirect Redirect URL or 200 / 201 status code
  1107. * @param array $amzHeaders Array of x-amz-meta-* headers
  1108. * @param array $headers Array of request headers or content type as a string
  1109. * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
  1110. * @return object
  1111. */
  1112. public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
  1113. $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
  1114. {
  1115. // Create policy object
  1116. $policy = new stdClass;
  1117. $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (self::__getTime() + $lifetime));
  1118. $policy->conditions = array();
  1119. $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
  1120. $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
  1121. $obj = new stdClass; // 200 for non-redirect uploads
  1122. if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
  1123. $obj->success_action_status = (string)$successRedirect;
  1124. else // URL
  1125. $obj->success_action_redirect = $successRedirect;
  1126. array_push($policy->conditions, $obj);
  1127. if ($acl !== self::ACL_PUBLIC_READ)
  1128. array_push($policy->conditions, array('eq', '$acl', $acl));
  1129. array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
  1130. if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
  1131. foreach (array_keys($headers) as $headerKey)
  1132. array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
  1133. foreach ($amzHeaders as $headerKey => $headerVal)
  1134. {
  1135. $obj = new stdClass;
  1136. $obj->{$headerKey} = (string)$headerVal;
  1137. array_push($policy->conditions, $obj);
  1138. }
  1139. array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
  1140. $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
  1141. // Create parameters
  1142. $params = new stdClass;
  1143. $params->AWSAccessKeyId = self::$__accessKey;
  1144. $params->key = $uriPrefix.'${filename}';
  1145. $params->acl = $acl;
  1146. $params->policy = $policy; unset($policy);
  1147. $params->signature = self::__getHash($params->policy);
  1148. if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
  1149. $params->success_action_status = (string)$successRedirect;
  1150. else
  1151. $params->success_action_redirect = $successRedirect;
  1152. foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
  1153. foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
  1154. return $params;
  1155. }
  1156. /**
  1157. * Create a CloudFront distribution
  1158. *
  1159. * @param string $bucket Bucket name
  1160. * @param boolean $enabled Enabled (true/false)
  1161. * @param array $cnames Array containing CNAME aliases
  1162. * @param string $comment Use the bucket name as the hostname
  1163. * @param string $defaultRootObject Default root object
  1164. * @param string $originAccessIdentity Origin access identity
  1165. * @param array $trustedSigners Array of trusted signers
  1166. * @return array | false
  1167. */
  1168. public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
  1169. {
  1170. if (!extension_loaded('openssl'))
  1171. {
  1172. self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
  1173. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1174. return false;
  1175. }
  1176. $useSSL = self::$useSSL;
  1177. self::$useSSL = true; // CloudFront requires SSL
  1178. $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
  1179. $rest->data = self::__getCloudFrontDistributionConfigXML(
  1180. $bucket.'.s3.amazonaws.com',
  1181. $enabled,
  1182. (string)$comment,
  1183. (string)microtime(true),
  1184. $cnames,
  1185. $defaultRootObject,
  1186. $originAccessIdentity,
  1187. $trustedSigners
  1188. );
  1189. $rest->size = strlen($rest->data);
  1190. $rest->setHeader('Content-Type', 'application/xml');
  1191. $rest = self::__getCloudFrontResponse($rest);
  1192. self::$useSSL = $useSSL;
  1193. if ($rest->error === false && $rest->code !== 201)
  1194. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1195. if ($rest->error !== false)
  1196. {
  1197. self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
  1198. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1199. return false;
  1200. } elseif ($rest->body instanceof SimpleXMLElement)
  1201. return self::__parseCloudFrontDistributionConfig($rest->body);
  1202. return false;
  1203. }
  1204. /**
  1205. * Get CloudFront distribution info
  1206. *
  1207. * @param string $distributionId Distribution ID from listDistributions()
  1208. * @return array | false
  1209. */
  1210. public static function getDistribution($distributionId)
  1211. {
  1212. if (!extension_loaded('openssl'))
  1213. {
  1214. self::__triggerError(sprintf("S3::getDistribution($distributionId): %s",
  1215. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1216. return false;
  1217. }
  1218. $useSSL = self::$useSSL;
  1219. self::$useSSL = true; // CloudFront requires SSL
  1220. $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
  1221. $rest = self::__getCloudFrontResponse($rest);
  1222. self::$useSSL = $useSSL;
  1223. if ($rest->error === false && $rest->code !== 200)
  1224. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1225. if ($rest->error !== false)
  1226. {
  1227. self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s",
  1228. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1229. return false;
  1230. }
  1231. elseif ($rest->body instanceof SimpleXMLElement)
  1232. {
  1233. $dist = self::__parseCloudFrontDistributionConfig($rest->body);
  1234. $dist['hash'] = $rest->headers['hash'];
  1235. $dist['id'] = $distributionId;
  1236. return $dist;
  1237. }
  1238. return false;
  1239. }
  1240. /**
  1241. * Update a CloudFront distribution
  1242. *
  1243. * @param array $dist Distribution array info identical to output of getDistribution()
  1244. * @return array | false
  1245. */
  1246. public static function updateDistribution($dist)
  1247. {
  1248. if (!extension_loaded('openssl'))
  1249. {
  1250. self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s",
  1251. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1252. return false;
  1253. }
  1254. $useSSL = self::$useSSL;
  1255. self::$useSSL = true; // CloudFront requires SSL
  1256. $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
  1257. $rest->data = self::__getCloudFrontDistributionConfigXML(
  1258. $dist['origin'],
  1259. $dist['enabled'],
  1260. $dist['comment'],
  1261. $dist['callerReference'],
  1262. $dist['cnames'],
  1263. $dist['defaultRootObject'],
  1264. $dist['originAccessIdentity'],
  1265. $dist['trustedSigners']
  1266. );
  1267. $rest->size = strlen($rest->data);
  1268. $rest->setHeader('If-Match', $dist['hash']);
  1269. $rest = self::__getCloudFrontResponse($rest);
  1270. self::$useSSL = $useSSL;
  1271. if ($rest->error === false && $rest->code !== 200)
  1272. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1273. if ($rest->error !== false)
  1274. {
  1275. self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s",
  1276. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1277. return false;
  1278. } else {
  1279. $dist = self::__parseCloudFrontDistributionConfig($rest->body);
  1280. $dist['hash'] = $rest->headers['hash'];
  1281. return $dist;
  1282. }
  1283. return false;
  1284. }
  1285. /**
  1286. * Delete a CloudFront distribution
  1287. *
  1288. * @param array $dist Distribution array info identical to output of getDistribution()
  1289. * @return boolean
  1290. */
  1291. public static function deleteDistribution($dist)
  1292. {
  1293. if (!extension_loaded('openssl'))
  1294. {
  1295. self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s",
  1296. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1297. return false;
  1298. }
  1299. $useSSL = self::$useSSL;
  1300. self::$useSSL = true; // CloudFront requires SSL
  1301. $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
  1302. $rest->setHeader('If-Match', $dist['hash']);
  1303. $rest = self::__getCloudFrontResponse($rest);
  1304. self::$useSSL = $useSSL;
  1305. if ($rest->error === false && $rest->code !== 204)
  1306. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1307. if ($rest->error !== false)
  1308. {
  1309. self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
  1310. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1311. return false;
  1312. }
  1313. return true;
  1314. }
  1315. /**
  1316. * Get a list of CloudFront distributions
  1317. *
  1318. * @return array
  1319. */
  1320. public static function listDistributions()
  1321. {
  1322. if (!extension_loaded('openssl'))
  1323. {
  1324. self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
  1325. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1326. return false;
  1327. }
  1328. $useSSL = self::$useSSL;
  1329. self::$useSSL = true; // CloudFront requires SSL
  1330. $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
  1331. $rest = self::__getCloudFrontResponse($rest);
  1332. self::$useSSL = $useSSL;
  1333. if ($rest->error === false && $rest->code !== 200)
  1334. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1335. if ($rest->error !== false)
  1336. {
  1337. self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
  1338. $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
  1339. return false;
  1340. }
  1341. elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
  1342. {
  1343. $list = array();
  1344. if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
  1345. {
  1346. //$info['marker'] = (string)$rest->body->Marker;
  1347. //$info['maxItems'] = (int)$rest->body->MaxItems;
  1348. //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
  1349. }
  1350. foreach ($rest->body->DistributionSummary as $summary)
  1351. $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
  1352. return $list;
  1353. }
  1354. return array();
  1355. }
  1356. /**
  1357. * List CloudFront Origin Access Identities
  1358. *
  1359. * @return array
  1360. */
  1361. public static function listOriginAccessIdentities()
  1362. {
  1363. if (!extension_loaded('openssl'))
  1364. {
  1365. self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
  1366. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1367. return false;
  1368. }
  1369. self::$useSSL = true; // CloudFront requires SSL
  1370. $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
  1371. $rest = self::__getCloudFrontResponse($rest);
  1372. $useSSL = self::$useSSL;
  1373. if ($rest->error === false && $rest->code !== 200)
  1374. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1375. if ($rest->error !== false)
  1376. {
  1377. trigger_error(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
  1378. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  1379. return false;
  1380. }
  1381. if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
  1382. {
  1383. $identities = array();
  1384. foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
  1385. if (isset($identity->S3CanonicalUserId))
  1386. $identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
  1387. return $identities;
  1388. }
  1389. return false;
  1390. }
  1391. /**
  1392. * Invalidate objects in a CloudFront distribution
  1393. *
  1394. * Thanks to Martin Lindkvist for S3::invalidateDistribution()
  1395. *
  1396. * @param string $distributionId Distribution ID from listDistributions()
  1397. * @param array $paths Array of object paths to invalidate
  1398. * @return boolean
  1399. */
  1400. public static function invalidateDistribution($distributionId, $paths)
  1401. {
  1402. if (!extension_loaded('openssl'))
  1403. {
  1404. self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s",
  1405. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1406. return false;
  1407. }
  1408. $useSSL = self::$useSSL;
  1409. self::$useSSL = true; // CloudFront requires SSL
  1410. $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
  1411. $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
  1412. $rest->size = strlen($rest->data);
  1413. $rest = self::__getCloudFrontResponse($rest);
  1414. self::$useSSL = $useSSL;
  1415. if ($rest->error === false && $rest->code !== 201)
  1416. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1417. if ($rest->error !== false)
  1418. {
  1419. trigger_error(sprintf("S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
  1420. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  1421. return false;
  1422. }
  1423. return true;
  1424. }
  1425. /**
  1426. * Get a InvalidationBatch DOMDocument
  1427. *
  1428. * @internal Used to create XML in invalidateDistribution()
  1429. * @param array $paths Paths to objects to invalidateDistribution
  1430. * @param int $callerReference
  1431. * @return string
  1432. */
  1433. private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0')
  1434. {
  1435. $dom = new DOMDocument('1.0', 'UTF-8');
  1436. $dom->formatOutput = true;
  1437. $invalidationBatch = $dom->createElement('InvalidationBatch');
  1438. foreach ($paths as $path)
  1439. $invalidationBatch->appendChild($dom->createElement('Path', $path));
  1440. $invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
  1441. $dom->appendChild($invalidationBatch);
  1442. return $dom->saveXML();
  1443. }
  1444. /**
  1445. * List your invalidation batches for invalidateDistribution() in a CloudFront distribution
  1446. *
  1447. * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html
  1448. * returned array looks like this:
  1449. * Array
  1450. * (
  1451. * [I31TWB0CN9V6XD] => InProgress
  1452. * [IT3TFE31M0IHZ] => Completed
  1453. * [I12HK7MPO1UQDA] => Completed
  1454. * [I1IA7R6JKTC3L2] => Completed
  1455. * )
  1456. *
  1457. * @param string $distributionId Distribution ID from listDistributions()
  1458. * @return array
  1459. */
  1460. public static function getDistributionInvalidationList($distributionId)
  1461. {
  1462. if (!extension_loaded('openssl'))
  1463. {
  1464. self::__triggerError(sprintf("S3::getDistributionInvalidationList(): [%s] %s",
  1465. "CloudFront functionality requires SSL"), __FILE__, __LINE__);
  1466. return false;
  1467. }
  1468. $useSSL = self::$useSSL;
  1469. self::$useSSL = true; // CloudFront requires SSL
  1470. $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
  1471. $rest = self::__getCloudFrontResponse($rest);
  1472. self::$useSSL = $useSSL;
  1473. if ($rest->error === false && $rest->code !== 200)
  1474. $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
  1475. if ($rest->error !== false)
  1476. {
  1477. trigger_error(sprintf("S3::getDistributionInvalidationList('{$distributionId}'): [%s]",
  1478. $rest->error['code'], $rest->error['message']), E_USER_WARNING);
  1479. return false;
  1480. }
  1481. elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary))
  1482. {
  1483. $list = array();
  1484. foreach ($rest->body->InvalidationSummary as $summary)
  1485. $list[(string)$summary->Id] = (string)$summary->Status;
  1486. return $list;
  1487. }
  1488. return array();
  1489. }
  1490. /**
  1491. * Get a DistributionConfig DOMDocument
  1492. *
  1493. * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
  1494. *
  1495. * @internal Used to create XML in createDistribution() and updateDistribution()
  1496. * @param string $bucket S3 Origin bucket
  1497. * @param boolean $enabled Enabled (true/false)
  1498. * @param string $comment Comment to append
  1499. * @param string $callerReference Caller reference
  1500. * @param array $cnames Array of CNAME aliases
  1501. * @param string $defaultRootObject Default root object
  1502. * @param string $originAccessIdentity Origin access identity
  1503. * @param array $trustedSigners Array of trusted signers
  1504. * @return string
  1505. */
  1506. private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
  1507. {
  1508. $dom = new DOMDocument('1.0', 'UTF-8');
  1509. $dom->formatOutput = true;
  1510. $distributionConfig = $dom->createElement('DistributionConfig');
  1511. $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
  1512. $origin = $dom->createElement('S3Origin');
  1513. $origin->appendChild($dom->createElement('DNSName', $bucket));
  1514. if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
  1515. $distributionConfig->appendChild($origin);
  1516. if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
  1517. $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
  1518. foreach ($cnames as $cname)
  1519. $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
  1520. if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
  1521. $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
  1522. $trusted = $dom->createElement('TrustedSigners');
  1523. foreach ($trustedSigners as $id => $type)
  1524. $trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
  1525. $distributionConfig->appendChild($trusted);
  1526. $dom->appendChild($distributionConfig);
  1527. //var_dump($dom->saveXML());
  1528. return $dom->saveXML();
  1529. }
  1530. /**
  1531. * Parse a CloudFront distribution config
  1532. *
  1533. * See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
  1534. *
  1535. * @internal Used to parse the CloudFront DistributionConfig node to an array
  1536. * @param object &$node DOMNode
  1537. * @return array
  1538. */
  1539. private static function __parseCloudFrontDistributionConfig(&$node)
  1540. {
  1541. if (isset($node->DistributionConfig))
  1542. return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
  1543. $dist = array();
  1544. if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
  1545. {
  1546. $dist['id'] = (string)$node->Id;
  1547. $dist['status'] = (string)$node->Status;
  1548. $dist['time'] = strtotime((string)$node->LastModifiedTime);
  1549. $dist['domain'] = (string)$node->DomainName;
  1550. }
  1551. if (isset($node->CallerReference))
  1552. $dist['callerReference'] = (string)$node->CallerReference;
  1553. if (isset($node->Enabled))
  1554. $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
  1555. if (isset($node->S3Origin))
  1556. {
  1557. if (isset($node->S3Origin->DNSName))
  1558. $dist['origin'] = (string)$node->S3Origin->DNSName;
  1559. $dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
  1560. (string)$node->S3Origin->OriginAccessIdentity : null;
  1561. }
  1562. $dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
  1563. $dist['cnames'] = array();
  1564. if (isset($node->CNAME))
  1565. foreach ($node->CNAME as $cname)
  1566. $dist['cnames'][(string)$cname] = (string)$cname;
  1567. $dist['trustedSigners'] = array();
  1568. if (isset($node->TrustedSigners))
  1569. foreach ($node->TrustedSigners as $signer)
  1570. {
  1571. if (isset($signer->Self))
  1572. $dist['trustedSigners'][''] = 'Self';
  1573. elseif (isset($signer->KeyPairId))
  1574. $dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
  1575. elseif (isset($signer->AwsAccountNumber))
  1576. $dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
  1577. }
  1578. $dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
  1579. return $dist;
  1580. }
  1581. /**
  1582. * Grab CloudFront response
  1583. *
  1584. * @internal Used to parse the CloudFront S3Request::getResponse() output
  1585. * @param object &$rest S3Request instance
  1586. * @return object
  1587. */
  1588. private static function __getCloudFrontResponse(&$rest)
  1589. {
  1590. $rest->getResponse();
  1591. if ($rest->response->error === false && isset($rest->response->body) &&
  1592. is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml')
  1593. {
  1594. $rest->response->body = simplexml_load_string($rest->response->body);
  1595. // Grab CloudFront errors
  1596. if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
  1597. $rest->response->body->Error->Message))
  1598. {
  1599. $rest->response->error = array(
  1600. 'code' => (string)$rest->response->body->Error->Code,
  1601. 'message' => (string)$rest->response->body->Error->Message
  1602. );
  1603. unset($rest->response->body);
  1604. }
  1605. }
  1606. return $rest->response;
  1607. }
  1608. /**
  1609. * Get MIME type for file
  1610. *
  1611. * To override the putObject() Content-Type, add it to $requestHeaders
  1612. *
  1613. * To use fileinfo, ensure the MAGIC environment variable is set
  1614. *
  1615. * @internal Used to get mime types
  1616. * @param string &$file File path
  1617. * @return string
  1618. */
  1619. private static function __getMIMEType(&$file)
  1620. {
  1621. static $exts = array(
  1622. 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif',
  1623. 'png' => 'image/png', 'ico' => 'image/x-icon', 'pdf' => 'application/pdf',
  1624. 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'svg' => 'image/svg+xml',
  1625. 'svgz' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash',
  1626. 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
  1627. 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
  1628. 'bz2' => 'application/x-bzip2', 'rar' => 'application/x-rar-compressed',
  1629. 'exe' => 'application/x-msdownload', 'msi' => 'application/x-msdownload',
  1630. 'cab' => 'application/vnd.ms-cab-compressed', 'txt' => 'text/plain',
  1631. 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
  1632. 'css' => 'text/css', 'js' => 'text/javascript',
  1633. 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
  1634. 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
  1635. 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
  1636. 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
  1637. );
  1638. $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
  1639. if (isset($exts[$ext])) return $exts[$ext];
  1640. // Use fileinfo if available
  1641. if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
  1642. ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
  1643. {
  1644. if (($type = finfo_file($finfo, $file)) !== false)
  1645. {
  1646. // Remove the charset and grab the last content-type
  1647. $type = explode(' ', str_replace('; charset=', ';charset=', $type));
  1648. $type = array_pop($type);
  1649. $type = explode(';', $type);
  1650. $type = trim(array_shift($type));
  1651. }
  1652. finfo_close($finfo);
  1653. if ($type !== false && strlen($type) > 0) return $type;
  1654. }
  1655. return 'application/octet-stream';
  1656. }
  1657. /**
  1658. * Get the current time
  1659. *
  1660. * @internal Used to apply offsets to sytem time
  1661. * @return integer
  1662. */
  1663. public static function __getTime()
  1664. {
  1665. return time() + self::$__timeOffset;
  1666. }
  1667. /**
  1668. * Generate the auth string: "AWS AccessKey:Signature"
  1669. *
  1670. * @internal Used by S3Request::getResponse()
  1671. * @param string $string String to sign
  1672. * @return string
  1673. */
  1674. public static function __getSignature($string)
  1675. {
  1676. return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
  1677. }
  1678. /**
  1679. * Creates a HMAC-SHA1 hash
  1680. *
  1681. * This uses the hash extension if loaded
  1682. *
  1683. * @internal Used by __getSignature()
  1684. * @param string $string String to sign
  1685. * @return string
  1686. */
  1687. private static function __getHash($string)
  1688. {
  1689. return base64_encode(extension_loaded('hash') ?
  1690. hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
  1691. (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
  1692. pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
  1693. (str_repeat(chr(0x36), 64))) . $string)))));
  1694. }
  1695. }
  1696. /**
  1697. * S3 Request class
  1698. *
  1699. * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
  1700. * @version 0.5.0-dev
  1701. */
  1702. final class S3Request
  1703. {
  1704. /**
  1705. * AWS URI
  1706. *
  1707. * @var string
  1708. * @access private
  1709. */
  1710. private $endpoint;
  1711. /**
  1712. * Verb
  1713. *
  1714. * @var string
  1715. * @access private
  1716. */
  1717. private $verb;
  1718. /**
  1719. * S3 bucket name
  1720. *
  1721. * @var string
  1722. * @access private
  1723. */
  1724. private $bucket;
  1725. /**
  1726. * Object URI
  1727. *
  1728. * @var string
  1729. * @access private
  1730. */
  1731. private $uri;
  1732. /**
  1733. * Final object URI
  1734. *
  1735. * @var string
  1736. * @access private
  1737. */
  1738. private $resource = '';
  1739. /**
  1740. * Additional request parameters
  1741. *
  1742. * @var array
  1743. * @access private
  1744. */
  1745. private $parameters = array();
  1746. /**
  1747. * Amazon specific request headers
  1748. *
  1749. * @var array
  1750. * @access private
  1751. */
  1752. private $amzHeaders = array();
  1753. /**
  1754. * HTTP request headers
  1755. *
  1756. * @var array
  1757. * @access private
  1758. */
  1759. private $headers = array(
  1760. 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
  1761. );
  1762. /**
  1763. * Use HTTP PUT?
  1764. *
  1765. * @var bool
  1766. * @access public
  1767. */
  1768. public $fp = false;
  1769. /**
  1770. * PUT file size
  1771. *
  1772. * @var int
  1773. * @access public
  1774. */
  1775. public $size = 0;
  1776. /**
  1777. * PUT post fields
  1778. *
  1779. * @var array
  1780. * @access public
  1781. */
  1782. public $data = false;
  1783. /**
  1784. * S3 request respone
  1785. *
  1786. * @var object
  1787. * @access public
  1788. */
  1789. public $response;
  1790. /**
  1791. * Constructor
  1792. *
  1793. * @param string $verb Verb
  1794. * @param string $bucket Bucket name
  1795. * @param string $uri Object URI
  1796. * @param string $endpoint AWS endpoint URI
  1797. * @return mixed
  1798. */
  1799. function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
  1800. {
  1801. $this->endpoint = $endpoint;
  1802. $this->verb = $verb;
  1803. $this->bucket = $bucket;
  1804. $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
  1805. //if ($this->bucket !== '')
  1806. // $this->resource = '/'.$this->bucket.$this->uri;
  1807. //else
  1808. // $this->resource = $this->uri;
  1809. if ($this->bucket !== '')
  1810. {
  1811. if ($this->__dnsBucketName($this->bucket))
  1812. {
  1813. $this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
  1814. $this->resource = '/'.$this->bucket.$this->uri;
  1815. }
  1816. else
  1817. {
  1818. $this->headers['Host'] = $this->endpoint;
  1819. $this->uri = $this->uri;
  1820. if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
  1821. $this->bucket = '';
  1822. $this->resource = $this->uri;
  1823. }
  1824. }
  1825. else
  1826. {
  1827. $this->headers['Host'] = $this->endpoint;
  1828. $this->resource = $this->uri;
  1829. }
  1830. $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
  1831. $this->response = new STDClass;
  1832. $this->response->error = false;
  1833. $this->response->body = null;
  1834. $this->response->headers = array();
  1835. }
  1836. /**
  1837. * Set request parameter
  1838. *
  1839. * @param string $key Key
  1840. * @param string $value Value
  1841. * @return void
  1842. */
  1843. public function setParameter($key, $value)
  1844. {
  1845. $this->parameters[$key] = $value;
  1846. }
  1847. /**
  1848. * Set request header
  1849. *
  1850. * @param string $key Key
  1851. * @param string $value Value
  1852. * @return void
  1853. */
  1854. public function setHeader($key, $value)
  1855. {
  1856. $this->headers[$key] = $value;
  1857. }
  1858. /**
  1859. * Set x-amz-meta-* header
  1860. *
  1861. * @param string $key Key
  1862. * @param string $value Value
  1863. * @return void
  1864. */
  1865. public function setAmzHeader($key, $value)
  1866. {
  1867. $this->amzHeaders[$key] = $value;
  1868. }
  1869. /**
  1870. * Get the S3 response
  1871. *
  1872. * @return object | false
  1873. */
  1874. public function getResponse()
  1875. {
  1876. $query = '';
  1877. if (sizeof($this->parameters) > 0)
  1878. {
  1879. $query = substr($this->uri, -1) !== '?' ? '?' : '&';
  1880. foreach ($this->parameters as $var => $value)
  1881. if ($value == null || $value == '') $query .= $var.'&';
  1882. else $query .= $var.'='.rawurlencode($value).'&';
  1883. $query = substr($query, 0, -1);
  1884. $this->uri .= $query;
  1885. if (array_key_exists('acl', $this->parameters) ||
  1886. array_key_exists('location', $this->parameters) ||
  1887. array_key_exists('torrent', $this->parameters) ||
  1888. array_key_exists('website', $this->parameters) ||
  1889. array_key_exists('logging', $this->parameters))
  1890. $this->resource .= $query;
  1891. }
  1892. $url = (S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
  1893. // Basic setup
  1894. $curl = curl_init();
  1895. curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
  1896. if (S3::$useSSL)
  1897. {
  1898. // Set protocol version
  1899. curl_setopt($curl, CURLOPT_SSLVERSION, S3::$useSSLVersion);
  1900. // SSL Validation can now be optional for those with broken OpenSSL installations
  1901. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 2 : 0);
  1902. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
  1903. if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey);
  1904. if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert);
  1905. if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert);
  1906. }
  1907. curl_setopt($curl, CURLOPT_URL, $url);
  1908. if (S3::$proxy != null && isset(S3::$proxy['host']))
  1909. {
  1910. curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']);
  1911. curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']);
  1912. if (isset(S3::$proxy['user'], S3::$proxy['pass']) && S3::$proxy['user'] != null && S3::$proxy['pass'] != null)
  1913. curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass']));
  1914. }
  1915. // Headers
  1916. $headers = array(); $amz = array();
  1917. foreach ($this->amzHeaders as $header => $value)
  1918. if (strlen($value) > 0) $headers[] = $header.': '.$value;
  1919. foreach ($this->headers as $header => $value)
  1920. if (strlen($value) > 0) $headers[] = $header.': '.$value;
  1921. // Collect AMZ headers for signature
  1922. foreach ($this->amzHeaders as $header => $value)
  1923. if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
  1924. // AMZ headers must be sorted
  1925. if (sizeof($amz) > 0)
  1926. {
  1927. //sort($amz);
  1928. usort($amz, array(&$this, '__sortMetaHeadersCmp'));
  1929. $amz = "\n".implode("\n", $amz);
  1930. } else $amz = '';
  1931. if (S3::hasAuth())
  1932. {
  1933. // Authorization string (CloudFront stringToSign should only contain a date)
  1934. if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
  1935. $headers[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']);
  1936. else
  1937. {
  1938. $headers[] = 'Authorization: ' . S3::__getSignature(
  1939. $this->verb."\n".
  1940. $this->headers['Content-MD5']."\n".
  1941. $this->headers['Content-Type']."\n".
  1942. $this->headers['Date'].$amz."\n".
  1943. $this->resource
  1944. );
  1945. }
  1946. }
  1947. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  1948. curl_setopt($curl, CURLOPT_HEADER, false);
  1949. curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
  1950. curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
  1951. curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
  1952. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  1953. // Request types
  1954. switch ($this->verb)
  1955. {
  1956. case 'GET': break;
  1957. case 'PUT': case 'POST': // POST only used for CloudFront
  1958. if ($this->fp !== false)
  1959. {
  1960. curl_setopt($curl, CURLOPT_PUT, true);
  1961. curl_setopt($curl, CURLOPT_INFILE, $this->fp);
  1962. if ($this->size >= 0)
  1963. curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
  1964. }
  1965. elseif ($this->data !== false)
  1966. {
  1967. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
  1968. curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
  1969. }
  1970. else
  1971. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
  1972. break;
  1973. case 'HEAD':
  1974. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
  1975. curl_setopt($curl, CURLOPT_NOBODY, true);
  1976. break;
  1977. case 'DELETE':
  1978. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
  1979. break;
  1980. default: break;
  1981. }
  1982. // Execute, grab errors
  1983. if (curl_exec($curl))
  1984. $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  1985. else
  1986. $this->response->error = array(
  1987. 'code' => curl_errno($curl),
  1988. 'message' => curl_error($curl),
  1989. 'resource' => $this->resource
  1990. );
  1991. @curl_close($curl);
  1992. // Parse body into XML
  1993. if ($this->response->error === false && isset($this->response->headers['type']) &&
  1994. 0 === strpos($this->response->headers['type'], 'application/xml') && isset($this->response->body))
  1995. {
  1996. $this->response->body = simplexml_load_string($this->response->body);
  1997. // Grab S3 errors
  1998. if (!in_array($this->response->code, array(200, 204, 206)) &&
  1999. isset($this->response->body->Code, $this->response->body->Message))
  2000. {
  2001. $this->response->error = array(
  2002. 'code' => (string)$this->response->body->Code,
  2003. 'message' => (string)$this->response->body->Message
  2004. );
  2005. if (isset($this->response->body->Resource))
  2006. $this->response->error['resource'] = (string)$this->response->body->Resource;
  2007. unset($this->response->body);
  2008. }
  2009. }
  2010. // Clean up file resources
  2011. if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
  2012. return $this->response;
  2013. }
  2014. /**
  2015. * Sort compare for meta headers
  2016. *
  2017. * @internal Used to sort x-amz meta headers
  2018. * @param string $a String A
  2019. * @param string $b String B
  2020. * @return integer
  2021. */
  2022. private function __sortMetaHeadersCmp($a, $b)
  2023. {
  2024. $lenA = strpos($a, ':');
  2025. $lenB = strpos($b, ':');
  2026. $minLen = min($lenA, $lenB);
  2027. $ncmp = strncmp($a, $b, $minLen);
  2028. if ($lenA == $lenB) return $ncmp;
  2029. if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
  2030. return $ncmp;
  2031. }
  2032. /**
  2033. * CURL write callback
  2034. *
  2035. * @param resource &$curl CURL resource
  2036. * @param string &$data Data
  2037. * @return integer
  2038. */
  2039. private function __responseWriteCallback(&$curl, &$data)
  2040. {
  2041. if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
  2042. return fwrite($this->fp, $data);
  2043. else
  2044. $this->response->body .= $data;
  2045. return strlen($data);
  2046. }
  2047. /**
  2048. * Check DNS conformity
  2049. *
  2050. * @param string $bucket Bucket name
  2051. * @return boolean
  2052. */
  2053. private function __dnsBucketName($bucket)
  2054. {
  2055. if (strlen($bucket) > 63 || preg_match("/[^a-z0-9\.-]/", $bucket) > 0) return false;
  2056. if (S3::$useSSL && strstr($bucket, '.') !== false) return false;
  2057. if (strstr($bucket, '-.') !== false) return false;
  2058. if (strstr($bucket, '..') !== false) return false;
  2059. if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
  2060. if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
  2061. return true;
  2062. }
  2063. /**
  2064. * CURL header callback
  2065. *
  2066. * @param resource $curl CURL resource
  2067. * @param string $data Data
  2068. * @return integer
  2069. */
  2070. private function __responseHeaderCallback($curl, $data)
  2071. {
  2072. if (($strlen = strlen($data)) <= 2) return $strlen;
  2073. if (substr($data, 0, 4) == 'HTTP')
  2074. $this->response->code = (int)substr($data, 9, 3);
  2075. else
  2076. {
  2077. $data = trim($data);
  2078. if (strpos($data, ': ') === false) return $strlen;
  2079. list($header, $value) = explode(': ', $data, 2);
  2080. if ($header == 'Last-Modified')
  2081. $this->response->headers['time'] = strtotime($value);
  2082. elseif ($header == 'Date')
  2083. $this->response->headers['date'] = strtotime($value);
  2084. elseif ($header == 'Content-Length')
  2085. $this->response->headers['size'] = (int)$value;
  2086. elseif ($header == 'Content-Type')
  2087. $this->response->headers['type'] = $value;
  2088. elseif ($header == 'ETag')
  2089. $this->response->headers['hash'] = $value[0] == '"' ? substr($value, 1, -1) : $value;
  2090. elseif (preg_match('/^x-amz-meta-.*$/', $header))
  2091. $this->response->headers[$header] = $value;
  2092. }
  2093. return $strlen;
  2094. }
  2095. }
  2096. /**
  2097. * S3 exception class
  2098. *
  2099. * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
  2100. * @version 0.5.0-dev
  2101. */
  2102. class S3Exception extends Exception {
  2103. /**
  2104. * Class constructor
  2105. *
  2106. * @param string $message Exception message
  2107. * @param string $file File in which exception was created
  2108. * @param string $line Line number on which exception was created
  2109. * @param int $code Exception code
  2110. */
  2111. function __construct($message, $file, $line, $code = 0)
  2112. {
  2113. parent::__construct($message, $code);
  2114. $this->file = $file;
  2115. $this->line = $line;
  2116. }
  2117. }