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") => cookie.set_max_age(
125                Duration::from_secs(max_age.ok_or(MissingPair::MaxAge)?.parse()?),
126            ),
127            _ if name.eq_ignore_ascii_case("Partitioned") => cookie.set_partitioned(true),
128            path if name.eq_ignore_ascii_case("Path") => {
129                cookie.set_path(path.ok_or(MissingPair::Path)?)
130            }
131            _ if name.eq_ignore_ascii_case("Secure") => cookie.set_secure(true),
132            same_site if name.eq_ignore_ascii_case("SameSite") => {
133                cookie.set_same_site(SameSite::parse(same_site.ok_or(MissingPair::SameSite)?)?)
134            }
135            _ if strict => return Err(ParseError::UnknownAttribute(name.to_owned())),
136            _ => continue,
137        }
138    }
139
140    Ok(cookie)
141}
142
143impl SameSite {
144    /// Parses a `SameSite` attribute value.
145    ///
146    /// # Arguments
147    /// - `value`: The string representation of the `SameSite` value.
148    ///
149    /// # Returns
150    /// A `Result` containing the parsed `SameSite` or a `ParseSameSiteError`.
151    ///
152    /// # Example
153    /// ```
154    /// use cookie_rs::prelude::*;
155    ///
156    /// let same_site = SameSite::parse("Strict").unwrap();
157    /// assert_eq!(same_site, SameSite::Strict);
158    /// ```
159    pub fn parse(value: &str) -> Result<Self, ParseSameSiteError> {
160        if value.eq_ignore_ascii_case("strict") {
161            Ok(Self::Strict)
162        } else if value.eq_ignore_ascii_case("lax") {
163            Ok(Self::Lax)
164        } else if value.eq_ignore_ascii_case("none") {
165            Ok(Self::None)
166        } else {
167            Err(ParseSameSiteError::UnknownValue(value.to_owned()))
168        }
169    }
170}