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
use crate::TextString;
use basic_text_internals::unicode::CGJ;
use basic_text_internals::{is_basic_text, is_basic_text_end};
use std::borrow::Cow;
use std::io::{self, BufRead};

/// An extension trait for `BufRead` which adds functions for reading
/// lines as `TextString`s.
pub trait BufReadText: BufRead {
    /// Read all bytes until a newline (the `0xA` byte) is reached, and append
    /// them to the provided buffer, similar to [`BufRead::read_line`], but
    /// require the input to contain valid Basic Text, and require each line
    /// to be a valid Basic Text string.
    ///
    /// Basic Text streams always end with a newline, so the returned string
    /// will always have a trailing newline.
    ///
    /// This function is blocking and should be used carefully: it is possible
    /// for an attacker to continuously send bytes without ever sending a
    /// newline or ending the stream.
    fn read_text_line(&mut self, buf: &mut TextString) -> io::Result<usize> {
        let len = self.read_line(&mut buf.0)?;

        if !is_basic_text(&buf.0) {
            buf.0.clear();
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "stream did not contain valid Basic Text",
            ));
        }

        Ok(len)
    }

    /// Read all bytes until a newline (the `0xA` byte) is reached, and append
    /// them to the provided buffer, similar to [`BufRead::read_line`],
    /// converting the input to Basic Text using lossy conversions if needed.
    ///
    /// Basic Text streams always end with a newline, so the returned string
    /// will always have a trailing newline.
    ///
    /// This function is blocking and should be used carefully: it is possible
    /// for an attacker to continuously send bytes without ever sending a
    /// newline or ending the stream.
    fn read_text_line_lossy(&mut self, buf: &mut TextString) -> io::Result<usize> {
        let len = self.read_line(&mut buf.0)?;

        if let Cow::Owned(text) = TextString::from_text_lossy(&buf.0) {
            buf.0 = text.0;
        }

        Ok(len)
    }

    /// Returns an iterator over the lines of this reader, similar to
    /// [`BufRead::lines`], but returning `TextString`s.
    fn text_lines(self) -> TextLines<Self>
    where
        Self: Sized,
    {
        TextLines { buf: self }
    }

    /// Returns an iterator over the lines of this reader, similar to
    /// [`BufRead::lines`], but returning `TextString`s, converting the
    /// input to Basic Text using lossy conversions if needed.
    fn text_lines_lossy(self) -> TextLinesLossy<Self>
    where
        Self: Sized,
    {
        TextLinesLossy { buf: self }
    }
}

/// An iterator over the lines of an instance of `BufReadText`.
///
/// This struct is generally created by calling [`text_lines`] on a
/// `BufReadText`. Please see the documentation of [`text_lines`] for more
/// details.
///
/// [`text_lines`]: BufReadText::text_lines
#[derive(Debug)]
pub struct TextLines<B> {
    buf: B,
}

impl<B: BufReadText> Iterator for TextLines<B> {
    type Item = io::Result<TextString>;

    fn next(&mut self) -> Option<io::Result<TextString>> {
        let mut buf = TextString::new();
        match self.buf.read_text_line(&mut buf) {
            Ok(0) => None,
            Ok(_n) => {
                debug_assert!(buf.0.ends_with('\n'));
                buf.0.pop();
                debug_assert!(!buf.0.ends_with('\r'));

                if let Some(c) = buf.0.chars().next_back() {
                    if !is_basic_text_end(c) {
                        return Some(Err(io::Error::new(
                            io::ErrorKind::InvalidData,
                            "stream did not contain valid Basic Text lines",
                        )));
                    }
                }

                Some(Ok(buf))
            }
            Err(e) => Some(Err(e)),
        }
    }
}

/// An iterator over the lines of an instance of `BufReadText`.
///
/// This struct is generally created by calling [`text_lines_lossy`] on a
/// `BufReadText`. Please see the documentation of [`text_lines_lossy`] for
/// more details.
///
/// [`text_lines_lossy`]: BufReadText::text_lines_lossy
#[derive(Debug)]
pub struct TextLinesLossy<B> {
    buf: B,
}

impl<B: BufReadText> Iterator for TextLinesLossy<B> {
    type Item = io::Result<TextString>;

    fn next(&mut self) -> Option<io::Result<TextString>> {
        let mut buf = TextString::new();
        match self.buf.read_text_line_lossy(&mut buf) {
            Ok(0) => None,
            Ok(_n) => {
                debug_assert!(buf.0.ends_with('\n'));
                buf.0.pop();
                debug_assert!(!buf.0.ends_with('\r'));

                // We just popped the newline, so make sure we're not exposing
                // an invalid end.
                if let Some(c) = buf.0.chars().next_back() {
                    if !is_basic_text_end(c) {
                        buf.0.push(CGJ);
                    }
                }

                Some(Ok(buf))
            }
            Err(e) => Some(Err(e)),
        }
    }
}

// Implement `BufReadText` for all `BufRead` implementations.
impl<T: BufRead> BufReadText for T {}