nvp.c 11.1 KB
Newer Older
1 2 3 4
/*
  mairix - message index builder and finder for maildir folders.

 **********************************************************************
5
 * Copyright (C) Richard P. Curnow  2006,2007
6 7 8 9 10 11 12 13 14 15 16 17
 *
 * 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.,
18
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 20 21 22
 *
 **********************************************************************
 */

23 24 25 26
#ifdef VERBOSE_TEST
#define TEST 1
#endif

27 28
/* Parse name/value pairs from mail headers into a lookup table. */
#include <stdio.h>
29
#include <ctype.h>
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
#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;
}
/*}}}*/
62
static void append_name(struct nvp *nvp, char **name)/*{{{*/
63 64 65 66
{
  struct nvp_entry *ne;
  ne = new(struct nvp_entry);
  ne->type = NVP_NAME;
67 68
  ne->lhs = *name;
  *name = NULL;
69 70 71
  append(nvp, ne);
}
/*}}}*/
72
static void append_majorminor(struct nvp *nvp, char **major, char **minor)/*{{{*/
73 74 75 76
{
  struct nvp_entry *ne;
  ne = new(struct nvp_entry);
  ne->type = NVP_MAJORMINOR;
77 78 79
  ne->lhs = *major;
  ne->rhs = *minor;
  *major = *minor = NULL;
80 81 82 83
  append(nvp, ne);

}
/*}}}*/
84
static void append_namevalue(struct nvp *nvp, char **name, char **value)/*{{{*/
85 86 87 88
{
  struct nvp_entry *ne;
  ne = new(struct nvp_entry);
  ne->type = NVP_NAMEVALUE;
89 90 91
  ne->lhs = *name;
  ne->rhs = *value;
  *name = *value = NULL;
92 93 94
  append(nvp, ne);
}
/*}}}*/
95
static void combine_namevalue(struct nvp *nvp, char **name, char **value)/*{{{*/
96 97 98 99
{
  struct nvp_entry *n;
  for (n=nvp->first; n; n=n->next) {
    if (n->type == NVP_NAMEVALUE) {
100
      if (!strcmp(n->lhs, *name)) {
101
        char *new_rhs;
102
        new_rhs = new_array(char, strlen(n->rhs) + strlen(*value) + 1);
103
        strcpy(new_rhs, n->rhs);
104
        strcat(new_rhs, *value);
105 106 107 108 109 110 111 112 113 114
        free(n->rhs);
        n->rhs = new_rhs;
        return;
      }
    }
  }
  /* No match : it's the first one */
  append_namevalue(nvp, name, value);
}
/*}}}*/
Peter Jeremy's avatar
Peter Jeremy committed
115 116 117 118 119 120 121 122 123 124 125
static int hex_to_val(int ch)/*{{{*/
{
    if (isdigit(ch))
	return (ch - '0');
    if (ch >= 'a' && ch <= 'f')
	return (10 + ch - 'a');
    if (ch >= 'A' && ch <= 'F')
	return (10 + ch - 'A');
    return (-1);
}
/*}}}*/
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
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);
}
/*}}}*/
146
struct nvp *make_nvp(struct msg_src *src, char *s, const char *pfx)/*{{{*/
147 148 149
{
  int current_state;
  unsigned int tok;
150
  char *q, *tempsrc, *tempdst;
151
  unsigned char qq;
152 153 154 155
  char *name = NULL;
  char *minor = NULL;
  char *value = NULL;
  char *copy_start;
156
  enum nvp_action last_action, current_action;
157
  enum nvp_copier last_copier;
158
  struct nvp *result;
159
  size_t pfxlen;
160

161 162 163 164 165
  pfxlen = strlen(pfx);
  if (strncasecmp(pfx, s, pfxlen))
    return NULL;
  s += pfxlen;

166 167 168 169 170 171 172
  result = new(struct nvp);
  result->first = result->last = NULL;

  current_state = nvp_in;

  q = s;
  last_action = GOT_NOTHING;
173
  last_copier = COPY_NOWHERE;
174
  do {
175 176 177
    qq = *(unsigned char *) q;
    if (qq) {
      tok = nvp_char2tok[qq];
178 179 180 181
    } else {
      tok = nvp_EOS;
    }
    current_state = nvp_next_state(current_state, tok);
182 183 184 185 186
#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
187 188

    if (current_state < 0) {
189 190 191
#ifdef TEST
      fprintf(stderr, "'%s' could not be parsed\n", s);
#else
192
      fprintf(stderr, "Header '%s%s' in %s could not be parsed\n",
Richard P. Curnow's avatar
Richard P. Curnow committed
193
          pfx, s, format_msg_src(src));
194
#endif
195 196 197
      release_nvp(result);
      return NULL;
    }
198

199 200 201 202 203 204 205 206 207
    if (nvp_copier[current_state] != last_copier) {
      if (last_copier != COPY_NOWHERE) {
        char *newstring = Malloc(q - copy_start + 1);
	memcpy(newstring, copy_start, q - copy_start);
	newstring[q - copy_start] = 0;
        switch (last_copier) {
          case COPY_TO_NAME:
            if (name) free(name);
            name = newstring;
208
#ifdef VERBOSE_TEST
209
            fprintf(stderr, "  COPY_TO_NAME \"%s\"\n", name);
210
#endif
211 212 213 214
            break;
          case COPY_TO_MINOR:
            if (minor) free(minor);
            minor = newstring;
215
#ifdef VERBOSE_TEST
216
            fprintf(stderr, "  COPY_TO_MINOR \"%s\"\n", minor);
217
#endif
218 219 220 221
            break;
          case COPY_TO_VALUE:
            if (value) free(value);
            value = newstring;
222
#ifdef VERBOSE_TEST
223
            fprintf(stderr, "  COPY_TO_VALUE \"%s\"\n", value);
224
#endif
225 226 227 228 229 230 231 232
            break;
          case COPY_NOWHERE:
            /* NOTREACHED */
            break;
        }
      }
      last_copier = nvp_copier[current_state];
      copy_start = q;
233 234 235 236 237
    }

    current_action = nvp_action[current_state];
    switch (current_action) {
      case GOT_NAME:
238
      case GOT_NAME_TRAILING_SPACE:
239 240
      case GOT_MAJORMINOR:
      case GOT_NAMEVALUE:
241
      case GOT_NAMEVALUE_CONT:
Peter Jeremy's avatar
Peter Jeremy committed
242
      case GOT_NAMEVALUE_CSET:
243
      case GOT_NAMEVALUE_CCONT:
244 245 246
#ifdef VERBOSE_TEST
        fprintf(stderr, "   Setting last action to %d\n", current_action);
#endif
247 248 249
        last_action = current_action;
        break;
      case GOT_TERMINATOR:
250 251 252
#ifdef VERBOSE_TEST
        fprintf(stderr, "   Hit terminator; last_action=%d\n", last_action);
#endif
253 254
        switch (last_action) {
          case GOT_NAME:
255
            append_name(result, &name);
256
            break;
257
          case GOT_NAME_TRAILING_SPACE:
258 259 260 261
            tempdst = name + strlen(name);
            while (isspace(*--tempdst)) {}
            *++tempdst = 0;
            append_name(result, &name);
262
            break;
263
          case GOT_MAJORMINOR:
264
            append_majorminor(result, &name, &minor);
265 266
            break;
          case GOT_NAMEVALUE:
267
            append_namevalue(result, &name, &value);
268
            break;
Peter Jeremy's avatar
Peter Jeremy committed
269
          case GOT_NAMEVALUE_CSET:
270
          case GOT_NAMEVALUE_CCONT:
271 272 273 274
	    for(tempsrc = tempdst = value; *tempsrc; tempsrc++) {
		if (*tempsrc == '%') {
		    int val = hex_to_val(*++tempsrc) << 4;
		    val |= hex_to_val(*++tempsrc);
Peter Jeremy's avatar
Peter Jeremy committed
275 276 277 278 279 280 281 282
		    if (val < 0) {
#ifdef TEST
			fprintf(stderr, "'%s' could not be parsed (%%)\n", s);
#else
			fprintf(stderr, "Header '%s%s' in %s could not be parsed\n",
				pfx, s, format_msg_src(src));
#endif
			release_nvp(result);
283 284
			result = NULL;
			goto out;
Peter Jeremy's avatar
Peter Jeremy committed
285
		    }
286
		    *tempdst++ = val;
Peter Jeremy's avatar
Peter Jeremy committed
287
		} else
288
		    *tempdst++ = *tempsrc;
Peter Jeremy's avatar
Peter Jeremy committed
289
	    }
290
            *tempdst = 0;
291
            if (current_action == GOT_NAMEVALUE_CSET)
292
              append_namevalue(result, &name, &value);
293
            else
294
              combine_namevalue(result, &name, &value);
Peter Jeremy's avatar
Peter Jeremy committed
295
            break;
296
          case GOT_NAMEVALUE_CONT:
297
            combine_namevalue(result, &name, &value);
298
            break;
299 300 301 302
          default:
            break;
        }
        break;
303 304
      case GOT_NOTHING:
        break;
305 306 307 308 309
    }

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

310 311 312 313 314
out:
  /* Not all productions consume these values */
  if (name) free(name);
  if (value) free(value);
  if (minor) free(minor);
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
  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;
    }
333
    free(ne);
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
  }
  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;
368
  fprintf(out, "----\n");
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
  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
433 434 435

static void do_test(char *s)
{
436
  struct nvp *n;
437
  n = make_nvp(NULL, s, "");
438 439 440 441
  if (n) {
    nvp_dump(n, stderr);
    free_nvp(n);
  }
442 443 444 445 446
}


int main (int argc, char **argv) {
  struct nvp *n;
Peter Jeremy's avatar
Peter Jeremy committed
447 448 449 450 451 452 453

  if (argc > 1) {
      while (*++argv)
	  do_test(*argv);
      return 0;
  }

454 455 456 457 458 459 460 461
#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
Peter Jeremy's avatar
Peter Jeremy committed
462
  do_test("attachment; filename*=utf-8''Section%204-1%20%E2%80%93%20Response%20Matrix%20PartIIA%2Edoc");
463
#if 0
Peter Jeremy's avatar
Peter Jeremy committed
464
  do_test("application/vnd.ms-excel;       name=\"thequiz.xls\"");
465 466 467
  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
468 469 470
  return 0;
}
#endif