Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/repository/s3/S3.php
Go to the documentation of this file.
00001 <?php
00002 
00036 class S3 {
00037         // ACL flags
00038         const ACL_PRIVATE = 'private';
00039         const ACL_PUBLIC_READ = 'public-read';
00040         const ACL_PUBLIC_READ_WRITE = 'public-read-write';
00041 
00042         public static $useSSL = true;
00043 
00044         private static $__accessKey; // AWS Access key
00045         private static $__secretKey; // AWS Secret key
00046 
00047 
00056         public function __construct($accessKey = null, $secretKey = null, $useSSL = true) {
00057                 if ($accessKey !== null && $secretKey !== null)
00058                         self::setAuth($accessKey, $secretKey);
00059                 self::$useSSL = $useSSL;
00060         }
00061 
00062 
00070         public static function setAuth($accessKey, $secretKey) {
00071                 self::$__accessKey = $accessKey;
00072                 self::$__secretKey = $secretKey;
00073         }
00074 
00075 
00082         public static function listBuckets($detailed = false) {
00083                 $rest = new S3Request('GET', '', '');
00084                 $rest = $rest->getResponse();
00085                 if ($rest->error === false && $rest->code !== 200)
00086                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00087                 if ($rest->error !== false) {
00088                         trigger_error(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00089                         return false;
00090                 }
00091                 $results = array(); //var_dump($rest->body);
00092                 if (!isset($rest->body->Buckets)) return $results;
00093 
00094                 if ($detailed) {
00095                         if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
00096                         $results['owner'] = array(
00097                                 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
00098                         );
00099                         $results['buckets'] = array();
00100                         foreach ($rest->body->Buckets->Bucket as $b)
00101                                 $results['buckets'][] = array(
00102                                         'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
00103                                 );
00104                 } else
00105                         foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
00106 
00107                 return $results;
00108         }
00109 
00110 
00111         /*
00112         * Get contents for a bucket
00113         *
00114         * If maxKeys is null this method will loop through truncated result sets
00115         *
00116         * @param string $bucket Bucket name
00117         * @param string $prefix Prefix
00118         * @param string $marker Marker (last file listed)
00119         * @param string $maxKeys Max keys (maximum number of keys to return)
00120         * @param string $delimiter Delimiter
00121         * @return array | false
00122         */
00123         public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null) {
00124                 $rest = new S3Request('GET', $bucket, '');
00125                 if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
00126                 if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
00127                 if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
00128                 if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
00129                 $response = $rest->getResponse();
00130                 if ($response->error === false && $response->code !== 200)
00131                         $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
00132                 if ($response->error !== false) {
00133                         trigger_error(sprintf("S3::getBucket(): [%s] %s", $response->error['code'], $response->error['message']), E_USER_WARNING);
00134                         return false;
00135                 }
00136 
00137                 $results = array();
00138 
00139                 $lastMarker = null;
00140                 if (isset($response->body, $response->body->Contents))
00141                         foreach ($response->body->Contents as $c) {
00142                                 $results[(string)$c->Key] = array(
00143                                         'name' => (string)$c->Key,
00144                                         'time' => strtotime((string)$c->LastModified),
00145                                         'size' => (int)$c->Size,
00146                                         'hash' => substr((string)$c->ETag, 1, -1)
00147                                 );
00148                                 $lastMarker = (string)$c->Key;
00149                                 //$response->body->IsTruncated = 'true'; break;
00150                         }
00151 
00152 
00153                 if (isset($response->body->IsTruncated) &&
00154                 (string)$response->body->IsTruncated == 'false') return $results;
00155 
00156                 // Loop through truncated results if maxKeys isn't specified
00157                 if ($maxKeys == null && $lastMarker !== null && (string)$response->body->IsTruncated == 'true')
00158                 do {
00159                         $rest = new S3Request('GET', $bucket, '');
00160                         if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
00161                         $rest->setParameter('marker', $lastMarker);
00162                         if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
00163 
00164                         if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break;
00165                         if (isset($response->body, $response->body->Contents))
00166                                 foreach ($response->body->Contents as $c) {
00167                                         $results[(string)$c->Key] = array(
00168                                                 'name' => (string)$c->Key,
00169                                                 'time' => strtotime((string)$c->LastModified),
00170                                                 'size' => (int)$c->Size,
00171                                                 'hash' => substr((string)$c->ETag, 1, -1)
00172                                         );
00173                                         $lastMarker = (string)$c->Key;
00174                                 }
00175                 } while ($response !== false && (string)$response->body->IsTruncated == 'true');
00176 
00177                 return $results;
00178         }
00179 
00180 
00189         public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) {
00190                 $rest = new S3Request('PUT', $bucket, '');
00191                 $rest->setAmzHeader('x-amz-acl', $acl);
00192 
00193                 if ($location !== false) {
00194                         $dom = new DOMDocument;
00195                         $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
00196                         $locationConstraint = $dom->createElement('LocationConstraint', strtoupper($location));
00197                         $createBucketConfiguration->appendChild($locationConstraint);
00198                         $dom->appendChild($createBucketConfiguration);
00199                         $rest->data = $dom->saveXML();
00200                         $rest->size = strlen($rest->data);
00201                         $rest->setHeader('Content-Type', 'application/xml');
00202                 }
00203                 $rest = $rest->getResponse();
00204 
00205                 if ($rest->error === false && $rest->code !== 200)
00206                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00207                 if ($rest->error !== false) {
00208                         trigger_error(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
00209                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00210                         return false;
00211                 }
00212                 return true;
00213         }
00214 
00215 
00222         public static function deleteBucket($bucket) {
00223                 $rest = new S3Request('DELETE', $bucket);
00224                 $rest = $rest->getResponse();
00225                 if ($rest->error === false && $rest->code !== 204)
00226                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00227                 if ($rest->error !== false) {
00228                         trigger_error(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
00229                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00230                         return false;
00231                 }
00232                 return true;
00233         }
00234 
00235 
00243         public static function inputFile($file, $md5sum = true) {
00244                 if (!file_exists($file) || !is_file($file) || !is_readable($file)) {
00245                         trigger_error('S3::inputFile(): Unable to open input file: '.$file, E_USER_WARNING);
00246                         return false;
00247                 }
00248                 return array('file' => $file, 'size' => filesize($file),
00249                 'md5sum' => $md5sum !== false ? (is_string($md5sum) ? $md5sum :
00250                 base64_encode(md5_file($file, true))) : '');
00251         }
00252 
00253 
00262         public static function inputResource(&$resource, $bufferSize, $md5sum = '') {
00263                 if (!is_resource($resource) || $bufferSize <= 0) {
00264                         trigger_error('S3::inputResource(): Invalid resource or buffer size', E_USER_WARNING);
00265                         return false;
00266                 }
00267                 $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
00268                 $input['fp'] =& $resource;
00269                 return $input;
00270         }
00271 
00272 
00284         public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) {
00285                 if ($input == false) return false;
00286                 $rest = new S3Request('PUT', $bucket, $uri);
00287 
00288                 if (is_string($input)) $input = array(
00289                         'data' => $input, 'size' => strlen($input),
00290                         'md5sum' => base64_encode(md5($input, true))
00291                 );
00292 
00293                 // Data
00294                 if (isset($input['fp']))
00295                         $rest->fp =& $input['fp'];
00296                 elseif (isset($input['file']))
00297                         $rest->fp = @fopen($input['file'], 'rb');
00298                 elseif (isset($input['data']))
00299                         $rest->data = $input['data'];
00300 
00301                 // Content-Length (required)
00302                 if (isset($input['size']) && $input['size'] > -1)
00303                         $rest->size = $input['size'];
00304                 else {
00305                         if (isset($input['file']))
00306                                 $rest->size = filesize($input['file']);
00307                         elseif (isset($input['data']))
00308                                 $rest->size = strlen($input['data']);
00309                 }
00310 
00311                 // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
00312                 if (is_array($requestHeaders))
00313                         foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
00314                 elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
00315                         $input['type'] = $requestHeaders;
00316 
00317                 // Content-Type
00318                 if (!isset($input['type'])) {
00319                         if (isset($requestHeaders['Content-Type']))
00320                                 $input['type'] =& $requestHeaders['Content-Type'];
00321                         elseif (isset($input['file']))
00322                                 $input['type'] = self::__getMimeType($input['file']);
00323                         else
00324                                 $input['type'] = 'application/octet-stream';
00325                 }
00326 
00327                 // We need to post with Content-Length and Content-Type, MD5 is optional
00328                 if ($rest->size > 0 && ($rest->fp !== false || $rest->data !== false)) {
00329                         $rest->setHeader('Content-Type', $input['type']);
00330                         if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
00331 
00332                         $rest->setAmzHeader('x-amz-acl', $acl);
00333                         foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
00334                         $rest->getResponse();
00335                 } else
00336                         $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
00337 
00338                 if ($rest->response->error === false && $rest->response->code !== 200)
00339                         $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
00340                 if ($rest->response->error !== false) {
00341                         trigger_error(sprintf("S3::putObject(): [%s] %s", $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING);
00342                         return false;
00343                 }
00344                 return true;
00345         }
00346 
00347 
00359         public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) {
00360                 return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
00361         }
00362 
00363 
00375         public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain') {
00376                 return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
00377         }
00378 
00379 
00388         public static function getObject($bucket, $uri, $saveTo = false) {
00389                 $rest = new S3Request('GET', $bucket, $uri);
00390                 if ($saveTo !== false) {
00391                         if (is_resource($saveTo))
00392                                 $rest->fp =& $saveTo;
00393                         else
00394                                 if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
00395                                         $rest->file = realpath($saveTo);
00396                                 else
00397                                         $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
00398                 }
00399                 if ($rest->response->error === false) $rest->getResponse();
00400 
00401                 if ($rest->response->error === false && $rest->response->code !== 200)
00402                         $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
00403                 if ($rest->response->error !== false) {
00404                         trigger_error(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
00405                         $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING);
00406                         return false;
00407                 }
00408                 return $rest->response;
00409         }
00410 
00411 
00420         public static function getObjectInfo($bucket, $uri, $returnInfo = true) {
00421                 $rest = new S3Request('HEAD', $bucket, $uri);
00422                 $rest = $rest->getResponse();
00423                 if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
00424                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00425                 if ($rest->error !== false) {
00426                         trigger_error(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
00427                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00428                         return false;
00429                 }
00430                 return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
00431         }
00432 
00433 
00444         public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE) {
00445                 $rest = new S3Request('PUT', $bucket, $uri);
00446                 $rest->setAmzHeader('x-amz-acl', $acl);
00447                 $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, $srcUri));
00448                 $rest = $rest->getResponse();
00449                 if ($rest->error === false && $rest->code !== 200)
00450                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00451                 if ($rest->error !== false) {
00452                         trigger_error(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
00453                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00454                         return false;
00455                 }
00456                 return isset($rest->body->LastModified, $rest->body->ETag) ? array(
00457                         'time' => strtotime((string)$rest->body->LastModified),
00458                         'hash' => substr((string)$rest->body->ETag, 1, -1)
00459                 ) : false;
00460         }
00461 
00462 
00471         public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null) {
00472                 // The S3 log delivery group has to be added to the target bucket's ACP
00473                 if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false) {
00474                         // Only add permissions to the target bucket when they do not exist
00475                         $aclWriteSet = false;
00476                         $aclReadSet = false;
00477                         foreach ($acp['acl'] as $acl)
00478                         if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery') {
00479                                 if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
00480                                 elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
00481                         }
00482                         if (!$aclWriteSet) $acp['acl'][] = array(
00483                                 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
00484                         );
00485                         if (!$aclReadSet) $acp['acl'][] = array(
00486                                 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
00487                         );
00488                         if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
00489                 }
00490 
00491                 $dom = new DOMDocument;
00492                 $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
00493                 $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
00494                 if ($targetBucket !== null) {
00495                         if ($targetPrefix == null) $targetPrefix = $bucket . '-';
00496                         $loggingEnabled = $dom->createElement('LoggingEnabled');
00497                         $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
00498                         $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
00499                         // TODO: Add TargetGrants?
00500                         $bucketLoggingStatus->appendChild($loggingEnabled);
00501                 }
00502                 $dom->appendChild($bucketLoggingStatus);
00503 
00504                 $rest = new S3Request('PUT', $bucket, '');
00505                 $rest->setParameter('logging', null);
00506                 $rest->data = $dom->saveXML();
00507                 $rest->size = strlen($rest->data);
00508                 $rest->setHeader('Content-Type', 'application/xml');
00509                 $rest = $rest->getResponse();
00510                 if ($rest->error === false && $rest->code !== 200)
00511                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00512                 if ($rest->error !== false) {
00513                         trigger_error(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s",
00514                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00515                         return false;
00516                 }
00517                 return true;
00518         }
00519 
00520 
00530         public static function getBucketLogging($bucket) {
00531                 $rest = new S3Request('GET', $bucket, '');
00532                 $rest->setParameter('logging', null);
00533                 $rest = $rest->getResponse();
00534                 if ($rest->error === false && $rest->code !== 200)
00535                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00536                 if ($rest->error !== false) {
00537                         trigger_error(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
00538                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00539                         return false;
00540                 }
00541                 if (!isset($rest->body->LoggingEnabled)) return false; // No logging
00542                 return array(
00543                         'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
00544                         'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
00545                 );
00546         }
00547 
00548 
00555         public static function disableBucketLogging($bucket) {
00556                 return self::setBucketLogging($bucket, null);
00557         }
00558 
00559 
00566         public static function getBucketLocation($bucket) {
00567                 $rest = new S3Request('GET', $bucket, '');
00568                 $rest->setParameter('location', null);
00569                 $rest = $rest->getResponse();
00570                 if ($rest->error === false && $rest->code !== 200)
00571                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00572                 if ($rest->error !== false) {
00573                         trigger_error(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
00574                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00575                         return false;
00576                 }
00577                 return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
00578         }
00579 
00580 
00589         public static function setAccessControlPolicy($bucket, $uri = '', $acp = array()) {
00590                 $dom = new DOMDocument;
00591                 $dom->formatOutput = true;
00592                 $accessControlPolicy = $dom->createElement('AccessControlPolicy');
00593                 $accessControlList = $dom->createElement('AccessControlList');
00594 
00595                 // It seems the owner has to be passed along too
00596                 $owner = $dom->createElement('Owner');
00597                 $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
00598                 $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
00599                 $accessControlPolicy->appendChild($owner);
00600 
00601                 foreach ($acp['acl'] as $g) {
00602                         $grant = $dom->createElement('Grant');
00603                         $grantee = $dom->createElement('Grantee');
00604                         $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
00605                         if (isset($g['id'])) { // CanonicalUser (DisplayName is omitted)
00606                                 $grantee->setAttribute('xsi:type', 'CanonicalUser');
00607                                 $grantee->appendChild($dom->createElement('ID', $g['id']));
00608                         } elseif (isset($g['email'])) { // AmazonCustomerByEmail
00609                                 $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
00610                                 $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
00611                         } elseif ($g['type'] == 'Group') { // Group
00612                                 $grantee->setAttribute('xsi:type', 'Group');
00613                                 $grantee->appendChild($dom->createElement('URI', $g['uri']));
00614                         }
00615                         $grant->appendChild($grantee);
00616                         $grant->appendChild($dom->createElement('Permission', $g['permission']));
00617                         $accessControlList->appendChild($grant);
00618                 }
00619 
00620                 $accessControlPolicy->appendChild($accessControlList);
00621                 $dom->appendChild($accessControlPolicy);
00622 
00623                 $rest = new S3Request('PUT', $bucket, $uri);
00624                 $rest->setParameter('acl', null);
00625                 $rest->data = $dom->saveXML();
00626                 $rest->size = strlen($rest->data);
00627                 $rest->setHeader('Content-Type', 'application/xml');
00628                 $rest = $rest->getResponse();
00629                 if ($rest->error === false && $rest->code !== 200)
00630                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00631                 if ($rest->error !== false) {
00632                         trigger_error(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
00633                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00634                         return false;
00635                 }
00636                 return true;
00637         }
00638 
00639 
00647         public static function getAccessControlPolicy($bucket, $uri = '') {
00648                 $rest = new S3Request('GET', $bucket, $uri);
00649                 $rest->setParameter('acl', null);
00650                 $rest = $rest->getResponse();
00651                 if ($rest->error === false && $rest->code !== 200)
00652                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00653                 if ($rest->error !== false) {
00654                         trigger_error(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
00655                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00656                         return false;
00657                 }
00658 
00659                 $acp = array();
00660                 if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) {
00661                         $acp['owner'] = array(
00662                                 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
00663                         );
00664                 }
00665                 if (isset($rest->body->AccessControlList)) {
00666                         $acp['acl'] = array();
00667                         foreach ($rest->body->AccessControlList->Grant as $grant) {
00668                                 foreach ($grant->Grantee as $grantee) {
00669                                         if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
00670                                                 $acp['acl'][] = array(
00671                                                         'type' => 'CanonicalUser',
00672                                                         'id' => (string)$grantee->ID,
00673                                                         'name' => (string)$grantee->DisplayName,
00674                                                         'permission' => (string)$grant->Permission
00675                                                 );
00676                                         elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
00677                                                 $acp['acl'][] = array(
00678                                                         'type' => 'AmazonCustomerByEmail',
00679                                                         'email' => (string)$grantee->EmailAddress,
00680                                                         'permission' => (string)$grant->Permission
00681                                                 );
00682                                         elseif (isset($grantee->URI)) // Group
00683                                                 $acp['acl'][] = array(
00684                                                         'type' => 'Group',
00685                                                         'uri' => (string)$grantee->URI,
00686                                                         'permission' => (string)$grant->Permission
00687                                                 );
00688                                         else continue;
00689                                 }
00690                         }
00691                 }
00692                 return $acp;
00693         }
00694 
00695 
00703         public static function deleteObject($bucket, $uri) {
00704                 $rest = new S3Request('DELETE', $bucket, $uri);
00705                 $rest = $rest->getResponse();
00706                 if ($rest->error === false && $rest->code !== 204)
00707                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00708                 if ($rest->error !== false) {
00709                         trigger_error(sprintf("S3::deleteObject(): [%s] %s",
00710                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00711                         return false;
00712                 }
00713                 return true;
00714         }
00715 
00716 
00727         public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) {
00728                 $expires = time() + $lifetime;
00729                 $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
00730                 return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
00731                 $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
00732                 urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
00733         }
00734 
00735 
00745         public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = '') {
00746                 self::$useSSL = true; // CloudFront requires SSL
00747                 $rest = new S3Request('POST', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com');
00748                 $rest->data = self::__getCloudFrontDistributionConfigXML($bucket.'.s3.amazonaws.com', $enabled, $comment, (string)microtime(true), $cnames);
00749                 $rest->size = strlen($rest->data);
00750                 $rest->setHeader('Content-Type', 'application/xml');
00751                 $rest = self::__getCloudFrontResponse($rest);
00752 
00753                 if ($rest->error === false && $rest->code !== 201)
00754                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00755                 if ($rest->error !== false) {
00756                         trigger_error(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", '$comment'): [%s] %s",
00757                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00758                         return false;
00759                 } elseif ($rest->body instanceof SimpleXMLElement)
00760                         return self::__parseCloudFrontDistributionConfig($rest->body);
00761                 return false;
00762         }
00763 
00764 
00771         public static function getDistribution($distributionId) {
00772                 self::$useSSL = true; // CloudFront requires SSL
00773                 $rest = new S3Request('GET', '', '2008-06-30/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
00774                 $rest = self::__getCloudFrontResponse($rest);
00775 
00776                 if ($rest->error === false && $rest->code !== 200)
00777                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00778                 if ($rest->error !== false) {
00779                         trigger_error(sprintf("S3::getDistribution($distributionId): [%s] %s",
00780                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00781                         return false;
00782                 } elseif ($rest->body instanceof SimpleXMLElement) {
00783                         $dist = self::__parseCloudFrontDistributionConfig($rest->body);
00784                         $dist['hash'] = $rest->headers['hash'];
00785                         return $dist;
00786                 }
00787                 return false;
00788         }
00789 
00790 
00797         public static function updateDistribution($dist) {
00798                 self::$useSSL = true; // CloudFront requires SSL
00799                 $rest = new S3Request('PUT', '', '2008-06-30/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
00800                 $rest->data = self::__getCloudFrontDistributionConfigXML($dist['origin'], $dist['enabled'], $dist['comment'], $dist['callerReference'], $dist['cnames']);
00801                 $rest->size = strlen($rest->data);
00802                 $rest->setHeader('If-Match', $dist['hash']);
00803                 $rest = self::__getCloudFrontResponse($rest);
00804 
00805                 if ($rest->error === false && $rest->code !== 200)
00806                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00807                 if ($rest->error !== false) {
00808                         trigger_error(sprintf("S3::updateDistribution({$dist['id']}, ".(int)$enabled.", '$comment'): [%s] %s",
00809                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00810                         return false;
00811                 } else {
00812                         $dist = self::__parseCloudFrontDistributionConfig($rest->body);
00813                         $dist['hash'] = $rest->headers['hash'];
00814                         return $dist;
00815                 }
00816                 return false;
00817         }
00818 
00819 
00826         public static function deleteDistribution($dist) {
00827                 self::$useSSL = true; // CloudFront requires SSL
00828                 $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
00829                 $rest->setHeader('If-Match', $dist['hash']);
00830                 $rest = self::__getCloudFrontResponse($rest);
00831 
00832                 if ($rest->error === false && $rest->code !== 204)
00833                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00834                 if ($rest->error !== false) {
00835                         trigger_error(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
00836                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00837                         return false;
00838                 }
00839                 return true;
00840         }
00841 
00842 
00848         public static function listDistributions() {
00849                 self::$useSSL = true; // CloudFront requires SSL
00850                 $rest = new S3Request('GET', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com');
00851                 $rest = self::__getCloudFrontResponse($rest);
00852 
00853                 if ($rest->error === false && $rest->code !== 200)
00854                         $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
00855                 if ($rest->error !== false) {
00856                         trigger_error(sprintf("S3::listDistributions(): [%s] %s",
00857                         $rest->error['code'], $rest->error['message']), E_USER_WARNING);
00858                         return false;
00859                 } elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary)) {
00860                         $list = array();
00861                         if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated)) {
00862                                 //$info['marker'] = (string)$rest->body->Marker;
00863                                 //$info['maxItems'] = (int)$rest->body->MaxItems;
00864                                 //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
00865                         }
00866                         foreach ($rest->body->DistributionSummary as $summary) {
00867                                 $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
00868                         }
00869                         return $list;
00870                 }
00871                 return array();
00872         }
00873 
00874 
00886         private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array()) {
00887                 $dom = new DOMDocument('1.0', 'UTF-8');
00888                 $dom->formatOutput = true;
00889                 $distributionConfig = $dom->createElement('DistributionConfig');
00890                 $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2008-06-30/');
00891                 $distributionConfig->appendChild($dom->createElement('Origin', $bucket));
00892                 $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
00893                 foreach ($cnames as $cname)
00894                         $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
00895                 if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
00896                 $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
00897                 $dom->appendChild($distributionConfig);
00898                 return $dom->saveXML();
00899         }
00900 
00901 
00909         private static function __parseCloudFrontDistributionConfig(&$node) {
00910                 $dist = array();
00911                 if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName)) {
00912                         $dist['id'] = (string)$node->Id;
00913                         $dist['status'] = (string)$node->Status;
00914                         $dist['time'] = strtotime((string)$node->LastModifiedTime);
00915                         $dist['domain'] = (string)$node->DomainName;
00916                 }
00917                 if (isset($node->CallerReference))
00918                         $dist['callerReference'] = (string)$node->CallerReference;
00919                 if (isset($node->Comment))
00920                         $dist['comment'] = (string)$node->Comment;
00921                 if (isset($node->Enabled, $node->Origin)) {
00922                         $dist['origin'] = (string)$node->Origin;
00923                         $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
00924                 } elseif (isset($node->DistributionConfig)) {
00925                         $dist = array_merge($dist, self::__parseCloudFrontDistributionConfig($node->DistributionConfig));
00926                 }
00927                 if (isset($node->CNAME)) {
00928                         $dist['cnames'] = array();
00929                         foreach ($node->CNAME as $cname) $dist['cnames'][(string)$cname] = (string)$cname;
00930                 }
00931                 return $dist;
00932         }
00933 
00934 
00942         private static function __getCloudFrontResponse(&$rest) {
00943                 $rest->getResponse();
00944                 if ($rest->response->error === false && isset($rest->response->body) &&
00945                 is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml') {
00946                         $rest->response->body = simplexml_load_string($rest->response->body);
00947                         // Grab CloudFront errors
00948                         if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
00949                         $rest->response->body->Error->Message)) {
00950                                 $rest->response->error = array(
00951                                         'code' => (string)$rest->response->body->Error->Code,
00952                                         'message' => (string)$rest->response->body->Error->Message
00953                                 );
00954                                 unset($rest->response->body);
00955                         }
00956                 }
00957                 return $rest->response;
00958         }
00959 
00960 
00968         public static function __getMimeType(&$file) {
00969                 $type = false;
00970                 // Fileinfo documentation says fileinfo_open() will use the
00971                 // MAGIC env var for the magic file
00972                 if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
00973                 ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) {
00974                         if (($type = finfo_file($finfo, $file)) !== false) {
00975                                 // Remove the charset and grab the last content-type
00976                                 $type = explode(' ', str_replace('; charset=', ';charset=', $type));
00977                                 $type = array_pop($type);
00978                                 $type = explode(';', $type);
00979                                 $type = trim(array_shift($type));
00980                         }
00981                         finfo_close($finfo);
00982 
00983                 // If anyone is still using mime_content_type()
00984                 } elseif (function_exists('mime_content_type'))
00985                         $type = trim(mime_content_type($file));
00986 
00987                 if ($type !== false && strlen($type) > 0) return $type;
00988 
00989                 // Otherwise do it the old fashioned way
00990                 static $exts = array(
00991                         'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
00992                         'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
00993                         'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
00994                         'zip' => 'application/zip', 'gz' => 'application/x-gzip',
00995                         'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
00996                         'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
00997                         'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
00998                         'css' => 'text/css', 'js' => 'text/javascript',
00999                         'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
01000                         'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
01001                         'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
01002                         'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
01003                 );
01004                 $ext = strToLower(pathInfo($file, PATHINFO_EXTENSION));
01005                 return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
01006         }
01007 
01008 
01016         public static function __getSignature($string) {
01017                 return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
01018         }
01019 
01020 
01030         private static function __getHash($string) {
01031                 return base64_encode(extension_loaded('hash') ?
01032                 hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
01033                 (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
01034                 pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
01035                 (str_repeat(chr(0x36), 64))) . $string)))));
01036         }
01037 
01038 }
01039 
01040 final class S3Request {
01041         private $verb, $bucket, $uri, $resource = '', $parameters = array(),
01042         $amzHeaders = array(), $headers = array(
01043                 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
01044         );
01045         public $fp = false, $size = 0, $data = false, $response;
01046 
01047 
01056         function __construct($verb, $bucket = '', $uri = '', $defaultHost = 's3.amazonaws.com') {
01057                 $this->verb = $verb;
01058                 $this->bucket = strtolower($bucket);
01059                 $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
01060 
01061                 if ($this->bucket !== '') {
01062                         $this->headers['Host'] = $this->bucket.'.'.$defaultHost;
01063                         $this->resource = '/'.$this->bucket.$this->uri;
01064                 } else {
01065                         $this->headers['Host'] = $defaultHost;
01066                         //$this->resource = strlen($this->uri) > 1 ? '/'.$this->bucket.$this->uri : $this->uri;
01067                         $this->resource = $this->uri;
01068                 }
01069                 $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
01070 
01071                 $this->response = new STDClass;
01072                 $this->response->error = false;
01073         }
01074 
01075 
01083         public function setParameter($key, $value) {
01084                 $this->parameters[$key] = $value;
01085         }
01086 
01087 
01095         public function setHeader($key, $value) {
01096                 $this->headers[$key] = $value;
01097         }
01098 
01099 
01107         public function setAmzHeader($key, $value) {
01108                 $this->amzHeaders[$key] = $value;
01109         }
01110 
01111 
01117         public function getResponse() {
01118                 $query = '';
01119                 if (sizeof($this->parameters) > 0) {
01120                         $query = substr($this->uri, -1) !== '?' ? '?' : '&';
01121                         foreach ($this->parameters as $var => $value)
01122                                 if ($value == null || $value == '') $query .= $var.'&';
01123                                 // Parameters should be encoded (thanks Sean O'Dea)
01124                                 else $query .= $var.'='.rawurlencode($value).'&';
01125                         $query = substr($query, 0, -1);
01126                         $this->uri .= $query;
01127 
01128                         if (array_key_exists('acl', $this->parameters) ||
01129                         array_key_exists('location', $this->parameters) ||
01130                         array_key_exists('torrent', $this->parameters) ||
01131                         array_key_exists('logging', $this->parameters))
01132                                 $this->resource .= $query;
01133                 }
01134                 $url = ((S3::$useSSL && extension_loaded('openssl')) ?
01135                 'https://':'http://').$this->headers['Host'].$this->uri;
01136                 //var_dump($this->bucket, $this->uri, $this->resource, $url);
01137 
01138                 // Basic setup
01139                 $curl = curl_init();
01140                 curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
01141 
01142                 if (S3::$useSSL) {
01143                         curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1);
01144                         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
01145                 }
01146 
01147                 curl_setopt($curl, CURLOPT_URL, $url);
01148 
01149                 // Headers
01150                 $headers = array(); $amz = array();
01151                 foreach ($this->amzHeaders as $header => $value)
01152                         if (strlen($value) > 0) $headers[] = $header.': '.$value;
01153                 foreach ($this->headers as $header => $value)
01154                         if (strlen($value) > 0) $headers[] = $header.': '.$value;
01155 
01156                 // Collect AMZ headers for signature
01157                 foreach ($this->amzHeaders as $header => $value)
01158                         if (strlen($value) > 0) $amz[] = strToLower($header).':'.$value;
01159 
01160                 // AMZ headers must be sorted
01161                 if (sizeof($amz) > 0) {
01162                         sort($amz);
01163                         $amz = "\n".implode("\n", $amz);
01164                 } else $amz = '';
01165 
01166                 // Authorization string (CloudFront stringToSign should only contain a date)
01167                 $headers[] = 'Authorization: ' . S3::__getSignature(
01168                         $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] :
01169                         $this->verb."\n".$this->headers['Content-MD5']."\n".
01170                         $this->headers['Content-Type']."\n".$this->headers['Date'].$amz."\n".$this->resource
01171                 );
01172 
01173                 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
01174                 curl_setopt($curl, CURLOPT_HEADER, false);
01175                 curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
01176                 curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
01177                 curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
01178                 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
01179 
01180                 // Request types
01181                 switch ($this->verb) {
01182                         case 'GET': break;
01183                         case 'PUT': case 'POST': // POST only used for CloudFront
01184                                 if ($this->fp !== false) {
01185                                         curl_setopt($curl, CURLOPT_PUT, true);
01186                                         curl_setopt($curl, CURLOPT_INFILE, $this->fp);
01187                                         if ($this->size > 0)
01188                                                 curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
01189                                 } elseif ($this->data !== false) {
01190                                         curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
01191                                         curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
01192                                         if ($this->size > 0)
01193                                                 curl_setopt($curl, CURLOPT_BUFFERSIZE, $this->size);
01194                                 } else
01195                                         curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
01196                         break;
01197                         case 'HEAD':
01198                                 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
01199                                 curl_setopt($curl, CURLOPT_NOBODY, true);
01200                         break;
01201                         case 'DELETE':
01202                                 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
01203                         break;
01204                         default: break;
01205                 }
01206 
01207                 // Execute, grab errors
01208                 if (curl_exec($curl))
01209                         $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
01210                 else
01211                         $this->response->error = array(
01212                                 'code' => curl_errno($curl),
01213                                 'message' => curl_error($curl),
01214                                 'resource' => $this->resource
01215                         );
01216 
01217                 @curl_close($curl);
01218 
01219                 // Parse body into XML
01220                 if ($this->response->error === false && isset($this->response->headers['type']) &&
01221                 $this->response->headers['type'] == 'application/xml' && isset($this->response->body)) {
01222                         $this->response->body = simplexml_load_string($this->response->body);
01223 
01224                         // Grab S3 errors
01225                         if (!in_array($this->response->code, array(200, 204)) &&
01226                         isset($this->response->body->Code, $this->response->body->Message)) {
01227                                 $this->response->error = array(
01228                                         'code' => (string)$this->response->body->Code,
01229                                         'message' => (string)$this->response->body->Message
01230                                 );
01231                                 if (isset($this->response->body->Resource))
01232                                         $this->response->error['resource'] = (string)$this->response->body->Resource;
01233                                 unset($this->response->body);
01234                         }
01235                 }
01236 
01237                 // Clean up file resources
01238                 if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
01239 
01240                 return $this->response;
01241         }
01242 
01243 
01251         private function __responseWriteCallback(&$curl, &$data) {
01252                 if ($this->response->code == 200 && $this->fp !== false)
01253                         return fwrite($this->fp, $data);
01254                 else
01255                         $this->response->body .= $data;
01256                 return strlen($data);
01257         }
01258 
01259 
01267         private function __responseHeaderCallback(&$curl, &$data) {
01268                 if (($strlen = strlen($data)) <= 2) return $strlen;
01269                 if (substr($data, 0, 4) == 'HTTP')
01270                         $this->response->code = (int)substr($data, 9, 3);
01271                 else {
01272                         list($header, $value) = explode(': ', trim($data), 2);
01273                         if ($header == 'Last-Modified')
01274                                 $this->response->headers['time'] = strtotime($value);
01275                         elseif ($header == 'Content-Length')
01276                                 $this->response->headers['size'] = (int)$value;
01277                         elseif ($header == 'Content-Type')
01278                                 $this->response->headers['type'] = $value;
01279                         elseif ($header == 'ETag')
01280                                 $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
01281                         elseif (preg_match('/^x-amz-meta-.*$/', $header))
01282                                 $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value;
01283                 }
01284                 return $strlen;
01285         }
01286 
01287 }
 All Data Structures Namespaces Files Functions Variables Enumerations