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 same_site;
13mod serialize;
14
15#[cfg(feature = "percent-encode")]
16mod encoding;
17
18pub use builder::CookieBuilder;
19use expires::Expires;
20
21use crate::{SameSite, util::TinyStr};
22
23#[derive(Default, Clone)]
25pub struct Cookie {
26 raw_value: Option<Box<str>>,
29 name: TinyStr,
30 value: TinyStr,
31 expires: Expires,
32 max_age: Option<u64>,
33 domain: Option<TinyStr>,
34 path: Option<TinyStr>,
35 secure: bool,
36 http_only: bool,
37 partitioned: bool,
38 same_site: Option<SameSite>,
39}
40
41impl Cookie {
42 pub fn new<N, V>(name: N, value: V) -> Cookie
56 where
57 N: Into<Cow<'static, str>>,
58 V: Into<Cow<'static, str>>,
59 {
60 Self::new_inner(TinyStr::from(name), TinyStr::from(value))
61 }
62
63 pub fn remove<N>(name: N) -> Cookie
88 where
89 N: Into<Cow<'static, str>>,
90 {
91 Cookie::new(name, "").into_remove()
92 }
93
94 pub(crate) fn into_remove(mut self) -> Self {
95 self.set_expires(Expires::remove());
96 self.set_max_age_secs(0);
97 self.set_value("");
98 self
99 }
100
101 fn new_inner(name: TinyStr, value: TinyStr) -> Cookie {
102 Cookie {
103 name,
104 value,
105 ..Default::default()
106 }
107 }
108
109 pub fn build<N, V>(name: N, value: V) -> CookieBuilder
125 where
126 N: Into<Cow<'static, str>>,
127 V: Into<Cow<'static, str>>,
128 {
129 CookieBuilder::new(name, value)
130 }
131
132 pub fn named<N>(name: N) -> CookieBuilder
145 where
146 N: Into<Cow<'static, str>>,
147 {
148 Self::build(name, "")
149 }
150
151 #[inline]
153 pub fn name(&self) -> &str {
154 self.name.as_str(self.raw_value.as_deref())
155 }
156
157 #[inline]
159 pub fn set_name<N: Into<Cow<'static, str>>>(&mut self, name: N) {
160 self.name = TinyStr::from(name)
161 }
162
163 #[inline]
165 pub fn value(&self) -> &str {
166 self.value.as_str(self.raw_value.as_deref())
167 }
168
169 #[inline]
171 pub fn set_value<V: Into<Cow<'static, str>>>(&mut self, value: V) {
172 self.value = TinyStr::from(value)
173 }
174
175 #[inline]
177 pub fn set_expires<E: Into<Expires>>(&mut self, expires: E) {
178 self.expires = expires.into();
179 }
180
181 #[inline]
186 pub fn max_age(&self) -> Option<Duration> {
187 self.max_age.map(Duration::from_secs)
188 }
189
190 #[inline]
192 pub fn max_age_secs(&self) -> Option<u64> {
193 self.max_age
194 }
195
196 #[inline]
198 pub fn set_max_age(&mut self, max_age: Duration) {
199 self.set_max_age_secs(max_age.as_secs());
200 }
201
202 #[inline]
204 pub fn set_max_age_secs(&mut self, max_age_secs: u64) {
205 self.max_age = Some(max_age_secs);
206 }
207
208 #[inline]
210 pub fn unset_max_age(&mut self) {
211 self.max_age = None;
212 }
213
214 #[inline]
216 pub fn domain(&self) -> Option<&str> {
217 self.domain
218 .as_ref()
219 .map(|s| s.as_str(self.raw_value.as_deref()))
220 }
221
222 pub(crate) fn domain_sanitized(&self) -> Option<&str> {
223 self.domain().map(|d| d.strip_prefix('.').unwrap_or(d))
224 }
225
226 #[inline]
228 pub fn set_domain<D: Into<Cow<'static, str>>>(&mut self, domain: D) {
229 self.domain = Some(TinyStr::from(domain))
230 }
231
232 #[inline]
234 pub fn unset_domain(&mut self) {
235 self.domain = None
236 }
237
238 #[inline]
240 pub fn path(&self) -> Option<&str> {
241 self.path
242 .as_ref()
243 .map(|val| val.as_str(self.raw_value.as_deref()))
244 }
245
246 #[inline]
248 pub fn set_path<D: Into<Cow<'static, str>>>(&mut self, path: D) {
249 self.path = Some(TinyStr::from(path))
250 }
251
252 #[inline]
254 pub fn unset_path(&mut self) {
255 self.path = None
256 }
257
258 #[inline]
260 pub fn secure(&self) -> bool {
261 self.secure
262 }
263
264 #[inline]
266 pub fn set_secure(&mut self, secure: bool) {
267 self.secure = secure
268 }
269
270 #[inline]
272 pub fn http_only(&self) -> bool {
273 self.http_only
274 }
275
276 #[inline]
278 pub fn set_http_only(&mut self, http_only: bool) {
279 self.http_only = http_only
280 }
281
282 #[inline]
284 pub fn partitioned(&self) -> bool {
285 self.partitioned
286 }
287
288 #[inline]
290 pub fn set_partitioned(&mut self, partitioned: bool) {
291 self.partitioned = partitioned;
292 }
293
294 #[inline]
296 pub fn same_site(&self) -> Option<SameSite> {
297 self.same_site
298 }
299
300 #[inline]
302 pub fn set_same_site<S: Into<Option<SameSite>>>(&mut self, same_site: S) {
303 self.same_site = same_site.into();
304 }
305}
306
307impl fmt::Display for Cookie {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 write!(f, "{:?}", self)
310 }
311}
312
313impl Debug for Cookie {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 let mut debug = f.debug_struct("Cookie");
316
317 debug
318 .field("name", &self.name())
319 .field("value", &self.value())
320 .field("max_age", &self.max_age())
321 .field("domain", &self.domain())
322 .field("path", &self.path())
323 .field("secure", &self.secure())
324 .field("http_only", &self.http_only())
325 .field("partitioned", &self.partitioned())
326 .field("expires", &self.expires)
327 .finish()
328 }
329}
330
331impl PartialEq<Cookie> for Cookie {
332 fn eq(&self, other: &Cookie) -> bool {
333 if self.name() != other.name()
334 || self.value() != other.value()
335 || self.secure() != other.secure()
336 || self.http_only() != other.http_only()
337 || self.partitioned() != other.partitioned()
338 || self.max_age() != other.max_age()
339 || self.same_site() != other.same_site()
340 || self.expires != other.expires
341 {
342 return false;
343 }
344
345 if !opt_str_eq(self.domain_sanitized(), other.domain_sanitized()) {
346 return false;
347 }
348
349 if !opt_str_eq(self.path(), other.path()) {
350 return false;
351 }
352
353 true
354 }
355}
356
357fn opt_str_eq(left: Option<&str>, right: Option<&str>) -> bool {
358 match (left, right) {
359 (None, None) => true,
360 (Some(l), Some(r)) => l.eq_ignore_ascii_case(r),
361 _ => false,
362 }
363}