/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */


#include "exim.h"


/*************************************************
*            Add new header on end of chain      *
*************************************************/

/* The header_last variable points to the last header during message reception
and delivery; otherwise it is NULL. We add new headers only when header_last is
not NULL. The function may get called sometimes when it is NULL (e.g. during
address verification where rewriting options exist). When called from a filter,
there may be multiple header lines in a single string.

Arguments:
  type      Exim header type character
  format    sprintf format
  ...       arguments for the format

Returns:    nothing
*/

void
header_add(int type, char *format, ...)
{
header_line *new;
va_list ap;
uschar *p, *q;
uschar buffer[HEADER_ADD_BUFFER_SIZE];

if (header_last == NULL) return;

va_start(ap, format);
if (!string_vformat(buffer, sizeof(buffer), format, ap))
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string too long in header_add: %.100s ...",
    buffer);
va_end(ap);

/* Loop for multiple header lines, taking care about continuations */

for (p = q = buffer; *p != 0; )
  {
  for (;;)
    {
    q = Ustrchr(q, '\n');
    if (q == NULL) q = p + Ustrlen(p);
    if (*(++q) != ' ' && *q != '\t') break;
    }

  new = store_get(sizeof(header_line));
  new->text = string_copyn(p, q - p);

  new->slen = q - p;
  new->next = NULL;
  new->type = type;

  header_last->next = new;
  header_last = new;

  p = q;
  }
}


/*************************************************
*          Check the name of a header            *
*************************************************/

/* This function scans a table of header field names that Exim recognizes, and
returns the identification of a match. If "resent" is true, the header is known
to start with "resent-". In that case, the function matches only those fields
that are allowed to appear with resent- in front of them.

Arguments:
  h             points to the header line
  is_resent     TRUE if the name starts "Resent-"

Returns:        One of the htype_ enum values, identifying the header
*/

int
header_checkname(header_line *h, BOOL is_resent)
{
uschar *text = h->text;
header_name *bot = header_names;
header_name *top = header_names + header_names_size;

if (is_resent) text += 7;

while (bot < top)
  {
  header_name *mid = bot + (top - bot)/2;
  int c = strncmpic(text, mid->name, mid->len);

  if (c == 0)
    {
    uschar *s = text + mid->len;
    while (isspace(*s)) s++;
    if (*s == ':')
      return (!is_resent || mid->allow_resent)? mid->htype : htype_other;
    c = 1;
    }

  if (c > 0) bot = mid + 1; else top = mid;
  }

return htype_other;
}


/*************************************************
*       Scan a header for certain strings        *
*************************************************/

/* This function is used for the "personal" test. It scans a particular header
line for any one of a number of strings, matched caselessly either as plain
strings, or as regular expressions. If the header line contains a list of
addresses, each match is applied only to the operative part of each address in
the header, and non-regular expressions must be exact matches.

The patterns can be provided either as a chain of string_item structures, or
inline in the argument list, or both. If there is more than one header of the
same name, they are all searched.

Arguments:
  name           header name, including the trailing colon
  has_addresses  TRUE if the header contains a list of addresses
  cond           value to return if the header contains any of the strings
  strings        points to a chain of string_item blocks
  count          number of inline strings
  ...            the inline strings

Returns:         cond if the header exists and contains one of the strings;
                   otherwise !cond
*/


/* First we have a local subroutine to handle a single pattern */

static BOOL
one_pattern_match(uschar *name, int slen, BOOL has_addresses, uschar *pattern)
{
BOOL yield = FALSE;
header_line *h;
const pcre *re = NULL;

/* If the pattern is a regex, compile it. Bomb out if compiling fails; these
patterns are all constructed internally and should be valid. */

if (*pattern == '^') re = regex_must_compile(pattern, TRUE, FALSE);

/* Scan for the required header(s) and scan each one */

for (h = header_list; !yield && h != NULL; h = h->next)
  {
  if (h->type == htype_old || slen > h->slen ||
      strncmpic(name, h->text, slen) != 0)
    continue;

  /* If the header is a list of addresses, extract each one in turn, and scan
  it. A non-regex scan must be an exact match for the address. */

  if (has_addresses)
    {
    uschar *s = h->text + slen;

    while (!yield && *s != 0)
      {
      uschar *error, *next;
      uschar *e = parse_find_address_end(s, FALSE);
      int terminator = *e;
      int start, end, domain;

      /* Temporarily terminate the string at the address end while extracting
      the operative address within. */

      *e = 0;
      next = parse_extract_address(s, &error, &start, &end, &domain, FALSE);
      *e = terminator;

      /* Move on, ready for the next address */

      s = e;
      if (*s == ',') s++;

      /* If there is some kind of syntax error, just give up on this header
      line. */

      if (next == NULL) break;

      /* Otherwise, test for the pattern; a non-regex must be an exact match */

      yield = (re == NULL)?
        (strcmpic(next, pattern) == 0)
        :
        (pcre_exec(re, NULL, CS next, Ustrlen(next), 0, PCRE_EOPT, NULL, 0)
          >= 0);
      }
    }

  /* For headers that are not lists of addresses, scan the entire header line,
  and just require "contains" for non-regex patterns. */

  else
    {
    yield = (re == NULL)?
      (strstric(h->text, pattern, FALSE) != NULL)
      :
      (pcre_exec(re, NULL, CS h->text, h->slen, 0, PCRE_EOPT, NULL, 0) >= 0);
    }
  }

return yield;
}


/* The externally visible interface */

BOOL
header_match(uschar *name, BOOL has_addresses, BOOL cond, string_item *strings,
  int count, ...)
{
va_list ap;
string_item *s;
int i;
int slen = Ustrlen(name);

for (s = strings; s != NULL; s = s->next)
  {
  if (one_pattern_match(name, slen, has_addresses, s->text)) return cond;
  }

va_start(ap, count);
for (i = 0; i < count; i++)
  {
  if (one_pattern_match(name, slen, has_addresses, va_arg(ap, uschar *)))
    return cond;
  }
va_end(ap);

return !cond;
}

/* End of header.c */
