1use crate::{get_str_with_offset, Error, Extended, TermInfo, TermInfoData, ValueStorage};
2use std::collections::HashMap;
3use std::io;
4use std::io::Read;
5
6const MAGIC_LEGACY: i16 = 0x11A;
8const MAGIC_32BIT: i16 = 0x21E;
10
11impl TermInfoData {
12 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)?; }
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 pub fn parse<R: io::Read>(mut reader: R) -> Result<TermInfo, Error> {
76 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)?; }
138
139 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 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
234fn 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}