1 /*
2   mairix - message index builder and finder for maildir folders.
3 
4  **********************************************************************
5  * Copyright (C) Richard P. Curnow  2006,2007
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of version 2 of the GNU General Public License as
9  * published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * 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 #ifdef VERBOSE_TEST
24 #define TEST 1
25 #endif
26 
27 /* Parse name/value pairs from mail headers into a lookup table. */
28 #include <stdio.h>
29 #include <ctype.h>
30 #include "mairix.h"
31 #include "nvptypes.h"
32 #include "nvpscan.h"
33 #include "nvp.h"
34 
35 enum nvp_type {/*{{{*/
36   NVP_NAME,
37   NVP_MAJORMINOR,
38   NVP_NAMEVALUE
39 };
40 /*}}}*/
41 struct nvp_entry {/*{{{*/
42   struct nvp_entry *next;
43   struct nvp_entry *prev;
44   enum nvp_type type;
45   char *lhs;
46   char *rhs;
47 };
48 /*}}}*/
49 struct nvp {/*{{{*/
50   struct nvp_entry *first, *last;
51 };
52 /*}}}*/
append(struct nvp * nvp,struct nvp_entry * ne)53 static void append(struct nvp *nvp, struct nvp_entry *ne)/*{{{*/
54 {
55   if (!ne->rhs) {
56     ne->rhs = Malloc(1);
57     ne->rhs[0] = 0;
58   }
59   if (!ne->lhs) {
60     ne->lhs = Malloc(1);
61     ne->lhs[0] = 0;
62   }
63   ne->next = NULL;
64   ne->prev = nvp->last;
65   if (nvp->last) nvp->last->next = ne;
66   else nvp->first = ne;
67   nvp->last = ne;
68 }
69 /*}}}*/
append_name(struct nvp * nvp,char ** name)70 static void append_name(struct nvp *nvp, char **name)/*{{{*/
71 {
72   struct nvp_entry *ne;
73   ne = new(struct nvp_entry);
74   ne->type = NVP_NAME;
75   ne->lhs = *name;
76   *name = NULL;
77   append(nvp, ne);
78 }
79 /*}}}*/
append_majorminor(struct nvp * nvp,char ** major,char ** minor)80 static void append_majorminor(struct nvp *nvp, char **major, char **minor)/*{{{*/
81 {
82   struct nvp_entry *ne;
83   ne = new(struct nvp_entry);
84   ne->type = NVP_MAJORMINOR;
85   ne->lhs = *major;
86   ne->rhs = *minor;
87   *major = *minor = NULL;
88   append(nvp, ne);
89 
90 }
91 /*}}}*/
append_namevalue(struct nvp * nvp,char ** name,char ** value)92 static void append_namevalue(struct nvp *nvp, char **name, char **value)/*{{{*/
93 {
94   struct nvp_entry *ne;
95   ne = new(struct nvp_entry);
96   ne->type = NVP_NAMEVALUE;
97   ne->lhs = *name;
98   ne->rhs = *value;
99   *name = *value = NULL;
100   append(nvp, ne);
101 }
102 /*}}}*/
combine_namevalue(struct nvp * nvp,char ** name,char ** value)103 static void combine_namevalue(struct nvp *nvp, char **name, char **value)/*{{{*/
104 {
105   struct nvp_entry *n;
106   for (n=nvp->first; n; n=n->next) {
107     if (n->type == NVP_NAMEVALUE) {
108       if (!strcmp(n->lhs, *name)) {
109         char *new_rhs;
110         new_rhs = new_array(char, strlen(n->rhs) + strlen(*value) + 1);
111         strcpy(new_rhs, n->rhs);
112         strcat(new_rhs, *value);
113         free(n->rhs);
114         n->rhs = new_rhs;
115         return;
116       }
117     }
118   }
119   /* No match : it's the first one */
120   append_namevalue(nvp, name, value);
121 }
122 /*}}}*/
hex_to_val(int ch)123 static int hex_to_val(int ch)/*{{{*/
124 {
125     if (isdigit(ch))
126 	return (ch - '0');
127     if (ch >= 'a' && ch <= 'f')
128 	return (10 + ch - 'a');
129     if (ch >= 'A' && ch <= 'F')
130 	return (10 + ch - 'A');
131     return (-1);
132 }
133 /*}}}*/
release_nvp(struct nvp * nvp)134 static void release_nvp(struct nvp *nvp)/*{{{*/
135 {
136   struct nvp_entry *e, *ne;
137   for (e=nvp->first; e; e=ne) {
138     ne = e->next;
139     switch (e->type) {
140       case NVP_NAME:
141         free(e->lhs);
142         break;
143       case NVP_MAJORMINOR:
144       case NVP_NAMEVALUE:
145         free(e->lhs);
146         free(e->rhs);
147         break;
148     }
149     free(e);
150   }
151   free(nvp);
152 }
153 /*}}}*/
make_nvp(struct msg_src * src,char * s,const char * pfx)154 struct nvp *make_nvp(struct msg_src *src, char *s, const char *pfx)/*{{{*/
155 {
156   int current_state;
157   unsigned int tok;
158   char *q, *tempsrc, *tempdst;
159   unsigned char qq;
160   char *name = NULL;
161   char *minor = NULL;
162   char *value = NULL;
163   char *copy_start;
164   enum nvp_action last_action, current_action;
165   enum nvp_copier last_copier;
166   struct nvp *result;
167   size_t pfxlen;
168 
169   pfxlen = strlen(pfx);
170   if (strncasecmp(pfx, s, pfxlen))
171     return NULL;
172   s += pfxlen;
173 
174   result = new(struct nvp);
175   result->first = result->last = NULL;
176 
177   current_state = nvp_in;
178 
179   q = s;
180   last_action = GOT_NOTHING;
181   last_copier = COPY_NOWHERE;
182   do {
183     qq = *(unsigned char *) q;
184     if (qq) {
185       tok = nvp_char2tok[qq];
186     } else {
187       tok = nvp_EOS;
188     }
189     current_state = nvp_next_state(current_state, tok);
190 #ifdef VERBOSE_TEST
191     fprintf(stderr, "Char %02x (%c) tok=%d new_current_state=%d\n",
192         qq, ((qq>=32) && (qq<=126)) ? qq : '.',
193         tok, current_state);
194 #endif
195 
196     if (current_state < 0) {
197 #ifdef TEST
198       fprintf(stderr, "'%s' could not be parsed\n", s);
199 #else
200       fprintf(stderr, "Header '%s%s' in %s could not be parsed\n",
201           pfx, s, format_msg_src(src));
202 #endif
203       release_nvp(result);
204       return NULL;
205     }
206 
207     if (nvp_copier[current_state] != last_copier) {
208       if (last_copier != COPY_NOWHERE) {
209         char *newstring = Malloc(q - copy_start + 1);
210 	memcpy(newstring, copy_start, q - copy_start);
211 	newstring[q - copy_start] = 0;
212         switch (last_copier) {
213           case COPY_TO_NAME:
214             free(name);
215             name = newstring;
216 #ifdef VERBOSE_TEST
217             fprintf(stderr, "  COPY_TO_NAME \"%s\"\n", name);
218 #endif
219             break;
220           case COPY_TO_MINOR:
221             free(minor);
222             minor = newstring;
223 #ifdef VERBOSE_TEST
224             fprintf(stderr, "  COPY_TO_MINOR \"%s\"\n", minor);
225 #endif
226             break;
227           case COPY_TO_VALUE:
228             free(value);
229             value = newstring;
230 #ifdef VERBOSE_TEST
231             fprintf(stderr, "  COPY_TO_VALUE \"%s\"\n", value);
232 #endif
233             break;
234           case COPY_NOWHERE:
235             /* NOTREACHED */
236             break;
237         }
238       }
239       last_copier = nvp_copier[current_state];
240       copy_start = q;
241     }
242 
243     current_action = nvp_action[current_state];
244     switch (current_action) {
245       case GOT_NAME:
246       case GOT_NAME_TRAILING_SPACE:
247       case GOT_MAJORMINOR:
248       case GOT_NAMEVALUE:
249       case GOT_NAMEVALUE_CONT:
250       case GOT_NAMEVALUE_CSET:
251       case GOT_NAMEVALUE_CCONT:
252 #ifdef VERBOSE_TEST
253         fprintf(stderr, "   Setting last action to %d\n", current_action);
254 #endif
255         last_action = current_action;
256         break;
257       case GOT_TERMINATOR:
258 #ifdef VERBOSE_TEST
259         fprintf(stderr, "   Hit terminator; last_action=%d\n", last_action);
260 #endif
261         switch (last_action) {
262           case GOT_NAME:
263             append_name(result, &name);
264             break;
265           case GOT_NAME_TRAILING_SPACE:
266             tempdst = name + strlen(name);
267             while (isspace(*--tempdst)) {}
268             *++tempdst = 0;
269             append_name(result, &name);
270             break;
271           case GOT_MAJORMINOR:
272             append_majorminor(result, &name, &minor);
273             break;
274           case GOT_NAMEVALUE:
275             append_namevalue(result, &name, &value);
276             break;
277           case GOT_NAMEVALUE_CSET:
278           case GOT_NAMEVALUE_CCONT:
279 	    for(tempsrc = tempdst = value; *tempsrc; tempsrc++) {
280 		if (*tempsrc == '%') {
281 		    int val = hex_to_val(*++tempsrc) << 4;
282 		    val |= hex_to_val(*++tempsrc);
283 		    if (val < 0) {
284 #ifdef TEST
285 			fprintf(stderr, "'%s' could not be parsed (%%)\n", s);
286 #else
287 			fprintf(stderr, "Header '%s%s' in %s could not be parsed\n",
288 				pfx, s, format_msg_src(src));
289 #endif
290 			release_nvp(result);
291 			result = NULL;
292 			goto out;
293 		    }
294 		    *tempdst++ = val;
295 		} else
296 		    *tempdst++ = *tempsrc;
297 	    }
298             *tempdst = 0;
299             if (current_action == GOT_NAMEVALUE_CSET)
300               append_namevalue(result, &name, &value);
301             else
302               combine_namevalue(result, &name, &value);
303             break;
304           case GOT_NAMEVALUE_CONT:
305             combine_namevalue(result, &name, &value);
306             break;
307           default:
308             break;
309         }
310         break;
311       case GOT_NOTHING:
312         break;
313     }
314 
315     q++;
316   } while (tok != nvp_EOS);
317 
318 out:
319   /* Not all productions consume these values */
320   free(name);
321   free(value);
322   free(minor);
323   return result;
324 }
325 /*}}}*/
free_nvp(struct nvp * nvp)326 void free_nvp(struct nvp *nvp)/*{{{*/
327 {
328   struct nvp_entry *ne, *nne;
329   for (ne = nvp->first; ne; ne=nne) {
330     nne = ne->next;
331     switch (ne->type) {
332       case NVP_NAME:
333         free(ne->lhs);
334         break;
335       case NVP_MAJORMINOR:
336       case NVP_NAMEVALUE:
337         free(ne->lhs);
338         free(ne->rhs);
339         break;
340     }
341     free(ne);
342   }
343   free(nvp);
344 }
345 /*}}}*/
nvp_lookup(struct nvp * nvp,const char * name)346 const char *nvp_lookup(struct nvp *nvp, const char *name)/*{{{*/
347 {
348   struct nvp_entry *ne;
349   for (ne = nvp->first; ne; ne=ne->next) {
350     if (ne->type == NVP_NAMEVALUE) {
351       if (!strcmp(ne->lhs, name)) {
352         return ne->rhs;
353       }
354     }
355   }
356   return NULL;
357 }
358 /*}}}*/
nvp_lookupcase(struct nvp * nvp,const char * name)359 const char *nvp_lookupcase(struct nvp *nvp, const char *name)/*{{{*/
360 {
361   struct nvp_entry *ne;
362   for (ne = nvp->first; ne; ne=ne->next) {
363     if (ne->type == NVP_NAMEVALUE) {
364       if (!strcasecmp(ne->lhs, name)) {
365         return ne->rhs;
366       }
367     }
368   }
369   return NULL;
370 }
371 /*}}}*/
372 
nvp_dump(struct nvp * nvp,FILE * out)373 void nvp_dump(struct nvp *nvp, FILE *out)/*{{{*/
374 {
375   struct nvp_entry *ne;
376   fprintf(out, "----\n");
377   for (ne = nvp->first; ne; ne=ne->next) {
378     switch (ne->type) {
379       case NVP_NAME:
380         fprintf(out, "NAME: %s\n", ne->lhs);
381         break;
382       case NVP_MAJORMINOR:
383         fprintf(out, "MAJORMINOR: %s/%s\n", ne->lhs, ne->rhs);
384         break;
385       case NVP_NAMEVALUE:
386         fprintf(out, "NAMEVALUE: %s=%s\n", ne->lhs, ne->rhs);
387         break;
388     }
389   }
390 }
391 /*}}}*/
392 
393 /* In these cases, we only look at the first entry */
nvp_major(struct nvp * nvp)394 const char *nvp_major(struct nvp *nvp)/*{{{*/
395 {
396   struct nvp_entry *ne;
397   ne = nvp->first;
398   if (ne) {
399     if (ne->type == NVP_MAJORMINOR) {
400       return ne->lhs;
401     } else {
402       return NULL;
403     }
404   } else {
405     return NULL;
406   }
407 }
408 /*}}}*/
nvp_minor(struct nvp * nvp)409 const char *nvp_minor(struct nvp *nvp)/*{{{*/
410 {
411   struct nvp_entry *ne;
412   ne = nvp->first;
413   if (ne) {
414     if (ne->type == NVP_MAJORMINOR) {
415       return ne->rhs;
416     } else {
417       return NULL;
418     }
419   } else {
420     return NULL;
421   }
422 }
423 /*}}}*/
nvp_first(struct nvp * nvp)424 const char *nvp_first(struct nvp *nvp)/*{{{*/
425 {
426   struct nvp_entry *ne;
427   ne = nvp->first;
428   if (ne) {
429     if (ne->type == NVP_NAME) {
430       return ne->lhs;
431     } else {
432       return NULL;
433     }
434   } else {
435     return NULL;
436   }
437 }
438 /*}}}*/
439 
440 #ifdef TEST
441 
do_test(char * s)442 static void do_test(char *s)
443 {
444   struct nvp *n;
445   n = make_nvp(NULL, s, "");
446   if (n) {
447     nvp_dump(n, stderr);
448     free_nvp(n);
449   }
450 }
451 
452 
main(int argc,char ** argv)453 int main (int argc, char **argv) {
454   struct nvp *n;
455 
456   if (argc > 1) {
457       while (*++argv)
458 	  do_test(*argv);
459       return 0;
460   }
461 
462 #if 0
463   do_test("attachment; filename=\"foo.c\"; prot=ro");
464   do_test("attachment; filename= \"foo bar.c\" ;prot=ro");
465   do_test("attachment ; filename= \"foo bar.c\" ;prot= ro");
466   do_test("attachment ; filename= \"foo bar.c\" ;prot= ro");
467   do_test("attachment ; filename= \"foo ;  bar.c\" ;prot= ro");
468   do_test("attachment ; x*0=\"hi \"; x*1=\"there\"");
469 #endif
470   do_test("attachment; filename*=utf-8''Section%204-1%20%E2%80%93%20Response%20Matrix%20PartIIA%2Edoc");
471 #if 0
472   do_test("application/vnd.ms-excel;       name=\"thequiz.xls\"");
473   do_test("inline; filename*0=\"aaaa bbbb cccc dddd eeee ffff gggg hhhh iiii jjjj\t kkkkllll\"");
474   do_test(" text/plain ; name= \"foo bar.c\" ;prot= ro/rw; read/write; read= foo bar");
475 #endif
476   return 0;
477 }
478 #endif
479