From f90901d26e3a42cab5781a31584fe825ceae5b2d Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Tue, 1 Oct 2019 16:51:41 +0100 Subject: [PATCH 01/19] Add support for per request endpoint config This makes it possible to customise the endpoint / hostname per request, rather than having the static / global state. NOTES: 1. A great level of effort has been put in to make sure backward compatibility changes to be minimal, but there are cases that couldn't have been avoided. Sorry peeps! 2. Also some custom business logic for bucket region deduction has been removed, and has been replaced with a callback. Other changes * Reduce global / static state in S3 class * Various efficiency tweaks * Various formatting tweaks --- S3.php | 1600 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 932 insertions(+), 668 deletions(-) diff --git a/S3.php b/S3.php index 60d08ae5..0c08fa4c 100644 --- a/S3.php +++ b/S3.php @@ -50,48 +50,33 @@ class S3 const SSE_AES256 = 'AES256'; /** - * The AWS Access key + * Default credentials to access AWS * - * @var string - * @access private - * @static + * @var Credentials|null */ - private static $__accessKey = null; + private static $__defaultCredentials; /** - * AWS Secret Key + * Default endpoint * - * @var string - * @access private - * @static + * @var EndpointConfig|null */ - private static $__secretKey = null; + private static $__defaultEndpoint; /** - * SSL Client key + * Callback to deduce auth region * - * @var string - * @access private - * @static + * @var callable|null */ - private static $__sslKey = null; + private static $__regionDeducer; /** * Default delimiter to be used, for example while getBucket(). - * @var string + * @var string|null * @access public * @static */ - public static $defDelimiter = null; - - /** - * AWS URI - * - * @var string - * @acess public - * @static - */ - public static $endpoint = 's3.amazonaws.com'; + public static $defDelimiter; /** * AWS Region @@ -102,42 +87,6 @@ class S3 */ public static $region = ''; - /** - * Proxy information - * - * @var null|array - * @access public - * @static - */ - public static $proxy = null; - - /** - * Connect using SSL? - * - * @var bool - * @access public - * @static - */ - public static $useSSL = false; - - /** - * Use SSL validation? - * - * @var bool - * @access public - * @static - */ - public static $useSSLValidation = true; - - /** - * Use SSL version - * - * @var const - * @access public - * @static - */ - public static $useSSLVersion = CURL_SSLVERSION_TLSv1; - /** * Use PHP exceptions? * @@ -154,46 +103,19 @@ class S3 */ private static $__timeOffset = 0; - /** - * SSL client key - * - * @var bool - * @access public - * @static - */ - public static $sslKey = null; - - /** - * SSL client certfificate - * - * @var string - * @acess public - * @static - */ - public static $sslCert = null; - - /** - * SSL CA cert (only required if you are having problems with your system CA cert) - * - * @var string - * @access public - * @static - */ - public static $sslCACert = null; - /** * AWS Key Pair ID * - * @var string + * @var string|null * @access private * @static */ - private static $__signingKeyPairId = null; + private static $__signingKeyPairId; /** * Key resource, freeSigningKey() must be called to clear it from memory * - * @var bool + * @var resource|bool * @access private * @static */ @@ -202,40 +124,65 @@ class S3 /** * CURL progress function callback * - * @var function + * @var callable|null * @access public * @static */ - public static $progressFunction = null; + public static $progressFunction; /** - * Constructor - if you're not using the class statically - * - * @param string $accessKey Access key - * @param string $secretKey Secret key - * @param boolean $useSSL Enable SSL - * @param string $endpoint Amazon URI - * @return void - */ + * Constructor - if you're not using the class statically + * + * @param string $accessKey Access key + * @param string $secretKey Secret key + * @param boolean $useSSL Enable SSL + * @param string $endpoint Amazon URI + * @param string $region AWS auth region for SigV4 + */ public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com', $region = '') { if ($accessKey !== null && $secretKey !== null) + { self::setAuth($accessKey, $secretKey); - self::$useSSL = $useSSL; - self::$endpoint = $endpoint; + } + + self::$__defaultEndpoint = new EndpointConfig(); + self::$__defaultEndpoint + ->withHostname($endpoint) + ->withSSLEnabled($useSSL); + self::$region = $region; } + /** + * Endpoint override helper + * + * @param EndpointConfig|null $endpoint + * @return EndpointConfig + */ + public static function getEndpoint(EndpointConfig $endpoint = null) + { + if ($endpoint !== null) { + return $endpoint; + } + + if (self::$__defaultEndpoint === null) { + self::$__defaultEndpoint = new EndpointConfig(); + } + + return self::$__defaultEndpoint; + } + /** - * Set the service endpoint - * - * @param string $host Hostname - * @return void - */ - public function setEndpoint($host) + * Set the service endpoint + * + * @param EndpointConfig $endpoint + * @return void + */ + public function setEndpoint(EndpointConfig $endpoint) { - self::$endpoint = $host; + self::$__defaultEndpoint = $endpoint; } @@ -252,26 +199,35 @@ public function setRegion($region) /** - * Get the service region - * - * @return string $region - * @static - */ - public static function getRegion() + * @param callable $deducer function to deduce auth region + */ + public static function setRegionDeducer($deducer) { - $region = self::$region; + self::$__regionDeducer = $deducer; + } + - // parse region from endpoint if not specific - if (empty($region)) + /** + * Get the service region + * + * @param $bucket + * @return string + * @static + */ + public static function getRegion($bucket) + { + if (!empty(self::$region)) { - if (preg_match("/s3[.-](?:website-|dualstack\.)?(.+)\.amazonaws\.com/i", self::$endpoint, $match) !== 0 - && strtolower($match[1]) !== "external-1") - { - $region = $match[1]; - } + return self::$region; } - return empty($region) ? 'us-east-1' : $region; + if (self::$__regionDeducer === null) + { + return ''; + } + + $endpoint = self::getEndpoint()->hostname; + return call_user_func(self::$__regionDeducer, $endpoint, $bucket); } @@ -284,8 +240,7 @@ public static function getRegion() */ public static function setAuth($accessKey, $secretKey) { - self::$__accessKey = $accessKey; - self::$__secretKey = $secretKey; + self::$__defaultCredentials = new Credentials($accessKey, $secretKey); } @@ -295,7 +250,37 @@ public static function setAuth($accessKey, $secretKey) * @return boolean */ public static function hasAuth() { - return (self::$__accessKey !== null && self::$__secretKey !== null); + return self::$__defaultCredentials !== null && self::$__defaultCredentials->isInitialised(); + } + + + /** + * Get access-key if set, otherwise null + * + * @return null|string + */ + public static function getAccessKey() + { + if (!self::hasAuth()) { + return null; + } + + return self::$__defaultCredentials->accessKey; + } + + + /** + * Get secret-key if set, otherwise null + * + * @return null|string + */ + public static function getSecretKey() + { + if (!self::hasAuth()) { + return null; + } + + return self::$__defaultCredentials->secretKey; } @@ -308,8 +293,9 @@ public static function hasAuth() { */ public static function setSSL($enabled, $validate = true) { - self::$useSSL = $enabled; - self::$useSSLValidation = $validate; + self::getEndpoint() + ->withSSLEnabled($enabled) + ->withSSLValidationEnabled($validate); } @@ -323,9 +309,10 @@ public static function setSSL($enabled, $validate = true) */ public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null) { - self::$sslCert = $sslCert; - self::$sslKey = $sslKey; - self::$sslCACert = $sslCACert; + self::getEndpoint() + ->withSSLEnabled() + ->withSSLValidationEnabled() + ->withSSLAuth($sslCert, $sslKey, $sslCACert); } @@ -335,12 +322,13 @@ public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = * @param string $host Proxy hostname and port (localhost:1234) * @param string $user Proxy username * @param string $pass Proxy password - * @param constant $type CURL proxy type + * @param int $type CURL proxy type (constants defined in curl module) * @return void */ public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5) { - self::$proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass); + self::getEndpoint() + ->withProxy($host, $user, $pass, $type); } @@ -357,20 +345,21 @@ public static function setExceptions($enabled = true) /** - * Set AWS time correction offset (use carefully) - * - * This can be used when an inaccurate system time is generating - * invalid request signatures. It should only be used as a last - * resort when the system time cannot be changed. - * - * @param string $offset Time offset (set to zero to use AWS server time) - * @return void - */ - public static function setTimeCorrectionOffset($offset = 0) + * Set AWS time correction offset (use carefully) + * + * This can be used when an inaccurate system time is generating + * invalid request signatures. It should only be used as a last + * resort when the system time cannot be changed. + * + * @param int $offset Time offset (set to zero to use AWS server time) + * @param EndpointConfig|null $endpoint + * @return void + */ + public static function setTimeCorrectionOffset($offset = 0, EndpointConfig $endpoint = null) { - if ($offset == 0) + if ($offset === 0) { - $rest = new S3Request('HEAD'); + $rest = new S3Request('HEAD', null, '', self::getEndpoint($endpoint)); $rest = $rest->getResponse(); $awstime = $rest->headers['date']; $systime = time(); @@ -381,20 +370,39 @@ public static function setTimeCorrectionOffset($offset = 0) /** - * Set signing key - * - * @param string $keyPairId AWS Key Pair ID - * @param string $signingKey Private Key - * @param boolean $isFile Load private key from file, set to false to load string - * @return boolean - */ + * Set signing key + * + * @param string $keyPairId AWS Key Pair ID + * @param string $signingKey Private Key + * @param boolean $isFile Load private key from file, set to false to load string + * @return boolean + * @throws S3Exception + */ public static function setSigningKey($keyPairId, $signingKey, $isFile = true) { self::$__signingKeyPairId = $keyPairId; - if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ? - file_get_contents($signingKey) : $signingKey)) !== false) return true; - self::__triggerError('S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__); - return false; + + if ($isFile) + { + $signingKeyMaterial = file_get_contents($signingKey); + } + else + { + $signingKeyMaterial = $signingKey; + } + + if ($signingKeyMaterial !== false) + { + self::$__signingKeyResource = openssl_pkey_get_private($signingKeyMaterial); + } + + if ($signingKeyMaterial === false || self::$__signingKeyResource === false) + { + self::__triggerError('S3::setSigningKey(): Unable to open load private key: ' . $signingKey, __FILE__, __LINE__); + return false; + } + + return true; } @@ -407,13 +415,15 @@ public static function setSigningKey($keyPairId, $signingKey, $isFile = true) public static function freeSigningKey() { if (self::$__signingKeyResource !== false) + { openssl_free_key(self::$__signingKeyResource); + } } /** * Set progress function * - * @param function $func Progress function + * @param callable $func Progress function * @return void */ public static function setProgressFunction($func = null) @@ -423,40 +433,45 @@ public static function setProgressFunction($func = null) /** - * Internal error handler - * - * @internal Internal error handler - * @param string $message Error message - * @param string $file Filename - * @param integer $line Line number - * @param integer $code Error code - * @return void - */ + * Internal error handler + * + * @param string $message Error message + * @param string $file Filename + * @param integer $line Line number + * @param integer $code Error code + * @return void + * @throws S3Exception + * @internal Internal error handler + */ private static function __triggerError($message, $file, $line, $code = 0) { if (self::$useExceptions) + { throw new S3Exception($message, $file, $line, $code); - else - trigger_error($message, E_USER_WARNING); + } + + trigger_error($message, E_USER_WARNING); } /** - * Get a list of buckets - * - * @param boolean $detailed Returns detailed bucket list when true - * @return array | false - */ - public static function listBuckets($detailed = false) + * Get a list of buckets + * + * @param boolean $detailed Returns detailed bucket list when true + * @param EndpointConfig|null $endpoint + * @return array | false + * @throws S3Exception + */ + public static function listBuckets($detailed = false, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', '', '', self::$endpoint); + $rest = new S3Request('GET', null, '', self::getEndpoint($endpoint)); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { self::__triggerError(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], - $rest->error['message']), __FILE__, __LINE__); + $rest->error['message']), __FILE__, __LINE__); return false; } $results = array(); @@ -481,22 +496,24 @@ public static function listBuckets($detailed = false) /** - * Get contents for a bucket - * - * If maxKeys is null this method will loop through truncated result sets - * - * @param string $bucket Bucket name - * @param string $prefix Prefix - * @param string $marker Marker (last file listed) - * @param string $maxKeys Max keys (maximum number of keys to return) - * @param string $delimiter Delimiter - * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes - * @return array | false - */ - public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false) + * Get contents for a bucket + * + * If maxKeys is null this method will loop through truncated result sets + * + * @param string|BucketConfig $bucket Bucket name + * @param string $prefix Prefix + * @param string $marker Marker (last file listed) + * @param string $maxKeys Max keys (maximum number of keys to return) + * @param string $delimiter Delimiter + * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes + * @param EndpointConfig|null $endpoint + * @return array | false + * @throws S3Exception + */ + public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', $bucket, '', self::$endpoint); - if ($maxKeys == 0) $maxKeys = null; + $rest = new S3Request('GET', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); + if ($maxKeys === 0) $maxKeys = null; if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker); if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys); @@ -508,7 +525,7 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe if ($response->error !== false) { self::__triggerError(sprintf("S3::getBucket(): [%s] %s", - $response->error['code'], $response->error['message']), __FILE__, __LINE__); + $response->error['code'], $response->error['message']), __FILE__, __LINE__); return false; } @@ -532,21 +549,21 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix); if (isset($response->body, $response->body->IsTruncated) && - (string)$response->body->IsTruncated == 'false') return $results; + (string)$response->body->IsTruncated === 'false') return $results; if (isset($response->body, $response->body->NextMarker)) $nextMarker = (string)$response->body->NextMarker; // Loop through truncated results if maxKeys isn't specified - if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true') + if ($maxKeys === null && $nextMarker !== null && (string)$response->body->IsTruncated === 'true') do { - $rest = new S3Request('GET', $bucket, '', self::$endpoint); + $rest = new S3Request('GET', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); $rest->setParameter('marker', $nextMarker); if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter); - if (($response = $rest->getResponse()) == false || $response->code !== 200) break; + if (($response = $rest->getResponse()) === false || $response->code !== 200) break; if (isset($response->body, $response->body->Contents)) foreach ($response->body->Contents as $c) @@ -567,68 +584,82 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe if (isset($response->body, $response->body->NextMarker)) $nextMarker = (string)$response->body->NextMarker; - } while ($response !== false && (string)$response->body->IsTruncated == 'true'); + } while ($response !== false && (string)$response->body->IsTruncated === 'true'); return $results; } /** - * Put a bucket - * - * @param string $bucket Bucket name - * @param constant $acl ACL flag - * @param string $location Set as "EU" to create buckets hosted in Europe - * @return boolean - */ - public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) + * Put a bucket + * + * @param string $bucket Bucket name + * @param string $acl ACL flag + * @param string|bool $location Set as "EU" to create buckets hosted in Europe + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false, EndpointConfig $endpoint = null) { - $rest = new S3Request('PUT', $bucket, '', self::$endpoint); + if ($location === false) + { + $location = self::getRegion($bucket); + if (empty($location)) + { + self::__triggerError("S3::putBucket({$bucket}, {$acl}, {$location}): Could not deduce region-code", __FILE__, __LINE__); + return false; + } + } + + $rest = new S3Request('PUT', new BucketConfig($bucket, $location), '', self::getEndpoint($endpoint)); $rest->setAmzHeader('x-amz-acl', $acl); - if ($location === false) $location = self::getRegion(); + $dom = new DOMDocument; + $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); + $locationConstraint = $dom->createElement('LocationConstraint', $location); + $createBucketConfiguration->appendChild($locationConstraint); + $dom->appendChild($createBucketConfiguration); + $rest->data = $dom->saveXML(); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); - if ($location !== false && $location !== "us-east-1") - { - $dom = new DOMDocument; - $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); - $locationConstraint = $dom->createElement('LocationConstraint', $location); - $createBucketConfiguration->appendChild($locationConstraint); - $dom->appendChild($createBucketConfiguration); - $rest->data = $dom->saveXML(); - $rest->size = strlen($rest->data); - $rest->setHeader('Content-Type', 'application/xml'); - } $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) + { $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + } + if ($rest->error !== false) { self::__triggerError(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } + return true; } /** - * Delete an empty bucket - * - * @param string $bucket Bucket name - * @return boolean - */ - public static function deleteBucket($bucket) + * Delete an empty bucket + * + * @param string|BucketConfig $bucket Bucket name + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function deleteBucket($bucket, EndpointConfig $endpoint = null) { - $rest = new S3Request('DELETE', $bucket, '', self::$endpoint); + $rest = new S3Request('DELETE', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 204) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { self::__triggerError(sprintf("S3::deleteBucket({$bucket}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } return true; @@ -636,12 +667,13 @@ public static function deleteBucket($bucket) /** - * Create input info array for putObject() - * - * @param string $file Input file - * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own) - * @return array | false - */ + * Create input info array for putObject() + * + * @param string $file Input file + * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own) + * @return array | false + * @throws S3Exception + */ public static function inputFile($file, $md5sum = true) { if (!file_exists($file) || !is_file($file) || !is_readable($file)) @@ -650,19 +682,31 @@ public static function inputFile($file, $md5sum = true) return false; } clearstatcache(false, $file); - return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ? - (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '', 'sha256sum' => hash_file('sha256', $file)); + + if ($md5sum === false) { + $md5sum = ''; + } elseif (!is_string($md5sum)) { + $md5sum = base64_encode(md5_file($file, true)); + } + + return array( + 'file' => $file, + 'size' => filesize($file), + 'md5sum' => $md5sum, + 'sha256sum' => hash_file('sha256', $file) + ); } /** - * Create input array info for putObject() with a resource - * - * @param string $resource Input resource to read from - * @param integer $bufferSize Input byte size - * @param string $md5sum MD5 hash to send (optional) - * @return array | false - */ + * Create input array info for putObject() with a resource + * + * @param resource $resource Input resource to read from + * @param int|bool $bufferSize Input byte size + * @param string $md5sum MD5 hash to send (optional) + * @return array | false + * @throws S3Exception + */ public static function inputResource(&$resource, $bufferSize = false, $md5sum = '') { if (!is_resource($resource) || (int)$bufferSize < 0) @@ -689,22 +733,24 @@ public static function inputResource(&$resource, $bufferSize = false, $md5sum = /** - * Put an object - * - * @param mixed $input Input data - * @param string $bucket Bucket name - * @param string $uri Object URI - * @param constant $acl ACL constant - * @param array $metaHeaders Array of x-amz-meta-* headers - * @param array $requestHeaders Array of request headers or content type as a string - * @param constant $storageClass Storage class constant - * @param constant $serverSideEncryption Server-side encryption - * @return boolean - */ - public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE) + * Put an object + * + * @param mixed $input Input data + * @param string|BucketConfig $bucket Bucket name + * @param string $uri Object URI + * @param string $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param array|string $requestHeaders Array of request headers or content type as a string + * @param string $storageClass Storage class constant + * @param string $serverSideEncryption Server-side encryption + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) { if ($input === false) return false; - $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint); + $rest = new S3Request('PUT', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); if (!is_array($input)) $input = array( 'data' => $input, 'size' => strlen($input), @@ -723,14 +769,12 @@ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE // Content-Length (required) if (isset($input['size']) && $input['size'] >= 0) $rest->size = $input['size']; - else { - if (isset($input['file'])) { - clearstatcache(false, $input['file']); - $rest->size = filesize($input['file']); - } - elseif (isset($input['data'])) - $rest->size = strlen($input['data']); + else if (isset($input['file'])) { + clearstatcache(false, $input['file']); + $rest->size = filesize($input['file']); } + elseif (isset($input['data'])) + $rest->size = strlen($input['data']); // Custom request headers (Content-Type, Content-Disposition, Content-Encoding) if (is_array($requestHeaders)) @@ -775,7 +819,7 @@ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE if ($rest->response->error !== false) { self::__triggerError(sprintf("S3::putObject(): [%s] %s", - $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__); + $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__); return false; } return true; @@ -783,59 +827,66 @@ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE /** - * Put an object from a file (legacy function) - * - * @param string $file Input file path - * @param string $bucket Bucket name - * @param string $uri Object URI - * @param constant $acl ACL constant - * @param array $metaHeaders Array of x-amz-meta-* headers - * @param string $contentType Content type - * @return boolean - */ - public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) + * Put an object from a file (legacy function) + * + * @param string $file Input file path + * @param string|BucketConfig $bucket Bucket name + * @param string $uri Object URI + * @param string $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param string $contentType Content type + * @param string $storageClass + * @param string $serverSideEncryption + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null, $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) { - return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType); + return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass, $serverSideEncryption, $endpoint); } /** - * Put an object from a string (legacy function) - * - * @param string $string Input data - * @param string $bucket Bucket name - * @param string $uri Object URI - * @param constant $acl ACL constant - * @param array $metaHeaders Array of x-amz-meta-* headers - * @param string $contentType Content type - * @return boolean - */ - public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain') + * Put an object from a string (legacy function) + * + * @param string $string Input data + * @param string|BucketConfig $bucket Bucket name + * @param string $uri Object URI + * @param string $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param string $contentType Content type + * @param string $storageClass + * @param string $serverSideEncryption + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain', $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) { - return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType); + return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass, $serverSideEncryption, $endpoint); } /** - * Get an object - * - * @param string $bucket Bucket name - * @param string $uri Object URI - * @param mixed $saveTo Filename or resource to write to - * @return mixed - */ - public static function getObject($bucket, $uri, $saveTo = false) + * Get an object + * + * @param string|BucketConfig $bucket Bucket name + * @param string $uri Object URI + * @param mixed $saveTo Filename or resource to write to + * @param EndpointConfig|null $endpoint + * @return mixed + * @throws S3Exception + */ + public static function getObject($bucket, $uri, $saveTo = false, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', $bucket, $uri, self::$endpoint); + $rest = new S3Request('GET', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); if ($saveTo !== false) { if (is_resource($saveTo)) $rest->fp =& $saveTo; - else - if (($rest->fp = @fopen($saveTo, 'wb')) !== false) - $rest->file = realpath($saveTo); - else - $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo); + else if (($rest->fp = @fopen($saveTo, 'wb')) === false) + $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: ' . $saveTo); } if ($rest->response->error === false) $rest->getResponse(); @@ -844,7 +895,7 @@ public static function getObject($bucket, $uri, $saveTo = false) if ($rest->response->error !== false) { self::__triggerError(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s", - $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__); + $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__); return false; } return $rest->response; @@ -852,45 +903,55 @@ public static function getObject($bucket, $uri, $saveTo = false) /** - * Get object information - * - * @param string $bucket Bucket name - * @param string $uri Object URI - * @param boolean $returnInfo Return response information - * @return mixed | false - */ - public static function getObjectInfo($bucket, $uri, $returnInfo = true) + * Get object information + * + * @param string|BucketConfig $bucket Bucket name + * @param string $uri Object URI + * @param boolean $returnInfo Return response information + * @param EndpointConfig|null $endpoint + * @return mixed | false + * @throws S3Exception + */ + public static function getObjectInfo($bucket, $uri, $returnInfo = true, EndpointConfig $endpoint = null) { - $rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint); + $rest = new S3Request('HEAD', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); $rest = $rest->getResponse(); if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404)) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { self::__triggerError(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + return false; + } + + if ($rest->code !== 200) + { return false; } - return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false; + + return $returnInfo ? $rest->headers : true; } /** - * Copy an object - * - * @param string $srcBucket Source bucket name - * @param string $srcUri Source object URI - * @param string $bucket Destination bucket name - * @param string $uri Destination object URI - * @param constant $acl ACL constant - * @param array $metaHeaders Optional array of x-amz-meta-* headers - * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.) - * @param constant $storageClass Storage class constant - * @return mixed | false - */ - public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD) + * Copy an object + * + * @param string $srcBucket Source bucket name + * @param string $srcUri Source object URI + * @param string|BucketConfig $bucket Destination bucket name + * @param string $uri Destination object URI + * @param string $acl ACL constant + * @param array $metaHeaders Optional array of x-amz-meta-* headers + * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.) + * @param string $storageClass Storage class constant + * @param EndpointConfig|null $endpoint + * @return mixed | false + * @throws S3Exception + */ + public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, EndpointConfig $endpoint = null) { - $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint); + $rest = new S3Request('PUT', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); $rest->setHeader('Content-Length', 0); foreach ($requestHeaders as $h => $v) strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v); @@ -899,7 +960,7 @@ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = sel $rest->setAmzHeader('x-amz-storage-class', $storageClass); $rest->setAmzHeader('x-amz-acl', $acl); $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri))); - if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0) + if (count($requestHeaders) > 0 || count($metaHeaders) > 0) $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE'); $rest = $rest->getResponse(); @@ -908,7 +969,7 @@ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = sel if ($rest->error !== false) { self::__triggerError(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } return isset($rest->body->LastModified, $rest->body->ETag) ? array( @@ -919,15 +980,17 @@ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = sel /** - * Set up a bucket redirection - * - * @param string $bucket Bucket name - * @param string $location Target host name - * @return boolean - */ - public static function setBucketRedirect($bucket = NULL, $location = NULL) + * Set up a bucket redirection + * + * @param string|BucketConfig $bucket Bucket name + * @param string $location Target host name + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function setBucketRedirect($bucket = NULL, $location = NULL, EndpointConfig $endpoint = null) { - $rest = new S3Request('PUT', $bucket, '', self::$endpoint); + $rest = new S3Request('PUT', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); if( empty($bucket) || empty($location) ) { self::__triggerError("S3::setBucketRedirect({$bucket}, {$location}): Empty parameter.", __FILE__, __LINE__); @@ -952,7 +1015,7 @@ public static function setBucketRedirect($bucket = NULL, $location = NULL) if ($rest->error !== false) { self::__triggerError(sprintf("S3::setBucketRedirect({$bucket}, {$location}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } return true; @@ -960,26 +1023,28 @@ public static function setBucketRedirect($bucket = NULL, $location = NULL) /** - * Set logging for a bucket - * - * @param string $bucket Bucket name - * @param string $targetBucket Target bucket (where logs are stored) - * @param string $targetPrefix Log prefix (e,g; domain.com-) - * @return boolean - */ - public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null) + * Set logging for a bucket + * + * @param string|BucketConfig $bucket Bucket name + * @param string $targetBucket Target bucket (where logs are stored) + * @param string $targetPrefix Log prefix (e,g; domain.com-) + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null, EndpointConfig $endpoint = null) { // The S3 log delivery group has to be added to the target bucket's ACP - if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false) + if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket)) !== false) { // Only add permissions to the target bucket when they do not exist $aclWriteSet = false; $aclReadSet = false; foreach ($acp['acl'] as $acl) - if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery') + if ($acl['type'] === 'Group' && $acl['uri'] === 'http://acs.amazonaws.com/groups/s3/LogDelivery') { - if ($acl['permission'] == 'WRITE') $aclWriteSet = true; - elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true; + if ($acl['permission'] === 'WRITE') $aclWriteSet = true; + elseif ($acl['permission'] === 'READ_ACP') $aclReadSet = true; } if (!$aclWriteSet) $acp['acl'][] = array( 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE' @@ -995,7 +1060,7 @@ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/'); if ($targetBucket !== null) { - if ($targetPrefix == null) $targetPrefix = $bucket . '-'; + if ($targetPrefix === null) $targetPrefix = $bucket . '-'; $loggingEnabled = $dom->createElement('LoggingEnabled'); $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket)); $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix)); @@ -1004,7 +1069,7 @@ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = } $dom->appendChild($bucketLoggingStatus); - $rest = new S3Request('PUT', $bucket, '', self::$endpoint); + $rest = new S3Request('PUT', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); $rest->setParameter('logging', null); $rest->data = $dom->saveXML(); $rest->size = strlen($rest->data); @@ -1015,25 +1080,27 @@ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = if ($rest->error !== false) { self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } return true; } - - - /** - * Get logging status for a bucket - * - * This will return false if logging is not enabled. - * Note: To enable logging, you also need to grant write access to the log group - * - * @param string $bucket Bucket name - * @return array | false - */ - public static function getBucketLogging($bucket) + + + /** + * Get logging status for a bucket + * + * This will return false if logging is not enabled. + * Note: To enable logging, you also need to grant write access to the log group + * + * @param string|BucketConfig $bucket Bucket name + * @param EndpointConfig|null $endpoint + * @return array | false + * @throws S3Exception + */ + public static function getBucketLogging($bucket, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', $bucket, '', self::$endpoint); + $rest = new S3Request('GET', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); $rest->setParameter('logging', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1041,7 +1108,7 @@ public static function getBucketLogging($bucket) if ($rest->error !== false) { self::__triggerError(sprintf("S3::getBucketLogging({$bucket}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } if (!isset($rest->body->LoggingEnabled)) return false; // No logging @@ -1053,26 +1120,37 @@ public static function getBucketLogging($bucket) /** - * Disable bucket logging - * - * @param string $bucket Bucket name - * @return boolean - */ - public static function disableBucketLogging($bucket) + * Disable bucket logging + * + * @param string|BucketConfig $bucket Bucket name + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function disableBucketLogging($bucket, EndpointConfig $endpoint = null) { - return self::setBucketLogging($bucket, null); + return self::setBucketLogging($bucket, null, $endpoint); } /** - * Get a bucket's location - * - * @param string $bucket Bucket name - * @return string | false - */ - public static function getBucketLocation($bucket) + * Get a bucket's location + * + * @param string|BucketConfig $bucket Bucket name + * @param EndpointConfig|null $endpoint + * @return string | false + * @throws S3Exception + */ + public static function getBucketLocation($bucket, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', $bucket, '', self::$endpoint); + $bucket = self::makeBucketConfig($bucket); + if (empty($bucket->authRegion)) { + // https://github.com/aws/aws-sdk-js/issues/462 + // Setting up any region other than 'us-east-1' + $bucket->authRegion = 'us-west-2'; + } + + $rest = new S3Request('GET', $bucket, '', self::getEndpoint($endpoint)); $rest->setParameter('location', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1080,7 +1158,7 @@ public static function getBucketLocation($bucket) if ($rest->error !== false) { self::__triggerError(sprintf("S3::getBucketLocation({$bucket}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US'; @@ -1088,14 +1166,16 @@ public static function getBucketLocation($bucket) /** - * Set object or bucket Access Control Policy - * - * @param string $bucket Bucket name - * @param string $uri Object URI - * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy) - * @return boolean - */ - public static function setAccessControlPolicy($bucket, $uri = '', $acp = array()) + * Set object or bucket Access Control Policy + * + * @param string|BucketConfig $bucket Bucket name + * @param string $uri Object URI + * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy) + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function setAccessControlPolicy($bucket, $uri = '', $acp = array(), EndpointConfig $endpoint = null) { $dom = new DOMDocument; $dom->formatOutput = true; @@ -1123,7 +1203,7 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail'); $grantee->appendChild($dom->createElement('EmailAddress', $g['email'])); } - elseif ($g['type'] == 'Group') + elseif ($g['type'] === 'Group') { // Group $grantee->setAttribute('xsi:type', 'Group'); $grantee->appendChild($dom->createElement('URI', $g['uri'])); @@ -1136,7 +1216,7 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() $accessControlPolicy->appendChild($accessControlList); $dom->appendChild($accessControlPolicy); - $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint); + $rest = new S3Request('PUT', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); $rest->setParameter('acl', null); $rest->data = $dom->saveXML(); $rest->size = strlen($rest->data); @@ -1147,7 +1227,7 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() if ($rest->error !== false) { self::__triggerError(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } return true; @@ -1155,15 +1235,17 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() /** - * Get object or bucket Access Control Policy - * - * @param string $bucket Bucket name - * @param string $uri Object URI - * @return mixed | false - */ - public static function getAccessControlPolicy($bucket, $uri = '') + * Get object or bucket Access Control Policy + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param EndpointConfig|null $endpoint + * @return mixed | false + * @throws S3Exception + */ + public static function getAccessControlPolicy($bucket, $uri = '', EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', $bucket, $uri, self::$endpoint); + $rest = new S3Request('GET', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); $rest->setParameter('acl', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1171,7 +1253,7 @@ public static function getAccessControlPolicy($bucket, $uri = '') if ($rest->error !== false) { self::__triggerError(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } @@ -1216,22 +1298,24 @@ public static function getAccessControlPolicy($bucket, $uri = '') /** - * Delete an object - * - * @param string $bucket Bucket name - * @param string $uri Object URI - * @return boolean - */ - public static function deleteObject($bucket, $uri) + * Delete an object + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param EndpointConfig|null $endpoint + * @return boolean + * @throws S3Exception + */ + public static function deleteObject($bucket, $uri, EndpointConfig $endpoint = null) { - $rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint); + $rest = new S3Request('DELETE', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 204) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { self::__triggerError(sprintf("S3::deleteObject(): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } return true; @@ -1254,7 +1338,7 @@ public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri)); return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s', // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires, - $hostBucket ? $bucket : self::$endpoint.'/'.$bucket, $uri, self::$__accessKey, $expires, + $hostBucket ? $bucket : self::getEndpoint()->hostname.'/'.$bucket, $uri, self::getAccessKey(), $expires, urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}"))); } @@ -1305,7 +1389,7 @@ public static function getSignedCannedURL($url, $lifetime) * * @param string $bucket Bucket name * @param string $uriPrefix Object URI prefix - * @param constant $acl ACL constant + * @param string $acl ACL constant * @param integer $lifetime Lifetime in seconds * @param integer $maxFileSize Maximum filesize in bytes (default 5MB) * @param string $successRedirect Redirect URL or 200 / 201 status code @@ -1321,40 +1405,40 @@ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = $policy = new stdClass; $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (self::__getTime() + $lifetime)); $policy->conditions = array(); - $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj); - $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj); + $obj = new stdClass; $obj->bucket = $bucket; $policy->conditions[] = $obj; + $obj = new stdClass; $obj->acl = $acl; $policy->conditions[] = $obj; $obj = new stdClass; // 200 for non-redirect uploads - if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201))) + if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201), true)) $obj->success_action_status = (string)$successRedirect; else // URL $obj->success_action_redirect = $successRedirect; - array_push($policy->conditions, $obj); + $policy->conditions[] = $obj; if ($acl !== self::ACL_PUBLIC_READ) - array_push($policy->conditions, array('eq', '$acl', $acl)); + $policy->conditions[] = array('eq', '$acl', $acl); - array_push($policy->conditions, array('starts-with', '$key', $uriPrefix)); - if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', '')); + $policy->conditions[] = array('starts-with', '$key', $uriPrefix); + if ($flashVars) $policy->conditions[] = array('starts-with', '$Filename', ''); foreach (array_keys($headers) as $headerKey) - array_push($policy->conditions, array('starts-with', '$'.$headerKey, '')); + $policy->conditions[] = array('starts-with', '$' . $headerKey, ''); foreach ($amzHeaders as $headerKey => $headerVal) { $obj = new stdClass; $obj->{$headerKey} = (string)$headerVal; - array_push($policy->conditions, $obj); + $policy->conditions[] = $obj; } - array_push($policy->conditions, array('content-length-range', 0, $maxFileSize)); + $policy->conditions[] = array('content-length-range', 0, $maxFileSize); $policy = base64_encode(str_replace('\/', '/', json_encode($policy))); // Create parameters $params = new stdClass; - $params->AWSAccessKeyId = self::$__accessKey; + $params->AWSAccessKeyId = self::getAccessKey(); $params->key = $uriPrefix.'${filename}'; $params->acl = $acl; $params->policy = $policy; unset($policy); $params->signature = self::__getHash($params->policy); - if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201))) + if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201), true)) $params->success_action_status = (string)$successRedirect; else $params->success_action_redirect = $successRedirect; @@ -1365,29 +1449,29 @@ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = /** - * Create a CloudFront distribution - * - * @param string $bucket Bucket name - * @param boolean $enabled Enabled (true/false) - * @param array $cnames Array containing CNAME aliases - * @param string $comment Use the bucket name as the hostname - * @param string $defaultRootObject Default root object - * @param string $originAccessIdentity Origin access identity - * @param array $trustedSigners Array of trusted signers - * @return array | false - */ + * Create a CloudFront distribution + * + * @param string $bucket Bucket name + * @param boolean $enabled Enabled (true/false) + * @param array $cnames Array containing CNAME aliases + * @param string $comment Use the bucket name as the hostname + * @param string $defaultRootObject Default root object + * @param string $originAccessIdentity Origin access identity + * @param array $trustedSigners Array of trusted signers + * @return array | false + * @throws S3Exception + */ public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array()) { if (!extension_loaded('openssl')) { - self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s", - "CloudFront functionality requires SSL"), __FILE__, __LINE__); + self::__triggerError(sprintf("S3::createDistribution({$bucket}, " . (int)$enabled . ", [], '$comment'): %s", + "CloudFront functionality requires SSL"), __FILE__, __LINE__); return false; } - $useSSL = self::$useSSL; - self::$useSSL = true; // CloudFront requires SSL - $rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $rest = new S3Request('POST', null, '2010-11-01/distribution', $cloudfrontEndpoint); $rest->data = self::__getCloudFrontDistributionConfigXML( $bucket.'.s3.amazonaws.com', $enabled, @@ -1403,14 +1487,12 @@ public static function createDistribution($bucket, $enabled = true, $cnames = ar $rest->setHeader('Content-Type', 'application/xml'); $rest = self::__getCloudFrontResponse($rest); - self::$useSSL = $useSSL; - if ($rest->error === false && $rest->code !== 201) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { - self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + self::__triggerError(sprintf("S3::createDistribution({$bucket}, " . (int)$enabled . ", [], '$comment'): [%s] %s", + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } elseif ($rest->body instanceof SimpleXMLElement) return self::__parseCloudFrontDistributionConfig($rest->body); @@ -1419,65 +1501,64 @@ public static function createDistribution($bucket, $enabled = true, $cnames = ar /** - * Get CloudFront distribution info - * - * @param string $distributionId Distribution ID from listDistributions() - * @return array | false - */ + * Get CloudFront distribution info + * + * @param string $distributionId Distribution ID from listDistributions() + * @return array | false + * @throws S3Exception + */ public static function getDistribution($distributionId) { if (!extension_loaded('openssl')) { self::__triggerError(sprintf("S3::getDistribution($distributionId): %s", - "CloudFront functionality requires SSL"), __FILE__, __LINE__); + "CloudFront functionality requires SSL"), __FILE__, __LINE__); return false; } - $useSSL = self::$useSSL; - self::$useSSL = true; // CloudFront requires SSL - $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $rest = new S3Request('GET', null, '2010-11-01/distribution/'.$distributionId, $cloudfrontEndpoint); $rest = self::__getCloudFrontResponse($rest); - self::$useSSL = $useSSL; - if ($rest->error === false && $rest->code !== 200) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } - elseif ($rest->body instanceof SimpleXMLElement) + + if ($rest->body instanceof SimpleXMLElement) { $dist = self::__parseCloudFrontDistributionConfig($rest->body); $dist['hash'] = $rest->headers['hash']; $dist['id'] = $distributionId; return $dist; } + return false; } /** - * Update a CloudFront distribution - * - * @param array $dist Distribution array info identical to output of getDistribution() - * @return array | false - */ + * Update a CloudFront distribution + * + * @param array $dist Distribution array info identical to output of getDistribution() + * @return array | false + * @throws S3Exception + */ public static function updateDistribution($dist) { if (!extension_loaded('openssl')) { self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s", - "CloudFront functionality requires SSL"), __FILE__, __LINE__); + "CloudFront functionality requires SSL"), __FILE__, __LINE__); return false; } - $useSSL = self::$useSSL; - - self::$useSSL = true; // CloudFront requires SSL - $rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $rest = new S3Request('PUT', null, '2010-11-01/distribution/'.$dist['id'].'/config', $cloudfrontEndpoint); $rest->data = self::__getCloudFrontDistributionConfigXML( $dist['origin'], $dist['enabled'], @@ -1493,54 +1574,48 @@ public static function updateDistribution($dist) $rest->setHeader('If-Match', $dist['hash']); $rest = self::__getCloudFrontResponse($rest); - self::$useSSL = $useSSL; - if ($rest->error === false && $rest->code !== 200) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; - } else { - $dist = self::__parseCloudFrontDistributionConfig($rest->body); - $dist['hash'] = $rest->headers['hash']; - return $dist; } - return false; + + $dist = self::__parseCloudFrontDistributionConfig($rest->body); + $dist['hash'] = $rest->headers['hash']; + return $dist; } /** - * Delete a CloudFront distribution - * - * @param array $dist Distribution array info identical to output of getDistribution() - * @return boolean - */ + * Delete a CloudFront distribution + * + * @param array $dist Distribution array info identical to output of getDistribution() + * @return boolean + * @throws S3Exception + */ public static function deleteDistribution($dist) { if (!extension_loaded('openssl')) { self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s", - "CloudFront functionality requires SSL"), __FILE__, __LINE__); + "CloudFront functionality requires SSL"), __FILE__, __LINE__); return false; } - $useSSL = self::$useSSL; - - self::$useSSL = true; // CloudFront requires SSL - $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $rest = new S3Request('DELETE', null, '2008-06-30/distribution/'.$dist['id'], $cloudfrontEndpoint); $rest->setHeader('If-Match', $dist['hash']); $rest = self::__getCloudFrontResponse($rest); - self::$useSSL = $useSSL; - if ($rest->error === false && $rest->code !== 204) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } return true; @@ -1548,42 +1623,35 @@ public static function deleteDistribution($dist) /** - * Get a list of CloudFront distributions - * - * @return array - */ + * Get a list of CloudFront distributions + * + * @return array|bool + * @throws S3Exception + */ public static function listDistributions() { if (!extension_loaded('openssl')) { - self::__triggerError(sprintf("S3::listDistributions(): [%s] %s", - "CloudFront functionality requires SSL"), __FILE__, __LINE__); + self::__triggerError('S3::listDistributions(): CloudFront functionality requires SSL', __FILE__, __LINE__); return false; } - $useSSL = self::$useSSL; - self::$useSSL = true; // CloudFront requires SSL - $rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $rest = new S3Request('GET', null, '2010-11-01/distribution', $cloudfrontEndpoint); $rest = self::__getCloudFrontResponse($rest); - self::$useSSL = $useSSL; if ($rest->error === false && $rest->code !== 200) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { - self::__triggerError(sprintf("S3::listDistributions(): [%s] %s", - $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); + self::__triggerError(sprintf('S3::listDistributions(): [%s] %s', + $rest->error['code'], $rest->error['message']), __FILE__, __LINE__); return false; } - elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary)) + + if ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary)) { $list = array(); - if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated)) - { - //$info['marker'] = (string)$rest->body->Marker; - //$info['maxItems'] = (int)$rest->body->MaxItems; - //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false; - } foreach ($rest->body->DistributionSummary as $summary) $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary); @@ -1593,23 +1661,22 @@ public static function listDistributions() } /** - * List CloudFront Origin Access Identities - * - * @return array - */ + * List CloudFront Origin Access Identities + * + * @return array|bool + * @throws S3Exception + */ public static function listOriginAccessIdentities() { if (!extension_loaded('openssl')) { - self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s", - "CloudFront functionality requires SSL"), __FILE__, __LINE__); + self::__triggerError('S3::listOriginAccessIdentities(): CloudFront functionality requires SSL', __FILE__, __LINE__); return false; } - self::$useSSL = true; // CloudFront requires SSL - $rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $rest = new S3Request('GET', null, '2010-11-01/origin-access-identity/cloudfront', $cloudfrontEndpoint); $rest = self::__getCloudFrontResponse($rest); - $useSSL = self::$useSSL; if ($rest->error === false && $rest->code !== 200) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -1633,30 +1700,29 @@ public static function listOriginAccessIdentities() /** - * Invalidate objects in a CloudFront distribution - * - * Thanks to Martin Lindkvist for S3::invalidateDistribution() - * - * @param string $distributionId Distribution ID from listDistributions() - * @param array $paths Array of object paths to invalidate - * @return boolean - */ + * Invalidate objects in a CloudFront distribution + * + * Thanks to Martin Lindkvist for S3::invalidateDistribution() + * + * @param string $distributionId Distribution ID from listDistributions() + * @param array $paths Array of object paths to invalidate + * @return bool + * @throws S3Exception + */ public static function invalidateDistribution($distributionId, $paths) { if (!extension_loaded('openssl')) { - self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s", - "CloudFront functionality requires SSL"), __FILE__, __LINE__); + self::__triggerError('S3::invalidateDistribution(): CloudFront functionality requires SSL', __FILE__, __LINE__); return false; } - $useSSL = self::$useSSL; - self::$useSSL = true; // CloudFront requires SSL - $rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + + $rest = new S3Request('POST', null, '2010-08-01/distribution/'.$distributionId.'/invalidation', $cloudfrontEndpoint); $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true)); $rest->size = strlen($rest->data); $rest = self::__getCloudFrontResponse($rest); - self::$useSSL = $useSSL; if ($rest->error === false && $rest->code !== 201) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -1671,13 +1737,13 @@ public static function invalidateDistribution($distributionId, $paths) /** - * Get a InvalidationBatch DOMDocument - * - * @internal Used to create XML in invalidateDistribution() - * @param array $paths Paths to objects to invalidateDistribution - * @param int $callerReference - * @return string - */ + * Get a InvalidationBatch DOMDocument + * + * @param array $paths Paths to objects to invalidateDistribution + * @param string $callerReference + * @return string + * @internal Used to create XML in invalidateDistribution() + */ private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') { $dom = new DOMDocument('1.0', 'UTF-8'); @@ -1693,45 +1759,45 @@ private static function __getCloudFrontInvalidationBatchXML($paths, $callerRefer /** - * List your invalidation batches for invalidateDistribution() in a CloudFront distribution - * - * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html - * returned array looks like this: - * Array - * ( - * [I31TWB0CN9V6XD] => InProgress - * [IT3TFE31M0IHZ] => Completed - * [I12HK7MPO1UQDA] => Completed - * [I1IA7R6JKTC3L2] => Completed - * ) - * - * @param string $distributionId Distribution ID from listDistributions() - * @return array - */ + * List your invalidation batches for invalidateDistribution() in a CloudFront distribution + * + * http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html + * returned array looks like this: + * Array + * ( + * [I31TWB0CN9V6XD] => InProgress + * [IT3TFE31M0IHZ] => Completed + * [I12HK7MPO1UQDA] => Completed + * [I1IA7R6JKTC3L2] => Completed + * ) + * + * @param string $distributionId Distribution ID from listDistributions() + * @return array|bool + * @throws S3Exception + */ public static function getDistributionInvalidationList($distributionId) { if (!extension_loaded('openssl')) { - self::__triggerError(sprintf("S3::getDistributionInvalidationList(): [%s] %s", - "CloudFront functionality requires SSL"), __FILE__, __LINE__); + self::__triggerError('S3::getDistributionInvalidationList(): CloudFront functionality requires SSL', __FILE__, __LINE__); return false; } - $useSSL = self::$useSSL; - self::$useSSL = true; // CloudFront requires SSL - $rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + + $rest = new S3Request('GET', null, '2010-11-01/distribution/'.$distributionId.'/invalidation', $cloudfrontEndpoint); $rest = self::__getCloudFrontResponse($rest); - self::$useSSL = $useSSL; if ($rest->error === false && $rest->code !== 200) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); if ($rest->error !== false) { - trigger_error(sprintf("S3::getDistributionInvalidationList('{$distributionId}'): [%s]", - $rest->error['code'], $rest->error['message']), E_USER_WARNING); + trigger_error(sprintf("S3::getDistributionInvalidationList('{$distributionId}'): [%s]: %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); return false; } - elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary)) + + if ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary)) { $list = array(); foreach ($rest->body->InvalidationSummary as $summary) @@ -1739,6 +1805,7 @@ public static function getDistributionInvalidationList($distributionId) return $list; } + return array(); } @@ -1819,7 +1886,7 @@ private static function __parseCloudFrontDistributionConfig(&$node) $dist['callerReference'] = (string)$node->CallerReference; if (isset($node->Enabled)) - $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false; + $dist['enabled'] = (string)$node->Enabled === 'true'; if (isset($node->S3Origin)) { @@ -1865,7 +1932,7 @@ private static function __getCloudFrontResponse(&$rest) { $rest->getResponse(); if ($rest->response->error === false && isset($rest->response->body) && - is_string($rest->response->body) && substr($rest->response->body, 0, 5) == 'response->body) && strpos($rest->response->body, 'response->body = simplexml_load_string($rest->response->body); // Grab CloudFront errors @@ -1918,7 +1985,7 @@ private static function __getMIMEType(&$file) if (isset($exts[$ext])) return $exts[$ext]; // Use fileinfo if available - if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) && + if (isset($_ENV['MAGIC']) && extension_loaded('fileinfo') && ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) { if (($type = finfo_file($finfo, $file)) !== false) @@ -1930,7 +1997,7 @@ private static function __getMIMEType(&$file) $type = trim(array_shift($type)); } finfo_close($finfo); - if ($type !== false && strlen($type) > 0) return $type; + if ($type !== false && $type !== '') return $type; } return 'application/octet-stream'; @@ -1958,7 +2025,7 @@ public static function __getTime() */ public static function __getSignature($string) { - return 'AWS '.self::$__accessKey.':'.self::__getHash($string); + return 'AWS '.self::getAccessKey().':'.self::__getHash($string); } @@ -1974,28 +2041,28 @@ public static function __getSignature($string) private static function __getHash($string) { return base64_encode(extension_loaded('hash') ? - hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1( - (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . - pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^ + hash_hmac('sha1', $string, self::getSecretKey(), true) : pack('H*', sha1( + (str_pad(self::getSecretKey(), 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . + pack('H*', sha1((str_pad(self::getSecretKey(), 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) . $string))))); } /** - * Generate the headers for AWS Signature V4 - * - * @internal Used by S3Request::getResponse() - * @param array $amzHeaders - * @param array $headers - * @param string $method - * @param string $uri - * @param array $parameters - * @return array - */ - public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $parameters) + * Generate the headers for AWS Signature V4 + * + * @param array $amzHeaders + * @param array $headers + * @param string $method + * @param string $uri + * @param array $parameters + * @param string $region + * @return string + * @internal Used by S3Request::getResponse() + */ + public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $parameters, $region) { $service = 's3'; - $region = S3::getRegion(); $algorithm = 'AWS4-HMAC-SHA256'; $combinedHeaders = array(); @@ -2043,7 +2110,7 @@ public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $p implode('/', $credentialScope), hash('sha256', $amzPayloadStr))); // Make Signature - $kSecret = 'AWS4' . self::$__secretKey; + $kSecret = 'AWS4' . self::getSecretKey(); $kDate = hash_hmac('sha256', $amzDateStamp, $kSecret, true); $kRegion = hash_hmac('sha256', $region, $kDate, true); $kService = hash_hmac('sha256', $service, $kRegion, true); @@ -2052,7 +2119,7 @@ public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $p $signature = hash_hmac('sha256', $stringToSignStr, $kSigning); return $algorithm . ' ' . implode(',', array( - 'Credential=' . self::$__accessKey . '/' . implode('/', $credentialScope), + 'Credential=' . self::getAccessKey() . '/' . implode('/', $credentialScope), 'SignedHeaders=' . implode(';', array_keys($combinedHeaders)), 'Signature=' . $signature, )); @@ -2073,10 +2140,26 @@ private static function __sortMetaHeadersCmp($a, $b) $lenB = strlen($b); $minLen = min($lenA, $lenB); $ncmp = strncmp($a, $b, $minLen); - if ($lenA == $lenB) return $ncmp; - if (0 == $ncmp) return $lenA < $lenB ? -1 : 1; + if ($lenA === $lenB) return $ncmp; + if (0 === $ncmp) return $lenA < $lenB ? -1 : 1; return $ncmp; } + + + /** + * Helper to transition from bucket names to bucket-config + * + * @param string|BucketConfig $bucket + * @return BucketConfig + */ + private static function makeBucketConfig($bucket) + { + if ($bucket instanceof BucketConfig) { + return $bucket; + } + + return new BucketConfig($bucket, self::getRegion($bucket)); + } } /** @@ -2088,25 +2171,23 @@ private static function __sortMetaHeadersCmp($a, $b) final class S3Request { /** - * AWS URI + * endpoint config * - * @var string - * @access private + * @var EndpointConfig */ private $endpoint; - + /** - * Verb + * HTTP Verb * * @var string - * @access private */ private $verb; /** - * S3 bucket name + * bucket config * - * @var string + * @var BucketConfig * @access private */ private $bucket; @@ -2156,7 +2237,7 @@ final class S3Request /** * Use HTTP PUT? * - * @var bool + * @var resource * @access public */ public $fp = false; @@ -2172,7 +2253,7 @@ final class S3Request /** * PUT post fields * - * @var array + * @var string * @access public */ public $data = false; @@ -2187,45 +2268,42 @@ final class S3Request /** - * Constructor - * - * @param string $verb Verb - * @param string $bucket Bucket name - * @param string $uri Object URI - * @param string $endpoint AWS endpoint URI - * @return mixed - */ - function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com') + * Constructor + * + * @param string $verb Verb + * @param BucketConfig $bucket Bucket config + * @param string $uri Object URI + * @param EndpointConfig $endpoint AWS endpoint URI + */ + public function __construct($verb, $bucket = null, $uri = '', $endpoint = null) { $this->endpoint = $endpoint; $this->verb = $verb; $this->bucket = $bucket; $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/'; - if ($this->bucket !== '') + if ($bucket !== null) { - if ($this->__dnsBucketName($this->bucket)) + if ($endpoint->forcePathStyle !== true && $this->__dnsBucketName($bucket->name, $endpoint->useSSL)) { - $this->headers['Host'] = $this->bucket.'.'.$this->endpoint; - $this->resource = '/'.$this->bucket.$this->uri; + $this->headers['Host'] = $bucket->name.'.'.$this->endpoint->hostname; + $this->resource = '/'.$bucket->name.$this->uri; } else { - // Old format, deprecated by AWS - removal scheduled for September 30th, 2020 - $this->headers['Host'] = $this->endpoint; - $this->uri = $this->uri; - if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri; - $this->bucket = ''; + // Old format, deprecated by AWS - removal scheduled for September 30th, 2020, + // but still useful for S3 compatible storage access, like https://min.io + $this->headers['Host'] = $this->endpoint->hostname; + $this->uri = '/'.$bucket->name.$this->uri; $this->resource = $this->uri; } } else { - $this->headers['Host'] = $this->endpoint; + $this->headers['Host'] = $this->endpoint->hostname; $this->resource = $this->uri; } - $this->headers['Date'] = gmdate('D, d M Y H:i:s T'); $this->response = new STDClass; $this->response->error = false; @@ -2280,12 +2358,11 @@ public function setAmzHeader($key, $value) */ public function getResponse() { - $query = ''; - if (sizeof($this->parameters) > 0) + if (count($this->parameters) > 0) { $query = substr($this->uri, -1) !== '?' ? '?' : '&'; foreach ($this->parameters as $var => $value) - if ($value == null || $value == '') $query .= $var.'&'; + if ($value === null || $value === '') $query .= $var.'&'; else $query .= $var.'='.rawurlencode($value).'&'; $query = substr($query, 0, -1); $this->uri .= $query; @@ -2297,34 +2374,35 @@ public function getResponse() array_key_exists('logging', $this->parameters)) $this->resource .= $query; } - $url = (S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri; + $url = ($this->endpoint->useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri; // Basic setup $curl = curl_init(); curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php'); - if (S3::$useSSL) + if ($this->endpoint->useSSL) { // Set protocol version - curl_setopt($curl, CURLOPT_SSLVERSION, S3::$useSSLVersion); + curl_setopt($curl, CURLOPT_SSLVERSION, $this->endpoint->useSSLVersion); // SSL Validation can now be optional for those with broken OpenSSL installations - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 2 : 0); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::getEndpoint()->useSSLValidation ? 2 : 0); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::getEndpoint()->useSSLValidation ? 1 : 0); - if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey); - if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert); - if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert); + if ($this->endpoint->sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, $this->endpoint->sslKey); + if ($this->endpoint->sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, $this->endpoint->sslCert); + if ($this->endpoint->sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, $this->endpoint->sslCACert); } curl_setopt($curl, CURLOPT_URL, $url); - if (S3::$proxy != null && isset(S3::$proxy['host'])) + if ($this->endpoint->proxy !== null && isset($this->endpoint->proxy['host'])) { - curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']); - curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']); - if (isset(S3::$proxy['user'], S3::$proxy['pass']) && S3::$proxy['user'] != null && S3::$proxy['pass'] != null) - curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass'])); + curl_setopt($curl, CURLOPT_PROXY, $this->endpoint->proxy['host']); + curl_setopt($curl, CURLOPT_PROXYTYPE, $this->endpoint->proxy['type']); + /** @noinspection NotOptimalIfConditionsInspection */ + if (isset($this->endpoint->proxy['user'], $this->endpoint->proxy['pass']) && $this->endpoint->proxy['user'] !== null && $this->endpoint->proxy['pass'] !== null) + curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', $this->endpoint->proxy['user'], $this->endpoint->proxy['pass'])); } // Headers @@ -2332,14 +2410,14 @@ public function getResponse() if (S3::hasAuth()) { // Authorization string (CloudFront stringToSign should only contain a date) - if ($this->headers['Host'] == 'cloudfront.amazonaws.com') + if ($this->headers['Host'] === 'cloudfront.amazonaws.com') { # TODO: Update CloudFront authentication foreach ($this->amzHeaders as $header => $value) - if (strlen($value) > 0) $httpHeaders[] = $header.': '.$value; + if ($value !== '') $httpHeaders[] = $header.': '.$value; foreach ($this->headers as $header => $value) - if (strlen($value) > 0) $httpHeaders[] = $header.': '.$value; + if ($value !== '') $httpHeaders[] = $header.': '.$value; $httpHeaders[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']); } @@ -2351,17 +2429,18 @@ public function getResponse() $this->amzHeaders['x-amz-content-sha256'] = hash('sha256', $this->data); foreach ($this->amzHeaders as $header => $value) - if (strlen($value) > 0) $httpHeaders[] = $header.': '.$value; + if ($value !== '') $httpHeaders[] = $header.': '.$value; foreach ($this->headers as $header => $value) - if (strlen($value) > 0) $httpHeaders[] = $header.': '.$value; + if ($value !== '') $httpHeaders[] = $header.': '.$value; $httpHeaders[] = 'Authorization: ' . S3::__getSignatureV4( $this->amzHeaders, $this->headers, $this->verb, $this->uri, - $this->parameters + $this->parameters, + $this->bucket->authRegion ); } @@ -2377,7 +2456,7 @@ public function getResponse() // Request types switch ($this->verb) { - case 'GET': break; + // case 'GET': break; case 'PUT': case 'POST': // POST only used for CloudFront if ($this->fp !== false) { @@ -2412,7 +2491,7 @@ public function getResponse() // Execute, grab errors if (curl_exec($curl)) - $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $this->response->code = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE); else $this->response->error = array( 'code' => curl_errno($curl), @@ -2423,14 +2502,14 @@ public function getResponse() @curl_close($curl); // Parse body into XML - if ($this->response->error === false && isset($this->response->headers['type']) && - $this->response->headers['type'] == 'application/xml' && isset($this->response->body)) + if ($this->response->error === false && isset($this->response->headers['type'], $this->response->body) + && $this->response->headers['type'] === 'application/xml') { $this->response->body = simplexml_load_string($this->response->body); // Grab S3 errors - if (!in_array($this->response->code, array(200, 204, 206)) && - isset($this->response->body->Code, $this->response->body->Message)) + if (isset($this->response->body->Code, $this->response->body->Message) && + !in_array($this->response->code, array(200, 204, 206), true)) { $this->response->error = array( 'code' => (string)$this->response->body->Code, @@ -2458,7 +2537,7 @@ public function getResponse() */ private function __responseWriteCallback(&$curl, &$data) { - if (in_array($this->response->code, array(200, 206)) && $this->fp !== false) + if ($this->fp !== false && in_array($this->response->code, array(200, 206), true)) return fwrite($this->fp, $data); else $this->response->body .= $data; @@ -2467,19 +2546,20 @@ private function __responseWriteCallback(&$curl, &$data) /** - * Check DNS conformity - * - * @param string $bucket Bucket name - * @return boolean - */ - private function __dnsBucketName($bucket) + * Check DNS conformity + * + * @param string $bucket Bucket name + * @param bool $useSSL + * @return boolean + */ + private function __dnsBucketName($bucket, $useSSL) { if (strlen($bucket) > 63 || preg_match("/[^a-z0-9\.-]/", $bucket) > 0) return false; - if (S3::$useSSL && strstr($bucket, '.') !== false) return false; - if (strstr($bucket, '-.') !== false) return false; - if (strstr($bucket, '..') !== false) return false; - if (!preg_match("/^[0-9a-z]/", $bucket)) return false; - if (!preg_match("/[0-9a-z]$/", $bucket)) return false; + if ($useSSL && strpos($bucket, '.') !== false) return false; + if (strpos($bucket, '-.') !== false) return false; + if (strpos($bucket, '..') !== false) return false; + if (!preg_match('/^[0-9a-z]/', $bucket)) return false; + if (!preg_match('/[0-9a-z]$/', $bucket)) return false; return true; } @@ -2494,7 +2574,7 @@ private function __dnsBucketName($bucket) private function __responseHeaderCallback($curl, $data) { if (($strlen = strlen($data)) <= 2) return $strlen; - if (substr($data, 0, 4) == 'HTTP') + if (strpos($data, 'HTTP') === 0) $this->response->code = (int)substr($data, 9, 3); else { @@ -2502,16 +2582,16 @@ private function __responseHeaderCallback($curl, $data) if (strpos($data, ': ') === false) return $strlen; list($header, $value) = explode(': ', $data, 2); $header = strtolower($header); - if ($header == 'last-modified') + if ($header === 'last-modified') $this->response->headers['time'] = strtotime($value); - elseif ($header == 'date') + elseif ($header === 'date') $this->response->headers['date'] = strtotime($value); - elseif ($header == 'content-length') + elseif ($header === 'content-length') $this->response->headers['size'] = (int)$value; - elseif ($header == 'content-type') + elseif ($header === 'content-type') $this->response->headers['type'] = $value; - elseif ($header == 'etag') - $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value; + elseif ($header === 'etag') + $this->response->headers['hash'] = $value{0} === '"' ? substr($value, 1, -1) : $value; elseif (preg_match('/^x-amz-meta-.*$/', $header)) $this->response->headers[$header] = $value; } @@ -2536,10 +2616,194 @@ class S3Exception extends Exception { * @param string $line Line number on which exception was created * @param int $code Exception code */ - function __construct($message, $file, $line, $code = 0) + public function __construct($message, $file, $line, $code = 0) { parent::__construct($message, $code); $this->file = $file; $this->line = $line; } } + +/** + * EndpointConfig class + */ +final class EndpointConfig +{ + const DEFAULT_HOST = 's3.amazonaws.com'; + + /** + * Enable SSL + * + * @var bool + */ + public $useSSL = true; + + /** + * Use SSL version + * + * @var int constant as defined by curl module + */ + public $useSSLVersion = CURL_SSLVERSION_TLSv1; + + /** + * Enable SSL host & peer validation + * + * @var bool + */ + public $useSSLValidation = true; + + /** + * SSL client key + * + * @var string + */ + public $sslKey; + + /** + * SSL client certfificate + * + * @var string + */ + public $sslCert; + + /** + * SSL CA cert (only required if you are having problems with your system CA cert) + * + * @var string + */ + public $sslCACert; + + /** + * Hostname + * + * @var string + */ + public $hostname = self::DEFAULT_HOST; + + /** + * Force Path-Style bucket reference in URL + * + * @var bool + */ + public $forcePathStyle = false; + + /** + * Proxy details + * @var array + */ + public $proxy; + + public function __construct($hostname = self::DEFAULT_HOST) + { + $this->hostname = $hostname; + } + + public function withHostname($hostname) + { + $this->hostname = $hostname; + return $this; + } + + public function withSSLEnabled($enabled = true) + { + $this->useSSL = $enabled; + return $this; + } + + public function withSSLValidationEnabled($enabled = true) + { + $this->useSSLValidation = $enabled; + return $this; + } + + public function withPathStyleEnabled($enabled = true) + { + $this->forcePathStyle = $enabled; + return $this; + } + + public function withSSLVersion($sslVersion = CURL_SSLVERSION_TLSv1) + { + $this->useSSLVersion = $sslVersion; + return $this; + } + + /** + * Set SSL client certificates (experimental) + * + * @param string $sslCert SSL client certificate + * @param string $sslKey SSL client key + * @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert) + * + * @return self + */ + public function withSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null) + { + $this->sslCert = $sslCert; + $this->sslKey = $sslKey; + $this->sslCACert = $sslCACert; + + return $this; + } + + /** + * Set proxy information + * + * @param string $host Proxy hostname and port (localhost:1234) + * @param string $user Proxy username + * @param string $pass Proxy password + * @param int $type CURL proxy type + * @return self + */ + public function withProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5) + { + $this->proxy = array( + 'host' => $host, + 'type' => $type, + 'user' => $user, + 'pass' => $pass + ); + + return $this; + } +} + +/** + * BucketConfig class + */ +final class BucketConfig +{ + public $name; + public $authRegion; + + public function __construct($name, $authRegion) + { + $this->name = $name; + $this->authRegion = $authRegion; + } + + public function __toString() + { + return $this->name; + } +} + +/** + * Credentials class + */ +final class Credentials +{ + public $accessKey; + public $secretKey; + + public function __construct($accessKey, $secretKey) + { + $this->accessKey = $accessKey; + $this->secretKey = $secretKey; + } + + public function isInitialised() + { + return $this->accessKey !== null && $this->secretKey !== null; + } +} From 6998c477eec1f89b4d4c017dee534174a92b0216 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Tue, 1 Oct 2019 17:06:23 +0100 Subject: [PATCH 02/19] Add per-endpoint signature version setting --- S3.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/S3.php b/S3.php index 0c08fa4c..74c0d3bd 100644 --- a/S3.php +++ b/S3.php @@ -49,6 +49,9 @@ class S3 const SSE_NONE = ''; const SSE_AES256 = 'AES256'; + const SigV2 = 'sigv2'; + const SigV4 = 'sigv4'; + /** * Default credentials to access AWS * @@ -2410,7 +2413,7 @@ public function getResponse() if (S3::hasAuth()) { // Authorization string (CloudFront stringToSign should only contain a date) - if ($this->headers['Host'] === 'cloudfront.amazonaws.com') + if ($this->endpoint->signatureVersion === S3::SigV2 || $this->headers['Host'] === 'cloudfront.amazonaws.com') { # TODO: Update CloudFront authentication foreach ($this->amzHeaders as $header => $value) @@ -2631,6 +2634,13 @@ final class EndpointConfig { const DEFAULT_HOST = 's3.amazonaws.com'; + /** + * Auth signature version + * + * @var string + */ + public $signatureVersion = S3::SigV4; + /** * Enable SSL * @@ -2728,6 +2738,12 @@ public function withSSLVersion($sslVersion = CURL_SSLVERSION_TLSv1) return $this; } + public function withSignatureVersion($version = S3::SigV4) + { + $this->signatureVersion = $version; + return $this; + } + /** * Set SSL client certificates (experimental) * From 74808c5fa84a947453bbcc9736e69791fd713c3d Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Wed, 2 Oct 2019 09:29:11 +0100 Subject: [PATCH 03/19] Update composer.json with ext dependencies --- composer.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4c55bc10..1abde163 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,13 @@ } ], "require": { - "php": ">=5.2.0" + "php": ">=5.2.0", + "ext-curl": "*", + "ext-dom": "*", + "ext-openssl": "*", + "ext-json": "*", + "ext-simplexml": "*", + "ext-fileinfo": "*" }, "autoload": { "classmap": ["S3.php"] From 347ea2c427ba3a6c30a3fb6646019ff0a2864d81 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Wed, 2 Oct 2019 09:30:30 +0100 Subject: [PATCH 04/19] Ignore composer.lock and composer.phar from git --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 48b8bf90..467da90d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ vendor/ +/composer.lock +/composer.phar From 5d8eacfd068f329d2dd50f1b59fe78a7d6d596c2 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 00:48:26 +0100 Subject: [PATCH 05/19] Ton of fixes from testing * Removed the region deducer callback; RTFM for me! * Added workaround to avoid 307 redirects by always talking to the correct regional-service-endpoint. This also improves performance overall as it requires less network magic in AWS. * Fixed examples such that no code need to be modified in order to run them; takes parameters from environment. * Tweaks to composer.json to fix autoloading and to specify extension requirements. * Added export-ignore git attributes to remove dev artifacts from composer install when included into other projects. --- .gitattributes | 8 + S3.php | 385 +++++++++++++++++++++++++---------------- composer.json | 3 + example-cloudfront.php | 28 ++- example-form.php | 25 +-- example-helpers.php | 25 +++ example-wrapper.php | 55 +++--- example.php | 66 +++---- 8 files changed, 338 insertions(+), 257 deletions(-) create mode 100644 .gitattributes create mode 100644 example-helpers.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c45aaf7f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +/README.md export-ignore +/example.php export-ignore +/example-cloudfront.php export-ignore +/example-form.php export-ignore +/example-wrapper.php export-ignore +/example-helpers.php export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/S3.php b/S3.php index 74c0d3bd..34487e50 100644 --- a/S3.php +++ b/S3.php @@ -66,13 +66,6 @@ class S3 */ private static $__defaultEndpoint; - /** - * Callback to deduce auth region - * - * @var callable|null - */ - private static $__regionDeducer; - /** * Default delimiter to be used, for example while getBucket(). * @var string|null @@ -134,27 +127,48 @@ class S3 public static $progressFunction; /** - * Constructor - if you're not using the class statically + * Constructor * * @param string $accessKey Access key * @param string $secretKey Secret key * @param boolean $useSSL Enable SSL * @param string $endpoint Amazon URI * @param string $region AWS auth region for SigV4 + * + * @deprecated Use static initializer S3::Init() instead */ public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com', $region = '') { + $creds = null; if ($accessKey !== null && $secretKey !== null) { - self::setAuth($accessKey, $secretKey); + $creds = new Credentials($accessKey, $secretKey); } - self::$__defaultEndpoint = new EndpointConfig(); - self::$__defaultEndpoint - ->withHostname($endpoint) - ->withSSLEnabled($useSSL); + $endpointCfg = new EndpointConfig($endpoint); + $endpointCfg->withSSLEnabled($useSSL); + + self::Init($creds, $region, $endpointCfg); + } + + /** + * Initialise default parameters + * + * @param Credentials $credentials Default credentials + * @param string $region Auth region for SigV4 + * @param EndpointConfig|null $endpoint Endpoint configuration, null for AWS S3 settings + */ + public static function Init(Credentials $credentials, $region = '', EndpointConfig $endpoint = null) + { + self::setCredentials($credentials); self::$region = $region; + + if ($endpoint === null) { + $endpoint = new EndpointConfig(); + } + + self::setEndpoint($endpoint); } /** @@ -183,7 +197,7 @@ public static function getEndpoint(EndpointConfig $endpoint = null) * @param EndpointConfig $endpoint * @return void */ - public function setEndpoint(EndpointConfig $endpoint) + public static function setEndpoint(EndpointConfig $endpoint) { self::$__defaultEndpoint = $endpoint; } @@ -201,36 +215,23 @@ public function setRegion($region) } - /** - * @param callable $deducer function to deduce auth region - */ - public static function setRegionDeducer($deducer) - { - self::$__regionDeducer = $deducer; - } - - /** * Get the service region * - * @param $bucket + * @param EndpointConfig|null $endpoint * @return string * @static */ - public static function getRegion($bucket) + public static function getRegion(EndpointConfig $endpoint = null) { if (!empty(self::$region)) { return self::$region; } - if (self::$__regionDeducer === null) - { - return ''; - } - - $endpoint = self::getEndpoint()->hostname; - return call_user_func(self::$__regionDeducer, $endpoint, $bucket); + // deduce region from default endpoint + return self::getEndpoint($endpoint) + ->getRegion(); } @@ -243,7 +244,18 @@ public static function getRegion($bucket) */ public static function setAuth($accessKey, $secretKey) { - self::$__defaultCredentials = new Credentials($accessKey, $secretKey); + self::setCredentials(new Credentials($accessKey, $secretKey)); + } + + + /** + * Set default credentials + * + * @param Credentials $creds + */ + public static function setCredentials(Credentials $creds) + { + self::$__defaultCredentials = $creds; } @@ -362,7 +374,7 @@ public static function setTimeCorrectionOffset($offset = 0, EndpointConfig $endp { if ($offset === 0) { - $rest = new S3Request('HEAD', null, '', self::getEndpoint($endpoint)); + $rest = new S3Request('HEAD', null, '', $endpoint); $rest = $rest->getResponse(); $awstime = $rest->headers['date']; $systime = time(); @@ -379,7 +391,6 @@ public static function setTimeCorrectionOffset($offset = 0, EndpointConfig $endp * @param string $signingKey Private Key * @param boolean $isFile Load private key from file, set to false to load string * @return boolean - * @throws S3Exception */ public static function setSigningKey($keyPairId, $signingKey, $isFile = true) { @@ -443,8 +454,9 @@ public static function setProgressFunction($func = null) * @param integer $line Line number * @param integer $code Error code * @return void - * @throws S3Exception * @internal Internal error handler + * + * @throws null */ private static function __triggerError($message, $file, $line, $code = 0) { @@ -463,11 +475,10 @@ private static function __triggerError($message, $file, $line, $code = 0) * @param boolean $detailed Returns detailed bucket list when true * @param EndpointConfig|null $endpoint * @return array | false - * @throws S3Exception */ public static function listBuckets($detailed = false, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', null, '', self::getEndpoint($endpoint)); + $rest = new S3Request('GET', null, '', $endpoint); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -511,11 +522,10 @@ public static function listBuckets($detailed = false, EndpointConfig $endpoint = * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes * @param EndpointConfig|null $endpoint * @return array | false - * @throws S3Exception */ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); + $rest = new S3Request('GET', $bucket, '', $endpoint); if ($maxKeys === 0) $maxKeys = null; if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker); @@ -561,7 +571,7 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe if ($maxKeys === null && $nextMarker !== null && (string)$response->body->IsTruncated === 'true') do { - $rest = new S3Request('GET', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); + $rest = new S3Request('GET', $bucket, '', $endpoint); if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); $rest->setParameter('marker', $nextMarker); if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter); @@ -601,13 +611,12 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe * @param string|bool $location Set as "EU" to create buckets hosted in Europe * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false, EndpointConfig $endpoint = null) { if ($location === false) { - $location = self::getRegion($bucket); + $location = self::getRegion($endpoint); if (empty($location)) { self::__triggerError("S3::putBucket({$bucket}, {$acl}, {$location}): Could not deduce region-code", __FILE__, __LINE__); @@ -615,17 +624,26 @@ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = } } - $rest = new S3Request('PUT', new BucketConfig($bucket, $location), '', self::getEndpoint($endpoint)); + $endpoint = self::getEndpoint($endpoint); + $rest = new S3Request('PUT', new BucketConfig($bucket, $endpoint->defaultRegion), '', $endpoint); $rest->setAmzHeader('x-amz-acl', $acl); - $dom = new DOMDocument; - $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); - $locationConstraint = $dom->createElement('LocationConstraint', $location); - $createBucketConfiguration->appendChild($locationConstraint); - $dom->appendChild($createBucketConfiguration); - $rest->data = $dom->saveXML(); - $rest->size = strlen($rest->data); - $rest->setHeader('Content-Type', 'application/xml'); + if ($endpoint->hostname !== EndpointConfig::AWS_S3_DEFAULT_HOST + || $location !== EndpointConfig::AWS_S3_DEFAULT_REGION) { + + $dom = new DOMDocument; + $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); + $locationConstraint = $dom->createElement('LocationConstraint', $location); + $createBucketConfiguration->appendChild($locationConstraint); + $dom->appendChild($createBucketConfiguration); + + $rest->data = $dom->saveXML(); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + } else { + $rest->data = ''; + $rest->size = 0; + } $rest = $rest->getResponse(); @@ -651,11 +669,10 @@ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = * @param string|BucketConfig $bucket Bucket name * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function deleteBucket($bucket, EndpointConfig $endpoint = null) { - $rest = new S3Request('DELETE', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); + $rest = new S3Request('DELETE', $bucket, '', $endpoint); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 204) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -675,7 +692,6 @@ public static function deleteBucket($bucket, EndpointConfig $endpoint = null) * @param string $file Input file * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own) * @return array | false - * @throws S3Exception */ public static function inputFile($file, $md5sum = true) { @@ -708,7 +724,6 @@ public static function inputFile($file, $md5sum = true) * @param int|bool $bufferSize Input byte size * @param string $md5sum MD5 hash to send (optional) * @return array | false - * @throws S3Exception */ public static function inputResource(&$resource, $bufferSize = false, $md5sum = '') { @@ -748,12 +763,11 @@ public static function inputResource(&$resource, $bufferSize = false, $md5sum = * @param string $serverSideEncryption Server-side encryption * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) { if ($input === false) return false; - $rest = new S3Request('PUT', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); + $rest = new S3Request('PUT', $bucket, $uri, $endpoint); if (!is_array($input)) $input = array( 'data' => $input, 'size' => strlen($input), @@ -842,7 +856,6 @@ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE * @param string $serverSideEncryption * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null, $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) { @@ -863,7 +876,6 @@ public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIV * @param string $serverSideEncryption * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain', $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) { @@ -879,11 +891,10 @@ public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_ * @param mixed $saveTo Filename or resource to write to * @param EndpointConfig|null $endpoint * @return mixed - * @throws S3Exception */ public static function getObject($bucket, $uri, $saveTo = false, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); + $rest = new S3Request('GET', $bucket, $uri, $endpoint); if ($saveTo !== false) { if (is_resource($saveTo)) @@ -913,11 +924,10 @@ public static function getObject($bucket, $uri, $saveTo = false, EndpointConfig * @param boolean $returnInfo Return response information * @param EndpointConfig|null $endpoint * @return mixed | false - * @throws S3Exception */ public static function getObjectInfo($bucket, $uri, $returnInfo = true, EndpointConfig $endpoint = null) { - $rest = new S3Request('HEAD', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); + $rest = new S3Request('HEAD', $bucket, $uri, $endpoint); $rest = $rest->getResponse(); if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404)) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -950,11 +960,10 @@ public static function getObjectInfo($bucket, $uri, $returnInfo = true, Endpoint * @param string $storageClass Storage class constant * @param EndpointConfig|null $endpoint * @return mixed | false - * @throws S3Exception */ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, EndpointConfig $endpoint = null) { - $rest = new S3Request('PUT', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); + $rest = new S3Request('PUT', $bucket, $uri, $endpoint); $rest->setHeader('Content-Length', 0); foreach ($requestHeaders as $h => $v) strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v); @@ -989,11 +998,10 @@ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = sel * @param string $location Target host name * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function setBucketRedirect($bucket = NULL, $location = NULL, EndpointConfig $endpoint = null) { - $rest = new S3Request('PUT', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); + $rest = new S3Request('PUT', $bucket, '', $endpoint); if( empty($bucket) || empty($location) ) { self::__triggerError("S3::setBucketRedirect({$bucket}, {$location}): Empty parameter.", __FILE__, __LINE__); @@ -1033,7 +1041,6 @@ public static function setBucketRedirect($bucket = NULL, $location = NULL, Endpo * @param string $targetPrefix Log prefix (e,g; domain.com-) * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null, EndpointConfig $endpoint = null) { @@ -1072,7 +1079,7 @@ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = } $dom->appendChild($bucketLoggingStatus); - $rest = new S3Request('PUT', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); + $rest = new S3Request('PUT', $bucket, '', $endpoint); $rest->setParameter('logging', null); $rest->data = $dom->saveXML(); $rest->size = strlen($rest->data); @@ -1099,11 +1106,10 @@ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = * @param string|BucketConfig $bucket Bucket name * @param EndpointConfig|null $endpoint * @return array | false - * @throws S3Exception */ public static function getBucketLogging($bucket, EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', self::makeBucketConfig($bucket), '', self::getEndpoint($endpoint)); + $rest = new S3Request('GET', $bucket, '', $endpoint); $rest->setParameter('logging', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1128,7 +1134,6 @@ public static function getBucketLogging($bucket, EndpointConfig $endpoint = null * @param string|BucketConfig $bucket Bucket name * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function disableBucketLogging($bucket, EndpointConfig $endpoint = null) { @@ -1139,21 +1144,17 @@ public static function disableBucketLogging($bucket, EndpointConfig $endpoint = /** * Get a bucket's location * - * @param string|BucketConfig $bucket Bucket name + * @param string $bucket Bucket name * @param EndpointConfig|null $endpoint * @return string | false - * @throws S3Exception */ public static function getBucketLocation($bucket, EndpointConfig $endpoint = null) { - $bucket = self::makeBucketConfig($bucket); - if (empty($bucket->authRegion)) { - // https://github.com/aws/aws-sdk-js/issues/462 - // Setting up any region other than 'us-east-1' - $bucket->authRegion = 'us-west-2'; - } + // https://github.com/aws/aws-sdk-js/issues/462 + // Setting up any region other than 'us-east-1' + $bucketConfig = new BucketConfig($bucket, 'us-west-2'); - $rest = new S3Request('GET', $bucket, '', self::getEndpoint($endpoint)); + $rest = new S3Request('GET', $bucketConfig, '', $endpoint); $rest->setParameter('location', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1176,7 +1177,6 @@ public static function getBucketLocation($bucket, EndpointConfig $endpoint = nul * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy) * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array(), EndpointConfig $endpoint = null) { @@ -1219,7 +1219,7 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() $accessControlPolicy->appendChild($accessControlList); $dom->appendChild($accessControlPolicy); - $rest = new S3Request('PUT', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); + $rest = new S3Request('PUT', $bucket, $uri, $endpoint); $rest->setParameter('acl', null); $rest->data = $dom->saveXML(); $rest->size = strlen($rest->data); @@ -1244,11 +1244,10 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() * @param string $uri Object URI * @param EndpointConfig|null $endpoint * @return mixed | false - * @throws S3Exception */ public static function getAccessControlPolicy($bucket, $uri = '', EndpointConfig $endpoint = null) { - $rest = new S3Request('GET', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); + $rest = new S3Request('GET', $bucket, $uri, $endpoint); $rest->setParameter('acl', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1307,11 +1306,10 @@ public static function getAccessControlPolicy($bucket, $uri = '', EndpointConfig * @param string $uri Object URI * @param EndpointConfig|null $endpoint * @return boolean - * @throws S3Exception */ public static function deleteObject($bucket, $uri, EndpointConfig $endpoint = null) { - $rest = new S3Request('DELETE', self::makeBucketConfig($bucket), $uri, self::getEndpoint($endpoint)); + $rest = new S3Request('DELETE', $bucket, $uri, $endpoint); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 204) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -1462,7 +1460,6 @@ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = * @param string $originAccessIdentity Origin access identity * @param array $trustedSigners Array of trusted signers * @return array | false - * @throws S3Exception */ public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array()) { @@ -1508,7 +1505,6 @@ public static function createDistribution($bucket, $enabled = true, $cnames = ar * * @param string $distributionId Distribution ID from listDistributions() * @return array | false - * @throws S3Exception */ public static function getDistribution($distributionId) { @@ -1549,7 +1545,6 @@ public static function getDistribution($distributionId) * * @param array $dist Distribution array info identical to output of getDistribution() * @return array | false - * @throws S3Exception */ public static function updateDistribution($dist) { @@ -1597,7 +1592,6 @@ public static function updateDistribution($dist) * * @param array $dist Distribution array info identical to output of getDistribution() * @return boolean - * @throws S3Exception */ public static function deleteDistribution($dist) { @@ -1629,7 +1623,6 @@ public static function deleteDistribution($dist) * Get a list of CloudFront distributions * * @return array|bool - * @throws S3Exception */ public static function listDistributions() { @@ -1667,7 +1660,6 @@ public static function listDistributions() * List CloudFront Origin Access Identities * * @return array|bool - * @throws S3Exception */ public static function listOriginAccessIdentities() { @@ -1710,7 +1702,6 @@ public static function listOriginAccessIdentities() * @param string $distributionId Distribution ID from listDistributions() * @param array $paths Array of object paths to invalidate * @return bool - * @throws S3Exception */ public static function invalidateDistribution($distributionId, $paths) { @@ -1776,7 +1767,6 @@ private static function __getCloudFrontInvalidationBatchXML($paths, $callerRefer * * @param string $distributionId Distribution ID from listDistributions() * @return array|bool - * @throws S3Exception */ public static function getDistributionInvalidationList($distributionId) { @@ -2064,7 +2054,7 @@ private static function __getHash($string) * @internal Used by S3Request::getResponse() */ public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $parameters, $region) - { + { $service = 's3'; $algorithm = 'AWS4-HMAC-SHA256'; @@ -2153,15 +2143,16 @@ private static function __sortMetaHeadersCmp($a, $b) * Helper to transition from bucket names to bucket-config * * @param string|BucketConfig $bucket + * @param EndpointConfig $endpoint * @return BucketConfig */ - private static function makeBucketConfig($bucket) + public static function makeBucketConfig($bucket, EndpointConfig $endpoint) { if ($bucket instanceof BucketConfig) { return $bucket; } - return new BucketConfig($bucket, self::getRegion($bucket)); + return new BucketConfig($bucket, self::getRegion($endpoint)); } } @@ -2274,38 +2265,27 @@ final class S3Request * Constructor * * @param string $verb Verb - * @param BucketConfig $bucket Bucket config + * @param BucketConfig|string $bucket Bucket config * @param string $uri Object URI * @param EndpointConfig $endpoint AWS endpoint URI */ - public function __construct($verb, $bucket = null, $uri = '', $endpoint = null) + public function __construct($verb, $bucket = null, $uri = '', EndpointConfig $endpoint = null) { + $endpoint = S3::getEndpoint($endpoint); + + if ($bucket !== null) { + $bucket = S3::makeBucketConfig($bucket, $endpoint); + } + $this->endpoint = $endpoint; $this->verb = $verb; $this->bucket = $bucket; $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/'; - if ($bucket !== null) - { - if ($endpoint->forcePathStyle !== true && $this->__dnsBucketName($bucket->name, $endpoint->useSSL)) - { - $this->headers['Host'] = $bucket->name.'.'.$this->endpoint->hostname; - $this->resource = '/'.$bucket->name.$this->uri; - } - else - { - // Old format, deprecated by AWS - removal scheduled for September 30th, 2020, - // but still useful for S3 compatible storage access, like https://min.io - $this->headers['Host'] = $this->endpoint->hostname; - $this->uri = '/'.$bucket->name.$this->uri; - $this->resource = $this->uri; - } - } - else - { - $this->headers['Host'] = $this->endpoint->hostname; - $this->resource = $this->uri; - } + $res = $endpoint->resolveHostUriAndResource($this->uri, $bucket); + $this->headers['Host'] = $res['host']; + $this->uri = $res['uri']; + $this->resource = $res['resource']; $this->headers['Date'] = gmdate('D, d M Y H:i:s T'); $this->response = new STDClass; @@ -2377,12 +2357,16 @@ public function getResponse() array_key_exists('logging', $this->parameters)) $this->resource .= $query; } - $url = ($this->endpoint->useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri; + $url = ($this->endpoint->useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint->hostname) . $this->uri; // Basic setup $curl = curl_init(); curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php'); + if ('ON' === strtoupper(getenv('CURL_VERBOSE'))) { + curl_setopt($curl, CURLOPT_VERBOSE, true); + } + if ($this->endpoint->useSSL) { // Set protocol version @@ -2437,13 +2421,24 @@ public function getResponse() foreach ($this->headers as $header => $value) if ($value !== '') $httpHeaders[] = $header.': '.$value; + $authRegion = ''; + if ($this->bucket !== null) { + $authRegion = $this->bucket->region; + } elseif ($this->headers['Host'] === $this->endpoint->hostname) { + $authRegion = $this->endpoint->defaultRegion; + } + + if (empty($authRegion) && !empty(S3::$region)) { + $authRegion = S3::$region; + } + $httpHeaders[] = 'Authorization: ' . S3::__getSignatureV4( $this->amzHeaders, - $this->headers, - $this->verb, + $this->headers, + $this->verb, $this->uri, $this->parameters, - $this->bucket->authRegion + $authRegion ); } @@ -2548,25 +2543,6 @@ private function __responseWriteCallback(&$curl, &$data) } - /** - * Check DNS conformity - * - * @param string $bucket Bucket name - * @param bool $useSSL - * @return boolean - */ - private function __dnsBucketName($bucket, $useSSL) - { - if (strlen($bucket) > 63 || preg_match("/[^a-z0-9\.-]/", $bucket) > 0) return false; - if ($useSSL && strpos($bucket, '.') !== false) return false; - if (strpos($bucket, '-.') !== false) return false; - if (strpos($bucket, '..') !== false) return false; - if (!preg_match('/^[0-9a-z]/', $bucket)) return false; - if (!preg_match('/[0-9a-z]$/', $bucket)) return false; - return true; - } - - /** * CURL header callback * @@ -2625,6 +2601,11 @@ public function __construct($message, $file, $line, $code = 0) $this->file = $file; $this->line = $line; } + + public function __toString() + { + return sprintf('%s @ %s:%d', parent::__toString(), $this->file, $this->line); + } } /** @@ -2632,7 +2613,11 @@ public function __construct($message, $file, $line, $code = 0) */ final class EndpointConfig { - const DEFAULT_HOST = 's3.amazonaws.com'; + const AWS_S3_SUFFIX = '.amazonaws.com'; + const AWS_S3_SUFFIX_LENGTH = 14; + + const AWS_S3_DEFAULT_HOST = 's3.amazonaws.com'; + const AWS_S3_DEFAULT_REGION = 'us-east-1'; /** * Auth signature version @@ -2688,7 +2673,14 @@ final class EndpointConfig * * @var string */ - public $hostname = self::DEFAULT_HOST; + public $hostname = self::AWS_S3_DEFAULT_HOST; + + /** + * default region + * + * @var string + */ + public $defaultRegion = self::AWS_S3_DEFAULT_REGION; /** * Force Path-Style bucket reference in URL @@ -2703,9 +2695,94 @@ final class EndpointConfig */ public $proxy; - public function __construct($hostname = self::DEFAULT_HOST) + public function __construct($hostname = self::AWS_S3_DEFAULT_HOST, $defaultRegion = null) { + if ($hostname === self::AWS_S3_DEFAULT_HOST) { + $defaultRegion = self::AWS_S3_DEFAULT_REGION; + } + $this->hostname = $hostname; + $this->defaultRegion = $defaultRegion; + } + + /** + * If it is an AWS S3 endpoint, extract region code from the hostname. + * @see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region + * + * @return string + */ + public function getRegion() + { + // if not an AWS endpoint, return the default region + if (1 !== preg_match("/s3[.-](?:website-|dualstack\.)?(.+)\.amazonaws\.com/i", $this->hostname, $match)) { + return $this->defaultRegion; + } + + if (strtolower($match[1]) === "external-1") { + return $this->defaultRegion; + } + + return $match[1]; + } + + /** + * Check DNS conformity + * + * @param string $bucket Bucket name + * @return boolean + */ + private function isDnsSafeName($bucket) + { + switch (true) { + case strlen($bucket) > 63: + case 0 !== preg_match("/[^a-z0-9\.-]/", $bucket): + case $this->useSSL && strpos($bucket, '.') !== false: + case strpos($bucket, '-.') !== false: + case strpos($bucket, '..') !== false: + case !preg_match('/^[0-9a-z]/', $bucket): + case !preg_match('/[0-9a-z]$/', $bucket): + return false; + } + + return true; + } + + /** + * @param $uri + * @param BucketConfig|null $bucket + * @return array + */ + public function resolveHostUriAndResource($uri, BucketConfig $bucket = null) + { + if ($bucket === null) { + return array( + 'host' => $this->hostname, + 'uri' => $uri, + 'resource' => $uri, + ); + } + + $pathStyle = $this->forcePathStyle || !$this->isDnsSafeName($bucket->name); + + if ($pathStyle) { + return array( + 'host' => $this->hostname, + 'uri' => '/' . $bucket->name . $uri, + 'resource' => $uri, + ); + } + + if ($this->hostname !== self::AWS_S3_DEFAULT_HOST || empty($bucket->region)) { + $hostname = $bucket->name . '.' . $this->hostname; + } else { + $hostname = sprintf('%s.s3.%s.amazonaws.com', $bucket->name, $bucket->region); + } + + return array( + 'host' => $hostname, + 'uri' => $uri, + 'resource' => '/' . $bucket->name . $uri, + ); } public function withHostname($hostname) @@ -2744,6 +2821,12 @@ public function withSignatureVersion($version = S3::SigV4) return $this; } + public function withDefaultRegion($defaultRegion) + { + $this->defaultRegion = $defaultRegion; + return $this; + } + /** * Set SSL client certificates (experimental) * @@ -2790,12 +2873,12 @@ public function withProxy($host, $user = null, $pass = null, $type = CURLPROXY_S final class BucketConfig { public $name; - public $authRegion; + public $region; - public function __construct($name, $authRegion) + public function __construct($name, $region) { $this->name = $name; - $this->authRegion = $authRegion; + $this->region = $region; } public function __toString() diff --git a/composer.json b/composer.json index 1abde163..0fe34b6d 100644 --- a/composer.json +++ b/composer.json @@ -21,5 +21,8 @@ }, "autoload": { "classmap": ["S3.php"] + }, + "autoload-dev": { + "files": ["example-helpers.php"] } } diff --git a/example-cloudfront.php b/example-cloudfront.php index f96a077a..1ee64c1f 100755 --- a/example-cloudfront.php +++ b/example-cloudfront.php @@ -6,24 +6,18 @@ * S3 class - CloudFront usage */ -if (!class_exists('S3')) require_once 'S3.php'; +require_once 'vendor/autoload.php'; -// AWS access info -if (!defined('awsAccessKey')) define('awsAccessKey', 'change-this'); -if (!defined('awsSecretKey')) define('awsSecretKey', 'change-this'); +// File to upload, we'll use this file since it exists +list($uploadFile) = get_included_files(); +$bucketName = uniqid('s3test', false); // Temporary bucket -// Check for CURL -if (!extension_loaded('curl') && !@dl(PHP_SHLIB_SUFFIX == 'so' ? 'curl.so' : 'php_curl.dll')) - exit("\nERROR: CURL extension not loaded\n\n"); - -// Pointless without your keys! -if (awsAccessKey == 'change-this' || awsSecretKey == 'change-this') - exit("\nERROR: AWS access information required\n\nPlease edit the following lines in this file:\n\n". - "define('awsAccessKey', 'change-me');\ndefine('awsSecretKey', 'change-me');\n\n"); - - -S3::setAuth(awsAccessKey, awsSecretKey); +// Initialise S3 +S3::Init( + new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), + _getenv('REGION', 'us-west-1') + ); function test_createDistribution($bucket, $cnames = array()) { @@ -36,7 +30,7 @@ function test_createDistribution($bucket, $cnames = array()) { function test_listDistributions() { if (($dists = S3::listDistributions()) !== false) { - if (sizeof($dists) == 0) echo "listDistributions(): No distributions\n"; + if (count($dists) === 0) echo "listDistributions(): No distributions\n"; foreach ($dists as $dist) { var_dump($dist); } @@ -63,7 +57,7 @@ function test_deleteDistribution($distributionId) { // To delete a distribution configuration you must first set enable=false with // the updateDistrubution() method and wait for status=Deployed: if (($dist = S3::getDistribution($distributionId)) !== false) { - if ($dist['status'] == 'Deployed') { + if ($dist['status'] === 'Deployed') { echo "deleteDistribution($distributionId): "; var_dump(S3::deleteDistribution($dist)); } else { echo "deleteDistribution($distributionId): Distribution not ready for deletion (status is not 'Deployed')\n"; diff --git a/example-form.php b/example-form.php index 2d180f86..247731a6 100644 --- a/example-form.php +++ b/example-form.php @@ -5,25 +5,18 @@ * S3 form upload example */ -if (!class_exists('S3')) require_once 'S3.php'; +require_once 'vendor/autoload.php'; -// AWS access info -if (!defined('awsAccessKey')) define('awsAccessKey', 'change-this'); -if (!defined('awsSecretKey')) define('awsSecretKey', 'change-this'); +// File to upload, we'll use this file since it exists +list($uploadFile) = get_included_files(); -// Check for CURL -if (!extension_loaded('curl') && !@dl(PHP_SHLIB_SUFFIX == 'so' ? 'curl.so' : 'php_curl.dll')) - exit("\nERROR: CURL extension not loaded\n\n"); - -// Pointless without your keys! -if (awsAccessKey == 'change-this' || awsSecretKey == 'change-this') - exit("\nERROR: AWS access information required\n\nPlease edit the following lines in this file:\n\n". - "define('awsAccessKey', 'change-me');\ndefine('awsSecretKey', 'change-me');\n\n"); - - -S3::setAuth(awsAccessKey, awsSecretKey); +// Initialise S3 +S3::Init( + new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), + _getenv('REGION', 'us-west-1') +); -$bucket = 'upload-bucket'; +$bucket = _getenv('BUCKET_NAME', 'upload-bucket'); $path = 'myfiles/'; // Can be empty '' $lifetime = 3600; // Period for which the parameters are valid diff --git a/example-helpers.php b/example-helpers.php new file mode 100644 index 00000000..4d2bdf76 --- /dev/null +++ b/example-helpers.php @@ -0,0 +1,25 @@ +__getURL($path); return (($info = self::getObjectInfo($this->url['host'], $this->url['path'])) !== false) ? array('size' => $info['size'], 'mtime' => $info['time'], 'ctime' => $info['time']) : false; } public function unlink($path) { - self::__getURL($path); + $this->__getURL($path); return self::deleteObject($this->url['host'], $this->url['path']); } public function mkdir($path, $mode, $options) { - self::__getURL($path); - return self::putBucket($this->url['host'], self::__translateMode($mode)); + $this->__getURL($path); + return self::putBucket($this->url['host'], $this->__translateMode($mode)); } public function rmdir($path) { - self::__getURL($path); + $this->__getURL($path); return self::deleteBucket($this->url['host']); } public function dir_opendir($path, $options) { - self::__getURL($path); + $this->__getURL($path); if (($contents = self::getBucket($this->url['host'], $this->url['path'])) !== false) { $pathlen = strlen($this->url['path']); - if (substr($this->url['path'], -1) == '/') $pathlen++; + if (substr($this->url['path'], -1) === '/') $pathlen++; $this->buffer = array(); foreach ($contents as $file) { if ($pathlen > 0) $file['name'] = substr($file['name'], $pathlen); @@ -78,7 +64,7 @@ public function dir_closedir() { } public function stream_close() { - if ($this->mode == 'w') { + if ($this->mode === 'w') { self::putObject($this->buffer, $this->url['host'], $this->url['path']); } $this->position = 0; @@ -105,9 +91,9 @@ public function stream_flush() { public function stream_open($path, $mode, $options, &$opened_path) { if (!in_array($mode, array('r', 'rb', 'w', 'wb'))) return false; // Mode not supported $this->mode = substr($mode, 0, 1); - self::__getURL($path); + $this->__getURL($path); $this->position = 0; - if ($this->mode == 'r') { + if ($this->mode === 'r') { if (($this->buffer = self::getObject($this->url['host'], $this->url['path'])) !== false) { if (is_object($this->buffer->body)) $this->buffer->body = (string)$this->buffer->body; } else return false; @@ -142,7 +128,7 @@ public function stream_eof() { public function stream_seek($offset, $whence) { switch ($whence) { case SEEK_SET: - if ($offset < strlen($this->buffer->body) && $offset >= 0) { + if ($offset >= 0 && $offset < strlen($this->buffer->body)) { $this->position = $offset; return true; } else return false; @@ -180,16 +166,21 @@ private function __translateMode($mode) { $acl = self::ACL_PUBLIC_READ; //$acl = self::ACL_PUBLIC_READ_WRITE; return $acl; } -} stream_wrapper_register('s3', 'S3Wrapper'); +} +stream_wrapper_register('s3', 'S3Wrapper'); -################################################################################ +################################################################################ -S3::setAuth(awsAccessKey, awsSecretKey); +$bucketName = uniqid('s3test', false); // Temporary bucket -$bucketName = uniqid('s3test'); +// Initialise S3 +S3::Init( + new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), + _getenv('REGION', 'us-west-1') +); echo "Creating bucket: {$bucketName}\n"; var_dump(mkdir("s3://{$bucketName}")); diff --git a/example.php b/example.php index d6c90dd6..bed4906a 100755 --- a/example.php +++ b/example.php @@ -6,98 +6,82 @@ * S3 class usage */ -if (!class_exists('S3')) require_once 'S3.php'; +require_once 'vendor/autoload.php'; -// AWS access info -if (!defined('awsAccessKey')) define('awsAccessKey', 'change-this'); -if (!defined('awsSecretKey')) define('awsSecretKey', 'change-this'); +// File to upload, we'll use this file since it exists +list($uploadFile) = get_included_files(); -$uploadFile = dirname(__FILE__).'/S3.php'; // File to upload, we'll use the S3 class since it exists -$bucketName = uniqid('s3test'); // Temporary bucket +$bucketName = uniqid('s3test', false); // Temporary bucket -// If you want to use PECL Fileinfo for MIME types: -//if (!extension_loaded('fileinfo') && @dl('fileinfo.so')) $_ENV['MAGIC'] = '/usr/share/file/magic'; - - -// Check if our upload file exists -if (!file_exists($uploadFile) || !is_file($uploadFile)) - exit("\nERROR: No such file: $uploadFile\n\n"); - -// Check for CURL -if (!extension_loaded('curl') && !@dl(PHP_SHLIB_SUFFIX == 'so' ? 'curl.so' : 'php_curl.dll')) - exit("\nERROR: CURL extension not loaded\n\n"); - -// Pointless without your keys! -if (awsAccessKey == 'change-this' || awsSecretKey == 'change-this') - exit("\nERROR: AWS access information required\n\nPlease edit the following lines in this file:\n\n". - "define('awsAccessKey', 'change-me');\ndefine('awsSecretKey', 'change-me');\n\n"); - -// Instantiate the class -$s3 = new S3(awsAccessKey, awsSecretKey); +// Initialise S3 +S3::Init( + new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), + _getenv('REGION', 'us-west-1') +); // List your buckets: -echo "S3::listBuckets(): ".print_r($s3->listBuckets(), 1)."\n"; +echo "S3::listBuckets(): ".print_r(S3::listBuckets(), 1)."\n"; // Create a bucket with public read access -if ($s3->putBucket($bucketName, S3::ACL_PUBLIC_READ)) { +if (S3::putBucket($bucketName, S3::ACL_PUBLIC_READ)) { echo "Created bucket {$bucketName}".PHP_EOL; // Put our file (also with public read access) - if ($s3->putObjectFile($uploadFile, $bucketName, baseName($uploadFile), S3::ACL_PUBLIC_READ)) { + if (S3::putObjectFile($uploadFile, $bucketName, baseName($uploadFile), S3::ACL_PUBLIC_READ)) { echo "S3::putObjectFile(): File copied to {$bucketName}/".baseName($uploadFile).PHP_EOL; // Get the contents of our bucket - $contents = $s3->getBucket($bucketName); + $contents = S3::getBucket($bucketName); echo "S3::getBucket(): Files in bucket {$bucketName}: ".print_r($contents, 1); // Get object info - $info = $s3->getObjectInfo($bucketName, baseName($uploadFile)); + $info = S3::getObjectInfo($bucketName, baseName($uploadFile)); echo "S3::getObjectInfo(): Info for {$bucketName}/".baseName($uploadFile).': '.print_r($info, 1); // You can also fetch the object into memory - // var_dump("S3::getObject() to memory", $s3->getObject($bucketName, baseName($uploadFile))); + // var_dump("S3::getObject() to memory", S3::getObject($bucketName, baseName($uploadFile))); // Or save it into a file (write stream) - // var_dump("S3::getObject() to savefile.txt", $s3->getObject($bucketName, baseName($uploadFile), 'savefile.txt')); + // var_dump("S3::getObject() to savefile.txt", S3::getObject($bucketName, baseName($uploadFile), 'savefile.txt')); // Or write it to a resource (write stream) - // var_dump("S3::getObject() to resource", $s3->getObject($bucketName, baseName($uploadFile), fopen('savefile.txt', 'wb'))); + // var_dump("S3::getObject() to resource", S3::getObject($bucketName, baseName($uploadFile), fopen('savefile.txt', 'wb'))); // Get the access control policy for a bucket: - // $acp = $s3->getAccessControlPolicy($bucketName); + // $acp = S3::getAccessControlPolicy($bucketName); // echo "S3::getAccessControlPolicy(): {$bucketName}: ".print_r($acp, 1); // Update an access control policy ($acp should be the same as the data returned by S3::getAccessControlPolicy()) - // $s3->setAccessControlPolicy($bucketName, '', $acp); - // $acp = $s3->getAccessControlPolicy($bucketName); + // S3::setAccessControlPolicy($bucketName, '', $acp); + // $acp = S3::getAccessControlPolicy($bucketName); // echo "S3::getAccessControlPolicy(): {$bucketName}: ".print_r($acp, 1); // Enable logging for a bucket: - // $s3->setBucketLogging($bucketName, 'logbucket', 'prefix'); + // S3::setBucketLogging($bucketName, 'logbucket', 'prefix'); - // if (($logging = $s3->getBucketLogging($bucketName)) !== false) { + // if (($logging = S3::getBucketLogging($bucketName)) !== false) { // echo "S3::getBucketLogging(): Logging for {$bucketName}: ".print_r($contents, 1); // } else { // echo "S3::getBucketLogging(): Logging for {$bucketName} not enabled\n"; // } // Disable bucket logging: - // var_dump($s3->disableBucketLogging($bucketName)); + // var_dump(S3::disableBucketLogging($bucketName)); // Delete our file - if ($s3->deleteObject($bucketName, baseName($uploadFile))) { + if (S3::deleteObject($bucketName, baseName($uploadFile))) { echo "S3::deleteObject(): Deleted file\n"; // Delete the bucket we created (a bucket has to be empty to be deleted) - if ($s3->deleteBucket($bucketName)) { + if (S3::deleteBucket($bucketName)) { echo "S3::deleteBucket(): Deleted bucket {$bucketName}\n"; } else { echo "S3::deleteBucket(): Failed to delete bucket (it probably isn't empty)\n"; From f2254777322493936d79496353f3b77133286d98 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 01:16:41 +0100 Subject: [PATCH 06/19] Remove access to global-state (default endpoint in S3 class) --- S3.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/S3.php b/S3.php index 34487e50..8c1ed849 100644 --- a/S3.php +++ b/S3.php @@ -624,7 +624,6 @@ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = } } - $endpoint = self::getEndpoint($endpoint); $rest = new S3Request('PUT', new BucketConfig($bucket, $endpoint->defaultRegion), '', $endpoint); $rest->setAmzHeader('x-amz-acl', $acl); @@ -2373,8 +2372,8 @@ public function getResponse() curl_setopt($curl, CURLOPT_SSLVERSION, $this->endpoint->useSSLVersion); // SSL Validation can now be optional for those with broken OpenSSL installations - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::getEndpoint()->useSSLValidation ? 2 : 0); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::getEndpoint()->useSSLValidation ? 1 : 0); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->endpoint->useSSLValidation ? 2 : 0); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->endpoint->useSSLValidation ? 1 : 0); if ($this->endpoint->sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, $this->endpoint->sslKey); if ($this->endpoint->sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, $this->endpoint->sslCert); From 2ea13998a34f52b6b52cc8fb3213b67520ed32db Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 01:17:00 +0100 Subject: [PATCH 07/19] Remove access to global-state (default endpoint in S3 class) --- S3.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/S3.php b/S3.php index 8c1ed849..6076af31 100644 --- a/S3.php +++ b/S3.php @@ -614,6 +614,8 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe */ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false, EndpointConfig $endpoint = null) { + $endpoint = self::getEndpoint($endpoint); + if ($location === false) { $location = self::getRegion($endpoint); From a7209bffabf61d2891691cb3838de3dc7235413a Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 01:17:34 +0100 Subject: [PATCH 08/19] Simplify code for __getHash() --- S3.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/S3.php b/S3.php index 6076af31..922d59b4 100644 --- a/S3.php +++ b/S3.php @@ -2034,11 +2034,16 @@ public static function __getSignature($string) */ private static function __getHash($string) { - return base64_encode(extension_loaded('hash') ? - hash_hmac('sha1', $string, self::getSecretKey(), true) : pack('H*', sha1( - (str_pad(self::getSecretKey(), 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . - pack('H*', sha1((str_pad(self::getSecretKey(), 64, chr(0x00)) ^ - (str_repeat(chr(0x36), 64))) . $string))))); + if (extension_loaded('hash')) { + return base64_encode( + hash_hmac('sha1', $string, self::getSecretKey(), true)); + } + + return base64_encode( + pack('H*', sha1( + (str_pad(self::getSecretKey(), 64, chr(0x00)) ^ str_repeat(chr(0x5c), 64)) . + pack('H*', sha1( + (str_pad(self::getSecretKey(), 64, chr(0x00)) ^ str_repeat(chr(0x36), 64)) . $string))))); } From 9c8dddd899473a590214416a453b26520399306b Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 01:18:07 +0100 Subject: [PATCH 09/19] Typo fix --- S3.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S3.php b/S3.php index 922d59b4..8de6a677 100644 --- a/S3.php +++ b/S3.php @@ -2001,7 +2001,7 @@ private static function __getMIMEType(&$file) /** * Get the current time * - * @internal Used to apply offsets to sytem time + * @internal Used to apply offsets to system time * @return integer */ public static function __getTime() From 14361aac14a1411dffc8e4a764ef4f40b150fd03 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 02:33:10 +0100 Subject: [PATCH 10/19] Add ability to specify credentials per request rather than just global --- S3.php | 288 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 166 insertions(+), 122 deletions(-) diff --git a/S3.php b/S3.php index 8de6a677..bb47b315 100644 --- a/S3.php +++ b/S3.php @@ -272,30 +272,20 @@ public static function hasAuth() { /** * Get access-key if set, otherwise null * - * @return null|string + * @param Credentials|null $creds + * @return Credentials|null */ - public static function getAccessKey() + public static function getCredentials(Credentials $creds = null) { - if (!self::hasAuth()) { - return null; + if ($creds !== null && $creds->isInitialised()) { + return $creds; } - return self::$__defaultCredentials->accessKey; - } - - - /** - * Get secret-key if set, otherwise null - * - * @return null|string - */ - public static function getSecretKey() - { - if (!self::hasAuth()) { - return null; + if (self::hasAuth()) { + return self::$__defaultCredentials; } - return self::$__defaultCredentials->secretKey; + return null; } @@ -474,11 +464,12 @@ private static function __triggerError($message, $file, $line, $code = 0) * * @param boolean $detailed Returns detailed bucket list when true * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return array | false */ - public static function listBuckets($detailed = false, EndpointConfig $endpoint = null) + public static function listBuckets($detailed = false, EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('GET', null, '', $endpoint); + $rest = new S3Request('GET', null, '', $endpoint, $creds); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -521,11 +512,14 @@ public static function listBuckets($detailed = false, EndpointConfig $endpoint = * @param string $delimiter Delimiter * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return array | false */ - public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false, EndpointConfig $endpoint = null) + public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, + $returnCommonPrefixes = false, + EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('GET', $bucket, '', $endpoint); + $rest = new S3Request('GET', $bucket, '', $endpoint, $creds); if ($maxKeys === 0) $maxKeys = null; if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker); @@ -571,7 +565,7 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe if ($maxKeys === null && $nextMarker !== null && (string)$response->body->IsTruncated === 'true') do { - $rest = new S3Request('GET', $bucket, '', $endpoint); + $rest = new S3Request('GET', $bucket, '', $endpoint, $creds); if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); $rest->setParameter('marker', $nextMarker); if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter); @@ -610,9 +604,10 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe * @param string $acl ACL flag * @param string|bool $location Set as "EU" to create buckets hosted in Europe * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false, EndpointConfig $endpoint = null) + public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false, EndpointConfig $endpoint = null, Credentials $creds = null) { $endpoint = self::getEndpoint($endpoint); @@ -626,7 +621,7 @@ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = } } - $rest = new S3Request('PUT', new BucketConfig($bucket, $endpoint->defaultRegion), '', $endpoint); + $rest = new S3Request('PUT', new BucketConfig($bucket, $endpoint->defaultRegion), '', $endpoint, $creds); $rest->setAmzHeader('x-amz-acl', $acl); if ($endpoint->hostname !== EndpointConfig::AWS_S3_DEFAULT_HOST @@ -669,11 +664,12 @@ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = * * @param string|BucketConfig $bucket Bucket name * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function deleteBucket($bucket, EndpointConfig $endpoint = null) + public static function deleteBucket($bucket, EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('DELETE', $bucket, '', $endpoint); + $rest = new S3Request('DELETE', $bucket, '', $endpoint, $creds); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 204) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -763,12 +759,13 @@ public static function inputResource(&$resource, $bufferSize = false, $md5sum = * @param string $storageClass Storage class constant * @param string $serverSideEncryption Server-side encryption * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) + public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null, Credentials $creds = null) { if ($input === false) return false; - $rest = new S3Request('PUT', $bucket, $uri, $endpoint); + $rest = new S3Request('PUT', $bucket, $uri, $endpoint, $creds); if (!is_array($input)) $input = array( 'data' => $input, 'size' => strlen($input), @@ -856,11 +853,15 @@ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE * @param string $storageClass * @param string $serverSideEncryption * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null, $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) + public static function putObjectFile( + $file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null, + $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, + EndpointConfig $endpoint = null, Credentials $creds = null) { - return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass, $serverSideEncryption, $endpoint); + return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass, $serverSideEncryption, $endpoint, $creds); } @@ -876,11 +877,15 @@ public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIV * @param string $storageClass * @param string $serverSideEncryption * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain', $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null) + public static function putObjectString( + $string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain', + $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, + EndpointConfig $endpoint = null, Credentials $creds = null) { - return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass, $serverSideEncryption, $endpoint); + return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass, $serverSideEncryption, $endpoint, $creds); } @@ -891,11 +896,12 @@ public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_ * @param string $uri Object URI * @param mixed $saveTo Filename or resource to write to * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return mixed */ - public static function getObject($bucket, $uri, $saveTo = false, EndpointConfig $endpoint = null) + public static function getObject($bucket, $uri, $saveTo = false, EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('GET', $bucket, $uri, $endpoint); + $rest = new S3Request('GET', $bucket, $uri, $endpoint, $creds); if ($saveTo !== false) { if (is_resource($saveTo)) @@ -924,11 +930,12 @@ public static function getObject($bucket, $uri, $saveTo = false, EndpointConfig * @param string $uri Object URI * @param boolean $returnInfo Return response information * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return mixed | false */ - public static function getObjectInfo($bucket, $uri, $returnInfo = true, EndpointConfig $endpoint = null) + public static function getObjectInfo($bucket, $uri, $returnInfo = true, EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('HEAD', $bucket, $uri, $endpoint); + $rest = new S3Request('HEAD', $bucket, $uri, $endpoint, $creds); $rest = $rest->getResponse(); if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404)) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -960,11 +967,12 @@ public static function getObjectInfo($bucket, $uri, $returnInfo = true, Endpoint * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.) * @param string $storageClass Storage class constant * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return mixed | false */ - public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, EndpointConfig $endpoint = null) + public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('PUT', $bucket, $uri, $endpoint); + $rest = new S3Request('PUT', $bucket, $uri, $endpoint, $creds); $rest->setHeader('Content-Length', 0); foreach ($requestHeaders as $h => $v) strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v); @@ -998,11 +1006,12 @@ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = sel * @param string|BucketConfig $bucket Bucket name * @param string $location Target host name * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function setBucketRedirect($bucket = NULL, $location = NULL, EndpointConfig $endpoint = null) + public static function setBucketRedirect($bucket = NULL, $location = NULL, EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('PUT', $bucket, '', $endpoint); + $rest = new S3Request('PUT', $bucket, '', $endpoint, $creds); if( empty($bucket) || empty($location) ) { self::__triggerError("S3::setBucketRedirect({$bucket}, {$location}): Empty parameter.", __FILE__, __LINE__); @@ -1041,9 +1050,10 @@ public static function setBucketRedirect($bucket = NULL, $location = NULL, Endpo * @param string $targetBucket Target bucket (where logs are stored) * @param string $targetPrefix Log prefix (e,g; domain.com-) * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null, EndpointConfig $endpoint = null) + public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null, EndpointConfig $endpoint = null, Credentials $creds = null) { // The S3 log delivery group has to be added to the target bucket's ACP if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket)) !== false) @@ -1080,7 +1090,7 @@ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = } $dom->appendChild($bucketLoggingStatus); - $rest = new S3Request('PUT', $bucket, '', $endpoint); + $rest = new S3Request('PUT', $bucket, '', $endpoint, $creds); $rest->setParameter('logging', null); $rest->data = $dom->saveXML(); $rest->size = strlen($rest->data); @@ -1106,11 +1116,12 @@ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = * * @param string|BucketConfig $bucket Bucket name * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return array | false */ - public static function getBucketLogging($bucket, EndpointConfig $endpoint = null) + public static function getBucketLogging($bucket, EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('GET', $bucket, '', $endpoint); + $rest = new S3Request('GET', $bucket, '', $endpoint, $creds); $rest->setParameter('logging', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1134,11 +1145,12 @@ public static function getBucketLogging($bucket, EndpointConfig $endpoint = null * * @param string|BucketConfig $bucket Bucket name * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function disableBucketLogging($bucket, EndpointConfig $endpoint = null) + public static function disableBucketLogging($bucket, EndpointConfig $endpoint = null, Credentials $creds = null) { - return self::setBucketLogging($bucket, null, $endpoint); + return self::setBucketLogging($bucket, null, null, $endpoint, $creds); } @@ -1147,15 +1159,16 @@ public static function disableBucketLogging($bucket, EndpointConfig $endpoint = * * @param string $bucket Bucket name * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return string | false */ - public static function getBucketLocation($bucket, EndpointConfig $endpoint = null) + public static function getBucketLocation($bucket, EndpointConfig $endpoint = null, Credentials $creds = null) { // https://github.com/aws/aws-sdk-js/issues/462 // Setting up any region other than 'us-east-1' $bucketConfig = new BucketConfig($bucket, 'us-west-2'); - $rest = new S3Request('GET', $bucketConfig, '', $endpoint); + $rest = new S3Request('GET', $bucketConfig, '', $endpoint, $creds); $rest->setParameter('location', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1177,9 +1190,10 @@ public static function getBucketLocation($bucket, EndpointConfig $endpoint = nul * @param string $uri Object URI * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy) * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function setAccessControlPolicy($bucket, $uri = '', $acp = array(), EndpointConfig $endpoint = null) + public static function setAccessControlPolicy($bucket, $uri = '', $acp = array(), EndpointConfig $endpoint = null, Credentials $creds = null) { $dom = new DOMDocument; $dom->formatOutput = true; @@ -1220,7 +1234,7 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() $accessControlPolicy->appendChild($accessControlList); $dom->appendChild($accessControlPolicy); - $rest = new S3Request('PUT', $bucket, $uri, $endpoint); + $rest = new S3Request('PUT', $bucket, $uri, $endpoint, $creds); $rest->setParameter('acl', null); $rest->data = $dom->saveXML(); $rest->size = strlen($rest->data); @@ -1244,11 +1258,12 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() * @param string $bucket Bucket name * @param string $uri Object URI * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return mixed | false */ - public static function getAccessControlPolicy($bucket, $uri = '', EndpointConfig $endpoint = null) + public static function getAccessControlPolicy($bucket, $uri = '', EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('GET', $bucket, $uri, $endpoint); + $rest = new S3Request('GET', $bucket, $uri, $endpoint, $creds); $rest->setParameter('acl', null); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 200) @@ -1306,11 +1321,12 @@ public static function getAccessControlPolicy($bucket, $uri = '', EndpointConfig * @param string $bucket Bucket name * @param string $uri Object URI * @param EndpointConfig|null $endpoint + * @param Credentials|null $creds * @return boolean */ - public static function deleteObject($bucket, $uri, EndpointConfig $endpoint = null) + public static function deleteObject($bucket, $uri, EndpointConfig $endpoint = null, Credentials $creds = null) { - $rest = new S3Request('DELETE', $bucket, $uri, $endpoint); + $rest = new S3Request('DELETE', $bucket, $uri, $endpoint, $creds); $rest = $rest->getResponse(); if ($rest->error === false && $rest->code !== 204) $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); @@ -1325,23 +1341,29 @@ public static function deleteObject($bucket, $uri, EndpointConfig $endpoint = nu /** - * Get a query string authenticated URL - * - * @param string $bucket Bucket name - * @param string $uri Object URI - * @param integer $lifetime Lifetime in seconds - * @param boolean $hostBucket Use the bucket name as the hostname - * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification) - * @return string - */ + * Get a query string authenticated URL + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param integer $lifetime Lifetime in seconds + * @param boolean $hostBucket Use the bucket name as the hostname + * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification) + * @return string|false + */ public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) { + $creds = self::getCredentials(); + if ($creds === null) { + self::__triggerError("S3::getAuthenticatedURL({$bucket}, {$uri}, ...): No credentials set", __FILE__, __LINE__); + return false; + } + $expires = self::__getTime() + $lifetime; $uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri)); - return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s', - // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires, - $hostBucket ? $bucket : self::getEndpoint()->hostname.'/'.$bucket, $uri, self::getAccessKey(), $expires, - urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}"))); + return sprintf(($https ? 'https' : 'http') . '://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s', + // $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires, + $hostBucket ? $bucket : self::getEndpoint()->hostname . '/' . $bucket, $uri, $creds->accessKey, $expires, + urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}", $creds->secretKey))); } @@ -1398,11 +1420,18 @@ public static function getSignedCannedURL($url, $lifetime) * @param array $amzHeaders Array of x-amz-meta-* headers * @param array $headers Array of request headers or content type as a string * @param boolean $flashVars Includes additional "Filename" variable posted by Flash - * @return object + * @return object|false */ - public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600, - $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false) + public static function getHttpUploadPostParams( + $bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600, $maxFileSize = 5242880, + $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false) { + $creds = self::getCredentials(); + if ($creds === null) { + self::__triggerError("S3::getHttpUploadPostParams({$bucket}, ...): No credentials set", __FILE__, __LINE__); + return false; + } + // Create policy object $policy = new stdClass; $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (self::__getTime() + $lifetime)); @@ -1435,11 +1464,11 @@ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = // Create parameters $params = new stdClass; - $params->AWSAccessKeyId = self::getAccessKey(); + $params->AWSAccessKeyId = $creds->accessKey; $params->key = $uriPrefix.'${filename}'; $params->acl = $acl; $params->policy = $policy; unset($policy); - $params->signature = self::__getHash($params->policy); + $params->signature = self::__getHash($params->policy, $creds); if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201), true)) $params->success_action_status = (string)$successRedirect; else @@ -1460,9 +1489,10 @@ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = * @param string $defaultRootObject Default root object * @param string $originAccessIdentity Origin access identity * @param array $trustedSigners Array of trusted signers + * @param Credentials|null $creds * @return array | false */ - public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array()) + public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array(), Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1472,7 +1502,7 @@ public static function createDistribution($bucket, $enabled = true, $cnames = ar } $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); - $rest = new S3Request('POST', null, '2010-11-01/distribution', $cloudfrontEndpoint); + $rest = new S3Request('POST', null, '2010-11-01/distribution', $cloudfrontEndpoint, $creds); $rest->data = self::__getCloudFrontDistributionConfigXML( $bucket.'.s3.amazonaws.com', $enabled, @@ -1505,9 +1535,10 @@ public static function createDistribution($bucket, $enabled = true, $cnames = ar * Get CloudFront distribution info * * @param string $distributionId Distribution ID from listDistributions() + * @param Credentials|null $creds * @return array | false */ - public static function getDistribution($distributionId) + public static function getDistribution($distributionId, Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1517,7 +1548,7 @@ public static function getDistribution($distributionId) } $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); - $rest = new S3Request('GET', null, '2010-11-01/distribution/'.$distributionId, $cloudfrontEndpoint); + $rest = new S3Request('GET', null, '2010-11-01/distribution/'.$distributionId, $cloudfrontEndpoint, $creds); $rest = self::__getCloudFrontResponse($rest); if ($rest->error === false && $rest->code !== 200) @@ -1545,9 +1576,10 @@ public static function getDistribution($distributionId) * Update a CloudFront distribution * * @param array $dist Distribution array info identical to output of getDistribution() + * @param Credentials|null $creds * @return array | false */ - public static function updateDistribution($dist) + public static function updateDistribution($dist, Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1557,7 +1589,7 @@ public static function updateDistribution($dist) } $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); - $rest = new S3Request('PUT', null, '2010-11-01/distribution/'.$dist['id'].'/config', $cloudfrontEndpoint); + $rest = new S3Request('PUT', null, '2010-11-01/distribution/'.$dist['id'].'/config', $cloudfrontEndpoint, $creds); $rest->data = self::__getCloudFrontDistributionConfigXML( $dist['origin'], $dist['enabled'], @@ -1592,9 +1624,10 @@ public static function updateDistribution($dist) * Delete a CloudFront distribution * * @param array $dist Distribution array info identical to output of getDistribution() + * @param Credentials|null $creds * @return boolean */ - public static function deleteDistribution($dist) + public static function deleteDistribution($dist, Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1604,7 +1637,7 @@ public static function deleteDistribution($dist) } $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); - $rest = new S3Request('DELETE', null, '2008-06-30/distribution/'.$dist['id'], $cloudfrontEndpoint); + $rest = new S3Request('DELETE', null, '2008-06-30/distribution/'.$dist['id'], $cloudfrontEndpoint, $creds); $rest->setHeader('If-Match', $dist['hash']); $rest = self::__getCloudFrontResponse($rest); @@ -1623,9 +1656,10 @@ public static function deleteDistribution($dist) /** * Get a list of CloudFront distributions * + * @param Credentials|null $creds * @return array|bool */ - public static function listDistributions() + public static function listDistributions(Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1634,7 +1668,7 @@ public static function listDistributions() } $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); - $rest = new S3Request('GET', null, '2010-11-01/distribution', $cloudfrontEndpoint); + $rest = new S3Request('GET', null, '2010-11-01/distribution', $cloudfrontEndpoint, $creds); $rest = self::__getCloudFrontResponse($rest); if ($rest->error === false && $rest->code !== 200) @@ -1660,9 +1694,10 @@ public static function listDistributions() /** * List CloudFront Origin Access Identities * + * @param Credentials|null $creds * @return array|bool */ - public static function listOriginAccessIdentities() + public static function listOriginAccessIdentities(Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1671,7 +1706,7 @@ public static function listOriginAccessIdentities() } $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); - $rest = new S3Request('GET', null, '2010-11-01/origin-access-identity/cloudfront', $cloudfrontEndpoint); + $rest = new S3Request('GET', null, '2010-11-01/origin-access-identity/cloudfront', $cloudfrontEndpoint, $creds); $rest = self::__getCloudFrontResponse($rest); if ($rest->error === false && $rest->code !== 200) @@ -1702,9 +1737,10 @@ public static function listOriginAccessIdentities() * * @param string $distributionId Distribution ID from listDistributions() * @param array $paths Array of object paths to invalidate + * @param Credentials|null $creds * @return bool */ - public static function invalidateDistribution($distributionId, $paths) + public static function invalidateDistribution($distributionId, $paths, Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1714,7 +1750,7 @@ public static function invalidateDistribution($distributionId, $paths) $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); - $rest = new S3Request('POST', null, '2010-08-01/distribution/'.$distributionId.'/invalidation', $cloudfrontEndpoint); + $rest = new S3Request('POST', null, '2010-08-01/distribution/'.$distributionId.'/invalidation', $cloudfrontEndpoint, $creds); $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true)); $rest->size = strlen($rest->data); $rest = self::__getCloudFrontResponse($rest); @@ -1767,9 +1803,10 @@ private static function __getCloudFrontInvalidationBatchXML($paths, $callerRefer * ) * * @param string $distributionId Distribution ID from listDistributions() + * @param Credentials|null $creds * @return array|bool */ - public static function getDistributionInvalidationList($distributionId) + public static function getDistributionInvalidationList($distributionId, Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1779,7 +1816,7 @@ public static function getDistributionInvalidationList($distributionId) $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); - $rest = new S3Request('GET', null, '2010-11-01/distribution/'.$distributionId.'/invalidation', $cloudfrontEndpoint); + $rest = new S3Request('GET', null, '2010-11-01/distribution/'.$distributionId.'/invalidation', $cloudfrontEndpoint, $creds); $rest = self::__getCloudFrontResponse($rest); if ($rest->error === false && $rest->code !== 200) @@ -2011,39 +2048,41 @@ public static function __getTime() /** - * Generate the auth string: "AWS AccessKey:Signature" - * - * @internal Used by S3Request::getResponse() - * @param string $string String to sign - * @return string - */ - public static function __getSignature($string) + * Generate the auth string: "AWS AccessKey:Signature" + * + * @param string $string String to sign + * @param Credentials $creds + * @return string + * @internal Used by S3Request::getResponse() + */ + public static function __getSignature($string, Credentials $creds) { - return 'AWS '.self::getAccessKey().':'.self::__getHash($string); + return 'AWS '.$creds->accessKey.':'.self::__getHash($string, $creds->secretKey); } /** - * Creates a HMAC-SHA1 hash - * - * This uses the hash extension if loaded - * - * @internal Used by __getSignature() - * @param string $string String to sign - * @return string - */ - private static function __getHash($string) + * Creates a HMAC-SHA1 hash + * + * This uses the hash extension if loaded + * + * @param string $string String to sign + * @param string $secretKey + * @return string + * @internal Used by __getSignature() + */ + private static function __getHash($string, $secretKey) { if (extension_loaded('hash')) { return base64_encode( - hash_hmac('sha1', $string, self::getSecretKey(), true)); + hash_hmac('sha1', $string, $secretKey, true)); } return base64_encode( pack('H*', sha1( - (str_pad(self::getSecretKey(), 64, chr(0x00)) ^ str_repeat(chr(0x5c), 64)) . + (str_pad($secretKey, 64, chr(0x00)) ^ str_repeat(chr(0x5c), 64)) . pack('H*', sha1( - (str_pad(self::getSecretKey(), 64, chr(0x00)) ^ str_repeat(chr(0x36), 64)) . $string))))); + (str_pad($secretKey, 64, chr(0x00)) ^ str_repeat(chr(0x36), 64)) . $string))))); } @@ -2055,11 +2094,12 @@ private static function __getHash($string) * @param string $method * @param string $uri * @param array $parameters + * @param Credentials $creds * @param string $region * @return string * @internal Used by S3Request::getResponse() */ - public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $parameters, $region) + public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $parameters, Credentials $creds, $region) { $service = 's3'; @@ -2109,7 +2149,7 @@ public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $p implode('/', $credentialScope), hash('sha256', $amzPayloadStr))); // Make Signature - $kSecret = 'AWS4' . self::getSecretKey(); + $kSecret = 'AWS4' . $creds->secretKey; $kDate = hash_hmac('sha256', $amzDateStamp, $kSecret, true); $kRegion = hash_hmac('sha256', $region, $kDate, true); $kService = hash_hmac('sha256', $service, $kRegion, true); @@ -2118,7 +2158,7 @@ public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $p $signature = hash_hmac('sha256', $stringToSignStr, $kSigning); return $algorithm . ' ' . implode(',', array( - 'Credential=' . self::getAccessKey() . '/' . implode('/', $credentialScope), + 'Credential=' . $creds->accessKey . '/' . implode('/', $credentialScope), 'SignedHeaders=' . implode(';', array_keys($combinedHeaders)), 'Signature=' . $signature, )); @@ -2177,6 +2217,11 @@ final class S3Request */ private $endpoint; + /** + * @var Credentials + */ + private $credentials; + /** * HTTP Verb * @@ -2206,7 +2251,7 @@ final class S3Request * @var string * @access private */ - private $resource = ''; + private $resource; /** * Additional request parameters @@ -2274,21 +2319,20 @@ final class S3Request * @param BucketConfig|string $bucket Bucket config * @param string $uri Object URI * @param EndpointConfig $endpoint AWS endpoint URI + * @param Credentials|null $creds */ - public function __construct($verb, $bucket = null, $uri = '', EndpointConfig $endpoint = null) + public function __construct($verb, $bucket = null, $uri = '', EndpointConfig $endpoint = null, Credentials $creds = null) { - $endpoint = S3::getEndpoint($endpoint); - + $this->endpoint = S3::getEndpoint($endpoint); + $this->credentials = S3::getCredentials($creds); if ($bucket !== null) { - $bucket = S3::makeBucketConfig($bucket, $endpoint); + $this->bucket = S3::makeBucketConfig($bucket, $this->endpoint); } - $this->endpoint = $endpoint; $this->verb = $verb; - $this->bucket = $bucket; $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/'; - $res = $endpoint->resolveHostUriAndResource($this->uri, $bucket); + $res = $this->endpoint->resolveHostUriAndResource($this->uri, $this->bucket); $this->headers['Host'] = $res['host']; $this->uri = $res['uri']; $this->resource = $res['resource']; @@ -2412,7 +2456,7 @@ public function getResponse() foreach ($this->headers as $header => $value) if ($value !== '') $httpHeaders[] = $header.': '.$value; - $httpHeaders[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']); + $httpHeaders[] = 'Authorization: ' . S3::__getSignature($this->headers['Date'], $this->credentials); } else { @@ -2444,6 +2488,7 @@ public function getResponse() $this->verb, $this->uri, $this->parameters, + $this->credentials, $authRegion ); @@ -2582,7 +2627,6 @@ private function __responseHeaderCallback($curl, $data) } return $strlen; } - } /** From 34671d86412ae1f7dee8a53bb019191037d4cad3 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 15:10:04 +0100 Subject: [PATCH 11/19] Add signature debug function Turned ON via an env variable SIGNATURE_DEBUG=ON --- S3.php | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/S3.php b/S3.php index bb47b315..4f9259a7 100644 --- a/S3.php +++ b/S3.php @@ -28,6 +28,24 @@ * Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates. */ +if (getenv('SIGNATURE_DEBUG') === 'ON') { + function SigDebug($format) + { + $args = func_get_args(); + if (count($args) === 0) { + return; + } + + $args[0] .= "\n"; + array_unshift($args, STDERR); + call_user_func_array('fprintf', $args); + } +} else { + function SigDebug() + {} +} + + /** * Amazon S3 PHP class * @@ -2141,19 +2159,28 @@ public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $p // request as string $amzPayloadStr = implode("\n", $amzPayload); + SigDebug('Payload to sign: [%s]', $amzPayloadStr); + // CredentialScope $credentialScope = array($amzDateStamp, $region, $service, 'aws4_request'); // stringToSign - $stringToSignStr = implode("\n", array($algorithm, $amzHeaders['x-amz-date'], - implode('/', $credentialScope), hash('sha256', $amzPayloadStr))); + $stringToSignStr = implode("\n", array($algorithm, $amzHeaders['x-amz-date'], + implode('/', $credentialScope), hash('sha256', $amzPayloadStr))); + + SigDebug('String to sign: [%s]', $stringToSignStr); // Make Signature $kSecret = 'AWS4' . $creds->secretKey; + SigDebug('$kSecret: %s', crc32($kSecret)); $kDate = hash_hmac('sha256', $amzDateStamp, $kSecret, true); + SigDebug('$kDate: %s', crc32($kDate)); $kRegion = hash_hmac('sha256', $region, $kDate, true); + SigDebug('$kRegion: %s', crc32($kRegion)); $kService = hash_hmac('sha256', $service, $kRegion, true); + SigDebug('$kService: %s', crc32($kService)); $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true); + SigDebug('$kSigning: %s', crc32($kSigning)); $signature = hash_hmac('sha256', $stringToSignStr, $kSigning); From 373559193f9223f35674c1870be3495404d7e9f2 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 15:10:34 +0100 Subject: [PATCH 12/19] Add example for custom endpoint --- example-custom-endpoint.php | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 example-custom-endpoint.php diff --git a/example-custom-endpoint.php b/example-custom-endpoint.php new file mode 100755 index 00000000..3026fb08 --- /dev/null +++ b/example-custom-endpoint.php @@ -0,0 +1,52 @@ +#!/usr/local/bin/php +withPathStyleEnabled(_getenv('USE_PATH_STYLE', 'YES') === 'YES') + ->withSSLEnabled(_getenv('USE_SSL', 'NO') === 'YES'); + +$creds = new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')); + +$bucketName = _getenv('BUCKET'); + +// Initialise S3 +S3::Init($creds, $region, $endpoint); + +// Put our file (also with public read access) +echo "S3::putObjectFile(): Copying {$uploadFile} to {$bucketName}/" . baseName($uploadFile) . PHP_EOL; +if (S3::putObjectFile($uploadFile, $bucketName, baseName($uploadFile), S3::ACL_PUBLIC_READ)) { + echo "S3::putObjectFile(): File copied to {$bucketName}/" . baseName($uploadFile) . PHP_EOL; + + + // Get object info + $info = S3::getObjectInfo($bucketName, baseName($uploadFile)); + echo "S3::getObjectInfo(): Info for {$bucketName}/" . baseName($uploadFile) . ': ' . print_r($info, 1); + + + // You can also fetch the object into memory + var_dump("S3::getObject() to memory", S3::getObject($bucketName, baseName($uploadFile))); + + + // Delete our file + if (S3::deleteObject($bucketName, baseName($uploadFile))) { + echo "S3::deleteObject(): Deleted file\n"; + } else { + echo "S3::deleteObject(): Failed to delete file\n"; + } +} else { + echo "S3::putObjectFile(): Failed to copy file\n"; +} + From 10e2c86f2cf2c84c0456157db6dc7f9c9797568a Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 15:13:55 +0100 Subject: [PATCH 13/19] Add export-ignore for example-custom-endpoint.php --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index c45aaf7f..d115c339 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,5 +4,6 @@ /example-form.php export-ignore /example-wrapper.php export-ignore /example-helpers.php export-ignore +/example-custom-endpoint.php export-ignore /.gitattributes export-ignore /.gitignore export-ignore From f6056b6aaef8eba41fe0702a8801962faac3b25d Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 15:17:47 +0100 Subject: [PATCH 14/19] Create LICENSE --- LICENSE | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f97f3277 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +BSD 2-Clause License + +Copyright (c) 2013, Donovan Schönknecht +Copyright (c) 2019, Red Matter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 284e94ea648d169cb8281d84798a86bcaa0d5917 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Thu, 3 Oct 2019 16:19:44 +0100 Subject: [PATCH 15/19] Add rename new classes to add S3 prefix --- S3.php | 288 ++++++++++++++++++------------------ example-cloudfront.php | 2 +- example-custom-endpoint.php | 4 +- example-form.php | 2 +- example-wrapper.php | 2 +- example.php | 2 +- 6 files changed, 150 insertions(+), 150 deletions(-) diff --git a/S3.php b/S3.php index 4f9259a7..0715d42c 100644 --- a/S3.php +++ b/S3.php @@ -73,14 +73,14 @@ class S3 /** * Default credentials to access AWS * - * @var Credentials|null + * @var S3Credentials|null */ private static $__defaultCredentials; /** * Default endpoint * - * @var EndpointConfig|null + * @var S3EndpointConfig|null */ private static $__defaultEndpoint; @@ -160,10 +160,10 @@ public function __construct($accessKey = null, $secretKey = null, $useSSL = fals $creds = null; if ($accessKey !== null && $secretKey !== null) { - $creds = new Credentials($accessKey, $secretKey); + $creds = new S3Credentials($accessKey, $secretKey); } - $endpointCfg = new EndpointConfig($endpoint); + $endpointCfg = new S3EndpointConfig($endpoint); $endpointCfg->withSSLEnabled($useSSL); self::Init($creds, $region, $endpointCfg); @@ -172,18 +172,18 @@ public function __construct($accessKey = null, $secretKey = null, $useSSL = fals /** * Initialise default parameters * - * @param Credentials $credentials Default credentials + * @param S3Credentials $credentials Default credentials * @param string $region Auth region for SigV4 - * @param EndpointConfig|null $endpoint Endpoint configuration, null for AWS S3 settings + * @param S3EndpointConfig|null $endpoint Endpoint configuration, null for AWS S3 settings */ - public static function Init(Credentials $credentials, $region = '', EndpointConfig $endpoint = null) + public static function Init(S3Credentials $credentials, $region = '', S3EndpointConfig $endpoint = null) { self::setCredentials($credentials); self::$region = $region; if ($endpoint === null) { - $endpoint = new EndpointConfig(); + $endpoint = new S3EndpointConfig(); } self::setEndpoint($endpoint); @@ -192,17 +192,17 @@ public static function Init(Credentials $credentials, $region = '', EndpointConf /** * Endpoint override helper * - * @param EndpointConfig|null $endpoint - * @return EndpointConfig + * @param S3EndpointConfig|null $endpoint + * @return S3EndpointConfig */ - public static function getEndpoint(EndpointConfig $endpoint = null) + public static function getEndpoint(S3EndpointConfig $endpoint = null) { if ($endpoint !== null) { return $endpoint; } if (self::$__defaultEndpoint === null) { - self::$__defaultEndpoint = new EndpointConfig(); + self::$__defaultEndpoint = new S3EndpointConfig(); } return self::$__defaultEndpoint; @@ -212,10 +212,10 @@ public static function getEndpoint(EndpointConfig $endpoint = null) /** * Set the service endpoint * - * @param EndpointConfig $endpoint + * @param S3EndpointConfig $endpoint * @return void */ - public static function setEndpoint(EndpointConfig $endpoint) + public static function setEndpoint(S3EndpointConfig $endpoint) { self::$__defaultEndpoint = $endpoint; } @@ -236,11 +236,11 @@ public function setRegion($region) /** * Get the service region * - * @param EndpointConfig|null $endpoint + * @param S3EndpointConfig|null $endpoint * @return string * @static */ - public static function getRegion(EndpointConfig $endpoint = null) + public static function getRegion(S3EndpointConfig $endpoint = null) { if (!empty(self::$region)) { @@ -262,16 +262,16 @@ public static function getRegion(EndpointConfig $endpoint = null) */ public static function setAuth($accessKey, $secretKey) { - self::setCredentials(new Credentials($accessKey, $secretKey)); + self::setCredentials(new S3Credentials($accessKey, $secretKey)); } /** * Set default credentials * - * @param Credentials $creds + * @param S3Credentials $creds */ - public static function setCredentials(Credentials $creds) + public static function setCredentials(S3Credentials $creds) { self::$__defaultCredentials = $creds; } @@ -290,10 +290,10 @@ public static function hasAuth() { /** * Get access-key if set, otherwise null * - * @param Credentials|null $creds - * @return Credentials|null + * @param S3Credentials|null $creds + * @return S3Credentials|null */ - public static function getCredentials(Credentials $creds = null) + public static function getCredentials(S3Credentials $creds = null) { if ($creds !== null && $creds->isInitialised()) { return $creds; @@ -375,10 +375,10 @@ public static function setExceptions($enabled = true) * resort when the system time cannot be changed. * * @param int $offset Time offset (set to zero to use AWS server time) - * @param EndpointConfig|null $endpoint + * @param S3EndpointConfig|null $endpoint * @return void */ - public static function setTimeCorrectionOffset($offset = 0, EndpointConfig $endpoint = null) + public static function setTimeCorrectionOffset($offset = 0, S3EndpointConfig $endpoint = null) { if ($offset === 0) { @@ -481,11 +481,11 @@ private static function __triggerError($message, $file, $line, $code = 0) * Get a list of buckets * * @param boolean $detailed Returns detailed bucket list when true - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return array | false */ - public static function listBuckets($detailed = false, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function listBuckets($detailed = false, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('GET', null, '', $endpoint, $creds); $rest = $rest->getResponse(); @@ -523,19 +523,19 @@ public static function listBuckets($detailed = false, EndpointConfig $endpoint = * * If maxKeys is null this method will loop through truncated result sets * - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $prefix Prefix * @param string $marker Marker (last file listed) * @param string $maxKeys Max keys (maximum number of keys to return) * @param string $delimiter Delimiter * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return array | false */ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false, - EndpointConfig $endpoint = null, Credentials $creds = null) + S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('GET', $bucket, '', $endpoint, $creds); if ($maxKeys === 0) $maxKeys = null; @@ -621,11 +621,11 @@ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKe * @param string $bucket Bucket name * @param string $acl ACL flag * @param string|bool $location Set as "EU" to create buckets hosted in Europe - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ - public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $endpoint = self::getEndpoint($endpoint); @@ -639,11 +639,11 @@ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = } } - $rest = new S3Request('PUT', new BucketConfig($bucket, $endpoint->defaultRegion), '', $endpoint, $creds); + $rest = new S3Request('PUT', new S3BucketConfig($bucket, $endpoint->defaultRegion), '', $endpoint, $creds); $rest->setAmzHeader('x-amz-acl', $acl); - if ($endpoint->hostname !== EndpointConfig::AWS_S3_DEFAULT_HOST - || $location !== EndpointConfig::AWS_S3_DEFAULT_REGION) { + if ($endpoint->hostname !== S3EndpointConfig::AWS_S3_DEFAULT_HOST + || $location !== S3EndpointConfig::AWS_S3_DEFAULT_REGION) { $dom = new DOMDocument; $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); @@ -680,12 +680,12 @@ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = /** * Delete an empty bucket * - * @param string|BucketConfig $bucket Bucket name - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param string|S3BucketConfig $bucket Bucket name + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ - public static function deleteBucket($bucket, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function deleteBucket($bucket, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('DELETE', $bucket, '', $endpoint, $creds); $rest = $rest->getResponse(); @@ -769,18 +769,18 @@ public static function inputResource(&$resource, $bufferSize = false, $md5sum = * Put an object * * @param mixed $input Input data - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $uri Object URI * @param string $acl ACL constant * @param array $metaHeaders Array of x-amz-meta-* headers * @param array|string $requestHeaders Array of request headers or content type as a string * @param string $storageClass Storage class constant * @param string $serverSideEncryption Server-side encryption - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ - public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { if ($input === false) return false; $rest = new S3Request('PUT', $bucket, $uri, $endpoint, $creds); @@ -863,21 +863,21 @@ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE * Put an object from a file (legacy function) * * @param string $file Input file path - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $uri Object URI * @param string $acl ACL constant * @param array $metaHeaders Array of x-amz-meta-* headers * @param string $contentType Content type * @param string $storageClass * @param string $serverSideEncryption - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ public static function putObjectFile( $file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null, $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, - EndpointConfig $endpoint = null, Credentials $creds = null) + S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass, $serverSideEncryption, $endpoint, $creds); } @@ -887,21 +887,21 @@ public static function putObjectFile( * Put an object from a string (legacy function) * * @param string $string Input data - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $uri Object URI * @param string $acl ACL constant * @param array $metaHeaders Array of x-amz-meta-* headers * @param string $contentType Content type * @param string $storageClass * @param string $serverSideEncryption - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ public static function putObjectString( $string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain', $storageClass = self::STORAGE_CLASS_STANDARD, $serverSideEncryption = self::SSE_NONE, - EndpointConfig $endpoint = null, Credentials $creds = null) + S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType, $storageClass, $serverSideEncryption, $endpoint, $creds); } @@ -910,14 +910,14 @@ public static function putObjectString( /** * Get an object * - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $uri Object URI * @param mixed $saveTo Filename or resource to write to - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return mixed */ - public static function getObject($bucket, $uri, $saveTo = false, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function getObject($bucket, $uri, $saveTo = false, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('GET', $bucket, $uri, $endpoint, $creds); if ($saveTo !== false) @@ -944,14 +944,14 @@ public static function getObject($bucket, $uri, $saveTo = false, EndpointConfig /** * Get object information * - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $uri Object URI * @param boolean $returnInfo Return response information - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return mixed | false */ - public static function getObjectInfo($bucket, $uri, $returnInfo = true, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function getObjectInfo($bucket, $uri, $returnInfo = true, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('HEAD', $bucket, $uri, $endpoint, $creds); $rest = $rest->getResponse(); @@ -978,17 +978,17 @@ public static function getObjectInfo($bucket, $uri, $returnInfo = true, Endpoint * * @param string $srcBucket Source bucket name * @param string $srcUri Source object URI - * @param string|BucketConfig $bucket Destination bucket name + * @param string|S3BucketConfig $bucket Destination bucket name * @param string $uri Destination object URI * @param string $acl ACL constant * @param array $metaHeaders Optional array of x-amz-meta-* headers * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.) * @param string $storageClass Storage class constant - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return mixed | false */ - public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('PUT', $bucket, $uri, $endpoint, $creds); $rest->setHeader('Content-Length', 0); @@ -1021,13 +1021,13 @@ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = sel /** * Set up a bucket redirection * - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $location Target host name - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ - public static function setBucketRedirect($bucket = NULL, $location = NULL, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function setBucketRedirect($bucket = NULL, $location = NULL, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('PUT', $bucket, '', $endpoint, $creds); @@ -1064,14 +1064,14 @@ public static function setBucketRedirect($bucket = NULL, $location = NULL, Endpo /** * Set logging for a bucket * - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $targetBucket Target bucket (where logs are stored) * @param string $targetPrefix Log prefix (e,g; domain.com-) - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ - public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { // The S3 log delivery group has to be added to the target bucket's ACP if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket)) !== false) @@ -1132,12 +1132,12 @@ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = * This will return false if logging is not enabled. * Note: To enable logging, you also need to grant write access to the log group * - * @param string|BucketConfig $bucket Bucket name - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param string|S3BucketConfig $bucket Bucket name + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return array | false */ - public static function getBucketLogging($bucket, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function getBucketLogging($bucket, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('GET', $bucket, '', $endpoint, $creds); $rest->setParameter('logging', null); @@ -1161,12 +1161,12 @@ public static function getBucketLogging($bucket, EndpointConfig $endpoint = null /** * Disable bucket logging * - * @param string|BucketConfig $bucket Bucket name - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param string|S3BucketConfig $bucket Bucket name + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ - public static function disableBucketLogging($bucket, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function disableBucketLogging($bucket, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { return self::setBucketLogging($bucket, null, null, $endpoint, $creds); } @@ -1176,15 +1176,15 @@ public static function disableBucketLogging($bucket, EndpointConfig $endpoint = * Get a bucket's location * * @param string $bucket Bucket name - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return string | false */ - public static function getBucketLocation($bucket, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function getBucketLocation($bucket, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { // https://github.com/aws/aws-sdk-js/issues/462 // Setting up any region other than 'us-east-1' - $bucketConfig = new BucketConfig($bucket, 'us-west-2'); + $bucketConfig = new S3BucketConfig($bucket, 'us-west-2'); $rest = new S3Request('GET', $bucketConfig, '', $endpoint, $creds); $rest->setParameter('location', null); @@ -1204,14 +1204,14 @@ public static function getBucketLocation($bucket, EndpointConfig $endpoint = nul /** * Set object or bucket Access Control Policy * - * @param string|BucketConfig $bucket Bucket name + * @param string|S3BucketConfig $bucket Bucket name * @param string $uri Object URI * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy) - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ - public static function setAccessControlPolicy($bucket, $uri = '', $acp = array(), EndpointConfig $endpoint = null, Credentials $creds = null) + public static function setAccessControlPolicy($bucket, $uri = '', $acp = array(), S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $dom = new DOMDocument; $dom->formatOutput = true; @@ -1275,11 +1275,11 @@ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array() * * @param string $bucket Bucket name * @param string $uri Object URI - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return mixed | false */ - public static function getAccessControlPolicy($bucket, $uri = '', EndpointConfig $endpoint = null, Credentials $creds = null) + public static function getAccessControlPolicy($bucket, $uri = '', S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('GET', $bucket, $uri, $endpoint, $creds); $rest->setParameter('acl', null); @@ -1338,11 +1338,11 @@ public static function getAccessControlPolicy($bucket, $uri = '', EndpointConfig * * @param string $bucket Bucket name * @param string $uri Object URI - * @param EndpointConfig|null $endpoint - * @param Credentials|null $creds + * @param S3EndpointConfig|null $endpoint + * @param S3Credentials|null $creds * @return boolean */ - public static function deleteObject($bucket, $uri, EndpointConfig $endpoint = null, Credentials $creds = null) + public static function deleteObject($bucket, $uri, S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $rest = new S3Request('DELETE', $bucket, $uri, $endpoint, $creds); $rest = $rest->getResponse(); @@ -1507,10 +1507,10 @@ public static function getHttpUploadPostParams( * @param string $defaultRootObject Default root object * @param string $originAccessIdentity Origin access identity * @param array $trustedSigners Array of trusted signers - * @param Credentials|null $creds + * @param S3Credentials|null $creds * @return array | false */ - public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array(), Credentials $creds = null) + public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array(), S3Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1519,7 +1519,7 @@ public static function createDistribution($bucket, $enabled = true, $cnames = ar return false; } - $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new S3EndpointConfig('cloudfront.amazonaws.com'); $rest = new S3Request('POST', null, '2010-11-01/distribution', $cloudfrontEndpoint, $creds); $rest->data = self::__getCloudFrontDistributionConfigXML( $bucket.'.s3.amazonaws.com', @@ -1553,10 +1553,10 @@ public static function createDistribution($bucket, $enabled = true, $cnames = ar * Get CloudFront distribution info * * @param string $distributionId Distribution ID from listDistributions() - * @param Credentials|null $creds + * @param S3Credentials|null $creds * @return array | false */ - public static function getDistribution($distributionId, Credentials $creds = null) + public static function getDistribution($distributionId, S3Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1565,7 +1565,7 @@ public static function getDistribution($distributionId, Credentials $creds = nul return false; } - $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new S3EndpointConfig('cloudfront.amazonaws.com'); $rest = new S3Request('GET', null, '2010-11-01/distribution/'.$distributionId, $cloudfrontEndpoint, $creds); $rest = self::__getCloudFrontResponse($rest); @@ -1594,10 +1594,10 @@ public static function getDistribution($distributionId, Credentials $creds = nul * Update a CloudFront distribution * * @param array $dist Distribution array info identical to output of getDistribution() - * @param Credentials|null $creds + * @param S3Credentials|null $creds * @return array | false */ - public static function updateDistribution($dist, Credentials $creds = null) + public static function updateDistribution($dist, S3Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1606,7 +1606,7 @@ public static function updateDistribution($dist, Credentials $creds = null) return false; } - $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new S3EndpointConfig('cloudfront.amazonaws.com'); $rest = new S3Request('PUT', null, '2010-11-01/distribution/'.$dist['id'].'/config', $cloudfrontEndpoint, $creds); $rest->data = self::__getCloudFrontDistributionConfigXML( $dist['origin'], @@ -1642,10 +1642,10 @@ public static function updateDistribution($dist, Credentials $creds = null) * Delete a CloudFront distribution * * @param array $dist Distribution array info identical to output of getDistribution() - * @param Credentials|null $creds + * @param S3Credentials|null $creds * @return boolean */ - public static function deleteDistribution($dist, Credentials $creds = null) + public static function deleteDistribution($dist, S3Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1654,7 +1654,7 @@ public static function deleteDistribution($dist, Credentials $creds = null) return false; } - $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new S3EndpointConfig('cloudfront.amazonaws.com'); $rest = new S3Request('DELETE', null, '2008-06-30/distribution/'.$dist['id'], $cloudfrontEndpoint, $creds); $rest->setHeader('If-Match', $dist['hash']); $rest = self::__getCloudFrontResponse($rest); @@ -1674,10 +1674,10 @@ public static function deleteDistribution($dist, Credentials $creds = null) /** * Get a list of CloudFront distributions * - * @param Credentials|null $creds + * @param S3Credentials|null $creds * @return array|bool */ - public static function listDistributions(Credentials $creds = null) + public static function listDistributions(S3Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1685,7 +1685,7 @@ public static function listDistributions(Credentials $creds = null) return false; } - $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new S3EndpointConfig('cloudfront.amazonaws.com'); $rest = new S3Request('GET', null, '2010-11-01/distribution', $cloudfrontEndpoint, $creds); $rest = self::__getCloudFrontResponse($rest); @@ -1712,10 +1712,10 @@ public static function listDistributions(Credentials $creds = null) /** * List CloudFront Origin Access Identities * - * @param Credentials|null $creds + * @param S3Credentials|null $creds * @return array|bool */ - public static function listOriginAccessIdentities(Credentials $creds = null) + public static function listOriginAccessIdentities(S3Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1723,7 +1723,7 @@ public static function listOriginAccessIdentities(Credentials $creds = null) return false; } - $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new S3EndpointConfig('cloudfront.amazonaws.com'); $rest = new S3Request('GET', null, '2010-11-01/origin-access-identity/cloudfront', $cloudfrontEndpoint, $creds); $rest = self::__getCloudFrontResponse($rest); @@ -1755,10 +1755,10 @@ public static function listOriginAccessIdentities(Credentials $creds = null) * * @param string $distributionId Distribution ID from listDistributions() * @param array $paths Array of object paths to invalidate - * @param Credentials|null $creds + * @param S3Credentials|null $creds * @return bool */ - public static function invalidateDistribution($distributionId, $paths, Credentials $creds = null) + public static function invalidateDistribution($distributionId, $paths, S3Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1766,7 +1766,7 @@ public static function invalidateDistribution($distributionId, $paths, Credentia return false; } - $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new S3EndpointConfig('cloudfront.amazonaws.com'); $rest = new S3Request('POST', null, '2010-08-01/distribution/'.$distributionId.'/invalidation', $cloudfrontEndpoint, $creds); $rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true)); @@ -1821,10 +1821,10 @@ private static function __getCloudFrontInvalidationBatchXML($paths, $callerRefer * ) * * @param string $distributionId Distribution ID from listDistributions() - * @param Credentials|null $creds + * @param S3Credentials|null $creds * @return array|bool */ - public static function getDistributionInvalidationList($distributionId, Credentials $creds = null) + public static function getDistributionInvalidationList($distributionId, S3Credentials $creds = null) { if (!extension_loaded('openssl')) { @@ -1832,7 +1832,7 @@ public static function getDistributionInvalidationList($distributionId, Credenti return false; } - $cloudfrontEndpoint = new EndpointConfig('cloudfront.amazonaws.com'); + $cloudfrontEndpoint = new S3EndpointConfig('cloudfront.amazonaws.com'); $rest = new S3Request('GET', null, '2010-11-01/distribution/'.$distributionId.'/invalidation', $cloudfrontEndpoint, $creds); $rest = self::__getCloudFrontResponse($rest); @@ -2069,11 +2069,11 @@ public static function __getTime() * Generate the auth string: "AWS AccessKey:Signature" * * @param string $string String to sign - * @param Credentials $creds + * @param S3Credentials $creds * @return string * @internal Used by S3Request::getResponse() */ - public static function __getSignature($string, Credentials $creds) + public static function __getSignature($string, S3Credentials $creds) { return 'AWS '.$creds->accessKey.':'.self::__getHash($string, $creds->secretKey); } @@ -2112,12 +2112,12 @@ private static function __getHash($string, $secretKey) * @param string $method * @param string $uri * @param array $parameters - * @param Credentials $creds + * @param S3Credentials $creds * @param string $region * @return string * @internal Used by S3Request::getResponse() */ - public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $parameters, Credentials $creds, $region) + public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $parameters, S3Credentials $creds, $region) { $service = 's3'; @@ -2215,17 +2215,17 @@ private static function __sortMetaHeadersCmp($a, $b) /** * Helper to transition from bucket names to bucket-config * - * @param string|BucketConfig $bucket - * @param EndpointConfig $endpoint - * @return BucketConfig + * @param string|S3BucketConfig $bucket + * @param S3EndpointConfig $endpoint + * @return S3BucketConfig */ - public static function makeBucketConfig($bucket, EndpointConfig $endpoint) + public static function makeBucketConfig($bucket, S3EndpointConfig $endpoint) { - if ($bucket instanceof BucketConfig) { + if ($bucket instanceof S3BucketConfig) { return $bucket; } - return new BucketConfig($bucket, self::getRegion($endpoint)); + return new S3BucketConfig($bucket, self::getRegion($endpoint)); } } @@ -2240,12 +2240,12 @@ final class S3Request /** * endpoint config * - * @var EndpointConfig + * @var S3EndpointConfig */ private $endpoint; /** - * @var Credentials + * @var S3Credentials */ private $credentials; @@ -2259,7 +2259,7 @@ final class S3Request /** * bucket config * - * @var BucketConfig + * @var S3BucketConfig * @access private */ private $bucket; @@ -2343,12 +2343,12 @@ final class S3Request * Constructor * * @param string $verb Verb - * @param BucketConfig|string $bucket Bucket config + * @param S3BucketConfig|string $bucket Bucket config * @param string $uri Object URI - * @param EndpointConfig $endpoint AWS endpoint URI - * @param Credentials|null $creds + * @param S3EndpointConfig $endpoint AWS endpoint URI + * @param S3Credentials|null $creds */ - public function __construct($verb, $bucket = null, $uri = '', EndpointConfig $endpoint = null, Credentials $creds = null) + public function __construct($verb, $bucket = null, $uri = '', S3EndpointConfig $endpoint = null, S3Credentials $creds = null) { $this->endpoint = S3::getEndpoint($endpoint); $this->credentials = S3::getCredentials($creds); @@ -2686,9 +2686,9 @@ public function __toString() } /** - * EndpointConfig class + * S3EndpointConfig class */ -final class EndpointConfig +final class S3EndpointConfig { const AWS_S3_SUFFIX = '.amazonaws.com'; const AWS_S3_SUFFIX_LENGTH = 14; @@ -2826,10 +2826,10 @@ private function isDnsSafeName($bucket) /** * @param $uri - * @param BucketConfig|null $bucket + * @param S3BucketConfig|null $bucket * @return array */ - public function resolveHostUriAndResource($uri, BucketConfig $bucket = null) + public function resolveHostUriAndResource($uri, S3BucketConfig $bucket = null) { if ($bucket === null) { return array( @@ -2945,9 +2945,9 @@ public function withProxy($host, $user = null, $pass = null, $type = CURLPROXY_S } /** - * BucketConfig class + * S3BucketConfig class */ -final class BucketConfig +final class S3BucketConfig { public $name; public $region; @@ -2965,9 +2965,9 @@ public function __toString() } /** - * Credentials class + * S3Credentials class */ -final class Credentials +final class S3Credentials { public $accessKey; public $secretKey; diff --git a/example-cloudfront.php b/example-cloudfront.php index 1ee64c1f..cd6654bb 100755 --- a/example-cloudfront.php +++ b/example-cloudfront.php @@ -15,7 +15,7 @@ // Initialise S3 S3::Init( - new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), + new S3Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), _getenv('REGION', 'us-west-1') ); diff --git a/example-custom-endpoint.php b/example-custom-endpoint.php index 3026fb08..891dab10 100755 --- a/example-custom-endpoint.php +++ b/example-custom-endpoint.php @@ -13,12 +13,12 @@ $region = _getenv('REGION'); -$endpoint = new EndpointConfig(_getenv('ENDPOINT'), $region); +$endpoint = new S3EndpointConfig(_getenv('ENDPOINT'), $region); $endpoint ->withPathStyleEnabled(_getenv('USE_PATH_STYLE', 'YES') === 'YES') ->withSSLEnabled(_getenv('USE_SSL', 'NO') === 'YES'); -$creds = new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')); +$creds = new S3Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')); $bucketName = _getenv('BUCKET'); diff --git a/example-form.php b/example-form.php index 247731a6..26a799f2 100644 --- a/example-form.php +++ b/example-form.php @@ -12,7 +12,7 @@ // Initialise S3 S3::Init( - new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), + new S3Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), _getenv('REGION', 'us-west-1') ); diff --git a/example-wrapper.php b/example-wrapper.php index 9d54a7df..8aba7208 100755 --- a/example-wrapper.php +++ b/example-wrapper.php @@ -178,7 +178,7 @@ private function __translateMode($mode) { // Initialise S3 S3::Init( - new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), + new S3Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), _getenv('REGION', 'us-west-1') ); diff --git a/example.php b/example.php index bed4906a..6ea579a3 100755 --- a/example.php +++ b/example.php @@ -15,7 +15,7 @@ // Initialise S3 S3::Init( - new Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), + new S3Credentials(_getenv('ACCESS_KEY'), _getenv('SECRET_KEY')), _getenv('REGION', 'us-west-1') ); From 2c3e28bd49d6f44dd5f5c940c5f94e130835c3c7 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Tue, 21 Jan 2020 23:33:44 +0000 Subject: [PATCH 16/19] Fix refactoring typo --- S3.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S3.php b/S3.php index 0715d42c..d687851b 100644 --- a/S3.php +++ b/S3.php @@ -2471,7 +2471,7 @@ public function getResponse() // Headers $httpHeaders = array(); - if (S3::hasAuth()) + if ($this->credentials !== null) { // Authorization string (CloudFront stringToSign should only contain a date) if ($this->endpoint->signatureVersion === S3::SigV2 || $this->headers['Host'] === 'cloudfront.amazonaws.com') From 4f9ef504a8ac633e49dab5476c9ff1452ebc2efb Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Tue, 4 Feb 2020 12:42:03 +0000 Subject: [PATCH 17/19] Add missing curl constants --- S3.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/S3.php b/S3.php index d687851b..d4b8e752 100644 --- a/S3.php +++ b/S3.php @@ -28,6 +28,12 @@ * Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates. */ +// Add curl constants that are missing till PHP v5.5 +defined('CURL_SSLVERSION_DEFAULT') || define('CURL_SSLVERSION_DEFAULT', 0); +defined('CURL_SSLVERSION_TLSv1') || define('CURL_SSLVERSION_TLSv1', 1); +defined('CURL_SSLVERSION_SSLv2') || define('CURL_SSLVERSION_SSLv2', 2); +defined('CURL_SSLVERSION_SSLv3') || define('CURL_SSLVERSION_SSLv3', 3); + if (getenv('SIGNATURE_DEBUG') === 'ON') { function SigDebug($format) { @@ -2464,9 +2470,12 @@ public function getResponse() { curl_setopt($curl, CURLOPT_PROXY, $this->endpoint->proxy['host']); curl_setopt($curl, CURLOPT_PROXYTYPE, $this->endpoint->proxy['type']); - /** @noinspection NotOptimalIfConditionsInspection */ - if (isset($this->endpoint->proxy['user'], $this->endpoint->proxy['pass']) && $this->endpoint->proxy['user'] !== null && $this->endpoint->proxy['pass'] !== null) + if (isset($this->endpoint->proxy['user'], $this->endpoint->proxy['pass']) + && $this->endpoint->proxy['user'] !== null + && $this->endpoint->proxy['pass'] !== null) + { curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', $this->endpoint->proxy['user'], $this->endpoint->proxy['pass'])); + } } // Headers From 82076064b11a3c1bfcf37987976f988bdc30e2dc Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Mon, 10 Feb 2020 15:55:25 +0000 Subject: [PATCH 18/19] Remove default arg value --- S3.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S3.php b/S3.php index d4b8e752..60a684f4 100644 --- a/S3.php +++ b/S3.php @@ -2142,7 +2142,7 @@ public static function __getSignatureV4($amzHeaders, $headers, $method, $uri, $p // Convert null query string parameters to strings and sort $parameters = array_map('strval', $parameters); uksort($parameters, array('self', '__sortMetaHeadersCmp')); - $queryString = http_build_query($parameters, null, '&', PHP_QUERY_RFC3986); + $queryString = http_build_query($parameters, null, '&'); // Payload $amzPayload = array($method); From 537ca820ebb022fe573f63e82bd3d69e227c7351 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Tue, 11 Feb 2020 00:49:59 +0000 Subject: [PATCH 19/19] Fix typo --- S3.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S3.php b/S3.php index 60a684f4..e39d5786 100644 --- a/S3.php +++ b/S3.php @@ -2783,7 +2783,7 @@ final class S3EndpointConfig public function __construct($hostname = self::AWS_S3_DEFAULT_HOST, $defaultRegion = null) { - if ($hostname === self::AWS_S3_DEFAULT_HOST) { + if ($defaultRegion === null && $hostname === self::AWS_S3_DEFAULT_HOST) { $defaultRegion = self::AWS_S3_DEFAULT_REGION; }