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}