1#![allow(non_upper_case_globals, missing_docs)]
2
3use std::collections::HashMap;
6use std::io;
7use std::io::prelude::*;
8
9use super::super::TermInfo;
10
11#[cfg(test)]
12mod tests;
13
14#[rustfmt::skip]
17pub(crate) static boolfnames: &[&str] = &["auto_left_margin", "auto_right_margin",
18    "no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type",
19    "hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above",
20    "memory_below", "move_insert_mode", "move_standout_mode", "over_strike", "status_line_esc_ok",
21    "dest_tabs_magic_smso", "tilde_glitch", "transparent_underline", "xon_xoff", "needs_xon_xoff",
22    "prtr_silent", "hard_cursor", "non_rev_rmcup", "no_pad_char", "non_dest_scroll_region",
23    "can_change", "back_color_erase", "hue_lightness_saturation", "col_addr_glitch",
24    "cr_cancels_micro_mode", "has_print_wheel", "row_addr_glitch", "semi_auto_right_margin",
25    "cpi_changes_res", "lpi_changes_res", "backspaces_with_bs", "crt_no_scrolling",
26    "no_correctly_working_cr", "gnu_has_meta_key", "linefeed_is_newline", "has_hardware_tabs",
27    "return_does_clr_eol"];
28
29#[rustfmt::skip]
30pub(crate) static boolnames: &[&str] = &["bw", "am", "xsb", "xhp", "xenl", "eo",
31    "gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon",
32    "nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy",
33    "xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"];
34
35#[rustfmt::skip]
36pub(crate) static numfnames: &[&str] = &[ "columns", "init_tabs", "lines",
37    "lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal",
38    "width_status_line", "num_labels", "label_height", "label_width", "max_attributes",
39    "maximum_windows", "max_colors", "max_pairs", "no_color_video", "buffer_capacity",
40    "dot_vert_spacing", "dot_horz_spacing", "max_micro_address", "max_micro_jump", "micro_col_size",
41    "micro_line_size", "number_of_pins", "output_res_char", "output_res_line",
42    "output_res_horz_inch", "output_res_vert_inch", "print_rate", "wide_char_size", "buttons",
43    "bit_image_entwining", "bit_image_type", "magic_cookie_glitch_ul", "carriage_return_delay",
44    "new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"];
45
46#[rustfmt::skip]
47pub(crate) static numnames: &[&str] = &[ "cols", "it", "lines", "lm", "xmc", "pb",
48    "vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv",
49    "spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs",
50    "btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"];
51
52#[rustfmt::skip]
53pub(crate) static stringfnames: &[&str] = &[ "back_tab", "bell", "carriage_return",
54    "change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos",
55    "column_address", "command_character", "cursor_address", "cursor_down", "cursor_home",
56    "cursor_invisible", "cursor_left", "cursor_mem_address", "cursor_normal", "cursor_right",
57    "cursor_to_ll", "cursor_up", "cursor_visible", "delete_character", "delete_line",
58    "dis_status_line", "down_half_line", "enter_alt_charset_mode", "enter_blink_mode",
59    "enter_bold_mode", "enter_ca_mode", "enter_delete_mode", "enter_dim_mode", "enter_insert_mode",
60    "enter_secure_mode", "enter_protected_mode", "enter_reverse_mode", "enter_standout_mode",
61    "enter_underline_mode", "erase_chars", "exit_alt_charset_mode", "exit_attribute_mode",
62    "exit_ca_mode", "exit_delete_mode", "exit_insert_mode", "exit_standout_mode",
63    "exit_underline_mode", "flash_screen", "form_feed", "from_status_line", "init_1string",
64    "init_2string", "init_3string", "init_file", "insert_character", "insert_line",
65    "insert_padding", "key_backspace", "key_catab", "key_clear", "key_ctab", "key_dc", "key_dl",
66    "key_down", "key_eic", "key_eol", "key_eos", "key_f0", "key_f1", "key_f10", "key_f2", "key_f3",
67    "key_f4", "key_f5", "key_f6", "key_f7", "key_f8", "key_f9", "key_home", "key_ic", "key_il",
68    "key_left", "key_ll", "key_npage", "key_ppage", "key_right", "key_sf", "key_sr", "key_stab",
69    "key_up", "keypad_local", "keypad_xmit", "lab_f0", "lab_f1", "lab_f10", "lab_f2", "lab_f3",
70    "lab_f4", "lab_f5", "lab_f6", "lab_f7", "lab_f8", "lab_f9", "meta_off", "meta_on", "newline",
71    "pad_char", "parm_dch", "parm_delete_line", "parm_down_cursor", "parm_ich", "parm_index",
72    "parm_insert_line", "parm_left_cursor", "parm_right_cursor", "parm_rindex", "parm_up_cursor",
73    "pkey_key", "pkey_local", "pkey_xmit", "print_screen", "prtr_off", "prtr_on", "repeat_char",
74    "reset_1string", "reset_2string", "reset_3string", "reset_file", "restore_cursor",
75    "row_address", "save_cursor", "scroll_forward", "scroll_reverse", "set_attributes", "set_tab",
76    "set_window", "tab", "to_status_line", "underline_char", "up_half_line", "init_prog", "key_a1",
77    "key_a3", "key_b2", "key_c1", "key_c3", "prtr_non", "char_padding", "acs_chars", "plab_norm",
78    "key_btab", "enter_xon_mode", "exit_xon_mode", "enter_am_mode", "exit_am_mode", "xon_character",
79    "xoff_character", "ena_acs", "label_on", "label_off", "key_beg", "key_cancel", "key_close",
80    "key_command", "key_copy", "key_create", "key_end", "key_enter", "key_exit", "key_find",
81    "key_help", "key_mark", "key_message", "key_move", "key_next", "key_open", "key_options",
82    "key_previous", "key_print", "key_redo", "key_reference", "key_refresh", "key_replace",
83    "key_restart", "key_resume", "key_save", "key_suspend", "key_undo", "key_sbeg", "key_scancel",
84    "key_scommand", "key_scopy", "key_screate", "key_sdc", "key_sdl", "key_select", "key_send",
85    "key_seol", "key_sexit", "key_sfind", "key_shelp", "key_shome", "key_sic", "key_sleft",
86    "key_smessage", "key_smove", "key_snext", "key_soptions", "key_sprevious", "key_sprint",
87    "key_sredo", "key_sreplace", "key_sright", "key_srsume", "key_ssave", "key_ssuspend",
88    "key_sundo", "req_for_input", "key_f11", "key_f12", "key_f13", "key_f14", "key_f15", "key_f16",
89    "key_f17", "key_f18", "key_f19", "key_f20", "key_f21", "key_f22", "key_f23", "key_f24",
90    "key_f25", "key_f26", "key_f27", "key_f28", "key_f29", "key_f30", "key_f31", "key_f32",
91    "key_f33", "key_f34", "key_f35", "key_f36", "key_f37", "key_f38", "key_f39", "key_f40",
92    "key_f41", "key_f42", "key_f43", "key_f44", "key_f45", "key_f46", "key_f47", "key_f48",
93    "key_f49", "key_f50", "key_f51", "key_f52", "key_f53", "key_f54", "key_f55", "key_f56",
94    "key_f57", "key_f58", "key_f59", "key_f60", "key_f61", "key_f62", "key_f63", "clr_bol",
95    "clear_margins", "set_left_margin", "set_right_margin", "label_format", "set_clock",
96    "display_clock", "remove_clock", "create_window", "goto_window", "hangup", "dial_phone",
97    "quick_dial", "tone", "pulse", "flash_hook", "fixed_pause", "wait_tone", "user0", "user1",
98    "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9", "orig_pair",
99    "orig_colors", "initialize_color", "initialize_pair", "set_color_pair", "set_foreground",
100    "set_background", "change_char_pitch", "change_line_pitch", "change_res_horz",
101    "change_res_vert", "define_char", "enter_doublewide_mode", "enter_draft_quality",
102    "enter_italics_mode", "enter_leftward_mode", "enter_micro_mode", "enter_near_letter_quality",
103    "enter_normal_quality", "enter_shadow_mode", "enter_subscript_mode", "enter_superscript_mode",
104    "enter_upward_mode", "exit_doublewide_mode", "exit_italics_mode", "exit_leftward_mode",
105    "exit_micro_mode", "exit_shadow_mode", "exit_subscript_mode", "exit_superscript_mode",
106    "exit_upward_mode", "micro_column_address", "micro_down", "micro_left", "micro_right",
107    "micro_row_address", "micro_up", "order_of_pins", "parm_down_micro", "parm_left_micro",
108    "parm_right_micro", "parm_up_micro", "select_char_set", "set_bottom_margin",
109    "set_bottom_margin_parm", "set_left_margin_parm", "set_right_margin_parm", "set_top_margin",
110    "set_top_margin_parm", "start_bit_image", "start_char_set_def", "stop_bit_image",
111    "stop_char_set_def", "subscript_characters", "superscript_characters", "these_cause_cr",
112    "zero_motion", "char_set_names", "key_mouse", "mouse_info", "req_mouse_pos", "get_mouse",
113    "set_a_foreground", "set_a_background", "pkey_plab", "device_type", "code_set_init",
114    "set0_des_seq", "set1_des_seq", "set2_des_seq", "set3_des_seq", "set_lr_margin",
115    "set_tb_margin", "bit_image_repeat", "bit_image_newline", "bit_image_carriage_return",
116    "color_names", "define_bit_image_region", "end_bit_image_region", "set_color_band",
117    "set_page_length", "display_pc_char", "enter_pc_charset_mode", "exit_pc_charset_mode",
118    "enter_scancode_mode", "exit_scancode_mode", "pc_term_options", "scancode_escape",
119    "alt_scancode_esc", "enter_horizontal_hl_mode", "enter_left_hl_mode", "enter_low_hl_mode",
120    "enter_right_hl_mode", "enter_top_hl_mode", "enter_vertical_hl_mode", "set_a_attributes",
121    "set_pglen_inch", "termcap_init2", "termcap_reset", "linefeed_if_not_lf", "backspace_if_not_bs",
122    "other_non_function_keys", "arrow_key_map", "acs_ulcorner", "acs_llcorner", "acs_urcorner",
123    "acs_lrcorner", "acs_ltee", "acs_rtee", "acs_btee", "acs_ttee", "acs_hline", "acs_vline",
124    "acs_plus", "memory_lock", "memory_unlock", "box_chars_1"];
125
126#[rustfmt::skip]
127pub(crate) static stringnames: &[&str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
128    "_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1",
129    "ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc",
130    "dim", "smir", "invis", "prot", "rev", "smso", "smul", "ech", "rmacs", "sgr0", "rmcup", "rmdc",
131    "rmir", "rmso", "rmul", "flash", "ff", "fsl", "is1", "is2", "is3", "if", "ich1", "il1", "ip",
132    "kbs", "ktbc", "kclr", "kctab", "_", "_", "kcud1", "_", "_", "_", "_", "_", "_", "_", "_", "_",
133    "_", "_", "_", "_", "_", "khome", "_", "_", "kcub1", "_", "knp", "kpp", "kcuf1", "_", "_",
134    "khts", "_", "rmkx", "smkx", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "rmm", "_",
135    "_", "pad", "dch", "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey",
136    "pfloc", "pfx", "mc0", "mc4", "_", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind",
137    "ri", "sgr", "_", "wind", "_", "tsl", "uc", "hu", "iprog", "_", "_", "_", "_", "_", "mc5p",
138    "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "_", "smln",
139    "rmln", "_", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "_", "kent", "kext", "kfnd", "khlp",
140    "kmrk", "kmsg", "kmov", "knxt", "kopn", "kopt", "kprv", "kprt", "krdo", "kref", "krfr", "krpl",
141    "krst", "kres", "ksav", "kspd", "kund", "kBEG", "kCAN", "kCMD", "kCPY", "kCRT", "_", "_",
142    "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", "kHOM", "_", "kLFT", "kMSG", "kMOV", "kNXT",
143    "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "_", "_",
144    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
145    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
146    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
147    "dclk", "rmclk", "cwin", "wingo", "_", "dial", "qdial", "_", "_", "hook", "pause", "wait", "_",
148    "_", "_", "_", "_", "_", "_", "_", "_", "_", "op", "oc", "initc", "initp", "scp", "setf",
149    "setb", "cpi", "lpi", "chr", "cvr", "defc", "swidm", "sdrfq", "sitm", "slm", "smicm", "snlq",
150    "snrmq", "sshm", "ssubm", "ssupm", "sum", "rwidm", "ritm", "rlm", "rmicm", "rshm", "rsubm",
151    "rsupm", "rum", "mhpa", "mcud1", "mcub1", "mcuf1", "mvpa", "mcuu1", "porder", "mcud", "mcub",
152    "mcuf", "mcuu", "scs", "smgb", "smgbp", "smglp", "smgrp", "smgt", "smgtp", "sbim", "scsd",
153    "rbim", "rcsd", "subcs", "supcs", "docr", "zerom", "csnm", "kmous", "minfo", "reqmp", "getm",
154    "setaf", "setab", "pfxl", "devt", "csin", "s0ds", "s1ds", "s2ds", "s3ds", "smglr", "smgtb",
155    "birep", "binel", "bicr", "colornm", "defbi", "endbi", "setcolor", "slines", "dispc", "smpch",
156    "rmpch", "smsc", "rmsc", "pctrm", "scesc", "scesa", "ehhlm", "elhlm", "elohlm", "erhlm",
157    "ethlm", "evhlm", "sgr1", "slength", "OTi2", "OTrs", "OTnl", "OTbs", "OTko", "OTma", "OTG2",
158    "OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
159    "box1"];
160
161fn read_le_u16(r: &mut dyn io::Read) -> io::Result<u16> {
162    let mut b = [0; 2];
163    r.read_exact(&mut b)?;
164    Ok((b[0] as u16) | ((b[1] as u16) << 8))
165}
166
167fn read_le_u32(r: &mut dyn io::Read) -> io::Result<u32> {
168    let mut b = [0; 4];
169    r.read_exact(&mut b)?;
170    Ok((b[0] as u32) | ((b[1] as u32) << 8) | ((b[2] as u32) << 16) | ((b[3] as u32) << 24))
171}
172
173fn read_byte(r: &mut dyn io::Read) -> io::Result<u8> {
174    match r.bytes().next() {
175        Some(s) => s,
176        None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
177    }
178}
179
180pub(crate) fn parse(file: &mut dyn io::Read, longnames: bool) -> Result<TermInfo, String> {
183    macro_rules! t( ($e:expr) => (
184        match $e {
185            Ok(e) => e,
186            Err(e) => return Err(e.to_string())
187        }
188    ) );
189
190    let (bnames, snames, nnames) = if longnames {
191        (boolfnames, stringfnames, numfnames)
192    } else {
193        (boolnames, stringnames, numnames)
194    };
195
196    let magic = t!(read_le_u16(file));
198
199    let extended = match magic {
200        0o0432 => false,
201        0o01036 => true,
202        _ => return Err(format!("invalid magic number, found {magic:o}")),
203    };
204
205    macro_rules! read_nonneg {
208        () => {{
209            match t!(read_le_u16(file)) as i16 {
210                n if n >= 0 => n as usize,
211                -1 => 0,
212                _ => return Err("incompatible file: length fields must be  >= -1".to_string()),
213            }
214        }};
215    }
216
217    let names_bytes = read_nonneg!();
218    let bools_bytes = read_nonneg!();
219    let numbers_count = read_nonneg!();
220    let string_offsets_count = read_nonneg!();
221    let string_table_bytes = read_nonneg!();
222
223    if names_bytes == 0 {
224        return Err("incompatible file: names field must be at least 1 byte wide".to_string());
225    }
226
227    if bools_bytes > boolnames.len() {
228        return Err("incompatible file: more booleans than expected".to_string());
229    }
230
231    if numbers_count > numnames.len() {
232        return Err("incompatible file: more numbers than expected".to_string());
233    }
234
235    if string_offsets_count > stringnames.len() {
236        return Err("incompatible file: more string offsets than expected".to_string());
237    }
238
239    let mut bytes = Vec::new();
241    t!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
242    let names_str = match String::from_utf8(bytes) {
243        Ok(s) => s,
244        Err(_) => return Err("input not utf-8".to_string()),
245    };
246
247    let term_names: Vec<String> = names_str.split('|').map(|s| s.to_string()).collect();
248    if t!(read_byte(file)) != b'\0' {
250        return Err("incompatible file: missing null terminator for names section".to_string());
251    }
252
253    let bools_map: HashMap<String, bool> = t! {
254        (0..bools_bytes).filter_map(|i| match read_byte(file) {
255            Err(e) => Some(Err(e)),
256            Ok(1) => Some(Ok((bnames[i].to_string(), true))),
257            Ok(_) => None
258        }).collect()
259    };
260
261    if (bools_bytes + names_bytes) % 2 == 1 {
262        t!(read_byte(file)); }
264
265    let numbers_map: HashMap<String, u32> = t! {
266        (0..numbers_count).filter_map(|i| {
267            let number = if extended { read_le_u32(file) } else { read_le_u16(file).map(Into::into) };
268
269            match number {
270                Ok(0xFFFF) => None,
271                Ok(n) => Some(Ok((nnames[i].to_string(), n))),
272                Err(e) => Some(Err(e))
273            }
274        }).collect()
275    };
276
277    let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
278        let string_offsets: Vec<u16> =
279            t!((0..string_offsets_count).map(|_| read_le_u16(file)).collect());
280
281        let mut string_table = Vec::new();
282        t!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
283
284        t!(string_offsets
285            .into_iter()
286            .enumerate()
287            .filter(|&(_, offset)| {
288                offset != 0xFFFF
290            })
291            .map(|(i, offset)| {
292                let offset = offset as usize;
293
294                let name = if snames[i] == "_" { stringfnames[i] } else { snames[i] };
295
296                if offset == 0xFFFE {
297                    return Ok((name.to_string(), Vec::new()));
300                }
301
302                let nulpos = string_table[offset..string_table_bytes].iter().position(|&b| b == 0);
304                match nulpos {
305                    Some(len) => {
306                        Ok((name.to_string(), string_table[offset..offset + len].to_vec()))
307                    }
308                    None => Err("invalid file: missing NUL in string_table".to_string()),
309                }
310            })
311            .collect())
312    } else {
313        HashMap::new()
314    };
315
316    Ok(TermInfo { names: term_names, bools: bools_map, numbers: numbers_map, strings: string_map })
318}
319
320pub(crate) fn msys_terminfo() -> TermInfo {
322    let mut strings = HashMap::new();
323    strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
324    strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
325    strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
326    strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
327
328    let mut numbers = HashMap::new();
329    numbers.insert("colors".to_string(), 8);
330
331    TermInfo {
332        names: vec!["cygwin".to_string()], bools: HashMap::new(),
334        numbers,
335        strings,
336    }
337}