'use strict'; var core = require('@aws-sdk/core'); var protocolHttp = require('@smithy/protocol-http'); var utilStream = require('@smithy/util-stream'); var isArrayBuffer = require('@smithy/is-array-buffer'); var crc32c = require('@aws-crypto/crc32c'); var getCrc32ChecksumAlgorithmFunction = require('./getCrc32ChecksumAlgorithmFunction'); var utilUtf8 = require('@smithy/util-utf8'); var utilMiddleware = require('@smithy/util-middleware'); const RequestChecksumCalculation = { WHEN_SUPPORTED: "WHEN_SUPPORTED", WHEN_REQUIRED: "WHEN_REQUIRED", }; const DEFAULT_REQUEST_CHECKSUM_CALCULATION = RequestChecksumCalculation.WHEN_SUPPORTED; const ResponseChecksumValidation = { WHEN_SUPPORTED: "WHEN_SUPPORTED", WHEN_REQUIRED: "WHEN_REQUIRED", }; const DEFAULT_RESPONSE_CHECKSUM_VALIDATION = RequestChecksumCalculation.WHEN_SUPPORTED; exports.ChecksumAlgorithm = void 0; (function (ChecksumAlgorithm) { ChecksumAlgorithm["MD5"] = "MD5"; ChecksumAlgorithm["CRC32"] = "CRC32"; ChecksumAlgorithm["CRC32C"] = "CRC32C"; ChecksumAlgorithm["CRC64NVME"] = "CRC64NVME"; ChecksumAlgorithm["SHA1"] = "SHA1"; ChecksumAlgorithm["SHA256"] = "SHA256"; })(exports.ChecksumAlgorithm || (exports.ChecksumAlgorithm = {})); exports.ChecksumLocation = void 0; (function (ChecksumLocation) { ChecksumLocation["HEADER"] = "header"; ChecksumLocation["TRAILER"] = "trailer"; })(exports.ChecksumLocation || (exports.ChecksumLocation = {})); const DEFAULT_CHECKSUM_ALGORITHM = exports.ChecksumAlgorithm.CRC32; var SelectorType; (function (SelectorType) { SelectorType["ENV"] = "env"; SelectorType["CONFIG"] = "shared config entry"; })(SelectorType || (SelectorType = {})); const stringUnionSelector = (obj, key, union, type) => { if (!(key in obj)) return undefined; const value = obj[key].toUpperCase(); if (!Object.values(union).includes(value)) { throw new TypeError(`Cannot load ${type} '${key}'. Expected one of ${Object.values(union)}, got '${obj[key]}'.`); } return value; }; const ENV_REQUEST_CHECKSUM_CALCULATION = "AWS_REQUEST_CHECKSUM_CALCULATION"; const CONFIG_REQUEST_CHECKSUM_CALCULATION = "request_checksum_calculation"; const NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS = { environmentVariableSelector: (env) => stringUnionSelector(env, ENV_REQUEST_CHECKSUM_CALCULATION, RequestChecksumCalculation, SelectorType.ENV), configFileSelector: (profile) => stringUnionSelector(profile, CONFIG_REQUEST_CHECKSUM_CALCULATION, RequestChecksumCalculation, SelectorType.CONFIG), default: DEFAULT_REQUEST_CHECKSUM_CALCULATION, }; const ENV_RESPONSE_CHECKSUM_VALIDATION = "AWS_RESPONSE_CHECKSUM_VALIDATION"; const CONFIG_RESPONSE_CHECKSUM_VALIDATION = "response_checksum_validation"; const NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS = { environmentVariableSelector: (env) => stringUnionSelector(env, ENV_RESPONSE_CHECKSUM_VALIDATION, ResponseChecksumValidation, SelectorType.ENV), configFileSelector: (profile) => stringUnionSelector(profile, CONFIG_RESPONSE_CHECKSUM_VALIDATION, ResponseChecksumValidation, SelectorType.CONFIG), default: DEFAULT_RESPONSE_CHECKSUM_VALIDATION, }; const crc64NvmeCrtContainer = { CrtCrc64Nvme: null, }; const CLIENT_SUPPORTED_ALGORITHMS = [ exports.ChecksumAlgorithm.CRC32, exports.ChecksumAlgorithm.CRC32C, exports.ChecksumAlgorithm.CRC64NVME, exports.ChecksumAlgorithm.SHA1, exports.ChecksumAlgorithm.SHA256, ]; const PRIORITY_ORDER_ALGORITHMS = [ exports.ChecksumAlgorithm.SHA256, exports.ChecksumAlgorithm.SHA1, exports.ChecksumAlgorithm.CRC32, exports.ChecksumAlgorithm.CRC32C, exports.ChecksumAlgorithm.CRC64NVME, ]; const getChecksumAlgorithmForRequest = (input, { requestChecksumRequired, requestAlgorithmMember, requestChecksumCalculation }) => { if (!requestAlgorithmMember) { return requestChecksumCalculation === RequestChecksumCalculation.WHEN_SUPPORTED || requestChecksumRequired ? DEFAULT_CHECKSUM_ALGORITHM : undefined; } if (!input[requestAlgorithmMember]) { return undefined; } const checksumAlgorithm = input[requestAlgorithmMember]; if (!CLIENT_SUPPORTED_ALGORITHMS.includes(checksumAlgorithm)) { throw new Error(`The checksum algorithm "${checksumAlgorithm}" is not supported by the client.` + ` Select one of ${CLIENT_SUPPORTED_ALGORITHMS}.`); } return checksumAlgorithm; }; const getChecksumLocationName = (algorithm) => algorithm === exports.ChecksumAlgorithm.MD5 ? "content-md5" : `x-amz-checksum-${algorithm.toLowerCase()}`; const hasHeader = (header, headers) => { const soughtHeader = header.toLowerCase(); for (const headerName of Object.keys(headers)) { if (soughtHeader === headerName.toLowerCase()) { return true; } } return false; }; const hasHeaderWithPrefix = (headerPrefix, headers) => { const soughtHeaderPrefix = headerPrefix.toLowerCase(); for (const headerName of Object.keys(headers)) { if (headerName.toLowerCase().startsWith(soughtHeaderPrefix)) { return true; } } return false; }; const isStreaming = (body) => body !== undefined && typeof body !== "string" && !ArrayBuffer.isView(body) && !isArrayBuffer.isArrayBuffer(body); const selectChecksumAlgorithmFunction = (checksumAlgorithm, config) => { switch (checksumAlgorithm) { case exports.ChecksumAlgorithm.MD5: return config.md5; case exports.ChecksumAlgorithm.CRC32: return getCrc32ChecksumAlgorithmFunction.getCrc32ChecksumAlgorithmFunction(); case exports.ChecksumAlgorithm.CRC32C: return crc32c.AwsCrc32c; case exports.ChecksumAlgorithm.CRC64NVME: if (typeof crc64NvmeCrtContainer.CrtCrc64Nvme !== "function") { throw new Error(`Please check whether you have installed the "@aws-sdk/crc64-nvme-crt" package explicitly. \n` + `You must also register the package by calling [require("@aws-sdk/crc64-nvme-crt");] ` + `or an ESM equivalent such as [import "@aws-sdk/crc64-nvme-crt";]. \n` + "For more information please go to " + "https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt"); } return crc64NvmeCrtContainer.CrtCrc64Nvme; case exports.ChecksumAlgorithm.SHA1: return config.sha1; case exports.ChecksumAlgorithm.SHA256: return config.sha256; default: throw new Error(`Unsupported checksum algorithm: ${checksumAlgorithm}`); } }; const stringHasher = (checksumAlgorithmFn, body) => { const hash = new checksumAlgorithmFn(); hash.update(utilUtf8.toUint8Array(body || "")); return hash.digest(); }; const flexibleChecksumsMiddlewareOptions = { name: "flexibleChecksumsMiddleware", step: "build", tags: ["BODY_CHECKSUM"], override: true, }; const flexibleChecksumsMiddleware = (config, middlewareConfig) => (next, context) => async (args) => { if (!protocolHttp.HttpRequest.isInstance(args.request)) { return next(args); } if (hasHeaderWithPrefix("x-amz-checksum-", args.request.headers)) { return next(args); } const { request, input } = args; const { body: requestBody, headers } = request; const { base64Encoder, streamHasher } = config; const { requestChecksumRequired, requestAlgorithmMember } = middlewareConfig; const requestChecksumCalculation = await config.requestChecksumCalculation(); const requestAlgorithmMemberName = requestAlgorithmMember?.name; const requestAlgorithmMemberHttpHeader = requestAlgorithmMember?.httpHeader; if (requestAlgorithmMemberName && !input[requestAlgorithmMemberName]) { if (requestChecksumCalculation === RequestChecksumCalculation.WHEN_SUPPORTED || requestChecksumRequired) { input[requestAlgorithmMemberName] = DEFAULT_CHECKSUM_ALGORITHM; if (requestAlgorithmMemberHttpHeader) { headers[requestAlgorithmMemberHttpHeader] = DEFAULT_CHECKSUM_ALGORITHM; } } } const checksumAlgorithm = getChecksumAlgorithmForRequest(input, { requestChecksumRequired, requestAlgorithmMember: requestAlgorithmMember?.name, requestChecksumCalculation, }); let updatedBody = requestBody; let updatedHeaders = headers; if (checksumAlgorithm) { switch (checksumAlgorithm) { case exports.ChecksumAlgorithm.CRC32: core.setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_CRC32", "U"); break; case exports.ChecksumAlgorithm.CRC32C: core.setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_CRC32C", "V"); break; case exports.ChecksumAlgorithm.CRC64NVME: core.setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_CRC64", "W"); break; case exports.ChecksumAlgorithm.SHA1: core.setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_SHA1", "X"); break; case exports.ChecksumAlgorithm.SHA256: core.setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_SHA256", "Y"); break; } const checksumLocationName = getChecksumLocationName(checksumAlgorithm); const checksumAlgorithmFn = selectChecksumAlgorithmFunction(checksumAlgorithm, config); if (isStreaming(requestBody)) { const { getAwsChunkedEncodingStream, bodyLengthChecker } = config; updatedBody = getAwsChunkedEncodingStream(typeof config.requestStreamBufferSize === "number" && config.requestStreamBufferSize >= 8 * 1024 ? utilStream.createBufferedReadable(requestBody, config.requestStreamBufferSize, context.logger) : requestBody, { base64Encoder, bodyLengthChecker, checksumLocationName, checksumAlgorithmFn, streamHasher, }); updatedHeaders = { ...headers, "content-encoding": headers["content-encoding"] ? `${headers["content-encoding"]},aws-chunked` : "aws-chunked", "transfer-encoding": "chunked", "x-amz-decoded-content-length": headers["content-length"], "x-amz-content-sha256": "STREAMING-UNSIGNED-PAYLOAD-TRAILER", "x-amz-trailer": checksumLocationName, }; delete updatedHeaders["content-length"]; } else if (!hasHeader(checksumLocationName, headers)) { const rawChecksum = await stringHasher(checksumAlgorithmFn, requestBody); updatedHeaders = { ...headers, [checksumLocationName]: base64Encoder(rawChecksum), }; } } const result = await next({ ...args, request: { ...request, headers: updatedHeaders, body: updatedBody, }, }); return result; }; const flexibleChecksumsInputMiddlewareOptions = { name: "flexibleChecksumsInputMiddleware", toMiddleware: "serializerMiddleware", relation: "before", tags: ["BODY_CHECKSUM"], override: true, }; const flexibleChecksumsInputMiddleware = (config, middlewareConfig) => (next, context) => async (args) => { const input = args.input; const { requestValidationModeMember } = middlewareConfig; const requestChecksumCalculation = await config.requestChecksumCalculation(); const responseChecksumValidation = await config.responseChecksumValidation(); switch (requestChecksumCalculation) { case RequestChecksumCalculation.WHEN_REQUIRED: core.setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED", "a"); break; case RequestChecksumCalculation.WHEN_SUPPORTED: core.setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED", "Z"); break; } switch (responseChecksumValidation) { case ResponseChecksumValidation.WHEN_REQUIRED: core.setFeature(context, "FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED", "c"); break; case ResponseChecksumValidation.WHEN_SUPPORTED: core.setFeature(context, "FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED", "b"); break; } if (requestValidationModeMember && !input[requestValidationModeMember]) { if (responseChecksumValidation === ResponseChecksumValidation.WHEN_SUPPORTED) { input[requestValidationModeMember] = "ENABLED"; } } return next(args); }; const getChecksumAlgorithmListForResponse = (responseAlgorithms = []) => { const validChecksumAlgorithms = []; for (const algorithm of PRIORITY_ORDER_ALGORITHMS) { if (!responseAlgorithms.includes(algorithm) || !CLIENT_SUPPORTED_ALGORITHMS.includes(algorithm)) { continue; } validChecksumAlgorithms.push(algorithm); } return validChecksumAlgorithms; }; const isChecksumWithPartNumber = (checksum) => { const lastHyphenIndex = checksum.lastIndexOf("-"); if (lastHyphenIndex !== -1) { const numberPart = checksum.slice(lastHyphenIndex + 1); if (!numberPart.startsWith("0")) { const number = parseInt(numberPart, 10); if (!isNaN(number) && number >= 1 && number <= 10000) { return true; } } } return false; }; const getChecksum = async (body, { checksumAlgorithmFn, base64Encoder }) => base64Encoder(await stringHasher(checksumAlgorithmFn, body)); const validateChecksumFromResponse = async (response, { config, responseAlgorithms, logger }) => { const checksumAlgorithms = getChecksumAlgorithmListForResponse(responseAlgorithms); const { body: responseBody, headers: responseHeaders } = response; for (const algorithm of checksumAlgorithms) { const responseHeader = getChecksumLocationName(algorithm); const checksumFromResponse = responseHeaders[responseHeader]; if (checksumFromResponse) { let checksumAlgorithmFn; try { checksumAlgorithmFn = selectChecksumAlgorithmFunction(algorithm, config); } catch (error) { if (algorithm === exports.ChecksumAlgorithm.CRC64NVME) { logger?.warn(`Skipping ${exports.ChecksumAlgorithm.CRC64NVME} checksum validation: ${error.message}`); continue; } throw error; } const { base64Encoder } = config; if (isStreaming(responseBody)) { response.body = utilStream.createChecksumStream({ expectedChecksum: checksumFromResponse, checksumSourceLocation: responseHeader, checksum: new checksumAlgorithmFn(), source: responseBody, base64Encoder, }); return; } const checksum = await getChecksum(responseBody, { checksumAlgorithmFn, base64Encoder }); if (checksum === checksumFromResponse) { break; } throw new Error(`Checksum mismatch: expected "${checksum}" but received "${checksumFromResponse}"` + ` in response header "${responseHeader}".`); } } }; const flexibleChecksumsResponseMiddlewareOptions = { name: "flexibleChecksumsResponseMiddleware", toMiddleware: "deserializerMiddleware", relation: "after", tags: ["BODY_CHECKSUM"], override: true, }; const flexibleChecksumsResponseMiddleware = (config, middlewareConfig) => (next, context) => async (args) => { if (!protocolHttp.HttpRequest.isInstance(args.request)) { return next(args); } const input = args.input; const result = await next(args); const response = result.response; const { requestValidationModeMember, responseAlgorithms } = middlewareConfig; if (requestValidationModeMember && input[requestValidationModeMember] === "ENABLED") { const { clientName, commandName } = context; const isS3WholeObjectMultipartGetResponseChecksum = clientName === "S3Client" && commandName === "GetObjectCommand" && getChecksumAlgorithmListForResponse(responseAlgorithms).every((algorithm) => { const responseHeader = getChecksumLocationName(algorithm); const checksumFromResponse = response.headers[responseHeader]; return !checksumFromResponse || isChecksumWithPartNumber(checksumFromResponse); }); if (isS3WholeObjectMultipartGetResponseChecksum) { return result; } await validateChecksumFromResponse(response, { config, responseAlgorithms, logger: context.logger, }); } return result; }; const getFlexibleChecksumsPlugin = (config, middlewareConfig) => ({ applyToStack: (clientStack) => { clientStack.add(flexibleChecksumsMiddleware(config, middlewareConfig), flexibleChecksumsMiddlewareOptions); clientStack.addRelativeTo(flexibleChecksumsInputMiddleware(config, middlewareConfig), flexibleChecksumsInputMiddlewareOptions); clientStack.addRelativeTo(flexibleChecksumsResponseMiddleware(config, middlewareConfig), flexibleChecksumsResponseMiddlewareOptions); }, }); const resolveFlexibleChecksumsConfig = (input) => { const { requestChecksumCalculation, responseChecksumValidation, requestStreamBufferSize } = input; return Object.assign(input, { requestChecksumCalculation: utilMiddleware.normalizeProvider(requestChecksumCalculation ?? DEFAULT_REQUEST_CHECKSUM_CALCULATION), responseChecksumValidation: utilMiddleware.normalizeProvider(responseChecksumValidation ?? DEFAULT_RESPONSE_CHECKSUM_VALIDATION), requestStreamBufferSize: Number(requestStreamBufferSize ?? 0), }); }; exports.CONFIG_REQUEST_CHECKSUM_CALCULATION = CONFIG_REQUEST_CHECKSUM_CALCULATION; exports.CONFIG_RESPONSE_CHECKSUM_VALIDATION = CONFIG_RESPONSE_CHECKSUM_VALIDATION; exports.DEFAULT_CHECKSUM_ALGORITHM = DEFAULT_CHECKSUM_ALGORITHM; exports.DEFAULT_REQUEST_CHECKSUM_CALCULATION = DEFAULT_REQUEST_CHECKSUM_CALCULATION; exports.DEFAULT_RESPONSE_CHECKSUM_VALIDATION = DEFAULT_RESPONSE_CHECKSUM_VALIDATION; exports.ENV_REQUEST_CHECKSUM_CALCULATION = ENV_REQUEST_CHECKSUM_CALCULATION; exports.ENV_RESPONSE_CHECKSUM_VALIDATION = ENV_RESPONSE_CHECKSUM_VALIDATION; exports.NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS = NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS; exports.NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS = NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS; exports.RequestChecksumCalculation = RequestChecksumCalculation; exports.ResponseChecksumValidation = ResponseChecksumValidation; exports.crc64NvmeCrtContainer = crc64NvmeCrtContainer; exports.flexibleChecksumsMiddleware = flexibleChecksumsMiddleware; exports.flexibleChecksumsMiddlewareOptions = flexibleChecksumsMiddlewareOptions; exports.getFlexibleChecksumsPlugin = getFlexibleChecksumsPlugin; exports.resolveFlexibleChecksumsConfig = resolveFlexibleChecksumsConfig;