| 1 |
<?php |
| 2 |
|
| 3 |
namespace Dudlewebs\WPMCS\s3\Aws\S3; |
| 4 |
|
| 5 |
use Dudlewebs\WPMCS\s3\Aws\Arn\ArnParser; |
| 6 |
use Dudlewebs\WPMCS\s3\Aws\Arn\ObjectLambdaAccessPointArn; |
| 7 |
use Dudlewebs\WPMCS\s3\Aws\ClientResolver; |
| 8 |
use Dudlewebs\WPMCS\s3\Aws\CommandInterface; |
| 9 |
use Dudlewebs\WPMCS\s3\Aws\Endpoint\EndpointProvider; |
| 10 |
use Dudlewebs\WPMCS\s3\Aws\Endpoint\PartitionEndpointProvider; |
| 11 |
use Dudlewebs\WPMCS\s3\GuzzleHttp\Exception\InvalidArgumentException; |
| 12 |
use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Uri; |
| 13 |
use Dudlewebs\WPMCS\s3\Psr\Http\Message\RequestInterface; |
| 14 |
/** |
| 15 |
* Used to update the URL used for S3 requests to support: |
| 16 |
* S3 Accelerate, S3 DualStack or Both. It will build to |
| 17 |
* host style paths unless specified, including for S3 |
| 18 |
* DualStack. |
| 19 |
* |
| 20 |
* IMPORTANT: this middleware must be added after the "build" step. |
| 21 |
* |
| 22 |
* @internal |
| 23 |
*/ |
| 24 |
class S3EndpointMiddleware |
| 25 |
{ |
| 26 |
private static $exclusions = ['CreateBucket' => \true, 'DeleteBucket' => \true, 'ListBuckets' => \true]; |
| 27 |
const NO_PATTERN = 0; |
| 28 |
const DUALSTACK = 1; |
| 29 |
const ACCELERATE = 2; |
| 30 |
const ACCELERATE_DUALSTACK = 3; |
| 31 |
const PATH_STYLE = 4; |
| 32 |
const HOST_STYLE = 5; |
| 33 |
/** @var bool */ |
| 34 |
private $accelerateByDefault; |
| 35 |
/** @var bool */ |
| 36 |
private $dualStackByDefault; |
| 37 |
/** @var bool */ |
| 38 |
private $pathStyleByDefault; |
| 39 |
/** @var string */ |
| 40 |
private $region; |
| 41 |
/** @var callable */ |
| 42 |
private $endpointProvider; |
| 43 |
/** @var callable */ |
| 44 |
private $nextHandler; |
| 45 |
/** @var string */ |
| 46 |
private $endpoint; |
| 47 |
/** |
| 48 |
* Create a middleware wrapper function |
| 49 |
* |
| 50 |
* @param string $region |
| 51 |
* @param EndpointProvider $endpointProvider |
| 52 |
* @param array $options |
| 53 |
* |
| 54 |
* @return callable |
| 55 |
*/ |
| 56 |
public static function wrap($region, $endpointProvider, array $options) |
| 57 |
{ |
| 58 |
return function (callable $handler) use($region, $endpointProvider, $options) { |
| 59 |
return new self($handler, $region, $options, $endpointProvider); |
| 60 |
}; |
| 61 |
} |
| 62 |
public function __construct(callable $nextHandler, $region, array $options, $endpointProvider = null) |
| 63 |
{ |
| 64 |
$this->pathStyleByDefault = isset($options['path_style']) ? (bool) $options['path_style'] : \false; |
| 65 |
$this->dualStackByDefault = isset($options['dual_stack']) ? (bool) $options['dual_stack'] : \false; |
| 66 |
$this->accelerateByDefault = isset($options['accelerate']) ? (bool) $options['accelerate'] : \false; |
| 67 |
$this->region = (string) $region; |
| 68 |
$this->endpoint = isset($options['endpoint']) ? $options['endpoint'] : ""; |
| 69 |
$this->endpointProvider = \is_null($endpointProvider) ? PartitionEndpointProvider::defaultProvider() : $endpointProvider; |
| 70 |
$this->nextHandler = $nextHandler; |
| 71 |
} |
| 72 |
public function __invoke(CommandInterface $command, RequestInterface $request) |
| 73 |
{ |
| 74 |
if (!empty($this->endpoint)) { |
| 75 |
$request = $this->applyEndpoint($command, $request); |
| 76 |
} else { |
| 77 |
switch ($this->endpointPatternDecider($command, $request)) { |
| 78 |
case self::HOST_STYLE: |
| 79 |
$request = $this->applyHostStyleEndpoint($command, $request); |
| 80 |
break; |
| 81 |
case self::NO_PATTERN: |
| 82 |
break; |
| 83 |
case self::PATH_STYLE: |
| 84 |
$request = $this->applyPathStyleEndpointCustomizations($command, $request); |
| 85 |
break; |
| 86 |
case self::DUALSTACK: |
| 87 |
$request = $this->applyDualStackEndpoint($command, $request); |
| 88 |
break; |
| 89 |
case self::ACCELERATE: |
| 90 |
$request = $this->applyAccelerateEndpoint($command, $request, 's3-accelerate'); |
| 91 |
break; |
| 92 |
case self::ACCELERATE_DUALSTACK: |
| 93 |
$request = $this->applyAccelerateEndpoint($command, $request, 's3-accelerate.dualstack'); |
| 94 |
break; |
| 95 |
} |
| 96 |
} |
| 97 |
$nextHandler = $this->nextHandler; |
| 98 |
return $nextHandler($command, $request); |
| 99 |
} |
| 100 |
private static function isRequestHostStyleCompatible(CommandInterface $command, RequestInterface $request) |
| 101 |
{ |
| 102 |
return S3Client::isBucketDnsCompatible($command['Bucket']) && ($request->getUri()->getScheme() === 'http' || \strpos($command['Bucket'], '.') === \false) && \filter_var($request->getUri()->getHost(), \FILTER_VALIDATE_IP) === \false; |
| 103 |
} |
| 104 |
private function endpointPatternDecider(CommandInterface $command, RequestInterface $request) |
| 105 |
{ |
| 106 |
$accelerate = isset($command['@use_accelerate_endpoint']) ? $command['@use_accelerate_endpoint'] : $this->accelerateByDefault; |
| 107 |
$dualStack = isset($command['@use_dual_stack_endpoint']) ? $command['@use_dual_stack_endpoint'] : $this->dualStackByDefault; |
| 108 |
$pathStyle = isset($command['@use_path_style_endpoint']) ? $command['@use_path_style_endpoint'] : $this->pathStyleByDefault; |
| 109 |
if ($accelerate && $dualStack) { |
| 110 |
// When try to enable both for operations excluded from s3-accelerate, |
| 111 |
// only dualstack endpoints will be enabled. |
| 112 |
return $this->canAccelerate($command) ? self::ACCELERATE_DUALSTACK : self::DUALSTACK; |
| 113 |
} |
| 114 |
if ($accelerate && $this->canAccelerate($command)) { |
| 115 |
return self::ACCELERATE; |
| 116 |
} |
| 117 |
if ($dualStack) { |
| 118 |
return self::DUALSTACK; |
| 119 |
} |
| 120 |
if (!$pathStyle && self::isRequestHostStyleCompatible($command, $request)) { |
| 121 |
return self::HOST_STYLE; |
| 122 |
} |
| 123 |
return self::PATH_STYLE; |
| 124 |
} |
| 125 |
private function canAccelerate(CommandInterface $command) |
| 126 |
{ |
| 127 |
return empty(self::$exclusions[$command->getName()]) && S3Client::isBucketDnsCompatible($command['Bucket']); |
| 128 |
} |
| 129 |
private function getBucketStyleHost(CommandInterface $command, $host) |
| 130 |
{ |
| 131 |
// For operations on the base host (e.g. ListBuckets) |
| 132 |
if (!isset($command['Bucket'])) { |
| 133 |
return $host; |
| 134 |
} |
| 135 |
return "{$command['Bucket']}.{$host}"; |
| 136 |
} |
| 137 |
private function applyHostStyleEndpoint(CommandInterface $command, RequestInterface $request) |
| 138 |
{ |
| 139 |
$uri = $request->getUri(); |
| 140 |
$request = $request->withUri($uri->withHost($this->getBucketStyleHost($command, $uri->getHost()))->withPath($this->getBucketlessPath($uri->getPath(), $command))); |
| 141 |
return $request; |
| 142 |
} |
| 143 |
private function applyPathStyleEndpointCustomizations(CommandInterface $command, RequestInterface $request) |
| 144 |
{ |
| 145 |
if ($command->getName() == 'WriteGetObjectResponse') { |
| 146 |
$dnsSuffix = $this->endpointProvider->getPartition($this->region, 's3')->getDnsSuffix(); |
| 147 |
$fips = \Dudlewebs\WPMCS\s3\Aws\is_fips_pseudo_region($this->region) ? "-fips" : ""; |
| 148 |
$region = \Dudlewebs\WPMCS\s3\Aws\strip_fips_pseudo_regions($this->region); |
| 149 |
$host = "{$command['RequestRoute']}.s3-object-lambda{$fips}.{$region}.{$dnsSuffix}"; |
| 150 |
$uri = $request->getUri(); |
| 151 |
$request = $request->withUri($uri->withHost($host)->withPath($this->getBucketlessPath($uri->getPath(), $command))); |
| 152 |
} |
| 153 |
return $request; |
| 154 |
} |
| 155 |
private function applyDualStackEndpoint(CommandInterface $command, RequestInterface $request) |
| 156 |
{ |
| 157 |
$request = $request->withUri($request->getUri()->withHost($this->getDualStackHost())); |
| 158 |
if (empty($command['@use_path_style_endpoint']) && !$this->pathStyleByDefault && self::isRequestHostStyleCompatible($command, $request)) { |
| 159 |
$request = $this->applyHostStyleEndpoint($command, $request); |
| 160 |
} |
| 161 |
return $request; |
| 162 |
} |
| 163 |
private function getDualStackHost() |
| 164 |
{ |
| 165 |
$dnsSuffix = $this->endpointProvider->getPartition($this->region, 's3')->getDnsSuffix(); |
| 166 |
return "s3.dualstack.{$this->region}.{$dnsSuffix}"; |
| 167 |
} |
| 168 |
private function applyAccelerateEndpoint(CommandInterface $command, RequestInterface $request, $pattern) |
| 169 |
{ |
| 170 |
$request = $request->withUri($request->getUri()->withHost($this->getAccelerateHost($command, $pattern))->withPath($this->getBucketlessPath($request->getUri()->getPath(), $command))); |
| 171 |
return $request; |
| 172 |
} |
| 173 |
private function getAccelerateHost(CommandInterface $command, $pattern) |
| 174 |
{ |
| 175 |
$dnsSuffix = $this->endpointProvider->getPartition($this->region, 's3')->getDnsSuffix(); |
| 176 |
return "{$command['Bucket']}.{$pattern}.{$dnsSuffix}"; |
| 177 |
} |
| 178 |
private function getBucketlessPath($path, CommandInterface $command) |
| 179 |
{ |
| 180 |
$pattern = '/^\\/' . \preg_quote($command['Bucket'], '/') . '/'; |
| 181 |
$path = \preg_replace($pattern, '', $path) ?: '/'; |
| 182 |
if (\substr($path, 0, 1) !== '/') { |
| 183 |
$path = '/' . $path; |
| 184 |
} |
| 185 |
return $path; |
| 186 |
} |
| 187 |
private function applyEndpoint(CommandInterface $command, RequestInterface $request) |
| 188 |
{ |
| 189 |
$dualStack = isset($command['@use_dual_stack_endpoint']) ? $command['@use_dual_stack_endpoint'] : $this->dualStackByDefault; |
| 190 |
if (ArnParser::isArn($command['Bucket'])) { |
| 191 |
$arn = ArnParser::parse($command['Bucket']); |
| 192 |
$outpost = $arn->getService() == 's3-outposts'; |
| 193 |
if ($outpost && $dualStack) { |
| 194 |
throw new InvalidArgumentException("Outposts + dualstack is not supported"); |
| 195 |
} |
| 196 |
if ($arn instanceof ObjectLambdaAccessPointArn) { |
| 197 |
return $request; |
| 198 |
} |
| 199 |
} |
| 200 |
if ($dualStack) { |
| 201 |
throw new InvalidArgumentException("Custom Endpoint + Dualstack not supported"); |
| 202 |
} |
| 203 |
if ($command->getName() == 'WriteGetObjectResponse') { |
| 204 |
$host = "{$command['RequestRoute']}.{$this->endpoint}"; |
| 205 |
$uri = $request->getUri(); |
| 206 |
return $request = $request->withUri($uri->withHost($host)->withPath($this->getBucketlessPath($uri->getPath(), $command))); |
| 207 |
} |
| 208 |
$host = $this->pathStyleByDefault ? $this->endpoint : $this->getBucketStyleHost($command, $this->endpoint); |
| 209 |
$uri = $request->getUri(); |
| 210 |
$scheme = $uri->getScheme(); |
| 211 |
if (empty($scheme)) { |
| 212 |
$request = $request->withUri($uri->withHost($host)); |
| 213 |
} else { |
| 214 |
$request = $request->withUri($uri); |
| 215 |
} |
| 216 |
return $request; |
| 217 |
} |
| 218 |
} |
| 219 |
|