Commit | Line | Data |
---|---|---|
775e241e TT |
1 | /* `dir', `vdir' and `ls' directory listing programs for GNU. |
2 | ||
3 | Modified by Chet Ramey for Readline. | |
4 | ||
cb41b9e7 TT |
5 | Copyright (C) 1985, 1988, 1990-1991, 1995-2010, 2012, 2017 |
6 | Free Software Foundation, Inc. | |
775e241e TT |
7 | |
8 | This program is free software: you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation, either version 3 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | This program is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ | |
20 | ||
21 | /* Written by Richard Stallman and David MacKenzie. */ | |
22 | ||
23 | /* Color support by Peter Anvin <Peter.Anvin@linux.org> and Dennis | |
24 | Flaherty <dennisf@denix.elk.miles.com> based on original patches by | |
25 | Greg Lee <lee@uhunix.uhcc.hawaii.edu>. */ | |
26 | ||
27 | #define READLINE_LIBRARY | |
28 | ||
29 | #if defined (HAVE_CONFIG_H) | |
30 | # include <config.h> | |
31 | #endif | |
32 | ||
33 | #include <stdio.h> | |
34 | ||
35 | // strdup() / strcpy() | |
36 | #if defined (HAVE_STRING_H) | |
37 | # include <string.h> | |
38 | #else /* !HAVE_STRING_H */ | |
39 | # include <strings.h> | |
40 | #endif /* !HAVE_STRING_H */ | |
41 | ||
42 | // abort() | |
43 | #if defined (HAVE_STDLIB_H) | |
44 | # include <stdlib.h> | |
45 | #else | |
46 | # include "ansi_stdlib.h" | |
47 | #endif /* HAVE_STDLIB_H */ | |
48 | ||
49 | #include "rldefs.h" // STREQ, savestring | |
50 | #include "readline.h" | |
51 | #include "rlprivate.h" | |
52 | #include "rlshell.h" | |
53 | #include "xmalloc.h" | |
54 | ||
55 | #include "colors.h" | |
56 | #include "parse-colors.h" | |
57 | ||
58 | #if defined (COLOR_SUPPORT) | |
59 | ||
60 | static bool get_funky_string (char **dest, const char **src, bool equals_end, size_t *output_count); | |
61 | ||
62 | struct bin_str _rl_color_indicator[] = | |
63 | { | |
64 | { LEN_STR_PAIR ("\033[") }, // lc: Left of color sequence | |
65 | { LEN_STR_PAIR ("m") }, // rc: Right of color sequence | |
66 | { 0, NULL }, // ec: End color (replaces lc+no+rc) | |
67 | { LEN_STR_PAIR ("0") }, // rs: Reset to ordinary colors | |
68 | { 0, NULL }, // no: Normal | |
69 | { 0, NULL }, // fi: File: default | |
70 | { LEN_STR_PAIR ("01;34") }, // di: Directory: bright blue | |
71 | { LEN_STR_PAIR ("01;36") }, // ln: Symlink: bright cyan | |
72 | { LEN_STR_PAIR ("33") }, // pi: Pipe: yellow/brown | |
73 | { LEN_STR_PAIR ("01;35") }, // so: Socket: bright magenta | |
74 | { LEN_STR_PAIR ("01;33") }, // bd: Block device: bright yellow | |
75 | { LEN_STR_PAIR ("01;33") }, // cd: Char device: bright yellow | |
76 | { 0, NULL }, // mi: Missing file: undefined | |
77 | { 0, NULL }, // or: Orphaned symlink: undefined | |
78 | { LEN_STR_PAIR ("01;32") }, // ex: Executable: bright green | |
79 | { LEN_STR_PAIR ("01;35") }, // do: Door: bright magenta | |
80 | { LEN_STR_PAIR ("37;41") }, // su: setuid: white on red | |
81 | { LEN_STR_PAIR ("30;43") }, // sg: setgid: black on yellow | |
82 | { LEN_STR_PAIR ("37;44") }, // st: sticky: black on blue | |
83 | { LEN_STR_PAIR ("34;42") }, // ow: other-writable: blue on green | |
84 | { LEN_STR_PAIR ("30;42") }, // tw: ow w/ sticky: black on green | |
85 | { LEN_STR_PAIR ("30;41") }, // ca: black on red | |
86 | { 0, NULL }, // mh: disabled by default | |
87 | { LEN_STR_PAIR ("\033[K") }, // cl: clear to end of line | |
88 | }; | |
89 | ||
90 | /* Parse a string as part of the LS_COLORS variable; this may involve | |
91 | decoding all kinds of escape characters. If equals_end is set an | |
92 | unescaped equal sign ends the string, otherwise only a : or \0 | |
93 | does. Set *OUTPUT_COUNT to the number of bytes output. Return | |
94 | true if successful. | |
95 | ||
96 | The resulting string is *not* null-terminated, but may contain | |
97 | embedded nulls. | |
98 | ||
99 | Note that both dest and src are char **; on return they point to | |
100 | the first free byte after the array and the character that ended | |
101 | the input string, respectively. */ | |
102 | ||
103 | static bool | |
104 | get_funky_string (char **dest, const char **src, bool equals_end, size_t *output_count) { | |
105 | char num; /* For numerical codes */ | |
106 | size_t count; /* Something to count with */ | |
107 | enum { | |
108 | ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX, ST_CARET, ST_END, ST_ERROR | |
109 | } state; | |
110 | const char *p; | |
111 | char *q; | |
112 | ||
113 | p = *src; /* We don't want to double-indirect */ | |
114 | q = *dest; /* the whole darn time. */ | |
115 | ||
116 | count = 0; /* No characters counted in yet. */ | |
117 | num = 0; | |
118 | ||
119 | state = ST_GND; /* Start in ground state. */ | |
120 | while (state < ST_END) | |
121 | { | |
122 | switch (state) | |
123 | { | |
124 | case ST_GND: /* Ground state (no escapes) */ | |
125 | switch (*p) | |
126 | { | |
127 | case ':': | |
128 | case '\0': | |
129 | state = ST_END; /* End of string */ | |
130 | break; | |
131 | case '\\': | |
132 | state = ST_BACKSLASH; /* Backslash scape sequence */ | |
133 | ++p; | |
134 | break; | |
135 | case '^': | |
136 | state = ST_CARET; /* Caret escape */ | |
137 | ++p; | |
138 | break; | |
139 | case '=': | |
140 | if (equals_end) | |
141 | { | |
142 | state = ST_END; /* End */ | |
143 | break; | |
144 | } | |
145 | /* else fall through */ | |
146 | default: | |
147 | *(q++) = *(p++); | |
148 | ++count; | |
149 | break; | |
150 | } | |
151 | break; | |
152 | ||
153 | case ST_BACKSLASH: /* Backslash escaped character */ | |
154 | switch (*p) | |
155 | { | |
156 | case '0': | |
157 | case '1': | |
158 | case '2': | |
159 | case '3': | |
160 | case '4': | |
161 | case '5': | |
162 | case '6': | |
163 | case '7': | |
164 | state = ST_OCTAL; /* Octal sequence */ | |
165 | num = *p - '0'; | |
166 | break; | |
167 | case 'x': | |
168 | case 'X': | |
169 | state = ST_HEX; /* Hex sequence */ | |
170 | num = 0; | |
171 | break; | |
172 | case 'a': /* Bell */ | |
173 | num = '\a'; | |
174 | break; | |
175 | case 'b': /* Backspace */ | |
176 | num = '\b'; | |
177 | break; | |
178 | case 'e': /* Escape */ | |
179 | num = 27; | |
180 | break; | |
181 | case 'f': /* Form feed */ | |
182 | num = '\f'; | |
183 | break; | |
184 | case 'n': /* Newline */ | |
185 | num = '\n'; | |
186 | break; | |
187 | case 'r': /* Carriage return */ | |
188 | num = '\r'; | |
189 | break; | |
190 | case 't': /* Tab */ | |
191 | num = '\t'; | |
192 | break; | |
193 | case 'v': /* Vtab */ | |
194 | num = '\v'; | |
195 | break; | |
196 | case '?': /* Delete */ | |
197 | num = 127; | |
198 | break; | |
199 | case '_': /* Space */ | |
200 | num = ' '; | |
201 | break; | |
202 | case '\0': /* End of string */ | |
203 | state = ST_ERROR; /* Error! */ | |
204 | break; | |
205 | default: /* Escaped character like \ ^ : = */ | |
206 | num = *p; | |
207 | break; | |
208 | } | |
209 | if (state == ST_BACKSLASH) | |
210 | { | |
211 | *(q++) = num; | |
212 | ++count; | |
213 | state = ST_GND; | |
214 | } | |
215 | ++p; | |
216 | break; | |
217 | ||
218 | case ST_OCTAL: /* Octal sequence */ | |
219 | if (*p < '0' || *p > '7') | |
220 | { | |
221 | *(q++) = num; | |
222 | ++count; | |
223 | state = ST_GND; | |
224 | } | |
225 | else | |
226 | num = (num << 3) + (*(p++) - '0'); | |
227 | break; | |
228 | ||
229 | case ST_HEX: /* Hex sequence */ | |
230 | switch (*p) | |
231 | { | |
232 | case '0': | |
233 | case '1': | |
234 | case '2': | |
235 | case '3': | |
236 | case '4': | |
237 | case '5': | |
238 | case '6': | |
239 | case '7': | |
240 | case '8': | |
241 | case '9': | |
242 | num = (num << 4) + (*(p++) - '0'); | |
243 | break; | |
244 | case 'a': | |
245 | case 'b': | |
246 | case 'c': | |
247 | case 'd': | |
248 | case 'e': | |
249 | case 'f': | |
250 | num = (num << 4) + (*(p++) - 'a') + 10; | |
251 | break; | |
252 | case 'A': | |
253 | case 'B': | |
254 | case 'C': | |
255 | case 'D': | |
256 | case 'E': | |
257 | case 'F': | |
258 | num = (num << 4) + (*(p++) - 'A') + 10; | |
259 | break; | |
260 | default: | |
261 | *(q++) = num; | |
262 | ++count; | |
263 | state = ST_GND; | |
264 | break; | |
265 | } | |
266 | break; | |
267 | ||
268 | case ST_CARET: /* Caret escape */ | |
269 | state = ST_GND; /* Should be the next state... */ | |
270 | if (*p >= '@' && *p <= '~') | |
271 | { | |
272 | *(q++) = *(p++) & 037; | |
273 | ++count; | |
274 | } | |
275 | else if (*p == '?') | |
276 | { | |
277 | *(q++) = 127; | |
278 | ++count; | |
279 | } | |
280 | else | |
281 | state = ST_ERROR; | |
282 | break; | |
283 | ||
284 | default: | |
285 | /* should we ? */ | |
286 | /* abort (); no, we should not */ | |
287 | state = ST_ERROR; | |
288 | break; | |
289 | } | |
290 | } | |
291 | ||
292 | *dest = q; | |
293 | *src = p; | |
294 | *output_count = count; | |
295 | ||
296 | return state != ST_ERROR; | |
297 | } | |
298 | #endif /* COLOR_SUPPORT */ | |
299 | ||
cb41b9e7 | 300 | void _rl_parse_colors(void) |
775e241e TT |
301 | { |
302 | #if defined (COLOR_SUPPORT) | |
303 | const char *p; /* Pointer to character being parsed */ | |
304 | char *buf; /* color_buf buffer pointer */ | |
305 | int state; /* State of parser */ | |
306 | int ind_no; /* Indicator number */ | |
307 | char label[3]; /* Indicator label */ | |
308 | COLOR_EXT_TYPE *ext; /* Extension we are working on */ | |
309 | ||
310 | p = sh_get_env_value ("LS_COLORS"); | |
311 | if (p == 0 || *p == '\0') | |
312 | { | |
313 | _rl_color_ext_list = NULL; | |
314 | return; | |
315 | } | |
316 | ||
317 | ext = NULL; | |
318 | strcpy (label, "??"); | |
319 | ||
320 | /* This is an overly conservative estimate, but any possible | |
321 | LS_COLORS string will *not* generate a color_buf longer than | |
322 | itself, so it is a safe way of allocating a buffer in | |
323 | advance. */ | |
324 | buf = color_buf = savestring (p); | |
325 | ||
326 | state = 1; | |
327 | while (state > 0) | |
328 | { | |
329 | switch (state) | |
330 | { | |
331 | case 1: /* First label character */ | |
332 | switch (*p) | |
333 | { | |
334 | case ':': | |
335 | ++p; | |
336 | break; | |
337 | ||
338 | case '*': | |
339 | /* Allocate new extension block and add to head of | |
340 | linked list (this way a later definition will | |
341 | override an earlier one, which can be useful for | |
342 | having terminal-specific defs override global). */ | |
343 | ||
344 | ext = (COLOR_EXT_TYPE *)xmalloc (sizeof *ext); | |
345 | ext->next = _rl_color_ext_list; | |
346 | _rl_color_ext_list = ext; | |
347 | ||
348 | ++p; | |
349 | ext->ext.string = buf; | |
350 | ||
351 | state = (get_funky_string (&buf, &p, true, &ext->ext.len) | |
352 | ? 4 : -1); | |
353 | break; | |
354 | ||
355 | case '\0': | |
356 | state = 0; /* Done! */ | |
357 | break; | |
358 | ||
359 | default: /* Assume it is file type label */ | |
360 | label[0] = *(p++); | |
361 | state = 2; | |
362 | break; | |
363 | } | |
364 | break; | |
365 | ||
366 | case 2: /* Second label character */ | |
367 | if (*p) | |
368 | { | |
369 | label[1] = *(p++); | |
370 | state = 3; | |
371 | } | |
372 | else | |
373 | state = -1; /* Error */ | |
374 | break; | |
375 | ||
376 | case 3: /* Equal sign after indicator label */ | |
377 | state = -1; /* Assume failure... */ | |
378 | if (*(p++) == '=')/* It *should* be... */ | |
379 | { | |
380 | for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) | |
381 | { | |
382 | if (STREQ (label, indicator_name[ind_no])) | |
383 | { | |
384 | _rl_color_indicator[ind_no].string = buf; | |
385 | state = (get_funky_string (&buf, &p, false, | |
386 | &_rl_color_indicator[ind_no].len) | |
387 | ? 1 : -1); | |
388 | break; | |
389 | } | |
390 | } | |
391 | if (state == -1) | |
392 | { | |
393 | _rl_errmsg ("LS_COLORS: unrecognized prefix: %s", label); | |
394 | /* recover from an unrecognized prefix */ | |
395 | while (p && *p && *p != ':') | |
396 | p++; | |
397 | if (p && *p == ':') | |
398 | state = 1; | |
399 | else if (p && *p == 0) | |
400 | state = 0; | |
401 | } | |
402 | } | |
403 | break; | |
404 | ||
405 | case 4: /* Equal sign after *.ext */ | |
406 | if (*(p++) == '=') | |
407 | { | |
408 | ext->seq.string = buf; | |
409 | state = (get_funky_string (&buf, &p, false, &ext->seq.len) | |
410 | ? 1 : -1); | |
411 | } | |
412 | else | |
413 | state = -1; | |
414 | /* XXX - recover here as with an unrecognized prefix? */ | |
415 | if (state == -1 && ext->ext.string) | |
416 | _rl_errmsg ("LS_COLORS: syntax error: %s", ext->ext.string); | |
417 | break; | |
418 | } | |
419 | } | |
420 | ||
421 | if (state < 0) | |
422 | { | |
423 | COLOR_EXT_TYPE *e; | |
424 | COLOR_EXT_TYPE *e2; | |
425 | ||
426 | _rl_errmsg ("unparsable value for LS_COLORS environment variable"); | |
427 | free (color_buf); | |
428 | for (e = _rl_color_ext_list; e != NULL; /* empty */) | |
429 | { | |
430 | e2 = e; | |
431 | e = e->next; | |
432 | free (e2); | |
433 | } | |
434 | _rl_color_ext_list = NULL; | |
435 | _rl_colored_stats = 0; /* can't have colored stats without colors */ | |
436 | } | |
437 | #else /* !COLOR_SUPPORT */ | |
438 | ; | |
439 | #endif /* !COLOR_SUPPORT */ | |
440 | } |