From a893568079d393aead7f4a4f04f619fb9724c7d7 Mon Sep 17 00:00:00 2001 From: Aleksandar Tomic Date: Mon, 11 May 2026 12:42:50 +0000 Subject: [PATCH] feat(azure): expose `split_sas` for custom SAS credential providers `split_sas` parses an Azure Shared Access Signature string into the `Vec<(String, String)>` query pairs expected by `AzureCredential::SASToken`. It is what `MicrosoftAzureBuilder` runs internally when a raw SAS string is supplied via `AzureConfigKey::SasKey`. Users implementing their own `CredentialProvider` that fetches SAS tokens at request time (e.g. tokens refreshed from a secret manager or coordination service) currently have to reimplement this parser to feed pairs into `AzureCredential::SASToken`. Reimplementations are easy to get subtly wrong: in particular, using `form_urlencoded` decodes `+` as space, which corrupts base64-encoded SAS signatures. Make `split_sas` public, change its return type to the public `crate::Result>` (the inner `Error` variants already convert into `crate::Error` via the existing `From` impl, so the two existing internal call sites are unchanged), document it, and re-export it from `object_store::azure`. Add tests for the empty/`?` edge cases and the missing-`=` error path. --- src/azure/builder.rs | 24 +++++++++++++++++++++++- src/azure/mod.rs | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/azure/builder.rs b/src/azure/builder.rs index afd7a0ac..1f57fac5 100644 --- a/src/azure/builder.rs +++ b/src/azure/builder.rs @@ -1074,7 +1074,8 @@ fn url_from_env(env_name: &str, default_url: &str) -> Result { Ok(url) } -fn split_sas(sas: &str) -> Result, Error> { +/// Parse a SAS token string into the query pairs expected by [`AzureCredential::SASToken`]. +pub fn split_sas(sas: &str) -> Result> { let sas = percent_decode_str(sas) .decode_utf8() .map_err(|source| Error::DecodeSasKey { source })?; @@ -1273,6 +1274,27 @@ mod tests { assert_eq!(expected, pairs); } + #[test] + fn azure_test_split_sas_trims_leading_question_mark_and_skips_empties() { + let pairs = split_sas("?&sv=2021-10-04& &sp=r").unwrap(); + assert_eq!( + pairs, + vec![ + ("sv".to_string(), "2021-10-04".to_string()), + ("sp".to_string(), "r".to_string()), + ], + ); + } + + #[test] + fn azure_test_split_sas_rejects_missing_equals() { + let err = split_sas("sv=2021-10-04&bogus").unwrap_err(); + assert!( + err.to_string().contains("Missing component"), + "unexpected error: {err}", + ); + } + #[test] fn azure_test_client_opts() { let key = "AZURE_PROXY_URL"; diff --git a/src/azure/mod.rs b/src/azure/mod.rs index e6b9b9c2..1429bec9 100644 --- a/src/azure/mod.rs +++ b/src/azure/mod.rs @@ -53,7 +53,7 @@ pub type AzureCredentialProvider = Arc