|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00026 require_once 'Zend/Service/Amazon/Abstract.php'; 00027 00031 require_once 'Zend/Crypt/Hmac.php'; 00032 00043 class Zend_Service_Amazon_S3 extends Zend_Service_Amazon_Abstract 00044 { 00050 protected static $_wrapperClients = array(); 00051 00057 protected $_endpoint; 00058 00059 const S3_ENDPOINT = 's3.amazonaws.com'; 00060 00061 const S3_ACL_PRIVATE = 'private'; 00062 const S3_ACL_PUBLIC_READ = 'public-read'; 00063 const S3_ACL_PUBLIC_WRITE = 'public-read-write'; 00064 const S3_ACL_AUTH_READ = 'authenticated-read'; 00065 00066 const S3_REQUESTPAY_HEADER = 'x-amz-request-payer'; 00067 const S3_ACL_HEADER = 'x-amz-acl'; 00068 const S3_CONTENT_TYPE_HEADER = 'Content-Type'; 00069 00076 public function setEndpoint($endpoint) 00077 { 00078 if (!($endpoint instanceof Zend_Uri_Http)) { 00079 $endpoint = Zend_Uri::factory($endpoint); 00080 } 00081 if (!$endpoint->valid()) { 00085 require_once 'Zend/Service/Amazon/S3/Exception.php'; 00086 throw new Zend_Service_Amazon_S3_Exception('Invalid endpoint supplied'); 00087 } 00088 $this->_endpoint = $endpoint; 00089 return $this; 00090 } 00091 00097 public function getEndpoint() 00098 { 00099 return $this->_endpoint; 00100 } 00101 00109 public function __construct($accessKey=null, $secretKey=null, $region=null) 00110 { 00111 parent::__construct($accessKey, $secretKey, $region); 00112 00113 $this->setEndpoint('http://'.self::S3_ENDPOINT); 00114 } 00115 00122 public function _validBucketName($bucket) 00123 { 00124 $len = strlen($bucket); 00125 if ($len < 3 || $len > 255) { 00129 require_once 'Zend/Service/Amazon/S3/Exception.php'; 00130 throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" must be between 3 and 255 characters long"); 00131 } 00132 00133 if (preg_match('/[^a-z0-9\._-]/', $bucket)) { 00137 require_once 'Zend/Service/Amazon/S3/Exception.php'; 00138 throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" contains invalid characters"); 00139 } 00140 00141 if (preg_match('/(\d){1,3}\.(\d){1,3}\.(\d){1,3}\.(\d){1,3}/', $bucket)) { 00145 require_once 'Zend/Service/Amazon/S3/Exception.php'; 00146 throw new Zend_Service_Amazon_S3_Exception("Bucket name \"$bucket\" cannot be an IP address"); 00147 } 00148 return true; 00149 } 00150 00157 public function createBucket($bucket, $location = null) 00158 { 00159 $this->_validBucketName($bucket); 00160 00161 if($location) { 00162 $data = '<CreateBucketConfiguration><LocationConstraint>'.$location.'</LocationConstraint></CreateBucketConfiguration>'; 00163 } 00164 else { 00165 $data = null; 00166 } 00167 $response = $this->_makeRequest('PUT', $bucket, null, array(), $data); 00168 00169 return ($response->getStatus() == 200); 00170 } 00171 00178 public function isBucketAvailable($bucket) 00179 { 00180 $response = $this->_makeRequest('HEAD', $bucket, array('max-keys'=>0)); 00181 00182 return ($response->getStatus() != 404); 00183 } 00184 00191 public function isObjectAvailable($object) 00192 { 00193 $response = $this->_makeRequest('HEAD', $object); 00194 00195 return ($response->getStatus() == 200); 00196 } 00197 00205 public function removeBucket($bucket) 00206 { 00207 $response = $this->_makeRequest('DELETE', $bucket); 00208 00209 // Look for a 204 No Content response 00210 return ($response->getStatus() == 204); 00211 } 00212 00219 public function getInfo($object) 00220 { 00221 $info = array(); 00222 00223 $object = $this->_fixupObjectName($object); 00224 $response = $this->_makeRequest('HEAD', $object); 00225 00226 if ($response->getStatus() == 200) { 00227 $info['type'] = $response->getHeader('Content-type'); 00228 $info['size'] = $response->getHeader('Content-length'); 00229 $info['mtime'] = strtotime($response->getHeader('Last-modified')); 00230 $info['etag'] = $response->getHeader('ETag'); 00231 } 00232 else { 00233 return false; 00234 } 00235 00236 return $info; 00237 } 00238 00244 public function getBuckets() 00245 { 00246 $response = $this->_makeRequest('GET'); 00247 00248 if ($response->getStatus() != 200) { 00249 return false; 00250 } 00251 00252 $xml = new SimpleXMLElement($response->getBody()); 00253 00254 $buckets = array(); 00255 foreach ($xml->Buckets->Bucket as $bucket) { 00256 $buckets[] = (string)$bucket->Name; 00257 } 00258 00259 return $buckets; 00260 } 00261 00268 public function cleanBucket($bucket) 00269 { 00270 $objects = $this->getObjectsByBucket($bucket); 00271 if (!$objects) { 00272 return false; 00273 } 00274 00275 foreach ($objects as $object) { 00276 $this->removeObject("$bucket/$object"); 00277 } 00278 return true; 00279 } 00280 00294 public function getObjectsByBucket($bucket, $params = array()) 00295 { 00296 $response = $this->_makeRequest('GET', $bucket, $params); 00297 00298 if ($response->getStatus() != 200) { 00299 return false; 00300 } 00301 00302 $xml = new SimpleXMLElement($response->getBody()); 00303 00304 $objects = array(); 00305 if (isset($xml->Contents)) { 00306 foreach ($xml->Contents as $contents) { 00307 foreach ($contents->Key as $object) { 00308 $objects[] = (string)$object; 00309 } 00310 } 00311 } 00312 00313 return $objects; 00314 } 00315 00322 protected function _fixupObjectName($object) 00323 { 00324 $nameparts = explode('/', $object); 00325 00326 $this->_validBucketName($nameparts[0]); 00327 00328 $firstpart = array_shift($nameparts); 00329 if (count($nameparts) == 0) { 00330 return $firstpart; 00331 } 00332 00333 return $firstpart.'/'.join('/', array_map('rawurlencode', $nameparts)); 00334 } 00335 00343 public function getObject($object, $paidobject=false) 00344 { 00345 $object = $this->_fixupObjectName($object); 00346 if ($paidobject) { 00347 $response = $this->_makeRequest('GET', $object, null, array(self::S3_REQUESTPAY_HEADER => 'requester')); 00348 } 00349 else { 00350 $response = $this->_makeRequest('GET', $object); 00351 } 00352 00353 if ($response->getStatus() != 200) { 00354 return false; 00355 } 00356 00357 return $response->getBody(); 00358 } 00359 00370 public function getObjectStream($object, $streamfile = null, $paidobject=false) 00371 { 00372 $object = $this->_fixupObjectName($object); 00373 self::getHttpClient()->setStream($streamfile?$streamfile:true); 00374 if ($paidobject) { 00375 $response = $this->_makeRequest('GET', $object, null, array(self::S3_REQUESTPAY_HEADER => 'requester')); 00376 } 00377 else { 00378 $response = $this->_makeRequest('GET', $object); 00379 } 00380 self::getHttpClient()->setStream(null); 00381 00382 if ($response->getStatus() != 200 || !($response instanceof Zend_Http_Response_Stream)) { 00383 return false; 00384 } 00385 00386 return $response; 00387 } 00388 00397 public function putObject($object, $data, $meta=null) 00398 { 00399 $object = $this->_fixupObjectName($object); 00400 $headers = (is_array($meta)) ? $meta : array(); 00401 00402 if(!is_resource($data)) { 00403 $headers['Content-MD5'] = base64_encode(md5($data, true)); 00404 } 00405 $headers['Expect'] = '100-continue'; 00406 00407 if (!isset($headers[self::S3_CONTENT_TYPE_HEADER])) { 00408 $headers[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($object); 00409 } 00410 00411 $response = $this->_makeRequest('PUT', $object, null, $headers, $data); 00412 00413 // Check the MD5 Etag returned by S3 against and MD5 of the buffer 00414 if ($response->getStatus() == 200) { 00415 // It is escaped by double quotes for some reason 00416 $etag = str_replace('"', '', $response->getHeader('Etag')); 00417 00418 if (is_resource($data) || $etag == md5($data)) { 00419 return true; 00420 } 00421 } 00422 00423 return false; 00424 } 00425 00434 public function putFile($path, $object, $meta=null) 00435 { 00436 $data = @file_get_contents($path); 00437 if ($data === false) { 00441 require_once 'Zend/Service/Amazon/S3/Exception.php'; 00442 throw new Zend_Service_Amazon_S3_Exception("Cannot read file $path"); 00443 } 00444 00445 if (!is_array($meta)) { 00446 $meta = array(); 00447 } 00448 00449 if (!isset($meta[self::S3_CONTENT_TYPE_HEADER])) { 00450 $meta[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($path); 00451 } 00452 00453 return $this->putObject($object, $data, $meta); 00454 } 00455 00464 public function putFileStream($path, $object, $meta=null) 00465 { 00466 $data = @fopen($path, "rb"); 00467 if ($data === false) { 00471 require_once 'Zend/Service/Amazon/S3/Exception.php'; 00472 throw new Zend_Service_Amazon_S3_Exception("Cannot open file $path"); 00473 } 00474 00475 if (!is_array($meta)) { 00476 $meta = array(); 00477 } 00478 00479 if (!isset($meta[self::S3_CONTENT_TYPE_HEADER])) { 00480 $meta[self::S3_CONTENT_TYPE_HEADER] = self::getMimeType($path); 00481 } 00482 00483 if(!isset($meta['Content-MD5'])) { 00484 $headers['Content-MD5'] = base64_encode(md5_file($path, true)); 00485 } 00486 00487 return $this->putObject($object, $data, $meta); 00488 } 00489 00496 public function removeObject($object) 00497 { 00498 $object = $this->_fixupObjectName($object); 00499 $response = $this->_makeRequest('DELETE', $object); 00500 00501 // Look for a 204 No Content response 00502 return ($response->getStatus() == 204); 00503 } 00504 00515 public function _makeRequest($method, $path='', $params=null, $headers=array(), $data=null) 00516 { 00517 $retry_count = 0; 00518 00519 if (!is_array($headers)) { 00520 $headers = array($headers); 00521 } 00522 00523 $headers['Date'] = gmdate(DATE_RFC1123, time()); 00524 00525 if(is_resource($data) && $method != 'PUT') { 00529 require_once 'Zend/Service/Amazon/S3/Exception.php'; 00530 throw new Zend_Service_Amazon_S3_Exception("Only PUT request supports stream data"); 00531 } 00532 00533 // build the end point out 00534 $parts = explode('/', $path, 2); 00535 $endpoint = clone($this->_endpoint); 00536 if ($parts[0]) { 00537 // prepend bucket name to the hostname 00538 $endpoint->setHost($parts[0].'.'.$endpoint->getHost()); 00539 } 00540 if (!empty($parts[1])) { 00541 $endpoint->setPath('/'.$parts[1]); 00542 } 00543 else { 00544 $endpoint->setPath('/'); 00545 if ($parts[0]) { 00546 $path = $parts[0].'/'; 00547 } 00548 } 00549 00550 self::addSignature($method, $path, $headers); 00551 00552 $client = self::getHttpClient(); 00553 00554 $client->resetParameters(); 00555 $client->setUri($endpoint); 00556 $client->setAuth(false); 00557 // Work around buglet in HTTP client - it doesn't clean headers 00558 // Remove when ZHC is fixed 00559 $client->setHeaders(array('Content-MD5' => null, 00560 'Expect' => null, 00561 'Range' => null, 00562 'x-amz-acl' => null)); 00563 00564 $client->setHeaders($headers); 00565 00566 if (is_array($params)) { 00567 foreach ($params as $name=>$value) { 00568 $client->setParameterGet($name, $value); 00569 } 00570 } 00571 00572 if (($method == 'PUT') && ($data !== null)) { 00573 if (!isset($headers['Content-type'])) { 00574 $headers['Content-type'] = self::getMimeType($path); 00575 } 00576 $client->setRawData($data, $headers['Content-type']); 00577 } 00578 do { 00579 $retry = false; 00580 00581 $response = $client->request($method); 00582 $response_code = $response->getStatus(); 00583 00584 // Some 5xx errors are expected, so retry automatically 00585 if ($response_code >= 500 && $response_code < 600 && $retry_count <= 5) { 00586 $retry = true; 00587 $retry_count++; 00588 sleep($retry_count / 4 * $retry_count); 00589 } 00590 else if ($response_code == 307) { 00591 // Need to redirect, new S3 endpoint given 00592 // This should never happen as Zend_Http_Client will redirect automatically 00593 } 00594 else if ($response_code == 100) { 00595 // echo 'OK to Continue'; 00596 } 00597 } while ($retry); 00598 00599 return $response; 00600 } 00601 00610 protected function addSignature($method, $path, &$headers) 00611 { 00612 if (!is_array($headers)) { 00613 $headers = array($headers); 00614 } 00615 00616 $type = $md5 = $date = ''; 00617 00618 // Search for the Content-type, Content-MD5 and Date headers 00619 foreach ($headers as $key=>$val) { 00620 if (strcasecmp($key, 'content-type') == 0) { 00621 $type = $val; 00622 } 00623 else if (strcasecmp($key, 'content-md5') == 0) { 00624 $md5 = $val; 00625 } 00626 else if (strcasecmp($key, 'date') == 0) { 00627 $date = $val; 00628 } 00629 } 00630 00631 // If we have an x-amz-date header, use that instead of the normal Date 00632 if (isset($headers['x-amz-date']) && isset($date)) { 00633 $date = ''; 00634 } 00635 00636 $sig_str = "$method\n$md5\n$type\n$date\n"; 00637 // For x-amz- headers, combine like keys, lowercase them, sort them 00638 // alphabetically and remove excess spaces around values 00639 $amz_headers = array(); 00640 foreach ($headers as $key=>$val) { 00641 $key = strtolower($key); 00642 if (substr($key, 0, 6) == 'x-amz-') { 00643 if (is_array($val)) { 00644 $amz_headers[$key] = $val; 00645 } 00646 else { 00647 $amz_headers[$key][] = preg_replace('/\s+/', ' ', $val); 00648 } 00649 } 00650 } 00651 if (!empty($amz_headers)) { 00652 ksort($amz_headers); 00653 foreach ($amz_headers as $key=>$val) { 00654 $sig_str .= $key.':'.implode(',', $val)."\n"; 00655 } 00656 } 00657 00658 $sig_str .= '/'.parse_url($path, PHP_URL_PATH); 00659 if (strpos($path, '?location') !== false) { 00660 $sig_str .= '?location'; 00661 } 00662 else if (strpos($path, '?acl') !== false) { 00663 $sig_str .= '?acl'; 00664 } 00665 else if (strpos($path, '?torrent') !== false) { 00666 $sig_str .= '?torrent'; 00667 } 00668 00669 $signature = base64_encode(Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'sha1', utf8_encode($sig_str), Zend_Crypt_Hmac::BINARY)); 00670 $headers['Authorization'] = 'AWS '.$this->_getAccessKey().':'.$signature; 00671 00672 return $sig_str; 00673 } 00674 00681 public static function getMimeType($path) 00682 { 00683 $ext = substr(strrchr($path, '.'), 1); 00684 00685 if(!$ext) { 00686 // shortcut 00687 return 'binary/octet-stream'; 00688 } 00689 00690 switch (strtolower($ext)) { 00691 case 'xls': 00692 $content_type = 'application/excel'; 00693 break; 00694 case 'hqx': 00695 $content_type = 'application/macbinhex40'; 00696 break; 00697 case 'doc': 00698 case 'dot': 00699 case 'wrd': 00700 $content_type = 'application/msword'; 00701 break; 00702 case 'pdf': 00703 $content_type = 'application/pdf'; 00704 break; 00705 case 'pgp': 00706 $content_type = 'application/pgp'; 00707 break; 00708 case 'ps': 00709 case 'eps': 00710 case 'ai': 00711 $content_type = 'application/postscript'; 00712 break; 00713 case 'ppt': 00714 $content_type = 'application/powerpoint'; 00715 break; 00716 case 'rtf': 00717 $content_type = 'application/rtf'; 00718 break; 00719 case 'tgz': 00720 case 'gtar': 00721 $content_type = 'application/x-gtar'; 00722 break; 00723 case 'gz': 00724 $content_type = 'application/x-gzip'; 00725 break; 00726 case 'php': 00727 case 'php3': 00728 case 'php4': 00729 $content_type = 'application/x-httpd-php'; 00730 break; 00731 case 'js': 00732 $content_type = 'application/x-javascript'; 00733 break; 00734 case 'ppd': 00735 case 'psd': 00736 $content_type = 'application/x-photoshop'; 00737 break; 00738 case 'swf': 00739 case 'swc': 00740 case 'rf': 00741 $content_type = 'application/x-shockwave-flash'; 00742 break; 00743 case 'tar': 00744 $content_type = 'application/x-tar'; 00745 break; 00746 case 'zip': 00747 $content_type = 'application/zip'; 00748 break; 00749 case 'mid': 00750 case 'midi': 00751 case 'kar': 00752 $content_type = 'audio/midi'; 00753 break; 00754 case 'mp2': 00755 case 'mp3': 00756 case 'mpga': 00757 $content_type = 'audio/mpeg'; 00758 break; 00759 case 'ra': 00760 $content_type = 'audio/x-realaudio'; 00761 break; 00762 case 'wav': 00763 $content_type = 'audio/wav'; 00764 break; 00765 case 'bmp': 00766 $content_type = 'image/bitmap'; 00767 break; 00768 case 'gif': 00769 $content_type = 'image/gif'; 00770 break; 00771 case 'iff': 00772 $content_type = 'image/iff'; 00773 break; 00774 case 'jb2': 00775 $content_type = 'image/jb2'; 00776 break; 00777 case 'jpg': 00778 case 'jpe': 00779 case 'jpeg': 00780 $content_type = 'image/jpeg'; 00781 break; 00782 case 'jpx': 00783 $content_type = 'image/jpx'; 00784 break; 00785 case 'png': 00786 $content_type = 'image/png'; 00787 break; 00788 case 'tif': 00789 case 'tiff': 00790 $content_type = 'image/tiff'; 00791 break; 00792 case 'wbmp': 00793 $content_type = 'image/vnd.wap.wbmp'; 00794 break; 00795 case 'xbm': 00796 $content_type = 'image/xbm'; 00797 break; 00798 case 'css': 00799 $content_type = 'text/css'; 00800 break; 00801 case 'txt': 00802 $content_type = 'text/plain'; 00803 break; 00804 case 'htm': 00805 case 'html': 00806 $content_type = 'text/html'; 00807 break; 00808 case 'xml': 00809 $content_type = 'text/xml'; 00810 break; 00811 case 'xsl': 00812 $content_type = 'text/xsl'; 00813 break; 00814 case 'mpg': 00815 case 'mpe': 00816 case 'mpeg': 00817 $content_type = 'video/mpeg'; 00818 break; 00819 case 'qt': 00820 case 'mov': 00821 $content_type = 'video/quicktime'; 00822 break; 00823 case 'avi': 00824 $content_type = 'video/x-ms-video'; 00825 break; 00826 case 'eml': 00827 $content_type = 'message/rfc822'; 00828 break; 00829 default: 00830 $content_type = 'binary/octet-stream'; 00831 break; 00832 } 00833 00834 return $content_type; 00835 } 00836 00843 public function registerAsClient($name) 00844 { 00845 self::$_wrapperClients[$name] = $this; 00846 return $this; 00847 } 00848 00855 public function unregisterAsClient($name) 00856 { 00857 unset(self::$_wrapperClients[$name]); 00858 return $this; 00859 } 00860 00867 public static function getWrapperClient($name) 00868 { 00869 return self::$_wrapperClients[$name]; 00870 } 00871 00878 public function registerStreamWrapper($name='s3') 00879 { 00883 require_once 'Zend/Service/Amazon/S3/Stream.php'; 00884 00885 stream_register_wrapper($name, 'Zend_Service_Amazon_S3_Stream'); 00886 $this->registerAsClient($name); 00887 } 00888 00895 public function unregisterStreamWrapper($name='s3') 00896 { 00897 stream_wrapper_unregister($name); 00898 $this->unregisterAsClient($name); 00899 } 00900 }