@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Swap S3 to first-party client

Summary:
Ref T5155. Swaps Phabricator over to the new first-party S3 client using the v4 authentication API so it works in all regions.

The API requires an explicit region, so the new `amazon-s3.region` is now required. I'll write guidance about this.

Test Plan:
- Uploaded files to S3.
- Migrated ~1GB of files to S3.
- Loaded a bunch of files off S3.
- Browsed around the S3 bucket.
- Deleted a file, verified the data on S3 was destroyed.
- Hit new setup warning.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T5155

Differential Revision: https://secure.phabricator.com/D14982

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