Skip to main content

cookie_rs/cookie/
parse.rs

1//! Parsing utilities for `Cookie`.
2//!
3//! This module provides functionality for parsing `Cookie` instances from strings.
4//! It supports both strict and lenient parsing modes and handles attributes such as
5//! `Domain`, `Path`, `Max-Age`, `Secure`, and more.
6//!
7//! # Features
8//! - Flexible parsing with `parse` and `parse_strict` methods.
9//! - Detailed error handling using `ParseError` and `ParseSameSiteError`.
10//! - Support for common cookie attributes.
11//!
12//! # Example
13//! ```
14//! use cookie_rs::prelude::*;
15//!
16//! let cookie = Cookie::parse("session=abc123; Path=/; Secure").unwrap();
17//! assert_eq!(cookie.name(), "session");
18//! assert_eq!(cookie.value(), "abc123");
19//! assert_eq!(cookie.path(), Some("/"));
20//! assert_eq!(cookie.secure(), Some(true));
21//! ```
22use std::borrow::Cow;
23use std::time::Duration;
24
25use super::Cookie;
26use super::SameSite;
27use crate::StringPrison;
28
29pub use self::error::*;
30
31pub mod error;
32
33impl<'a> Cookie<'a> {
34    /// Parses a cookie from a string in a lenient mode.
35    ///
36    /// In lenient mode, unknown attributes are ignored.
37    ///
38    /// # Arguments
39    /// - `value`: The string representation of the cookie.
40    ///
41    /// # Returns
42    /// A `Result` containing the parsed `Cookie` or a `ParseError`.
43    ///
44    /// # Example
45    /// ```
46    /// use cookie_rs::prelude::*;
47    ///
48    /// let cookie = Cookie::parse("session=abc123; Secure").unwrap();
49    /// assert_eq!(cookie.name(), "session");
50    /// assert_eq!(cookie.value(), "abc123");
51    /// assert_eq!(cookie.secure(), Some(true));
52    /// ```
53    pub fn parse<V: Into<Cow<'a, str>>>(value: V) -> Result<Self, ParseError> {
54        Self::inner_parse(value.into(), false)
55    }
56
57    /// Parses a cookie from a string in a strict mode.
58    ///
59    /// In strict mode, unknown attributes cause an error.
60    ///
61    /// # Arguments
62    /// - `value`: The string representation of the cookie.
63    ///
64    /// # Returns
65    /// A `Result` containing the parsed `Cookie` or a `ParseError`.
66    ///
67    /// # Example
68    /// ```
69    /// use cookie_rs::prelude::*;
70    ///
71    /// let result = Cookie::parse_strict("session=abc123; UnknownAttr");
72    /// assert!(result.is_err());
73    /// ```
74    pub fn parse_strict<V: Into<Cow<'a, str>>>(value: V) -> Result<Self, ParseError> {
75        Self::inner_parse(value.into(), true)
76    }
77
78    pub(crate) fn inner_parse(value: Cow<'a, str>, strict: bool) -> Result<Self, ParseError> {
79        let prison = StringPrison::new(value);
80
81        // SAFETY: prison and slice owned by the same struct
82        let str = unsafe { prison.get() };
83
84        let mut cookie = parse_cookie(str, strict)?;
85        cookie.prison = Some(prison);
86
87        Ok(cookie)
88    }
89}
90
91fn parse_cookie(str: &str, strict: bool) -> Result<Cookie<'_>, ParseError> {
92    let mut attributes = str.split(';');
93
94    let (name, value) = attributes
95        .next()
96        .expect("Missing any attributes")
97        .split_once('=')
98        .ok_or(MissingPair::NameValue)?;
99
100    let (name, value) = (name.trim(), value.trim());
101
102    if name.is_empty() {
103        return Err(ParseError::EmptyName);
104    }
105
106    let mut cookie = Cookie::new(name, value);
107
108    for attribute in attributes {
109        let mut pair = attribute.splitn(2, '=');
110
111        let (name, value) = (
112            pair.next().expect("missing any attribute name").trim(),
113            pair.next().map(|v| v.trim()),
114        );
115
116        match value {
117            domain if name.eq_ignore_ascii_case("Domain") => {
118                cookie.set_domain(domain.ok_or(MissingPair::Domain)?)
119            }
120            expires if name.eq_ignore_ascii_case("Expires") => {
121                cookie.set_expires(expires.ok_or(MissingPair::Expires)?)
122            }
123            _ if name.eq_ignore_ascii_case("HttpOnly") => cookie.set_http_only(true),
124            max_age if name.eq_ignore_ascii_case("Max-Age") => {
125                let secs: i64 = max_age.ok_or(MissingPair::MaxAge)?.parse()?;
126                cookie.set_max_age(Duration::from_secs(secs.max(0) as u64))
127            }
128            _ if name.eq_ignore_ascii_case("Partitioned") => cookie.set_partitioned(true),
129            path if name.eq_ignore_ascii_case("Path") => {
130                cookie.set_path(path.ok_or(MissingPair::Path)?)
131            }
132            _ if name.eq_ignore_ascii_case("Secure") => cookie.set_secure(true),
133            same_site if name.eq_ignore_ascii_case("SameSite") => {
134                cookie.set_same_site(SameSite::parse(same_site.ok_or(MissingPair::SameSite)?)?)
135            }
136            _ if strict => return Err(ParseError::UnknownAttribute(name.to_owned())),
137            _ => continue,
138        }
139    }
140
141    Ok(cookie)
142}
143
144impl SameSite {
145    /// Parses a `SameSite` attribute value.
146    ///
147    /// # Arguments
148    /// - `value`: The string representation of the `SameSite` value.
149    ///
150    /// # Returns
151    /// A `Result` containing the parsed `SameSite` or a `ParseSameSiteError`.
152    ///
153    /// # Example
154    /// ```
155    /// use cookie_rs::prelude::*;
156    ///
157    /// let same_site = SameSite::parse("Strict").unwrap();
158    /// assert_eq!(same_site, SameSite::Strict);
159    /// ```
160    pub fn parse(value: &str) -> Result<Self, ParseSameSiteError> {
161        if value.eq_ignore_ascii_case("strict") {
162            Ok(Self::Strict)
163        } else if value.eq_ignore_ascii_case("lax") {
164            Ok(Self::Lax)
165        } else if value.eq_ignore_ascii_case("none") {
166            Ok(Self::None)
167        } else {
168            Err(ParseSameSiteError::UnknownValue(value.to_owned()))
169        }
170    }
171}