fix: URL-encode query params in client and pass --meta to server on save

- URL-encode all query parameter keys and values in get_json_with_query
  and post_stream. Previously raw JSON like {"project":"alpha"} was
  sent unencoded, causing 'invalid uri character' errors.
- Pass settings.meta (key=value pairs) from client save to server as
  metadata. Previously always passed empty HashMap, so --meta was
  silently ignored in client save mode.
This commit is contained in:
2026-03-14 19:16:39 -03:00
parent 0658d8378f
commit 886ac98b21
2 changed files with 25 additions and 3 deletions

View File

@@ -15,6 +15,24 @@ pub struct ItemInfo {
pub metadata: HashMap<String, String>, pub metadata: HashMap<String, String>,
} }
/// Percent-encode a value for use in a URL query string.
fn url_encode(s: &str) -> String {
let mut result = String::with_capacity(s.len() * 3);
for byte in s.bytes() {
match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
result.push(byte as char);
}
_ => {
result.push('%');
result.push(char::from_digit((byte >> 4) as u32, 16).unwrap());
result.push(char::from_digit((byte & 0xF) as u32, 16).unwrap());
}
}
}
result
}
pub struct KeepClient { pub struct KeepClient {
base_url: String, base_url: String,
agent: ureq::Agent, agent: ureq::Agent,
@@ -113,7 +131,7 @@ impl KeepClient {
if i > 0 { if i > 0 {
url.push('&'); url.push('&');
} }
url.push_str(&format!("{key}={value}")); url.push_str(&format!("{}={}", url_encode(key), url_encode(value)));
} }
} }
let mut req = self.agent.get(&url); let mut req = self.agent.get(&url);
@@ -166,7 +184,7 @@ impl KeepClient {
if i > 0 { if i > 0 {
url.push('&'); url.push('&');
} }
url.push_str(&format!("{key}={value}")); url.push_str(&format!("{}={}", url_encode(key), url_encode(value)));
} }
} }

View File

@@ -225,7 +225,11 @@ fn main() -> Result<(), Error> {
return match mode { return match mode {
KeepModes::Save => { KeepModes::Save => {
let metadata = std::collections::HashMap::new(); let metadata: std::collections::HashMap<String, String> = settings
.meta
.iter()
.filter_map(|(k, v)| v.as_ref().map(|val| (k.clone(), val.clone())))
.collect();
keep::modes::client::save::mode(&client, &mut cmd, &settings, tags, metadata) keep::modes::client::save::mode(&client, &mut cmd, &settings, tags, metadata)
} }
KeepModes::Get => keep::modes::client::get::mode( KeepModes::Get => keep::modes::client::get::mode(