GCS Path Encoding#
OpenDAL's GCS service implementation uses a custom percent-encoding scheme for object path segments in HTTP request URIs. The encoding logic is centralized in uri.rs and applied consistently inside the core request-builder functions in core.rs.
Encoding Scheme#
The GCS-specific encode set (GCS_PATH_ENCODE_SET) is derived from NON_ALPHANUMERIC, keeping only -, _, ., and * unencoded. This is more aggressive than the standard encodeURIComponent (which additionally preserves !, ~, ', (, )) and intentionally encodes / β because GCS treats slashes in the JSON/XML API as regular characters, not path delimiters. The public entry point is percent_encode_path.
Where Encoding Is Applied#
Encoding happens once, inside each core request builder, after prepending the configured root to the path. Examples from core.rs:
| Operation | Core function | Encoding call |
|---|---|---|
| Read | gcs_get_object_request | percent_encode_path(&p) in JSON API URL |
| Stat | gcs_head_object_request | percent_encode_path(&p) |
| Write (single) | gcs_insert_object_request | percent_encode_path(&p) in name= query param |
| Delete | gcs_delete_object_request | percent_encode_path(&p) |
| Copy | gcs_copy_object | percent_encode_path(&source) / percent_encode_path(&dest) |
| List | gcs_list_objects | percent_encode_path(&p) in prefix= query param |
| Upload part | gcs_upload_part | percent_encode_path(&p) |
| Complete multipart | gcs_complete_multipart_upload | percent_encode_path(&p) |
| Abort multipart | gcs_abort_multipart_upload | percent_encode_path(&p) |
The presign (XML API) variants β gcs_get_object_xml_request, gcs_insert_object_xml_request, gcs_head_object_xml_request β use raw paths without calling percent_encode_path, relying on the HTTP client / signer to handle encoding for the XML API endpoint.
Known Bug: Double-Encoding in Writer (β Open as of 2026-07-20)#
writer.rs currently pre-encodes paths before passing them to core request builders, causing double-encoding :
write_oncecallsgcs_insert_object_request(&percent_encode_path(&self.path), β¦)β core then encodes again, so/β%2Fβ%252Finitiate_partsimilarly pre-encodes beforegcs_initiate_multipart_uploadwrite_partandcomplete_partpass the raw path, making them inconsistent withwrite_once/initiate_partwithin the same multipart flow
Effect: Any object key containing / (e.g. dir/file.parquet) is stored under an incorrectly double-encoded name. A subsequent read using the original path encodes once (correctly) β but the object was stored under a doubly-encoded name β resulting in NotFound (404). This has been reproduced against fsouza/fake-gcs-server with the Python binding.
Fix direction (PR #7927, ): Remove the percent_encode_path calls in writer.rs and pass &self.path directly to core request builders, matching the pattern used by write_part, complete_part, and the reader layer.
gcs_initiate_multipart_upload β Additional Gap#
gcs_initiate_multipart_upload builds its URL as {endpoint}/{bucket}/{p}?uploads without calling percent_encode_path . This means the multipart initiation uses a raw (unencoded) path while subsequent upload-part and complete calls use an encoded path. This is a separate gap from the writer double-encoding issue, and paths with special characters in multipart uploads via this function may behave differently from those in single-shot writes.
Key Files#
| File | Role |
|---|---|
core/src/services/gcs/uri.rs | Defines GCS_PATH_ENCODE_SET and percent_encode_path |
core/src/services/gcs/core.rs | All HTTP request builders; canonical point for encoding |
core/src/services/gcs/writer.rs | Multipart/single writer β currently double-encodes in write_once and initiate_part |