S3EndpointMiddleware.php 7.0 KB

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