/*
**
** Copyright (C) 1994 Swedish University Network (SUNET)
**
**
** This program is developed by UDAC, Uppsala University by commission
** of the Swedish University Network (SUNET). 
**
** 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 2 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 FITTNESS 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, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
**
**                                        Martin.Wendel@udac.uu.se
**                                        Torbjorn.Wictorin@udac.uu.se
**
**                                        UDAC	
**                                        P.O. Box 174
**                                        S-751 04 Uppsala
**                                        Sweden
**
*/


#include "emil.h"
#include "charset/charset.h"
int     strptrncnv(CHARSET *, CHARSET *, struct data *, struct data *, int);

static char * out_data_part(struct data *);

static char delim[256] = {
0, 0, 0, 0, 0, 0, 0, 0,  0, 1, 1, 0, 0, 1, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
1, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 1,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 1, 0, 1, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  

0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  
};

struct resheader {
  char *field;
  long types;
};
static struct resheader resh[] = {
"Return-Path", 0,
"Received", 0,
"Reply-To", RADDR | ATOM | RFC1522,
"From", RADDR | ATOM | RFC1522,
"Sender", RADDR | ATOM | RFC1522,
"Resent-Reply-To", RADDR | ATOM | RFC1522,
"Resent-From", RADDR | ATOM | RFC1522,
"Resent-Sender", RADDR | ATOM | RFC1522,
"Date", 0,
"Resent-Date", 0, 
"To", RADDR | ATOM | RFC1522,
"Resent-To", RADDR | ATOM | RFC1522,
"cc", RADDR | ATOM | RFC1522,
"Resent-cc", RADDR | ATOM | RFC1522,
"bcc", RADDR | ATOM | RFC1522,
"Resent-bcc", RADDR | ATOM | RFC1522,
"Message-ID", 0, 
"Resent-Message-ID", 0,
"In-Reply-To", RADDR | ATOM | RFC1522,
"References", RADDR | ATOM | RFC1522,
"Keywords", ATOM | RFC1522,
"Subject", TEXT,
"Comments", TEXT,
"Encrypted", ATOM,
NULL, TEXT};

struct hprs *
copy_hprs(struct hprs *h)
{
  struct hprs *th;
  struct data *tbuf, *inbuf;

  inbuf = h->td;
  tbuf = (struct data *)Yalloc(sizeof(struct data ));
  tbuf->contents = inbuf->contents;
  tbuf->offset = inbuf->offset;
  tbuf->end = inbuf->end;
  tbuf->size = inbuf->size;
  
  th = (struct hprs *)Yalloc(sizeof(struct hprs));
  th->sd = tbuf;
  th->td = tbuf;
  return(th);
}

int
where(char *s, char c)
{
  char *t;
  if ((t = index(s, c)) == NULL)
    return(0);
  else
    return(t - s);
}

int parse_rfc1522(struct header *th)
{
  struct hprs *h;
  char *field;
  int i;

  if (th->value == NULL)
    return(OK);
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "+   Parse header\n");
#endif
  
  if ((field = th->field->contents) == NULL)
    return(OK);

  /* Return if header is reserved, otherwise get types */
  for (i = 0; resh[i].field != NULL; i++)
    if (cmatch(field, resh[i].field) == TRUE)
      break;

  if (resh[i].types == 0)
    return(OK);

  /* Insert discrimination of headers here */
  if (th->hvalue == NULL)
    {
      h = (struct hprs *)Yalloc(sizeof(struct hprs));
      h->types = resh[i].types;
      h->sd = th->value;
      h->td = h->sd;
      if (parse_hline(h) != OK)
	h = NULL;
    }
  else
    h = th->hvalue;

  if (h != NULL)
    {
      if (convert_hlines(h) == OK)
	th->hvalue = h;
      return(OK);
    }
  else
    {
      
      logger(LOG_WARNING, "parse_rfc1522: Error parsing hline");
      return(NOK);
    }
}

int
parse_hline(struct hprs *h)
{
  struct data *inbuf;
  struct hprs *tmpd;
  int offset;
  int type = UNKNOWN;
  unsigned char *inb;
  int len;

  inbuf = h->sd;
  inb = inbuf->contents + inbuf->offset;
  offset = inbuf->offset;
  while (inbuf->offset < inbuf->end)
    {
      switch (type & h->types)
	{
	case UNKNOWN:
	  /* Loop through the available types */
	  if (type == 0)
	    type = 1;
	  else
	    type = type <<1; /* Shift */
	  if (type == TOP)
	    {
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "+   No applicable type (failed).\n");
#endif
	      return(NOK);
	    }
	  break;
	  
	case COMMENT:
	  offset = inbuf->offset;
	  if (*inb != '(')
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  inb++;
	  inbuf->offset += 1;
	  if ((len = where(inb, ')')) == 0)
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  inbuf->bodystart = inbuf->offset;
	  inbuf->bodyend = inbuf->bodystart + len;
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "-   Found comment '%s'.\n", out_data_part(inbuf));
#endif
	  tmpd = copy_hprs(h);
	  tmpd->sd->end = inbuf->bodyend ? inbuf->bodyend : inbuf->end;
	  tmpd->types = ATOM | RFC1522 | COMMENT;
	  inbuf->offset += len + 1;
	  inb += len + 1;
	  if (parse_hline(tmpd) == OK)
	    {
	      h->type = COMMENT;
	      h->child = tmpd;
	    }
	  else
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  tmpd = copy_hprs(h);
	  tmpd->types = h->types;
	  
	  if (parse_hline(tmpd) == OK)
	    {
	      h->sibling = tmpd;
	      return(OK);
	    }
	  else
	    {
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  break;
      
	case HQSTRING:
	  offset = inbuf->offset;
	  if (*inb != '"')
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  inb++;
	  inbuf->offset += 1;
	  if ((len = where(inb, '"')) == 0)
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  inbuf->bodystart = inbuf->offset;
	  inbuf->bodyend = inbuf->bodystart + len;
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "-   Found qstring '%s'.\n", out_data_part(inbuf));
#endif
	  tmpd = copy_hprs(h);
	  tmpd->types = ATOM;
	  tmpd->sd->end = inbuf->bodyend ? inbuf->bodyend : inbuf->end;
	  inbuf->offset += len + 1;
	  inb += len + 1;
	    
	  if (parse_hline(tmpd) == OK)
	    {
	      h->type = HQSTRING;
	      h->child = tmpd;
	    }
	  else
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  tmpd = copy_hprs(h);
	  tmpd->types = h->types;
	  
	  if (parse_hline(tmpd) == OK)
	    {
	      h->sibling = tmpd;
	      return(OK);
	    }
	  else
	    {
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  break;
      
	case DLITERAL:
	  offset = inbuf->offset;
	  if (*inb != '[')
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  inb++;
	  inbuf->offset += 1;
	  if ((len = where(inb, ']')) == 0)
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  inbuf->bodystart = inbuf->offset;
	  inbuf->bodyend = inbuf->bodystart + len;
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "-   Found domain literal '%s'.\n", out_data_part(inbuf));
#endif
	  tmpd = copy_hprs(h);
	  tmpd->types = ATOM;
	  tmpd->sd->end = inbuf->bodyend ? inbuf->bodyend : inbuf->end;
	  inbuf->offset += len + 1;
	  inb += len + 1;
	    
	  if (parse_hline(tmpd) == OK)
	    {
	      h->type = DLITERAL;
	      h->child = tmpd;
	    }
	  else
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  tmpd = copy_hprs(h);
	  tmpd->types = h->types;
	  
	  if (parse_hline(tmpd) == OK)
	    {
	      h->sibling = tmpd;
	      return(OK);
	    }
	  else
	    {
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  break;
      
	case RADDR:
	  offset = inbuf->offset;
	  if (*inb != '<')
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  inb++;
	  inbuf->offset += 1;
	  if ((len = where(inb, '>')) == 0)
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  inbuf->bodystart = inbuf->offset;
	  inbuf->bodyend = inbuf->bodystart + len;
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "-   Found address '%s'.\n", out_data_part(inbuf));
#endif
	  tmpd = copy_hprs(h);
	  tmpd->types = ATOM;
	  tmpd->sd->end = inbuf->bodyend ? inbuf->bodyend : inbuf->end;
	  inbuf->offset += len + 1;
	  inb += len + 1;
	    
	  if (parse_hline(tmpd) == OK)
	    {
	      h->type = RADDR;
	      h->child = tmpd;
	    }
	  else
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }
	  tmpd = copy_hprs(h);
	  tmpd->types = h->types;
	  
	  if (parse_hline(tmpd) == OK)
	    {
	      h->sibling = tmpd;
	      return(OK);
	    }
	  else
	    {
	      type = type <<1;
	      inbuf->offset = offset;
 	      break;
	    }
	  break;
      
	case TEXT:
	  offset = inbuf->offset;
	  inbuf->bodystart = inbuf->offset;
	  inbuf->bodyend = inbuf->end;
      
	  tmpd = copy_hprs(h);
	  tmpd->types = ATOM | RFC1522 | COMMENT;
	  tmpd->sd->end = inbuf->bodyend ? inbuf->bodyend : inbuf->end;
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "-   Found text.\n");
#endif
	    
	  if (parse_hline(tmpd) == OK)
	    {
	      h->type = TEXT;
	      h->child = tmpd;
	      return(OK);
	    }
	  else
	    {
	      type = type <<1; /* Shift */
	      inbuf->offset = offset;
	      break;
	    }

	  break;

	case RFC1522:
	  offset = inbuf->offset;
	  if (*inb != '=')
	    {
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  inbuf->offset += 1;
	  inb++;
	  if (*inb != '?')
	    {
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "*   rfc1522 decoding failed on starting '=?'\n");
#endif
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  inbuf->offset += 1;
	  inb++;
	  /* Get charset */
	  if ((len = where(inb, '?')) == 0)
	    {
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "*   rfc1522 decoding failed on second '='\n");
#endif
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  inbuf->charset = (char *)Yalloc(len + 1);
	  strncpy(inbuf->charset, inb, len);
	  inb += len + 1;
	  inbuf->offset += len + 1;
	  /* Get encoding */
	  inbuf->encoding = 0;
	  switch(*inb)
	    {
	    case 'q':
	    case 'Q':
	      inbuf->encoding = EQP;
	      break;
	    case 'b':
	    case 'B':
	      inbuf->encoding = EBASE64;
	      break;
	    default:
	      break;
	    }
	  if (inbuf->encoding == 0)
	    {
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "*   rfc1522 decoding failed on encoding '%c'\n", *inb);
#endif
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  inb++;
	  inbuf->offset += 1;
	  if (*inb != '?')
	    {
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "*   rfc1522 decoding failed on third '?'\n");
#endif
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  inb++;
	  inbuf->offset += 1;
	  
	  /* Get string */
	  if ((len = where(inb, '?')) == 0)
	    {
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "*   rfc1522 decoding failed on string\n");
#endif
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  if (*(inbuf->contents + len + inbuf->offset) != '?')
	    {
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "*   rfc1522 decoding failed on closing '?'\n");
#endif
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  inb += len + 1;
	  if (*inb != '=')
	    {
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "*   rfc1522 decoding failed on closing '='\n");
#endif
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  h->pstart = inbuf->offset;
	  h->pend = inbuf->offset + len;
	  inbuf->bodystart = offset;
	  inbuf->offset += len + 2;
	  inbuf->bodyend = inbuf->offset;
	  inb++;
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "-   Found rfc1522 encoded string '%s'.\n", out_data_part(inbuf));
#endif
	  tmpd = copy_hprs(h);
	  tmpd->types = h->types;
	  
	  if (parse_hline(tmpd) == OK)
	    {
	      h->type = RFC1522;
	      h->sibling = tmpd;
	      return(OK);
	    }
	  else
	    {
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  break;
	  
	case ATOM:
	  offset = inbuf->offset;
	  h->types = h->types | DELIMITER;
	  if (delim[*inb] != '\0')
	    {
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  else
	    {
	      inbuf->bodystart = inbuf->offset;
	      while(inbuf->offset < inbuf->end)
		{
		  if (delim[*inb] != '\0')
		    break;
		  else
		    {
		      inb++;
		      inbuf->offset += 1;
		    }
		}
	      inbuf->bodyend = inbuf->offset;
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "-   Found atom '%s'.\n", out_data_part(inbuf));
#endif
	      tmpd = copy_hprs(h);
	      tmpd->types = h->types;
	  
	      if (parse_hline(tmpd) == OK)
		{
		  h->type = ATOM;
		  h->sibling = tmpd;
		  return(OK);
		}
	      else
		{
		  type = type <<1;
		  inbuf->offset = offset;
		  break;
		}
	    }
	  break;
	  
	case DELIMITER:
	  offset = inbuf->offset;
	  if (delim[*inb] == '\0')
	    {
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  inbuf->bodystart = inbuf->offset;
	  inb++;
	  inbuf->offset += 1;
	  inbuf->bodyend = inbuf->offset;
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "-   Found delimiter '%s'.\n", out_data_part(inbuf));
#endif
	  tmpd = copy_hprs(h);
	  tmpd->types = h->types;
	  
	  if (parse_hline(tmpd) == OK)
	    {
	      h->type = DELIMITER;
	      h->sibling = tmpd;
	      return(OK);
	    }
	  else
	    {
	      type = type <<1;
	      inbuf->offset = offset;
	      break;
	    }
	  break;
	  
	default:
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "*** Unknown type (failed).\n");
#endif
	  return(NOK);
	  break;
	}
    }
  inbuf->bodyend = inbuf->offset;
  return(OK);
}


int
convert_hlines(struct hprs *h)
{
  int status;
  if (target == NULL || source == NULL)
    return(NOK);

  h->td->offset = h->td->bodystart;
  /* Check 8bit data */
  check_bits(h->td);

  while ((status = recode_hline(h)) != OK)
    h->td->offset = h->td->bodystart;
  
  if (h->child != NULL)
    (void)convert_hlines(h->child);
  if (h->sibling != NULL)
    (void)convert_hlines(h->sibling);
  return(OK);
}

int
recode_hline(struct hprs *h)
{
  switch (h->td->encoding)
    {
    case E7BIT:
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "-   is 7bit\n");
#endif
      if (h->td->charset == NULL)
	h->td->charset = source->charset == NULL ? NEWSTR("ISO-8859-1"):
	  source->charset;
      if (h_tocharset(h) == OK)
	check_bits(h->td);

      if (h->td->encoding != E7BIT)
	return(NOK);
      return(OK);
      break;
    case E8BIT:
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "-   is 8bit\n");
#endif
      if (h->td->charset == NULL)
	h->td->charset = source->charset == NULL ? NEWSTR("ISO-8859-1"):
	  source->charset;
      if (h_tocharset(h) == OK)
	check_bits(h->td);

      if (h->td->encoding != E8BIT)
	return(NOK);
      /* Convert if allowed */
      if (process)
	{
	  if (h->types & RFC1522 && h->type == ATOM)
	    switch(target->htext)
	      {
	      case EQP:
		h_to_quoted_printable(h);
		break;
	      case EBASE64:
		h_to_base64(h);
		break;
	      }
	}
      return(OK);
      break;
    case EQP:
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "-   is qp\n");
#endif
      if (process == 0 || target->htext != EQP || 
	  ((target->charset != NULL) &&
	   (cmatch(target->charset, h->td->charset) == FALSE)))
	{
	  if (h_from_quoted_printable(h) == OK)
	    {
	      check_bits(h->td);
	      return(NOK);
	    }
	  return(OK);
	}
	return(OK);
      break;

    case EBASE64:
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "-   is base64\n");
#endif
      if (process == 0 || target->htext != EBASE64 || 
	  ((target->charset != NULL) &&
	   (cmatch(target->charset, h->td->charset) == FALSE)))
	{
	  if (h_from_base64(h) == OK)
	    {
	      check_bits(h->td);
	      return(NOK);
	    }
	  return(OK);
	}
      else
	return(OK);
      break;

    default:
      return(OK);
      break;
    }
  return(NOK);
}




int
h_tocharset(struct hprs *h)
{
  CHARSET *schar, *tchar;
  struct data *inbuf, *outbuf;
  char *sset, *tset;
  int copy = 0;

  inbuf = h->td;

  sset = h->td->charset;
  if (target->htext == E7BIT || target->htext == ESE)
    tset = NEWSTR("ISO-8859-1");
  else
    tset = target->charset;

#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "* Header tocharset");
#endif
  if (inbuf->size == 0)
    {
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, "--- Empty input (failed).\n");
#endif
      return(NOK);
    }
  if (sset == NULL || tset == NULL ||
      (schar = (CHARSET *)getchset(sset, 29)) == NULL ||
      (tchar = (CHARSET *)getchset(tset, 29)) == NULL)
    copy = 1;
  
      
  if (copy)
    {
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, ", copying.\n");
#endif
      return(NOK);
    }
  else
    {
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, ", from %s to %s", sset, tset);
#endif
      outbuf = (struct data *) Yalloc(sizeof(struct data ));
      strptrncnv(tchar, schar, outbuf, inbuf, HEAD_BUF);
      outbuf->bodyend = outbuf->end;
      outbuf->charset = NEWSTR(tset);
      h->td = outbuf;
    }
  if (target->htext == E7BIT)
    {
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, ", to 7bit");
#endif
      h->td->offset = h->td->bodystart;
      to_hstripped(h);
    }
  if (target->htext == ESE)
    {
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, ", to se");
#endif
      h->td->offset = h->td->bodystart;
      to_h7bit(h);
    }
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, ", done.\n");
#endif
  return(OK);
}

char *
out_data_part(struct data *d)
{
  char *c;
  c = (char *)Yalloc(2 + d->bodyend - d->bodystart);
  strncpy(c, d->contents + d->bodystart, d->bodyend - d->bodystart);
  return(c);
}




