From 20de7624989392d5a3c569e935bd9b475aa540e2 Mon Sep 17 00:00:00 2001 From: zerone0x <39543393+zerone0x@users.noreply.github.com> Date: Sun, 5 Jul 2026 20:05:02 +0000 Subject: [PATCH] fix: preserve unreserved path ID characters Fixes #842 Co-Authored-By: bridge --- crates/google-workspace/src/validate.rs | 39 ++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/google-workspace/src/validate.rs b/crates/google-workspace/src/validate.rs index 32ef200f..55e9edd6 100644 --- a/crates/google-workspace/src/validate.rs +++ b/crates/google-workspace/src/validate.rs @@ -273,10 +273,35 @@ fn normalize_non_existing(path: &Path) -> Result { // ── URL encoding ────────────────────────────────────────────────────── /// Percent-encode a value for use as a single URL path segment (e.g., file ID, -/// calendar ID, message ID). All non-alphanumeric characters are encoded. +/// calendar ID, message ID). RFC 3986 unreserved characters (`A-Z`, `a-z`, +/// `0-9`, `-`, `.`, `_`, `~`) are left intact; everything else that can alter +/// URL structure is encoded. pub fn encode_path_segment(s: &str) -> String { - use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; - utf8_percent_encode(s, NON_ALPHANUMERIC).to_string() + use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; + + const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'#') + .add(b'%') + .add(b'<') + .add(b'>') + .add(b'?') + .add(b'`') + .add(b'{') + .add(b'}') + .add(b'/') + .add(b':') + .add(b';') + .add(b'=') + .add(b'@') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^') + .add(b'|'); + + utf8_percent_encode(s, PATH_SEGMENT_ENCODE_SET).to_string() } /// Percent-encode a value for use in URI path templates where `/` should stay @@ -509,7 +534,13 @@ mod tests { fn test_encode_path_segment_email() { let encoded = encode_path_segment("user@gmail.com"); assert!(!encoded.contains('@')); - assert!(!encoded.contains('.')); + assert!(encoded.contains('.')); + } + + #[test] + fn test_encode_path_segment_preserves_unreserved_script_id() { + let script_id = "1-ukYIb8XjT9KQlsj6_NbryM5UJXBwAMeY5GKtSBA05csXty3iY7DhO-P"; + assert_eq!(encode_path_segment(script_id), script_id); } #[test]