/*
 * parseconfig.c
 * 
 * Routine library for parsing the configuration file
 *
 * Copyright © 1995 H. Peter Anvin
 */

#include "magicfilter.h"

/*
 * int getoffset(char *startpos, char **endpos)
 *
 * Returns the parsing offset (a decimal, octal or hex number, or "default").
 * Returns the number, or one of the negative numbers
 *    MAG_DEFAULT      ... is default
 *    MAG_COMMENT      ... comment line
 *    MAG_ERR          ... invalid offset
 *
 * *endpos is set to the first character after the offset, unless NULL
 */

int getoffset(char *p, char **endpos)
{
  int offset = 0;		/* So far */
  enum { st_start, st_zero, st_oct, st_dec, st_hex, st_done } state = st_start;

#if DEBUG > 3
  fprintf(stderr,"getoffset %s\n", p);
#endif

  while ( isspace(*p) ) p++;	/* Skip leading whitespace */

  while ( state != st_done )
    {
      switch( state )
	{
	case st_start:
	  switch ( *p )
	    {
	    case '\b':		/* Whitespace */
	    case '\t':
	    case '\n':
	    case '\v':
	    case '\f':
	    case '\r':
	    case ' ':
	      p++;
	      break;
	      
	    case '#':
	    case '\0':		/* Blank line counts as comment */
	      offset = MAG_COMMENT;
	      state = st_done;
	      break;

	    case '0':
	      state = st_zero;
	      p++;
	      break;

	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
	      offset = *p - '0';
	      state = st_dec;
	      p++;
	      break;
	      
	    case 'd':
	      if ( strncmp(p, "default", 7) )
		{
		  offset = MAG_ERR;
		}
	      else
		{
		  p += 7;
		  offset = MAG_DEFAULT;
		}
	      state = st_done;
	      break;

	    default:
	      offset = MAG_ERR;
	      state = st_done;
	      break;
	    }
	  break;

	case st_zero:
	  if ( *p == 'x' || *p == 'X' )
	    {
	      state = st_hex;
	      p++;
	      break;
	    }
	  /* else fall through */

	case st_oct:
	  if ( *p < '0' || *p > '7' )
	    {
	      state = st_done;
	    }
	  else
	    {
	      offset <<= 3;
	      offset += *(p++) - '0';
	    }
	  break;

	case st_dec:
	  if ( *p < '0' || *p > '9' )
	    {
	      state = st_done;
	    }
	  else
	    {
	      offset *= 10;
	      offset += *(p++) - '0';
	    }
	  break;

	case st_hex:
	  if ( *p >= '0' && *p <= '9' )
	    {
	      offset <<= 4;
	      offset += *(p++) - '0';
	    }
	  else if ( *p >= 'A' && *p <= 'F' )
	    {
	      offset <<= 4;
	      offset += *(p++) - 'A' + 10;
	    }
	  else if ( *p >= 'a' && *p <= 'f' )
	    {
	      offset <<= 4;
	      offset += *(p++) - 'a' + 10;
	    }
	  else
	    {
	      state = st_done;
	    }
	}
    }

  if ( endpos )
    *endpos = p;		/* Where did we end? */

  return offset;
}

/*
 * int getmagic(char *startpos, char **endpos, char *magic, char *mask)
 *
 * Parse a magic string, returning the number of bytes in the magic string.
 * Stores the endpoint in *endpos if requested.  If magic and/or mask
 * is NULL, do not store; used to count the number of characters in the
 * magic/mask string.
 *
 * Each character in mask is either 0 for \? (don't care) or (char)-1 for
 * any other (do care).
 *
 * If mask is set to the senitel value NO_WILD (a unique pointer value
 * pointing to a bogus static string) the \? will not be interpreted
 * as don't care; just as a regular escaped questionmark.
 *
 */
char NO_WILD[1] = "";		/* Used as senitel value */

int getmagic(char *p, char **endpos, char *magic, char *mask)
{
  int count = 0;		/* Count of bytes */
  int quote = 0;		/* Quoted? */
  int ch;
  int wildcards = 1;		/* Wildcards allowed by default */
  enum { st_def, st_backsl, st_oct, st_hex, st_done } state = st_def;

#if DEBUG > 3
  fprintf(stderr,"getmagic %s\n", p);
#endif

  if ( mask == NO_WILD )	/* If wildcards forbidden */
    {
      mask = NULL;		/* Don't store a mask */
      wildcards = 0;		/* Don't accept wildcards */
    }

  while ( isspace(*p) ) p++;	/* Skip leading whitespace */
  
  while ( state != st_done )
    {
      switch ( state )
	{
	case st_def:
	  switch ( *p )
	    {
	    case '\n':		/* Unconditional whitespace */
	    case '\r':
	    case '\f':
	    case '\0':
	      if ( quote )
		count = MAG_ERR;
	      state = st_done;
	      break;

	    case '\\':		/* Backslash */
	      p++;
	      state = st_backsl;
	      break;

	    case '\"':		/* Quote */
	      p++;
	      quote = !quote;
	      break;

	    case '\b':		/* Whitespace */
	    case '\t':
	    case '\v':
	    case ' ':
	      if ( !quote )
		{
		  state = st_done;
		  break;
		}
	      /* else fall though */

	    default:
	      if ( magic ) *(magic++) = *p;
	      if ( mask ) *(mask++) = (char)(-1);
	      p++; count++;
	      break;
	    }
	  break;
	  
	case st_backsl:		/* After backslash */
	  ch = -1;
	  switch ( *p )
	    {
	    case 'a':
	      ch = 7;		/* Bell */
	      break;
	    case 'b':
	      ch = '\b';	/* Backspace */
	      break;
	    case 'e':
	      ch = 27;		/* Escape */
	      break;
	    case 'f':
	      ch = '\f';	/* Form feed */
	      break;
	    case 'n':
	      ch = '\n';	/* Newline */
	      break;
	    case 'r':
	      ch = '\r';	/* Return */
	      break;
	    case 't':
	      ch = '\t';	/* Tab */
	      break;
	    case 'v':
	      ch = '\v';	/* Vtab */
	      break;
	    case 'x':
	    case 'X':
	      state = st_hex;	/* Hex prefix */
	      ch = 0;
	      break;
	    case '0':
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	      state = st_oct;	/* Octal prefix */
	      ch = *p - '0';
	      break;
	    case '?':
	      if ( wildcards )
		{
		  if ( magic ) *(magic++) = (char)0; /* Ignore byte */
		  if ( mask ) *(mask++) = (char)0;
		  count++;
		  state = st_def;
		  break;
		}
	      /* else fall through */
	    default:
	      ch = *p;		/* Preserve character (e.g. " or \) */
	      break;
	    }
	  
	  p++;
	  
	  if ( state == st_backsl )
	    {
	      if ( magic ) *(magic++) = ch;
	      if ( mask ) *(mask++) = (char)(-1);
	      count++;
	      state = st_def;
	    }
	  break;

	case st_oct:
	  if ( *p >= '0' && *p <='7' )
	    {
	      ch <<= 3;
	      ch += *(p++) - '0';
	    }
	  else
	    {
	      if ( magic ) *(magic++) = ch;
	      if ( mask ) *(mask++) = (char)(-1);
	      count++;
	      state = st_def;	/* Do not advance p */
	    }
	  break;

	case st_hex:
	  if ( *p >= '0' && *p <= '7' )
	    {
	      ch <<= 4;
	      ch += *(p++) - '0';
	    }
	  else if ( *p >= 'A' && *p <= 'F' )
	    {
	      ch <<= 4;
	      ch += *(p++) - 'A' + 10;
	    }
	  else if ( *p >= 'a' && *p <= 'f' )
	    {
	      ch <<= 4;
	      ch += *(p++) - 'a' + 10;
	    }
	  else
	    {
	      if ( magic ) *(magic++) = ch;
	      if ( mask ) *(mask++) = (char)(-1);
	      count++;
	      state = st_def;	/* Do not advance p */
	    }
	  break;
	}
    }

  if (endpos) *endpos = p;
  return count;
}

/*
 * enum actions getaction(char *pos, char **cmd)
 *
 * Reads the action proposed and returns:
 *
 * ACT_CAT       ... output file raw
 * ACT_DROP      ... silently ignore
 * ACT_REJECT    ... mail user and complain
 * ACT_FILTER    ... run through once, then done
 * ACT_PIPETHRU  ... run through command, then feed back through filter
 * ACT_ADDCR     ... \n -> \r\n, \f -> \r\f
 * ACT_PS        ... same as ADDCR, but add Ctrl-D at end
 * ACT_FFILTER   ... same as FILTER, but use a temp file
 * ACT_FPIPE     ... same as PIPETHRU, but use a temp file
 *
 * If the command takes an argument, point cmd to it if non-NULL
 *
 * IMPORTANT: The matching algorithm is a bit simplistic.  If any action
 * is a proper prefix of any other action, the longer action must be first
 * in the list.
 */

enum actions getaction(char *pos, char **cmd)
{
  enum actions act;
  static char *act_strs[] = { "cat", "ignore", "reject", "filter",
			     "pipe", "text", "postscript",
			     "ffilter", "fpipe", NULL };
  char **act_str;
  int i;

#if DEBUG > 3
  fprintf(stderr,"getaction %s\n", pos);
#endif
  
  while ( isspace(*pos) ) pos++; /* Ignore leading whitespace */
  
  if ( ! *pos )
    return ACT_ERR;		/* Syntax error */
    
  for ( act = ACT_CAT, act_str = act_strs ; *act_str ; act++, act_str++ )
    if ( strncmp(pos, *act_str, i = strlen(*act_str)) == 0 )
      {
	pos += i;
	break;
      }
  
  if ( ! *act_str )
    {
#if DEBUG > 3
      fprintf(stderr,"No match\n");
#endif
      return ACT_ERR;		/* Unknown action */
    }
  
  if ( *pos && !isspace(*pos) )
    {
#if DEBUG > 3
      fprintf(stderr,"Garbage at end: %s\n", pos);
#endif
      return ACT_ERR;		/* Garbage after action name */
    }

  if ( cmd )
    {
      while ( isspace(*pos) ) pos++; /* Skip leading whitespace again */
      *cmd = pos;
    }
  
  return act;
}

