Skip to main content

ical/tree/value/
node.rs

1//! # Value node
2//!
3//! The raw value of a content line, on the syntax side.
4//!
5//! [`IcalValueNode`] is the syntactic peer of the decoded
6//! [`IcalValue`](crate::value::IcalValue): the bytes after a line's colon,
7//! read as `;`-separated components of `,`-separated
8//! [`IcalValueLeaf`](crate::tree::leaf) values.
9//!
10//! Those are raw bytes, so a foreign charset survives. Straight from parse
11//! the value stays one unsplit slice walked on demand; an edit or a model
12//! encode splits it into owned components, which then become the source of
13//! truth.
14//!
15//! The splitting is generic, counting and preserving separators so the value
16//! round-trips; what the components *mean* is the lens's business.
17//!
18//! The codec that unescapes components into decoded values and re-escapes
19//! edits back lives on this type, applying the rules of the sibling
20//! [`mode`](crate::tree::codec::mode) codec.
21
22use core::fmt;
23
24use alloc::{borrow::Cow, string::String, vec::Vec};
25
26use crate::tree::{
27    codec::{
28        encode::{encode_bytes_component, encode_component},
29        mode::Escaper,
30        unescape::{unescape_bytes, unescape_with},
31    },
32    leaf::IcalValueLeaf,
33};
34
35/// A raw value: `;`-separated components, each a list of `,`-separated leaves.
36///
37/// From parse the value is held as one unsplit `raw` slice and walked lazily,
38/// so a parse that never decodes and a byte-faithful reserialize do no
39/// splitting at all. The first edit (or a model encode) splits it into owned
40/// `components`, which then take over as the source of truth.
41///
42/// The `escaper` records which version's escaping rules the codec must apply;
43/// it is stamped from the calendar version after parsing (see
44/// `IcalCst::parse`).
45#[derive(Clone, Debug, Default)]
46pub struct IcalValueNode<'a> {
47    /// The unsplit value bytes, straight from parse and authoritative until an
48    /// edit or a model encode replaces them; `None` once `components` is the
49    /// source of truth.
50    raw: Option<Cow<'a, [u8]>>,
51    /// The split components, authoritative when `raw` is `None`; while `raw` is
52    /// `Some` this stays empty and reads split `raw` on demand.
53    components: Vec<Vec<IcalValueLeaf<'a>>>,
54    /// The escaping rules to read and write this value with.
55    pub escaper: Escaper,
56}
57
58impl<'a> IcalValueNode<'a> {
59    /// Wrap a raw value, unsplit and borrowed, to be walked lazily. The colon,
60    /// name and eol are the line's business; this is only the bytes after the
61    /// colon.
62    pub fn parse(value: &'a [u8]) -> Self {
63        Self {
64            raw: Some(Cow::Borrowed(value)),
65            components: Vec::new(),
66            escaper: Escaper::default(),
67        }
68    }
69
70    /// Build a value node from already-split components (the model encode
71    /// path); there is no `raw`, so the components are the source of truth.
72    pub(crate) fn from_components(
73        components: Vec<Vec<IcalValueLeaf<'a>>>,
74        escaper: Escaper,
75    ) -> Self {
76        Self {
77            raw: None,
78            components,
79            escaper,
80        }
81    }
82
83    /// The number of `;`-separated components (at least one, since an empty
84    /// value is one empty component).
85    pub fn component_count(&self) -> usize {
86        match &self.raw {
87            Some(raw) => {
88                let mut count = 0;
89                split_on(raw, b';', |_| count += 1);
90                count
91            }
92            None => self.components.len(),
93        }
94    }
95
96    /// Decode the whole value as one string, its `;` and `,` kept literal.
97    ///
98    /// The read to reach for. A value the specification gives no structure of
99    /// its own, a URI above all, separates nothing with its semicolon (RFC
100    /// 5545 3.3.13), so naming a component there truncates
101    /// `ATTACH:data:text/plain;base64,AAA` at the media type.
102    pub fn decode(&self) -> Cow<'_, str> {
103        match &self.raw {
104            Some(raw) => unescape_with(raw, self.escaper),
105            None => {
106                let joined = self.joined_bytes();
107                Cow::Owned(unescape_with(&joined, self.escaper).into_owned())
108            }
109        }
110    }
111
112    /// Decode the whole value as its `,`-separated list, `;` kept literal.
113    ///
114    /// The list read to reach for: a comma separates the items of a list
115    /// value, and nothing else in the value is cut.
116    pub fn decode_list(&self) -> Vec<Cow<'_, str>> {
117        let mut values = Vec::new();
118
119        match &self.raw {
120            Some(raw) => split_on(raw, b',', |value| {
121                values.push(unescape_with(value, self.escaper))
122            }),
123            None => {
124                let joined = self.joined_bytes();
125                split_on(&joined, b',', |value| {
126                    values.push(Cow::Owned(unescape_with(value, self.escaper).into_owned()))
127                });
128            }
129        }
130
131        values
132    }
133
134    /// The whole value's `,`-separated items, still escaped as on the wire.
135    ///
136    /// The raw twin of [`decode_list`](Self::decode_list), item for item. An
137    /// unescape is not injective, `\N` and `\n` both reading as a line break
138    /// (RFC 5545 3.3.11), so a caller weighing what two sides wrote wants the
139    /// bytes rather than the decoding.
140    pub(crate) fn raw_list(&self) -> Vec<Vec<u8>> {
141        let mut values = Vec::new();
142
143        match &self.raw {
144            Some(raw) => split_on(raw, b',', |value| values.push(value.to_vec())),
145            None => {
146                let joined = self.joined_bytes();
147                split_on(&joined, b',', |value| values.push(value.to_vec()));
148            }
149        }
150
151        values
152    }
153
154    /// The whole value as raw unescaped bytes, not transcoded, for a value
155    /// carrying a foreign charset. Its `;` and `,` stay literal.
156    pub fn decode_bytes(&self) -> Cow<'_, [u8]> {
157        match &self.raw {
158            Some(raw) => unescape_bytes(raw, self.escaper),
159            None => {
160                let joined = self.joined_bytes();
161                Cow::Owned(unescape_bytes(&joined, self.escaper).into_owned())
162            }
163        }
164    }
165
166    /// Decode the `i`th `;`-component as one string, its `,` kept literal.
167    ///
168    /// Only for a value the specification structures with `;`, which in
169    /// iCalendar is `GEO`, `REQUEST-STATUS` and the rule parts of `RECUR`: on
170    /// any other value naming a component drops everything past the first `;`.
171    /// The whole-value read is [`decode`](Self::decode).
172    pub fn decode_component(&self, i: usize) -> Cow<'_, str> {
173        match self.component_at(i) {
174            // NOTE: The whole component slice already has the commas in place,
175            // so unescaping it verbatim keeps them literal.
176            Some(Component::Raw(bytes)) => unescape_with(bytes, self.escaper),
177            Some(Component::Split(leaves)) => {
178                if leaves.len() <= 1 {
179                    return leaves
180                        .first()
181                        .map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
182                        .unwrap_or(Cow::Borrowed(""));
183                }
184
185                let mut raw = Vec::new();
186                for (j, leaf) in leaves.iter().enumerate() {
187                    if j > 0 {
188                        raw.push(b',');
189                    }
190                    raw.extend_from_slice(leaf.as_bytes());
191                }
192
193                Cow::Owned(unescape_with(&raw, self.escaper).into_owned())
194            }
195            None => Cow::Borrowed(""),
196        }
197    }
198
199    /// Decode the `i`th `;`-component as its `,`-separated list.
200    ///
201    /// Carries the caveat of [`decode_component`](Self::decode_component): a
202    /// value with no `;`-structure of its own wants
203    /// [`decode_list`](Self::decode_list) instead.
204    pub fn decode_component_list(&self, i: usize) -> Vec<Cow<'_, str>> {
205        match self.component_at(i) {
206            Some(Component::Raw(bytes)) => {
207                let mut values = Vec::new();
208                split_on(bytes, b',', |value| {
209                    values.push(unescape_with(value, self.escaper))
210                });
211                values
212            }
213            Some(Component::Split(leaves)) => leaves
214                .iter()
215                .map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
216                .collect(),
217            None => Vec::new(),
218        }
219    }
220
221    /// Wrap owned value bytes unsplit, so they go back on the wire exactly as
222    /// given. Backs the value kinds that carry no escaping of their own.
223    pub(crate) fn from_raw(bytes: Vec<u8>, escaper: Escaper) -> IcalValueNode<'static> {
224        IcalValueNode {
225            raw: Some(Cow::Owned(bytes)),
226            components: Vec::new(),
227            escaper,
228        }
229    }
230
231    /// The raw (still-escaped) bytes of the first component's first value, for
232    /// the simple single-value lines (the envelope values and diagnostics).
233    pub(crate) fn first_value_bytes(&self) -> &[u8] {
234        match self.component_at(0) {
235            Some(Component::Raw(bytes)) => first_value(bytes),
236            Some(Component::Split(leaves)) => {
237                leaves.first().map(|leaf| leaf.as_bytes()).unwrap_or(b"")
238            }
239            None => b"",
240        }
241    }
242
243    /// Replace the whole value with one component of `,`-separated values,
244    /// escaping each.
245    ///
246    /// The inverse of [`decode_list`](Self::decode_list), so reading a value
247    /// and writing it back leaves no component of the old value behind.
248    pub fn set<S: AsRef<str>>(&mut self, values: &[S]) {
249        self.raw = None;
250        self.components.clear();
251        self.components.push(encode_component(values, self.escaper));
252    }
253
254    /// Replace the whole value with one component of raw value bytes (the
255    /// foreign-charset escape hatch), escaping structural separators but
256    /// writing the bytes verbatim.
257    pub fn set_bytes<B: AsRef<[u8]>>(&mut self, values: &[B]) {
258        self.raw = None;
259        self.components.clear();
260        self.components
261            .push(encode_bytes_component(values, self.escaper));
262    }
263
264    /// Set the `i`th component, escaping each value. Pads with empty components
265    /// when needed; every other component is left untouched, so a parsed
266    /// calendar keeps its bytes. For a `;`-structured value, as
267    /// [`decode_component`](Self::decode_component) spells out.
268    pub fn set_component<S: AsRef<str>>(&mut self, i: usize, values: &[S]) {
269        self.materialize();
270
271        while self.components.len() <= i {
272            self.components.push(Vec::new());
273        }
274
275        self.components[i] = encode_component(values, self.escaper);
276    }
277
278    /// Set the `i`th component from raw value bytes (the foreign-charset escape
279    /// hatch), escaping structural separators but writing the bytes verbatim.
280    pub fn set_component_bytes<B: AsRef<[u8]>>(&mut self, i: usize, values: &[B]) {
281        self.materialize();
282
283        while self.components.len() <= i {
284            self.components.push(Vec::new());
285        }
286
287        self.components[i] = encode_bytes_component(values, self.escaper);
288    }
289
290    /// Serialize the raw value bytes (name, colon and eol are the line's job)
291    /// into `out`, exactly as parsed. An untouched value emits its `raw` slice
292    /// with no reassembly.
293    pub(crate) fn write_bytes(&self, out: &mut Vec<u8>) {
294        if let Some(raw) = &self.raw {
295            out.extend_from_slice(raw);
296            return;
297        }
298
299        for (i, component) in self.components.iter().enumerate() {
300            if i > 0 {
301                out.push(b';');
302            }
303
304            for (j, leaf) in component.iter().enumerate() {
305                if j > 0 {
306                    out.push(b',');
307                }
308
309                out.extend_from_slice(leaf.as_bytes());
310            }
311        }
312    }
313
314    /// Convert into an owned value node (`'static`), keeping it lazy: an
315    /// unsplit value stays unsplit, only its bytes become owned.
316    pub(crate) fn into_static(self) -> IcalValueNode<'static> {
317        match self.raw {
318            Some(raw) => IcalValueNode {
319                raw: Some(Cow::Owned(raw.into_owned())),
320                components: Vec::new(),
321                escaper: self.escaper,
322            },
323            None => IcalValueNode {
324                raw: None,
325                components: self
326                    .components
327                    .into_iter()
328                    .map(|component| {
329                        component
330                            .into_iter()
331                            .map(IcalValueLeaf::into_static)
332                            .collect()
333                    })
334                    .collect(),
335                escaper: self.escaper,
336            },
337        }
338    }
339
340    /// The whole value's still-escaped bytes, reassembled from the split
341    /// components. Only a split node needs this: an unsplit one already holds
342    /// the value as one slice and reads it borrowed.
343    fn joined_bytes(&self) -> Vec<u8> {
344        let mut out = Vec::new();
345        self.write_bytes(&mut out);
346        out
347    }
348
349    /// Locate the `i`th component, either as a raw slice of the unsplit value
350    /// or as the already-split leaves.
351    fn component_at(&self, i: usize) -> Option<Component<'_, 'a>> {
352        match &self.raw {
353            Some(raw) => {
354                let mut found = None;
355                let mut index = 0;
356                split_on(raw, b';', |component| {
357                    if index == i {
358                        found = Some(component);
359                    }
360                    index += 1;
361                });
362                found.map(Component::Raw)
363            }
364            None => self
365                .components
366                .get(i)
367                .map(|leaves| Component::Split(leaves)),
368        }
369    }
370
371    /// Split the unsplit `raw` value into owned components so it can be edited
372    /// in place; a no-op once already split. Only edits pay this cost.
373    fn materialize(&mut self) {
374        let Some(raw) = self.raw.take() else {
375            return;
376        };
377
378        self.components = match raw {
379            Cow::Borrowed(bytes) => split_all(bytes),
380            Cow::Owned(bytes) => split_all_owned(&bytes),
381        };
382    }
383}
384
385impl fmt::Display for IcalValueNode<'_> {
386    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387        if let Some(raw) = &self.raw {
388            return f.write_str(&String::from_utf8_lossy(raw));
389        }
390
391        for (i, component) in self.components.iter().enumerate() {
392            if i > 0 {
393                f.write_str(";")?;
394            }
395
396            for (j, leaf) in component.iter().enumerate() {
397                if j > 0 {
398                    f.write_str(",")?;
399                }
400
401                f.write_str(&String::from_utf8_lossy(leaf.as_bytes()))?;
402            }
403        }
404
405        Ok(())
406    }
407}
408
409/// One located component: a slice of the still-unsplit value, or its leaves
410/// once the value has been split for editing.
411enum Component<'s, 'a> {
412    /// The component's bytes, still `,`-joined, borrowed from the unsplit
413    /// value.
414    Raw(&'s [u8]),
415    /// The component's already-split leaves.
416    Split(&'s [IcalValueLeaf<'a>]),
417}
418
419/// The first `,`-separated value of a component (escape-aware): the bytes up to
420/// the first unescaped comma, or the whole component when it has none.
421fn first_value(component: &[u8]) -> &[u8] {
422    let mut first = None;
423    split_on(component, b',', |value| {
424        first.get_or_insert(value);
425    });
426    first.unwrap_or(component)
427}
428
429/// Split a value into its `;`-separated components, each a list of its
430/// `,`-separated leaves, borrowing from `bytes`.
431fn split_all(bytes: &[u8]) -> Vec<Vec<IcalValueLeaf<'_>>> {
432    let mut components = Vec::new();
433    split_on(bytes, b';', |component| {
434        let mut values = Vec::new();
435        split_on(component, b',', |value| {
436            values.push(IcalValueLeaf::from(value));
437        });
438        components.push(values);
439    });
440    components
441}
442
443/// Split like [`split_all`] but copying each leaf, for the owned bytes an
444/// edit-after-`into_static` value carries (which no slice can borrow from).
445fn split_all_owned(bytes: &[u8]) -> Vec<Vec<IcalValueLeaf<'static>>> {
446    let mut components = Vec::new();
447    split_on(bytes, b';', |component| {
448        let mut values = Vec::new();
449        split_on(component, b',', |value| {
450            values.push(IcalValueLeaf::from(value.to_vec()));
451        });
452        components.push(values);
453    });
454    components
455}
456
457/// Call `piece` for each span between unescaped `sep` bytes, at least once.
458///
459/// A backslash escapes the next byte (so `\;` / `\,` do not split), and
460/// `memchr` skips straight to the next `sep` or backslash rather than scanning
461/// byte by byte, so a large separator-free value (base64) passes in one go.
462fn split_on<'b>(bytes: &'b [u8], sep: u8, mut piece: impl FnMut(&'b [u8])) {
463    let mut start = 0;
464    let mut i = 0;
465
466    while let Some(offset) = memchr::memchr2(b'\\', sep, &bytes[i..]) {
467        let pos = i + offset;
468        if bytes[pos] == b'\\' {
469            i = (pos + 2).min(bytes.len());
470        } else {
471            piece(&bytes[start..pos]);
472            start = pos + 1;
473            i = pos + 1;
474        }
475    }
476
477    piece(&bytes[start..]);
478}
479
480#[cfg(test)]
481mod tests {
482    use alloc::{borrow::Cow, string::ToString, vec, vec::Vec};
483
484    use crate::tree::value::node::IcalValueNode;
485
486    #[test]
487    fn splits_components_and_values_then_round_trips() {
488        let node = IcalValueNode::parse(b"a;b,c;");
489        assert_eq!(node.component_count(), 3);
490        assert_eq!(
491            node.decode_component_list(1),
492            vec![Cow::Borrowed("b"), Cow::Borrowed("c")]
493        );
494        assert_eq!(node.to_string(), "a;b,c;");
495    }
496
497    #[test]
498    fn keeps_escaped_separators_inside_one_value() {
499        let node = IcalValueNode::parse(br"a\,b\;c;d");
500        assert_eq!(node.component_count(), 2);
501        assert_eq!(node.decode_component_list(0).len(), 1);
502        assert_eq!(node.to_string(), r"a\,b\;c;d");
503    }
504
505    #[test]
506    fn an_edit_splits_and_preserves_untouched_components() {
507        let mut node = IcalValueNode::parse(b"a;b;c");
508        node.set_component(1, &["X"]);
509        assert_eq!(node.to_string(), "a;X;c");
510    }
511
512    /// Every reader answers the same before and after the node materializes.
513    ///
514    /// A node holds its value as raw bytes until the first edit splits it into
515    /// components, and each reader has a branch per state. A parse-and-read
516    /// exercises the lazy branches; these are the other half, where a
517    /// disagreement would show as a value changing shape on an unrelated write.
518    fn assert_readers_agree(node: &IcalValueNode<'_>, whole: &str) {
519        assert_eq!(node.component_count(), whole.split(';').count());
520        assert_eq!(node.decode(), whole);
521        assert_eq!(node.decode_bytes().as_ref(), whole.as_bytes());
522        assert_eq!(
523            node.decode_list(),
524            whole.split(',').map(Cow::Borrowed).collect::<Vec<_>>(),
525        );
526        assert_eq!(node.decode_component(0), "a");
527        assert_eq!(
528            node.decode_component_list(1),
529            vec![Cow::Borrowed("b"), Cow::Borrowed("c")],
530        );
531        assert_eq!(node.decode_component(1), "b,c");
532        assert_eq!(node.decode_component_list(2), vec![Cow::Borrowed("d")]);
533    }
534
535    #[test]
536    fn readers_agree_before_and_after_an_edit_materializes_the_node() {
537        let mut node = IcalValueNode::parse(b"a;b,c;d");
538        assert_readers_agree(&node, "a;b,c;d");
539
540        // NOTE: Writing component 3 leaves the read components alone, but it is
541        // what moves the node off its raw bytes.
542        node.set_component(3, &["e"]);
543        assert_readers_agree(&node, "a;b,c;d;e");
544        assert_eq!(node.to_string(), "a;b,c;d;e");
545    }
546
547    #[test]
548    fn readers_agree_after_an_owned_node_is_edited() {
549        let mut node = IcalValueNode::parse(b"a;b,c;d").into_static();
550        assert_readers_agree(&node, "a;b,c;d");
551
552        node.set_component(3, &["e"]);
553        assert_readers_agree(&node, "a;b,c;d;e");
554        assert_eq!(node.to_string(), "a;b,c;d;e");
555    }
556
557    #[test]
558    fn a_whole_value_write_leaves_no_component_of_the_old_value_behind() {
559        let mut node = IcalValueNode::parse(b"a;b,c;d");
560        let whole = node.decode().into_owned();
561        node.set(&[whole]);
562        assert_eq!(node.decode(), "a;b,c;d");
563        assert_eq!(node.to_string(), r"a\;b\,c\;d");
564
565        let mut node = IcalValueNode::parse(b"a;b,c;d");
566        let whole = node.decode_bytes().into_owned();
567        node.set_bytes(&[whole]);
568        assert_eq!(node.decode_bytes().as_ref(), b"a;b,c;d");
569    }
570}