vendor/aws/aws-sdk-php/src/S3/S3Client.php line 473

Open in your IDE?
  1. <?php
  2. namespace Aws\S3;
  3. use Aws\Api\ApiProvider;
  4. use Aws\Api\DocModel;
  5. use Aws\Api\Service;
  6. use Aws\AwsClient;
  7. use Aws\ClientResolver;
  8. use Aws\Exception\AwsException;
  9. use Aws\HandlerList;
  10. use Aws\Middleware;
  11. use Aws\RetryMiddleware;
  12. use Aws\S3\Exception\S3Exception;
  13. use Aws\ResultInterface;
  14. use Aws\CommandInterface;
  15. use GuzzleHttp\Psr7;
  16. use Psr\Http\Message\RequestInterface;
  17. use Psr\Http\Message\StreamInterface;
  18. /**
  19.  * Client used to interact with **Amazon Simple Storage Service (Amazon S3)**.
  20.  */
  21. class S3Client extends AwsClient
  22. {
  23.     public static function getArguments()
  24.     {
  25.         $args parent::getArguments();
  26.         $args['retries']['fn'] = [__CLASS__'_applyRetryConfig'];
  27.         $args['api_provider']['fn'] = [__CLASS__'_applyApiProvider'];
  28.         return $args + [
  29.             'bucket_endpoint' => [
  30.                 'type'    => 'config',
  31.                 'valid'   => ['bool'],
  32.                 'doc'     => 'Set to true to send requests to a hardcoded '
  33.                     'bucket endpoint rather than create an endpoint as a '
  34.                     'result of injecting the bucket into the URL. This '
  35.                     'option is useful for interacting with CNAME endpoints.',
  36.             ],
  37.         ];
  38.     }
  39.     /**
  40.      * {@inheritdoc}
  41.      *
  42.      * In addition to the options available to
  43.      * {@see Aws\AwsClient::__construct}, S3Client accepts the following
  44.      * options:
  45.      *
  46.      * - bucket_endpoint: (bool) Set to true to send requests to a
  47.      *   hardcoded bucket endpoint rather than create an endpoint as a result
  48.      *   of injecting the bucket into the URL. This option is useful for
  49.      *   interacting with CNAME endpoints.
  50.      * - calculate_md5: (bool) Set to false to disable calculating an MD5
  51.      *   for all Amazon S3 signed uploads.
  52.      *
  53.      * @param array $args
  54.      */
  55.     public function __construct(array $args)
  56.     {
  57.         parent::__construct($args);
  58.         $stack $this->getHandlerList();
  59.         $stack->appendInit(SSECMiddleware::wrap($this->getEndpoint()->getScheme()), 's3.ssec');
  60.         $stack->appendBuild(ApplyMd5Middleware::wrap(), 's3.md5');
  61.         $stack->appendBuild(
  62.             Middleware::contentType(['PutObject''UploadPart']),
  63.             's3.content_type'
  64.         );
  65.         // Use the bucket style middleware when using a "bucket_endpoint" (for cnames)
  66.         if ($this->getConfig('bucket_endpoint')) {
  67.             $stack->appendBuild(BucketEndpointMiddleware::wrap(), 's3.bucket_endpoint');
  68.         }
  69.         $stack->appendSign(PutObjectUrlMiddleware::wrap(), 's3.put_object_url');
  70.         $stack->appendSign(PermanentRedirectMiddleware::wrap(), 's3.permanent_redirect');
  71.         $stack->appendInit(Middleware::sourceFile($this->getApi()), 's3.source_file');
  72.         $stack->appendInit($this->getLocationConstraintMiddleware(), 's3.location');
  73.     }
  74.     /**
  75.      * Determine if a string is a valid name for a DNS compatible Amazon S3
  76.      * bucket.
  77.      *
  78.      * DNS compatible bucket names can be used as a subdomain in a URL (e.g.,
  79.      * "<bucket>.s3.amazonaws.com").
  80.      *
  81.      * @param string $bucket Bucket name to check.
  82.      *
  83.      * @return bool
  84.      */
  85.     public static function isBucketDnsCompatible($bucket)
  86.     {
  87.         $bucketLen strlen($bucket);
  88.         return ($bucketLen >= && $bucketLen <= 63) &&
  89.             // Cannot look like an IP address
  90.             !filter_var($bucketFILTER_VALIDATE_IP) &&
  91.             preg_match('/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/'$bucket);
  92.     }
  93.     /**
  94.      * Create a pre-signed URL for the given S3 command object.
  95.      *
  96.      * @param CommandInterface $command     Command to create a pre-signed
  97.      *                                      URL for.
  98.      * @param int|string|\DateTime $expires The time at which the URL should
  99.      *                                      expire. This can be a Unix
  100.      *                                      timestamp, a PHP DateTime object,
  101.      *                                      or a string that can be evaluated
  102.      *                                      by strtotime().
  103.      *
  104.      * @return RequestInterface
  105.      */
  106.     public function createPresignedRequest(CommandInterface $command$expires)
  107.     {
  108.         /** @var \Aws\Signature\SignatureInterface $signer */
  109.         $signer call_user_func(
  110.             $this->getSignatureProvider(),
  111.             $this->getConfig('signature_version'),
  112.             $this->getApi()->getSigningName(),
  113.             $this->getRegion()
  114.         );
  115.         return $signer->presign(
  116.             \Aws\serialize($command),
  117.             $this->getCredentials()->wait(),
  118.             $expires
  119.         );
  120.     }
  121.     /**
  122.      * Returns the URL to an object identified by its bucket and key.
  123.      *
  124.      * The URL returned by this method is not signed nor does it ensure the the
  125.      * bucket and key given to the method exist. If you need a signed URL, then
  126.      * use the {@see \Aws\S3\S3Client::createPresignedRequest} method and get
  127.      * the URI of the signed request.
  128.      *
  129.      * @param string $bucket  The name of the bucket where the object is located
  130.      * @param string $key     The key of the object
  131.      *
  132.      * @return string The URL to the object
  133.      */
  134.     public function getObjectUrl($bucket$key)
  135.     {
  136.         $command $this->getCommand('GetObject', [
  137.             'Bucket' => $bucket,
  138.             'Key'    => $key
  139.         ]);
  140.         return (string) \Aws\serialize($command)->getUri();
  141.     }
  142.     /**
  143.      * Determines whether or not a bucket exists by name.
  144.      *
  145.      * @param string $bucket  The name of the bucket
  146.      *
  147.      * @return bool
  148.      */
  149.     public function doesBucketExist($bucket)
  150.     {
  151.         return $this->checkExistenceWithCommand(
  152.             $this->getCommand('HeadBucket', ['Bucket' => $bucket])
  153.         );
  154.     }
  155.     /**
  156.      * Determines whether or not an object exists by name.
  157.      *
  158.      * @param string $bucket  The name of the bucket
  159.      * @param string $key     The key of the object
  160.      * @param array  $options Additional options available in the HeadObject
  161.      *                        operation (e.g., VersionId).
  162.      *
  163.      * @return bool
  164.      */
  165.     public function doesObjectExist($bucket$key, array $options = [])
  166.     {
  167.         return $this->checkExistenceWithCommand(
  168.             $this->getCommand('HeadObject', [
  169.                 'Bucket' => $bucket,
  170.                 'Key'    => $key
  171.             ] + $options)
  172.         );
  173.     }
  174.     /**
  175.      * Raw URL encode a key and allow for '/' characters
  176.      *
  177.      * @param string $key Key to encode
  178.      *
  179.      * @return string Returns the encoded key
  180.      */
  181.     public static function encodeKey($key)
  182.     {
  183.         return str_replace('%2F''/'rawurlencode($key));
  184.     }
  185.     /**
  186.      * Register the Amazon S3 stream wrapper with this client instance.
  187.      */
  188.     public function registerStreamWrapper()
  189.     {
  190.         StreamWrapper::register($this);
  191.     }
  192.     /**
  193.      * Deletes objects from Amazon S3 that match the result of a ListObjects
  194.      * operation. For example, this allows you to do things like delete all
  195.      * objects that match a specific key prefix.
  196.      *
  197.      * @param string $bucket  Bucket that contains the object keys
  198.      * @param string $prefix  Optionally delete only objects under this key prefix
  199.      * @param string $regex   Delete only objects that match this regex
  200.      * @param array  $options Aws\S3\BatchDelete options array.
  201.      *
  202.      * @see Aws\S3\S3Client::listObjects
  203.      * @throws \RuntimeException if no prefix and no regex is given
  204.      */
  205.     public function deleteMatchingObjects(
  206.         $bucket,
  207.         $prefix '',
  208.         $regex '',
  209.         array $options = []
  210.     ) {
  211.         if (!$prefix && !$regex) {
  212.             throw new \RuntimeException('A prefix or regex is required.');
  213.         }
  214.         $params = ['Bucket' => $bucket'Prefix' => $prefix];
  215.         $iter $this->getIterator('ListObjects'$params);
  216.         if ($regex) {
  217.             $iter \Aws\filter($iter, function ($c) use ($regex) {
  218.                 return preg_match($regex$c['Key']);
  219.             });
  220.         }
  221.         BatchDelete::fromIterator($this$bucket$iter$options)->delete();
  222.     }
  223.     /**
  224.      * Upload a file, stream, or string to a bucket.
  225.      *
  226.      * If the upload size exceeds the specified threshold, the upload will be
  227.      * performed using concurrent multipart uploads.
  228.      *
  229.      * The options array accepts the following options:
  230.      *
  231.      * - before_upload: (callable) Callback to invoke before any upload
  232.      *   operations during the upload process. The callback should have a
  233.      *   function signature like `function (Aws\Command $command) {...}`.
  234.      * - concurrency: (int, default=int(3)) Maximum number of concurrent
  235.      *   `UploadPart` operations allowed during a multipart upload.
  236.      * - mup_threshold: (int, default=int(16777216)) The size, in bytes, allowed
  237.      *   before the upload must be sent via a multipart upload. Default: 16 MB.
  238.      * - params: (array, default=array([])) Custom parameters to use with the
  239.      *   upload. For single uploads, they must correspond to those used for the
  240.      *   `PutObject` operation. For multipart uploads, they correspond to the
  241.      *   parameters of the `CreateMultipartUpload` operation.
  242.      * - part_size: (int) Part size to use when doing a multipart upload.
  243.      *
  244.      * @param string $bucket  Bucket to upload the object.
  245.      * @param string $key     Key of the object.
  246.      * @param mixed  $body    Object data to upload. Can be a
  247.      *                        StreamInterface, PHP stream resource, or a
  248.      *                        string of data to upload.
  249.      * @param string $acl     ACL to apply to the object (default: private).
  250.      * @param array  $options Options used to configure the upload process.
  251.      *
  252.      * @see Aws\S3\MultipartUploader for more info about multipart uploads.
  253.      * @return ResultInterface Returns the result of the upload.
  254.      */
  255.     public function upload($bucket$key$body$acl 'private', array $options = [])
  256.     {
  257.         // Prepare the options.
  258.         static $defaults = [
  259.             'before_upload' => null,
  260.             'concurrency'   => 3,
  261.             'mup_threshold' => 16777216,
  262.             'params'        => [],
  263.             'part_size'     => null,
  264.         ];
  265.         $options array_intersect_key($options $defaults$defaults);
  266.         // Perform the required operations to upload the S3 Object.
  267.         $body Psr7\stream_for($body);
  268.         if ($this->requiresMultipart($body$options['mup_threshold'])) {
  269.             // Perform a multipart upload.
  270.             $options['before_initiate'] = function ($command) use ($options) {
  271.                 foreach ($options['params'] as $k => $v) {
  272.                     $command[$k] = $v;
  273.                 }
  274.             };
  275.             return (new MultipartUploader($this$body, [
  276.                 'bucket' => $bucket,
  277.                 'key'    => $key,
  278.                 'acl'    => $acl
  279.             ] + $options))->upload();
  280.         } else {
  281.             // Perform a regular PutObject operation.
  282.             $command $this->getCommand('PutObject', [
  283.                 'Bucket' => $bucket,
  284.                 'Key'    => $key,
  285.                 'Body'   => $body,
  286.                 'ACL'    => $acl,
  287.             ] + $options['params']);
  288.             if (is_callable($options['before_upload'])) {
  289.                 $options['before_upload']($command);
  290.             }
  291.             return $this->execute($command);
  292.         }
  293.     }
  294.     /**
  295.      * Recursively uploads all files in a given directory to a given bucket.
  296.      *
  297.      * @param string $directory Full path to a directory to upload
  298.      * @param string $bucket    Name of the bucket
  299.      * @param string $keyPrefix Virtual directory key prefix to add to each upload
  300.      * @param array  $options   Options available in Aws\S3\Transfer::__construct
  301.      *
  302.      * @see Aws\S3\Transfer for more options and customization
  303.      */
  304.     public function uploadDirectory(
  305.         $directory,
  306.         $bucket,
  307.         $keyPrefix null,
  308.         array $options = []
  309.     ) {
  310.         $d "s3://$bucket. ($keyPrefix '/' ltrim($keyPrefix'/') : '');
  311.         (new Transfer($this$directory$d$options))->transfer();
  312.     }
  313.     /**
  314.      * Downloads a bucket to the local filesystem
  315.      *
  316.      * @param string $directory Directory to download to
  317.      * @param string $bucket    Bucket to download from
  318.      * @param string $keyPrefix Only download objects that use this key prefix
  319.      * @param array  $options   Options available in Aws\S3\Transfer::__construct
  320.      */
  321.     public function downloadBucket(
  322.         $directory,
  323.         $bucket,
  324.         $keyPrefix '',
  325.         array $options = []
  326.     ) {
  327.         $s "s3://$bucket. ($keyPrefix '/' ltrim($keyPrefix'/') : '');
  328.         (new Transfer($this$s$directory$options))->transfer();
  329.     }
  330.     /**
  331.      * Determines if the body should be uploaded using PutObject or the
  332.      * Multipart Upload System. It also modifies the passed-in $body as needed
  333.      * to support the upload.
  334.      *
  335.      * @param StreamInterface $body      Stream representing the body.
  336.      * @param integer             $threshold Minimum bytes before using Multipart.
  337.      *
  338.      * @return bool
  339.      */
  340.     private function requiresMultipart(StreamInterface &$body$threshold)
  341.     {
  342.         // If body size known, compare to threshold to determine if Multipart.
  343.         if ($body->getSize() !== null) {
  344.             return $body->getSize() >= $threshold;
  345.         }
  346.         // Handle the situation where the body size is unknown.
  347.         // Read up to 5MB into a buffer to determine how to upload the body.
  348.         $buffer Psr7\stream_for();
  349.         Psr7\copy_to_stream($body$bufferMultipartUploader::PART_MIN_SIZE);
  350.         // If body < 5MB, use PutObject with the buffer.
  351.         if ($buffer->getSize() < MultipartUploader::PART_MIN_SIZE) {
  352.             $buffer->seek(0);
  353.             $body $buffer;
  354.             return false;
  355.         }
  356.         // If body >= 5 MB, then use multipart. [YES]
  357.         if ($body->isSeekable()) {
  358.             // If the body is seekable, just rewind the body.
  359.             $body->seek(0);
  360.         } else {
  361.             // If the body is non-seekable, stitch the rewind the buffer and
  362.             // the partially read body together into one stream. This avoids
  363.             // unnecessary disc usage and does not require seeking on the
  364.             // original stream.
  365.             $buffer->seek(0);
  366.             $body = new Psr7\AppendStream([$buffer$body]);
  367.         }
  368.         return true;
  369.     }
  370.     /**
  371.      * Determines whether or not a resource exists using a command
  372.      *
  373.      * @param CommandInterface $command Command used to poll for the resource
  374.      *
  375.      * @return bool
  376.      * @throws S3Exception|\Exception if there is an unhandled exception
  377.      */
  378.     private function checkExistenceWithCommand(CommandInterface $command)
  379.     {
  380.         try {
  381.             $this->execute($command);
  382.             return true;
  383.         } catch (S3Exception $e) {
  384.             if ($e->getAwsErrorCode() == 'AccessDenied') {
  385.                 return true;
  386.             }
  387.             if ($e->getStatusCode() >= 500) {
  388.                 throw $e;
  389.             }
  390.             return false;
  391.         }
  392.     }
  393.     /**
  394.      * Provides a middleware that removes the need to specify LocationConstraint on CreateBucket.
  395.      *
  396.      * @return \Closure
  397.      */
  398.     private function getLocationConstraintMiddleware()
  399.     {
  400.         return function (callable $handler) {
  401.             return function ($command$request null) use ($handler) {
  402.                 if ($command->getName() === 'CreateBucket') {
  403.                     $region $this->getRegion();
  404.                     if ($region === 'us-east-1') {
  405.                         unset($command['CreateBucketConfiguration']);
  406.                     } else {
  407.                         $command['CreateBucketConfiguration'] = ['LocationConstraint' => $region];
  408.                     }
  409.                 }
  410.                 return $handler($command$request);
  411.             };
  412.         };
  413.     }
  414.     /** @internal */
  415.     public static function _applyRetryConfig($value$_HandlerList $list)
  416.     {
  417.         if (!$value) {
  418.             return;
  419.         }
  420.         $decider RetryMiddleware::createDefaultDecider($value);
  421.         $decider = function ($retries$request$result$error) use ($decider) {
  422.             if ($decider($retries$request$result$error)) {
  423.                 return true;
  424.             } elseif ($error instanceof AwsException) {
  425.                 return $error->getResponse()
  426.                     && strpos(
  427.                         $error->getResponse()->getBody(),
  428.                         'Your socket connection to the server'
  429.                     ) !== false;
  430.             }
  431.             return false;
  432.         };
  433.         $delay = [RetryMiddleware::class, 'exponentialDelay'];
  434.         $list->appendSign(Middleware::retry($decider$delay), 'retry');
  435.     }
  436.     /** @internal */
  437.     public static function _applyApiProvider($value, array &$argsHandlerList $list)
  438.     {
  439.         ClientResolver::_apply_api_provider($value$args$list);
  440.         $args['parser'] = new GetBucketLocationParser($args['parser']);
  441.     }
  442.     /**
  443.      * @internal
  444.      * @codeCoverageIgnore
  445.      */
  446.     public static function applyDocFilters(array $api, array $docs)
  447.     {
  448.         $b64 '<div class="alert alert-info">This value will be base64 '
  449.             'encoded on your behalf.</div>';
  450.         // Add the SourceFile parameter.
  451.         $docs['shapes']['SourceFile']['base'] = 'The path to a file on disk to use instead of the Body parameter.';
  452.         $api['shapes']['SourceFile'] = ['type' => 'string'];
  453.         $api['shapes']['PutObjectRequest']['members']['SourceFile'] = ['shape' => 'SourceFile'];
  454.         $api['shapes']['UploadPartRequest']['members']['SourceFile'] = ['shape' => 'SourceFile'];
  455.         // Several SSECustomerKey documentation updates.
  456.         $docs['shapes']['SSECustomerKey']['append'] = $b64;
  457.         $docs['shapes']['CopySourceSSECustomerKey']['append'] = $b64;
  458.         $docs['shapes']['SSECustomerKeyMd5']['append'] = '<div class="alert alert-info">The value will be computed on '
  459.             'your behalf if it is not supplied.</div>';
  460.         // Add the ObjectURL to various output shapes and documentation.
  461.         $objectUrl 'The URI of the created object.';
  462.         $api['shapes']['ObjectURL'] = ['type' => 'string'];
  463.         $api['shapes']['PutObjectOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
  464.         $api['shapes']['CopyObjectOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
  465.         $api['shapes']['CompleteMultipartUploadOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
  466.         $docs['shapes']['ObjectURL']['base'] = $objectUrl;
  467.         // Fix references to Location Constraint.
  468.         unset($api['shapes']['CreateBucketRequest']['payload']);
  469.         $api['shapes']['BucketLocationConstraint']['enum'] = [
  470.             "ap-northeast-1",
  471.             "ap-southeast-2",
  472.             "ap-southeast-1",
  473.             "cn-north-1",
  474.             "eu-central-1",
  475.             "eu-west-1",
  476.             "us-east-1",
  477.             "us-west-1",
  478.             "us-west-2",
  479.             "sa-east-1",
  480.         ];
  481.         // Add a note that the ContentMD5 is optional.
  482.         $docs['shapes']['ContentMD5']['append'] = '<div class="alert alert-info">The value will be computed on '
  483.             'your behalf.</div>';
  484.         return [
  485.             new Service($apiApiProvider::defaultProvider()),
  486.             new DocModel($docs)
  487.         ];
  488.     }
  489. }