| /* `dir', `vdir' and `ls' directory listing programs for GNU. |
| |
| Modified by Chet Ramey for Readline. |
| |
| Copyright (C) 1985, 1988, 1990-1991, 1995-2010, 2012, 2017 |
| Free Software Foundation, Inc. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| /* Written by Richard Stallman and David MacKenzie. */ |
| |
| /* Color support by Peter Anvin <Peter.Anvin@linux.org> and Dennis |
| Flaherty <dennisf@denix.elk.miles.com> based on original patches by |
| Greg Lee <lee@uhunix.uhcc.hawaii.edu>. */ |
| |
| #define READLINE_LIBRARY |
| |
| #if defined (HAVE_CONFIG_H) |
| # include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| |
| // strdup() / strcpy() |
| #if defined (HAVE_STRING_H) |
| # include <string.h> |
| #else /* !HAVE_STRING_H */ |
| # include <strings.h> |
| #endif /* !HAVE_STRING_H */ |
| |
| // abort() |
| #if defined (HAVE_STDLIB_H) |
| # include <stdlib.h> |
| #else |
| # include "ansi_stdlib.h" |
| #endif /* HAVE_STDLIB_H */ |
| |
| #include "rldefs.h" // STREQ, savestring |
| #include "readline.h" |
| #include "rlprivate.h" |
| #include "rlshell.h" |
| #include "xmalloc.h" |
| |
| #include "colors.h" |
| #include "parse-colors.h" |
| |
| #if defined (COLOR_SUPPORT) |
| |
| static bool get_funky_string (char **dest, const char **src, bool equals_end, size_t *output_count); |
| |
| struct bin_str _rl_color_indicator[] = |
| { |
| { LEN_STR_PAIR ("\033[") }, // lc: Left of color sequence |
| { LEN_STR_PAIR ("m") }, // rc: Right of color sequence |
| { 0, NULL }, // ec: End color (replaces lc+no+rc) |
| { LEN_STR_PAIR ("0") }, // rs: Reset to ordinary colors |
| { 0, NULL }, // no: Normal |
| { 0, NULL }, // fi: File: default |
| { LEN_STR_PAIR ("01;34") }, // di: Directory: bright blue |
| { LEN_STR_PAIR ("01;36") }, // ln: Symlink: bright cyan |
| { LEN_STR_PAIR ("33") }, // pi: Pipe: yellow/brown |
| { LEN_STR_PAIR ("01;35") }, // so: Socket: bright magenta |
| { LEN_STR_PAIR ("01;33") }, // bd: Block device: bright yellow |
| { LEN_STR_PAIR ("01;33") }, // cd: Char device: bright yellow |
| { 0, NULL }, // mi: Missing file: undefined |
| { 0, NULL }, // or: Orphaned symlink: undefined |
| { LEN_STR_PAIR ("01;32") }, // ex: Executable: bright green |
| { LEN_STR_PAIR ("01;35") }, // do: Door: bright magenta |
| { LEN_STR_PAIR ("37;41") }, // su: setuid: white on red |
| { LEN_STR_PAIR ("30;43") }, // sg: setgid: black on yellow |
| { LEN_STR_PAIR ("37;44") }, // st: sticky: black on blue |
| { LEN_STR_PAIR ("34;42") }, // ow: other-writable: blue on green |
| { LEN_STR_PAIR ("30;42") }, // tw: ow w/ sticky: black on green |
| { LEN_STR_PAIR ("30;41") }, // ca: black on red |
| { 0, NULL }, // mh: disabled by default |
| { LEN_STR_PAIR ("\033[K") }, // cl: clear to end of line |
| }; |
| |
| /* Parse a string as part of the LS_COLORS variable; this may involve |
| decoding all kinds of escape characters. If equals_end is set an |
| unescaped equal sign ends the string, otherwise only a : or \0 |
| does. Set *OUTPUT_COUNT to the number of bytes output. Return |
| true if successful. |
| |
| The resulting string is *not* null-terminated, but may contain |
| embedded nulls. |
| |
| Note that both dest and src are char **; on return they point to |
| the first free byte after the array and the character that ended |
| the input string, respectively. */ |
| |
| static bool |
| get_funky_string (char **dest, const char **src, bool equals_end, size_t *output_count) { |
| char num; /* For numerical codes */ |
| size_t count; /* Something to count with */ |
| enum { |
| ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX, ST_CARET, ST_END, ST_ERROR |
| } state; |
| const char *p; |
| char *q; |
| |
| p = *src; /* We don't want to double-indirect */ |
| q = *dest; /* the whole darn time. */ |
| |
| count = 0; /* No characters counted in yet. */ |
| num = 0; |
| |
| state = ST_GND; /* Start in ground state. */ |
| while (state < ST_END) |
| { |
| switch (state) |
| { |
| case ST_GND: /* Ground state (no escapes) */ |
| switch (*p) |
| { |
| case ':': |
| case '\0': |
| state = ST_END; /* End of string */ |
| break; |
| case '\\': |
| state = ST_BACKSLASH; /* Backslash scape sequence */ |
| ++p; |
| break; |
| case '^': |
| state = ST_CARET; /* Caret escape */ |
| ++p; |
| break; |
| case '=': |
| if (equals_end) |
| { |
| state = ST_END; /* End */ |
| break; |
| } |
| /* else fall through */ |
| default: |
| *(q++) = *(p++); |
| ++count; |
| break; |
| } |
| break; |
| |
| case ST_BACKSLASH: /* Backslash escaped character */ |
| switch (*p) |
| { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| state = ST_OCTAL; /* Octal sequence */ |
| num = *p - '0'; |
| break; |
| case 'x': |
| case 'X': |
| state = ST_HEX; /* Hex sequence */ |
| num = 0; |
| break; |
| case 'a': /* Bell */ |
| num = '\a'; |
| break; |
| case 'b': /* Backspace */ |
| num = '\b'; |
| break; |
| case 'e': /* Escape */ |
| num = 27; |
| break; |
| case 'f': /* Form feed */ |
| num = '\f'; |
| break; |
| case 'n': /* Newline */ |
| num = '\n'; |
| break; |
| case 'r': /* Carriage return */ |
| num = '\r'; |
| break; |
| case 't': /* Tab */ |
| num = '\t'; |
| break; |
| case 'v': /* Vtab */ |
| num = '\v'; |
| break; |
| case '?': /* Delete */ |
| num = 127; |
| break; |
| case '_': /* Space */ |
| num = ' '; |
| break; |
| case '\0': /* End of string */ |
| state = ST_ERROR; /* Error! */ |
| break; |
| default: /* Escaped character like \ ^ : = */ |
| num = *p; |
| break; |
| } |
| if (state == ST_BACKSLASH) |
| { |
| *(q++) = num; |
| ++count; |
| state = ST_GND; |
| } |
| ++p; |
| break; |
| |
| case ST_OCTAL: /* Octal sequence */ |
| if (*p < '0' || *p > '7') |
| { |
| *(q++) = num; |
| ++count; |
| state = ST_GND; |
| } |
| else |
| num = (num << 3) + (*(p++) - '0'); |
| break; |
| |
| case ST_HEX: /* Hex sequence */ |
| switch (*p) |
| { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| num = (num << 4) + (*(p++) - '0'); |
| break; |
| case 'a': |
| case 'b': |
| case 'c': |
| case 'd': |
| case 'e': |
| case 'f': |
| num = (num << 4) + (*(p++) - 'a') + 10; |
| break; |
| case 'A': |
| case 'B': |
| case 'C': |
| case 'D': |
| case 'E': |
| case 'F': |
| num = (num << 4) + (*(p++) - 'A') + 10; |
| break; |
| default: |
| *(q++) = num; |
| ++count; |
| state = ST_GND; |
| break; |
| } |
| break; |
| |
| case ST_CARET: /* Caret escape */ |
| state = ST_GND; /* Should be the next state... */ |
| if (*p >= '@' && *p <= '~') |
| { |
| *(q++) = *(p++) & 037; |
| ++count; |
| } |
| else if (*p == '?') |
| { |
| *(q++) = 127; |
| ++count; |
| } |
| else |
| state = ST_ERROR; |
| break; |
| |
| default: |
| /* should we ? */ |
| /* abort (); no, we should not */ |
| state = ST_ERROR; |
| break; |
| } |
| } |
| |
| *dest = q; |
| *src = p; |
| *output_count = count; |
| |
| return state != ST_ERROR; |
| } |
| #endif /* COLOR_SUPPORT */ |
| |
| void _rl_parse_colors(void) |
| { |
| #if defined (COLOR_SUPPORT) |
| const char *p; /* Pointer to character being parsed */ |
| char *buf; /* color_buf buffer pointer */ |
| int state; /* State of parser */ |
| int ind_no; /* Indicator number */ |
| char label[3]; /* Indicator label */ |
| COLOR_EXT_TYPE *ext; /* Extension we are working on */ |
| |
| p = sh_get_env_value ("LS_COLORS"); |
| if (p == 0 || *p == '\0') |
| { |
| _rl_color_ext_list = NULL; |
| return; |
| } |
| |
| ext = NULL; |
| strcpy (label, "??"); |
| |
| /* This is an overly conservative estimate, but any possible |
| LS_COLORS string will *not* generate a color_buf longer than |
| itself, so it is a safe way of allocating a buffer in |
| advance. */ |
| buf = color_buf = savestring (p); |
| |
| state = 1; |
| while (state > 0) |
| { |
| switch (state) |
| { |
| case 1: /* First label character */ |
| switch (*p) |
| { |
| case ':': |
| ++p; |
| break; |
| |
| case '*': |
| /* Allocate new extension block and add to head of |
| linked list (this way a later definition will |
| override an earlier one, which can be useful for |
| having terminal-specific defs override global). */ |
| |
| ext = (COLOR_EXT_TYPE *)xmalloc (sizeof *ext); |
| ext->next = _rl_color_ext_list; |
| _rl_color_ext_list = ext; |
| |
| ++p; |
| ext->ext.string = buf; |
| |
| state = (get_funky_string (&buf, &p, true, &ext->ext.len) |
| ? 4 : -1); |
| break; |
| |
| case '\0': |
| state = 0; /* Done! */ |
| break; |
| |
| default: /* Assume it is file type label */ |
| label[0] = *(p++); |
| state = 2; |
| break; |
| } |
| break; |
| |
| case 2: /* Second label character */ |
| if (*p) |
| { |
| label[1] = *(p++); |
| state = 3; |
| } |
| else |
| state = -1; /* Error */ |
| break; |
| |
| case 3: /* Equal sign after indicator label */ |
| state = -1; /* Assume failure... */ |
| if (*(p++) == '=')/* It *should* be... */ |
| { |
| for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) |
| { |
| if (STREQ (label, indicator_name[ind_no])) |
| { |
| _rl_color_indicator[ind_no].string = buf; |
| state = (get_funky_string (&buf, &p, false, |
| &_rl_color_indicator[ind_no].len) |
| ? 1 : -1); |
| break; |
| } |
| } |
| if (state == -1) |
| { |
| _rl_errmsg ("LS_COLORS: unrecognized prefix: %s", label); |
| /* recover from an unrecognized prefix */ |
| while (p && *p && *p != ':') |
| p++; |
| if (p && *p == ':') |
| state = 1; |
| else if (p && *p == 0) |
| state = 0; |
| } |
| } |
| break; |
| |
| case 4: /* Equal sign after *.ext */ |
| if (*(p++) == '=') |
| { |
| ext->seq.string = buf; |
| state = (get_funky_string (&buf, &p, false, &ext->seq.len) |
| ? 1 : -1); |
| } |
| else |
| state = -1; |
| /* XXX - recover here as with an unrecognized prefix? */ |
| if (state == -1 && ext->ext.string) |
| _rl_errmsg ("LS_COLORS: syntax error: %s", ext->ext.string); |
| break; |
| } |
| } |
| |
| if (state < 0) |
| { |
| COLOR_EXT_TYPE *e; |
| COLOR_EXT_TYPE *e2; |
| |
| _rl_errmsg ("unparsable value for LS_COLORS environment variable"); |
| free (color_buf); |
| for (e = _rl_color_ext_list; e != NULL; /* empty */) |
| { |
| e2 = e; |
| e = e->next; |
| free (e2); |
| } |
| _rl_color_ext_list = NULL; |
| _rl_colored_stats = 0; /* can't have colored stats without colors */ |
| } |
| #else /* !COLOR_SUPPORT */ |
| ; |
| #endif /* !COLOR_SUPPORT */ |
| } |