openauth_core/cookies/
chunked.rs1use std::collections::BTreeMap;
2
3use super::parse::parse_cookies;
4use super::types::{Cookie, CookieOptions};
5
6const ALLOWED_COOKIE_SIZE: usize = 4096;
7const ESTIMATED_EMPTY_COOKIE_SIZE: usize = 200;
8const CHUNK_SIZE: usize = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
9
10#[derive(Debug, Clone)]
11pub struct ChunkedCookieStore {
12 cookie_name: String,
13 cookie_options: CookieOptions,
14 chunks: BTreeMap<String, String>,
15 direct_value: Option<String>,
16}
17
18impl ChunkedCookieStore {
19 pub fn new(
20 cookie_name: impl Into<String>,
21 cookie_options: CookieOptions,
22 header: &str,
23 ) -> Self {
24 let cookie_name = cookie_name.into();
25 let parsed = parse_cookies(header);
26 let direct_value = parsed.get(&cookie_name).cloned();
27 let prefix = format!("{cookie_name}.");
28 let chunks = parsed
29 .into_iter()
30 .filter(|(name, _)| name.starts_with(&prefix))
31 .collect();
32 Self {
33 cookie_name,
34 cookie_options,
35 chunks,
36 direct_value,
37 }
38 }
39
40 pub fn value(&self) -> Option<String> {
41 if let Some(value) = &self.direct_value {
42 return Some(value.clone());
43 }
44 if self.chunks.is_empty() {
45 return None;
46 }
47 let mut chunks = self
48 .chunks
49 .iter()
50 .filter_map(|(name, value)| chunk_index(name).map(|index| (index, value)))
51 .collect::<Vec<_>>();
52 chunks.sort_by_key(|(index, _)| *index);
53 Some(
54 chunks
55 .into_iter()
56 .map(|(_, value)| value.as_str())
57 .collect(),
58 )
59 }
60
61 pub fn chunk(&self, value: &str) -> Vec<Cookie> {
62 if value.len() <= CHUNK_SIZE {
63 return vec![Cookie {
64 name: self.cookie_name.clone(),
65 value: value.to_owned(),
66 attributes: self.cookie_options.clone(),
67 }];
68 }
69 value
70 .as_bytes()
71 .chunks(CHUNK_SIZE)
72 .enumerate()
73 .map(|(index, chunk)| Cookie {
74 name: format!("{}.{}", self.cookie_name, index),
75 value: String::from_utf8_lossy(chunk).into_owned(),
76 attributes: self.cookie_options.clone(),
77 })
78 .collect()
79 }
80
81 pub fn clean(&self) -> Vec<Cookie> {
82 self.chunks
83 .keys()
84 .map(|name| {
85 let mut attributes = self.cookie_options.clone();
86 attributes.max_age = Some(0);
87 Cookie {
88 name: name.clone(),
89 value: String::new(),
90 attributes,
91 }
92 })
93 .collect()
94 }
95}
96
97fn chunk_index(cookie_name: &str) -> Option<usize> {
98 cookie_name.rsplit_once('.')?.1.parse().ok()
99}