PluginProbe
Media Cloud Sync / 1.4.2
Media Cloud Sync v1.4.2
1.4.2 1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 All 36 releases
← All changes | includes/sdk/s3/Aws/S3/ApplyChecksumMiddleware.php +115 -33 1.2.6 → 1.4.2 View file →
@@ -2,12 +2,15 @@
2 2
3 3 namespace Dudlewebs\WPMCS\s3\Aws\S3;
4 4
5 5 use Dudlewebs\WPMCS\s3\Aws\Api\Service;
6 +use Dudlewebs\WPMCS\s3\Aws\Api\Shape;
6 7 use Dudlewebs\WPMCS\s3\Aws\CommandInterface;
8 +use Dudlewebs\WPMCS\s3\Aws\MetricsBuilder;
7 9 use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7;
8 10 use InvalidArgumentException;
9 11 use Dudlewebs\WPMCS\s3\Psr\Http\Message\RequestInterface;
12 +use Dudlewebs\WPMCS\s3\Psr\Http\Message\StreamInterface;
10 13 /**
11 14 * Apply required or optional checksums to requests before sending.
12 15 *
13 16 * IMPORTANT: This middleware must be added after the "build" step.
@@ -16,11 +19,22 @@
16 19 */
17 20 class ApplyChecksumMiddleware
18 21 {
19 22 use CalculatesChecksumTrait;
20 - private static $sha256 = ['PutObject', 'UploadPart'];
23 + public const DEFAULT_CALCULATION_MODE = 'when_supported';
24 + public const DEFAULT_ALGORITHM = 'crc32';
25 + /**
26 + * @var true[]
27 + *
28 + * S3 Operations for which pre-calculated SHA256
29 + * Checksums can be added to the command
30 + */
31 + public static $sha256 = ['PutObject' => \true, 'UploadPart' => \true];
21 32 /** @var Service */
22 33 private $api;
34 + /** @var array */
35 + private $config;
36 + /** @var callable */
23 37 private $nextHandler;
24 38 /**
25 39 * Create a middleware wrapper function.
26 40 *
@@ -26,18 +40,19 @@
26 40 *
27 41 * @param Service $api
28 42 * @return callable
29 43 */
30 - public static function wrap(Service $api)
44 + public static function wrap(Service $api, array $config = [])
31 45 {
32 - return function (callable $handler) use($api) {
33 - return new self($handler, $api);
46 + return function (callable $handler) use($api, $config) {
47 + return new self($handler, $api, $config);
34 48 };
35 49 }
36 - public function __construct(callable $nextHandler, Service $api)
50 + public function __construct(callable $nextHandler, Service $api, array $config = [])
37 51 {
38 52 $this->api = $api;
39 53 $this->nextHandler = $nextHandler;
54 + $this->config = $config;
40 55 }
41 56 public function __invoke(CommandInterface $command, RequestInterface $request)
42 57 {
43 58 $next = $this->nextHandler;
@@ -42,39 +57,106 @@
42 57 {
43 58 $next = $this->nextHandler;
44 59 $name = $command->getName();
45 60 $body = $request->getBody();
46 - $op = $this->api->getOperation($command->getName());
47 - $checksumInfo = isset($op['httpChecksum']) ? $op['httpChecksum'] : [];
48 - $checksumMemberName = \array_key_exists('requestAlgorithmMember', $checksumInfo) ? $checksumInfo['requestAlgorithmMember'] : "";
49 - $requestedAlgorithm = isset($command[$checksumMemberName]) ? $command[$checksumMemberName] : null;
50 - if (!empty($checksumMemberName) && !empty($requestedAlgorithm)) {
61 + $operation = $this->api->getOperation($name);
62 + $mode = $this->config['request_checksum_calculation'] ?? self::DEFAULT_CALCULATION_MODE;
63 + $command->getMetricsBuilder()->identifyMetricByValueAndAppend('request_checksum_calculation', $mode);
64 + // Trigger warning if AddContentMD5 is specified for PutObject or UploadPart
65 + $this->handleDeprecatedAddContentMD5($command);
66 + $checksumInfo = $operation['httpChecksum'] ?? [];
67 + $checksumMemberName = $checksumInfo['requestAlgorithmMember'] ?? '';
68 + $checksumMember = !empty($checksumMemberName) ? $operation->getInput()->getMember($checksumMemberName) : null;
69 + $checksumRequired = $checksumInfo['requestChecksumRequired'] ?? \false;
70 + $requestedAlgorithm = $command[$checksumMemberName] ?? null;
71 + $shouldAddChecksum = $this->shouldAddChecksum($mode, $checksumRequired, $checksumMember, $requestedAlgorithm);
72 + if ($shouldAddChecksum) {
73 + if (!$this->hasAlgorithmHeader($request)) {
74 + $supportedAlgorithms = \array_map('strtolower', $checksumMember['enum'] ?? []);
75 + $algorithm = $this->determineChecksumAlgorithm($supportedAlgorithms, $requestedAlgorithm, $checksumMemberName);
76 + $request = $this->addAlgorithmHeader($algorithm, $request, $body);
77 + $command->getMetricsBuilder()->identifyMetricByValueAndAppend('request_checksum', $algorithm);
78 + }
79 + }
80 + // Set the content hash header if ContentSHA256 is provided
81 + if (isset(self::$sha256[$name]) && $command['ContentSHA256']) {
82 + $request = $request->withHeader('X-Amz-Content-Sha256', $command['ContentSHA256']);
83 + $command->getMetricsBuilder()->append(MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_SHA256);
84 + }
85 + return $next($command, $request);
86 + }
87 + /**
88 + * @param CommandInterface $command
89 + *
90 + * @return void
91 + */
92 + private function handleDeprecatedAddContentMD5(CommandInterface $command) : void
93 + {
94 + if (!empty($command['AddContentMD5'])) {
95 + \trigger_error('S3 no longer supports MD5 checksums. ' . 'A CRC32 checksum will be computed and applied on your behalf.', \E_USER_DEPRECATED);
96 + $command['ChecksumAlgorithm'] = self::DEFAULT_ALGORITHM;
97 + }
98 + }
99 + /**
100 + * @param string $mode
101 + * @param Shape|null $checksumMember
102 + * @param string $name
103 + * @param bool $checksumRequired
104 + * @param string|null $requestedAlgorithm
105 + *
106 + * @return bool
107 + */
108 + private function shouldAddChecksum(string $mode, bool $checksumRequired, ?Shape $checksumMember, ?string $requestedAlgorithm) : bool
109 + {
110 + return $mode === 'when_supported' && $checksumMember || $mode === 'when_required' && ($checksumRequired || $checksumMember && $requestedAlgorithm);
111 + }
112 + /**
113 + * @param Shape|null $checksumMember
114 + * @param string|null $requestedAlgorithm
115 + * @param string|null $checksumMemberName
116 + *
117 + * @return string
118 + */
119 + private function determineChecksumAlgorithm(array $supportedAlgorithms, ?string $requestedAlgorithm, ?string $checksumMemberName) : string
120 + {
121 + $algorithm = self::DEFAULT_ALGORITHM;
122 + if ($requestedAlgorithm) {
51 123 $requestedAlgorithm = \strtolower($requestedAlgorithm);
52 - $checksumMember = $op->getInput()->getMember($checksumMemberName);
53 - $supportedAlgorithms = isset($checksumMember['enum']) ? \array_map('strtolower', $checksumMember['enum']) : null;
54 - if (\is_array($supportedAlgorithms) && \in_array($requestedAlgorithm, $supportedAlgorithms)) {
55 - $headerName = "x-amz-checksum-{$requestedAlgorithm}";
56 - $encoded = $this->getEncodedValue($requestedAlgorithm, $body);
57 - if (!$request->hasHeader($headerName)) {
58 - $request = $request->withHeader($headerName, $encoded);
59 - }
60 - } else {
61 - throw new InvalidArgumentException("Unsupported algorithm supplied for input variable {$checksumMemberName}." . " Supported checksums for this operation include: " . \implode(", ", $supportedAlgorithms) . ".");
124 + if (!\in_array($requestedAlgorithm, $supportedAlgorithms)) {
125 + throw new InvalidArgumentException("Unsupported algorithm supplied for input variable {$checksumMemberName}. " . "Supported checksums for this operation include: " . \implode(", ", $supportedAlgorithms) . ".");
62 126 }
63 - return $next($command, $request);
127 + $algorithm = $requestedAlgorithm;
64 128 }
65 - if (!empty($checksumInfo)) {
66 - //if the checksum member is absent, check if it's required
67 - $checksumRequired = isset($checksumInfo['requestChecksumRequired']) ? $checksumInfo['requestChecksumRequired'] : null;
68 - if (!empty($checksumRequired) && !$request->hasHeader('Content-MD5')) {
69 - // Set the content MD5 header for operations that require it.
70 - $request = $request->withHeader('Content-MD5', \base64_encode(Psr7\Utils::hash($body, 'md5', \true)));
71 - return $next($command, $request);
129 + return $algorithm;
130 + }
131 + /**
132 + * @param string $requestedAlgorithm
133 + * @param RequestInterface $request
134 + * @param StreamInterface $body
135 + *
136 + * @return RequestInterface
137 + */
138 + private function addAlgorithmHeader(string $requestedAlgorithm, RequestInterface $request, StreamInterface $body) : RequestInterface
139 + {
140 + $headerName = "x-amz-checksum-{$requestedAlgorithm}";
141 + if (!$request->hasHeader($headerName)) {
142 + $encoded = self::getEncodedValue($requestedAlgorithm, $body);
143 + $request = $request->withHeader($headerName, $encoded);
144 + }
145 + return $request;
146 + }
147 + /**
148 + * @param RequestInterface $request
149 + *
150 + * @return bool
151 + */
152 + private function hasAlgorithmHeader(RequestInterface $request) : bool
153 + {
154 + $headers = $request->getHeaders();
155 + foreach ($headers as $name => $values) {
156 + if (\stripos($name, 'x-amz-checksum-') === 0) {
157 + return \true;
72 158 }
73 159 }
74 - if (\in_array($name, self::$sha256) && $command['ContentSHA256']) {
75 - // Set the content hash header if provided in the parameters.
76 - $request = $request->withHeader('X-Amz-Content-Sha256', $command['ContentSHA256']);
77 - }
78 - return $next($command, $request);
160 + return \false;
79 161 }
80 162 }