File indexing completed on 2024-12-22 05:37:02

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Service
0017  * @subpackage Amazon_S3
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * @see Zend_Service_Amazon_Abstract
0025  */
0026 // require_once 'Zend/Service/Amazon/Abstract.php';
0027 
0028 /**
0029  * @see Zend_Crypt_Hmac
0030  */
0031 // require_once 'Zend/Crypt/Hmac.php';
0032 
0033 /**
0034  * Amazon S3 PHP connection class
0035  *
0036  * @category   Zend
0037  * @package    Zend_Service
0038  * @subpackage Amazon_S3
0039  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0040  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0041  * @see        http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
0042  */
0043 class Zend_Service_Amazon_S3 extends Zend_Service_Amazon_Abstract
0044 {
0045     /**
0046      * Store for stream wrapper clients
0047      *
0048      * @var array
0049      */
0050     protected static $_wrapperClients = array();
0051 
0052     /**
0053      * Endpoint for the service
0054      *
0055      * @var Zend_Uri_Http
0056      */
0057     protected $_endpoint;
0058 
0059     const S3_ENDPOINT = 's3.amazonaws.com';
0060 
0061     const S3_ACL_PRIVATE = 'private';
0062     const S3_ACL_PUBLIC_READ = 'public-read';
0063     const S3_ACL_PUBLIC_WRITE = 'public-read-write';
0064     const S3_ACL_AUTH_READ = 'authenticated-read';
0065 
0066     const S3_REQUESTPAY_HEADER = 'x-amz-request-payer';
0067     const S3_ACL_HEADER = 'x-amz-acl';
0068     const S3_CONTENT_TYPE_HEADER = 'Content-Type';
0069 
0070     /**
0071      * Set S3 endpoint to use
0072      *
0073      * @param string|Zend_Uri_Http $endpoint
0074      * @return Zend_Service_Amazon_S3
0075      */
0076     public function setEndpoint($endpoint)
0077     {
0078         if (!($endpoint instanceof Zend_Uri_Http)) {
0079             $endpoint = Zend_Uri::factory($endpoint);
0080         }
0081         if (!$endpoint->valid()) {
0082             /**
0083              * @see Zend_Service_Amazon_S3_Exception
0084              */
0085             // require_once 'Zend/Service/Amazon/S3/Exception.php';
0086             throw new Zend_Service_Amazon_S3_Exception('Invalid endpoint supplied');
0087         }
0088         $this->_endpoint = $endpoint;
0089         return $this;
0090     }
0091 
0092     /**
0093      * Get current S3 endpoint
0094      *
0095      * @return Zend_Uri_Http
0096      */
0097     public function getEndpoint()
0098     {
0099         return $this->_endpoint;
0100     }
0101 
0102     /**
0103      * Constructor
0104      *
0105      * @param string $accessKey
0106      * @param string $secretKey
0107      * @param string $region
0108      */
0109     public function __construct($accessKey=null, $secretKey=null, $region=null)
0110     {
0111         parent::__construct($accessKey, $secretKey, $region);
0112 
0113         $this->setEndpoint('http://'.self::S3_ENDPOINT);
0114     }
0115 
0116     /**
0117      * Verify if the bucket name is valid
0118      *
0119      * @param string $bucket
0120      * @return boolean
0121      */
0122     public function _validBucketName($bucket)
0123     {
0124         $len = strlen($bucket);
0125         if ($len < 3 || $len > 255) {
0126             /**
0127              * @see Zend_Service_Amazon_S3_Exception
0128              */
0129             // require_once 'Zend/Service/Amazon/S3/Exception.php';
0130             throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" must be between 3 and 255 characters long");
0131         }
0132 
0133         if (preg_match('/[^a-z0-9\._-]/', $bucket)) {
0134             /**
0135              * @see Zend_Service_Amazon_S3_Exception
0136              */
0137             // require_once 'Zend/Service/Amazon/S3/Exception.php';
0138             throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" contains invalid characters");
0139         }
0140 
0141         if (preg_match('/(\d){1,3}\.(\d){1,3}\.(\d){1,3}\.(\d){1,3}/', $bucket)) {
0142             /**
0143              * @see Zend_Service_Amazon_S3_Exception
0144              */
0145             // require_once 'Zend/Service/Amazon/S3/Exception.php';
0146             throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" cannot be an IP address");
0147         }
0148         return true;
0149     }
0150 
0151     /**
0152      * Add a new bucket
0153      *
0154      * @param  string $bucket
0155      * @return boolean
0156      */
0157     public function createBucket($bucket, $location = null)
0158     {
0159         $this->_validBucketName($bucket);
0160         $headers=array();
0161         if($location) {
0162             $data = '<CreateBucketConfiguration><LocationConstraint>'.$location.'</LocationConstraint></CreateBucketConfiguration>';
0163             $headers[self::S3_CONTENT_TYPE_HEADER]= 'text/plain';
0164             $headers['Content-size']= strlen($data);
0165         } else {
0166             $data = null;
0167         }
0168         $response = $this->_makeRequest('PUT', $bucket, null, $headers, $data);
0169 
0170         return ($response->getStatus() == 200);
0171     }
0172 
0173     /**
0174      * Checks if a given bucket name is available
0175      *
0176      * @param  string $bucket
0177      * @return boolean
0178      */
0179     public function isBucketAvailable($bucket)
0180     {
0181         $response = $this->_makeRequest('HEAD', $bucket, array('max-keys'=>0));
0182 
0183         return ($response->getStatus() != 404);
0184     }
0185 
0186     /**
0187      * Checks if a given object exists
0188      *
0189      * @param  string $object
0190      * @return boolean
0191      */
0192     public function isObjectAvailable($object)
0193     {
0194         $object = $this->_fixupObjectName($object);
0195         $response = $this->_makeRequest('HEAD', $object);
0196 
0197         return ($response->getStatus() == 200);
0198     }
0199 
0200     /**
0201      * Remove a given bucket. All objects in the bucket must be removed prior
0202      * to removing the bucket.
0203      *
0204      * @param  string $bucket
0205      * @return boolean
0206      */
0207     public function removeBucket($bucket)
0208     {
0209         $response = $this->_makeRequest('DELETE', $bucket);
0210 
0211         // Look for a 204 No Content response
0212         return ($response->getStatus() == 204);
0213     }
0214 
0215     /**
0216      * Get metadata information for a given object
0217      *
0218      * @param  string $object
0219      * @return array|false
0220      */
0221     public function getInfo($object)
0222     {
0223         $info = array();
0224 
0225         $object = $this->_fixupObjectName($object);
0226         $response = $this->_makeRequest('HEAD', $object);
0227 
0228         if ($response->getStatus() == 200) {
0229             $info['type'] = $response->getHeader('Content-type');
0230             $info['size'] = $response->getHeader('Content-length');
0231             $info['mtime'] = strtotime($response->getHeader('Last-modified'));
0232             $info['etag'] = $response->getHeader('ETag');
0233         }
0234         else {
0235             return false;
0236         }
0237 
0238         return $info;
0239     }
0240 
0241     /**
0242      * List the S3 buckets
0243      *
0244      * @return array|false
0245      */
0246     public function getBuckets()
0247     {
0248         $response = $this->_makeRequest('GET');
0249 
0250         if ($response->getStatus() != 200) {
0251             return false;
0252         }
0253 
0254         $xml = new SimpleXMLElement($response->getBody());
0255 
0256         $buckets = array();
0257         foreach ($xml->Buckets->Bucket as $bucket) {
0258             $buckets[] = (string)$bucket->Name;
0259         }
0260 
0261         return $buckets;
0262     }
0263 
0264     /**
0265      * Remove all objects in the bucket.
0266      *
0267      * @param string $bucket
0268      * @return boolean
0269      */
0270     public function cleanBucket($bucket)
0271     {
0272         $objects = $this->getObjectsByBucket($bucket);
0273         if (!$objects) {
0274             return false;
0275         }
0276 
0277         while (!empty($objects)) {
0278             foreach ($objects as $object) {
0279                 $this->removeObject("$bucket/$object");
0280             }
0281             $params= array (
0282                 'marker' => $objects[count($objects)-1]
0283             );
0284             $objects = $this->getObjectsByBucket($bucket,$params);
0285         }
0286         
0287         return true;
0288     }
0289 
0290     /**
0291      * List the objects in a bucket.
0292      *
0293      * Provides the list of object keys that are contained in the bucket.  Valid params include the following.
0294      * 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.
0295      * 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.
0296      * 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.
0297      * 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.
0298      *
0299      * @param  string $bucket
0300      * @param array $params S3 GET Bucket Paramater
0301      * @return array|false
0302      */
0303     public function getObjectsByBucket($bucket, $params = array())
0304     {
0305         $response = $this->_makeRequest('GET', $bucket, $params);
0306 
0307         if ($response->getStatus() != 200) {
0308             return false;
0309         }
0310 
0311         $xml = new SimpleXMLElement($response->getBody());
0312 
0313         $objects = array();
0314         if (isset($xml->Contents)) {
0315             foreach ($xml->Contents as $contents) {
0316                 foreach ($contents->Key as $object) {
0317                     $objects[] = (string)$object;
0318                 }
0319             }
0320         }
0321 
0322         return $objects;
0323     }
0324      /**
0325      * List the objects and common prefixes in a bucket.
0326      *
0327      * Provides the list of object keys and common prefixes that are contained in the bucket.  Valid params include the following.
0328      * 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.
0329      * 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.
0330      * 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.
0331      * 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.
0332      *
0333      * @see ZF-11401
0334      * @param  string $bucket
0335      * @param array $params S3 GET Bucket Paramater
0336      * @return array|false
0337      */
0338     public function getObjectsAndPrefixesByBucket($bucket, $params = array())
0339     {
0340         $response = $this->_makeRequest('GET', $bucket, $params);
0341 
0342         if ($response->getStatus() != 200) {
0343             return false;
0344         }
0345 
0346         $xml = new SimpleXMLElement($response->getBody());
0347 
0348         $objects = array();
0349         if (isset($xml->Contents)) {
0350             foreach ($xml->Contents as $contents) {
0351                 foreach ($contents->Key as $object) {
0352                     $objects[] = (string)$object;
0353                 }
0354             }
0355         }
0356         $prefixes = array();
0357         if (isset($xml->CommonPrefixes)) {
0358             foreach ($xml->CommonPrefixes as $prefix) {
0359                 foreach ($prefix->Prefix as $object) {
0360                     $prefixes[] = (string)$object;
0361                 }
0362             }
0363         }
0364 
0365         return array(
0366             'objects'  => $objects,
0367             'prefixes' => $prefixes
0368         );
0369     }
0370     /**
0371      * Make sure the object name is valid
0372      *
0373      * @param  string $object
0374      * @return string
0375      */
0376     protected function _fixupObjectName($object)
0377     {
0378         $nameparts = explode('/', $object);
0379 
0380         $this->_validBucketName($nameparts[0]);
0381 
0382         $firstpart = array_shift($nameparts);
0383         if (count($nameparts) == 0) {
0384             return $firstpart;
0385         }
0386 
0387         return $firstpart.'/'.join('/', array_map('rawurlencode', $nameparts));
0388     }
0389 
0390     /**
0391      * Get an object
0392      *
0393      * @param  string $object
0394      * @param  bool   $paidobject This is "requestor pays" object
0395      * @return string|false
0396      */
0397     public function getObject($object, $paidobject=false)
0398     {
0399         $object = $this->_fixupObjectName($object);
0400         if ($paidobject) {
0401             $response = $this->_makeRequest('GET', $object, null, array(self::S3_REQUESTPAY_HEADER => 'requester'));
0402         }
0403         else {
0404             $response = $this->_makeRequest('GET', $object);
0405         }
0406 
0407         if ($response->getStatus() != 200) {
0408             return false;
0409         }
0410 
0411         return $response->getBody();
0412     }
0413 
0414     /**
0415      * Get an object using streaming
0416      *
0417      * Can use either provided filename for storage or create a temp file if none provided.
0418      *
0419      * @param  string $object Object path
0420      * @param  string $streamfile File to write the stream to
0421      * @param  bool   $paidobject This is "requestor pays" object
0422      * @return Zend_Http_Response_Stream|false
0423      */
0424     public function getObjectStream($object, $streamfile = null, $paidobject=false)
0425     {
0426         $object = $this->_fixupObjectName($object);
0427         self::getHttpClient()->setStream($streamfile?$streamfile:true);
0428         if ($paidobject) {
0429             $response = $this->_makeRequest('GET', $object, null, array(self::S3_REQUESTPAY_HEADER => 'requester'));
0430         }
0431         else {
0432             $response = $this->_makeRequest('GET', $object);
0433         }
0434         self::getHttpClient()->setStream(null);
0435 
0436         if ($response->getStatus() != 200 || !($response instanceof Zend_Http_Response_Stream)) {
0437             return false;
0438         }
0439 
0440         return $response;
0441     }
0442 
0443     /**
0444      * Upload an object by a PHP string
0445      *
0446      * @param  string $object Object name
0447      * @param  string|resource $data   Object data (can be string or stream)
0448      * @param  array  $meta   Metadata
0449      * @return boolean
0450      */
0451     public function putObject($object, $data, $meta=null)
0452     {
0453         $object = $this->_fixupObjectName($object);
0454         $headers = (is_array($meta)) ? $meta : array();
0455 
0456         if(!is_resource($data)) {
0457             $headers['Content-MD5'] = base64_encode(md5($data, true));
0458         }
0459         $headers['Expect'] = '100-continue';
0460 
0461         if (!isset($headers[self::S3_CONTENT_TYPE_HEADER])) {
0462             $headers[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($object);
0463         }
0464 
0465         $response = $this->_makeRequest('PUT', $object, null, $headers, $data);
0466 
0467         // Check the MD5 Etag returned by S3 against and MD5 of the buffer
0468         if ($response->getStatus() == 200) {
0469             // It is escaped by double quotes for some reason
0470             $etag = str_replace('"', '', $response->getHeader('Etag'));
0471 
0472             if (is_resource($data) || $etag == md5($data)) {
0473                 return true;
0474             }
0475         }
0476 
0477         return false;
0478     }
0479 
0480     /**
0481      * Put file to S3 as object
0482      *
0483      * @param string $path   File name
0484      * @param string $object Object name
0485      * @param array  $meta   Metadata
0486      * @return boolean
0487      */
0488     public function putFile($path, $object, $meta=null)
0489     {
0490         $data = @file_get_contents($path);
0491         if ($data === false) {
0492             /**
0493              * @see Zend_Service_Amazon_S3_Exception
0494              */
0495             // require_once 'Zend/Service/Amazon/S3/Exception.php';
0496             throw new Zend_Service_Amazon_S3_Exception("Cannot read file $path");
0497         }
0498 
0499         if (!is_array($meta)) {
0500             $meta = array();
0501         }
0502 
0503         if (!isset($meta[self::S3_CONTENT_TYPE_HEADER])) {
0504            $meta[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($path);
0505         }
0506 
0507         return $this->putObject($object, $data, $meta);
0508     }
0509 
0510     /**
0511      * Put file to S3 as object, using streaming
0512      *
0513      * @param string $path   File name
0514      * @param string $object Object name
0515      * @param array  $meta   Metadata
0516      * @return boolean
0517      */
0518     public function putFileStream($path, $object, $meta=null)
0519     {
0520         $data = @fopen($path, "rb");
0521         if ($data === false) {
0522             /**
0523              * @see Zend_Service_Amazon_S3_Exception
0524              */
0525             // require_once 'Zend/Service/Amazon/S3/Exception.php';
0526             throw new Zend_Service_Amazon_S3_Exception("Cannot open file $path");
0527         }
0528 
0529         if (!is_array($meta)) {
0530             $meta = array();
0531         }
0532 
0533         if (!isset($meta[self::S3_CONTENT_TYPE_HEADER])) {
0534            $meta[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($path);
0535         }
0536 
0537         if(!isset($meta['Content-MD5'])) {
0538             $meta['Content-MD5'] = base64_encode(md5_file($path, true));
0539         }
0540 
0541         return $this->putObject($object, $data, $meta);
0542     }
0543 
0544     /**
0545      * Remove a given object
0546      *
0547      * @param  string $object
0548      * @return boolean
0549      */
0550     public function removeObject($object)
0551     {
0552         $object = $this->_fixupObjectName($object);
0553         $response = $this->_makeRequest('DELETE', $object);
0554 
0555         // Look for a 204 No Content response
0556         return ($response->getStatus() == 204);
0557     }
0558 
0559     /**
0560      * Copy an object
0561      *
0562      * @param  string $sourceObject  Source object name
0563      * @param  string $destObject    Destination object name
0564      * @param  array  $meta          (OPTIONAL) Metadata to apply to desination object.
0565      *                               Set to null to copy metadata from source object.
0566      * @return boolean
0567      */
0568     public function copyObject($sourceObject, $destObject, $meta = null)
0569     {
0570         $sourceObject = $this->_fixupObjectName($sourceObject);
0571         $destObject   = $this->_fixupObjectName($destObject);
0572 
0573         $headers = (is_array($meta)) ? $meta : array();
0574         $headers['x-amz-copy-source'] = $sourceObject;
0575         $headers['x-amz-metadata-directive'] = $meta === null ? 'COPY' : 'REPLACE';
0576 
0577         $response = $this->_makeRequest('PUT', $destObject, null, $headers);
0578 
0579         if ($response->getStatus() == 200 && !stristr($response->getBody(), '<Error>')) {
0580             return true;
0581         }
0582 
0583         return false;
0584     }
0585 
0586     /**
0587      * Move an object
0588      *
0589      * Performs a copy to dest + verify + remove source
0590      *
0591      * @param string $sourceObject  Source object name
0592      * @param string $destObject    Destination object name
0593      * @param array  $meta          (OPTIONAL) Metadata to apply to destination object.
0594      *                              Set to null to retain existing metadata.
0595      */
0596     public function moveObject($sourceObject, $destObject, $meta = null)
0597     {
0598         $sourceInfo = $this->getInfo($sourceObject);
0599 
0600         $this->copyObject($sourceObject, $destObject, $meta);
0601         $destInfo = $this->getInfo($destObject);
0602 
0603         if ($sourceInfo['etag'] === $destInfo['etag']) {
0604             return $this->removeObject($sourceObject);
0605         } else {
0606             return false;
0607         }
0608     }
0609 
0610     /**
0611      * Make a request to Amazon S3
0612      *
0613      * @param  string $method    Request method
0614      * @param  string $path        Path to requested object
0615      * @param  array  $params    Request parameters
0616      * @param  array  $headers    HTTP headers
0617      * @param  string|resource $data        Request data
0618      * @return Zend_Http_Response
0619      */
0620     public function _makeRequest($method, $path='', $params=null, $headers=array(), $data=null)
0621     {
0622         $retry_count = 0;
0623 
0624         if (!is_array($headers)) {
0625             $headers = array($headers);
0626         }
0627 
0628         $headers['Date'] = gmdate(DATE_RFC1123, time());
0629 
0630         if(is_resource($data) && $method != 'PUT') {
0631             /**
0632              * @see Zend_Service_Amazon_S3_Exception
0633              */
0634             // require_once 'Zend/Service/Amazon/S3/Exception.php';
0635             throw new Zend_Service_Amazon_S3_Exception("Only PUT request supports stream data");
0636         }
0637 
0638         // build the end point out
0639         $parts = explode('/', $path, 2);
0640         $endpoint = clone($this->_endpoint);
0641         if ($parts[0]) {
0642             // prepend bucket name to the hostname
0643             $endpoint->setHost($parts[0].'.'.$endpoint->getHost());
0644         }
0645         if (!empty($parts[1])) {
0646             // ZF-10218, ZF-10122
0647             $pathparts = explode('?',$parts[1]);
0648             $endpath = $pathparts[0];
0649             $endpoint->setPath('/'.$endpath);
0650             
0651         }
0652         else {
0653             $endpoint->setPath('/');
0654             if ($parts[0]) {
0655                 $path = $parts[0].'/';
0656             }
0657         }
0658         self::addSignature($method, $path, $headers);
0659 
0660         $client = self::getHttpClient();
0661 
0662         $client->resetParameters(true);
0663         $client->setUri($endpoint);
0664         $client->setAuth(false);
0665         // Work around buglet in HTTP client - it doesn't clean headers
0666         // Remove when ZHC is fixed
0667         /*
0668         $client->setHeaders(array('Content-MD5'              => null,
0669                                   'Content-Encoding'         => null,
0670                                   'Expect'                   => null,
0671                                   'Range'                    => null,
0672                                   'x-amz-acl'                => null,
0673                                   'x-amz-copy-source'        => null,
0674                                   'x-amz-metadata-directive' => null));
0675         */
0676         $client->setHeaders($headers);
0677 
0678         if (is_array($params)) {
0679             foreach ($params as $name=>$value) {
0680                 $client->setParameterGet($name, $value);
0681             }
0682          }
0683 
0684          if (($method == 'PUT') && ($data !== null)) {
0685              if (!isset($headers['Content-type'])) {
0686                  $headers['Content-type'] = self::getMimeType($path);
0687              }
0688              $client->setRawData($data, $headers['Content-type']);
0689          } 
0690          do {
0691             $retry = false;
0692 
0693             $response = $client->request($method);
0694             $response_code = $response->getStatus();
0695 
0696             // Some 5xx errors are expected, so retry automatically
0697             if ($response_code >= 500 && $response_code < 600 && $retry_count <= 5) {
0698                 $retry = true;
0699                 $retry_count++;
0700                 sleep($retry_count / 4 * $retry_count);
0701             }
0702             else if ($response_code == 307) {
0703                 // Need to redirect, new S3 endpoint given
0704                 // This should never happen as Zend_Http_Client will redirect automatically
0705             }
0706             else if ($response_code == 100) {
0707                 // echo 'OK to Continue';
0708             }
0709         } while ($retry);
0710 
0711         return $response;
0712     }
0713 
0714     /**
0715      * Add the S3 Authorization signature to the request headers
0716      *
0717      * @param  string $method
0718      * @param  string $path
0719      * @param  array &$headers
0720      * @return string
0721      */
0722     protected function addSignature($method, $path, &$headers)
0723     {
0724         if (!is_array($headers)) {
0725             $headers = array($headers);
0726         }
0727 
0728         $type = $md5 = $date = '';
0729 
0730         // Search for the Content-type, Content-MD5 and Date headers
0731         foreach ($headers as $key=>$val) {
0732             if (strcasecmp($key, 'content-type') == 0) {
0733                 $type = $val;
0734             }
0735             else if (strcasecmp($key, 'content-md5') == 0) {
0736                 $md5 = $val;
0737             }
0738             else if (strcasecmp($key, 'date') == 0) {
0739                 $date = $val;
0740             }
0741         }
0742 
0743         // If we have an x-amz-date header, use that instead of the normal Date
0744         if (isset($headers['x-amz-date']) && isset($date)) {
0745             $date = '';
0746         }
0747 
0748         $sig_str = "$method\n$md5\n$type\n$date\n";
0749         // For x-amz- headers, combine like keys, lowercase them, sort them
0750         // alphabetically and remove excess spaces around values
0751         $amz_headers = array();
0752         foreach ($headers as $key=>$val) {
0753             $key = strtolower($key);
0754             if (substr($key, 0, 6) == 'x-amz-') {
0755                 if (is_array($val)) {
0756                     $amz_headers[$key] = $val;
0757                 }
0758                 else {
0759                     $amz_headers[$key][] = preg_replace('/\s+/', ' ', $val);
0760                 }
0761             }
0762         }
0763         if (!empty($amz_headers)) {
0764             ksort($amz_headers);
0765             foreach ($amz_headers as $key=>$val) {
0766                 $sig_str .= $key.':'.implode(',', $val)."\n";
0767             }
0768         }
0769 
0770         $sig_str .= '/'.parse_url($path, PHP_URL_PATH);
0771         if (strpos($path, '?location') !== false) {
0772             $sig_str .= '?location';
0773         }
0774         else if (strpos($path, '?acl') !== false) {
0775             $sig_str .= '?acl';
0776         }
0777         else if (strpos($path, '?torrent') !== false) {
0778             $sig_str .= '?torrent';
0779         }
0780         else if (strpos($path, '?versions') !== false) {
0781             $sig_str .= '?versions';
0782         }
0783 
0784         $signature = base64_encode(Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'sha1', utf8_encode($sig_str), Zend_Crypt_Hmac::BINARY));
0785         $headers['Authorization'] = 'AWS '.$this->_getAccessKey().':'.$signature;
0786 
0787         return $sig_str;
0788     }
0789 
0790     /**
0791      * Attempt to get the content-type of a file based on the extension
0792      *
0793      * @param  string $path
0794      * @return string
0795      */
0796     public static function getMimeType($path)
0797     {
0798         $ext = substr(strrchr($path, '.'), 1);
0799 
0800         if(!$ext) {
0801             // shortcut
0802             return 'binary/octet-stream';
0803         }
0804 
0805         switch (strtolower($ext)) {
0806             case 'xls':
0807                 $content_type = 'application/excel';
0808                 break;
0809             case 'hqx':
0810                 $content_type = 'application/macbinhex40';
0811                 break;
0812             case 'doc':
0813             case 'dot':
0814             case 'wrd':
0815                 $content_type = 'application/msword';
0816                 break;
0817             case 'pdf':
0818                 $content_type = 'application/pdf';
0819                 break;
0820             case 'pgp':
0821                 $content_type = 'application/pgp';
0822                 break;
0823             case 'ps':
0824             case 'eps':
0825             case 'ai':
0826                 $content_type = 'application/postscript';
0827                 break;
0828             case 'ppt':
0829                 $content_type = 'application/powerpoint';
0830                 break;
0831             case 'rtf':
0832                 $content_type = 'application/rtf';
0833                 break;
0834             case 'tgz':
0835             case 'gtar':
0836                 $content_type = 'application/x-gtar';
0837                 break;
0838             case 'gz':
0839                 $content_type = 'application/x-gzip';
0840                 break;
0841             case 'php':
0842             case 'php3':
0843             case 'php4':
0844                 $content_type = 'application/x-httpd-php';
0845                 break;
0846             case 'js':
0847                 $content_type = 'application/x-javascript';
0848                 break;
0849             case 'ppd':
0850             case 'psd':
0851                 $content_type = 'application/x-photoshop';
0852                 break;
0853             case 'swf':
0854             case 'swc':
0855             case 'rf':
0856                 $content_type = 'application/x-shockwave-flash';
0857                 break;
0858             case 'tar':
0859                 $content_type = 'application/x-tar';
0860                 break;
0861             case 'zip':
0862                 $content_type = 'application/zip';
0863                 break;
0864             case 'mid':
0865             case 'midi':
0866             case 'kar':
0867                 $content_type = 'audio/midi';
0868                 break;
0869             case 'mp2':
0870             case 'mp3':
0871             case 'mpga':
0872                 $content_type = 'audio/mpeg';
0873                 break;
0874             case 'ra':
0875                 $content_type = 'audio/x-realaudio';
0876                 break;
0877             case 'wav':
0878                 $content_type = 'audio/wav';
0879                 break;
0880             case 'bmp':
0881                 $content_type = 'image/bitmap';
0882                 break;
0883             case 'gif':
0884                 $content_type = 'image/gif';
0885                 break;
0886             case 'iff':
0887                 $content_type = 'image/iff';
0888                 break;
0889             case 'jb2':
0890                 $content_type = 'image/jb2';
0891                 break;
0892             case 'jpg':
0893             case 'jpe':
0894             case 'jpeg':
0895                 $content_type = 'image/jpeg';
0896                 break;
0897             case 'jpx':
0898                 $content_type = 'image/jpx';
0899                 break;
0900             case 'png':
0901                 $content_type = 'image/png';
0902                 break;
0903             case 'tif':
0904             case 'tiff':
0905                 $content_type = 'image/tiff';
0906                 break;
0907             case 'wbmp':
0908                 $content_type = 'image/vnd.wap.wbmp';
0909                 break;
0910             case 'xbm':
0911                 $content_type = 'image/xbm';
0912                 break;
0913             case 'css':
0914                 $content_type = 'text/css';
0915                 break;
0916             case 'txt':
0917                 $content_type = 'text/plain';
0918                 break;
0919             case 'htm':
0920             case 'html':
0921                 $content_type = 'text/html';
0922                 break;
0923             case 'xml':
0924                 $content_type = 'text/xml';
0925                 break;
0926             case 'xsl':
0927                 $content_type = 'text/xsl';
0928                 break;
0929             case 'mpg':
0930             case 'mpe':
0931             case 'mpeg':
0932                 $content_type = 'video/mpeg';
0933                 break;
0934             case 'qt':
0935             case 'mov':
0936                 $content_type = 'video/quicktime';
0937                 break;
0938             case 'avi':
0939                 $content_type = 'video/x-ms-video';
0940                 break;
0941             case 'eml':
0942                 $content_type = 'message/rfc822';
0943                 break;
0944             default:
0945                 $content_type = 'binary/octet-stream';
0946                 break;
0947         }
0948 
0949         return $content_type;
0950     }
0951 
0952     /**
0953      * Register this object as stream wrapper client
0954      *
0955      * @param  string $name
0956      * @return Zend_Service_Amazon_S3
0957      */
0958     public function registerAsClient($name)
0959     {
0960         self::$_wrapperClients[$name] = $this;
0961         return $this;
0962     }
0963 
0964     /**
0965      * Unregister this object as stream wrapper client
0966      *
0967      * @param  string $name
0968      * @return Zend_Service_Amazon_S3
0969      */
0970     public function unregisterAsClient($name)
0971     {
0972         unset(self::$_wrapperClients[$name]);
0973         return $this;
0974     }
0975 
0976     /**
0977      * Get wrapper client for stream type
0978      *
0979      * @param  string $name
0980      * @return Zend_Service_Amazon_S3
0981      */
0982     public static function getWrapperClient($name)
0983     {
0984         return self::$_wrapperClients[$name];
0985     }
0986 
0987     /**
0988      * Register this object as stream wrapper
0989      *
0990      * @param  string $name
0991      * @return Zend_Service_Amazon_S3
0992      */
0993     public function registerStreamWrapper($name='s3')
0994     {
0995         /**
0996          * @see Zend_Service_Amazon_S3_Stream
0997          */
0998         // require_once 'Zend/Service/Amazon/S3/Stream.php';
0999 
1000         stream_register_wrapper($name, 'Zend_Service_Amazon_S3_Stream');
1001         $this->registerAsClient($name);
1002     }
1003 
1004     /**
1005      * Unregister this object as stream wrapper
1006      *
1007      * @param  string $name
1008      * @return Zend_Service_Amazon_S3
1009      */
1010     public function unregisterStreamWrapper($name='s3')
1011     {
1012         stream_wrapper_unregister($name);
1013         $this->unregisterAsClient($name);
1014     }
1015 }