| 1 |
<?php |
| 2 |
|
| 3 |
namespace Dudlewebs\WPMCS\s3\Aws\S3; |
| 4 |
|
| 5 |
use Dudlewebs\WPMCS\s3\Aws\Api\Service; |
| 6 |
use Dudlewebs\WPMCS\s3\Aws\CommandInterface; |
| 7 |
use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7; |
| 8 |
use InvalidArgumentException; |
| 9 |
use Dudlewebs\WPMCS\s3\Psr\Http\Message\RequestInterface; |
| 10 |
use Dudlewebs\WPMCS\s3\Psr\Http\Message\StreamInterface; |
| 11 |
/** |
| 12 |
* Apply required or optional checksums to requests before sending. |
| 13 |
* |
| 14 |
* IMPORTANT: This middleware must be added after the "build" step. |
| 15 |
* |
| 16 |
* @internal |
| 17 |
*/ |
| 18 |
class ApplyChecksumMiddleware |
| 19 |
{ |
| 20 |
use CalculatesChecksumTrait; |
| 21 |
private static $sha256AndMd5 = ['PutObject', 'UploadPart']; |
| 22 |
/** @var Service */ |
| 23 |
private $api; |
| 24 |
private $nextHandler; |
| 25 |
/** |
| 26 |
* Create a middleware wrapper function. |
| 27 |
* |
| 28 |
* @param Service $api |
| 29 |
* @return callable |
| 30 |
*/ |
| 31 |
public static function wrap(Service $api) |
| 32 |
{ |
| 33 |
return function (callable $handler) use($api) { |
| 34 |
return new self($handler, $api); |
| 35 |
}; |
| 36 |
} |
| 37 |
public function __construct(callable $nextHandler, Service $api) |
| 38 |
{ |
| 39 |
$this->api = $api; |
| 40 |
$this->nextHandler = $nextHandler; |
| 41 |
} |
| 42 |
public function __invoke(CommandInterface $command, RequestInterface $request) |
| 43 |
{ |
| 44 |
$next = $this->nextHandler; |
| 45 |
$name = $command->getName(); |
| 46 |
$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'] : ""; |
| 52 |
$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) . "."); |
| 61 |
} |
| 62 |
return $next($command, $request); |
| 63 |
} |
| 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 |
$request = $request->withHeader('X-Amz-Content-Sha256', $command['ContentSHA256']); |
| 81 |
} |
| 82 |
return $next($command, $request); |
| 83 |
} |
| 84 |
/** |
| 85 |
* @param string $requestedAlgorithm |
| 86 |
* @param RequestInterface $request |
| 87 |
* @param StreamInterface $body |
| 88 |
* @return RequestInterface |
| 89 |
*/ |
| 90 |
private function addAlgorithmHeader(string $requestedAlgorithm, RequestInterface $request, StreamInterface $body) |
| 91 |
{ |
| 92 |
$headerName = "x-amz-checksum-{$requestedAlgorithm}"; |
| 93 |
if (!$request->hasHeader($headerName)) { |
| 94 |
$encoded = $this->getEncodedValue($requestedAlgorithm, $body); |
| 95 |
$request = $request->withHeader($headerName, $encoded); |
| 96 |
} |
| 97 |
return $request; |
| 98 |
} |
| 99 |
/** |
| 100 |
* @param CommandInterface $command |
| 101 |
* @return bool |
| 102 |
*/ |
| 103 |
private function isS3Express(CommandInterface $command) : bool |
| 104 |
{ |
| 105 |
return isset($command['@context']['signing_service']) && $command['@context']['signing_service'] === 's3express'; |
| 106 |
} |
| 107 |
} |
| 108 |
|