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

static	int is_bound(struct data *, char *); /* forward */

int
boundary_check(struct message *m)
{
  struct message *parent;
  int encoding;
  int check;
  
  encoding = m->sd->encoding;
  check = m->sd->check;
  parent = m->parent;
  /* 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);
}


/*
 *
 */
static	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)
{
  struct data *cbuf, *inbuf;
  struct message *cm;

  inbuf = m->sd;
  /* 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->loffset = inbuf->loffset;
  cbuf->charset = inbuf->charset;
  cbuf->format = inbuf->format;


  /* Copy message structure */
  cm = (struct message *) Yalloc(sizeof(struct message));
  cm->sd = cbuf;
  cm->td = cbuf;
  return(cm);
}


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

int
getline(struct data *inbuf)
{
  char *tmp;
  if (inbuf->contents == NULL)
    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
 * parse_message(inbuf, message)
 *
 * Parse message and divide into a hierarchical structure of message parts.
 *
 */

int
parse_message(struct message *m)
{
  struct data *inbuf;
  struct message *parent;
  int linelen;
  int encoding;
  int do_sibling = FALSE;

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

#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "+ Parsing body part level: %i.\n", m->level);
#endif

  /* 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);
    }


  /* Find start of multipart body parts */
  parent = m->parent;
  if (parent != NULL && parent->sd->startbound != NULL)
    {
      /* Find start boundary */
      while (is_bound(inbuf, parent->sd->startbound) != TRUE)
	{
	  if ((linelen = getline(inbuf)) == 0)
	    break;
	  inbuf->offset += linelen;
	  if (inbuf->end <= inbuf->offset)
	    return(NOK);
	  inbuf->loffset += 1;
	}
      /* Move past boundary */
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, "+   Found start boundary.\n");
#endif
      inbuf->offset += getline(inbuf);
      inbuf->loffset += 1;
    }



  /* Check header if possible */
  if (m->sd->format != RFC822 || m->level == 0)
    {
      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)
	    inbuf->lineend = inbuf->loffset + m->sd->bodylines;
	}
      else
	{
	  /* Load header failed */
	  sprintf(ebuf, "parse_message: load header failed: %li", inbuf->loffset);
	  logger(LOG_ERR, ebuf);
	  return(NOK);
	}
    }
      
  /* Body starts here */
  inbuf->bodystart = inbuf->offset;
  inbuf->linestart = inbuf->loffset;
  

  /* If Multipart or a standard rfc822 message (that may contain a mix 
   * of text and encodings) create a child and run parse_message 
   * on the child.
   * If MIME Multipart define the end boundary as end of 
   * current data. If Mailtool Multipart define end according 
   * to the lines field. Else leave end undefined. 
   */

  if (inbuf->encoding == EMULTI)
    {
      struct data *childbuf;
      struct message *childm;

      /* Copy off a child */
      childm = copy_mstruct(m);

      /* Find end of part if prematured by an end boundary (MIME) */
      if (m->sd->endbound != NULL)
	{
	  while (is_bound(inbuf, m->sd->endbound) != TRUE)
	    {
	      if ((linelen = getline(inbuf)) == 0)
		break;
	      inbuf->offset += linelen;
	      if (inbuf->end <= inbuf->offset)
		return(OK);
	      inbuf->loffset += 1;
	    }
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "+   Found end boundary.\n");
#endif
	  /* Set bodyend to just before the end boundary */
	  inbuf->bodyend = inbuf->offset;
	  inbuf->lineend = inbuf->loffset;
	  if (inbuf->format == MIME)
	    inbuf->bodyend -= 1;
	}
      else
	{
	  /* Body end is end of data */
	  inbuf->bodyend = inbuf->end;
	  inbuf->lineend = inbuf->lend;
	}
	  


      /* Set more child data */
      childbuf = childm->sd;
      childm->level = m->level + 1;
      childm->parent = m;
      childbuf->end = (inbuf->bodyend != 0) ? inbuf->bodyend: inbuf->end;
      childbuf->lend = (inbuf->lineend != 0) ? inbuf->lineend: inbuf->lend;

      /* Parse child */
      if (parse_message(childm) == OK)
	{
	  /* Parse successful.
	   * Connect child to current part and beware that data
	   * now belongs to the children. If the contents was 
	   * only one part, fix this here, to reduce the depth 
	   * of this part of the structure.
	   */

	  /* Attach child */
	  m->child = childm;
	  /* Move past end boundary, (this is the childs parent) */
	  if (m->sd->endbound != NULL)
	    {
	      while (is_bound(inbuf, m->sd->endbound) == TRUE)
		{
		  if ((linelen = getline(inbuf)) == 0)
		    break;
		  inbuf->offset += linelen;
		  if (inbuf->end <= inbuf->offset)
		    {
		      inbuf->bodyend = inbuf->end;
		      inbuf->lineend = inbuf->loffset;
		      return(OK);
		    }
		  inbuf->loffset += 1;
		}
	    }
	  if (m->level == 0)
	    return(OK); /* All data processed */
	}
      else
	{
	  /* Erroneous multipart, set type to text instead and
	   * run test check below 
	   */
	}
    }

  if (inbuf->encoding == 0)
    {
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, "+   No encoding (7bit)\n");
#endif
      inbuf->encoding = E7BIT;
    }

  /* Loop through rest of the message */
  while ((linelen = getline(inbuf)) != 0)
    {
      if (inbuf->bodyend <= inbuf->offset &&
	  inbuf->lineend <= inbuf->loffset)
	/* Either bodyend not set or bodyend reached */
	{
	  
	  if ((encoding = boundary_check(m)) != 0 || do_sibling == TRUE)
	    {
	      /* If boundary check successful, create sibling.
	       */
	      struct data *sibbuf;
	      struct message *sibm;
	      
#ifdef DEBUG
	      if (edebug)
		fprintf(stderr, "+   Boundary check OK.\n");
#endif
	      do_sibling = FALSE;
	      /* Allocate siblings data sructure */
	      sibm = copy_mstruct(m);
	      sibbuf = sibm->sd;
	      sibbuf->encoding = encoding;
	      sibbuf->end = inbuf->end;
	      sibbuf->lend = inbuf->lend;
	      sibm->bigsib = m;
	      sibm->level = m->level;
	      sibm->parent = m->parent;
	      /* Run Sibling */
	      if (parse_message(sibm) == OK)
		{
		  /* If sibling went successful set end of current 
		   * part and attach sibling to current.
		   */
		  m->sibling = sibm;
		  inbuf->bodyend = inbuf->offset;
		  inbuf->lineend = inbuf->loffset;
		  if (inbuf->format == MIME)
		    inbuf->bodyend -= 1;
		  /* Successful return from current part */
		  return(OK);
		}
	      else
	      /* parse_message(sibm) failed, data is already rewound 
	       * and sibling remains unattached.
	       */
		{
		  inbuf->offset += linelen;
		  inbuf->loffset += 1;
		}
	    }
	  else
	    {
	      /* boundary_check(m) failed, continue with current */
	      if (inbuf->bodyend == 0)
		/* End not reached because boundary check failed;
		 * data processing may continue 
		 */
		{
		  /* Process data.
		   * If typed data, MIME or Mailtool, trust types
		   * and perform boundary checks. If untyped raw rfc822
		   * data, uuencode or BinHex (who have an explicit
		   * end), process until that end is found.
		   */
		  if (inbuf->encoding == EBINHEX)
		    {
		      if (decode_binhex(m) == NOK)
			{
			  sprintf(ebuf, "WARNING: parse_message: BinHex decode failed :%li", inbuf->loffset);
			  logger(LOG_WARNING, ebuf);
			  return(NOK);
			}
			else
			  {
			    inbuf->lineend = inbuf->loffset;
			    inbuf->bodyend = inbuf->offset;
			    /* return(OK); */
			  }
		    } 
		  else
		    if (inbuf->encoding == EUUENCODE)
		      {
			if (decode_uuencode(m) == NOK)
			  {
			    sprintf(ebuf, "WARNING: parse_message: UUdecode failed :%li", inbuf->loffset);
			    logger(LOG_WARNING, ebuf);
			    return(NOK);
			  }
			else
			  {
			    inbuf->lineend = inbuf->loffset;
			    inbuf->bodyend = inbuf->offset;
			    /* return(OK); */
			  }
		      }
		    else
		      /* Trust what we've got and look for boundary */
		      {
			inbuf->offset += linelen;
			inbuf->loffset += 1;
		      }
		}
	      else
		/* inbuf->bodyend != 0 
		 * Body end set and reached.
		 */
		{
		  do_sibling = TRUE;
		}
	    }
	}
      else
	/* Body end set but not reached, process data */
	{
	  /* Process data.
	   * If typed data, MIME or Mailtool, trust types
	   * and perform boundary checks. If untyped raw rfc822
	   * data, uuencode or BinHex (who have an explicit
	   * end), process until that end is found. Done above.
	   */
	  inbuf->offset += linelen;
	  inbuf->loffset += 1;
	}
      
    }
  /* End of data */
  if (inbuf->bodyend == 0)
    inbuf->bodyend = inbuf->offset;
  if (inbuf->lineend == 0)
    inbuf->lineend = inbuf->loffset;
  return(OK);
}
