Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
 
3
namespace Aws\S3\Parser;
4
 
5
use Aws\Api\Service;
6
use Aws\CommandInterface;
7
use Aws\ResultInterface;
8
use Aws\S3\CalculatesChecksumTrait;
9
use Aws\S3\Exception\S3Exception;
10
use Psr\Http\Message\ResponseInterface;
11
 
12
/**
13
 * A custom s3 result mutator that validates the response checksums.
14
 *
15
 * @internal
16
 */
17
final class ValidateResponseChecksumResultMutator implements S3ResultMutator
18
{
19
    use CalculatesChecksumTrait;
20
 
21
    public const DEFAULT_VALIDATION_MODE = 'when_supported';
22
 
23
    /** @var Service $api */
24
    private $api;
25
 
26
    /** @var array $api */
27
    private $config;
28
 
29
    /**
30
     * @param Service $api
31
     * @param array $config
32
     */
33
    public function __construct(Service $api, array $config = [])
34
    {
35
        $this->api = $api;
36
        $this->config = $config;
37
    }
38
 
39
    /**
40
     * @param ResultInterface $result
41
     * @param CommandInterface|null $command
42
     * @param ResponseInterface|null $response
43
     *
44
     * @return ResultInterface
45
     */
46
    public function __invoke(
47
        ResultInterface $result,
48
        ?CommandInterface $command = null,
49
        ?ResponseInterface $response = null
50
    ): ResultInterface
51
    {
52
        $operation = $this->api->getOperation($command->getName());
53
 
54
        // Skip this middleware if the operation doesn't have an httpChecksum
55
        $checksumInfo = $operation['httpChecksum'] ?? null;
56
        if (is_null($checksumInfo)) {
57
            return $result;
58
        }
59
 
60
        $mode = $this->config['response_checksum_validation'] ?? self::DEFAULT_VALIDATION_MODE;
61
        $checksumModeEnabledMember = $checksumInfo['requestValidationModeMember'] ?? "";
62
        $checksumModeEnabled = strtolower($command[$checksumModeEnabledMember] ?? "");
63
        $responseAlgorithms = $checksumInfo['responseAlgorithms'] ?? [];
64
        $shouldSkipValidation = $this->shouldSkipValidation(
65
            $mode,
66
            $checksumModeEnabled,
67
            $responseAlgorithms
68
        );
69
 
70
        if ($shouldSkipValidation) {
71
            return $result;
72
        }
73
 
74
        $checksumPriority = $this->getChecksumPriority();
75
        $checksumsToCheck = array_intersect($responseAlgorithms, array_map(
76
            'strtoupper',
77
            array_keys($checksumPriority))
78
        );
79
        $checksumValidationInfo = $this->validateChecksum($checksumsToCheck, $response);
80
 
81
        if ($checksumValidationInfo['status'] === "SUCCEEDED") {
82
            $result['ChecksumValidated'] = $checksumValidationInfo['checksum'];
83
        } elseif ($checksumValidationInfo['status'] === "FAILED") {
84
            if ($this->isMultipartGetObject($command, $checksumValidationInfo)) {
85
                return $result;
86
            }
87
            throw new S3Exception(
88
                "Calculated response checksum did not match the expected value",
89
                $command
90
            );
91
        }
92
 
93
        return $result;
94
    }
95
 
96
    /**
97
     * @param $checksumPriority
98
     * @param ResponseInterface $response
99
     *
100
     * @return array
101
     */
102
    private function validateChecksum(
103
        $checksumPriority,
104
        ResponseInterface $response
105
    ): array
106
    {
107
        $checksumToValidate = $this->chooseChecksumHeaderToValidate(
108
            $checksumPriority,
109
            $response
110
        );
111
        $validationStatus = "SKIPPED";
112
        $checksumHeaderValue = null;
113
        if (!empty($checksumToValidate)) {
114
            $checksumHeaderValue = $response->getHeaderLine(
115
                'x-amz-checksum-' . $checksumToValidate
116
            );
117
            if (!empty($checksumHeaderValue)) {
118
                $calculatedChecksumValue = $this->getEncodedValue(
119
                    $checksumToValidate,
120
                    $response->getBody()
121
                );
122
                $validationStatus = $checksumHeaderValue == $calculatedChecksumValue
123
                    ? "SUCCEEDED"
124
                    : "FAILED";
125
            }
126
        }
127
        return [
128
            "status" => $validationStatus,
129
            "checksum" => $checksumToValidate,
130
            "checksumHeaderValue" => $checksumHeaderValue,
131
        ];
132
    }
133
 
134
    /**
135
     * @param $checksumPriority
136
     * @param ResponseInterface $response
137
     *
138
     * @return string
139
     */
140
    private function chooseChecksumHeaderToValidate(
141
        $checksumPriority,
142
        ResponseInterface $response
143
    ):? string
144
    {
145
        foreach ($checksumPriority as $checksum) {
146
            $checksumHeader = 'x-amz-checksum-' . $checksum;
147
            if ($response->hasHeader($checksumHeader)) {
148
                return $checksum;
149
            }
150
        }
151
 
152
        return null;
153
    }
154
 
155
    /**
156
     * @param string $mode
157
     * @param string $checksumModeEnabled
158
     * @param array $responseAlgorithms
159
     *
160
     * @return bool
161
     */
162
    private function shouldSkipValidation(
163
        string $mode,
164
        string $checksumModeEnabled,
165
        array $responseAlgorithms
166
    ): bool
167
    {
168
        return empty($responseAlgorithms)
169
            || ($mode === 'when_required' && $checksumModeEnabled !== 'enabled');
170
    }
171
 
172
    /**
173
     * @return string[]
174
     */
175
    private function getChecksumPriority(): array
176
    {
177
        return extension_loaded('awscrt')
178
            ? self::$supportedAlgorithms
179
            : array_slice(self::$supportedAlgorithms, 1);
180
    }
181
 
182
    /**
183
     * @param CommandInterface $command
184
     * @param array $checksumValidationInfo
185
     *
186
     * @return bool
187
     */
188
    private function isMultipartGetObject(
189
        CommandInterface $command,
190
        array $checksumValidationInfo
191
    ): bool
192
    {
193
        if ($command->getName() !== "GetObject"
194
            || empty($checksumValidationInfo['checksumHeaderValue'])
195
        ) {
196
            return false;
197
        }
198
 
199
        $headerValue = $checksumValidationInfo['checksumHeaderValue'];
200
        $lastDashPos = strrpos($headerValue, '-');
201
        $endOfChecksum = substr($headerValue, $lastDashPos + 1);
202
 
203
        return is_numeric($endOfChecksum)
204
            && (int) $endOfChecksum > 1
205
            && (int) $endOfChecksum < 10000;
206
    }
207
}