S3v4.php 76 KB

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