ApplyChecksumMiddleware.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. <?php
  2. namespace Aws\S3;
  3. use Aws\Api\Service;
  4. use Aws\CommandInterface;
  5. use GuzzleHttp\Psr7;
  6. use InvalidArgumentException;
  7. use Psr\Http\Message\RequestInterface;
  8. use Psr\Http\Message\StreamInterface;
  9. /**
  10. * Apply required or optional checksums to requests before sending.
  11. *
  12. * IMPORTANT: This middleware must be added after the "build" step.
  13. *
  14. * @internal
  15. */
  16. class ApplyChecksumMiddleware
  17. {
  18. use CalculatesChecksumTrait;
  19. private static $sha256AndMd5 = [
  20. 'PutObject',
  21. 'UploadPart',
  22. ];
  23. /** @var Service */
  24. private $api;
  25. private $nextHandler;
  26. /**
  27. * Create a middleware wrapper function.
  28. *
  29. * @param Service $api
  30. * @return callable
  31. */
  32. public static function wrap(Service $api)
  33. {
  34. return function (callable $handler) use ($api) {
  35. return new self($handler, $api);
  36. };
  37. }
  38. public function __construct(callable $nextHandler, Service $api)
  39. {
  40. $this->api = $api;
  41. $this->nextHandler = $nextHandler;
  42. }
  43. public function __invoke(
  44. CommandInterface $command,
  45. RequestInterface $request
  46. ) {
  47. $next = $this->nextHandler;
  48. $name = $command->getName();
  49. $body = $request->getBody();
  50. //Checks if AddContentMD5 has been specified for PutObject or UploadPart
  51. $addContentMD5 = $command['AddContentMD5'] ?? null;
  52. $op = $this->api->getOperation($command->getName());
  53. $checksumInfo = $op['httpChecksum'] ?? [];
  54. $checksumMemberName = array_key_exists('requestAlgorithmMember', $checksumInfo)
  55. ? $checksumInfo['requestAlgorithmMember']
  56. : "";
  57. $requestedAlgorithm = $command[$checksumMemberName] ?? null;
  58. if (!empty($checksumMemberName) && !empty($requestedAlgorithm)) {
  59. $requestedAlgorithm = strtolower($requestedAlgorithm);
  60. $checksumMember = $op->getInput()->getMember($checksumMemberName);
  61. $supportedAlgorithms = isset($checksumMember['enum'])
  62. ? array_map('strtolower', $checksumMember['enum'])
  63. : null;
  64. if (is_array($supportedAlgorithms)
  65. && in_array($requestedAlgorithm, $supportedAlgorithms)
  66. ) {
  67. $request = $this->addAlgorithmHeader($requestedAlgorithm, $request, $body);
  68. } else {
  69. throw new InvalidArgumentException(
  70. "Unsupported algorithm supplied for input variable {$checksumMemberName}."
  71. . " Supported checksums for this operation include: "
  72. . implode(", ", $supportedAlgorithms) . "."
  73. );
  74. }
  75. return $next($command, $request);
  76. }
  77. if (!empty($checksumInfo)) {
  78. //if the checksum member is absent, check if it's required
  79. $checksumRequired = $checksumInfo['requestChecksumRequired'] ?? null;
  80. if ((!empty($checksumRequired))
  81. || (in_array($name, self::$sha256AndMd5) && $addContentMD5)
  82. ) {
  83. //S3Express doesn't support MD5; default to crc32 instead
  84. if ($this->isS3Express($command)) {
  85. $request = $this->addAlgorithmHeader('crc32', $request, $body);
  86. } elseif (!$request->hasHeader('Content-MD5')) {
  87. // Set the content MD5 header for operations that require it.
  88. $request = $request->withHeader(
  89. 'Content-MD5',
  90. base64_encode(Psr7\Utils::hash($body, 'md5', true))
  91. );
  92. }
  93. return $next($command, $request);
  94. }
  95. }
  96. if (in_array($name, self::$sha256AndMd5) && $command['ContentSHA256']) {
  97. // Set the content hash header if provided in the parameters.
  98. $request = $request->withHeader(
  99. 'X-Amz-Content-Sha256',
  100. $command['ContentSHA256']
  101. );
  102. }
  103. return $next($command, $request);
  104. }
  105. /**
  106. * @param string $requestedAlgorithm
  107. * @param RequestInterface $request
  108. * @param StreamInterface $body
  109. * @return RequestInterface
  110. */
  111. private function addAlgorithmHeader(
  112. string $requestedAlgorithm,
  113. RequestInterface $request,
  114. StreamInterface $body
  115. ) {
  116. $headerName = "x-amz-checksum-{$requestedAlgorithm}";
  117. if (!$request->hasHeader($headerName)) {
  118. $encoded = $this->getEncodedValue($requestedAlgorithm, $body);
  119. $request = $request->withHeader($headerName, $encoded);
  120. }
  121. return $request;
  122. }
  123. /**
  124. * @param CommandInterface $command
  125. * @return bool
  126. */
  127. private function isS3Express(CommandInterface $command): bool
  128. {
  129. return isset($command['@context']['signing_service'])
  130. && $command['@context']['signing_service'] === 's3express';
  131. }
  132. }