/*
  mairix - message index builder and finder for maildir folders.

 **********************************************************************
 * Copyright (C) Richard P. Curnow  2006,2007
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 *
 **********************************************************************
 */

#ifdef VERBOSE_TEST
#define TEST 1
#endif

/* Parse name/value pairs from mail headers into a lookup table. */
#include <stdio.h>
#include <ctype.h>
#include "mairix.h"
#include "nvptypes.h"
#include "nvpscan.h"
#include "nvp.h"

enum nvp_type {/*{{{*/
  NVP_NAME,
  NVP_MAJORMINOR,
  NVP_NAMEVALUE
};
/*}}}*/
struct nvp_entry {/*{{{*/
  struct nvp_entry *next;
  struct nvp_entry *prev;
  enum nvp_type type;
  char *lhs;
  char *rhs;
};
/*}}}*/
struct nvp {/*{{{*/
  struct nvp_entry *first, *last;
};
/*}}}*/
static void append(struct nvp *nvp, struct nvp_entry *ne)/*{{{*/
{
  ne->next = NULL;
  ne->prev = nvp->last;
  if (nvp->last) nvp->last->next = ne;
  else nvp->first = ne;
  nvp->last = ne;
}
/*}}}*/
static void append_name(struct nvp *nvp, char *name)/*{{{*/
{
  struct nvp_entry *ne;
  ne = new(struct nvp_entry);
  ne->type = NVP_NAME;
  ne->lhs = new_string(name);
  append(nvp, ne);
}
/*}}}*/
static void append_majorminor(struct nvp *nvp, char *major, char *minor)/*{{{*/
{
  struct nvp_entry *ne;
  ne = new(struct nvp_entry);
  ne->type = NVP_MAJORMINOR;
  ne->lhs = new_string(major);
  ne->rhs = new_string(minor);
  append(nvp, ne);

}
/*}}}*/
static void append_namevalue(struct nvp *nvp, char *name, char *value)/*{{{*/
{
  struct nvp_entry *ne;
  ne = new(struct nvp_entry);
  ne->type = NVP_NAMEVALUE;
  ne->lhs = new_string(name);
  ne->rhs = new_string(value);
  append(nvp, ne);
}
/*}}}*/
static void combine_namevalue(struct nvp *nvp, char *name, char *value)/*{{{*/
{
  struct nvp_entry *n;
  for (n=nvp->first; n; n=n->next) {
    if (n->type == NVP_NAMEVALUE) {
      if (!strcmp(n->lhs, name)) {
        char *new_rhs;
        new_rhs = new_array(char, strlen(n->rhs) + strlen(value) + 1);
        strcpy(new_rhs, n->rhs);
        strcat(new_rhs, value);
        free(n->rhs);
        n->rhs = new_rhs;
        return;
      }
    }
  }
  /* No match : it's the first one */
  append_namevalue(nvp, name, value);
}
/*}}}*/
static void release_nvp(struct nvp *nvp)/*{{{*/
{
  struct nvp_entry *e, *ne;
  for (e=nvp->first; e; e=ne) {
    ne = e->next;
    switch (e->type) {
      case NVP_NAME:
        free(e->lhs);
        break;
      case NVP_MAJORMINOR:
      case NVP_NAMEVALUE:
        free(e->lhs);
        free(e->rhs);
        break;
    }
    free(e);
  }
  free(nvp);
}
/*}}}*/
struct nvp *make_nvp(struct msg_src *src, char *s)/*{{{*/
{
  int current_state;
  unsigned int tok;
  char *q;
  unsigned char qq;
  char name[256];
  char minor[256];
  char value[256];
  enum nvp_action last_action, current_action;
  struct nvp *result;

  char *nn, *mm, *vv;

  result = new(struct nvp);
  result->first = result->last = NULL;

  current_state = nvp_in;

  q = s;
  nn = name;
  mm = minor;
  vv = value;
  last_action = GOT_NOTHING;
  do {
    qq = *(unsigned char *) q;
    if (qq) {
      tok = nvp_char2tok[qq];
    } else {
      tok = nvp_EOS;
    }
    current_state = nvp_next_state(current_state, tok);
#ifdef VERBOSE_TEST
    fprintf(stderr, "Char %02x (%c) tok=%d new_current_state=%d\n",
        qq, ((qq>=32) && (qq<=126)) ? qq : '.',
        tok, current_state);
#endif

    if (current_state < 0) {
#ifdef TEST
      fprintf(stderr, "'%s' could not be parsed\n", s);
#else
      fprintf(stderr, "Header '%s' in %s could not be parsed\n",
          s, format_msg_src(src));
#endif
      release_nvp(result);
      return NULL;
    }

    switch (nvp_copier[current_state]) {
      case COPY_TO_NAME:
#ifdef VERBOSE_TEST
        fprintf(stderr, "  COPY_TO_NAME\n");
#endif
        *nn++ = *q;
        break;
      case COPY_TO_MINOR:
#ifdef VERBOSE_TEST
        fprintf(stderr, "  COPY_TO_MINOR\n");
#endif
        *mm++ = *q;
        break;
      case COPY_TO_VALUE:
#ifdef VERBOSE_TEST
        fprintf(stderr, "  COPY_TO_VALUE\n");
#endif
        *vv++ = *q;
        break;
      case COPY_NOWHERE:
        break;
    }

    current_action = nvp_action[current_state];
    switch (current_action) {
      case GOT_NAME:
      case GOT_NAME_TRAILING_SPACE:
      case GOT_MAJORMINOR:
      case GOT_NAMEVALUE:
      case GOT_NAMEVALUE_CONT:
#ifdef VERBOSE_TEST
        fprintf(stderr, "   Setting last action to %d\n", current_action);
#endif
        last_action = current_action;
        break;
      case GOT_TERMINATOR:
#ifdef VERBOSE_TEST
        fprintf(stderr, "   Hit terminator; last_action=%d\n", last_action);
#endif
        switch (last_action) {
          case GOT_NAME:
            *nn = 0;
            append_name(result, name);
            break;
          case GOT_NAME_TRAILING_SPACE:
            while (isspace(*--nn)) {}
            *++nn = 0;
            append_name(result, name);
            break;
          case GOT_MAJORMINOR:
            *nn = 0;
            *mm = 0;
            append_majorminor(result, name, minor);
            break;
          case GOT_NAMEVALUE:
            *nn = 0;
            *vv = 0;
            append_namevalue(result, name, value);
            break;
          case GOT_NAMEVALUE_CONT:
            *nn = 0;
            *vv = 0;
            combine_namevalue(result, name, value);
            break;
          default:
            break;
        }
        nn = name;
        mm = minor;
        vv = value;
        break;
      case GOT_NOTHING:
        break;
    }

    q++;
  } while (tok != nvp_EOS);

  return result;
}
/*}}}*/
void free_nvp(struct nvp *nvp)/*{{{*/
{
  struct nvp_entry *ne, *nne;
  for (ne = nvp->first; ne; ne=nne) {
    nne = ne->next;
    switch (ne->type) {
      case NVP_NAME:
        free(ne->lhs);
        break;
      case NVP_MAJORMINOR:
      case NVP_NAMEVALUE:
        free(ne->lhs);
        free(ne->rhs);
        break;
    }
  }
  free(nvp);
}
/*}}}*/
const char *nvp_lookup(struct nvp *nvp, const char *name)/*{{{*/
{
  struct nvp_entry *ne;
  for (ne = nvp->first; ne; ne=ne->next) {
    if (ne->type == NVP_NAMEVALUE) {
      if (!strcmp(ne->lhs, name)) {
        return ne->rhs;
      }
    }
  }
  return NULL;
}
/*}}}*/
const char *nvp_lookupcase(struct nvp *nvp, const char *name)/*{{{*/
{
  struct nvp_entry *ne;
  for (ne = nvp->first; ne; ne=ne->next) {
    if (ne->type == NVP_NAMEVALUE) {
      if (!strcasecmp(ne->lhs, name)) {
        return ne->rhs;
      }
    }
  }
  return NULL;
}
/*}}}*/

void nvp_dump(struct nvp *nvp, FILE *out)/*{{{*/
{
  struct nvp_entry *ne;
  fprintf(out, "----\n");
  for (ne = nvp->first; ne; ne=ne->next) {
    switch (ne->type) {
      case NVP_NAME:
        fprintf(out, "NAME: %s\n", ne->lhs);
        break;
      case NVP_MAJORMINOR:
        fprintf(out, "MAJORMINOR: %s/%s\n", ne->lhs, ne->rhs);
        break;
      case NVP_NAMEVALUE:
        fprintf(out, "NAMEVALUE: %s=%s\n", ne->lhs, ne->rhs);
        break;
    }
  }
}
/*}}}*/

/* In these cases, we only look at the first entry */
const char *nvp_major(struct nvp *nvp)/*{{{*/
{
  struct nvp_entry *ne;
  ne = nvp->first;
  if (ne) {
    if (ne->type == NVP_MAJORMINOR) {
      return ne->lhs;
    } else {
      return NULL;
    }
  } else {
    return NULL;
  }
}
/*}}}*/
const char *nvp_minor(struct nvp *nvp)/*{{{*/
{
  struct nvp_entry *ne;
  ne = nvp->first;
  if (ne) {
    if (ne->type == NVP_MAJORMINOR) {
      return ne->rhs;
    } else {
      return NULL;
    }
  } else {
    return NULL;
  }
}
/*}}}*/
const char *nvp_first(struct nvp *nvp)/*{{{*/
{
  struct nvp_entry *ne;
  ne = nvp->first;
  if (ne) {
    if (ne->type == NVP_NAME) {
      return ne->lhs;
    } else {
      return NULL;
    }
  } else {
    return NULL;
  }
}
/*}}}*/

#ifdef TEST

static void do_test(char *s)
{
  struct nvp *n;
  n = make_nvp(NULL, s);
  if (n) {
    nvp_dump(n, stderr);
    free_nvp(n);
  }
}


int main (int argc, char **argv) {
  struct nvp *n;
#if 0
  do_test("attachment; filename=\"foo.c\"; prot=ro");
  do_test("attachment; filename= \"foo bar.c\" ;prot=ro");
  do_test("attachment ; filename= \"foo bar.c\" ;prot= ro");
  do_test("attachment ; filename= \"foo bar.c\" ;prot= ro");
  do_test("attachment ; filename= \"foo ;  bar.c\" ;prot= ro");
  do_test("attachment ; x*0=\"hi \"; x*1=\"there\"");
#endif

  do_test("application/vnd.ms-excel;       name=\"thequiz.xls\"");
#if 0
  do_test("inline; filename*0=\"aaaa bbbb cccc dddd eeee ffff gggg hhhh iiii jjjj\t kkkkllll\"");
  do_test(" text/plain ; name= \"foo bar.c\" ;prot= ro/rw; read/write; read= foo bar");
#endif
  return 0;
}
#endif






syntax highlighted by Code2HTML, v. 0.9.1