S3EndpointMiddleware.php 7.7 KB

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