← All changes
|
includes/sdk/s3/Aws/S3/ApplyChecksumMiddleware.php
+115
-33
1.2.4
→
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 | } |