PageRenderTime 29ms CodeModel.GetById 10ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/Zend/Service/Amazon/S3.php

https://bitbucket.org/simukti/zf1
PHP | 1015 lines | 590 code | 109 blank | 316 comment | 105 complexity | 3fb11fbdc0e7df3cb60f100e617097eb MD5 | raw file
   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-2012 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 24593 2012-01-05 20:35:02Z matthew $
  21 */
  22
  23/**
  24 * @see Zend_Service_Amazon_Abstract
  25 */
  26require_once 'Zend/Service/Amazon/Abstract.php';
  27
  28/**
  29 * @see Zend_Crypt_Hmac
  30 */
  31require_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-2012 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 */
  43class 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        $headers=array();
 161        if($location) {
 162            $data = '<CreateBucketConfiguration><LocationConstraint>'.$location.'</LocationConstraint></CreateBucketConfiguration>';
 163            $headers[self::S3_CONTENT_TYPE_HEADER]= 'text/plain';
 164            $headers['Content-size']= strlen($data);
 165        } else {
 166            $data = null;
 167        }
 168        $response = $this->_makeRequest('PUT', $bucket, null, $headers, $data);
 169
 170        return ($response->getStatus() == 200);
 171    }
 172
 173    /**
 174     * Checks if a given bucket name is available
 175     *
 176     * @param  string $bucket
 177     * @return boolean
 178     */
 179    public function isBucketAvailable($bucket)
 180    {
 181        $response = $this->_makeRequest('HEAD', $bucket, array('max-keys'=>0));
 182
 183        return ($response->getStatus() != 404);
 184    }
 185
 186    /**
 187     * Checks if a given object exists
 188     *
 189     * @param  string $object
 190     * @return boolean
 191     */
 192    public function isObjectAvailable($object)
 193    {
 194        $object = $this->_fixupObjectName($object);
 195        $response = $this->_makeRequest('HEAD', $object);
 196
 197        return ($response->getStatus() == 200);
 198    }
 199
 200    /**
 201     * Remove a given bucket. All objects in the bucket must be removed prior
 202     * to removing the bucket.
 203     *
 204     * @param  string $bucket
 205     * @return boolean
 206     */
 207    public function removeBucket($bucket)
 208    {
 209        $response = $this->_makeRequest('DELETE', $bucket);
 210
 211        // Look for a 204 No Content response
 212        return ($response->getStatus() == 204);
 213    }
 214
 215    /**
 216     * Get metadata information for a given object
 217     *
 218     * @param  string $object
 219     * @return array|false
 220     */
 221    public function getInfo($object)
 222    {
 223        $info = array();
 224
 225        $object = $this->_fixupObjectName($object);
 226        $response = $this->_makeRequest('HEAD', $object);
 227
 228        if ($response->getStatus() == 200) {
 229            $info['type'] = $response->getHeader('Content-type');
 230            $info['size'] = $response->getHeader('Content-length');
 231            $info['mtime'] = strtotime($response->getHeader('Last-modified'));
 232            $info['etag'] = $response->getHeader('ETag');
 233        }
 234        else {
 235            return false;
 236        }
 237
 238        return $info;
 239    }
 240
 241    /**
 242     * List the S3 buckets
 243     *
 244     * @return array|false
 245     */
 246    public function getBuckets()
 247    {
 248        $response = $this->_makeRequest('GET');
 249
 250        if ($response->getStatus() != 200) {
 251            return false;
 252        }
 253
 254        $xml = new SimpleXMLElement($response->getBody());
 255
 256        $buckets = array();
 257        foreach ($xml->Buckets->Bucket as $bucket) {
 258            $buckets[] = (string)$bucket->Name;
 259        }
 260
 261        return $buckets;
 262    }
 263
 264    /**
 265     * Remove all objects in the bucket.
 266     *
 267     * @param string $bucket
 268     * @return boolean
 269     */
 270    public function cleanBucket($bucket)
 271    {
 272        $objects = $this->getObjectsByBucket($bucket);
 273        if (!$objects) {
 274            return false;
 275        }
 276
 277        while (!empty($objects)) {
 278            foreach ($objects as $object) {
 279                $this->removeObject("$bucket/$object");
 280            }
 281            $params= array (
 282                'marker' => $objects[count($objects)-1]
 283            );
 284            $objects = $this->getObjectsByBucket($bucket,$params);
 285        }
 286        
 287        return true;
 288    }
 289
 290    /**
 291     * List the objects in a bucket.
 292     *
 293     * Provides the list of object keys that are contained in the bucket.  Valid params include the following.
 294     * 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.
 295     * 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.
 296     * 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.
 297     * 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.
 298     *
 299     * @param  string $bucket
 300     * @param array $params S3 GET Bucket Paramater
 301     * @return array|false
 302     */
 303    public function getObjectsByBucket($bucket, $params = array())
 304    {
 305        $response = $this->_makeRequest('GET', $bucket, $params);
 306
 307        if ($response->getStatus() != 200) {
 308            return false;
 309        }
 310
 311        $xml = new SimpleXMLElement($response->getBody());
 312
 313        $objects = array();
 314        if (isset($xml->Contents)) {
 315            foreach ($xml->Contents as $contents) {
 316                foreach ($contents->Key as $object) {
 317                    $objects[] = (string)$object;
 318                }
 319            }
 320        }
 321
 322        return $objects;
 323    }
 324     /**
 325     * List the objects and common prefixes in a bucket.
 326     *
 327     * Provides the list of object keys and common prefixes that are contained in the bucket.  Valid params include the following.
 328     * 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.
 329     * 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.
 330     * 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.
 331     * 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.
 332     *
 333     * @see ZF-11401
 334     * @param  string $bucket
 335     * @param array $params S3 GET Bucket Paramater
 336     * @return array|false
 337     */
 338    public function getObjectsAndPrefixesByBucket($bucket, $params = array())
 339    {
 340        $response = $this->_makeRequest('GET', $bucket, $params);
 341
 342        if ($response->getStatus() != 200) {
 343            return false;
 344        }
 345
 346        $xml = new SimpleXMLElement($response->getBody());
 347
 348        $objects = array();
 349        if (isset($xml->Contents)) {
 350            foreach ($xml->Contents as $contents) {
 351                foreach ($contents->Key as $object) {
 352                    $objects[] = (string)$object;
 353                }
 354            }
 355        }
 356        $prefixes = array();
 357        if (isset($xml->CommonPrefixes)) {
 358            foreach ($xml->CommonPrefixes as $prefix) {
 359                foreach ($prefix->Prefix as $object) {
 360                    $prefixes[] = (string)$object;
 361                }
 362            }
 363        }
 364
 365        return array(
 366            'objects'  => $objects,
 367            'prefixes' => $prefixes
 368        );
 369    }
 370    /**
 371     * Make sure the object name is valid
 372     *
 373     * @param  string $object
 374     * @return string
 375     */
 376    protected function _fixupObjectName($object)
 377    {
 378        $nameparts = explode('/', $object);
 379
 380        $this->_validBucketName($nameparts[0]);
 381
 382        $firstpart = array_shift($nameparts);
 383        if (count($nameparts) == 0) {
 384            return $firstpart;
 385        }
 386
 387        return $firstpart.'/'.join('/', array_map('rawurlencode', $nameparts));
 388    }
 389
 390    /**
 391     * Get an object
 392     *
 393     * @param  string $object
 394     * @param  bool   $paidobject This is "requestor pays" object
 395     * @return string|false
 396     */
 397    public function getObject($object, $paidobject=false)
 398    {
 399        $object = $this->_fixupObjectName($object);
 400        if ($paidobject) {
 401            $response = $this->_makeRequest('GET', $object, null, array(self::S3_REQUESTPAY_HEADER => 'requester'));
 402        }
 403        else {
 404            $response = $this->_makeRequest('GET', $object);
 405        }
 406
 407        if ($response->getStatus() != 200) {
 408            return false;
 409        }
 410
 411        return $response->getBody();
 412    }
 413
 414    /**
 415     * Get an object using streaming
 416     *
 417     * Can use either provided filename for storage or create a temp file if none provided.
 418     *
 419     * @param  string $object Object path
 420     * @param  string $streamfile File to write the stream to
 421     * @param  bool   $paidobject This is "requestor pays" object
 422     * @return Zend_Http_Response_Stream|false
 423     */
 424    public function getObjectStream($object, $streamfile = null, $paidobject=false)
 425    {
 426        $object = $this->_fixupObjectName($object);
 427        self::getHttpClient()->setStream($streamfile?$streamfile:true);
 428        if ($paidobject) {
 429            $response = $this->_makeRequest('GET', $object, null, array(self::S3_REQUESTPAY_HEADER => 'requester'));
 430        }
 431        else {
 432            $response = $this->_makeRequest('GET', $object);
 433        }
 434        self::getHttpClient()->setStream(null);
 435
 436        if ($response->getStatus() != 200 || !($response instanceof Zend_Http_Response_Stream)) {
 437            return false;
 438        }
 439
 440        return $response;
 441    }
 442
 443    /**
 444     * Upload an object by a PHP string
 445     *
 446     * @param  string $object Object name
 447     * @param  string|resource $data   Object data (can be string or stream)
 448     * @param  array  $meta   Metadata
 449     * @return boolean
 450     */
 451    public function putObject($object, $data, $meta=null)
 452    {
 453        $object = $this->_fixupObjectName($object);
 454        $headers = (is_array($meta)) ? $meta : array();
 455
 456        if(!is_resource($data)) {
 457            $headers['Content-MD5'] = base64_encode(md5($data, true));
 458        }
 459        $headers['Expect'] = '100-continue';
 460
 461        if (!isset($headers[self::S3_CONTENT_TYPE_HEADER])) {
 462            $headers[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($object);
 463        }
 464
 465        $response = $this->_makeRequest('PUT', $object, null, $headers, $data);
 466
 467        // Check the MD5 Etag returned by S3 against and MD5 of the buffer
 468        if ($response->getStatus() == 200) {
 469            // It is escaped by double quotes for some reason
 470            $etag = str_replace('"', '', $response->getHeader('Etag'));
 471
 472            if (is_resource($data) || $etag == md5($data)) {
 473                return true;
 474            }
 475        }
 476
 477        return false;
 478    }
 479
 480    /**
 481     * Put file to S3 as object
 482     *
 483     * @param string $path   File name
 484     * @param string $object Object name
 485     * @param array  $meta   Metadata
 486     * @return boolean
 487     */
 488    public function putFile($path, $object, $meta=null)
 489    {
 490        $data = @file_get_contents($path);
 491        if ($data === false) {
 492            /**
 493             * @see Zend_Service_Amazon_S3_Exception
 494             */
 495            require_once 'Zend/Service/Amazon/S3/Exception.php';
 496            throw new Zend_Service_Amazon_S3_Exception("Cannot read file $path");
 497        }
 498
 499        if (!is_array($meta)) {
 500            $meta = array();
 501        }
 502
 503        if (!isset($meta[self::S3_CONTENT_TYPE_HEADER])) {
 504           $meta[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($path);
 505        }
 506
 507        return $this->putObject($object, $data, $meta);
 508    }
 509
 510    /**
 511     * Put file to S3 as object, using streaming
 512     *
 513     * @param string $path   File name
 514     * @param string $object Object name
 515     * @param array  $meta   Metadata
 516     * @return boolean
 517     */
 518    public function putFileStream($path, $object, $meta=null)
 519    {
 520        $data = @fopen($path, "rb");
 521        if ($data === false) {
 522            /**
 523             * @see Zend_Service_Amazon_S3_Exception
 524             */
 525            require_once 'Zend/Service/Amazon/S3/Exception.php';
 526            throw new Zend_Service_Amazon_S3_Exception("Cannot open file $path");
 527        }
 528
 529        if (!is_array($meta)) {
 530            $meta = array();
 531        }
 532
 533        if (!isset($meta[self::S3_CONTENT_TYPE_HEADER])) {
 534           $meta[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($path);
 535        }
 536
 537        if(!isset($meta['Content-MD5'])) {
 538            $meta['Content-MD5'] = base64_encode(md5_file($path, true));
 539        }
 540
 541        return $this->putObject($object, $data, $meta);
 542    }
 543
 544    /**
 545     * Remove a given object
 546     *
 547     * @param  string $object
 548     * @return boolean
 549     */
 550    public function removeObject($object)
 551    {
 552        $object = $this->_fixupObjectName($object);
 553        $response = $this->_makeRequest('DELETE', $object);
 554
 555        // Look for a 204 No Content response
 556        return ($response->getStatus() == 204);
 557    }
 558
 559    /**
 560     * Copy an object
 561     *
 562     * @param  string $sourceObject  Source object name
 563     * @param  string $destObject    Destination object name
 564     * @param  array  $meta          (OPTIONAL) Metadata to apply to desination object.
 565     *                               Set to null to copy metadata from source object.
 566     * @return boolean
 567     */
 568    public function copyObject($sourceObject, $destObject, $meta = null)
 569    {
 570        $sourceObject = $this->_fixupObjectName($sourceObject);
 571        $destObject   = $this->_fixupObjectName($destObject);
 572
 573        $headers = (is_array($meta)) ? $meta : array();
 574        $headers['x-amz-copy-source'] = $sourceObject;
 575        $headers['x-amz-metadata-directive'] = $meta === null ? 'COPY' : 'REPLACE';
 576
 577        $response = $this->_makeRequest('PUT', $destObject, null, $headers);
 578
 579        if ($response->getStatus() == 200 && !stristr($response->getBody(), '<Error>')) {
 580            return true;
 581        }
 582
 583        return false;
 584    }
 585
 586    /**
 587     * Move an object
 588     *
 589     * Performs a copy to dest + verify + remove source
 590     *
 591     * @param string $sourceObject  Source object name
 592     * @param string $destObject    Destination object name
 593     * @param array  $meta          (OPTIONAL) Metadata to apply to destination object.
 594     *                              Set to null to retain existing metadata.
 595     */
 596    public function moveObject($sourceObject, $destObject, $meta = null)
 597    {
 598        $sourceInfo = $this->getInfo($sourceObject);
 599
 600        $this->copyObject($sourceObject, $destObject, $meta);
 601        $destInfo = $this->getInfo($destObject);
 602
 603        if ($sourceInfo['etag'] === $destInfo['etag']) {
 604            return $this->removeObject($sourceObject);
 605        } else {
 606            return false;
 607        }
 608    }
 609
 610    /**
 611     * Make a request to Amazon S3
 612     *
 613     * @param  string $method    Request method
 614     * @param  string $path        Path to requested object
 615     * @param  array  $params    Request parameters
 616     * @param  array  $headers    HTTP headers
 617     * @param  string|resource $data        Request data
 618     * @return Zend_Http_Response
 619     */
 620    public function _makeRequest($method, $path='', $params=null, $headers=array(), $data=null)
 621    {
 622        $retry_count = 0;
 623
 624        if (!is_array($headers)) {
 625            $headers = array($headers);
 626        }
 627
 628        $headers['Date'] = gmdate(DATE_RFC1123, time());
 629
 630        if(is_resource($data) && $method != 'PUT') {
 631            /**
 632             * @see Zend_Service_Amazon_S3_Exception
 633             */
 634            require_once 'Zend/Service/Amazon/S3/Exception.php';
 635            throw new Zend_Service_Amazon_S3_Exception("Only PUT request supports stream data");
 636        }
 637
 638        // build the end point out
 639        $parts = explode('/', $path, 2);
 640        $endpoint = clone($this->_endpoint);
 641        if ($parts[0]) {
 642            // prepend bucket name to the hostname
 643            $endpoint->setHost($parts[0].'.'.$endpoint->getHost());
 644        }
 645        if (!empty($parts[1])) {
 646            // ZF-10218, ZF-10122
 647            $pathparts = explode('?',$parts[1]);
 648            $endpath = $pathparts[0];
 649            $endpoint->setPath('/'.$endpath);
 650            
 651        }
 652        else {
 653            $endpoint->setPath('/');
 654            if ($parts[0]) {
 655                $path = $parts[0].'/';
 656            }
 657        }
 658        self::addSignature($method, $path, $headers);
 659
 660        $client = self::getHttpClient();
 661
 662        $client->resetParameters(true);
 663        $client->setUri($endpoint);
 664        $client->setAuth(false);
 665        // Work around buglet in HTTP client - it doesn't clean headers
 666        // Remove when ZHC is fixed
 667        /*
 668        $client->setHeaders(array('Content-MD5'              => null,
 669                                  'Content-Encoding'         => null,
 670                                  'Expect'                   => null,
 671                                  'Range'                    => null,
 672                                  'x-amz-acl'                => null,
 673                                  'x-amz-copy-source'        => null,
 674                                  'x-amz-metadata-directive' => null));
 675        */
 676        $client->setHeaders($headers);
 677
 678        if (is_array($params)) {
 679            foreach ($params as $name=>$value) {
 680                $client->setParameterGet($name, $value);
 681            }
 682         }
 683
 684         if (($method == 'PUT') && ($data !== null)) {
 685             if (!isset($headers['Content-type'])) {
 686                 $headers['Content-type'] = self::getMimeType($path);
 687             }
 688             $client->setRawData($data, $headers['Content-type']);
 689         } 
 690         do {
 691            $retry = false;
 692
 693            $response = $client->request($method);
 694            $response_code = $response->getStatus();
 695
 696            // Some 5xx errors are expected, so retry automatically
 697            if ($response_code >= 500 && $response_code < 600 && $retry_count <= 5) {
 698                $retry = true;
 699                $retry_count++;
 700                sleep($retry_count / 4 * $retry_count);
 701            }
 702            else if ($response_code == 307) {
 703                // Need to redirect, new S3 endpoint given
 704                // This should never happen as Zend_Http_Client will redirect automatically
 705            }
 706            else if ($response_code == 100) {
 707                // echo 'OK to Continue';
 708            }
 709        } while ($retry);
 710
 711        return $response;
 712    }
 713
 714    /**
 715     * Add the S3 Authorization signature to the request headers
 716     *
 717     * @param  string $method
 718     * @param  string $path
 719     * @param  array &$headers
 720     * @return string
 721     */
 722    protected function addSignature($method, $path, &$headers)
 723    {
 724        if (!is_array($headers)) {
 725            $headers = array($headers);
 726        }
 727
 728        $type = $md5 = $date = '';
 729
 730        // Search for the Content-type, Content-MD5 and Date headers
 731        foreach ($headers as $key=>$val) {
 732            if (strcasecmp($key, 'content-type') == 0) {
 733                $type = $val;
 734            }
 735            else if (strcasecmp($key, 'content-md5') == 0) {
 736                $md5 = $val;
 737            }
 738            else if (strcasecmp($key, 'date') == 0) {
 739                $date = $val;
 740            }
 741        }
 742
 743        // If we have an x-amz-date header, use that instead of the normal Date
 744        if (isset($headers['x-amz-date']) && isset($date)) {
 745            $date = '';
 746        }
 747
 748        $sig_str = "$method\n$md5\n$type\n$date\n";
 749        // For x-amz- headers, combine like keys, lowercase them, sort them
 750        // alphabetically and remove excess spaces around values
 751        $amz_headers = array();
 752        foreach ($headers as $key=>$val) {
 753            $key = strtolower($key);
 754            if (substr($key, 0, 6) == 'x-amz-') {
 755                if (is_array($val)) {
 756                    $amz_headers[$key] = $val;
 757                }
 758                else {
 759                    $amz_headers[$key][] = preg_replace('/\s+/', ' ', $val);
 760                }
 761            }
 762        }
 763        if (!empty($amz_headers)) {
 764            ksort($amz_headers);
 765            foreach ($amz_headers as $key=>$val) {
 766                $sig_str .= $key.':'.implode(',', $val)."\n";
 767            }
 768        }
 769
 770        $sig_str .= '/'.parse_url($path, PHP_URL_PATH);
 771        if (strpos($path, '?location') !== false) {
 772            $sig_str .= '?location';
 773        }
 774        else if (strpos($path, '?acl') !== false) {
 775            $sig_str .= '?acl';
 776        }
 777        else if (strpos($path, '?torrent') !== false) {
 778            $sig_str .= '?torrent';
 779        }
 780        else if (strpos($path, '?versions') !== false) {
 781            $sig_str .= '?versions';
 782        }
 783
 784        $signature = base64_encode(Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'sha1', utf8_encode($sig_str), Zend_Crypt_Hmac::BINARY));
 785        $headers['Authorization'] = 'AWS '.$this->_getAccessKey().':'.$signature;
 786
 787        return $sig_str;
 788    }
 789
 790    /**
 791     * Attempt to get the content-type of a file based on the extension
 792     *
 793     * @param  string $path
 794     * @return string
 795     */
 796    public static function getMimeType($path)
 797    {
 798        $ext = substr(strrchr($path, '.'), 1);
 799
 800        if(!$ext) {
 801            // shortcut
 802            return 'binary/octet-stream';
 803        }
 804
 805        switch (strtolower($ext)) {
 806            case 'xls':
 807                $content_type = 'application/excel';
 808                break;
 809            case 'hqx':
 810                $content_type = 'application/macbinhex40';
 811                break;
 812            case 'doc':
 813            case 'dot':
 814            case 'wrd':
 815                $content_type = 'application/msword';
 816                break;
 817            case 'pdf':
 818                $content_type = 'application/pdf';
 819                break;
 820            case 'pgp':
 821                $content_type = 'application/pgp';
 822                break;
 823            case 'ps':
 824            case 'eps':
 825            case 'ai':
 826                $content_type = 'application/postscript';
 827                break;
 828            case 'ppt':
 829                $content_type = 'application/powerpoint';
 830                break;
 831            case 'rtf':
 832                $content_type = 'application/rtf';
 833                break;
 834            case 'tgz':
 835            case 'gtar':
 836                $content_type = 'application/x-gtar';
 837                break;
 838            case 'gz':
 839                $content_type = 'application/x-gzip';
 840                break;
 841            case 'php':
 842            case 'php3':
 843            case 'php4':
 844                $content_type = 'application/x-httpd-php';
 845                break;
 846            case 'js':
 847                $content_type = 'application/x-javascript';
 848                break;
 849            case 'ppd':
 850            case 'psd':
 851                $content_type = 'application/x-photoshop';
 852                break;
 853            case 'swf':
 854            case 'swc':
 855            case 'rf':
 856                $content_type = 'application/x-shockwave-flash';
 857                break;
 858            case 'tar':
 859                $content_type = 'application/x-tar';
 860                break;
 861            case 'zip':
 862                $content_type = 'application/zip';
 863                break;
 864            case 'mid':
 865            case 'midi':
 866            case 'kar':
 867                $content_type = 'audio/midi';
 868                break;
 869            case 'mp2':
 870            case 'mp3':
 871            case 'mpga':
 872                $content_type = 'audio/mpeg';
 873                break;
 874            case 'ra':
 875                $content_type = 'audio/x-realaudio';
 876                break;
 877            case 'wav':
 878                $content_type = 'audio/wav';
 879                break;
 880            case 'bmp':
 881                $content_type = 'image/bitmap';
 882                break;
 883            case 'gif':
 884                $content_type = 'image/gif';
 885                break;
 886            case 'iff':
 887                $content_type = 'image/iff';
 888                break;
 889            case 'jb2':
 890                $content_type = 'image/jb2';
 891                break;
 892            case 'jpg':
 893            case 'jpe':
 894            case 'jpeg':
 895                $content_type = 'image/jpeg';
 896                break;
 897            case 'jpx':
 898                $content_type = 'image/jpx';
 899                break;
 900            case 'png':
 901                $content_type = 'image/png';
 902                break;
 903            case 'tif':
 904            case 'tiff':
 905                $content_type = 'image/tiff';
 906                break;
 907            case 'wbmp':
 908                $content_type = 'image/vnd.wap.wbmp';
 909                break;
 910            case 'xbm':
 911                $content_type = 'image/xbm';
 912                break;
 913            case 'css':
 914                $content_type = 'text/css';
 915                break;
 916            case 'txt':
 917                $content_type = 'text/plain';
 918                break;
 919            case 'htm':
 920            case 'html':
 921                $content_type = 'text/html';
 922                break;
 923            case 'xml':
 924                $content_type = 'text/xml';
 925                break;
 926            case 'xsl':
 927                $content_type = 'text/xsl';
 928                break;
 929            case 'mpg':
 930            case 'mpe':
 931            case 'mpeg':
 932                $content_type = 'video/mpeg';
 933                break;
 934            case 'qt':
 935            case 'mov':
 936                $content_type = 'video/quicktime';
 937                break;
 938            case 'avi':
 939                $content_type = 'video/x-ms-video';
 940                break;
 941            case 'eml':
 942                $content_type = 'message/rfc822';
 943                break;
 944            default:
 945                $content_type = 'binary/octet-stream';
 946                break;
 947        }
 948
 949        return $content_type;
 950    }
 951
 952    /**
 953     * Register this object as stream wrapper client
 954     *
 955     * @param  string $name
 956     * @return Zend_Service_Amazon_S3
 957     */
 958    public function registerAsClient($name)
 959    {
 960        self::$_wrapperClients[$name] = $this;
 961        return $this;
 962    }
 963
 964    /**
 965     * Unregister this object as stream wrapper client
 966     *
 967     * @param  string $name
 968     * @return Zend_Service_Amazon_S3
 969     */
 970    public function unregisterAsClient($name)
 971    {
 972        unset(self::$_wrapperClients[$name]);
 973        return $this;
 974    }
 975
 976    /**
 977     * Get wrapper client for stream type
 978     *
 979     * @param  string $name
 980     * @return Zend_Service_Amazon_S3
 981     */
 982    public static function getWrapperClient($name)
 983    {
 984        return self::$_wrapperClients[$name];
 985    }
 986
 987    /**
 988     * Register this object as stream wrapper
 989     *
 990     * @param  string $name
 991     * @return Zend_Service_Amazon_S3
 992     */
 993    public function registerStreamWrapper($name='s3')
 994    {
 995        /**
 996         * @see Zend_Service_Amazon_S3_Stream
 997         */
 998        require_once 'Zend/Service/Amazon/S3/Stream.php';
 999
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}