S3.php 72 KB

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