/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 1999-2025 Free Software Foundation, Inc.

   GNU Mailutils 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 3, or (at your option)
   any later version.

   GNU Mailutils is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS 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 GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */

#include "imap4d.h"
#include <mailutils/assoc.h>

/*
 * This will be a royal pain in the arse to implement
 * Alain: True, but the new lib mailbox should coming handy with
 * some sort of query interface.
 * Sergey: It was, indeed.
 */

/* Implementation details:

   The searching criteria are parsed and a parse tree is created. Each
   node is of type search_node (see below) and contains either data
   (struct value) or an instruction, which evaluates to a boolean value.

   The function search_run recursively evaluates the tree and returns a
   boolean number, 0 or 1 depending on whether the current message meets
   the search conditions. */

struct parsebuf;

enum value_type
  {
    value_undefined,
    value_number,
    value_string,
    value_date,
    value_msgset
  };

enum node_type
  {
    node_call,
    node_and,
    node_or,
    node_not,
    node_value,
    node_false
  };

struct value
{
  enum value_type type;
  union
  {
    char *string;
    mu_off_t number;
    time_t date;
    mu_msgset_t msgset;
  } v;
};

#define MAX_NODE_ARGS 2

struct search_node;

typedef void (*instr_fn) (struct parsebuf *, struct search_node *,
			  struct value *, struct value *);

struct search_node
{
  enum node_type type;
  union
  {
    struct key_node
    {
      char *keyword;
      instr_fn fun;
      int narg;
      struct search_node *arg[MAX_NODE_ARGS];
    } key;
    struct search_node *arg[2]; /* Binary operation */
    struct value value;
  } v;
};

static void cond_msgset (struct parsebuf *, struct search_node *,
			 struct value *, struct value *);
static void cond_bcc (struct parsebuf *, struct search_node *,
		      struct value *, struct value *);
static void cond_before (struct parsebuf *, struct search_node *,
			 struct value *, struct value *);
static void cond_body (struct parsebuf *, struct search_node *,
		       struct value *, struct value *);
static void cond_cc (struct parsebuf *, struct search_node *,
		     struct value *, struct value *);
static void cond_from (struct parsebuf *, struct search_node *,
		       struct value *, struct value *);
static void cond_header (struct parsebuf *, struct search_node *,
			 struct value *, struct value *);
static void cond_keyword (struct parsebuf *, struct search_node *,
			  struct value *, struct value *);
static void cond_larger (struct parsebuf *, struct search_node *,
			 struct value *, struct value *);
static void cond_on (struct parsebuf *, struct search_node *,
		     struct value *, struct value *);
static void cond_sentbefore (struct parsebuf *, struct search_node *,
			     struct value *, struct value *);
static void cond_senton (struct parsebuf *, struct search_node *,
			 struct value *, struct value *);
static void cond_sentsince (struct parsebuf *, struct search_node *,
			    struct value *, struct value *);
static void cond_since (struct parsebuf *, struct search_node *,
			struct value *, struct value *);
static void cond_smaller (struct parsebuf *, struct search_node *,
			  struct value *, struct value *);
static void cond_subject (struct parsebuf *, struct search_node *,
			  struct value *, struct value *);
static void cond_text (struct parsebuf *, struct search_node *,
		       struct value *, struct value *);
static void cond_to (struct parsebuf *, struct search_node *,
		     struct value *, struct value *);
static void cond_uid (struct parsebuf *, struct search_node *,
		      struct value *, struct value *);

/* A basic condition structure */
struct cond
{
  char *name;          /* Condition name */
  char *argtypes;      /* String of argument types or NULL if it takes no
			  args */
  instr_fn inst;       /* Corresponding instruction function */
};

/* Types are: s -- string
	      n -- number
	      d -- date
	      m -- message set
*/

/* List of basic conditions. "ALL" and <message set> is handled separately */
struct cond condlist[] =
{
  { "BCC",        "s",  cond_bcc },
  { "BEFORE",     "d",  cond_before },
  { "BODY",       "s",  cond_body },
  { "CC",         "s",  cond_cc },
  { "FROM",       "s",  cond_from },
  { "HEADER",     "ss", cond_header },
  { "KEYWORD",    "s",  cond_keyword },
  { "LARGER",     "n",  cond_larger },
  { "ON",         "d",  cond_on },
  { "SENTBEFORE", "d",  cond_sentbefore },
  { "SENTON",     "d",  cond_senton },
  { "SENTSINCE",  "d",  cond_sentsince },
  { "SINCE",      "d",  cond_since },
  { "SMALLER",    "n",  cond_smaller },
  { "SUBJECT",    "s",  cond_subject },
  { "TEXT",       "s",  cond_text },
  { "TO",         "s",  cond_to },
  { "UID",        "u",  cond_uid },
  { NULL }
};

/* Other search keys described by rfc2060 are implemented on top of these
   basic conditions. Condition equivalence structure defines the equivalent
   condition in terms of basic ones. (Kind of macro substitution) */

struct cond_equiv
{
  char *name;           /* RFC2060 search key name */
  char *equiv;          /* Equivalent query in terms of basic conds */
};

struct cond_equiv equiv_list[] =
{
  { "ANSWERED",   "KEYWORD \\Answered" },
  { "DELETED",    "KEYWORD \\Deleted" },
  { "DRAFT",      "KEYWORD \\Draft" },
  { "FLAGGED",    "KEYWORD \\Flagged" },
  { "NEW",        "(RECENT UNSEEN)" },
  { "OLD",        "NOT RECENT" },
  { "RECENT",     "KEYWORD \\Recent" },
  { "SEEN",       "KEYWORD \\Seen" },
  { "UNANSWERED", "NOT KEYWORD \\Answered" },
  { "UNDELETED",  "NOT KEYWORD \\Deleted" },
  { "UNDRAFT",    "NOT KEYWORD \\Draft" },
  { "UNFLAGGED",  "NOT KEYWORD \\Flagged" },
  { "UNKEYWORD",  "NOT KEYWORD" },
  { "UNSEEN",     "NOT KEYWORD \\Seen" },
  { NULL }
};

/* A memory allocation chain used to keep track of objects allocated during
   the recursive-descend parsing. */
struct mem_chain
{
  struct mem_chain *next;
  void *mem;
  void (*free_fun) (void *);
};

/* Code and stack sizes for execution of compiled search statement */
#define CODESIZE 64
#define CODEINCR 16
#define STACKSIZE 64
#define STACKINCR 16

/* Maximum length of a token. Tokens longer than that are accepted, provided
   that they are enclosed in doublequotes */
#define MAXTOKEN 64

/* Parse buffer structure */
struct parsebuf
{
  imap4d_tokbuf_t tok;          /* Token buffer */
  int arg;                      /* Argument number */
  char *token;                  /* Current token */
  int isuid;                    /* UIDs instead of msgnos are required */
  char *err_mesg;               /* Error message if a parse error occurred */
  struct mem_chain *alloc;      /* Chain of objects allocated during parsing */
  char *charset;                /* Charset, other than US-ASCII requested */

  struct search_node *tree;     /* Parse tree */

				/* Execution time only: */
  size_t msgno;                 /* Number of current message */
  mu_message_t msg;             /* Current message */
};

static void parse_free_mem (struct parsebuf *pb);
static void *parse_regmem (struct parsebuf *pb, void *mem, void (*f)(void*));
static char *parse_strdup (struct parsebuf *pb, char *s);
static void *parse_alloc (struct parsebuf *pb, size_t size);
static struct search_node *parse_search_key_list (struct parsebuf *pb);
static struct search_node *parse_search_key (struct parsebuf *pb);
static int parse_gettoken (struct parsebuf *pb, int req);
static int search_run (struct parsebuf *pb);
static void do_search (struct parsebuf *pb);
static int available_charset (const char *charset);

/*
6.4.4.  SEARCH Command

   Arguments:  OPTIONAL [CHARSET] specification
	       searching criteria (one or more)

   Responses:  REQUIRED untagged response: SEARCH

   Result:     OK - search completed
	       NO - search error: can't search that [CHARSET] or
		    criteria
	       BAD - command unknown or arguments invalid
*/

int
imap4d_search (struct imap4d_session *session,
	       struct imap4d_command *command, imap4d_tokbuf_t tok)
{
  int rc;
  char *err_text= "";

  rc = imap4d_search0 (tok, 0, &err_text);
  return io_completion_response (command, rc, "%s", err_text);
}

int
imap4d_search0 (imap4d_tokbuf_t tok, int isuid, char **err_text)
{
  struct parsebuf parsebuf;

  memset (&parsebuf, 0, sizeof(parsebuf));
  parsebuf.tok = tok;
  parsebuf.arg = IMAP4_ARG_1 + !!isuid;
  parsebuf.err_mesg = NULL;
  parsebuf.alloc = NULL;
  parsebuf.isuid = isuid;

  if (!parse_gettoken (&parsebuf, 0))
    {
      *err_text = "Too few args";
      return RESP_BAD;
    }

  if (mu_c_strcasecmp (parsebuf.token, "CHARSET") == 0)
    {
      if (!parse_gettoken (&parsebuf, 0))
	{
	  *err_text = "Too few args";
	  return RESP_BAD;
	}

      if (mu_c_strcasecmp (parsebuf.token, "US-ASCII"))
	{
	  parsebuf.charset = parse_strdup (&parsebuf, parsebuf.token);
	  if (!available_charset (parsebuf.charset))
	    {
	      *err_text = "[BADCHARSET] Charset not supported";
	      return RESP_NO;
	    }
	}
      else
	parsebuf.charset = NULL;

      if (!parse_gettoken (&parsebuf, 0))
	{
	  *err_text = "Too few args";
	  return RESP_BAD;
	}
    }

  /* Compile the expression */
  parsebuf.tree = parse_search_key_list (&parsebuf);
  if (!parsebuf.tree)
    {
      *err_text = parsebuf.err_mesg ? parsebuf.err_mesg : "Parse error";
      parse_free_mem (&parsebuf);
      return RESP_BAD;
    }

  if (parsebuf.token)
    {
      parse_free_mem (&parsebuf);
      *err_text = "Junk at the end of statement";
      return RESP_BAD;
    }

  /* Execute compiled expression */
  do_search (&parsebuf);

  parse_free_mem (&parsebuf);

  *err_text = "Completed";
  return RESP_OK;
}

/* For each message from the mailbox execute the query from `pb' and
   output the message number if the query returned 1 */
void
do_search (struct parsebuf *pb)
{
  size_t count = 0;

  mu_mailbox_messages_count (mbox, &count);

  io_sendf ("* SEARCH");
  for (pb->msgno = 1; pb->msgno <= count; pb->msgno++)
    {
      if (mu_mailbox_get_message (mbox, pb->msgno, &pb->msg) == 0
	  && search_run (pb))
	{
	  if (pb->isuid)
	    {
	      size_t uid;
	      mu_message_get_uid (pb->msg, &uid);
	      io_sendf (" %s", mu_umaxtostr (uid));
	    }
	  else
	    io_sendf (" %s", mu_umaxtostr (pb->msgno));
	}
    }
  io_sendf ("\n");
}

/* Parse buffer functions */

int
parse_gettoken (struct parsebuf *pb, int req)
{
  if (req && pb->arg >= imap4d_tokbuf_argc (pb->tok))
    {
      pb->err_mesg = "Unexpected end of statement";
      return 0;
    }
  pb->token = imap4d_tokbuf_getarg (pb->tok, pb->arg++);
  return 1;
}

/* Memory handling */

/* Free all memory allocated for parsebuf structure */
void
parse_free_mem (struct parsebuf *pb)
{
  struct mem_chain *alloc, *next;
  alloc = pb->alloc;
  while (alloc)
    {
      next = alloc->next;
      if (alloc->free_fun)
	alloc->free_fun (alloc->mem);
      else
	free (alloc->mem);
      free (alloc);
      alloc = next;
    }
}

/* Register a memory pointer mem with the parsebuf */
void *
parse_regmem (struct parsebuf *pb, void *mem, void (*f) (void*))
{
  struct mem_chain *mp;

  mp = mu_alloc (sizeof(*mp));
  mp->next = pb->alloc;
  pb->alloc = mp;
  mp->mem = mem;
  mp->free_fun = f;
  return mem;
}

/* Allocate `size' bytes of memory within parsebuf structure */
void *
parse_alloc (struct parsebuf *pb, size_t size)
{
  void *p = mu_alloc (size);
  return parse_regmem (pb, p, NULL);
}

/* Create a copy of the string. */
char *
parse_strdup (struct parsebuf *pb, char *s)
{
  s = mu_strdup (s);
  if (!s)
    imap4d_bye (ERR_NO_MEM);
  return parse_regmem (pb, s, NULL);
}

static void
free_msgset (void *ptr)
{
  mu_msgset_free (ptr);
}

mu_msgset_t
parse_msgset_create (struct parsebuf *pb, mu_mailbox_t mbox, int flags)
{
  mu_msgset_t msgset;

  if (mu_msgset_create (&msgset, mbox, flags))
    imap4d_bye (ERR_NO_MEM);
  return parse_regmem (pb, msgset, free_msgset);
}

/* A recursive-descent parser for the following grammar:
   search_key_list : search_key
		   | search_key_list search_key
		   ;

   search_key      : simple_key
		   | NOT simple_key
		   | OR simple_key simple_key
		   | '(' search_key_list ')'
		   ;
*/

struct search_node *parse_simple_key (struct parsebuf *pb);
struct search_node *parse_equiv_key (struct parsebuf *pb);

struct search_node *
parse_search_key_list (struct parsebuf *pb)
{
  struct search_node *leftarg = NULL;

  while (pb->token && pb->token[0] != ')')
    {
      struct search_node *rightarg = parse_search_key (pb);
      if (!rightarg)
	return NULL;
      if (!leftarg)
	leftarg = rightarg;
      else
	{
	  struct search_node *node = parse_alloc (pb, sizeof *node);
	  node->type = node_and;
	  node->v.arg[0] = leftarg;
	  node->v.arg[1] = rightarg;
	  leftarg = node;
	}
    }
  return leftarg;
}

struct search_node *
parse_search_key (struct parsebuf *pb)
{
  struct search_node *node;

  if (strcmp (pb->token, "(") == 0)
    {
      if (parse_gettoken (pb, 1) == 0)
	return NULL;

      node = parse_search_key_list (pb);
      if (!node)
	return NULL;

      if (strcmp (pb->token, ")"))
	{
	  pb->err_mesg = "Unbalanced parenthesis";
	  return NULL;
	}
      parse_gettoken (pb, 0);
      return node;
    }
  else if (mu_c_strcasecmp (pb->token, "ALL") == 0)
    {
      node = parse_alloc (pb, sizeof *node);
      node->type = node_value;
      node->v.value.type = value_number;
      node->v.value.v.number = 1;

      parse_gettoken (pb, 0);
      return node;
    }
  else if (mu_c_strcasecmp (pb->token, "NOT") == 0)
    {
      struct search_node *np;

      if (parse_gettoken (pb, 1) == 0)
	 return NULL;

      np = parse_search_key (pb);
      if (!np)
	 return NULL;

      node = parse_alloc (pb, sizeof *node);
      node->type = node_not;
      node->v.arg[0] = np;

      return node;
    }
  else if (mu_c_strcasecmp (pb->token, "OR") == 0)
    {
      struct search_node *leftarg, *rightarg;

      if (parse_gettoken (pb, 1) == 0)
	 return NULL;

      if ((leftarg = parse_search_key (pb)) == NULL)
	return NULL;
      if (!pb->token)
	{
	  pb->err_mesg = "Too few args";
	  return NULL;
	}
      if ((rightarg = parse_search_key (pb)) == NULL)
	 return NULL;

      node = parse_alloc (pb, sizeof *node);
      node->type = node_or;
      node->v.arg[0] = leftarg;
      node->v.arg[1] = rightarg;

      return node;
    }
  else
    return parse_equiv_key (pb);
}

struct search_node *
parse_equiv_key (struct parsebuf *pb)
{
  struct search_node *node;
  struct cond_equiv *condp;
  int save_arg;
  imap4d_tokbuf_t save_tok;

  for (condp = equiv_list; condp->name && mu_c_strcasecmp (condp->name, pb->token);
	condp++)
    ;

  if (!condp->name)
    return parse_simple_key (pb);

  save_arg = pb->arg;
  save_tok = pb->tok;
  pb->tok = imap4d_tokbuf_from_string (condp->equiv);
  pb->arg = 0;

  parse_gettoken (pb, 0);

  node = parse_search_key_list (pb);
  if (!node)
    {
      /* shouldn't happen? */
      mu_diag_output (MU_DIAG_CRIT, _("%s:%d: INTERNAL ERROR (please report)"),
		       __FILE__, __LINE__);
      abort ();
    }
  imap4d_tokbuf_destroy (&pb->tok);

  pb->arg = save_arg;
  pb->tok = save_tok;
  parse_gettoken (pb, 0);
  return node;
}

struct search_node *
parse_simple_key (struct parsebuf *pb)
{
  struct search_node *node;
  struct cond *condp;
  time_t time;

  for (condp = condlist; condp->name && mu_c_strcasecmp (condp->name, pb->token);
       condp++)
    ;

  if (!condp->name)
    {
      mu_msgset_t msgset = parse_msgset_create (pb, mbox, MU_MSGSET_NUM);
      int rc = mu_msgset_parse_imap (msgset,
				     MU_MSGSET_NUM,
				     pb->token, NULL);

      if (rc == 0)
	{
	  struct search_node *np = parse_alloc (pb, sizeof *np);
	  np->type = node_value;
	  np->v.value.type = value_msgset;
	  np->v.value.v.msgset = msgset;

	  node = parse_alloc (pb, sizeof *node);
	  node->type = node_call;
	  node->v.key.keyword = "msgset";
	  node->v.key.narg = 1;
	  node->v.key.arg[0] = np;
	  node->v.key.fun = cond_msgset;

	  parse_gettoken (pb, 0);

	  return node;
	}
      else if (rc == MU_ERR_PARSE)
	{
	  pb->err_mesg = "Unknown search criterion";
	  return NULL;
	}
      else
	{
	  /* MU_ERR_NOENT or similar */
	  node = parse_alloc (pb, sizeof *node);
	  node->type = node_false;
	  parse_gettoken (pb, 0);
	  return node;
	}
    }

  node = parse_alloc (pb, sizeof *node);
  node->type = node_call;
  node->v.key.keyword = condp->name;
  node->v.key.fun = condp->inst;
  node->v.key.narg = 0;

  parse_gettoken (pb, 0);
  if (condp->argtypes)
    {
      char *t = condp->argtypes;
      char *s;
      mu_off_t number;
      struct search_node *arg;

      for (; *t; t++, parse_gettoken (pb, 0))
	{
	  if (node->v.key.narg >= MAX_NODE_ARGS)
	    {
	      pb->err_mesg = "INTERNAL ERROR: too many arguments";
	      return NULL;
	    }

	  if (!pb->token)
	    {
	      pb->err_mesg = "Not enough arguments for criterion";
	      return NULL;
	    }

	  arg = parse_alloc (pb, sizeof *arg);
	  arg->type = node_value;
	  switch (*t)
	    {
	    case 's': /* string */
	      arg->v.value.type = value_string;
	      arg->v.value.v.string = parse_strdup (pb, pb->token);
	      break;

	    case 'n': /* number */
	      number = strtoul (pb->token, &s, 10);
	      if (*s)
		{
		  pb->err_mesg = "Invalid number";
		  return NULL;
		}
	      arg->v.value.type = value_number;
	      arg->v.value.v.number = number;
	      break;

	    case 'd': /* date */
	      if (util_parse_internal_date (pb->token, &time,
					    datetime_date_only))
		{
		  pb->err_mesg = "Bad date format";
		  return NULL;
		}
	      arg->v.value.type = value_date;
	      arg->v.value.v.date = time;
	      break;

	    case 'u': /* UID message set */
	      arg->v.value.v.msgset = parse_msgset_create (pb, NULL,
							   MU_MSGSET_NUM);
	      arg->v.value.type = value_msgset;
	      if (mu_msgset_parse_imap (arg->v.value.v.msgset, MU_MSGSET_UID,
					pb->token, NULL))
		{
		  pb->err_mesg = "Bogus number set";
		  return NULL;
		}
	      break;

	    default:
	      mu_diag_output (MU_DIAG_CRIT,
			      _("%s:%d: INTERNAL ERROR (please report)"),
			      __FILE__, __LINE__);
	      abort (); /* should never happen */
	    }
	  node->v.key.arg[node->v.key.narg++] = arg;
	}
    }
  return node;
}

/* Executes a query from parsebuf */
void
evaluate_node (struct search_node *node, struct parsebuf *pb,
	       struct value *val)
{
  int i;
  struct value argval[MAX_NODE_ARGS];

  switch (node->type)
    {
    case node_call:
      for (i = 0; i < node->v.key.narg; i++)
	{
	  /* FIXME: if (i >= MAX_NODE_ARGS) */
	  evaluate_node (node->v.key.arg[i], pb, &argval[i]);
	  /* FIXME: node types? */
	}

      node->v.key.fun (pb, node, argval, val);
      break;

    case node_and:
      val->type = value_number;
      evaluate_node (node->v.arg[0], pb, &argval[0]);
      if (argval[0].v.number == 0)
	val->v.number = 0;
      else
	{
	  evaluate_node (node->v.arg[1], pb, &argval[1]);
	  val->v.number = argval[1].v.number;
	}
      break;

    case node_or:
      val->type = value_number;
      evaluate_node (node->v.arg[0], pb, &argval[0]);
      if (argval[0].v.number)
	val->v.number = 1;
      else
	{
	  evaluate_node (node->v.arg[1], pb, &argval[1]);
	  val->v.number = argval[1].v.number;
	}
      break;

    case node_not:
      evaluate_node (node->v.arg[0], pb, &argval[0]);
      val->type = value_number;
      val->v.number = !argval[0].v.number;
      break;

    case node_value:
      *val = node->v.value;
      break;

    case node_false:
      val->type = value_number;
      val->v.number = 0;
      break;
    }
}

int
search_run (struct parsebuf *pb)
{
  struct value value;

  value.type = value_undefined;
  evaluate_node (pb->tree, pb, &value);
  if (value.type != value_number)
    {
      mu_diag_output (MU_DIAG_CRIT, _("%s:%d: INTERNAL ERROR (please report)"),
	     __FILE__, __LINE__);
      abort (); /* should never happen */
    }
  return value.v.number != 0;
}

/* Helper functions for evaluating conditions */

/* Scan the header of a message for the occurrence of field named `name'.
   Return true if any of the occurrences contained substring `value' */
static int
_scan_header (struct parsebuf *pb, char *name, char *value)
{
  char *hval;
  mu_header_t header = NULL;
  int i, rc;
  int result = 0;
  char *needle;
  
  mu_message_get_header (pb->msg, &header);

  unistr_downcase (value, &needle);

  for (i = 1;
       result == 0
	 && (rc = mu_header_aget_value_unfold_n (header, name, i, &hval)) == 0;
       i++)
    {
      if (pb->charset)
	{
	  char *tmp;
	  rc = mu_rfc2047_decode (pb->charset, hval, &tmp);
	  if (rc)
	    {
	      mu_diag_funcall (MU_DIAG_ERR, "mu_rfc2047_decode", hval, rc);
	      free (hval);
	      continue;
	    }
	  free (hval);
	  hval = tmp;
	}
      result = unistr_is_substring_dn (hval, needle);
      free (hval);
    }
  if (!(rc == 0 || rc == MU_ERR_NOENT))
    mu_diag_funcall (MU_DIAG_ERR, "mu_header_aget_value_unfold_n", NULL, rc);
  free (needle);
  
  return result;
}

/* Get the value of Date: field and convert it to timestamp */
static int
_header_date (struct parsebuf *pb, time_t *timep)
{
  const char *hval;
  mu_header_t header = NULL;

  mu_message_get_header (pb->msg, &header);
  if (mu_header_sget_value (header, "Date", &hval) == 0
      && util_parse_822_date (hval, timep, datetime_date_only))
    return 0;
  return 1;
}

/* Scan all header fields for the occurrence of a substring `text' */
static int
_scan_header_all (struct parsebuf *pb, char *text)
{
  mu_header_t header = NULL;
  size_t fcount = 0;
  int i, rc;
  int result;
  char *needle;
  
  mu_message_get_header (pb->msg, &header);
  mu_header_get_field_count (header, &fcount);
  unistr_downcase (text, &needle);
  result = 0;
  for (i = 1; result == 0 && i < fcount; i++)
    {
      char *hval;

      rc = mu_header_aget_field_value_unfold (header, i, &hval);
      if (rc)
	{
	  mu_diag_funcall (MU_DIAG_ERR, "mu_header_aget_field_value_unfold",
			   NULL, rc);
	  continue;
	}

      if (pb->charset)
	{
	  char *tmp;
	  rc = mu_rfc2047_decode (pb->charset, hval, &tmp);
	  if (rc)
	    {
	      mu_diag_funcall (MU_DIAG_ERR, "mu_rfc2047_decode", hval, rc);
	      free (hval);
	      continue;
	    }

	  free (hval);
	  hval = tmp;
	}
      result = unistr_is_substring_dn (hval, needle);
      free (hval);
    }
  free (needle);
  return result;
}

static int
_match_text (struct parsebuf *pb, mu_message_t msg, mu_content_type_t ct,
	     char const *encoding,
	     char *text)
{
  mu_body_t body;
  mu_stream_t str;
  int rc;
  int result;
  char *buffer = NULL;
  size_t bufsize = 0;
  size_t n;
  char *needle;
  
  mu_message_get_body (msg, &body);
  mu_body_get_streamref (body, &str);

  if (encoding)
    {
      mu_stream_t flt;
      rc = mu_filter_create (&flt, str, encoding,
			     MU_FILTER_DECODE,
			     MU_STREAM_READ);
      mu_stream_unref (str);
      if (rc)
	{
	  mu_error (_("cannot handle encoding %s: %s"),
		    encoding, mu_strerror (rc));
	  return 0;
	}
      str = flt;
    }

  if (pb->charset)
    {
      struct mu_mime_param *param;
      if (mu_assoc_lookup (ct->param, "charset", &param) == 0
	  && mu_c_strcasecmp (param->value, pb->charset))
	{
	  char const *argv[] = { "iconv", NULL, NULL, NULL };
	  mu_stream_t flt;

	  argv[1] = param->value;
	  argv[2] = pb->charset;
	  rc = mu_filter_chain_create (&flt, str,
				       MU_FILTER_ENCODE,
				       MU_STREAM_READ,
				       MU_ARRAY_SIZE (argv) - 1,
				       (char**) argv);
	  mu_stream_unref (str);
	  if (rc)
	    {
	      mu_error (_("cannot convert from charset %s to %s"),
			param->value, pb->charset);
	      return 0;
	    }
	  str = flt;
	}
    }

  unistr_downcase (text, &needle);
  result = 0;
  while ((rc = mu_stream_getline (str, &buffer, &bufsize, &n)) == 0
	 && n > 0)
    {
      result = unistr_is_substring_dn (buffer, needle);
      if (result)
	break;
    }
  free (needle);
  mu_stream_destroy (&str);
  if (rc)
    mu_diag_funcall (MU_DIAG_ERR, "mu_stream_getline", NULL, rc);
  return result;
}

static int
_match_multipart (struct parsebuf *pb, mu_message_t msg, char *text)
{
  mu_header_t hdr;
  char *encoding;
  int ismp;
  int result = 0;
  mu_content_type_t ct;
  char *buf;
  int rc;

  if (mu_message_is_multipart (msg, &ismp))
    return 0;
  if (mu_message_get_header (msg, &hdr))
    return 0;

  if (mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TYPE, &buf))
    {
      buf = strdup ("text/plain");
      if (!buf)
	return 0;
    }
  rc = mu_content_type_parse (buf, NULL, &ct);
  free (buf);
  if (rc)
    return 0;

  if (mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING,
				   &encoding))
    encoding = NULL;

  if (ismp)
    {
      size_t i, nparts;

      rc = mu_message_get_num_parts (msg, &nparts);
      if (rc)
	{
	  mu_diag_funcall (MU_DIAG_ERR, "mu_message_get_num_parts", NULL, rc);
	}
      else
	{
	  for (i = 1; i <= nparts; i++)
	    {
	      mu_message_t submsg = NULL;
	      
	      if (mu_message_get_part (msg, i, &submsg) == 0)
		{
		  result = _match_multipart (pb, submsg, text);
		  if (result)
		    break;
		}
	    }
	}
    }
  else if (mu_c_strcasecmp (ct->type, "message") == 0
	   && mu_c_strcasecmp (ct->subtype, "rfc822") == 0)
    {
      mu_message_t submsg = NULL;

      if (mu_message_unencapsulate (msg, &submsg, NULL) == 0)
	{
	  result = _match_multipart (pb, submsg, text);
	}
    }
  else if (mu_c_strcasecmp (ct->type, "text") == 0)
    result = _match_text (pb, msg, ct, encoding, text);

  free (encoding);
  mu_content_type_destroy (&ct);

  return result;
}

/* Scan body of the message for the occurrence of a substring */
static int
_scan_body (struct parsebuf *pb, char *text)
{
  return _match_multipart (pb, pb->msg, text);
}

/* Basic instructions */

static void
cond_msgset (struct parsebuf *pb, struct search_node *node, struct value *arg,
	     struct value *retval)
{
  int rc = mu_msgset_locate (arg[0].v.msgset, pb->msgno, NULL);
  retval->type = value_number;
  retval->v.number = rc == 0;
}

static void
cond_bcc (struct parsebuf *pb, struct search_node *node, struct value *arg,
	  struct value *retval)
{
  retval->type = value_number;
  retval->v.number = _scan_header (pb, MU_HEADER_BCC, arg[0].v.string);
}

static void
cond_before (struct parsebuf *pb, struct search_node *node, struct value *arg,
	     struct value *retval)
{
  time_t t = arg[0].v.date;
  time_t mesg_time;
  const char *date;
  mu_envelope_t env;

  mu_message_get_envelope (pb->msg, &env);
  retval->type = value_number;
  if (mu_envelope_sget_date (env, &date))
    retval->v.number = 0;
  else
    {
      util_parse_ctime_date (date, &mesg_time, datetime_date_only);
      retval->v.number = mesg_time < t;
    }
}

static void
cond_body (struct parsebuf *pb, struct search_node *node, struct value *arg,
	   struct value *retval)
{
  retval->type = value_number;
  retval->v.number = _scan_body (pb, arg[0].v.string);
}

static void
cond_cc (struct parsebuf *pb, struct search_node *node, struct value *arg,
	 struct value *retval)
{
  retval->type = value_number;
  retval->v.number = _scan_header (pb, MU_HEADER_CC, arg[0].v.string);
}

static void
cond_from (struct parsebuf *pb, struct search_node *node, struct value *arg,
	   struct value *retval)
{
  char *s = arg[0].v.string;
  mu_envelope_t env;
  const char *from;
  int rc = 0;

  mu_message_get_envelope (pb->msg, &env);
  if (mu_envelope_sget_sender (env, &from) == 0)
    rc = mu_c_strcasestr (from, s) != NULL;

  retval->type = value_number;
  retval->v.number = rc || _scan_header (pb, MU_HEADER_FROM, s);
}

static void
cond_header (struct parsebuf *pb, struct search_node *node, struct value *arg,
	     struct value *retval)
{
  char *name = arg[0].v.string;
  char *value = arg[1].v.string;

  retval->type = value_number;
  retval->v.number = _scan_header (pb, name, value);
}

static void
cond_keyword (struct parsebuf *pb, struct search_node *node, struct value *arg,
	     struct value *retval)
{
  char *s = arg[0].v.string;
  mu_attribute_t attr = NULL;

  mu_message_get_attribute (pb->msg, &attr);
  retval->type = value_number;
  retval->v.number = util_attribute_matches_flag (attr, s);
}

static void
cond_larger (struct parsebuf *pb, struct search_node *node, struct value *arg,
	     struct value *retval)
{
  size_t size = 0;

  mu_message_size (pb->msg, &size);
  retval->type = value_number;
  retval->v.number = size > arg[0].v.number;
}

static void
cond_on (struct parsebuf *pb, struct search_node *node, struct value *arg,
	 struct value *retval)
{
  time_t t = arg[0].v.date;
  time_t mesg_time;
  const char *date;
  mu_envelope_t env;

  mu_message_get_envelope (pb->msg, &env);
  retval->type = value_number;
  if (mu_envelope_sget_date (env, &date))
    retval->v.number = 0;
  else
    {
      util_parse_ctime_date (date, &mesg_time, datetime_date_only);
      retval->v.number = t <= mesg_time && mesg_time <= t + 86400;
    }
}

static void
cond_sentbefore (struct parsebuf *pb, struct search_node *node,
		 struct value *arg,
		 struct value *retval)
{
  time_t t = arg[0].v.date;
  time_t mesg_time = 0;

  _header_date (pb, &mesg_time);
  retval->type = value_number;
  retval->v.number = mesg_time < t;
}

static void
cond_senton (struct parsebuf *pb, struct search_node *node, struct value *arg,
	     struct value *retval)
{
  time_t t = arg[0].v.date;
  time_t mesg_time = 0;

  _header_date (pb, &mesg_time);
  retval->type = value_number;
  retval->v.number = t <= mesg_time && mesg_time <= t + 86400;
}

static void
cond_sentsince (struct parsebuf *pb, struct search_node *node,
		struct value *arg,
		struct value *retval)
{
  time_t t = arg[0].v.date;
  time_t mesg_time = 0;

  _header_date (pb, &mesg_time);
  retval->type = value_number;
  retval->v.number = mesg_time >= t;
}

static void
cond_since (struct parsebuf *pb, struct search_node *node, struct value *arg,
	    struct value *retval)
{
  time_t t = arg[0].v.date;
  time_t mesg_time;
  const char *date;
  mu_envelope_t env;

  mu_message_get_envelope (pb->msg, &env);
  retval->type = value_number;
  if (mu_envelope_sget_date (env, &date))
    retval->v.number = 0;
  else
    {
      util_parse_ctime_date (date, &mesg_time, datetime_date_only);
      retval->v.number = mesg_time >= t;
    }
}

static void
cond_smaller (struct parsebuf *pb, struct search_node *node, struct value *arg,
	      struct value *retval)
{
  size_t size = 0;

  mu_message_size (pb->msg, &size);
  retval->type = value_number;
  retval->v.number = size < arg[0].v.number;
}

static void
cond_subject (struct parsebuf *pb, struct search_node *node, struct value *arg,
	      struct value *retval)
{
  retval->type = value_number;
  retval->v.number = _scan_header (pb, MU_HEADER_SUBJECT, arg[0].v.string);
}

static void
cond_text (struct parsebuf *pb, struct search_node *node, struct value *arg,
	   struct value *retval)
{
  char *s = arg[0].v.string;
  retval->type = value_number;
  retval->v.number = _scan_header_all (pb, s) || _scan_body (pb, s);
}

static void
cond_to (struct parsebuf *pb, struct search_node *node, struct value *arg,
	 struct value *retval)
{
  retval->type = value_number;
  retval->v.number = _scan_header (pb, MU_HEADER_TO, arg[0].v.string);
}

static void
cond_uid (struct parsebuf *pb, struct search_node *node, struct value *arg,
	  struct value *retval)
{
  int rc;
  size_t uid = 0;

  mu_message_get_uid (pb->msg, &uid);
  rc = mu_msgset_locate (arg[0].v.msgset, pb->msgno, NULL);
  retval->type = value_number;
  retval->v.number = rc == 0;
}

/* Return 1 if the CHARSET is available.
   This function assumes that charset is available if it is possible
   to create a filter for encoding ASCII data into it.
 */
static int
available_charset (const char *charset)
{
  int rc;
  mu_stream_t flt;
  mu_stream_t null;
  char const *argv[] = { "iconv", "US-ASCII", NULL, NULL };

  rc = mu_nullstream_create (&null, MU_STREAM_READ);
  if (rc)
    return 0;
  argv[2] = charset;
  rc = mu_filter_chain_create (&flt, null, MU_FILTER_ENCODE, MU_STREAM_READ,
			       MU_ARRAY_SIZE (argv) - 1, (char**) argv);
  mu_stream_unref (null);
  if (rc)
    return 0;
  mu_stream_destroy (&flt);
  return 1;
}
