web/lib/Zend/Service/Amazon/S3.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Service
       
    17  * @subpackage Amazon_S3
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  * @version    $Id: S3.php 23224 2010-10-22 13:45:57Z matthew $
       
    21  */
       
    22 
       
    23 /**
       
    24  * @see Zend_Service_Amazon_Abstract
       
    25  */
       
    26 require_once 'Zend/Service/Amazon/Abstract.php';
       
    27 
       
    28 /**
       
    29  * @see Zend_Crypt_Hmac
       
    30  */
       
    31 require_once 'Zend/Crypt/Hmac.php';
       
    32 
       
    33 /**
       
    34  * Amazon S3 PHP connection class
       
    35  *
       
    36  * @category   Zend
       
    37  * @package    Zend_Service
       
    38  * @subpackage Amazon_S3
       
    39  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    40  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    41  * @see        http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
       
    42  */
       
    43 class Zend_Service_Amazon_S3 extends Zend_Service_Amazon_Abstract
       
    44 {
       
    45     /**
       
    46      * Store for stream wrapper clients
       
    47      *
       
    48      * @var array
       
    49      */
       
    50     protected static $_wrapperClients = array();
       
    51 
       
    52     /**
       
    53      * Endpoint for the service
       
    54      *
       
    55      * @var Zend_Uri_Http
       
    56      */
       
    57     protected $_endpoint;
       
    58 
       
    59     const S3_ENDPOINT = 's3.amazonaws.com';
       
    60 
       
    61     const S3_ACL_PRIVATE = 'private';
       
    62     const S3_ACL_PUBLIC_READ = 'public-read';
       
    63     const S3_ACL_PUBLIC_WRITE = 'public-read-write';
       
    64     const S3_ACL_AUTH_READ = 'authenticated-read';
       
    65 
       
    66     const S3_REQUESTPAY_HEADER = 'x-amz-request-payer';
       
    67     const S3_ACL_HEADER = 'x-amz-acl';
       
    68     const S3_CONTENT_TYPE_HEADER = 'Content-Type';
       
    69 
       
    70     /**
       
    71      * Set S3 endpoint to use
       
    72      *
       
    73      * @param string|Zend_Uri_Http $endpoint
       
    74      * @return Zend_Service_Amazon_S3
       
    75      */
       
    76     public function setEndpoint($endpoint)
       
    77     {
       
    78         if (!($endpoint instanceof Zend_Uri_Http)) {
       
    79             $endpoint = Zend_Uri::factory($endpoint);
       
    80         }
       
    81         if (!$endpoint->valid()) {
       
    82             /**
       
    83              * @see Zend_Service_Amazon_S3_Exception
       
    84              */
       
    85             require_once 'Zend/Service/Amazon/S3/Exception.php';
       
    86             throw new Zend_Service_Amazon_S3_Exception('Invalid endpoint supplied');
       
    87         }
       
    88         $this->_endpoint = $endpoint;
       
    89         return $this;
       
    90     }
       
    91 
       
    92     /**
       
    93      * Get current S3 endpoint
       
    94      *
       
    95      * @return Zend_Uri_Http
       
    96      */
       
    97     public function getEndpoint()
       
    98     {
       
    99         return $this->_endpoint;
       
   100     }
       
   101 
       
   102     /**
       
   103      * Constructor
       
   104      *
       
   105      * @param string $accessKey
       
   106      * @param string $secretKey
       
   107      * @param string $region
       
   108      */
       
   109     public function __construct($accessKey=null, $secretKey=null, $region=null)
       
   110     {
       
   111         parent::__construct($accessKey, $secretKey, $region);
       
   112 
       
   113         $this->setEndpoint('http://'.self::S3_ENDPOINT);
       
   114     }
       
   115 
       
   116     /**
       
   117      * Verify if the bucket name is valid
       
   118      *
       
   119      * @param string $bucket
       
   120      * @return boolean
       
   121      */
       
   122     public function _validBucketName($bucket)
       
   123     {
       
   124         $len = strlen($bucket);
       
   125         if ($len < 3 || $len > 255) {
       
   126             /**
       
   127              * @see Zend_Service_Amazon_S3_Exception
       
   128              */
       
   129             require_once 'Zend/Service/Amazon/S3/Exception.php';
       
   130             throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" must be between 3 and 255 characters long");
       
   131         }
       
   132 
       
   133         if (preg_match('/[^a-z0-9\._-]/', $bucket)) {
       
   134             /**
       
   135              * @see Zend_Service_Amazon_S3_Exception
       
   136              */
       
   137             require_once 'Zend/Service/Amazon/S3/Exception.php';
       
   138             throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" contains invalid characters");
       
   139         }
       
   140 
       
   141         if (preg_match('/(\d){1,3}\.(\d){1,3}\.(\d){1,3}\.(\d){1,3}/', $bucket)) {
       
   142             /**
       
   143              * @see Zend_Service_Amazon_S3_Exception
       
   144              */
       
   145             require_once 'Zend/Service/Amazon/S3/Exception.php';
       
   146             throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" cannot be an IP address");
       
   147         }
       
   148         return true;
       
   149     }
       
   150 
       
   151     /**
       
   152      * Add a new bucket
       
   153      *
       
   154      * @param  string $bucket
       
   155      * @return boolean
       
   156      */
       
   157     public function createBucket($bucket, $location = null)
       
   158     {
       
   159         $this->_validBucketName($bucket);
       
   160 
       
   161         if($location) {
       
   162             $data = '<CreateBucketConfiguration><LocationConstraint>'.$location.'</LocationConstraint></CreateBucketConfiguration>';
       
   163         }
       
   164         else {
       
   165             $data = null;
       
   166         }
       
   167         $response = $this->_makeRequest('PUT', $bucket, null, array(), $data);
       
   168 
       
   169         return ($response->getStatus() == 200);
       
   170     }
       
   171 
       
   172     /**
       
   173      * Checks if a given bucket name is available
       
   174      *
       
   175      * @param  string $bucket
       
   176      * @return boolean
       
   177      */
       
   178     public function isBucketAvailable($bucket)
       
   179     {
       
   180         $response = $this->_makeRequest('HEAD', $bucket, array('max-keys'=>0));
       
   181 
       
   182         return ($response->getStatus() != 404);
       
   183     }
       
   184 
       
   185     /**
       
   186      * Checks if a given object exists
       
   187      *
       
   188      * @param  string $object
       
   189      * @return boolean
       
   190      */
       
   191     public function isObjectAvailable($object)
       
   192     {
       
   193         $object = $this->_fixupObjectName($object);
       
   194         $response = $this->_makeRequest('HEAD', $object);
       
   195 
       
   196         return ($response->getStatus() == 200);
       
   197     }
       
   198 
       
   199     /**
       
   200      * Remove a given bucket. All objects in the bucket must be removed prior
       
   201      * to removing the bucket.
       
   202      *
       
   203      * @param  string $bucket
       
   204      * @return boolean
       
   205      */
       
   206     public function removeBucket($bucket)
       
   207     {
       
   208         $response = $this->_makeRequest('DELETE', $bucket);
       
   209 
       
   210         // Look for a 204 No Content response
       
   211         return ($response->getStatus() == 204);
       
   212     }
       
   213 
       
   214     /**
       
   215      * Get metadata information for a given object
       
   216      *
       
   217      * @param  string $object
       
   218      * @return array|false
       
   219      */
       
   220     public function getInfo($object)
       
   221     {
       
   222         $info = array();
       
   223 
       
   224         $object = $this->_fixupObjectName($object);
       
   225         $response = $this->_makeRequest('HEAD', $object);
       
   226 
       
   227         if ($response->getStatus() == 200) {
       
   228             $info['type'] = $response->getHeader('Content-type');
       
   229             $info['size'] = $response->getHeader('Content-length');
       
   230             $info['mtime'] = strtotime($response->getHeader('Last-modified'));
       
   231             $info['etag'] = $response->getHeader('ETag');
       
   232         }
       
   233         else {
       
   234             return false;
       
   235         }
       
   236 
       
   237         return $info;
       
   238     }
       
   239 
       
   240     /**
       
   241      * List the S3 buckets
       
   242      *
       
   243      * @return array|false
       
   244      */
       
   245     public function getBuckets()
       
   246     {
       
   247         $response = $this->_makeRequest('GET');
       
   248 
       
   249         if ($response->getStatus() != 200) {
       
   250             return false;
       
   251         }
       
   252 
       
   253         $xml = new SimpleXMLElement($response->getBody());
       
   254 
       
   255         $buckets = array();
       
   256         foreach ($xml->Buckets->Bucket as $bucket) {
       
   257             $buckets[] = (string)$bucket->Name;
       
   258         }
       
   259 
       
   260         return $buckets;
       
   261     }
       
   262 
       
   263     /**
       
   264      * Remove all objects in the bucket.
       
   265      *
       
   266      * @param string $bucket
       
   267      * @return boolean
       
   268      */
       
   269     public function cleanBucket($bucket)
       
   270     {
       
   271         $objects = $this->getObjectsByBucket($bucket);
       
   272         if (!$objects) {
       
   273             return false;
       
   274         }
       
   275 
       
   276         foreach ($objects as $object) {
       
   277             $this->removeObject("$bucket/$object");
       
   278         }
       
   279         return true;
       
   280     }
       
   281 
       
   282     /**
       
   283      * List the objects in a bucket.
       
   284      *
       
   285      * Provides the list of object keys that are contained in the bucket.  Valid params include the following.
       
   286      * prefix - Limits the response to keys which begin with the indicated prefix. You can use prefixes to separate a bucket into different sets of keys in a way similar to how a file system uses folders.
       
   287      * marker - Indicates where in the bucket to begin listing. The list will only include keys that occur lexicographically after marker. This is convenient for pagination: To get the next page of results use the last key of the current page as the marker.
       
   288      * max-keys - The maximum number of keys you'd like to see in the response body. The server might return fewer than this many keys, but will not return more.
       
   289      * delimiter - Causes keys that contain the same string between the prefix and the first occurrence of the delimiter to be rolled up into a single result element in the CommonPrefixes collection. These rolled-up keys are not returned elsewhere in the response.
       
   290      *
       
   291      * @param  string $bucket
       
   292      * @param array $params S3 GET Bucket Paramater
       
   293      * @return array|false
       
   294      */
       
   295     public function getObjectsByBucket($bucket, $params = array())
       
   296     {
       
   297         $response = $this->_makeRequest('GET', $bucket, $params);
       
   298 
       
   299         if ($response->getStatus() != 200) {
       
   300             return false;
       
   301         }
       
   302 
       
   303         $xml = new SimpleXMLElement($response->getBody());
       
   304 
       
   305         $objects = array();
       
   306         if (isset($xml->Contents)) {
       
   307             foreach ($xml->Contents as $contents) {
       
   308                 foreach ($contents->Key as $object) {
       
   309                     $objects[] = (string)$object;
       
   310                 }
       
   311             }
       
   312         }
       
   313 
       
   314         return $objects;
       
   315     }
       
   316 
       
   317     /**
       
   318      * Make sure the object name is valid
       
   319      *
       
   320      * @param  string $object
       
   321      * @return string
       
   322      */
       
   323     protected function _fixupObjectName($object)
       
   324     {
       
   325         $nameparts = explode('/', $object);
       
   326 
       
   327         $this->_validBucketName($nameparts[0]);
       
   328 
       
   329         $firstpart = array_shift($nameparts);
       
   330         if (count($nameparts) == 0) {
       
   331             return $firstpart;
       
   332         }
       
   333 
       
   334         return $firstpart.'/'.join('/', array_map('rawurlencode', $nameparts));
       
   335     }
       
   336 
       
   337     /**
       
   338      * Get an object
       
   339      *
       
   340      * @param  string $object
       
   341      * @param  bool   $paidobject This is "requestor pays" object
       
   342      * @return string|false
       
   343      */
       
   344     public function getObject($object, $paidobject=false)
       
   345     {
       
   346         $object = $this->_fixupObjectName($object);
       
   347         if ($paidobject) {
       
   348             $response = $this->_makeRequest('GET', $object, null, array(self::S3_REQUESTPAY_HEADER => 'requester'));
       
   349         }
       
   350         else {
       
   351             $response = $this->_makeRequest('GET', $object);
       
   352         }
       
   353 
       
   354         if ($response->getStatus() != 200) {
       
   355             return false;
       
   356         }
       
   357 
       
   358         return $response->getBody();
       
   359     }
       
   360 
       
   361     /**
       
   362      * Get an object using streaming
       
   363      *
       
   364      * Can use either provided filename for storage or create a temp file if none provided.
       
   365      *
       
   366      * @param  string $object Object path
       
   367      * @param  string $streamfile File to write the stream to
       
   368      * @param  bool   $paidobject This is "requestor pays" object
       
   369      * @return Zend_Http_Response_Stream|false
       
   370      */
       
   371     public function getObjectStream($object, $streamfile = null, $paidobject=false)
       
   372     {
       
   373         $object = $this->_fixupObjectName($object);
       
   374         self::getHttpClient()->setStream($streamfile?$streamfile:true);
       
   375         if ($paidobject) {
       
   376             $response = $this->_makeRequest('GET', $object, null, array(self::S3_REQUESTPAY_HEADER => 'requester'));
       
   377         }
       
   378         else {
       
   379             $response = $this->_makeRequest('GET', $object);
       
   380         }
       
   381         self::getHttpClient()->setStream(null);
       
   382 
       
   383         if ($response->getStatus() != 200 || !($response instanceof Zend_Http_Response_Stream)) {
       
   384             return false;
       
   385         }
       
   386 
       
   387         return $response;
       
   388     }
       
   389 
       
   390     /**
       
   391      * Upload an object by a PHP string
       
   392      *
       
   393      * @param  string $object Object name
       
   394      * @param  string|resource $data   Object data (can be string or stream)
       
   395      * @param  array  $meta   Metadata
       
   396      * @return boolean
       
   397      */
       
   398     public function putObject($object, $data, $meta=null)
       
   399     {
       
   400         $object = $this->_fixupObjectName($object);
       
   401         $headers = (is_array($meta)) ? $meta : array();
       
   402 
       
   403         if(!is_resource($data)) {
       
   404             $headers['Content-MD5'] = base64_encode(md5($data, true));
       
   405         }
       
   406         $headers['Expect'] = '100-continue';
       
   407 
       
   408         if (!isset($headers[self::S3_CONTENT_TYPE_HEADER])) {
       
   409             $headers[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($object);
       
   410         }
       
   411 
       
   412         $response = $this->_makeRequest('PUT', $object, null, $headers, $data);
       
   413 
       
   414         // Check the MD5 Etag returned by S3 against and MD5 of the buffer
       
   415         if ($response->getStatus() == 200) {
       
   416             // It is escaped by double quotes for some reason
       
   417             $etag = str_replace('"', '', $response->getHeader('Etag'));
       
   418 
       
   419             if (is_resource($data) || $etag == md5($data)) {
       
   420                 return true;
       
   421             }
       
   422         }
       
   423 
       
   424         return false;
       
   425     }
       
   426 
       
   427     /**
       
   428      * Put file to S3 as object
       
   429      *
       
   430      * @param string $path   File name
       
   431      * @param string $object Object name
       
   432      * @param array  $meta   Metadata
       
   433      * @return boolean
       
   434      */
       
   435     public function putFile($path, $object, $meta=null)
       
   436     {
       
   437         $data = @file_get_contents($path);
       
   438         if ($data === false) {
       
   439             /**
       
   440              * @see Zend_Service_Amazon_S3_Exception
       
   441              */
       
   442             require_once 'Zend/Service/Amazon/S3/Exception.php';
       
   443             throw new Zend_Service_Amazon_S3_Exception("Cannot read file $path");
       
   444         }
       
   445 
       
   446         if (!is_array($meta)) {
       
   447             $meta = array();
       
   448         }
       
   449 
       
   450         if (!isset($meta[self::S3_CONTENT_TYPE_HEADER])) {
       
   451            $meta[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($path);
       
   452         }
       
   453 
       
   454         return $this->putObject($object, $data, $meta);
       
   455     }
       
   456 
       
   457     /**
       
   458      * Put file to S3 as object, using streaming
       
   459      *
       
   460      * @param string $path   File name
       
   461      * @param string $object Object name
       
   462      * @param array  $meta   Metadata
       
   463      * @return boolean
       
   464      */
       
   465     public function putFileStream($path, $object, $meta=null)
       
   466     {
       
   467         $data = @fopen($path, "rb");
       
   468         if ($data === false) {
       
   469             /**
       
   470              * @see Zend_Service_Amazon_S3_Exception
       
   471              */
       
   472             require_once 'Zend/Service/Amazon/S3/Exception.php';
       
   473             throw new Zend_Service_Amazon_S3_Exception("Cannot open file $path");
       
   474         }
       
   475 
       
   476         if (!is_array($meta)) {
       
   477             $meta = array();
       
   478         }
       
   479 
       
   480         if (!isset($meta[self::S3_CONTENT_TYPE_HEADER])) {
       
   481            $meta[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($path);
       
   482         }
       
   483 
       
   484         if(!isset($meta['Content-MD5'])) {
       
   485             $headers['Content-MD5'] = base64_encode(md5_file($path, true));
       
   486         }
       
   487 
       
   488         return $this->putObject($object, $data, $meta);
       
   489     }
       
   490 
       
   491     /**
       
   492      * Remove a given object
       
   493      *
       
   494      * @param  string $object
       
   495      * @return boolean
       
   496      */
       
   497     public function removeObject($object)
       
   498     {
       
   499         $object = $this->_fixupObjectName($object);
       
   500         $response = $this->_makeRequest('DELETE', $object);
       
   501 
       
   502         // Look for a 204 No Content response
       
   503         return ($response->getStatus() == 204);
       
   504     }
       
   505 
       
   506     /**
       
   507      * Copy an object
       
   508      *
       
   509      * @param  string $sourceObject  Source object name
       
   510      * @param  string $destObject    Destination object name
       
   511      * @param  array  $meta          (OPTIONAL) Metadata to apply to desination object.
       
   512      *                               Set to null to copy metadata from source object.
       
   513      * @return boolean
       
   514      */
       
   515     public function copyObject($sourceObject, $destObject, $meta = null)
       
   516     {
       
   517         $sourceObject = $this->_fixupObjectName($sourceObject);
       
   518         $destObject   = $this->_fixupObjectName($destObject);
       
   519 
       
   520         $headers = (is_array($meta)) ? $meta : array();
       
   521         $headers['x-amz-copy-source'] = $sourceObject;
       
   522         $headers['x-amz-metadata-directive'] = $meta === null ? 'COPY' : 'REPLACE';
       
   523 
       
   524         $response = $this->_makeRequest('PUT', $destObject, null, $headers);
       
   525 
       
   526         if ($response->getStatus() == 200 && !stristr($response->getBody(), '<Error>')) {
       
   527             return true;
       
   528         }
       
   529 
       
   530         return false;
       
   531     }
       
   532 
       
   533     /**
       
   534      * Move an object
       
   535      *
       
   536      * Performs a copy to dest + verify + remove source
       
   537      *
       
   538      * @param string $sourceObject  Source object name
       
   539      * @param string $destObject    Destination object name
       
   540      * @param array  $meta          (OPTIONAL) Metadata to apply to destination object.
       
   541      *                              Set to null to retain existing metadata.
       
   542      */
       
   543     public function moveObject($sourceObject, $destObject, $meta = null)
       
   544     {
       
   545         $sourceInfo = $this->getInfo($sourceObject);
       
   546 
       
   547         $this->copyObject($sourceObject, $destObject, $meta);
       
   548         $destInfo = $this->getInfo($destObject);
       
   549 
       
   550         if ($sourceInfo['etag'] === $destInfo['etag']) {
       
   551             return $this->removeObject($sourceObject);
       
   552         } else {
       
   553             return false;
       
   554         }
       
   555     }
       
   556 
       
   557     /**
       
   558      * Make a request to Amazon S3
       
   559      *
       
   560      * @param  string $method	Request method
       
   561      * @param  string $path		Path to requested object
       
   562      * @param  array  $params	Request parameters
       
   563      * @param  array  $headers	HTTP headers
       
   564      * @param  string|resource $data		Request data
       
   565      * @return Zend_Http_Response
       
   566      */
       
   567     public function _makeRequest($method, $path='', $params=null, $headers=array(), $data=null)
       
   568     {
       
   569         $retry_count = 0;
       
   570 
       
   571         if (!is_array($headers)) {
       
   572             $headers = array($headers);
       
   573         }
       
   574 
       
   575         $headers['Date'] = gmdate(DATE_RFC1123, time());
       
   576 
       
   577         if(is_resource($data) && $method != 'PUT') {
       
   578             /**
       
   579              * @see Zend_Service_Amazon_S3_Exception
       
   580              */
       
   581             require_once 'Zend/Service/Amazon/S3/Exception.php';
       
   582             throw new Zend_Service_Amazon_S3_Exception("Only PUT request supports stream data");
       
   583         }
       
   584 
       
   585         // build the end point out
       
   586         $parts = explode('/', $path, 2);
       
   587         $endpoint = clone($this->_endpoint);
       
   588         if ($parts[0]) {
       
   589             // prepend bucket name to the hostname
       
   590             $endpoint->setHost($parts[0].'.'.$endpoint->getHost());
       
   591         }
       
   592         if (!empty($parts[1])) {
       
   593             $endpoint->setPath('/'.$parts[1]);
       
   594         }
       
   595         else {
       
   596             $endpoint->setPath('/');
       
   597             if ($parts[0]) {
       
   598                 $path = $parts[0].'/';
       
   599             }
       
   600         }
       
   601 
       
   602         self::addSignature($method, $path, $headers);
       
   603 
       
   604         $client = self::getHttpClient();
       
   605 
       
   606         $client->resetParameters();
       
   607         $client->setUri($endpoint);
       
   608         $client->setAuth(false);
       
   609         // Work around buglet in HTTP client - it doesn't clean headers
       
   610         // Remove when ZHC is fixed
       
   611         $client->setHeaders(array('Content-MD5'              => null,
       
   612                                   'Content-Encoding'         => null,
       
   613                                   'Expect'                   => null,
       
   614                                   'Range'                    => null,
       
   615                                   'x-amz-acl'                => null,
       
   616                                   'x-amz-copy-source'        => null,
       
   617                                   'x-amz-metadata-directive' => null));
       
   618 
       
   619         $client->setHeaders($headers);
       
   620 
       
   621         if (is_array($params)) {
       
   622             foreach ($params as $name=>$value) {
       
   623                 $client->setParameterGet($name, $value);
       
   624             }
       
   625          }
       
   626 
       
   627          if (($method == 'PUT') && ($data !== null)) {
       
   628              if (!isset($headers['Content-type'])) {
       
   629                  $headers['Content-type'] = self::getMimeType($path);
       
   630              }
       
   631              $client->setRawData($data, $headers['Content-type']);
       
   632          }
       
   633          do {
       
   634             $retry = false;
       
   635 
       
   636             $response = $client->request($method);
       
   637             $response_code = $response->getStatus();
       
   638 
       
   639             // Some 5xx errors are expected, so retry automatically
       
   640             if ($response_code >= 500 && $response_code < 600 && $retry_count <= 5) {
       
   641                 $retry = true;
       
   642                 $retry_count++;
       
   643                 sleep($retry_count / 4 * $retry_count);
       
   644             }
       
   645             else if ($response_code == 307) {
       
   646                 // Need to redirect, new S3 endpoint given
       
   647                 // This should never happen as Zend_Http_Client will redirect automatically
       
   648             }
       
   649             else if ($response_code == 100) {
       
   650                 // echo 'OK to Continue';
       
   651             }
       
   652         } while ($retry);
       
   653 
       
   654         return $response;
       
   655     }
       
   656 
       
   657     /**
       
   658      * Add the S3 Authorization signature to the request headers
       
   659      *
       
   660      * @param  string $method
       
   661      * @param  string $path
       
   662      * @param  array &$headers
       
   663      * @return string
       
   664      */
       
   665     protected function addSignature($method, $path, &$headers)
       
   666     {
       
   667         if (!is_array($headers)) {
       
   668             $headers = array($headers);
       
   669         }
       
   670 
       
   671         $type = $md5 = $date = '';
       
   672 
       
   673         // Search for the Content-type, Content-MD5 and Date headers
       
   674         foreach ($headers as $key=>$val) {
       
   675             if (strcasecmp($key, 'content-type') == 0) {
       
   676                 $type = $val;
       
   677             }
       
   678             else if (strcasecmp($key, 'content-md5') == 0) {
       
   679                 $md5 = $val;
       
   680             }
       
   681             else if (strcasecmp($key, 'date') == 0) {
       
   682                 $date = $val;
       
   683             }
       
   684         }
       
   685 
       
   686         // If we have an x-amz-date header, use that instead of the normal Date
       
   687         if (isset($headers['x-amz-date']) && isset($date)) {
       
   688             $date = '';
       
   689         }
       
   690 
       
   691         $sig_str = "$method\n$md5\n$type\n$date\n";
       
   692         // For x-amz- headers, combine like keys, lowercase them, sort them
       
   693         // alphabetically and remove excess spaces around values
       
   694         $amz_headers = array();
       
   695         foreach ($headers as $key=>$val) {
       
   696             $key = strtolower($key);
       
   697             if (substr($key, 0, 6) == 'x-amz-') {
       
   698                 if (is_array($val)) {
       
   699                     $amz_headers[$key] = $val;
       
   700                 }
       
   701                 else {
       
   702                     $amz_headers[$key][] = preg_replace('/\s+/', ' ', $val);
       
   703                 }
       
   704             }
       
   705         }
       
   706         if (!empty($amz_headers)) {
       
   707             ksort($amz_headers);
       
   708             foreach ($amz_headers as $key=>$val) {
       
   709                 $sig_str .= $key.':'.implode(',', $val)."\n";
       
   710             }
       
   711         }
       
   712 
       
   713         $sig_str .= '/'.parse_url($path, PHP_URL_PATH);
       
   714         if (strpos($path, '?location') !== false) {
       
   715             $sig_str .= '?location';
       
   716         }
       
   717         else if (strpos($path, '?acl') !== false) {
       
   718             $sig_str .= '?acl';
       
   719         }
       
   720         else if (strpos($path, '?torrent') !== false) {
       
   721             $sig_str .= '?torrent';
       
   722         }
       
   723 
       
   724         $signature = base64_encode(Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'sha1', utf8_encode($sig_str), Zend_Crypt_Hmac::BINARY));
       
   725         $headers['Authorization'] = 'AWS '.$this->_getAccessKey().':'.$signature;
       
   726 
       
   727         return $sig_str;
       
   728     }
       
   729 
       
   730     /**
       
   731      * Attempt to get the content-type of a file based on the extension
       
   732      *
       
   733      * @param  string $path
       
   734      * @return string
       
   735      */
       
   736     public static function getMimeType($path)
       
   737     {
       
   738         $ext = substr(strrchr($path, '.'), 1);
       
   739 
       
   740         if(!$ext) {
       
   741             // shortcut
       
   742             return 'binary/octet-stream';
       
   743         }
       
   744 
       
   745         switch (strtolower($ext)) {
       
   746             case 'xls':
       
   747                 $content_type = 'application/excel';
       
   748                 break;
       
   749             case 'hqx':
       
   750                 $content_type = 'application/macbinhex40';
       
   751                 break;
       
   752             case 'doc':
       
   753             case 'dot':
       
   754             case 'wrd':
       
   755                 $content_type = 'application/msword';
       
   756                 break;
       
   757             case 'pdf':
       
   758                 $content_type = 'application/pdf';
       
   759                 break;
       
   760             case 'pgp':
       
   761                 $content_type = 'application/pgp';
       
   762                 break;
       
   763             case 'ps':
       
   764             case 'eps':
       
   765             case 'ai':
       
   766                 $content_type = 'application/postscript';
       
   767                 break;
       
   768             case 'ppt':
       
   769                 $content_type = 'application/powerpoint';
       
   770                 break;
       
   771             case 'rtf':
       
   772                 $content_type = 'application/rtf';
       
   773                 break;
       
   774             case 'tgz':
       
   775             case 'gtar':
       
   776                 $content_type = 'application/x-gtar';
       
   777                 break;
       
   778             case 'gz':
       
   779                 $content_type = 'application/x-gzip';
       
   780                 break;
       
   781             case 'php':
       
   782             case 'php3':
       
   783             case 'php4':
       
   784                 $content_type = 'application/x-httpd-php';
       
   785                 break;
       
   786             case 'js':
       
   787                 $content_type = 'application/x-javascript';
       
   788                 break;
       
   789             case 'ppd':
       
   790             case 'psd':
       
   791                 $content_type = 'application/x-photoshop';
       
   792                 break;
       
   793             case 'swf':
       
   794             case 'swc':
       
   795             case 'rf':
       
   796                 $content_type = 'application/x-shockwave-flash';
       
   797                 break;
       
   798             case 'tar':
       
   799                 $content_type = 'application/x-tar';
       
   800                 break;
       
   801             case 'zip':
       
   802                 $content_type = 'application/zip';
       
   803                 break;
       
   804             case 'mid':
       
   805             case 'midi':
       
   806             case 'kar':
       
   807                 $content_type = 'audio/midi';
       
   808                 break;
       
   809             case 'mp2':
       
   810             case 'mp3':
       
   811             case 'mpga':
       
   812                 $content_type = 'audio/mpeg';
       
   813                 break;
       
   814             case 'ra':
       
   815                 $content_type = 'audio/x-realaudio';
       
   816                 break;
       
   817             case 'wav':
       
   818                 $content_type = 'audio/wav';
       
   819                 break;
       
   820             case 'bmp':
       
   821                 $content_type = 'image/bitmap';
       
   822                 break;
       
   823             case 'gif':
       
   824                 $content_type = 'image/gif';
       
   825                 break;
       
   826             case 'iff':
       
   827                 $content_type = 'image/iff';
       
   828                 break;
       
   829             case 'jb2':
       
   830                 $content_type = 'image/jb2';
       
   831                 break;
       
   832             case 'jpg':
       
   833             case 'jpe':
       
   834             case 'jpeg':
       
   835                 $content_type = 'image/jpeg';
       
   836                 break;
       
   837             case 'jpx':
       
   838                 $content_type = 'image/jpx';
       
   839                 break;
       
   840             case 'png':
       
   841                 $content_type = 'image/png';
       
   842                 break;
       
   843             case 'tif':
       
   844             case 'tiff':
       
   845                 $content_type = 'image/tiff';
       
   846                 break;
       
   847             case 'wbmp':
       
   848                 $content_type = 'image/vnd.wap.wbmp';
       
   849                 break;
       
   850             case 'xbm':
       
   851                 $content_type = 'image/xbm';
       
   852                 break;
       
   853             case 'css':
       
   854                 $content_type = 'text/css';
       
   855                 break;
       
   856             case 'txt':
       
   857                 $content_type = 'text/plain';
       
   858                 break;
       
   859             case 'htm':
       
   860             case 'html':
       
   861                 $content_type = 'text/html';
       
   862                 break;
       
   863             case 'xml':
       
   864                 $content_type = 'text/xml';
       
   865                 break;
       
   866             case 'xsl':
       
   867                 $content_type = 'text/xsl';
       
   868                 break;
       
   869             case 'mpg':
       
   870             case 'mpe':
       
   871             case 'mpeg':
       
   872                 $content_type = 'video/mpeg';
       
   873                 break;
       
   874             case 'qt':
       
   875             case 'mov':
       
   876                 $content_type = 'video/quicktime';
       
   877                 break;
       
   878             case 'avi':
       
   879                 $content_type = 'video/x-ms-video';
       
   880                 break;
       
   881             case 'eml':
       
   882                 $content_type = 'message/rfc822';
       
   883                 break;
       
   884             default:
       
   885                 $content_type = 'binary/octet-stream';
       
   886                 break;
       
   887         }
       
   888 
       
   889         return $content_type;
       
   890     }
       
   891 
       
   892     /**
       
   893      * Register this object as stream wrapper client
       
   894      *
       
   895      * @param  string $name
       
   896      * @return Zend_Service_Amazon_S3
       
   897      */
       
   898     public function registerAsClient($name)
       
   899     {
       
   900         self::$_wrapperClients[$name] = $this;
       
   901         return $this;
       
   902     }
       
   903 
       
   904     /**
       
   905      * Unregister this object as stream wrapper client
       
   906      *
       
   907      * @param  string $name
       
   908      * @return Zend_Service_Amazon_S3
       
   909      */
       
   910     public function unregisterAsClient($name)
       
   911     {
       
   912         unset(self::$_wrapperClients[$name]);
       
   913         return $this;
       
   914     }
       
   915 
       
   916     /**
       
   917      * Get wrapper client for stream type
       
   918      *
       
   919      * @param  string $name
       
   920      * @return Zend_Service_Amazon_S3
       
   921      */
       
   922     public static function getWrapperClient($name)
       
   923     {
       
   924         return self::$_wrapperClients[$name];
       
   925     }
       
   926 
       
   927     /**
       
   928      * Register this object as stream wrapper
       
   929      *
       
   930      * @param  string $name
       
   931      * @return Zend_Service_Amazon_S3
       
   932      */
       
   933     public function registerStreamWrapper($name='s3')
       
   934     {
       
   935         /**
       
   936          * @see Zend_Service_Amazon_S3_Stream
       
   937          */
       
   938         require_once 'Zend/Service/Amazon/S3/Stream.php';
       
   939 
       
   940         stream_register_wrapper($name, 'Zend_Service_Amazon_S3_Stream');
       
   941         $this->registerAsClient($name);
       
   942     }
       
   943 
       
   944     /**
       
   945      * Unregister this object as stream wrapper
       
   946      *
       
   947      * @param  string $name
       
   948      * @return Zend_Service_Amazon_S3
       
   949      */
       
   950     public function unregisterStreamWrapper($name='s3')
       
   951     {
       
   952         stream_wrapper_unregister($name);
       
   953         $this->unregisterAsClient($name);
       
   954     }
       
   955 }