| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 | <?phpnamespace Aws\S3;use Aws\Api\Parser\AbstractParser;use Aws\Api\Service;use Aws\Api\StructureShape;use Aws\CommandInterface;use Aws\S3\Exception\S3Exception;use Psr\Http\Message\ResponseInterface;use Psr\Http\Message\StreamInterface;/** * @internal Decorates a parser for the S3 service to validate the response checksum. */class ValidateResponseChecksumParser extends AbstractParser{    use CalculatesChecksumTrait;    /**     * @param callable $parser Parser to wrap.     */    public function __construct(callable $parser, Service $api)    {        $this->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;    }}
 |