You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/03/24 10:54:07 UTC

(opendal) branch main updated: fix(s3): Fix improper handle for copy response (#7294)

This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 22e673117 fix(s3): Fix improper handle for copy response (#7294)
22e673117 is described below

commit 22e673117ab9e92e9521fd1ca48137bd385a29d8
Author: dentiny <de...@gmail.com>
AuthorDate: Tue Mar 24 03:53:59 2026 -0700

    fix(s3): Fix improper handle for copy response (#7294)
    
    * fix(s3): Fix improper handle for copy response
    
    * comment
    
    * format
---
 core/services/s3/src/backend.rs | 24 +++++++++++++++++++++++-
 core/services/s3/src/core.rs    | 11 +++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/core/services/s3/src/backend.rs b/core/services/s3/src/backend.rs
index 26d8b4713..b996cf8e9 100644
--- a/core/services/s3/src/backend.rs
+++ b/core/services/s3/src/backend.rs
@@ -1082,7 +1082,29 @@ impl Access for S3Backend {
         let status = resp.status();
 
         match status {
-            StatusCode::OK => Ok(RpCopy::default()),
+            StatusCode::OK => {
+                // S3 CopyObject may return an error embedded in a 200 OK response body
+                // when the error occurs during the copy (e.g., throttling, internal error).
+                // We must parse the body to detect this.
+                //
+                // ref: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
+                // ref: https://repost.aws/knowledge-center/s3-resolve-200-internalerror
+                let body = resp.into_body().to_bytes();
+
+                let result: CopyObjectResult =
+                    quick_xml::de::from_reader(body.as_ref()).map_err(new_xml_deserialize_error)?;
+
+                // On success, ETag is always present. If it's empty, the body was not a
+                // valid <CopyObjectResult> — it's an error response embedded in 200 OK.
+                if result.etag.is_empty() {
+                    return Err(
+                        Error::new(ErrorKind::Unexpected, String::from_utf8_lossy(&body))
+                            .set_temporary(),
+                    );
+                }
+
+                Ok(RpCopy::default())
+            }
             _ => Err(parse_error(resp)),
         }
     }
diff --git a/core/services/s3/src/core.rs b/core/services/s3/src/core.rs
index f5482754f..dbb29a5b8 100644
--- a/core/services/s3/src/core.rs
+++ b/core/services/s3/src/core.rs
@@ -1111,6 +1111,17 @@ pub struct CompleteMultipartUploadResult {
     pub request_id: String,
 }
 
+/// Partial output of a successful `CopyObject` operation.
+///
+/// ref: <https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html#API_CopyObject_ResponseSyntax>
+#[derive(Debug, Default, Deserialize)]
+#[serde(default, rename_all = "PascalCase")]
+pub struct CopyObjectResult {
+    #[serde(rename = "ETag")]
+    pub etag: String,
+    pub last_modified: String,
+}
+
 /// Request of DeleteObjects.
 #[derive(Default, Debug, Serialize)]
 #[serde(default, rename = "Delete", rename_all = "PascalCase")]