api = $api; $this->parser = $parser; } public function __invoke(CommandInterface $command, ResponseInterface $response) { $fn = $this->parser; $result = $fn($command, $response); //Skip this middleware if the operation doesn't have an httpChecksum $op = $this->api->getOperation($command->getName()); $checksumInfo = isset($op['httpChecksum']) ? $op['httpChecksum'] : []; if (empty($checksumInfo)) { return $result; } //Skip this middleware if the operation doesn't send back a checksum, or the user doesn't opt in $checksumModeEnabledMember = isset($checksumInfo['requestValidationModeMember']) ? $checksumInfo['requestValidationModeMember'] : ""; $checksumModeEnabled = isset($command[$checksumModeEnabledMember]) ? $command[$checksumModeEnabledMember] : ""; $responseAlgorithms = isset($checksumInfo['responseAlgorithms']) ? $checksumInfo['responseAlgorithms'] : []; if (empty($responseAlgorithms) || \strtolower($checksumModeEnabled) !== "enabled") { return $result; } if (\extension_loaded('awscrt')) { $checksumPriority = ['CRC32C', 'CRC32', 'SHA1', 'SHA256']; } else { $checksumPriority = ['CRC32', 'SHA1', 'SHA256']; } $checksumsToCheck = \array_intersect($responseAlgorithms, $checksumPriority); $checksumValidationInfo = $this->validateChecksum($checksumsToCheck, $response); if ($checksumValidationInfo['status'] == "SUCCEEDED") { $result['ChecksumValidated'] = $checksumValidationInfo['checksum']; } else { if ($checksumValidationInfo['status'] == "FAILED") { //Ignore failed validations on GetObject if it's a multipart get which returned a full multipart object if ($command->getName() == "GetObject" && !empty($checksumValidationInfo['checksumHeaderValue'])) { $headerValue = $checksumValidationInfo['checksumHeaderValue']; $lastDashPos = \strrpos($headerValue, '-'); $endOfChecksum = \substr($headerValue, $lastDashPos + 1); if (\is_numeric($endOfChecksum) && \intval($endOfChecksum) > 1 && \intval($endOfChecksum) < 10000) { return $result; } } throw new S3Exception("Calculated response checksum did not match the expected value", $command); } } return $result; } public function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response) { return $this->parser->parseMemberFromStream($stream, $member, $response); } /** * @param $checksumPriority * @param ResponseInterface $response */ public function validateChecksum($checksumPriority, ResponseInterface $response) { $checksumToValidate = $this->chooseChecksumHeaderToValidate($checksumPriority, $response); $validationStatus = "SKIPPED"; $checksumHeaderValue = null; if (!empty($checksumToValidate)) { $checksumHeaderValue = $response->getHeader('x-amz-checksum-' . $checksumToValidate); if (isset($checksumHeaderValue)) { $checksumHeaderValue = $checksumHeaderValue[0]; $calculatedChecksumValue = $this->getEncodedValue($checksumToValidate, $response->getBody()); $validationStatus = $checksumHeaderValue == $calculatedChecksumValue ? "SUCCEEDED" : "FAILED"; } } return ["status" => $validationStatus, "checksum" => $checksumToValidate, "checksumHeaderValue" => $checksumHeaderValue]; } /** * @param $checksumPriority * @param ResponseInterface $response */ public function chooseChecksumHeaderToValidate($checksumPriority, ResponseInterface $response) { foreach ($checksumPriority as $checksum) { $checksumHeader = 'x-amz-checksum-' . $checksum; if ($response->hasHeader($checksumHeader)) { return $checksum; } } return null; } }