/*
**
** 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 270for 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"
#define HQUOTE -1

/* Function definitions */
void process_header_line(struct header *, char *);
void save_header(struct message *, struct data *, short, int);
void clear_end(struct data *);
void add_header_item();
void add_header_delimiter();
static void code_header(struct header *);
static void code_hvalue(struct hprs *, struct data *);
int isheader(char *);
/* 
 * 
 */

int
load_header(struct message *m)
{
  struct data *line_buf;
  struct data *indata;
  int linelen;
  long startoffset;
  long startline;
  char *buf;

  indata = m->sd;

  line_buf = (struct data *)Yalloc(sizeof (struct data));
  /*
   * Make sure there is data to process, else exit.
   */
  if (indata->end <= indata->offset)
    return(OK);

  startoffset = indata->offset;
  startline = indata->loffset;

  /*
   * If current header is not empty, look for folded header lines.
   */
  while (1)
    {
      buf = indata->contents + indata->offset;

      if ((linelen = getline(indata)) == 0)
	{
	  /* End of data, illegal header */
	  indata->offset = startoffset; /* Rewind */
	  indata->loffset = startline; /* Rewind */
	  m->h = NULL;
	  return(NOK);
	}

#ifdef DEBUG
      if (edebug)
	{
	  fprintf(stderr, "Read: ");
	  fwrite(indata->contents + indata->offset, 1, linelen, stderr);
	}
#endif

      indata->loffset += 1;
      if ((*buf == ' ' || *buf == '\t') && line_buf->end != 0) 
	{
	  /* Unfold header line and append to previos line */
/*	  clear_end(line_buf); */
	  append_data(line_buf, buf, linelen, HEAD_BUF);
	}
      else
	/* Not folded yet */
	{
	  if (line_buf->end != 0)
	  /* If no header line yet, append header line */
	  /* Already got header line */
	    {
	      if (strncasecmp(line_buf->contents, "from ", 5) == 0 ||
		  strncasecmp(line_buf->contents,">from ", 6) == 0)
		{
		  /* Process folded UNIX from line */
		  save_header(m, line_buf, (short) UNIXFROM, RFC822);
		  line_buf->end = 0;
		  *(line_buf->contents) = '\0';
		}
	      else
		{
		  if (isheader(line_buf->contents))
		    {
		      save_header(m, line_buf, (short) EMHEADER, m->sd->format);
		      line_buf->end = 0;
		      *(line_buf->contents) = '\0';
		    }
		  else
		    {
		      /* Syntax error or end of header */
		      indata->offset = startoffset; /* Rewind */
		      sprintf(ebuf, "load_header: illegal end of header: %s", line_buf->contents);
		      logger(LOG_ERR, ebuf);
		      m->h = NULL;
		      return(NOK);
		    }
		}
	    }
	}
      if((linelen == 1 && *buf == '\n') || (linelen == 2 && *buf == '\r'))
	{
	  /* End of header */
	  indata->offset++;
	  return(OK);
	}
      
      if (line_buf->end == 0)
	{
	  append_data(line_buf, buf, linelen, HEAD_BUF);
	}
      indata->offset += linelen;
    }
  /* Not reached */
}

/* 
 * 
 */

void
clear_end(struct data *d)
{
  if (*(d->contents + d->end - 1) == '\n')
    {
      if (*(d->contents + d->end - 2) == '\r')
	{
	  *(d->contents + d->end - 2) = '\0';
	  d->end -= 2;
	}
      else
	{
	  *(d->contents + d->end - 1) = '\0';
	  d->end -= 1;
	}
    }
}


/* 
 * save_header()
 *
 * Save the header field and header value in a list of header structures.
 *
 */


void
save_header(struct message *mess, struct data *line, short type, int format)
{
  struct header *hdr, *thdr;
  char *value;
  hdr = (struct header *)Yalloc(sizeof(struct header));
  hdr->type = type;
  hdr->format = format;
  clear_end(line);
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "+ Save header: %s\n", line->contents);
#endif
  hdr->field = (struct data *)Yalloc(sizeof(struct data ));
  if (type == UNIXFROM)
    append_data(hdr->field, line->contents, line->end, HEAD_BUF);
  else
    {
      hdr->value = (struct data *)Yalloc(sizeof(struct data ));
      value = index(line->contents, ':');	assert(value);
      *value = '\0';
      value++;
      while(isspace(*value))
	value++;
      append_data(hdr->field, line->contents, strlen(line->contents), HEAD_BUF);
      append_data(hdr->value, value, strlen(value), HEAD_BUF);
    }
  if (mess->h == NULL)
    mess->h = hdr;
  else
    {
      for (thdr = mess->h; thdr->next != NULL; thdr = thdr->next)
	;
      thdr->next = hdr;
    }
  code_header(hdr);
}

void
add_header(struct message *mess, char *field, char *value, int format)
{
  struct header *hdr, *thdr;
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "+ Add header: %s: %s\n", field, value);
#endif

  if (mess->h != NULL)
    for (thdr = mess->h; thdr != NULL; thdr = thdr->next)
      {
	if (thdr->field->end != 0 && 
	    cmatch(thdr->field->contents, field) == TRUE)
	  {
	    thdr->value->offset = 0;
	    thdr->value->end = 0;
	    thdr->value->bodyend = 0;
	    append_data(thdr->value, value, strlen(value), HEAD_BUF);
	    append_char(thdr->value, '\0', HEAD_BUF); /* NULL terminate */
	    thdr->format = format;
	    thdr->hvalue = NULL;
	    return;
	  }
	hdr = thdr;
      }
  thdr = hdr;
  hdr = (struct header *)Yalloc(sizeof(struct header));
  hdr->type = EMHEADER;
  hdr->format = format;
  hdr->field = (struct data *)Yalloc(sizeof(struct data ));
  hdr->value = (struct data *)Yalloc(sizeof(struct data ));
  append_data(hdr->field, field, strlen(field), HEAD_BUF);
  if (value != NULL)
    append_data(hdr->value, value, strlen(value), HEAD_BUF);

  if (mess->h == NULL)
    mess->h = hdr;
  else
    thdr->next = hdr;
}
void
rm_header(struct message *mess, char *field)
{
  struct header *hdr, *thdr;
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "+ Remove header: %s: **\n", field);
#endif

  if (mess->h != NULL)
    for (thdr = mess->h; thdr != NULL; thdr = thdr->next)
      {
	if (thdr->field->end != 0 && 
	    cmatch(thdr->field->contents, field) == TRUE)
	  {
	    if (thdr != NULL && thdr == mess->h)
	      mess->h = thdr->next;
	    else
	      hdr->next = thdr->next;
	    break;
	  }
	hdr = thdr;
      }
}

int
isheader(char *string)
{
  char *t, *p;
  if ((t = index(string, ':')) == NULL || t == string)
    return(0);
  if ((p = index(string, ' ')) != NULL && p < t)
    return(0);
  return(1);
}


static	void code_header(struct header *th)
{
  struct data *outbuf;
  
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "+ code_header.\n");
#endif
  parse_rfc1522(th);
  if (th->hvalue != NULL)
    {
      outbuf = (struct data *)Yalloc(sizeof(struct data));
      (void)code_hvalue(th->hvalue, outbuf);
      th->value = outbuf;
    }
}

static void code_hvalue(struct hprs *h, struct data *outbuf)
{
#ifdef DEBUG
	  if (edebug)
	    fprintf(stderr, "+ code_hvalue.\n");
#endif

  switch (h->type)
    {
    case ATOM:
    case DELIMITER:
    case RFC1522:
      append_data(outbuf, h->td->contents + h->td->bodystart,
		  h->td->bodyend - h->td->bodystart, HEAD_BUF);
      break;
    case DLITERAL:
      append_char(outbuf, '[', HEAD_BUF);
      break;
    case COMMENT:
      append_char(outbuf, '(', HEAD_BUF);
      break;
    case HQSTRING:
      append_char(outbuf, '"', HEAD_BUF);
      break;
    case RADDR:
      append_char(outbuf, '<', HEAD_BUF);
      break;
    default:
      break;
    }
  if (h->child != NULL)
    code_hvalue(h->child, outbuf);
  switch(h->type)
    {
    case DLITERAL:
      append_char(outbuf, ']', HEAD_BUF);
      break;
    case COMMENT:
      append_char(outbuf, ')', HEAD_BUF);
      break;
    case HQSTRING:
      append_char(outbuf, '"', HEAD_BUF);
      break;
    case RADDR:
      append_char(outbuf, '>', HEAD_BUF);
      break;
    default:
      break;
    }
  
  if (h->sibling != NULL)
    code_hvalue(h->sibling, outbuf);
}

char *
clear_end_space(char *c)
{
  char *d;
  d = c + strlen(c) -1;
  while (isspace(*d))
    {
      *d = '\0';
      d--;
    }
  return(c);
}
