diff --git a/core/services/s3/src/backend.rs b/core/services/s3/src/backend.rs index f6002d5a2857..ab6c2dd73b31 100644 --- a/core/services/s3/src/backend.rs +++ b/core/services/s3/src/backend.rs @@ -721,10 +721,8 @@ impl S3Builder { } } -impl Builder for S3Builder { - type Config = S3Config; - - fn build(self) -> Result { +impl S3Builder { + fn build_backend(self) -> Result { debug!("backend build started: {:?}", &self); let S3Builder { @@ -1032,6 +1030,14 @@ impl Builder for S3Builder { } } +impl Builder for S3Builder { + type Config = S3Config; + + fn build(self) -> Result { + self.build_backend() + } +} + /// Backend for s3 services. #[derive(Debug, Clone)] pub struct S3Backend { @@ -1359,4 +1365,33 @@ mod tests { "application/json" ); } + + #[test] + fn test_s3_copy_object_request_sets_content_length_zero() -> Result<()> { + let backend = S3Builder::default() + .bucket("test") + .region("us-east-1") + .skip_signature() + .disable_config_load() + .disable_ec2_metadata() + .build_backend()?; + + let req = backend + .core + .s3_copy_object_request("from.txt", "to.txt", &OpCopy::default())?; + + assert_eq!(req.method(), http::Method::PUT); + assert!(req.uri().to_string().ends_with("/test/to.txt")); + assert_eq!( + req.headers().get(http::header::CONTENT_LENGTH).unwrap(), + "0" + ); + assert_eq!( + req.headers().get(constants::X_AMZ_COPY_SOURCE).unwrap(), + "test/from.txt" + ); + assert!(req.body().is_empty()); + + Ok(()) + } } diff --git a/core/services/s3/src/core.rs b/core/services/s3/src/core.rs index a66d204eebf8..65673f6a58d6 100644 --- a/core/services/s3/src/core.rs +++ b/core/services/s3/src/core.rs @@ -690,13 +690,12 @@ impl S3Core { self.send(ctx, req).await } - pub async fn s3_copy_object( + pub fn s3_copy_object_request( &self, - ctx: &OperationContext, from: &str, to: &str, args: &OpCopy, - ) -> Result> { + ) -> Result> { let from = build_abs_path(&self.root, from); let to = build_abs_path(&self.root, to); @@ -769,10 +768,24 @@ impl S3Core { // Inject operation to the request. .extension(Operation::Copy) .extension(ServiceOperation("CopyObject")) + // AWS S3 doesn't care, but some other S3-compat providers + // require a mandatory `content-length` header to work + .header(CONTENT_LENGTH, 0) .header(constants::X_AMZ_COPY_SOURCE, &source) .body(Buffer::new()) .map_err(new_request_build_error)?; + Ok(req) + } + + pub async fn s3_copy_object( + &self, + ctx: &OperationContext, + from: &str, + to: &str, + args: &OpCopy, + ) -> Result> { + let req = self.s3_copy_object_request(from, to, args)?; self.send(ctx, req).await }