| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 | <?phpnamespace Aws\S3;use Aws\Arn\ArnParser;use Aws\Arn\ObjectLambdaAccessPointArn;use Aws\ClientResolver;use Aws\CommandInterface;use Aws\Endpoint\EndpointProvider;use Aws\Endpoint\PartitionEndpointProvider;use GuzzleHttp\Exception\InvalidArgumentException;use GuzzleHttp\Psr7\Uri;use Psr\Http\Message\RequestInterface;/** * Used to update the URL used for S3 requests to support: * S3 Accelerate, S3 DualStack or Both. It will build to * host style paths unless specified, including for S3 * DualStack. * * IMPORTANT: this middleware must be added after the "build" step. * * @internal */class S3EndpointMiddleware{    private static $exclusions = [        'CreateBucket' => true,        'DeleteBucket' => true,        'ListBuckets' => true,    ];    const NO_PATTERN = 0;    const DUALSTACK = 1;    const ACCELERATE = 2;    const ACCELERATE_DUALSTACK = 3;    const PATH_STYLE = 4;    const HOST_STYLE = 5;    /** @var bool */    private $accelerateByDefault;    /** @var bool */    private $dualStackByDefault;    /** @var bool */    private $pathStyleByDefault;    /** @var string */    private $region;    /** @var callable */    private $endpointProvider;    /** @var callable */    private $nextHandler;    /** @var string */    private $endpoint;    /**     * Create a middleware wrapper function     *     * @param string $region     * @param EndpointProvider $endpointProvider     * @param array  $options     *     * @return callable     */    public static function wrap($region, $endpointProvider, array $options)    {        return function (callable $handler) use ($region, $endpointProvider, $options) {            return new self($handler, $region, $options, $endpointProvider);        };    }    public function __construct(        callable $nextHandler,        $region,        array $options,        $endpointProvider = null    ) {        $this->pathStyleByDefault = isset($options['path_style'])            ? (bool) $options['path_style'] : false;        $this->dualStackByDefault = isset($options['dual_stack'])            ? (bool) $options['dual_stack'] : false;        $this->accelerateByDefault = isset($options['accelerate'])            ? (bool) $options['accelerate'] : false;        $this->region = (string) $region;        $this->endpoint = isset($options['endpoint'])            ? $options['endpoint'] : "";        $this->endpointProvider = is_null($endpointProvider)            ? PartitionEndpointProvider::defaultProvider()            : $endpointProvider;        $this->nextHandler = $nextHandler;    }    public function __invoke(CommandInterface $command, RequestInterface $request)    {        if (!empty($this->endpoint)) {            $request = $this->applyEndpoint($command, $request);        } else {            switch ($this->endpointPatternDecider($command, $request)) {                case self::HOST_STYLE:                    $request = $this->applyHostStyleEndpoint($command, $request);                    break;                case self::NO_PATTERN:                    break;                case self::PATH_STYLE:                    $request = $this->applyPathStyleEndpointCustomizations($command, $request);                    break;                case self::DUALSTACK:                    $request = $this->applyDualStackEndpoint($command, $request);                    break;                case self::ACCELERATE:                    $request = $this->applyAccelerateEndpoint(                        $command,                        $request,                        's3-accelerate'                    );                    break;                case self::ACCELERATE_DUALSTACK:                    $request = $this->applyAccelerateEndpoint(                        $command,                        $request,                        's3-accelerate.dualstack'                    );                    break;            }        }        $nextHandler = $this->nextHandler;        return $nextHandler($command, $request);    }    private static function isRequestHostStyleCompatible(        CommandInterface $command,        RequestInterface $request    ) {        return S3Client::isBucketDnsCompatible($command['Bucket'])            && (                $request->getUri()->getScheme() === 'http'                || strpos($command['Bucket'], '.') === false            )            && filter_var($request->getUri()->getHost(), FILTER_VALIDATE_IP) === false;    }    private function endpointPatternDecider(        CommandInterface $command,        RequestInterface $request    ) {        $accelerate = isset($command['@use_accelerate_endpoint'])            ? $command['@use_accelerate_endpoint'] : $this->accelerateByDefault;        $dualStack = isset($command['@use_dual_stack_endpoint'])            ? $command['@use_dual_stack_endpoint'] : $this->dualStackByDefault;        $pathStyle = isset($command['@use_path_style_endpoint'])            ? $command['@use_path_style_endpoint'] : $this->pathStyleByDefault;        if ($accelerate && $dualStack) {            // When try to enable both for operations excluded from s3-accelerate,            // only dualstack endpoints will be enabled.            return $this->canAccelerate($command)                ? self::ACCELERATE_DUALSTACK                : self::DUALSTACK;        }        if ($accelerate && $this->canAccelerate($command)) {            return self::ACCELERATE;        }        if ($dualStack) {            return self::DUALSTACK;        }        if (!$pathStyle            && self::isRequestHostStyleCompatible($command, $request)        ) {            return self::HOST_STYLE;        }        return self::PATH_STYLE;    }    private function canAccelerate(CommandInterface $command)    {        return empty(self::$exclusions[$command->getName()])            && S3Client::isBucketDnsCompatible($command['Bucket']);    }    private function getBucketStyleHost(CommandInterface $command, $host)    {        // For operations on the base host (e.g. ListBuckets)        if (!isset($command['Bucket'])) {            return $host;        }        return "{$command['Bucket']}.{$host}";    }    private function applyHostStyleEndpoint(        CommandInterface $command,        RequestInterface $request    ) {        $uri = $request->getUri();        $request = $request->withUri(            $uri->withHost($this->getBucketStyleHost(                    $command,                    $uri->getHost()                ))                ->withPath($this->getBucketlessPath(                    $uri->getPath(),                    $command                ))        );        return $request;    }    private function applyPathStyleEndpointCustomizations(        CommandInterface $command,        RequestInterface $request    ) {        if ($command->getName() == 'WriteGetObjectResponse') {            $dnsSuffix = $this->endpointProvider                ->getPartition($this->region, 's3')                ->getDnsSuffix();            $fips = \Aws\is_fips_pseudo_region($this->region) ? "-fips" : "";            $region = \Aws\strip_fips_pseudo_regions($this->region);            $host =                "{$command['RequestRoute']}.s3-object-lambda{$fips}.{$region}.{$dnsSuffix}";            $uri = $request->getUri();            $request = $request->withUri(                $uri->withHost($host)                    ->withPath($this->getBucketlessPath(                        $uri->getPath(),                        $command                    ))            );        }        return $request;    }    private function applyDualStackEndpoint(        CommandInterface $command,        RequestInterface $request    ) {        $request = $request->withUri(            $request->getUri()->withHost($this->getDualStackHost())        );        if (empty($command['@use_path_style_endpoint'])            && !$this->pathStyleByDefault            && self::isRequestHostStyleCompatible($command, $request)        ) {            $request = $this->applyHostStyleEndpoint($command, $request);        }        return $request;    }    private function getDualStackHost()    {        $dnsSuffix = $this->endpointProvider            ->getPartition($this->region, 's3')            ->getDnsSuffix();        return "s3.dualstack.{$this->region}.{$dnsSuffix}";    }    private function applyAccelerateEndpoint(        CommandInterface $command,        RequestInterface $request,        $pattern    ) {        $request = $request->withUri(            $request->getUri()                ->withHost($this->getAccelerateHost($command, $pattern))                ->withPath($this->getBucketlessPath(                    $request->getUri()->getPath(),                    $command                ))        );        return $request;    }    private function getAccelerateHost(CommandInterface $command, $pattern)    {        $dnsSuffix = $this->endpointProvider            ->getPartition($this->region, 's3')            ->getDnsSuffix();        return "{$command['Bucket']}.{$pattern}.{$dnsSuffix}";    }    private function getBucketlessPath($path, CommandInterface $command)    {        $pattern = '/^\\/' . preg_quote($command['Bucket'], '/') . '/';        $path = preg_replace($pattern, '', $path) ?: '/';        if (substr($path, 0 , 1) !== '/') {            $path = '/' . $path;        }        return $path;    }    private function applyEndpoint(        CommandInterface $command,        RequestInterface $request    ) {        $dualStack = isset($command['@use_dual_stack_endpoint'])            ? $command['@use_dual_stack_endpoint'] : $this->dualStackByDefault;        if (ArnParser::isArn($command['Bucket'])) {            $arn = ArnParser::parse($command['Bucket']);            $outpost = $arn->getService() == 's3-outposts';            if ($outpost && $dualStack) {                throw new InvalidArgumentException("Outposts + dualstack is not supported");            }            if ($arn instanceof ObjectLambdaAccessPointArn) {                return $request;            }        }        if ($dualStack) {            throw new InvalidArgumentException("Custom Endpoint + Dualstack not supported");        }        if ($command->getName() == 'WriteGetObjectResponse') {            $host = "{$command['RequestRoute']}.{$this->endpoint}";            $uri = $request->getUri();            return $request = $request->withUri(                $uri->withHost($host)                    ->withPath($this->getBucketlessPath(                        $uri->getPath(),                        $command                    ))            );        }        $host = ($this->pathStyleByDefault) ?            $this->endpoint :            $this->getBucketStyleHost(                $command,                $this->endpoint            );        $uri = $request->getUri();        $scheme = $uri->getScheme();        if(empty($scheme)){            $request = $request->withUri(                $uri->withHost($host)            );        } else {            $request = $request->withUri($uri);        }        return $request;    }}
 |