cookie_monster/cookie/
mod.rs1use std::{
2 borrow::Cow,
3 fmt::{self, Debug},
4 time::Duration,
5};
6
7mod builder;
8mod domain;
9pub(crate) mod expires;
10mod parse;
11mod path;
12pub(crate) mod prefix;
13pub(crate) mod same_site;
14mod serialize;
15
16#[cfg(feature = "percent-encode")]
17mod encoding;
18
19pub use builder::CookieBuilder;
20use expires::Expires;
21use prefix::CookiePrefix;
22
23use crate::{SameSite, util::TinyStr};
24
25#[derive(Default, Clone)]
27pub struct Cookie {
28 raw_value: Option<Box<str>>,
31 name: TinyStr,
32 value: TinyStr,
33 expires: Expires,
34 max_age: Option<u64>,
35 domain: Option<TinyStr>,
36 path: Option<TinyStr>,
37 secure: bool,
38 http_only: bool,
39 partitioned: bool,
40 same_site: Option<SameSite>,
41 prefix: Option<CookiePrefix>,
43}
44
45impl Cookie {
46 pub fn new<N, V>(name: N, value: V) -> Cookie
60 where
61 N: Into<Cow<'static, str>>,
62 V: Into<Cow<'static, str>>,
63 {
64 Self::new_inner(TinyStr::from(name), TinyStr::from(value))
65 }
66
67 pub fn remove<N>(name: N) -> Cookie
92 where
93 N: Into<Cow<'static, str>>,
94 {
95 Cookie::new(name, "").into_remove()
96 }
97
98 pub(crate) fn into_remove(mut self) -> Self {
99 self.set_expires(Expires::remove());
100 self.set_max_age_secs(0);
101 self.set_value("");
102 self
103 }
104
105 fn new_inner(name: TinyStr, value: TinyStr) -> Cookie {
106 Cookie {
107 name,
108 value,
109 ..Default::default()
110 }
111 }
112
113 pub fn build<N, V>(name: N, value: V) -> CookieBuilder
129 where
130 N: Into<Cow<'static, str>>,
131 V: Into<Cow<'static, str>>,
132 {
133 CookieBuilder::new(name, value)
134 }
135
136 pub fn named<N>(name: N) -> CookieBuilder
149 where
150 N: Into<Cow<'static, str>>,
151 {
152 Self::build(name, "")
153 }
154
155 #[inline]
157 pub fn name(&self) -> &str {
158 self.name.as_str(self.raw_value.as_deref())
159 }
160
161 #[inline]
166 pub fn set_name<N: Into<Cow<'static, str>>>(&mut self, name: N) {
167 self.name = TinyStr::from(name)
168 }
169
170 #[inline]
172 pub fn value(&self) -> &str {
173 self.value.as_str(self.raw_value.as_deref())
174 }
175
176 #[inline]
178 pub fn set_value<V: Into<Cow<'static, str>>>(&mut self, value: V) {
179 self.value = TinyStr::from(value)
180 }
181
182 #[inline]
184 pub fn set_expires<E: Into<Expires>>(&mut self, expires: E) {
185 self.expires = expires.into();
186 }
187
188 #[inline]
193 pub fn max_age(&self) -> Option<Duration> {
194 self.max_age.map(Duration::from_secs)
195 }
196
197 #[inline]
199 pub fn max_age_secs(&self) -> Option<u64> {
200 self.max_age
201 }
202
203 #[inline]
205 pub fn set_max_age(&mut self, max_age: Duration) {
206 self.set_max_age_secs(max_age.as_secs());
207 }
208
209 #[inline]
211 pub fn set_max_age_secs(&mut self, max_age_secs: u64) {
212 self.max_age = Some(max_age_secs);
213 }
214
215 #[inline]
217 pub fn unset_max_age(&mut self) {
218 self.max_age = None;
219 }
220
221 #[inline]
223 pub fn domain(&self) -> Option<&str> {
224 self.domain
225 .as_ref()
226 .map(|s| s.as_str(self.raw_value.as_deref()))
227 }
228
229 pub(crate) fn domain_sanitized(&self) -> Option<&str> {
230 self.domain().map(|d| d.strip_prefix('.').unwrap_or(d))
231 }
232
233 #[inline]
235 pub fn set_domain<D: Into<Cow<'static, str>>>(&mut self, domain: D) {
236 self.domain = Some(TinyStr::from(domain))
237 }
238
239 #[inline]
241 pub fn unset_domain(&mut self) {
242 self.domain = None
243 }
244
245 #[inline]
247 pub fn path(&self) -> Option<&str> {
248 self.path
249 .as_ref()
250 .map(|val| val.as_str(self.raw_value.as_deref()))
251 }
252
253 #[inline]
255 pub fn set_path<D: Into<Cow<'static, str>>>(&mut self, path: D) {
256 self.path = Some(TinyStr::from(path))
257 }
258
259 #[inline]
261 pub fn unset_path(&mut self) {
262 self.path = None
263 }
264
265 #[inline]
267 pub fn is_secure(&self) -> bool {
268 self.secure
269 }
270
271 #[inline]
273 pub fn set_secure(&mut self, secure: bool) {
274 self.secure = secure
275 }
276
277 #[inline]
279 pub fn is_http_only(&self) -> bool {
280 self.http_only
281 }
282
283 #[inline]
285 pub fn set_http_only(&mut self, http_only: bool) {
286 self.http_only = http_only
287 }
288
289 #[inline]
291 pub fn is_partitioned(&self) -> bool {
292 self.partitioned
293 }
294
295 #[inline]
297 pub fn set_partitioned(&mut self, partitioned: bool) {
298 self.partitioned = partitioned;
299 }
300
301 #[inline]
303 pub fn same_site(&self) -> Option<SameSite> {
304 self.same_site
305 }
306
307 #[inline]
309 pub fn set_same_site<S: Into<Option<SameSite>>>(&mut self, same_site: S) {
310 self.same_site = same_site.into();
311 }
312}
313
314impl fmt::Display for Cookie {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 write!(f, "{:?}", self)
317 }
318}
319
320impl Debug for Cookie {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 let mut debug = f.debug_struct("Cookie");
323
324 debug
325 .field("name", &self.name())
326 .field("value", &self.value())
327 .field("max_age", &self.max_age())
328 .field("domain", &self.domain())
329 .field("path", &self.path())
330 .field("secure", &self.is_secure())
331 .field("http_only", &self.is_http_only())
332 .field("partitioned", &self.is_partitioned())
333 .field("expires", &self.expires)
334 .field("same_site", &self.same_site)
335 .field("prefix", &self.prefix)
336 .finish()
337 }
338}
339
340impl PartialEq<Cookie> for Cookie {
341 fn eq(&self, other: &Cookie) -> bool {
342 if self.name() != other.name()
343 || self.value() != other.value()
344 || self.is_secure() != other.is_secure()
345 || self.is_http_only() != other.is_http_only()
346 || self.is_partitioned() != other.is_partitioned()
347 || self.max_age() != other.max_age()
348 || self.same_site() != other.same_site()
349 || self.expires != other.expires
350 || self.prefix != other.prefix
351 {
352 return false;
353 }
354
355 if !opt_str_eq(self.domain_sanitized(), other.domain_sanitized()) {
356 return false;
357 }
358
359 if !opt_str_eq(self.path(), other.path()) {
360 return false;
361 }
362
363 true
364 }
365}
366
367fn opt_str_eq(left: Option<&str>, right: Option<&str>) -> bool {
368 match (left, right) {
369 (None, None) => true,
370 (Some(l), Some(r)) => l.eq_ignore_ascii_case(r),
371 _ => false,
372 }
373}