[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/repository/s3/ -> S3.php (source)

   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  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1