Skip to main content

openauth_core/cookies/
chunked.rs

1use 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}