Skip to main content

threadloom_dom/
storage.rs

1use serde::{de::DeserializeOwned, Serialize};
2
3// Minimal URL percent-encode/decode so the cached JSON is always a safe
4// cookie value ([A-Za-z0-9%-] only). Works on both wasm and desktop targets.
5pub fn url_encode(s: &str) -> String {
6    const SAFE: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~";
7    let mut out = String::with_capacity(s.len());
8    for b in s.bytes() {
9        if SAFE.contains(&b) {
10            out.push(b as char);
11        } else {
12            out.push('%');
13            out.push(char::from_digit((b >> 4) as u32, 16).unwrap().to_ascii_uppercase());
14            out.push(char::from_digit((b & 0x0f) as u32, 16).unwrap().to_ascii_uppercase());
15        }
16    }
17    out
18}
19
20pub fn url_decode(s: &str) -> Option<String> {
21    let bytes = s.as_bytes();
22    let mut out: Vec<u8> = Vec::with_capacity(s.len());
23    let mut i = 0;
24    while i < bytes.len() {
25        if bytes[i] == b'%' && i + 2 < bytes.len() {
26            let hi = char::from(bytes[i + 1]).to_digit(16)?;
27            let lo = char::from(bytes[i + 2]).to_digit(16)?;
28            out.push(((hi << 4) | lo) as u8);
29            i += 3;
30        } else {
31            out.push(bytes[i]);
32            i += 1;
33        }
34    }
35    String::from_utf8(out).ok()
36}
37
38pub fn set_json_cookie<T: Serialize>(name: &str, value: &T, max_age: u32) {
39    if let Ok(json) = serde_json::to_string(value) {
40        let enc = url_encode(&json);
41        crate::set_cookie!(name, enc, max_age);
42    }
43}
44
45pub fn get_json_cookie<T: DeserializeOwned>(name: &str) -> Option<T> {
46    let raw = crate::get_cookie!(name)?;
47    if raw.is_empty() {
48        return None;
49    }
50    let json = url_decode(&raw)?;
51    serde_json::from_str(&json).ok()
52}