1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

/*! Defines primitives in control files.

See <https://www.debian.org/doc/debian-policy/ch-controlfields.html>
for the canonical source of truth for how control files work.
*/

use {
    crate::{
        dependency::DependencyList,
        error::{DebianError, Result},
    },
    chrono::{DateTime, TimeZone, Utc},
    futures::{AsyncBufRead, AsyncBufReadExt},
    pin_project::pin_project,
    std::{
        borrow::Cow,
        collections::HashMap,
        io::{BufRead, Write},
        str::FromStr,
    },
};

/// A field value in a control file.
///
/// This represents the value after the colon (`:`) in field definitions.
///
/// There are canonically 3 types of field values: *simple*, *folded*, and *multiline*.
/// The differences between *folded* and *multiline* are semantic. *folded* is logically
/// a single line spanning multiple formatted lines and whitespace is not significant.
/// *multiline* has similar syntax as *folded* but whitespace is significant.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ControlFieldValue<'a> {
    Simple(Cow<'a, str>),
    Folded(Cow<'a, str>),
    Multiline(Cow<'a, str>),
}

impl<'a> AsRef<Cow<'a, str>> for ControlFieldValue<'a> {
    fn as_ref(&self) -> &Cow<'a, str> {
        match self {
            Self::Simple(v) => v,
            Self::Folded(v) => v,
            Self::Multiline(v) => v,
        }
    }
}

impl<'a> ControlFieldValue<'a> {
    /// Obtain an iterator over string values in this field.
    ///
    /// [Self::Simple] variants will emit a single item.
    ///
    /// [Self::Folded] and [Self::Multiline] may emit multiple items.
    ///
    /// For variants stored as multiple lines, leading whitespace will be trimmed, as necessary.
    pub fn iter_lines(&self) -> Box<(dyn Iterator<Item = &str> + '_)> {
        match self {
            Self::Simple(v) => Box::new([v.as_ref()].into_iter()),
            Self::Folded(values) => Box::new(values.lines().map(|x| x.trim_start())),
            Self::Multiline(values) => Box::new(values.lines().map(|x| x.trim_start())),
        }
    }

    /// Obtain an iterator over words in the string value.
    ///
    /// The result may be non-meaningful for multiple line variants.
    pub fn iter_words(&self) -> Box<(dyn Iterator<Item = &str> + '_)> {
        Box::new(self.as_ref().split_ascii_whitespace())
    }

    /// Write this value to a writer.
    pub fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
        let data = match self {
            Self::Simple(v) => v,
            Self::Folded(v) => v,
            Self::Multiline(v) => v,
        };

        writer.write_all(data.as_bytes())
    }

    /// Obtain the inner string backing this value and consume this instance.
    pub fn into_inner(self) -> Cow<'a, str> {
        match self {
            Self::Simple(v) => v,
            Self::Folded(v) => v,
            Self::Multiline(v) => v,
        }
    }

    /// Convert this strongly typed value into a [ControlField].
    pub fn to_control_field(self, name: Cow<'a, str>) -> ControlField<'a> {
        ControlField::new(name, self.into_inner())
    }
}

/// A field in a control file.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ControlField<'a> {
    name: Cow<'a, str>,
    value: Cow<'a, str>,
}

impl<'a> ControlField<'a> {
    /// Construct an instance from a field name and value.
    pub fn new(name: Cow<'a, str>, value: Cow<'a, str>) -> Self {
        Self { name, value }
    }

    /// Construct an instance from an iterable of lines.
    ///
    /// Each line should not have leading whitespace.
    pub fn from_lines(name: Cow<'a, str>, lines: impl Iterator<Item = String>) -> Self {
        let value = lines
            .enumerate()
            .map(|(i, line)| if i == 0 { line } else { format!(" {}", line) })
            .collect::<Vec<_>>()
            .join("\n")
            .into();

        Self { name, value }
    }

    /// The name of this field.
    pub fn name(&self) -> &str {
        self.name.as_ref()
    }

    /// Obtain the value as a [&str].
    ///
    /// The value's original file formatting (including newlines and leading whitespace)
    /// is included.
    pub fn value_str(&self) -> &str {
        self.value.as_ref()
    }

    /// Obtain the value as a [ControlFieldValue::Simple] if possible.
    ///
    /// This will fail if the tracked value has multiple lines.
    pub fn as_simple(&self) -> Result<ControlFieldValue<'a>> {
        if self.value.as_ref().contains('\n') {
            Err(DebianError::ControlSimpleValueNoMultiline)
        } else {
            Ok(ControlFieldValue::Simple(self.value.clone()))
        }
    }

    /// Obtain the value as a [ControlFieldValue::Folded].
    pub fn as_folded(&self) -> ControlFieldValue<'a> {
        ControlFieldValue::Folded(self.value.clone())
    }

    /// Obtain the value as a [ControlFieldValue::Multiline].
    pub fn as_multiline(&self) -> ControlFieldValue<'a> {
        ControlFieldValue::Multiline(self.value.clone())
    }

    /// Obtain an iterator of words in the value.
    pub fn iter_words(&self) -> Box<(dyn Iterator<Item = &str> + '_)> {
        Box::new(self.value.as_ref().split_ascii_whitespace())
    }

    /// Obtain an iterator of lines in the value.
    ///
    /// Leading whitespace from each line is stripped.
    pub fn iter_lines(&self) -> Box<(dyn Iterator<Item = &str> + '_)> {
        Box::new(self.value.lines().map(|x| x.trim_start()))
    }

    /// Obtain an iterator of comma delimited values in the value.
    ///
    /// Whitespace surrounding the comma is trimmed.
    pub fn iter_comma_delimited(&self) -> Box<(dyn Iterator<Item = &str> + '_)> {
        Box::new(self.value.as_ref().split(',').map(|v| v.trim()))
    }

    /// Write the contents of this field to a writer.
    pub fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
        writer.write_all(self.name.as_bytes())?;
        writer.write_all(b": ")?;
        writer.write_all(self.value.as_ref().as_bytes())?;
        writer.write_all(b"\n")
    }
}

impl<'a> ToString for ControlField<'a> {
    fn to_string(&self) -> String {
        format!("{}: {}\n", self.name, self.value_str())
    }
}

/// A paragraph in a control file.
///
/// A paragraph is an ordered series of control fields.
///
/// Field names are case insensitive on read and case preserving on set.
///
/// Paragraphs can only contain a single occurrence of a field and this is enforced through
/// the mutation APIs.
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ControlParagraph<'a> {
    fields: Vec<ControlField<'a>>,
}

impl<'a> ControlParagraph<'a> {
    /// Whether the paragraph is empty.
    ///
    /// Empty is defined by the lack of any fields.
    pub fn is_empty(&self) -> bool {
        self.fields.is_empty()
    }

    /// Set the value of a field via a [ControlField].
    ///
    /// If a field with the same name (case insensitive compare) already exists, the old value
    /// will be replaced by the incoming value.
    pub fn set_field(&mut self, field: ControlField<'a>) {
        self.fields
            .retain(|cf| cf.name.to_lowercase() != field.name.to_lowercase());
        self.fields.push(field);
    }

    /// Set the value of a field defined via strings.
    ///
    /// If a field with the same name (case insensitive compare) already exists, the old value
    /// will be replaced by the incoming value.
    pub fn set_field_from_string(&mut self, name: Cow<'a, str>, value: Cow<'a, str>) {
        self.set_field(ControlField::new(name, value));
    }

    /// Whether a named field is present in this paragraph.
    pub fn has_field(&self, name: &str) -> bool {
        self.field(name).is_some()
    }

    /// Iterate over fields in this paragraph.
    ///
    /// Iteration order is insertion order.
    pub fn iter_fields(&self) -> impl Iterator<Item = &ControlField<'a>> {
        self.fields.iter()
    }

    /// Obtain the field with a given name in this paragraph.
    pub fn field(&self, name: &str) -> Option<&'_ ControlField<'a>> {
        self.fields
            .iter()
            .find(|f| f.name.as_ref().to_lowercase() == name.to_lowercase())
    }

    /// Obtain a mutable reference to the field with a given name.
    pub fn field_mut(&mut self, name: &str) -> Option<&'a mut ControlField> {
        self.fields
            .iter_mut()
            .find(|f| f.name.as_ref().to_lowercase() == name.to_lowercase())
    }

    /// Obtain the named field and error if it isn't defined.
    pub fn required_field(&self, name: &str) -> Result<&'_ ControlField<'a>> {
        self.field(name)
            .ok_or_else(|| DebianError::ControlRequiredFieldMissing(name.to_string()))
    }

    /// Obtain the raw string value of the named field.
    pub fn field_str(&self, name: &str) -> Option<&str> {
        self.field(name).map(|f| f.value_str())
    }

    /// Obtain the raw string value of the named field, erroring if the field is not present.
    pub fn required_field_str(&self, name: &str) -> Result<&str> {
        Ok(self.required_field(name)?.value_str())
    }

    /// Obtain the value of a field, evaluated as a boolean.
    ///
    /// The field is [true] iff its string value is `yes`.
    pub fn field_bool(&self, name: &str) -> Option<bool> {
        self.field_str(name).map(|v| matches!(v, "yes"))
    }

    /// Obtain the value of a field, evaluated as a [u64].
    pub fn field_u64(&self, name: &str) -> Option<Result<u64>> {
        self.field_str(name).map(|x| {
            u64::from_str(x).map_err(|e| DebianError::ControlFieldIntParse(name.to_string(), e))
        })
    }

    /// Obtain the value of a field, parsed as a [DependencyList].
    pub fn field_dependency_list(&self, name: &str) -> Option<Result<DependencyList>> {
        self.field_str(name).map(DependencyList::parse)
    }

    /// Obtain the value of a field parsed as an RFC 5322 date string.
    ///
    /// This will parse values like `Sat, 09 Oct 2021 09:34:56 UTC`.
    ///
    /// The timezone is always normalized to UTC even if it is expressed differently in the
    /// source string.
    pub fn field_datetime_rfc5322(&self, name: &str) -> Option<Result<DateTime<Utc>>> {
        self.field_str(name)
            .map(|v| Ok(Utc.timestamp(mailparse::dateparse(v)?, 0)))
    }

    /// Obtain the field with the given name as a [ControlFieldValue::Simple], if possible.
    pub fn field_simple(&self, name: &str) -> Option<Result<ControlFieldValue<'a>>> {
        self.field(name).map(|cf| cf.as_simple())
    }

    /// Obtain the field with the given name as a [ControlFieldValue::Folded].
    pub fn field_folded(&self, name: &str) -> Option<ControlFieldValue<'a>> {
        self.field(name).map(|cf| cf.as_folded())
    }

    /// Obtain the field with the given name as a [ControlFieldValue::Multiline].
    pub fn field_multiline(&self, name: &str) -> Option<ControlFieldValue<'a>> {
        self.field(name).map(|cf| cf.as_multiline())
    }

    /// Obtain an iterator of words in the named field.
    pub fn iter_field_words(&self, name: &str) -> Option<Box<(dyn Iterator<Item = &str> + '_)>> {
        self.field(name)
            .map(|f| Box::new(f.value.split_ascii_whitespace()) as Box<dyn Iterator<Item = &str>>)
    }

    /// Obtain an iterator of lines in the named field.
    pub fn iter_field_lines(&self, name: &str) -> Option<Box<(dyn Iterator<Item = &str> + '_)>> {
        self.field(name).map(|f| f.iter_lines())
    }

    /// Obtain an iterator of comma-delimited values in the named field.
    pub fn iter_field_comma_delimited(
        &self,
        name: &str,
    ) -> Option<Box<(dyn Iterator<Item = &str> + '_)>> {
        self.field(name).map(|f| f.iter_comma_delimited())
    }

    /// Convert this paragraph to a [HashMap].
    ///
    /// Values will be the string normalization of the field value, including newlines and
    /// leading whitespace.
    ///
    /// If a field occurs multiple times, its last value will be recorded in the returned map.
    pub fn as_str_hash_map(&self) -> HashMap<&str, &str> {
        HashMap::from_iter(
            self.fields
                .iter()
                .map(|field| (field.name.as_ref(), field.value_str())),
        )
    }

    /// Serialize the paragraph to a writer.
    ///
    /// A trailing newline is written as part of the final field. However, an
    /// extra newline is not present. So if serializing multiple paragraphs, an
    /// additional line break must be written to effectively terminate this paragraph
    /// if the writer is not at EOF.
    pub fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
        for field in &self.fields {
            field.write(writer)?;
        }

        Ok(())
    }
}

impl<'a> ToString for ControlParagraph<'a> {
    fn to_string(&self) -> String {
        let fields = self
            .fields
            .iter()
            .map(|f| f.to_string())
            .collect::<Vec<_>>();

        fields.join("")
    }
}

/// Holds parsing state for Debian control files.
///
/// Instances of this type are essentially fed lines of text and periodically emit
/// [ControlParagraph] instances as they are completed.
#[derive(Clone, Debug, Default)]
pub struct ControlFileParser {
    paragraph: ControlParagraph<'static>,
    field: Option<String>,
}

impl ControlFileParser {
    /// Write a line to the parser.
    ///
    /// If the line terminates an in-progress paragraph, that paragraph will be returned.
    /// Otherwise `Ok(None)` is returned.
    ///
    /// `Err` is returned if the control file in invalid.
    pub fn write_line(&mut self, line: &str) -> Result<Option<ControlParagraph<'static>>> {
        let is_empty_line = line.trim().is_empty();
        let is_indented = line.starts_with(' ') && line.len() > 1;

        let current_field = self.field.take();

        // Empty lines signify the end of a paragraph. Flush any state.
        if is_empty_line {
            if let Some(field) = current_field {
                self.flush_field(field)?;
            }

            return Ok(if self.paragraph.is_empty() {
                None
            } else {
                let para = self.paragraph.clone();
                self.paragraph = ControlParagraph::default();
                Some(para)
            });
        }

        match (current_field, is_indented) {
            // We have a field on the stack and got an unindented line. This
            // must be the beginning of a new field. Flush the current field.
            (Some(v), false) => {
                self.flush_field(v)?;

                self.field = if is_empty_line {
                    None
                } else {
                    Some(line.to_string())
                };

                Ok(None)
            }

            // We got a non-empty line and no field is currently being
            // processed. This must be the start of a new field.
            (None, _) => {
                self.field = Some(line.to_string());

                Ok(None)
            }
            // We have a field on the stack and got an indented line. This
            // must be a field value continuation. Add it to the current
            // field.
            (Some(v), true) => {
                self.field = Some(v + line);

                Ok(None)
            }
        }
    }

    /// Finish parsing, consuming self.
    ///
    /// If a non-empty paragraph is present in the instance, it will be returned. Else if there
    /// is no unflushed state, None is returned.
    pub fn finish(mut self) -> Result<Option<ControlParagraph<'static>>> {
        if let Some(field) = self.field.take() {
            self.flush_field(field)?;
        }

        Ok(if self.paragraph.is_empty() {
            None
        } else {
            Some(self.paragraph)
        })
    }

    fn flush_field(&mut self, v: String) -> Result<()> {
        let mut parts = v.splitn(2, ':');

        let name = parts.next().ok_or_else(|| {
            DebianError::ControlParseError(format!("error parsing line '{}'; missing colon", v))
        })?;
        let value = parts
            .next()
            .ok_or_else(|| {
                DebianError::ControlParseError(format!(
                    "error parsing field '{}'; could not detect value",
                    v
                ))
            })?
            .trim();

        self.paragraph
            .set_field_from_string(Cow::Owned(name.to_string()), Cow::Owned(value.to_string()));

        Ok(())
    }
}

/// A reader for [ControlParagraph].
///
/// Instances are bound to a reader, which is capable of feeding lines into a parser.
///
/// Instances can be consumed as an iterator. Each call into the iterator will attempt to
/// read a full paragraph from the underlying reader.
pub struct ControlParagraphReader<R: BufRead> {
    reader: R,
    parser: Option<ControlFileParser>,
}

impl<R: BufRead> ControlParagraphReader<R> {
    /// Create a new instance bound to a reader.
    pub fn new(reader: R) -> Self {
        Self {
            reader,
            parser: Some(ControlFileParser::default()),
        }
    }

    /// Consumes the instance, returning the original reader.
    pub fn into_inner(self) -> R {
        self.reader
    }

    fn get_next(&mut self) -> Result<Option<ControlParagraph<'static>>> {
        let mut parser = self.parser.take().unwrap();

        loop {
            let mut line = String::new();

            let bytes_read = self.reader.read_line(&mut line)?;

            if bytes_read != 0 {
                if let Some(paragraph) = parser.write_line(&line)? {
                    self.parser.replace(parser);
                    return Ok(Some(paragraph));
                }
                // Continue reading.
            } else {
                return if let Some(paragraph) = parser.finish()? {
                    Ok(Some(paragraph))
                } else {
                    Ok(None)
                };
            }
        }
    }
}

impl<R: BufRead> Iterator for ControlParagraphReader<R> {
    type Item = Result<ControlParagraph<'static>>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.parser.is_none() {
            None
        } else {
            match self.get_next() {
                Ok(Some(para)) => Some(Ok(para)),
                Ok(None) => None,
                Err(e) => Some(Err(e)),
            }
        }
    }
}

/// An asynchronous reader of [ControlParagraph].
///
/// Instances are bound to a reader, which is capable of reading lines.
#[pin_project]
pub struct ControlParagraphAsyncReader<R> {
    #[pin]
    reader: R,
    parser: Option<ControlFileParser>,
}

impl<R> ControlParagraphAsyncReader<R>
where
    R: AsyncBufRead + Unpin,
{
    /// Create a new instance bound to a reader.
    pub fn new(reader: R) -> Self {
        Self {
            reader,
            parser: Some(ControlFileParser::default()),
        }
    }

    /// Consumes self, returning the inner reader.
    pub fn into_inner(self) -> R {
        self.reader
    }

    /// Read the next available paragraph from this reader.
    ///
    /// Resolves to [None] on end of input.
    pub async fn read_paragraph(&mut self) -> Result<Option<ControlParagraph<'static>>> {
        let mut parser = if let Some(parser) = self.parser.take() {
            parser
        } else {
            return Ok(None);
        };

        loop {
            let mut line = String::new();

            let bytes_read = self.reader.read_line(&mut line).await?;

            if bytes_read != 0 {
                if let Some(paragraph) = parser.write_line(&line)? {
                    self.parser.replace(parser);
                    return Ok(Some(paragraph));
                }
                // Continue reading.
            } else {
                return if let Some(paragraph) = parser.finish()? {
                    Ok(Some(paragraph))
                } else {
                    Ok(None)
                };
            }
        }
    }
}

/// A debian control file.
///
/// A control file is an ordered series of paragraphs.
#[derive(Clone, Debug, Default)]
pub struct ControlFile<'a> {
    paragraphs: Vec<ControlParagraph<'a>>,
}

impl<'a> ControlFile<'a> {
    /// Construct a new instance by parsing data from a reader.
    pub fn parse_reader<R: BufRead>(reader: &mut R) -> Result<Self> {
        let mut paragraphs = Vec::new();
        let mut parser = ControlFileParser::default();

        loop {
            let mut line = String::new();
            let bytes_read = reader.read_line(&mut line)?;

            // .read_line() indicates EOF by Ok(0).
            if bytes_read == 0 {
                break;
            }

            if let Some(paragraph) = parser.write_line(&line)? {
                paragraphs.push(paragraph);
            }
        }

        if let Some(paragraph) = parser.finish()? {
            paragraphs.push(paragraph);
        }

        Ok(Self { paragraphs })
    }

    /// Parse a control file from a string.
    pub fn parse_str(s: &str) -> Result<Self> {
        let mut reader = std::io::BufReader::new(s.as_bytes());
        Self::parse_reader(&mut reader)
    }

    /// Add a paragraph to this control file.
    pub fn add_paragraph(&mut self, p: ControlParagraph<'a>) {
        self.paragraphs.push(p);
    }

    /// Obtain paragraphs in this control file.
    pub fn paragraphs(&self) -> impl Iterator<Item = &ControlParagraph<'a>> {
        self.paragraphs.iter()
    }

    /// Obtain paragraphs in this control file, consuming self.
    pub fn into_paragraphs(self) -> impl Iterator<Item = ControlParagraph<'a>> {
        self.paragraphs.into_iter()
    }

    /// Serialize the control file to a writer.
    pub fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
        for p in &self.paragraphs {
            p.write(writer)?;
            writer.write_all(b"\n")?;
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn control_paragraph_field_semantics() {
        let mut p = ControlParagraph::default();

        // Same cased field name results in overwrite.
        p.set_field_from_string("foo".into(), "bar".into());
        p.set_field_from_string("foo".into(), "baz".into());
        assert_eq!(p.field("foo").unwrap().value, "baz");

        // Different case results in overwrite.
        p.set_field_from_string("FOO".into(), "bar".into());
        assert_eq!(p.field("foo").unwrap().value, "bar");
        assert_eq!(p.field("FOO").unwrap().value, "bar");
    }

    #[test]
    fn parse_paragraph_release() -> Result<()> {
        let paragraphs = ControlParagraphReader::new(std::io::Cursor::new(include_bytes!(
            "testdata/release-debian-bullseye"
        )))
        .collect::<Result<Vec<_>>>()?;

        assert_eq!(paragraphs.len(), 1);
        let p = &paragraphs[0];

        assert_eq!(p.fields.len(), 14);

        assert!(p.has_field("Origin"));
        assert!(p.has_field("Version"));
        assert!(!p.has_field("Missing"));

        assert!(p.field("Version").is_some());

        let fields = &p.fields;
        assert_eq!(fields[0].name, "Origin");
        assert_eq!(fields[0].value, "Debian");

        assert_eq!(fields[3].name, "Version");
        assert_eq!(fields[3].value, "11.1");

        let ml = p.field_multiline("MD5Sum").unwrap();
        assert_eq!(ml.iter_lines().count(), 600);
        assert_eq!(
            ml.iter_lines().next().unwrap(),
            "7fdf4db15250af5368cc52a91e8edbce   738242 contrib/Contents-all"
        );

        assert!(p.field_multiline("SHA256").is_some());

        assert_eq!(fields[0].iter_words().collect::<Vec<_>>(), vec!["Debian"]);

        let values = p
            .field_multiline("MD5Sum")
            .unwrap()
            .iter_lines()
            .map(|x| x.to_string())
            .collect::<Vec<_>>();

        assert_eq!(values.len(), 600);
        assert_eq!(
            values[0],
            "7fdf4db15250af5368cc52a91e8edbce   738242 contrib/Contents-all"
        );
        assert_eq!(
            values[1],
            "cbd7bc4d3eb517ac2b22f929dfc07b47    57319 contrib/Contents-all.gz"
        );
        assert_eq!(
            values[599],
            "e3830f6fc5a946b5a5b46e8277e1d86f    80488 non-free/source/Sources.xz"
        );

        let values = p
            .field_multiline("SHA256")
            .unwrap()
            .iter_lines()
            .map(|x| x.to_string())
            .collect::<Vec<_>>();
        assert_eq!(values.len(), 600);
        assert_eq!(
            values[0],
            "3957f28db16e3f28c7b34ae84f1c929c567de6970f3f1b95dac9b498dd80fe63   738242 contrib/Contents-all",
        );
        assert_eq!(
            values[1],
            "3e9a121d599b56c08bc8f144e4830807c77c29d7114316d6984ba54695d3db7b    57319 contrib/Contents-all.gz",
        );
        assert_eq!(values[599], "30f3f996941badb983141e3b29b2ed5941d28cf81f9b5f600bb48f782d386fc7    80488 non-free/source/Sources.xz");

        Ok(())
    }

    #[test]
    fn test_parse_system_lists() -> Result<()> {
        let paths = glob::glob("/var/lib/apt/lists/*_Packages")
            .unwrap()
            .chain(glob::glob("/var/lib/apt/lists/*_Sources").unwrap())
            .chain(glob::glob("/var/lib/apt/lists/*i18n_Translation-*").unwrap());

        for path in paths {
            let path = path.unwrap();

            eprintln!("parsing {}", path.display());
            let fh = std::fs::File::open(&path)?;
            let reader = std::io::BufReader::new(fh);

            for para in ControlParagraphReader::new(reader) {
                para?;
            }
        }

        Ok(())
    }
}