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

int do_sibling = FALSE;

/*
 * int
 * parse_message(inbuf, message)
 *
 * Parse message and divide into a hierarchical structure of message parts.
 *
 */

int
parse_message(struct message *m)
{
  struct data *inbuf;

  /* Initialize inbuf */
  inbuf = (struct data *)m->sd;

  /* Exit on empty input */
  if (inbuf->size == 0 || (inbuf->end <= inbuf->offset))
    {
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, "*** Empty input (failed).\n");
#endif
      logger(LOG_ERR, "parse_message: Empty input");
      return(NOK);
    }
  /* Check header if possible */
  if (!m->headerdone && (m->sd->format != RFC822 || m->level == 0))
      if (check_header(m) != OK)
	return(NOK);
      
  switch(m->sd->format)
    {
    case MIME:
      return(parse_mime_message(m));
      break;
    case MAILTOOL:
      return(parse_sun_message(m));
      break;
    case RFC822:
      return(parse_rfc822_message(m));
      break;
    default:
      break;
    }

  return(NOK);
}


int
move_past_boundary(struct message *m, char *boundary)
{
  int linelen;
  struct data *inbuf;
  inbuf = (struct data *)m->sd;
  /* Find start boundary */
  while (is_bound(inbuf, boundary) != TRUE)
    {
      if ((linelen = getline(inbuf)) == 0)
	{
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "*   move_past_boundary failed, EOF.\n");
#endif
	return(NOK);
	}
      inbuf->offset += linelen;
      if (inbuf->end <= inbuf->offset)
	{
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "move_past_boundary: cannot find boundary\n");
#endif
	  sprintf(ebuf, "move_past_boundary: cannot find boundary");
	  logger(LOG_ERR, ebuf);
	  return(NOK);
	}
      inbuf->loffset += 1;
    }
  /* Move past boundary */
  inbuf->offset += getline(inbuf);
  inbuf->loffset += 1;
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "+   Found  boundary, moved to %lu\n", inbuf->offset);
#endif
  return(OK);
}

int
check_header(struct message *m)
{
  m->headerdone = 1;
  if (load_header(m) == OK)
    /* Offset is updated with size of header! */
    {
      /* Decode the loaded header */
      decode_header(m);

      /* Set body lines, if available 
       * (body length is not trusted)
       */
      if (m->sd->bodylines != 0)
	m->sd->lineend = m->sd->loffset + m->sd->bodylines;
    }
  else
    {
      /* Load header failed */
      sprintf(ebuf, "check_header: load header failed: %lu", m->sd->loffset);
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, "%s\n", ebuf);
#endif
      logger(LOG_ERR, ebuf);
      return(NOK);
    }
  return(OK);
}

int
check_encoding(struct message *m)
{
  struct data *inbuf;
  int status;

  inbuf = m->sd;

  /* Check for encoding */
  switch (inbuf->encoding)
    {
    case 0:
      inbuf->encoding = E7BIT;
      status = OK;
      break;
    case EBINHEX:
      if (decode_binhex(m) == NOK)
	{
	  sprintf(ebuf, "WARNING: parse_message: BinHex decode failed :%lu", 
		  inbuf->loffset);
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "%s\n", ebuf);
#endif
	  logger(LOG_WARNING, ebuf);
	  status = NOK;
	}
      else
	status = OK;

      break;
    case EUUENCODE:
      if (decode_uuencode(m) == NOK)
	{
	  sprintf(ebuf, "WARNING: parse_message: UUencode decode failed :%lu", 
		  inbuf->loffset);
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "%s\n", ebuf);
#endif
	  logger(LOG_WARNING, ebuf);
	  status = NOK;
	}
      else
	status = OK;
      
      break;
    case EBASE64:
      if (decode_base64(m) == NOK)
	{
	  sprintf(ebuf, "WARNING: parse_message: Base64 decode failed :%lu", 
		  inbuf->loffset);
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "%s\n", ebuf);
#endif
	  logger(LOG_WARNING, ebuf);
	  status = NOK;
	}
      else
	status = OK;
      if (inbuf->bodyend == 0)
	inbuf->bodyend = inbuf->end;
      break;
    case EQP:
      if (decode_quoted_printable(m) == NOK)
	{
	  sprintf(ebuf, "WARNING: parse_message: Quoted-Printable decode failed :%lu", 
		  inbuf->loffset);
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "%s\n", ebuf);
#endif
	  logger(LOG_WARNING, ebuf);
	  status = NOK;
	}
      else
	status = OK;
      if (inbuf->bodyend == 0)
	inbuf->bodyend = inbuf->end;
      break;
    default:
      status = OK;
      break;
    }
  return(status);
}
int
boundary_check(struct message *m)
{
  struct message *parent;
  int encoding;
  int check;
  
  encoding = m->sd->encoding;
  check = m->sd->check;
  parent = m->parent;
#ifdef DEBUG
  if (edebug)
  fprintf(stderr, "checking part %d with check %d and encoding %d\nat offset %lu\n",
#endif
	  m->sd->count, check, encoding, m->sd->offset);
  /* Check a multipart, look for start boundary */
  if (parent != NULL && parent->sd->startbound != NULL && 
      (strlen(parent->sd->startbound)) != 0)
    {
      if (is_bound(m->sd, parent->sd->startbound))
	{
	  return(E7BIT); /* Just a precaution */
	}
    }      

  /* Check for uuencode */
  if (check & EUUENCODE)
    {
      if (encoding & EUUENCODE)
	return(0);
    /* Check for binhex and uuencode */
      if (strncmp((m->sd->contents + m->sd->offset), "begin ", 6) == 0)
	{
	  return(EUUENCODE);
	}
    }
  /* Check for BinHex */
  if (check & EBINHEX)
    {
      if (encoding & EBINHEX)
	return(0);
    /* Check for BinHex */
      if (strncasecmp((m->sd->contents + m->sd->offset), "(This file ", 11) == 0)
	{
	  return(EBINHEX);
	}
    }
  if (encoding == 0)
    return(E7BIT);
  return(0);
}


/*
 *
 */
int
is_bound(struct data *d, char *boundary)
{
  int blen;
  if (boundary != NULL && 
      (blen = strlen(boundary)) != 0)
    {
	/*
	printf("is_bound: '%20.20s'\n          '%20.20'\n",
		d->contents+d->offset,
		boundary);
	*/
      
      if (d->offset + blen < d->end &&
	  strncmp((d->contents + d->offset), boundary, blen) == 0)
	{
	  return(TRUE);
	}
    }      
  return(FALSE);
}


/*
 *
 */
struct message *
copy_mstruct(struct message *m, int force)
{
  struct data *cbuf, *inbuf;
  struct message *cm;
#ifdef DEBUG
  if (edebug)
  fprintf(stderr, "+ Copying structure\n");
#endif
  inbuf = m->sd;

  if (force < 2)
    {
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, "+   Creating new data structure\n");
#endif
      /* Copy data structure */
      cbuf = (struct data *) Yalloc(sizeof(struct data));
      cbuf->contents = inbuf->contents; /* data */
      cbuf->offset = inbuf->offset;     /* and offset */
      cbuf->check = inbuf->check;
      cbuf->size = inbuf->size;
      cbuf->count = inbuf->count + 1;
      cbuf->loffset = inbuf->loffset;
      cbuf->charset = inbuf->charset;
      cbuf->format = inbuf->format;
    }
  else
    cbuf = m->sd;


  /* Copy message structure */
  if (force < 1 && m->sd->format == RFC822)
    {
#ifdef DEBUG
      if (edebug)
      fprintf(stderr, "+   Living with old message structure\n");
#endif
      m->sd->next = cbuf;
      m->sd = m->sd->next;
      m->td = m->sd;
#ifdef DEBUG
      if (edebug)
      fprintf(stderr, "Reached offset %lu\n", m->sd->offset);
#endif
      return(m);
    }
  else
    {
#ifdef DEBUG
      if (edebug)
      fprintf(stderr, "+   Copying message structure\n");
#endif
      cm = (struct message *) Yalloc(sizeof(struct message));
      cm->sdl = cbuf;
      cm->sd = cm->sdl;
      cm->td = cbuf;
      return(cm);
    }
}


/*
 * int
 * getline(inbuf)
 *
 * Move pointer to next line of inbuf. Return line length.
 */

int
getline(struct data *inbuf)
{
  unsigned char *tmp;
  if (inbuf == NULL || inbuf->contents == NULL ||
      inbuf->offset >= inbuf->end)
    return(0);
  tmp = index(inbuf->contents + inbuf->offset, '\n');
  if (tmp == NULL || (tmp - inbuf->contents) >= inbuf->end)
    return(inbuf->end - inbuf->offset);
  else
    return(tmp - inbuf->contents + 1 - inbuf->offset);
}


int
set_end_by_boundary(struct message *m, char *boundary)
{
  int linelen, offset, loffset;
  struct data *inbuf;
  inbuf = (struct data *)m->sd;

#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "*  set_end_by_boundary\n");
#endif
  offset = inbuf->offset;
  loffset = inbuf->loffset;
  while (is_bound(inbuf, boundary) != TRUE)
    {
      if ((linelen = getline(inbuf)) == 0)
	{
	  if (m->parent)
	    inbuf->bodyend = m->parent->sd->bodyend;
	  else
	    inbuf->bodyend = inbuf->offset;
	  inbuf->lineend = inbuf->loffset;
	  inbuf->offset = offset;
	  inbuf->loffset = loffset;
	  sprintf(ebuf, "set_end_by_boundary: cannot find boundary, setting end to %lu\n", inbuf->bodyend);
	  logger(LOG_ERR, ebuf);
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "set_end_by_boundary: cannot find boundary, setting end to %lu\n", inbuf->bodyend);
#endif
	  return(NOK);
	}
      inbuf->offset += linelen;
      inbuf->loffset += 1;
    }
  /* Set bodyend to just before the end boundary */
  inbuf->bodyend = inbuf->offset;
  inbuf->lineend = inbuf->loffset;
  if (m->sd->format == MIME)
    inbuf->bodyend -= 1;
  inbuf->offset = offset;
  inbuf->loffset = loffset;
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "+   Found boundary, setting end at %lu.\n", inbuf->bodyend);
#endif
  return(OK);
}

int
set_end_by_lines(struct message *m, int lines)
{
  int linelen, offset, loffset, lineend;
  struct data *inbuf;
  inbuf = (struct data *)m->sd;

#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "*  set_end_by_lines\n");
#endif
  offset = inbuf->offset;
  loffset = inbuf->loffset;
  lineend = loffset + lines;
  while ((linelen = getline(inbuf)) != 0 && lineend > inbuf->loffset)
	{
	  inbuf->offset += linelen;
	  inbuf->loffset += 1;
	}
  
  /* Set bodyend to just before the end boundary */
  inbuf->bodyend = inbuf->offset;
  inbuf->lineend = inbuf->loffset;
  inbuf->offset = offset;
  inbuf->loffset = loffset;
  return(OK);
}

