/* `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 */

static void
free_color_ext_list (void)
{
  COLOR_EXT_TYPE *e;
  COLOR_EXT_TYPE *e2;

  for (e = _rl_color_ext_list; e != NULL; /* empty */)
    {
      e2 = e;
      e = e->next;
      free (e2);
    }

  _rl_color_ext_list = 0;  
}

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)
    {

      _rl_errmsg ("unparsable value for LS_COLORS environment variable");
      free (color_buf);
      free_color_ext_list ();      

      _rl_colored_stats = 0;	/* can't have colored stats without colors */
      _rl_colored_completion_prefix = 0;	/* or colored prefixes */
    }
#else /* !COLOR_SUPPORT */
  ;
#endif /* !COLOR_SUPPORT */
}

void
rl_reparse_colors (void)
{
  char *v;

  v = sh_get_env_value ("LS_COLORS");
  if (v == 0 && color_buf == 0)
    return;		/* no change */
  if (v && color_buf && STREQ (v, color_buf))
    return;		/* no change */

  free (color_buf);
  free_color_ext_list ();

  _rl_parse_colors ();
}
