diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f3ca5b3..8eb49035 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,11 +125,12 @@ jobs: - name: Setup LocalStack (AWS emulation) run: | - echo "LOCALSTACK_CONTAINER=$(docker run -d -p 4566:4566 localstack/localstack:4.0.3)" >> $GITHUB_ENV + echo "LOCALSTACK_CONTAINER=$(docker run -d -p 4566:4566 localstack/localstack:4.11.1)" >> $GITHUB_ENV echo "EC2_METADATA_CONTAINER=$(docker run -d -p 1338:1338 amazon/amazon-ec2-metadata-mock:v1.9.2 --imdsv2)" >> $GITHUB_ENV aws --endpoint-url=http://localhost:4566 s3 mb s3://test-bucket aws --endpoint-url=http://localhost:4566 s3 mb s3://test-bucket-for-spawn aws --endpoint-url=http://localhost:4566 s3 mb s3://test-bucket-for-checksum + aws --endpoint-url=http://localhost:4566 s3 mb s3://test-bucket-for-copy-if-not-exists aws --endpoint-url=http://localhost:4566 s3api create-bucket --bucket test-object-lock --object-lock-enabled-for-bucket KMS_KEY=$(aws --endpoint-url=http://localhost:4566 kms create-key --description "test key") diff --git a/src/aws/client.rs b/src/aws/client.rs index 63371875..150a47c2 100644 --- a/src/aws/client.rs +++ b/src/aws/client.rs @@ -701,24 +701,24 @@ impl S3Client { // If SSE-C is used, we must include the encryption headers in every upload request. request = request.with_encryption_headers(); } + let (parts, body) = request.send().await?.into_parts(); - let checksum_sha256 = parts - .headers - .get(SHA256_CHECKSUM) - .and_then(|v| v.to_str().ok()) - .map(|v| v.to_string()); - - let e_tag = match is_copy { - false => get_etag(&parts.headers).map_err(|source| Error::Metadata { source })?, - true => { - let response = body - .bytes() - .await - .map_err(|source| Error::CreateMultipartResponseBody { source })?; - let response: CopyPartResult = quick_xml::de::from_reader(response.reader()) - .map_err(|source| Error::InvalidMultipartResponse { source })?; - response.e_tag - } + let (e_tag, checksum_sha256) = if is_copy { + let response = body + .bytes() + .await + .map_err(|source| Error::CreateMultipartResponseBody { source })?; + let response: CopyPartResult = quick_xml::de::from_reader(response.reader()) + .map_err(|source| Error::InvalidMultipartResponse { source })?; + (response.e_tag, response.checksum_sha256) + } else { + let e_tag = get_etag(&parts.headers).map_err(|source| Error::Metadata { source })?; + let checksum_sha256 = parts + .headers + .get(SHA256_CHECKSUM) + .and_then(|v| v.to_str().ok()) + .map(|v| v.to_string()); + (e_tag, checksum_sha256) }; let content_id = if self.config.checksum == Some(Checksum::SHA256) { diff --git a/src/aws/mod.rs b/src/aws/mod.rs index 3e658afc..cb66f9dc 100644 --- a/src/aws/mod.rs +++ b/src/aws/mod.rs @@ -553,6 +553,32 @@ mod tests { store.delete(&path).await.unwrap(); } + #[tokio::test] + async fn copy_multipart_file_with_signature() { + maybe_skip_integration!(); + + let bucket = "test-bucket-for-copy-if-not-exists"; + let store = AmazonS3Builder::from_env() + .with_bucket_name(bucket) + .with_checksum_algorithm(Checksum::SHA256) + .with_copy_if_not_exists(S3CopyIfNotExists::Multipart) + .build() + .unwrap(); + + let src = Path::parse("src.bin").unwrap(); + let dst = Path::parse("dst.bin").unwrap(); + store + .put(&src, PutPayload::from(vec![0u8; 100_000])) + .await + .unwrap(); + if store.head(&dst).await.is_ok() { + store.delete(&dst).await.unwrap(); + } + store.copy_if_not_exists(&src, &dst).await.unwrap(); + store.delete(&src).await.unwrap(); + store.delete(&dst).await.unwrap(); + } + #[tokio::test] async fn write_multipart_file_with_signature_object_lock() { maybe_skip_integration!(); diff --git a/src/client/s3.rs b/src/client/s3.rs index a2221fbb..a1b113e0 100644 --- a/src/client/s3.rs +++ b/src/client/s3.rs @@ -98,6 +98,8 @@ pub(crate) struct InitiateMultipartUploadResult { pub(crate) struct CopyPartResult { #[serde(rename = "ETag")] pub e_tag: String, + #[serde(default, rename = "ChecksumSHA256")] + pub checksum_sha256: Option, } #[derive(Debug, Serialize)]