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 +95 -40 1.2.13 → 1.4.2 View file →
@@ -2,9 +2,11 @@
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;
10 12 use Dudlewebs\WPMCS\s3\Psr\Http\Message\StreamInterface;
@@ -17,11 +19,22 @@
17 19 */
18 20 class ApplyChecksumMiddleware
19 21 {
20 22 use CalculatesChecksumTrait;
21 - private static $sha256AndMd5 = ['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];
22 32 /** @var Service */
23 33 private $api;
34 + /** @var array */
35 + private $config;
36 + /** @var callable */
24 37 private $nextHandler;
25 38 /**
26 39 * Create a middleware wrapper function.
27 40 *
@@ -27,18 +40,19 @@
27 40 *
28 41 * @param Service $api
29 42 * @return callable
30 43 */
31 - public static function wrap(Service $api)
44 + public static function wrap(Service $api, array $config = [])
32 45 {
33 - return function (callable $handler) use($api) {
34 - return new self($handler, $api);
46 + return function (callable $handler) use($api, $config) {
47 + return new self($handler, $api, $config);
35 48 };
36 49 }
37 - public function __construct(callable $nextHandler, Service $api)
50 + public function __construct(callable $nextHandler, Service $api, array $config = [])
38 51 {
39 52 $this->api = $api;
40 53 $this->nextHandler = $nextHandler;
54 + $this->config = $config;
41 55 }
42 56 public function __invoke(CommandInterface $command, RequestInterface $request)
43 57 {
44 58 $next = $this->nextHandler;
@@ -43,65 +57,106 @@
43 57 {
44 58 $next = $this->nextHandler;
45 59 $name = $command->getName();
46 60 $body = $request->getBody();
47 - //Checks if AddContentMD5 has been specified for PutObject or UploadPart
48 - $addContentMD5 = $command['AddContentMD5'] ?? null;
49 - $op = $this->api->getOperation($command->getName());
50 - $checksumInfo = $op['httpChecksum'] ?? [];
51 - $checksumMemberName = \array_key_exists('requestAlgorithmMember', $checksumInfo) ? $checksumInfo['requestAlgorithmMember'] : "";
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;
52 70 $requestedAlgorithm = $command[$checksumMemberName] ?? null;
53 - if (!empty($checksumMemberName) && !empty($requestedAlgorithm)) {
54 - $requestedAlgorithm = \strtolower($requestedAlgorithm);
55 - $checksumMember = $op->getInput()->getMember($checksumMemberName);
56 - $supportedAlgorithms = isset($checksumMember['enum']) ? \array_map('strtolower', $checksumMember['enum']) : null;
57 - if (\is_array($supportedAlgorithms) && \in_array($requestedAlgorithm, $supportedAlgorithms)) {
58 - $request = $this->addAlgorithmHeader($requestedAlgorithm, $request, $body);
59 - } else {
60 - throw new InvalidArgumentException("Unsupported algorithm supplied for input variable {$checksumMemberName}." . " Supported checksums for this operation include: " . \implode(", ", $supportedAlgorithms) . ".");
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);
61 78 }
62 - return $next($command, $request);
63 79 }
64 - if (!empty($checksumInfo)) {
65 - //if the checksum member is absent, check if it's required
66 - $checksumRequired = $checksumInfo['requestChecksumRequired'] ?? null;
67 - if (!empty($checksumRequired) || \in_array($name, self::$sha256AndMd5) && $addContentMD5) {
68 - //S3Express doesn't support MD5; default to crc32 instead
69 - if ($this->isS3Express($command)) {
70 - $request = $this->addAlgorithmHeader('crc32', $request, $body);
71 - } elseif (!$request->hasHeader('Content-MD5')) {
72 - // Set the content MD5 header for operations that require it.
73 - $request = $request->withHeader('Content-MD5', \base64_encode(Psr7\Utils::hash($body, 'md5', \true)));
74 - }
75 - return $next($command, $request);
76 - }
77 - }
78 - if (\in_array($name, self::$sha256AndMd5) && $command['ContentSHA256']) {
79 - // Set the content hash header if provided in the parameters.
80 + // Set the content hash header if ContentSHA256 is provided
81 + if (isset(self::$sha256[$name]) && $command['ContentSHA256']) {
80 82 $request = $request->withHeader('X-Amz-Content-Sha256', $command['ContentSHA256']);
83 + $command->getMetricsBuilder()->append(MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_SHA256);
81 84 }
82 85 return $next($command, $request);
83 86 }
84 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) {
123 + $requestedAlgorithm = \strtolower($requestedAlgorithm);
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) . ".");
126 + }
127 + $algorithm = $requestedAlgorithm;
128 + }
129 + return $algorithm;
130 + }
131 + /**
85 132 * @param string $requestedAlgorithm
86 133 * @param RequestInterface $request
87 134 * @param StreamInterface $body
135 + *
88 136 * @return RequestInterface
89 137 */
90 - private function addAlgorithmHeader(string $requestedAlgorithm, RequestInterface $request, StreamInterface $body)
138 + private function addAlgorithmHeader(string $requestedAlgorithm, RequestInterface $request, StreamInterface $body) : RequestInterface
91 139 {
92 140 $headerName = "x-amz-checksum-{$requestedAlgorithm}";
93 141 if (!$request->hasHeader($headerName)) {
94 - $encoded = $this->getEncodedValue($requestedAlgorithm, $body);
142 + $encoded = self::getEncodedValue($requestedAlgorithm, $body);
95 143 $request = $request->withHeader($headerName, $encoded);
96 144 }
97 145 return $request;
98 146 }
99 147 /**
100 - * @param CommandInterface $command
148 + * @param RequestInterface $request
149 + *
101 150 * @return bool
102 151 */
103 - private function isS3Express(CommandInterface $command) : bool
152 + private function hasAlgorithmHeader(RequestInterface $request) : bool
104 153 {
105 - return isset($command['@context']['signing_service']) && $command['@context']['signing_service'] === 's3express';
154 + $headers = $request->getHeaders();
155 + foreach ($headers as $name => $values) {
156 + if (\stripos($name, 'x-amz-checksum-') === 0) {
157 + return \true;
158 + }
159 + }
160 + return \false;
106 161 }
107 162 }