Skip to main content

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}