Skip to content

Commit a6cbf95

Browse files
authored
Merge pull request #265 from http-rs/clear-site-data
Add `cache::ClearSiteData` header
2 parents 1f8c4d6 + bedaf28 commit a6cbf95

File tree

5 files changed

+373
-1
lines changed

5 files changed

+373
-1
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::fmt::Display;
2+
use std::str::FromStr;
3+
4+
/// An HTTP `Clear-Site-Data` directive.
5+
///
6+
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data#Directives)
7+
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
8+
pub enum ClearDirective {
9+
/// Indicates that the server wishes to remove locally cached data (i.e. the
10+
/// browser cache, see HTTP caching) for the origin of the response URL.
11+
/// Depending on the browser, this might also clear out things like
12+
/// pre-rendered pages, script caches, WebGL shader caches, or address bar
13+
/// suggestions.
14+
Cache,
15+
/// Indicates that the server wishes to remove all cookies for the origin of
16+
/// the response URL. HTTP authentication credentials are also cleared out.
17+
/// This affects the entire registered domain, including subdomains. So
18+
/// https://example.com as well as https://stage.example.com, will have
19+
/// cookies cleared.
20+
Cookies,
21+
/// Indicates that the server wishes to remove all DOM storage for the origin
22+
/// of the response URL.
23+
Storage,
24+
/// Indicates that the server wishes to reload all browsing contexts for the
25+
/// origin of the response (Location.reload).
26+
ExecutionContexts,
27+
}
28+
29+
impl ClearDirective {
30+
/// Get the formatted string.
31+
pub fn as_str(&self) -> &'static str {
32+
match self {
33+
ClearDirective::Cache => r#""cache""#,
34+
ClearDirective::Cookies => r#""cookies""#,
35+
ClearDirective::Storage => r#""storage""#,
36+
ClearDirective::ExecutionContexts => r#""executionContexts""#,
37+
}
38+
}
39+
}
40+
41+
impl Display for ClearDirective {
42+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43+
write!(f, "{}", self.as_str())
44+
}
45+
}
46+
47+
impl FromStr for ClearDirective {
48+
type Err = std::string::ParseError;
49+
50+
fn from_str(s: &str) -> Result<Self, Self::Err> {
51+
match s {
52+
r#""cache""# => Ok(Self::Cache),
53+
r#""cookies""# => Ok(Self::Cookies),
54+
r#""storage""# => Ok(Self::Storage),
55+
r#""executionContexts""# => Ok(Self::ExecutionContexts),
56+
_ => todo!(),
57+
}
58+
}
59+
}

src/cache/clear_site_data/mod.rs

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
//! Clear browsing data (cookies, storage, cache) associated with the
2+
//! requesting website
3+
4+
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, CLEAR_SITE_DATA};
5+
6+
use std::fmt::{self, Debug, Write};
7+
use std::iter::Iterator;
8+
use std::option;
9+
use std::slice;
10+
use std::str::FromStr;
11+
12+
mod directive;
13+
14+
pub use directive::ClearDirective;
15+
16+
/// Clear browsing data (cookies, storage, cache) associated with the
17+
/// requesting website.
18+
///
19+
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data)
20+
///
21+
/// # Specifications
22+
///
23+
/// - [Clear Site Data](https://w3c.github.io/webappsec-clear-site-data/)
24+
///
25+
/// # Examples
26+
///
27+
/// ```
28+
/// # fn main() -> http_types::Result<()> {
29+
/// #
30+
/// use http_types::Response;
31+
/// use http_types::cache::{ClearSiteData, ClearDirective};
32+
///
33+
/// let mut entries = ClearSiteData::new();
34+
/// entries.push(ClearDirective::Cache);
35+
/// entries.push(ClearDirective::Cookies);
36+
///
37+
/// let mut res = Response::new(200);
38+
/// entries.apply(&mut res);
39+
///
40+
/// let entries = ClearSiteData::from_headers(res)?.unwrap();
41+
/// let mut entries = entries.iter();
42+
/// assert_eq!(entries.next().unwrap(), &ClearDirective::Cache);
43+
/// assert_eq!(entries.next().unwrap(), &ClearDirective::Cookies);
44+
/// #
45+
/// # Ok(()) }
46+
/// ```
47+
pub struct ClearSiteData {
48+
entries: Vec<ClearDirective>,
49+
wildcard: bool,
50+
}
51+
52+
impl ClearSiteData {
53+
/// Create a new instance of `ClearSiteData`.
54+
pub fn new() -> Self {
55+
Self {
56+
entries: vec![],
57+
wildcard: false,
58+
}
59+
}
60+
61+
/// Create a new instance from headers.
62+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
63+
let mut entries = vec![];
64+
let header_values = match headers.as_ref().get(CLEAR_SITE_DATA) {
65+
Some(headers) => headers,
66+
None => return Ok(None),
67+
};
68+
69+
let mut wildcard = false;
70+
for value in header_values {
71+
for part in value.as_str().trim().split(',') {
72+
let part = part.trim();
73+
if part == r#""*""# {
74+
wildcard = true;
75+
continue;
76+
}
77+
entries.push(ClearDirective::from_str(part)?);
78+
}
79+
}
80+
81+
Ok(Some(Self { entries, wildcard }))
82+
}
83+
84+
/// Sets the `If-Match` header.
85+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
86+
headers.as_mut().insert(CLEAR_SITE_DATA, self.value());
87+
}
88+
89+
/// Get the `HeaderName`.
90+
pub fn name(&self) -> HeaderName {
91+
CLEAR_SITE_DATA
92+
}
93+
94+
/// Get the `HeaderValue`.
95+
pub fn value(&self) -> HeaderValue {
96+
let mut output = String::new();
97+
for (n, etag) in self.entries.iter().enumerate() {
98+
match n {
99+
0 => write!(output, "{}", etag.to_string()).unwrap(),
100+
_ => write!(output, ", {}", etag.to_string()).unwrap(),
101+
};
102+
}
103+
104+
if self.wildcard {
105+
match output.len() {
106+
0 => write!(output, r#""*""#).unwrap(),
107+
_ => write!(output, r#", "*""#).unwrap(),
108+
};
109+
}
110+
111+
// SAFETY: the internal string is validated to be ASCII.
112+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
113+
}
114+
115+
/// Push a directive into the list of entries.
116+
pub fn push(&mut self, directive: impl Into<ClearDirective>) {
117+
self.entries.push(directive.into());
118+
}
119+
120+
/// Returns `true` if a wildcard directive was set.
121+
pub fn wildcard(&self) -> bool {
122+
self.wildcard
123+
}
124+
125+
/// Set the wildcard directive.
126+
pub fn set_wildcard(&mut self, wildcard: bool) {
127+
self.wildcard = wildcard
128+
}
129+
130+
/// An iterator visiting all server entries.
131+
pub fn iter(&self) -> Iter<'_> {
132+
Iter {
133+
inner: self.entries.iter(),
134+
}
135+
}
136+
137+
/// An iterator visiting all server entries.
138+
pub fn iter_mut(&mut self) -> IterMut<'_> {
139+
IterMut {
140+
inner: self.entries.iter_mut(),
141+
}
142+
}
143+
}
144+
145+
impl IntoIterator for ClearSiteData {
146+
type Item = ClearDirective;
147+
type IntoIter = IntoIter;
148+
149+
#[inline]
150+
fn into_iter(self) -> Self::IntoIter {
151+
IntoIter {
152+
inner: self.entries.into_iter(),
153+
}
154+
}
155+
}
156+
157+
impl<'a> IntoIterator for &'a ClearSiteData {
158+
type Item = &'a ClearDirective;
159+
type IntoIter = Iter<'a>;
160+
161+
#[inline]
162+
fn into_iter(self) -> Self::IntoIter {
163+
self.iter()
164+
}
165+
}
166+
167+
impl<'a> IntoIterator for &'a mut ClearSiteData {
168+
type Item = &'a mut ClearDirective;
169+
type IntoIter = IterMut<'a>;
170+
171+
#[inline]
172+
fn into_iter(self) -> Self::IntoIter {
173+
self.iter_mut()
174+
}
175+
}
176+
177+
/// A borrowing iterator over entries in `ClearSiteData`.
178+
#[derive(Debug)]
179+
pub struct IntoIter {
180+
inner: std::vec::IntoIter<ClearDirective>,
181+
}
182+
183+
impl Iterator for IntoIter {
184+
type Item = ClearDirective;
185+
186+
fn next(&mut self) -> Option<Self::Item> {
187+
self.inner.next()
188+
}
189+
190+
#[inline]
191+
fn size_hint(&self) -> (usize, Option<usize>) {
192+
self.inner.size_hint()
193+
}
194+
}
195+
196+
/// A lending iterator over entries in `ClearSiteData`.
197+
#[derive(Debug)]
198+
pub struct Iter<'a> {
199+
inner: slice::Iter<'a, ClearDirective>,
200+
}
201+
202+
impl<'a> Iterator for Iter<'a> {
203+
type Item = &'a ClearDirective;
204+
205+
fn next(&mut self) -> Option<Self::Item> {
206+
self.inner.next()
207+
}
208+
209+
#[inline]
210+
fn size_hint(&self) -> (usize, Option<usize>) {
211+
self.inner.size_hint()
212+
}
213+
}
214+
215+
/// A mutable iterator over entries in `ClearSiteData`.
216+
#[derive(Debug)]
217+
pub struct IterMut<'a> {
218+
inner: slice::IterMut<'a, ClearDirective>,
219+
}
220+
221+
impl<'a> Iterator for IterMut<'a> {
222+
type Item = &'a mut ClearDirective;
223+
224+
fn next(&mut self) -> Option<Self::Item> {
225+
self.inner.next()
226+
}
227+
228+
#[inline]
229+
fn size_hint(&self) -> (usize, Option<usize>) {
230+
self.inner.size_hint()
231+
}
232+
}
233+
234+
impl ToHeaderValues for ClearSiteData {
235+
type Iter = option::IntoIter<HeaderValue>;
236+
fn to_header_values(&self) -> crate::Result<Self::Iter> {
237+
// A HeaderValue will always convert into itself.
238+
Ok(self.value().to_header_values().unwrap())
239+
}
240+
}
241+
242+
impl Debug for ClearSiteData {
243+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244+
let mut list = f.debug_list();
245+
for directive in &self.entries {
246+
list.entry(directive);
247+
}
248+
list.finish()
249+
}
250+
}
251+
252+
#[cfg(test)]
253+
mod test {
254+
use crate::cache::{ClearDirective, ClearSiteData};
255+
use crate::Response;
256+
257+
#[test]
258+
fn smoke() -> crate::Result<()> {
259+
let mut entries = ClearSiteData::new();
260+
entries.push(ClearDirective::Cache);
261+
entries.push(ClearDirective::Cookies);
262+
263+
let mut res = Response::new(200);
264+
entries.apply(&mut res);
265+
266+
let entries = ClearSiteData::from_headers(res)?.unwrap();
267+
let mut entries = entries.iter();
268+
assert_eq!(entries.next().unwrap(), &ClearDirective::Cache);
269+
assert_eq!(entries.next().unwrap(), &ClearDirective::Cookies);
270+
Ok(())
271+
}
272+
273+
#[test]
274+
fn wildcard() -> crate::Result<()> {
275+
let mut entries = ClearSiteData::new();
276+
entries.push(ClearDirective::Cache);
277+
entries.set_wildcard(true);
278+
279+
let mut res = Response::new(200);
280+
entries.apply(&mut res);
281+
282+
let entries = ClearSiteData::from_headers(res)?.unwrap();
283+
assert_eq!(entries.wildcard(), true);
284+
let mut entries = entries.iter();
285+
assert_eq!(entries.next().unwrap(), &ClearDirective::Cache);
286+
Ok(())
287+
}
288+
289+
#[test]
290+
fn parse_quotes_correctly() -> crate::Result<()> {
291+
let mut res = Response::new(200);
292+
res.insert_header("clear-site-data", r#""cookies""#);
293+
294+
let entries = ClearSiteData::from_headers(res)?.unwrap();
295+
assert_eq!(entries.wildcard(), false);
296+
let mut entries = entries.iter();
297+
assert_eq!(entries.next().unwrap(), &ClearDirective::Cookies);
298+
299+
let mut res = Response::new(200);
300+
res.insert_header("clear-site-data", r#""*""#);
301+
302+
let entries = ClearSiteData::from_headers(res)?.unwrap();
303+
assert_eq!(entries.wildcard(), true);
304+
let mut entries = entries.iter();
305+
assert_eq!(entries.next(), None);
306+
Ok(())
307+
}
308+
}

src/cache/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
1111
mod age;
1212
mod cache_control;
13+
mod clear_site_data;
1314
mod expires;
1415

1516
pub use age::Age;
1617
pub use cache_control::CacheControl;
1718
pub use cache_control::CacheDirective;
19+
pub use clear_site_data::{ClearDirective, ClearSiteData};
1820
pub use expires::Expires;

src/headers/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ pub const AUTHORIZATION: HeaderName = HeaderName::from_lowercase_str("authorizat
8181
/// The `Cache-Control` Header
8282
pub const CACHE_CONTROL: HeaderName = HeaderName::from_lowercase_str("cache-control");
8383

84+
/// The `Clear-Site-Data` Header
85+
pub const CLEAR_SITE_DATA: HeaderName = HeaderName::from_lowercase_str("clear-site-data");
86+
8487
/// The `Connection` Header
8588
pub const CONNECTION: HeaderName = HeaderName::from_lowercase_str("connection");
8689

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
//! declare it up front. But if you don't, things will still work.
9595
9696
#![deny(missing_debug_implementations, nonstandard_style)]
97-
#![warn(missing_docs, unreachable_pub)]
97+
#![warn(missing_docs)]
9898
#![allow(clippy::new_without_default)]
9999
#![cfg_attr(backtrace, feature(backtrace))]
100100
#![cfg_attr(feature = "docs", feature(doc_cfg))]

0 commit comments

Comments
 (0)