safari_binarycookies/iter.rs
1//! Lazy cookie stream over a `.binarycookies` buffer.
2
3use core::fmt;
4use core::iter::FusedIterator;
5
6use crate::decode::Decoder;
7use crate::error::Error;
8use crate::model::Cookie;
9
10/// Returns a lazy iterator over every cookie in `input`, flattening the page
11/// layer.
12///
13/// The file header (magic, page count, page-size table) is validated eagerly;
14/// pages and cookies are then parsed on demand. On input that decodes fully,
15/// the iterator yields exactly the cookies of
16/// [`from_bytes`](crate::from_bytes), in order. On malformed input it yields
17/// the cookies parsed before the first structural violation, then that error
18/// once, then fuses — whereas [`from_bytes`](crate::from_bytes) returns only
19/// the error.
20///
21/// # Errors
22///
23/// Returns an error when the magic is wrong, the page count exceeds the
24/// hardening cap, or the header is truncated.
25///
26/// # Examples
27///
28/// ```
29/// // A minimal file: `cook` magic + zero pages + 8-byte checksum.
30/// let data = *b"cook\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
31/// let mut cookies = safari_binarycookies::cookies(&data)?;
32/// assert!(cookies.next().is_none());
33/// # Ok::<(), safari_binarycookies::Error>(())
34/// ```
35pub fn cookies(input: &[u8]) -> Result<Cookies<'_>, Error> {
36 let mut decoder = Decoder::new(input);
37 decoder.signature()?;
38 let pages_left = decoder.page_count()?;
39 decoder.skip_page_size_table(pages_left)?;
40 Ok(Cookies {
41 decoder,
42 pages_left,
43 left_in_page: 0,
44 finished: false,
45 })
46}
47
48/// Lazy iterator returned by [`cookies`].
49///
50/// Yields `Result<Cookie, Error>` and fuses after the first error or the end
51/// of the last page. Cloning is cheap and restarts nothing: a clone resumes
52/// from the current position.
53#[derive(Clone)]
54#[must_use = "iterators are lazy and do nothing unless consumed"]
55pub struct Cookies<'a> {
56 decoder: Decoder<'a>,
57 pages_left: u32,
58 left_in_page: u32,
59 finished: bool,
60}
61
62impl Cookies<'_> {
63 fn read_page_header(&mut self) -> Result<u32, Error> {
64 self.decoder.page_tag()?;
65 let count = self.decoder.cookie_count()?;
66 self.decoder.skip_cookie_offset_table(count)?;
67 self.decoder.page_end()?;
68 Ok(count)
69 }
70}
71
72impl Iterator for Cookies<'_> {
73 type Item = Result<Cookie, Error>;
74
75 fn next(&mut self) -> Option<Self::Item> {
76 if self.finished {
77 return None;
78 }
79 loop {
80 if self.left_in_page > 0 {
81 self.left_in_page -= 1;
82 let result = self.decoder.cookie();
83 if result.is_err() {
84 self.finished = true;
85 }
86 return Some(result);
87 }
88 if self.pages_left > 0 {
89 self.pages_left -= 1;
90 match self.read_page_header() {
91 Ok(count) => self.left_in_page = count,
92 Err(error) => {
93 self.finished = true;
94 return Some(Err(error));
95 }
96 }
97 continue;
98 }
99 self.finished = true;
100 // Mirror the eager decoder, which reads the 8-byte checksum after
101 // the last page, so both paths fail alike on a truncated trailer.
102 return match self.decoder.checksum() {
103 Ok(_) => None,
104 Err(error) => Some(Err(error)),
105 };
106 }
107 }
108
109 // Remaining yields are unknowable up front (a parse error both adds an
110 // item and ends the stream), so only the fused empty state is exact.
111 fn size_hint(&self) -> (usize, Option<usize>) {
112 if self.finished {
113 (0, Some(0))
114 } else {
115 (0, None)
116 }
117 }
118}
119
120impl FusedIterator for Cookies<'_> {}
121
122impl fmt::Debug for Cookies<'_> {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.debug_struct("Cookies")
125 .field("pages_left", &self.pages_left)
126 .field("left_in_page", &self.left_in_page)
127 .field("finished", &self.finished)
128 .finish_non_exhaustive()
129 }
130}