1use std::collections::HashMap;
8use std::fmt::{Debug, Display, Formatter};
9use std::fs;
10use std::fs::File;
11use std::io::Read;
12use std::path::PathBuf;
13
14use crate::capabilities::{BoolCapability, NumberCapability, StringCapability};
15
16const MAGIC_LEGACY: i16 = 0x11A;
18const MAGIC_32BIT: i16 = 0x21E;
20const NAMES_OFFSET: usize = 12;
22
23const EXT_HEADER_SIZE: usize = 10;
24const TERMINFO_HEADER_SIZE: usize = 12;
25const TERMINFO_MAX_SIZE: usize = 4096;
26
27#[derive(Debug)]
29pub struct TermInfo {
30 data: Vec<u8>,
31 read_i32: bool,
32 int_size: usize,
33 sec_name_size: usize,
34 sec_bool_size: usize,
35 sec_number_size: usize,
36 sec_str_offsets_size: usize,
37 sec_str_table_size: usize,
38 ext_bool: HashMap<String, bool>,
39 ext_numbers: HashMap<String, i32>,
40 ext_strings: HashMap<String, String>,
41}
42
43#[derive(Debug)]
44pub enum TermInfoError {
45 InvalidDataSize,
46 InvalidMagicNum,
47 InvalidData,
48 InvalidName,
49}
50
51impl Display for TermInfoError {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 write!(f, "{}",
54 match self {
55 TermInfoError::InvalidDataSize => "file/data length is above 4096 bytes or under 12 bytes",
56 TermInfoError::InvalidMagicNum => "magic number mismatch",
57 TermInfoError::InvalidData => "terminfo data is invalid or corrupt",
58 TermInfoError::InvalidName => "terminfo not found"
59 })
60 }
61}
62
63impl TermInfo {
64 pub fn get_string(&self, cap: StringCapability) -> Option<String> {
79 let idx = cap as usize;
80 if idx >= self.sec_str_offsets_size {
81 None
82 } else {
83 let tbl_idx = read_i16(&self.data, self.offset_str_offsets() + (idx * 2)) as usize;
84 if tbl_idx == 0 {
85 None
86 } else {
87 Some(read_str(&self.data, self.offset_str_table() + tbl_idx).0.to_string())
88 }
89 }
90 }
91
92 pub fn get_number(&self, cap: NumberCapability) -> Option<i32> {
107 let idx = cap as usize;
108 if idx >= self.sec_number_size {
109 None
110 } else {
111 Some(read_int(&self.data, self.offset_number() + (idx * self.int_size), self.read_i32))
112 }
113 }
114
115 pub fn get_bool(&self, cap: BoolCapability) -> Option<bool> {
130 let idx = cap as usize;
131 if idx >= self.sec_bool_size {
132 None
133 } else {
134 Some(self.data[(self.offset_bool() + idx)] == 1)
135 }
136 }
137
138 pub fn get_ext_bool(&self, name: &str) -> Option<&bool> {
152 self.ext_bool.get(name)
153 }
154
155 pub fn get_ext_number(&self, name: &str) -> Option<&i32> {
169 self.ext_numbers.get(name)
170 }
171
172 pub fn get_ext_string(&self, name: &str) -> Option<&String> {
186 self.ext_strings.get(name)
187 }
188
189 pub fn from_env() -> Result<Self, TermInfoError> {
191 if let Ok(term) = std::env::var("TERM") {
192 TermInfo::from_name(term.as_str())
193 } else {
194 Err(TermInfoError::InvalidName)
195 }
196 }
197
198 pub fn from_name(name: &str) -> Result<Self, TermInfoError> {
200 if name.len() == 0 {
201 return Err(TermInfoError::InvalidName);
202 }
203
204 let first_letter = name.chars().nth(0).unwrap_or('X');
205
206 let mut paths: Vec<PathBuf> = Vec::new();
207 if let Ok(env_terminfo) = std::env::var("TERMINFO") {
209 paths.push(PathBuf::from(format!("{}/{}/{}", env_terminfo, first_letter, name)));
210 }
211
212 if let Ok(env_home) = std::env::var("HOME") {
214 paths.push(PathBuf::from(format!("{}/{}/{}", env_home, first_letter, name)));
215 }
216
217 paths.push(PathBuf::from(format!("/etc/terminfo/{}/{}", first_letter, name)));
219 paths.push(PathBuf::from(format!("/lib/terminfo/{}/{}", first_letter, name)));
220 paths.push(PathBuf::from(format!("/usr/share/terminfo/{}/{}", first_letter, name)));
221 paths.push(PathBuf::from(format!("/usr/share/misc/terminfo/{}/{}", first_letter, name)));
222
223 paths.push(PathBuf::from(format!("/etc/terminfo/{:X}/{}", first_letter as u8, name)));
225 paths.push(PathBuf::from(format!("/lib/terminfo/{:X}/{}", first_letter as u8, name)));
226 paths.push(PathBuf::from(format!("/usr/share/terminfo/{:X}/{}", first_letter as u8, name)));
227 paths.push(PathBuf::from(format!("/usr/share/misc/terminfo/{:X}/{}", first_letter as u8, name)));
228
229 for path in paths {
230 if path.exists() {
231 return TermInfo::from_file(path.to_str().unwrap())
232 }
233 }
234
235 Err(TermInfoError::InvalidName)
236 }
237
238 pub fn from_file(filename: &str) -> Result<Self, TermInfoError> {
240 TermInfo::from_data(read_all_bytes_from_file(filename))
241 }
242
243 pub fn from_data(data: Vec<u8>) -> Result<TermInfo, TermInfoError> {
245 if data.len() < TERMINFO_HEADER_SIZE || data.len() > TERMINFO_MAX_SIZE {
246 return Err(TermInfoError::InvalidDataSize);
247 }
248
249 let mut info = TermInfo {
250 data,
251 read_i32: false,
252 int_size: 2,
253 sec_name_size: 0,
254 sec_bool_size: 0,
255 sec_number_size: 0,
256 sec_str_offsets_size: 0,
257 sec_str_table_size: 0,
258 ext_bool: HashMap::new(),
259 ext_numbers: HashMap::new(),
260 ext_strings: HashMap::new(),
261 };
262
263 let magic = read_i16(&info.data, 0);
265
266 info.read_i32 = match magic {
267 MAGIC_LEGACY => false,
268 MAGIC_32BIT => true,
269 _ => return Err(TermInfoError::InvalidMagicNum),
270 };
271
272 info.int_size = match info.read_i32 {
273 true => 4,
274 false => 2,
275 };
276
277 if read_i16(&info.data, 2) < 0
278 || read_i16(&info.data, 4) < 0
279 || read_i16(&info.data, 6) < 0
280 || read_i16(&info.data, 8) < 0
281 || read_i16(&info.data, 10) < 0
282 {
283 return Err(TermInfoError::InvalidData)
284 }
285
286 info.sec_name_size = read_i16(&info.data, 2) as usize;
287 info.sec_bool_size = read_i16(&info.data, 4) as usize;
288 info.sec_number_size = read_i16(&info.data, 6) as usize;
289 info.sec_str_offsets_size = read_i16(&info.data, 8) as usize;
290 info.sec_str_table_size = read_i16(&info.data, 10) as usize;
291
292
293 let mut ext_offset = round_up_even(info.offset_str_table() + info.sec_str_table_size);
299
300 if ext_offset + EXT_HEADER_SIZE < info.data.len() {
302 if read_i16(&info.data, ext_offset) < 0
303 || read_i16(&info.data, ext_offset + 2) < 0
304 || read_i16(&info.data, ext_offset + 4) < 0
305 {
306 return Ok(info);
308 }
309
310 let ext_bool_count = read_i16(&info.data, ext_offset) as usize;
311 let ext_number_count = read_i16(&info.data, ext_offset + 2) as usize;
312 let ext_str_count = read_i16(&info.data, ext_offset + 4) as usize;
313
314 let mut bool_values = Vec::with_capacity(ext_bool_count);
316
317 ext_offset += EXT_HEADER_SIZE;
318 for i in 0..ext_bool_count {
319 let pos = ext_offset + read_i16(&info.data, ext_offset + i * 2) as usize;
320
321 if pos == 0 || ext_offset > info.data.len() {
322 return Ok(info);
323 }
324
325 bool_values.push(info.data[pos] == 1);
326 }
327
328 let mut number_values = Vec::with_capacity(ext_number_count);
330
331 ext_offset += if ext_bool_count == 0 { 0 } else { (ext_bool_count - 1) * 2 };
332 for i in 0..ext_number_count {
333 let pos = ext_offset + read_i16(&info.data, ext_offset + i * 2) as usize;
334
335 if pos == 0 || ext_offset > info.data.len() {
336 return Ok(info);
337 }
338
339 &number_values.push(read_int(&info.data, pos, info.read_i32));
340 }
341
342 let mut str_values = Vec::with_capacity(ext_str_count);
347
348 ext_offset += if ext_number_count == 0 { 0 } else { (ext_number_count - 1) * 2 };
349
350 let tbl_offset = ext_offset
351 + ext_str_count * 2
352 + (ext_bool_count + ext_number_count + ext_str_count) * 2;
353 let mut last_end: usize = 0;
354 for i in 0..ext_str_count {
355 let pos = tbl_offset + read_i16(&info.data, ext_offset + i * 2) as usize;
356
357 if pos == 0 || ext_offset > info.data.len() {
358 return Ok(info);
359 }
360
361 let (str, null_term_pos) = read_str(&info.data, pos);
362 &str_values.push(str);
363 last_end = last_end.max(null_term_pos)
364 }
365
366 let mut names = Vec::with_capacity(ext_bool_count + ext_number_count + ext_str_count);
369 let mut pos = last_end + 1;
370
371 while pos < info.data.len() {
372 let (str, null_term_pos) = read_str(&info.data, pos);
373 &names.push(str);
374 pos = null_term_pos + 1;
375 }
376
377 for i in 0..ext_bool_count {
379 &info.ext_bool.insert(names[i].to_string(), bool_values[i]);
380 }
381
382 for i in 0..ext_number_count {
384 &info.ext_numbers
385 .insert(names[i + ext_bool_count - 1].to_string(), number_values[i]);
386 }
387
388 for i in 0..ext_str_count {
390 &info.ext_strings.insert(
391 names[i + ext_bool_count + ext_number_count].to_string(),
392 str_values[i].to_string(),
393 );
394 }
395 }
396
397 Ok(info)
398 }
399
400 fn offset_bool(&self) -> usize {
402 NAMES_OFFSET + self.sec_name_size
403 }
404 fn offset_number(&self) -> usize {
406 round_up_even(self.offset_bool() + self.sec_bool_size)
407 }
408 fn offset_str_offsets(&self) -> usize {
411 self.offset_number() + (self.sec_number_size * self.int_size)
412 }
413 fn offset_str_table(&self) -> usize {
415 self.offset_str_offsets() + (self.sec_str_offsets_size * 2)
416 }
417}
418
419fn read_int(data: &Vec<u8>, pos: usize, as_32bit: bool) -> i32 {
430 match as_32bit {
431 true => read_i32(data, pos),
432 false => read_i16(data, pos) as i32,
433 }
434}
435
436fn read_i32(data: &Vec<u8>, pos: usize) -> i32 {
441 ((data[pos] as i32) << 24)
442 | ((data[pos + 1] as i32) << 16)
443 | ((data[pos + 2] as i32) << 8)
444 | (data[pos + 3] as i32)
445}
446
447fn read_i16(data: &Vec<u8>, pos: usize) -> i16 {
452 ((data[pos + 1] as i16) << 8) | (data[pos] as i16)
453}
454
455fn read_all_bytes_from_file(filename: &str) -> Vec<u8> {
460 let mut f = File::open(&filename).expect("no file found");
461 let metadata = fs::metadata(&filename).expect("unable to read metadata");
462 let mut buffer = vec![0; metadata.len() as usize];
463 f.read(&mut buffer).expect("buffer overflow");
464
465 buffer
466}
467
468fn read_str(data: &Vec<u8>, pos: usize) -> (String, usize) {
473 let null_term = find_null_term(data, pos);
474 (data[pos..null_term].iter()
475 .map(|c| *c as char)
476 .collect::<String>(),
477 null_term)
478}
479
480fn find_null_term(data: &Vec<u8>, pos: usize) -> usize {
482 let mut term_pos = pos as i32;
483 while term_pos < data.len() as i32 && data[term_pos as usize] != '\0' as u8 {
484 term_pos += 1;
485 }
486 term_pos as usize
487}
488
489fn round_up_even(n: usize) -> usize {
491 match n % 2 {
492 1 => n + 1,
493 _ => n,
494 }
495}