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