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}