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