.s3.amazonaws.com").
*
* @param string $bucket Bucket name to check.
*
* @return bool
*/
public static function isBucketDnsCompatible($bucket)
{
if (!is_string($bucket)) {
return false;
}
$bucketLen = strlen($bucket);
return ($bucketLen >= 3 && $bucketLen <= 63) &&
// Cannot look like an IP address
!filter_var($bucket, FILTER_VALIDATE_IP) &&
preg_match('/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/', $bucket);
}
public static function _apply_use_arn_region($value, array &$args, HandlerList $list)
{
if ($value instanceof CacheInterface) {
$value = UseArnRegionConfigurationProvider::defaultProvider($args);
}
if (is_callable($value)) {
$value = $value();
}
if ($value instanceof PromiseInterface) {
$value = $value->wait();
}
if ($value instanceof ConfigurationInterface) {
$args['use_arn_region'] = $value;
} else {
// The Configuration class itself will validate other inputs
$args['use_arn_region'] = new Configuration($value);
}
}
public function createPresignedRequest(CommandInterface $command, $expires, array $options = [])
{
$command = clone $command;
$command->getHandlerList()->remove('signer');
$request = \Aws\serialize($command);
$signing_name = empty($command->getAuthSchemes())
? $this->getSigningName($request->getUri()->getHost())
: $command->getAuthSchemes()['name'];
$signature_version = $this->getSignatureVersionFromCommand($command);
/** @var \Aws\Signature\SignatureInterface $signer */
$signer = call_user_func(
$this->getSignatureProvider(),
$signature_version,
$signing_name,
$this->getConfig('signing_region')
);
if ($signature_version == 'v4-s3express') {
$provider = $this->getConfig('s3_express_identity_provider');
$credentials = $provider($command)->wait();
} else {
$credentials = $this->getCredentials()->wait();
}
return $signer->presign(
$request,
$credentials,
$expires,
$options
);
}
/**
* Returns the URL to an object identified by its bucket and key.
*
* The URL returned by this method is not signed nor does it ensure that the
* bucket and key given to the method exist. If you need a signed URL, then
* use the {@see \Aws\S3\S3Client::createPresignedRequest} method and get
* the URI of the signed request.
*
* @param string $bucket The name of the bucket where the object is located
* @param string $key The key of the object
*
* @return string The URL to the object
*/
public function getObjectUrl($bucket, $key)
{
$command = $this->getCommand('GetObject', [
'Bucket' => $bucket,
'Key' => $key
]);
return (string) \Aws\serialize($command)->getUri();
}
/**
* Raw URL encode a key and allow for '/' characters
*
* @param string $key Key to encode
*
* @return string Returns the encoded key
*/
public static function encodeKey($key)
{
return str_replace('%2F', '/', rawurlencode($key));
}
/**
* Provides a middleware that removes the need to specify LocationConstraint on CreateBucket.
*
* @return \Closure
*/
private function getLocationConstraintMiddleware()
{
$region = $this->getRegion();
return static function (callable $handler) use ($region) {
return function (Command $command, $request = null) use ($handler, $region) {
if ($command->getName() === 'CreateBucket') {
$locationConstraint = isset($command['CreateBucketConfiguration']['LocationConstraint'])
? $command['CreateBucketConfiguration']['LocationConstraint']
: null;
if ($locationConstraint === 'us-east-1') {
unset($command['CreateBucketConfiguration']);
} elseif ('us-east-1' !== $region && empty($locationConstraint)) {
$command['CreateBucketConfiguration'] = ['LocationConstraint' => $region];
}
}
return $handler($command, $request);
};
};
}
/**
* Provides a middleware that supports the `SaveAs` parameter.
*
* @return \Closure
*/
private function getSaveAsParameter()
{
return static function (callable $handler) {
return function (Command $command, $request = null) use ($handler) {
if ($command->getName() === 'GetObject' && isset($command['SaveAs'])) {
$command['@http']['sink'] = $command['SaveAs'];
unset($command['SaveAs']);
}
return $handler($command, $request);
};
};
}
/**
* Provides a middleware that disables content decoding on HeadObject
* commands.
*
* @return \Closure
*/
private function getHeadObjectMiddleware()
{
return static function (callable $handler) {
return function (
CommandInterface $command,
RequestInterface $request = null
) use ($handler) {
if ($command->getName() === 'HeadObject'
&& !isset($command['@http']['decode_content'])
) {
$command['@http']['decode_content'] = false;
}
return $handler($command, $request);
};
};
}
/**
* Provides a middleware that autopopulates the EncodingType parameter on
* ListObjects commands.
*
* @return \Closure
*/
private function getEncodingTypeMiddleware()
{
return static function (callable $handler) {
return function (Command $command, $request = null) use ($handler) {
$autoSet = false;
if ($command->getName() === 'ListObjects'
&& empty($command['EncodingType'])
) {
$command['EncodingType'] = 'url';
$autoSet = true;
}
return $handler($command, $request)
->then(function (ResultInterface $result) use ($autoSet) {
if ($result['EncodingType'] === 'url' && $autoSet) {
static $topLevel = [
'Delimiter',
'Marker',
'NextMarker',
'Prefix',
];
static $nested = [
['Contents', 'Key'],
['CommonPrefixes', 'Prefix'],
];
foreach ($topLevel as $key) {
if (isset($result[$key])) {
$result[$key] = urldecode($result[$key]);
}
}
foreach ($nested as $steps) {
if (isset($result[$steps[0]])) {
foreach ($result[$steps[0]] as $key => $part) {
if (isset($part[$steps[1]])) {
$result[$steps[0]][$key][$steps[1]]
= urldecode($part[$steps[1]]);
}
}
}
}
}
return $result;
});
};
};
}
/**
* Provides a middleware that checks for an empty path and a
* non-empty query string.
*
* @return \Closure
*/
private function getEmptyPathWithQuery()
{
return static function (callable $handler) {
return function (Command $command, RequestInterface $request) use ($handler) {
$uri = $request->getUri();
if (empty($uri->getPath()) && !empty($uri->getQuery())) {
$uri = $uri->withPath('/');
$request = $request->withUri($uri);
}
return $handler($command, $request);
};
};
}
/**
* Provides a middleware that disables express session auth when
* customers opt out of it.
*
* @return \Closure
*/
private function getDisableExpressSessionAuthMiddleware()
{
return function (callable $handler) {
return function (
CommandInterface $command,
RequestInterface $request = null
) use ($handler) {
if (!empty($command->getAuthSchemes()['version'] )
&& $command->getAuthSchemes()['version'] == 'v4-s3express'
) {
$authScheme = $command->getAuthSchemes();
$authScheme['version'] = 's3v4';
$command->setAuthSchemes($authScheme);
}
return $handler($command, $request);
};
};
}
/**
* Special handling for when the service name is s3-object-lambda.
* So, if the host contains s3-object-lambda, then the service name
* returned is s3-object-lambda, otherwise the default signing service is returned.
* @param string $host The host to validate if is a s3-object-lambda URL.
* @return string returns the signing service name to be used
*/
private function getSigningName($host)
{
if (strpos( $host, 's3-object-lambda')) {
return 's3-object-lambda';
}
return $this->getConfig('signing_name');
}
public static function _default_disable_express_session_auth(array &$args) {
return ConfigurationResolver::resolve(
's3_disable_express_session_auth',
false,
'bool',
$args
);
}
public static function _default_s3_express_identity_provider(array $args)
{
if ($args['config']['disable_express_session_auth']) {
return false;
}
return new S3ExpressIdentityProvider($args['region']);
}
/**
* Modifies API definition to remove `Bucket` from request URIs.
* This is now handled by the endpoint ruleset.
*
* @return void
*
* @internal
*/
private function processEndpointV2Model()
{
$definition = $this->getApi()->getDefinition();
foreach($definition['operations'] as &$operation) {
if (isset($operation['http']['requestUri'])) {
$requestUri = $operation['http']['requestUri'];
if ($requestUri === "/{Bucket}") {
$requestUri = str_replace('/{Bucket}', '/', $requestUri);
} else {
$requestUri = str_replace('/{Bucket}', '', $requestUri);
}
$operation['http']['requestUri'] = $requestUri;
}
}
$this->getApi()->setDefinition($definition);
}
/**
* Adds service-specific client built-in values
*
* @return void
*/
private function addBuiltIns($args)
{
if (isset($args['region'])
&& $args['region'] !== 'us-east-1'
) {
return false;
}
if (!isset($args['region'])
&& ConfigurationResolver::resolve('region', '', 'string') !== 'us-east-1'
) {
return false;
}
$key = 'AWS::S3::UseGlobalEndpoint';
$result = $args['s3_us_east_1_regional_endpoint'] instanceof \Closure ?
$args['s3_us_east_1_regional_endpoint']()->wait() : $args['s3_us_east_1_regional_endpoint'];
if (is_string($result)) {
if ($result === 'regional') {
$value = false;
} else if ($result === 'legacy') {
$value = true;
} else {
return;
}
} else {
if ($result->isFallback()
|| $result->getEndpointsType() === 'legacy'
) {
$value = true;
} else {
$value = false;
}
}
$this->clientBuiltIns[$key] = $value;
}
/** @internal */
public static function _applyRetryConfig($value, $args, HandlerList $list)
{
if ($value) {
$config = \Aws\Retry\ConfigurationProvider::unwrap($value);
if ($config->getMode() === 'legacy') {
$maxRetries = $config->getMaxAttempts() - 1;
$decider = RetryMiddleware::createDefaultDecider($maxRetries);
$decider = function ($retries, $command, $request, $result, $error) use ($decider, $maxRetries) {
$maxRetries = null !== $command['@retries']
? $command['@retries']
: $maxRetries;
if ($decider($retries, $command, $request, $result, $error)) {
return true;
}
if ($error instanceof AwsException
&& $retries < $maxRetries
) {
if ($error->getResponse()
&& $error->getResponse()->getStatusCode() >= 400
) {
return strpos(
$error->getResponse()->getBody(),
'Your socket connection to the server'
) !== false;
}
if ($error->getPrevious() instanceof RequestException) {
// All commands except CompleteMultipartUpload are
// idempotent and may be retried without worry if a
// networking error has occurred.
return $command->getName() !== 'CompleteMultipartUpload';
}
}
return false;
};
$delay = [RetryMiddleware::class, 'exponentialDelay'];
$list->appendSign(Middleware::retry($decider, $delay), 'retry');
} else {
$defaultDecider = RetryMiddlewareV2::createDefaultDecider(
new QuotaManager(),
$config->getMaxAttempts()
);
$list->appendSign(
RetryMiddlewareV2::wrap(
$config,
[
'collect_stats' => $args['stats']['retries'],
'decider' => function(
$attempts,
CommandInterface $cmd,
$result
) use ($defaultDecider, $config) {
$isRetryable = $defaultDecider($attempts, $cmd, $result);
if (!$isRetryable
&& $result instanceof AwsException
&& $attempts < $config->getMaxAttempts()
) {
if (!empty($result->getResponse())
&& $result->getResponse()->getStatusCode() >= 400
) {
return strpos(
$result->getResponse()->getBody(),
'Your socket connection to the server'
) !== false;
}
if ($result->getPrevious() instanceof RequestException
&& $cmd->getName() !== 'CompleteMultipartUpload'
) {
$isRetryable = true;
}
}
return $isRetryable;
}
]
),
'retry'
);
}
}
}
/** @internal */
public static function _applyApiProvider($value, array &$args, HandlerList $list)
{
ClientResolver::_apply_api_provider($value, $args);
$args['parser'] = new GetBucketLocationParser(
new ValidateResponseChecksumParser(
new AmbiguousSuccessParser(
new RetryableMalformedResponseParser(
$args['parser'],
$args['exception_class']
),
$args['error_parser'],
$args['exception_class']
),
$args['api']
)
);
}
/**
* @internal
* @codeCoverageIgnore
*/
public static function applyDocFilters(array $api, array $docs)
{
$b64 = 'This value will be base64 encoded on your behalf.
';
$opt = 'This value will be computed for you it is not supplied.
';
// Add a note on the CopyObject docs
$s3ExceptionRetryMessage = "Additional info on response behavior: if there is"
. " an internal error in S3 after the request was successfully recieved,"
. " a 200 response will be returned with an S3Exception
embedded"
. " in it; this will still be caught and retried by"
. " RetryMiddleware.
";
$docs['operations']['CopyObject'] .= $s3ExceptionRetryMessage;
$docs['operations']['CompleteMultipartUpload'] .= $s3ExceptionRetryMessage;
$docs['operations']['UploadPartCopy'] .= $s3ExceptionRetryMessage;
$docs['operations']['UploadPart'] .= $s3ExceptionRetryMessage;
// Add note about stream ownership in the putObject call
$guzzleStreamMessage = "Additional info on behavior of the stream"
. " parameters: Psr7 takes ownership of streams and will automatically close"
. " streams when this method is called with a stream as the Body
"
. " parameter. To prevent this, set the Body
using"
. " GuzzleHttp\Psr7\stream_for
method with a is an instance of"
. " Psr\Http\Message\StreamInterface
, and it will be returned"
. " unmodified. This will allow you to keep the stream in scope.
";
$docs['operations']['PutObject'] .= $guzzleStreamMessage;
// Add the SourceFile parameter.
$docs['shapes']['SourceFile']['base'] = 'The path to a file on disk to use instead of the Body parameter.';
$api['shapes']['SourceFile'] = ['type' => 'string'];
$api['shapes']['PutObjectRequest']['members']['SourceFile'] = ['shape' => 'SourceFile'];
$api['shapes']['UploadPartRequest']['members']['SourceFile'] = ['shape' => 'SourceFile'];
// Add the ContentSHA256 parameter.
$docs['shapes']['ContentSHA256']['base'] = 'A SHA256 hash of the body content of the request.';
$api['shapes']['ContentSHA256'] = ['type' => 'string'];
$api['shapes']['PutObjectRequest']['members']['ContentSHA256'] = ['shape' => 'ContentSHA256'];
$api['shapes']['UploadPartRequest']['members']['ContentSHA256'] = ['shape' => 'ContentSHA256'];
$docs['shapes']['ContentSHA256']['append'] = $opt;
// Add the AddContentMD5 parameter.
$docs['shapes']['AddContentMD5']['base'] = 'Set to true to calculate the ContentMD5 for the upload.';
$api['shapes']['AddContentMD5'] = ['type' => 'boolean'];
$api['shapes']['PutObjectRequest']['members']['AddContentMD5'] = ['shape' => 'AddContentMD5'];
$api['shapes']['UploadPartRequest']['members']['AddContentMD5'] = ['shape' => 'AddContentMD5'];
// Add the SaveAs parameter.
$docs['shapes']['SaveAs']['base'] = 'The path to a file on disk to save the object data.';
$api['shapes']['SaveAs'] = ['type' => 'string'];
$api['shapes']['GetObjectRequest']['members']['SaveAs'] = ['shape' => 'SaveAs'];
// Several SSECustomerKey documentation updates.
$docs['shapes']['SSECustomerKey']['append'] = $b64;
$docs['shapes']['CopySourceSSECustomerKey']['append'] = $b64;
$docs['shapes']['SSECustomerKeyMd5']['append'] = $opt;
// Add the ObjectURL to various output shapes and documentation.
$docs['shapes']['ObjectURL']['base'] = 'The URI of the created object.';
$api['shapes']['ObjectURL'] = ['type' => 'string'];
$api['shapes']['PutObjectOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
$api['shapes']['CopyObjectOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
$api['shapes']['CompleteMultipartUploadOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
// Fix references to Location Constraint.
unset($api['shapes']['CreateBucketRequest']['payload']);
$api['shapes']['BucketLocationConstraint']['enum'] = [
"ap-northeast-1",
"ap-southeast-2",
"ap-southeast-1",
"cn-north-1",
"eu-central-1",
"eu-west-1",
"us-east-1",
"us-west-1",
"us-west-2",
"sa-east-1",
];
// Add a note that the ContentMD5 is automatically computed, except for with PutObject and UploadPart
$docs['shapes']['ContentMD5']['append'] = 'The value will be computed on '
. 'your behalf.
';
$docs['shapes']['ContentMD5']['excludeAppend'] = ['PutObjectRequest', 'UploadPartRequest'];
//Add a note to ContentMD5 for PutObject and UploadPart that specifies the value is required
// When uploading to a bucket with object lock enabled and that it is not computed automatically
$objectLock = 'This value is required if uploading to a bucket '
. 'which has Object Lock enabled. It will not be calculated for you automatically. If you wish to have '
. 'the value calculated for you, use the `AddContentMD5` parameter.
';
$docs['shapes']['ContentMD5']['appendOnly'] = [
'message' => $objectLock,
'shapes' => ['PutObjectRequest', 'UploadPartRequest']
];
return [
new Service($api, ApiProvider::defaultProvider()),
new DocModel($docs)
];
}
/**
* @internal
* @codeCoverageIgnore
*/
public static function addDocExamples($examples)
{
$getObjectExample = [
'input' => [
'Bucket' => 'arn:aws:s3:us-east-1:123456789012:accesspoint:myaccesspoint',
'Key' => 'my-key'
],
'output' => [
'Body' => 'class GuzzleHttp\Psr7\Stream#208 (7) {...}',
'ContentLength' => '11',
'ContentType' => 'application/octet-stream',
],
'comments' => [
'input' => '',
'output' => 'Simplified example output'
],
'description' => 'The following example retrieves an object by referencing the bucket via an S3 accesss point ARN. Result output is simplified for the example.',
'id' => '',
'title' => 'To get an object via an S3 access point ARN'
];
if (isset($examples['GetObject'])) {
$examples['GetObject'] []= $getObjectExample;
} else {
$examples['GetObject'] = [$getObjectExample];
}
$putObjectExample = [
'input' => [
'Bucket' => 'arn:aws:s3:us-east-1:123456789012:accesspoint:myaccesspoint',
'Key' => 'my-key',
'Body' => 'my-body',
],
'output' => [
'ObjectURL' => 'https://my-bucket.s3.us-east-1.amazonaws.com/my-key'
],
'comments' => [
'input' => '',
'output' => 'Simplified example output'
],
'description' => 'The following example uploads an object by referencing the bucket via an S3 accesss point ARN. Result output is simplified for the example.',
'id' => '',
'title' => 'To upload an object via an S3 access point ARN'
];
if (isset($examples['PutObject'])) {
$examples['PutObject'] []= $putObjectExample;
} else {
$examples['PutObject'] = [$putObjectExample];
}
return $examples;
}
/**
* @param CommandInterface $command
* @return array|mixed|null
*/
private function getSignatureVersionFromCommand(CommandInterface $command)
{
$signatureVersion = empty($command->getAuthSchemes())
? $this->getConfig('signature_version')
: $command->getAuthSchemes()['version'];
return $signatureVersion;
}
}