Completed
Pull Request — master (#1)
by
unknown
09:06
created

S3EndpointMiddleware::canAccelerate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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