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}