| 1 |
<?php |
| 2 |
|
| 3 |
namespace Dudlewebs\WPMCS\s3\Aws\S3; |
| 4 |
|
| 5 |
use Dudlewebs\WPMCS\s3\Aws\Api\Parser\AbstractParser; |
| 6 |
use Dudlewebs\WPMCS\s3\Aws\Api\Service; |
| 7 |
use Dudlewebs\WPMCS\s3\Aws\Api\StructureShape; |
| 8 |
use Dudlewebs\WPMCS\s3\Aws\CommandInterface; |
| 9 |
use Dudlewebs\WPMCS\s3\Aws\S3\Exception\S3Exception; |
| 10 |
use Dudlewebs\WPMCS\s3\Psr\Http\Message\ResponseInterface; |
| 11 |
use Dudlewebs\WPMCS\s3\Psr\Http\Message\StreamInterface; |
| 12 |
/** |
| 13 |
* @internal Decorates a parser for the S3 service to validate the response checksum. |
| 14 |
*/ |
| 15 |
class ValidateResponseChecksumParser extends AbstractParser |
| 16 |
{ |
| 17 |
use CalculatesChecksumTrait; |
| 18 |
/** |
| 19 |
* @param callable $parser Parser to wrap. |
| 20 |
*/ |
| 21 |
public function __construct(callable $parser, Service $api) |
| 22 |
{ |
| 23 |
$this->api = $api; |
| 24 |
$this->parser = $parser; |
| 25 |
} |
| 26 |
public function __invoke(CommandInterface $command, ResponseInterface $response) |
| 27 |
{ |
| 28 |
$fn = $this->parser; |
| 29 |
$result = $fn($command, $response); |
| 30 |
//Skip this middleware if the operation doesn't have an httpChecksum |
| 31 |
$op = $this->api->getOperation($command->getName()); |
| 32 |
$checksumInfo = isset($op['httpChecksum']) ? $op['httpChecksum'] : []; |
| 33 |
if (empty($checksumInfo)) { |
| 34 |
return $result; |
| 35 |
} |
| 36 |
//Skip this middleware if the operation doesn't send back a checksum, or the user doesn't opt in |
| 37 |
$checksumModeEnabledMember = isset($checksumInfo['requestValidationModeMember']) ? $checksumInfo['requestValidationModeMember'] : ""; |
| 38 |
$checksumModeEnabled = isset($command[$checksumModeEnabledMember]) ? $command[$checksumModeEnabledMember] : ""; |
| 39 |
$responseAlgorithms = isset($checksumInfo['responseAlgorithms']) ? $checksumInfo['responseAlgorithms'] : []; |
| 40 |
if (empty($responseAlgorithms) || \strtolower($checksumModeEnabled) !== "enabled") { |
| 41 |
return $result; |
| 42 |
} |
| 43 |
if (\extension_loaded('awscrt')) { |
| 44 |
$checksumPriority = ['CRC32C', 'CRC32', 'SHA1', 'SHA256']; |
| 45 |
} else { |
| 46 |
$checksumPriority = ['CRC32', 'SHA1', 'SHA256']; |
| 47 |
} |
| 48 |
$checksumsToCheck = \array_intersect($responseAlgorithms, $checksumPriority); |
| 49 |
$checksumValidationInfo = $this->validateChecksum($checksumsToCheck, $response); |
| 50 |
if ($checksumValidationInfo['status'] == "SUCCEEDED") { |
| 51 |
$result['ChecksumValidated'] = $checksumValidationInfo['checksum']; |
| 52 |
} else { |
| 53 |
if ($checksumValidationInfo['status'] == "FAILED") { |
| 54 |
//Ignore failed validations on GetObject if it's a multipart get which returned a full multipart object |
| 55 |
if ($command->getName() == "GetObject" && !empty($checksumValidationInfo['checksumHeaderValue'])) { |
| 56 |
$headerValue = $checksumValidationInfo['checksumHeaderValue']; |
| 57 |
$lastDashPos = \strrpos($headerValue, '-'); |
| 58 |
$endOfChecksum = \substr($headerValue, $lastDashPos + 1); |
| 59 |
if (\is_numeric($endOfChecksum) && \intval($endOfChecksum) > 1 && \intval($endOfChecksum) < 10000) { |
| 60 |
return $result; |
| 61 |
} |
| 62 |
} |
| 63 |
throw new S3Exception("Calculated response checksum did not match the expected value", $command); |
| 64 |
} |
| 65 |
} |
| 66 |
return $result; |
| 67 |
} |
| 68 |
public function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response) |
| 69 |
{ |
| 70 |
return $this->parser->parseMemberFromStream($stream, $member, $response); |
| 71 |
} |
| 72 |
/** |
| 73 |
* @param $checksumPriority |
| 74 |
* @param ResponseInterface $response |
| 75 |
*/ |
| 76 |
public function validateChecksum($checksumPriority, ResponseInterface $response) |
| 77 |
{ |
| 78 |
$checksumToValidate = $this->chooseChecksumHeaderToValidate($checksumPriority, $response); |
| 79 |
$validationStatus = "SKIPPED"; |
| 80 |
$checksumHeaderValue = null; |
| 81 |
if (!empty($checksumToValidate)) { |
| 82 |
$checksumHeaderValue = $response->getHeader('x-amz-checksum-' . $checksumToValidate); |
| 83 |
if (isset($checksumHeaderValue)) { |
| 84 |
$checksumHeaderValue = $checksumHeaderValue[0]; |
| 85 |
$calculatedChecksumValue = $this->getEncodedValue($checksumToValidate, $response->getBody()); |
| 86 |
$validationStatus = $checksumHeaderValue == $calculatedChecksumValue ? "SUCCEEDED" : "FAILED"; |
| 87 |
} |
| 88 |
} |
| 89 |
return ["status" => $validationStatus, "checksum" => $checksumToValidate, "checksumHeaderValue" => $checksumHeaderValue]; |
| 90 |
} |
| 91 |
/** |
| 92 |
* @param $checksumPriority |
| 93 |
* @param ResponseInterface $response |
| 94 |
*/ |
| 95 |
public function chooseChecksumHeaderToValidate($checksumPriority, ResponseInterface $response) |
| 96 |
{ |
| 97 |
foreach ($checksumPriority as $checksum) { |
| 98 |
$checksumHeader = 'x-amz-checksum-' . $checksum; |
| 99 |
if ($response->hasHeader($checksumHeader)) { |
| 100 |
return $checksum; |
| 101 |
} |
| 102 |
} |
| 103 |
return null; |
| 104 |
} |
| 105 |
} |
| 106 |
|