/*************************************************
*     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"
#include "lf_functions.h"
#include "lsearch.h"



/*************************************************
*              Open entry point                  *
*************************************************/

/* See local README for interface description */

void *
lsearch_open(uschar *filename, uschar **errmsg)
{
FILE *f = Ufopen(filename, "rb");
if (f == NULL)
  {
  int save_errno = errno;
  *errmsg = string_open_failed(errno, "%s for linear search", filename);
  errno = save_errno;
  return NULL;
  }
return f;
}



/*************************************************
*             Check entry point                  *
*************************************************/

BOOL
lsearch_check(void *handle, uschar *filename, int modemask, uid_t *owners,
  gid_t *owngroups, uschar **errmsg)
{
return lf_check_file(fileno((FILE *)handle), filename, S_IFREG, modemask,
  owners, owngroups, "lsearch", errmsg) == 0;
}



/*************************************************
*  Internal function for lsearch/(n)wildlsearch  *
*************************************************/

/* See local README for interface description, plus two one additional
arguments: wild is TRUE for wildlsearch, and expand is TRUE for pattern
expansion.

There is some messy logic in here to cope with very long data lines that do not
fit into the fixed sized buffer. Most of the time this will never be exercised,
but people do occasionally do weird things. */

static int
internal_lsearch_find(void *handle, uschar *filename, uschar *keystring,
  int length, uschar **result, uschar **errmsg, BOOL wild, BOOL expand)
{
FILE *f = (FILE *)handle;
BOOL last_was_eol = TRUE;
BOOL this_is_eol = TRUE;
uschar buffer[4096];

filename = filename;  /* Keep picky compilers happy */
errmsg = errmsg;

rewind(f);
for (last_was_eol = TRUE;
     Ufgets(buffer, sizeof(buffer), f) != NULL;
     last_was_eol = this_is_eol)
  {
  int ptr, size;
  int p = Ustrlen(buffer);
  int linekeylength;
  uschar *yield;
  uschar *s = buffer;

  /* Check whether this the final segment of a line. If it follows an
  incomplete part-line, skip it. */

  this_is_eol = p > 0 && buffer[p-1] == '\n';
  if (!last_was_eol) continue;

  /* We now have the start of a physical line. If this is a final line segment,
  remove trailing white space. */

  if (this_is_eol)
    {
    while (p > 0 && isspace((uschar)buffer[p-1])) p--;
    buffer[p] = 0;
    }

  /* If the buffer is empty it might be (a) a complete empty line, or (b) the
  start of a line that begins with so much white space that it doesn't all fit
  in the buffer. In both cases we want to skip the entire physical line.

  If the buffer begins with # it is a comment line; if it begins with white
  space it is a logical continuation; again, we want to skip the entire
  physical line. */

  if (buffer[0] == 0 || buffer[0] == '#' || isspace(buffer[0])) continue;

  /* We assume that they key will fit in the buffer. If the key starts with ",
  read it as a quoted string. We don't use string_dequote() because that uses
  new store for the result, and we may be doing this many times in a long file.
  We know that the dequoted string must be shorter than the original, because
  we are removing the quotes, and also any escape sequences always turn two or
  more characters into one character. Therefore, we can store the new string in
  the same buffer. */

  if (*s == '\"')
    {
    uschar *t = s++;
    while (*s != 0 && *s != '\"')
      {
      if (*s == '\\') *t++ = string_interpret_escape(&s);
        else *t++ = *s;
      s++;
      }
    if (*s != 0) s++;               /* Past terminating " */
    linekeylength = t - buffer;
    }

  /* Otherwise it is terminated by a colon or white space */

  else
    {
    while (*s != 0 && *s != ':' && !isspace(*s)) s++;
    linekeylength = s - buffer;
    }

  /* A wild lsearch treats each key as a possible wildcarded string. */

  if (wild)
    {
    int rc;
    int save = buffer[linekeylength];
    uschar *list = buffer;
    buffer[linekeylength] = 0;
    rc = match_isinlist(keystring,
      &list,
      UCHAR_MAX+(expand? 1:2),  /* Single-item list, possibly expanded */
      NULL,                     /* No anchor */
      NULL,                     /* No caching */
      MCL_STRING,
      TRUE,  /* Caseless */
      NULL);
    buffer[linekeylength] = save;

    if (rc == FAIL) continue;
    if (rc == DEFER) return DEFER;
    }

  /* A non-wild lsearch treats each key as a litersl */

  else
    {
    if (linekeylength != length || strncmpic(buffer, keystring, length) != 0)
      continue;
    }

  /* The key has matched. Skip spaces after the key, and allow an optional
  colon after the spaces. This is an odd specification, but it's for
  compatibility. */

  while (isspace((uschar)*s)) s++;
  if (*s == ':')
    {
    s++;
    while (isspace((uschar)*s)) s++;
    }

  /* Now we want to build the result string to contain the data. There can be
  two kinds of continuation: (a) the physical line may not all have fitted into
  the buffer, and (b) there may be logical continuation lines, for which we
  must convert all leading white space into a single blank.

  Initialize, and copy the first segment of data. */

  size = 100;
  ptr = 0;
  yield = store_get(size);
  if (*s != 0)
    yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));

  /* Now handle continuations */

  for (last_was_eol = this_is_eol;
       Ufgets(buffer, sizeof(buffer), f) != NULL;
       last_was_eol = this_is_eol)
    {
    s = buffer;
    p = Ustrlen(buffer);
    this_is_eol = p > 0 && buffer[p-1] == '\n';

    /* Remove trailing white space from a physical line end */

    if (this_is_eol)
      {
      while (p > 0 && isspace((uschar)buffer[p-1])) p--;
      buffer[p] = 0;
      }

    /* If this is not a physical line continuation, skip it entirely if it's
    empty or starts with #. Otherwise, break the loop if it doesn't start with
    white space. Otherwise, replace leading white space with a single blank. */

    if (last_was_eol)
      {
      if (buffer[0] == 0 || buffer[0] == '#') continue;
      if (!isspace((uschar)buffer[0])) break;
      while (isspace((uschar)*s)) s++;
      *(--s) = ' ';
      }

    /* Join a physical or logical line continuation onto the result string. */

    yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
    }

  yield[ptr] = 0;
  *result = yield;
  return OK;
  }

return FAIL;
}


/*************************************************
*         Find entry point for lsearch           *
*************************************************/

/* See local README for interface description */

int
lsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
  uschar **result, uschar **errmsg)
{
return internal_lsearch_find(handle, filename, keystring, length, result,
  errmsg, FALSE, FALSE);
}



/*************************************************
*      Find entry point for wildlsearch          *
*************************************************/

/* See local README for interface description */

int
wildlsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
  uschar **result, uschar **errmsg)
{
return internal_lsearch_find(handle, filename, keystring, length, result,
  errmsg, TRUE, TRUE);
}



/*************************************************
*      Find entry point for nwildlsearch         *
*************************************************/

/* See local README for interface description */

int
nwildlsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
  uschar **result, uschar **errmsg)
{
return internal_lsearch_find(handle, filename, keystring, length, result,
  errmsg, TRUE, FALSE);
}



/*************************************************
*              Close entry point                 *
*************************************************/

/* See local README for interface description */

void
lsearch_close(void *handle)
{
fclose((FILE *)handle);
}

/* End of lookups/lsearch.c */
