1#![allow(non_upper_case_globals, missing_docs)]
2
3//! ncurses-compatible compiled terminfo format parsing (term(5))
4
5use std::collections::HashMap;
6use std::io;
7use std::io::prelude::*;
8
9use super::super::TermInfo;
10
11#[cfg(test)]
12mod tests;
13
14// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
15
16#[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: [u8; 2] = [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: [u8; 4] = [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: Result) => s,
176 None => Err(io::const_error!(io::ErrorKind::Other, "end of file")),
177 }
178}
179
180/// Parse a compiled terminfo entry, using long capability names if `longnames`
181/// is true
182pub(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 // Check magic number
197 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 // According to the spec, these fields must be >= -1 where -1 means that the feature is not
206 // supported. Using 0 instead of -1 works because we skip sections with length 0.
207 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 // don't read NUL
240 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 // consume NUL
249 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)); // compensate for padding
263 }
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 // non-entry
289 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 // undocumented: FFFE indicates cap@, which means the capability is not present
298 // unsure if the handling for this is correct
299 return Ok((name.to_string(), Vec::new()));
300 }
301
302 // Find the offset of the NUL we want to go to
303 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 // And that's all there is to it
317 Ok(TermInfo { names: term_names, bools: bools_map, numbers: numbers_map, strings: string_map })
318}
319
320/// Creates a dummy TermInfo struct for msys terminals
321pub(crate) fn msys_terminfo() -> TermInfo {
322 let mut strings: HashMap> = HashMap::new();
323 strings.insert(k:"sgr0".to_string(), v:b"\x1B[0m".to_vec());
324 strings.insert(k:"bold".to_string(), v:b"\x1B[1m".to_vec());
325 strings.insert(k:"setaf".to_string(), v:b"\x1B[3%p1%dm".to_vec());
326 strings.insert(k:"setab".to_string(), v:b"\x1B[4%p1%dm".to_vec());
327
328 let mut numbers: HashMap = HashMap::new();
329 numbers.insert(k:"colors".to_string(), v:8);
330
331 TermInfo {
332 names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
333 bools: HashMap::new(),
334 numbers,
335 strings,
336 }
337}
338