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 b71870c28 fix(gcs): headers missing in XML multipart API and incorrect 
x-goog-acl header values in XML API (#6275)
b71870c28 is described below

commit b71870c28957c2ad75d9d2427023a343740351e6
Author: William Linna <[email protected]>
AuthorDate: Tue Jun 10 14:18:36 2025 +0700

    fix(gcs): headers missing in XML multipart API and incorrect x-goog-acl 
header values in XML API (#6275)
    
    * gcs: Add headers in multipart requests (xml api)
    
    * gcs: Write predefined acl in correct format when using XML API
    
    Fixes https://github.com/apache/opendal/issues/5612
    
    * Run cargo fmt
    
    * Fix eprintln with log::warn
---
 core/src/services/gcs/core.rs   | 63 ++++++++++++++++++++++++++++++++++++++---
 core/src/services/gcs/writer.rs |  2 +-
 2 files changed, 60 insertions(+), 5 deletions(-)

diff --git a/core/src/services/gcs/core.rs b/core/src/services/gcs/core.rs
index df666671c..34eafda8c 100644
--- a/core/src/services/gcs/core.rs
+++ b/core/src/services/gcs/core.rs
@@ -28,6 +28,8 @@ use backon::Retryable;
 use bytes::Buf;
 use bytes::Bytes;
 use constants::*;
+use http::header::CACHE_CONTROL;
+use http::header::CONTENT_DISPOSITION;
 use http::header::CONTENT_ENCODING;
 use http::header::CONTENT_LENGTH;
 use http::header::CONTENT_TYPE;
@@ -370,7 +372,11 @@ impl GcsCore {
         }
 
         if let Some(acl) = &self.predefined_acl {
-            req = req.header(X_GOOG_ACL, acl);
+            if let Some(predefined_acl_in_xml_spec) = 
predefined_acl_to_xml_header(acl) {
+                req = req.header(X_GOOG_ACL, predefined_acl_in_xml_spec);
+            } else {
+                log::warn!("Unrecognized predefined_acl. Ignoring");
+            }
         }
 
         if let Some(storage_class) = &self.default_storage_class {
@@ -561,14 +567,50 @@ impl GcsCore {
         self.send(req).await
     }
 
-    pub async fn gcs_initiate_multipart_upload(&self, path: &str) -> 
Result<Response<Buffer>> {
+    pub async fn gcs_initiate_multipart_upload(
+        &self,
+        path: &str,
+        op: &OpWrite,
+    ) -> Result<Response<Buffer>> {
         let p = build_abs_path(&self.root, path);
 
         let url = format!("{}/{}/{}?uploads", self.endpoint, self.bucket, p);
 
-        let mut req = Request::post(&url)
+        let mut builder = Request::post(&url)
             .header(CONTENT_LENGTH, 0)
-            .extension(Operation::Write)
+            .extension(Operation::Write);
+
+        if let Some(header_val) = op.content_disposition() {
+            builder = builder.header(CONTENT_DISPOSITION, header_val);
+        }
+
+        if let Some(header_val) = op.content_encoding() {
+            builder = builder.header(CONTENT_ENCODING, header_val);
+        }
+
+        if let Some(header_val) = op.content_type() {
+            builder = builder.header(CONTENT_TYPE, header_val);
+        }
+
+        if let Some(header_val) = op.cache_control() {
+            builder = builder.header(CACHE_CONTROL, header_val);
+        }
+
+        if let Some(metadata) = op.user_metadata() {
+            for (k, v) in metadata {
+                builder = builder.header(&format!("x-goog-meta-{k}"), v);
+            }
+        }
+
+        if let Some(acl) = self.predefined_acl.as_ref() {
+            if let Some(predefined_acl_in_xml_spec) = 
predefined_acl_to_xml_header(acl) {
+                builder = builder.header(X_GOOG_ACL, 
predefined_acl_in_xml_spec);
+            } else {
+                log::warn!("Unrecognized predefined_acl. Ignoring");
+            }
+        }
+
+        let mut req = builder
             .body(Buffer::new())
             .map_err(new_request_build_error)?;
 
@@ -709,6 +751,19 @@ impl GcsCore {
     }
 }
 
+// https://cloud.google.com/storage/docs/xml-api/reference-headers#xgoogacl
+fn predefined_acl_to_xml_header(predefined_acl: &str) -> Option<&'static str> {
+    match predefined_acl {
+        "projectPrivate" => Some("project-private"),
+        "private" => Some("private"),
+        "bucketOwnerRead" => Some("bucket-owner-read"),
+        "bucketOwnerFullControl" => Some("bucket-owner-full-control"),
+        "publicRead" => Some("public-read"),
+        "authenticatedRead" => Some("authenticated-read"),
+        _ => None,
+    }
+}
+
 #[derive(Debug, Serialize)]
 #[serde(default, rename_all = "camelCase")]
 pub struct InsertRequestMetadata<'a> {
diff --git a/core/src/services/gcs/writer.rs b/core/src/services/gcs/writer.rs
index c2b763c20..377762fdb 100644
--- a/core/src/services/gcs/writer.rs
+++ b/core/src/services/gcs/writer.rs
@@ -74,7 +74,7 @@ impl oio::MultipartWrite for GcsWriter {
     async fn initiate_part(&self) -> Result<String> {
         let resp = self
             .core
-            .gcs_initiate_multipart_upload(&percent_encode_path(&self.path))
+            .gcs_initiate_multipart_upload(&percent_encode_path(&self.path), 
&self.op)
             .await?;
 
         if !resp.status().is_success() {

Reply via email to