termini/
parsing.rs

1use crate::{get_str_with_offset, Error, Extended, TermInfo, TermInfoData, ValueStorage};
2use std::collections::HashMap;
3use std::io;
4use std::io::Read;
5
6/// magic number octal 0432 for legacy ncurses terminfo
7const MAGIC_LEGACY: i16 = 0x11A;
8/// magic number octal 01036 for new ncurses terminfo
9const MAGIC_32BIT: i16 = 0x21E;
10
11impl TermInfoData {
12    /// Create terminfo database by parse byte-array directly
13    pub fn parse<R: io::Read>(
14        mut reader: R,
15        numbers_32bit: bool,
16        bool_cnt: u16,
17        numbers_cnt: u16,
18        string_cnt: u16,
19        table_bytes: u16,
20        aligned: bool,
21    ) -> Result<TermInfoData, Error> {
22        let bools = (0..bool_cnt)
23            .map(|_| match read_byte(&mut reader) {
24                Err(e) => Err(e),
25                Ok(1) => Ok(true),
26                Ok(_) => Ok(false),
27            })
28            .collect::<Result<_, _>>()?;
29
30        if bool_cnt % 2 == aligned.into() {
31            read_byte(&mut reader)?; // compensate for padding
32        }
33
34        let numbers = (0..numbers_cnt)
35            .map(|_| {
36                if numbers_32bit {
37                    read_i32(&mut reader)
38                } else {
39                    read_i16(&mut reader).map(i32::from)
40                }
41            })
42            .collect::<Result<_, _>>()?;
43
44        let strings: Box<[_]> = (0..string_cnt)
45            .map(|_| read_u16(&mut reader))
46            .collect::<Result<_, _>>()?;
47
48        for &off in &*strings {
49            if matches!(off, 0..=0xfffd if off > table_bytes) {
50                return Err(Error::OutOfBoundString {
51                    off,
52                    table_size: table_bytes,
53                });
54            }
55        }
56
57        let mut str_table = Vec::new();
58        let read = reader
59            .take(table_bytes.into())
60            .read_to_end(&mut str_table)?;
61        if read != table_bytes as usize {
62            return Err(io::Error::new(io::ErrorKind::Other, "end of file").into());
63        }
64
65        Ok(TermInfoData {
66            bools,
67            numbers,
68            strings,
69            str_table: str_table.into_boxed_slice(),
70        })
71    }
72}
73impl TermInfo {
74    /// Create terminfo database by parse byte-array directly
75    pub fn parse<R: io::Read>(mut reader: R) -> Result<TermInfo, Error> {
76        // read the magic number.
77        let magic = read_i16(&mut reader)?;
78
79        let number_32bit = match magic {
80            MAGIC_LEGACY => false,
81            MAGIC_32BIT => true,
82            num => return Err(Error::InvalidMagicNum(num)),
83        };
84
85        let names_bytes = read_non_neg_i16(&mut reader)?;
86        let bool_count = read_non_neg_i16(&mut reader)?;
87        let numbers_count = read_non_neg_i16(&mut reader)?;
88        let string_count = read_non_neg_i16(&mut reader)?;
89        let string_table_bytes = read_non_neg_i16(&mut reader)?;
90
91        if names_bytes == 0 {
92            return Err(Error::NoNames);
93        }
94
95        let term_names = read_str(&mut reader, names_bytes - 1)?;
96        let mut term_names = term_names.split('|').map(|it| it.trim().to_owned());
97        let name = term_names.next().unwrap();
98        let mut aliases: Vec<_> = term_names.collect();
99
100        if read_byte(&mut reader)? != b'\0' {
101            return Err(Error::NamesMissingNull);
102        }
103
104        let data = TermInfoData::parse(
105            &mut reader,
106            number_32bit,
107            bool_count,
108            numbers_count,
109            string_count,
110            string_table_bytes,
111            names_bytes % 2 == 0,
112        )?;
113
114        let extended =
115            try_parse_ext_capabilities(reader, number_32bit, string_table_bytes % 2 == 1)
116                .unwrap_or_default();
117
118        let res = TermInfo {
119            name,
120            description: aliases.pop().unwrap_or_default(),
121            aliases,
122            data,
123            extended,
124        };
125
126        Ok(res)
127    }
128}
129
130fn try_parse_ext_capabilities(
131    mut reader: impl io::Read,
132    number_32bit: bool,
133    unaligned: bool,
134) -> Result<Extended, Error> {
135    if unaligned {
136        read_byte(&mut reader)?; // compensate for padding
137    }
138
139    // the term(5) manpage doesn't describe this properly
140    // what is calls "the size of the string table" is
141    // actually the number of strings in the table (including names),
142    // and what it calls the "last offset in the string table" is
143    // actually the size in bytes of the string table
144    let bool_count = read_non_neg_i16(&mut reader)?;
145    let num_count = read_non_neg_i16(&mut reader)?;
146    let string_count = read_non_neg_i16(&mut reader)?;
147    let num_strings_in_table = read_non_neg_i16(&mut reader)?;
148    let table_bytes = read_non_neg_i16(&mut reader)?;
149    // debug_assert!(num_strings_in_table >= bool_count + num_count + 2 * string_count);
150
151    // TODO bounds checks
152    let data = TermInfoData::parse(
153        &mut reader,
154        number_32bit,
155        bool_count,
156        num_count,
157        num_strings_in_table,
158        table_bytes,
159        true,
160    )?;
161
162    if string_count as usize >= data.strings.len() {
163        return Err(Error::InvalidNames);
164    }
165
166    let names_off = data.strings[..string_count as usize]
167        .iter()
168        .rev()
169        .filter_map(|&off| Some(off + data.get_str_at(off)?.len() as u16))
170        .max()
171        .unwrap_or(0)
172        + 1;
173
174    let mut names = data.strings[string_count as usize..].iter().map(|&off| {
175        if matches!(off, 0..=0xfffd if off as usize + names_off as usize >= table_bytes as usize) {
176            return Some(Err(Error::OutOfBoundString {
177                off: off + names_off,
178                table_size: table_bytes,
179            }));
180        }
181        let res = get_str_with_offset(&*data.str_table, off, names_off)?.to_owned();
182        match String::from_utf8(res) {
183            Ok(res) => Some(Ok(res)),
184            Err(err) => Some(Err(err.into())),
185        }
186    });
187
188    let mut capabilities = HashMap::with_capacity((bool_count + num_count + string_count) as usize);
189
190    for (&val, name) in data.bools.iter().zip(&mut names) {
191        if let Some(name) = name {
192            if val {
193                capabilities.insert(name?, ValueStorage::True);
194            }
195        }
196    }
197
198    for (&val, name) in data.numbers.iter().zip(&mut names) {
199        if let Some(name) = name {
200            if val != 0xffff {
201                capabilities.insert(name?, ValueStorage::Number(val));
202            }
203        }
204    }
205    for (&val, name) in data.strings.iter().zip(&mut names) {
206        if let Some(name) = name {
207            if !matches!(val, 0xffff | 0xfffe) {
208                capabilities.insert(name?, ValueStorage::String(val));
209            }
210        }
211    }
212
213    let mut str_table = Vec::from(data.str_table);
214    str_table.truncate(names_off as usize);
215
216    Ok(Extended {
217        capabilities,
218        table: str_table.into_boxed_slice(),
219    })
220}
221
222fn read_i16<R: io::Read>(mut data: R) -> Result<i16, Error> {
223    let mut buf = [0; 2];
224    data.read_exact(&mut buf)?;
225    Ok(i16::from_le_bytes(buf))
226}
227
228fn read_u16<R: io::Read>(mut data: R) -> Result<u16, Error> {
229    let mut buf = [0; 2];
230    data.read_exact(&mut buf)?;
231    Ok(u16::from_le_bytes(buf))
232}
233
234/// According to the spec, these fields must be >= -1 where -1 means that the
235/// feature is not
236/// supported. Using 0 instead of -1 works because we skip sections with length
237/// 0.
238fn read_non_neg_i16(data: impl io::Read) -> Result<u16, Error> {
239    match read_i16(data)? {
240        n @ 0.. => Ok(n as u16),
241        -1 => Ok(0),
242        _ => Err(Error::InvalidNames),
243    }
244}
245
246fn read_i32<R: io::Read>(mut data: R) -> Result<i32, Error> {
247    let mut buf = [0; 4];
248    data.read_exact(&mut buf)?;
249    Ok(i32::from_le_bytes(buf))
250}
251
252fn read_str(data: impl io::Read, size: u16) -> Result<String, Error> {
253    let mut bytes = Vec::new();
254    let read = data.take(size.into()).read_to_end(&mut bytes)?;
255    if read != size as usize {
256        return Err(io::Error::new(io::ErrorKind::Other, "end of file").into());
257    }
258    let bytes = String::from_utf8(bytes)?;
259    Ok(bytes)
260}
261
262fn read_byte(r: &mut impl io::Read) -> io::Result<u8> {
263    match r.bytes().next() {
264        Some(s) => s,
265        None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
266    }
267}