1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1985-2011 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Eclipse Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.eclipse.org/org/documents/epl-v10.html *
11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
19 * Phong Vo <kpv@research.att.com> *
20 * *
21 ***********************************************************************/
22 #pragma prototyped
23
24 /*
25 * Glenn Fowler
26 * AT&T Research
27 *
28 * mime/mailcap support library
29 */
30
31 static const char id[] = "\n@(#)$Id: mime library (AT&T Research) 2002-10-29 $\0\n";
32
33 static const char lib[] = "libast:mime";
34
35 #include "mimelib.h"
36
37 typedef struct Att_s
38 {
39 struct Att_s* next;
40 char* name;
41 char* value;
42 } Att_t;
43
44 typedef struct Cap_s
45 {
46 struct Cap_s* next;
47 unsigned long flags;
48 Att_t att;
49 char* test;
50 char data[1];
51 } Cap_t;
52
53 typedef struct
54 {
55 Dtlink_t link;
56 Cap_t* cap;
57 Cap_t* pac;
58 char name[1];
59 } Ent_t;
60
61 typedef struct
62 {
63 char* data;
64 int size;
65 } String_t;
66
67 typedef struct
68 {
69 char* next;
70 String_t name;
71 String_t value;
72 } Parse_t;
73
74 typedef struct
75 {
76 const char* pattern;
77 int prefix;
78 Sfio_t* fp;
79 int hit;
80 } Walk_t;
81
82 /*
83 * convert c to lower case
84 */
85
86 static int
lower(int c)87 lower(int c)
88 {
89 return isupper(c) ? tolower(c) : c;
90 }
91
92 /*
93 * Ent_t case insensitive comparf
94 */
95
96 static int
order(Dt_t * dt,void * a,void * b,Dtdisc_t * disc)97 order(Dt_t* dt, void* a, void* b, Dtdisc_t* disc)
98 {
99 return strcasecmp(a, b);
100 }
101
102 /*
103 * Cap_t free
104 */
105
106 static void
dropcap(Cap_t * cap)107 dropcap(Cap_t* cap)
108 {
109 Att_t* att;
110
111 while (att = cap->att.next)
112 {
113 cap->att.next = att->next;
114 free(att);
115 }
116 free(cap);
117 }
118
119 /*
120 * Ent_t freef
121 */
122
123 static void
drop(Dt_t * dt,void * object,Dtdisc_t * disc)124 drop(Dt_t* dt, void* object, Dtdisc_t* disc)
125 {
126 Ent_t* ent = (Ent_t*)object;
127 Cap_t* cap;
128
129 while (cap = ent->cap)
130 {
131 ent->cap = cap->next;
132 dropcap(cap);
133 }
134 free(ent);
135 }
136
137 /*
138 * add mime type entry in s to mp
139 */
140
141 int
mimeset(Mime_t * mp,char * s,unsigned long flags)142 mimeset(Mime_t* mp, char* s, unsigned long flags)
143 {
144 Ent_t* ent;
145 Cap_t* cap;
146 Att_t* att;
147 char* t;
148 char* v;
149 char* k;
150 char* x;
151 Att_t* tta;
152 int q;
153
154 for (; isspace(*s); s++);
155 if (*s && *s != '#')
156 {
157 cap = 0;
158 for (v = s; *v && *v != ';'; v++)
159 if (isspace(*v) || *v == '/' && *(v + 1) == '*')
160 *v = 0;
161 if (*v)
162 {
163 *v++ = 0;
164 do
165 {
166 for (; isspace(*v); v++);
167 if (cap)
168 {
169 for (t = v; *t && !isspace(*t) && *t != '='; t++);
170 for (k = t; isspace(*t); t++);
171 if (!*t || *t == '=' || *t == ';')
172 {
173 if (*t)
174 while (isspace(*++t));
175 *k = 0;
176 k = v;
177 v = t;
178 }
179 else
180 k = 0;
181 }
182 if (*v == '"')
183 q = *v++;
184 else
185 q = 0;
186 for (t = v; *t; t++)
187 if (*t == '\\')
188 {
189 switch (*(t + 1))
190 {
191 case 0:
192 case '\\':
193 case '%':
194 *t = *(t + 1);
195 break;
196 default:
197 *t = ' ';
198 break;
199 }
200 if (!*++t)
201 break;
202 }
203 else if (*t == q)
204 {
205 *t = ' ';
206 q = 0;
207 }
208 else if (*t == ';' && !q)
209 {
210 *t = ' ';
211 break;
212 }
213 for (; t > v && isspace(*(t - 1)); t--);
214 if (t <= v && (!cap || !k))
215 break;
216 if (!cap)
217 {
218 if (!(cap = newof(0, Cap_t, 1, strlen(v) + 1)))
219 return -1;
220 if (*t)
221 *t++ = 0;
222 tta = &cap->att;
223 tta->name = "default";
224 x = strcopy(tta->value = cap->data, v) + 1;
225 }
226 else if (k)
227 {
228 if (*t)
229 *t++ = 0;
230 if (!(att = newof(0, Att_t, 1, 0)))
231 return -1;
232 x = strcopy(att->name = x, k) + 1;
233 x = strcopy(att->value = x, v) + 1;
234 tta = tta->next = att;
235 if (!strcasecmp(k, "test"))
236 cap->test = att->value;
237 }
238 } while (*(v = t));
239 }
240 ent = (Ent_t*)dtmatch(mp->cap, s);
241 if (cap)
242 {
243 if (ent)
244 {
245 Cap_t* dup;
246 Cap_t* pud;
247
248 for (pud = 0, dup = ent->cap; dup; pud = dup, dup = dup->next)
249 if (!cap->test && !dup->test || cap->test && dup->test && streq(cap->test, dup->test))
250 {
251 if (flags & MIME_REPLACE)
252 {
253 if (pud)
254 pud->next = cap;
255 else
256 ent->cap = cap;
257 if (!(cap->next = dup->next))
258 ent->pac = cap;
259 cap = dup;
260 }
261 dropcap(cap);
262 return 0;
263 }
264 ent->pac = ent->pac->next = cap;
265 }
266 else if (!(ent = newof(0, Ent_t, 1, strlen(s) + 1)))
267 return -1;
268 else
269 {
270 strcpy(ent->name, s);
271 ent->cap = ent->pac = cap;
272 dtinsert(mp->cap, ent);
273 }
274 }
275 else if (ent && (flags & MIME_REPLACE))
276 dtdelete(mp->cap, ent);
277 }
278 return 0;
279 }
280
281 /*
282 * load mime type files into mp
283 */
284
285 int
mimeload(Mime_t * mp,const char * file,unsigned long flags)286 mimeload(Mime_t* mp, const char* file, unsigned long flags)
287 {
288 char* s;
289 char* t;
290 char* e;
291 int n;
292 Sfio_t* fp;
293
294 if (!(s = (char*)file))
295 {
296 flags |= MIME_LIST;
297 if (!(s = getenv(MIME_FILES_ENV)))
298 s = MIME_FILES;
299 }
300 for (;;)
301 {
302 if (!(flags & MIME_LIST))
303 e = 0;
304 else if (e = strchr(s, ':'))
305 {
306 /*
307 * ok, so ~ won't work for the last list element
308 * we do it for MIME_FILES_ENV anyway
309 */
310
311 if ((strneq(s, "~/", n = 2) || strneq(s, "$HOME/", n = 6) || strneq(s, "${HOME}/", n = 8)) && (t = getenv("HOME")))
312 {
313 sfputr(mp->buf, t, -1);
314 s += n - 1;
315 }
316 sfwrite(mp->buf, s, e - s);
317 if (!(s = sfstruse(mp->buf)))
318 return -1;
319 }
320 if (fp = tokline(s, SF_READ, NiL))
321 {
322 while (t = sfgetr(fp, '\n', 1))
323 if (mimeset(mp, t, flags))
324 break;
325 sfclose(fp);
326 }
327 else if (!(flags & MIME_LIST))
328 return -1;
329 if (!e)
330 break;
331 s = e + 1;
332 }
333 return 0;
334 }
335
336 /*
337 * mimelist walker
338 */
339
340 static int
list(Dt_t * dt,void * object,void * context)341 list(Dt_t* dt, void* object, void* context)
342 {
343 Walk_t* wp = (Walk_t*)context;
344 Ent_t* ent = (Ent_t*)object;
345 Cap_t* cap;
346 Att_t* att;
347
348 if (!wp->pattern || !strncasecmp(ent->name, wp->pattern, wp->prefix) && (!ent->name[wp->prefix] || ent->name[wp->prefix] == '/'))
349 {
350 wp->hit++;
351 for (cap = ent->cap; cap; cap = cap->next)
352 {
353 sfprintf(wp->fp, "%s", ent->name);
354 for (att = &cap->att; att; att = att->next)
355 {
356 sfprintf(wp->fp, "\n\t");
357 if (att != &cap->att)
358 {
359 sfprintf(wp->fp, "%s", att->name);
360 if (*att->value)
361 sfprintf(wp->fp, " = ");
362 }
363 sfputr(wp->fp, att->value, -1);
364 }
365 sfprintf(wp->fp, "\n");
366 }
367 }
368 return 0;
369 }
370
371 /*
372 * find entry matching type
373 * if exact match fails then left and right x- and right version number
374 * permutations are attempted
375 */
376
377 static Ent_t*
find(Mime_t * mp,const char * type)378 find(Mime_t* mp, const char* type)
379 {
380 char* lp;
381 char* rp;
382 char* rb;
383 char* rv;
384 int rc;
385 int i;
386 char* s;
387 Ent_t* ent;
388 char buf[256];
389
390 static const char* prefix[] = { "", "", "x-", "x-", "" };
391
392 if ((ent = (Ent_t*)dtmatch(mp->cap, type)) ||
393 !(rp = strchr(lp = (char*)type, '/')) ||
394 strlen(lp) >= sizeof(buf))
395 return ent;
396 strcpy(buf, type);
397 rp = buf + (rp - lp);
398 *rp++ = 0;
399 if (*rp == 'x' && *(rp + 1) == '-')
400 rp += 2;
401 lp = buf;
402 if (*lp == 'x' && *(lp + 1) == '-')
403 lp += 2;
404 rb = rp;
405 for (rv = rp + strlen(rp); rv > rp && (isdigit(*(rv - 1)) || *(rv - 1) == '.'); rv--);
406 rc = *rv;
407 do
408 {
409 rp = rb;
410 do
411 {
412 for (i = 0; i < elementsof(prefix) - 1; i++)
413 {
414 sfprintf(mp->buf, "%s%s/%s%s", prefix[i], lp, prefix[i + 1], rp);
415 if (!(s = sfstruse(mp->buf)))
416 return 0;
417 if (ent = (Ent_t*)dtmatch(mp->cap, s))
418 return ent;
419 if (rc)
420 {
421 *rv = 0;
422 sfprintf(mp->buf, "%s%s/%s%s", prefix[i], lp, prefix[i + 1], rp);
423 if (!(s = sfstruse(mp->buf)))
424 return 0;
425 if (ent = (Ent_t*)dtmatch(mp->cap, s))
426 return ent;
427 *rv = rc;
428 }
429 }
430 while (*rp && *rp++ != '-');
431 } while (*rp);
432 while (*lp && *lp++ != '-');
433 } while (*lp);
434 return (Ent_t*)dtmatch(mp->cap, buf);
435 }
436
437 /*
438 * list mime <type,data> for pat on fp
439 */
440
441 int
mimelist(Mime_t * mp,Sfio_t * fp,const char * pattern)442 mimelist(Mime_t* mp, Sfio_t* fp, const char* pattern)
443 {
444 Ent_t* ent;
445 Walk_t ws;
446
447 ws.fp = fp;
448 ws.hit = 0;
449 ws.prefix = 0;
450 if (ws.pattern = pattern)
451 {
452 while (*pattern && *pattern++ != '/');
453 if (!*pattern || *pattern == '*' && !*(pattern + 1))
454 ws.prefix = pattern - ws.pattern;
455 else if (ent = find(mp, ws.pattern))
456 {
457 ws.pattern = 0;
458 list(mp->cap, ent, &ws);
459 return ws.hit;
460 }
461 }
462 dtwalk(mp->cap, list, &ws);
463 return ws.hit;
464 }
465
466 /*
467 * get next arg in pp
468 * 0 returned if no more args
469 */
470
471 static int
arg(Parse_t * pp,int first)472 arg(Parse_t* pp, int first)
473 {
474 char* s;
475 int c;
476 int q;
477 int x;
478
479 for (s = pp->next; isspace(*s) && *s != '\n'; s++);
480 if (!*s || *s == '\n')
481 {
482 pp->next = s;
483 return 0;
484 }
485 pp->name.data = s;
486 pp->value.data = 0;
487 q = 0;
488 x = 0;
489 while ((c = *s++) && c != ';' && c != '\n')
490 {
491 if (c == '"')
492 {
493 q = 1;
494 if (pp->value.data)
495 {
496 pp->value.data = s;
497 if (x)
498 x = -1;
499 else
500 x = 1;
501 }
502 else if (!x && pp->name.data == (s - 1))
503 {
504 x = 1;
505 pp->name.data = s;
506 }
507 do
508 {
509 if (!(c = *s++) || c == '\n')
510 {
511 s--;
512 break;
513 }
514 } while (c != '"');
515 if (first < 0 || x > 0)
516 {
517 c = ';';
518 break;
519 }
520 }
521 else if (c == '=' && !first)
522 {
523 first = 1;
524 pp->name.size = s - pp->name.data - 1;
525 pp->value.data = s;
526 }
527 else if (first >= 0 && isspace(c))
528 break;
529 }
530 pp->next = s - (c != ';');
531 if (first >= 0 || !q)
532 for (s--; s > pp->name.data && isspace(*(s - 1)); s--);
533 if (pp->value.data)
534 pp->value.size = s - pp->value.data - (q && first < 0);
535 else
536 {
537 pp->value.size = 0;
538 pp->name.size = s - pp->name.data - (q && first < 0);
539 }
540 if (first >= 0 && pp->name.size > 0 && pp->name.data[pp->name.size - 1] == ':')
541 return 0;
542 return pp->name.size > 0;
543 }
544
545 /*
546 * low level for mimeview()
547 */
548
549 static char*
expand(Mime_t * mp,char * s,const char * name,const char * type,const char * opts)550 expand(Mime_t* mp, char* s, const char* name, const char* type, const char* opts)
551 {
552 char* t;
553 char* v;
554 int c;
555 int e;
556 int n;
557 Parse_t pp;
558
559 mp->disc->flags |= MIME_PIPE;
560 for (;;)
561 {
562 switch (c = *s++)
563 {
564 case 0:
565 case '\n':
566 break;
567 case '%':
568 if ((c = *s++) == '{' && (e = '}') || c == '(' && (e = ')'))
569 {
570 for (t = s; *s && *s != e; s++);
571 n = s - t;
572 switch (*s)
573 {
574 case '}':
575 s++;
576 c = '{';
577 break;
578 case ')':
579 s++;
580 if (c = *s)
581 s++;
582 break;
583 }
584 }
585 else
586 t = 0;
587 switch (c)
588 {
589 case 's':
590 v = (char*)name;
591 mp->disc->flags &= ~MIME_PIPE;
592 break;
593 case 't':
594 v = (char*)type;
595 break;
596 case '{':
597 for (t = s; *s && *s != '}'; s++);
598 if (*s && (c = s++ - t) && (pp.next = (char*)opts))
599 while (arg(&pp, 0))
600 if (pp.name.size == c && !strncasecmp(pp.name.data, t, c))
601 {
602 if (pp.value.size)
603 sfwrite(mp->buf, pp.value.data, pp.value.size);
604 break;
605 }
606 continue;
607 default:
608 sfputc(mp->buf, c);
609 continue;
610 }
611 if (v && *v)
612 n = strlen(v);
613 else if (t)
614 v = t;
615 else
616 continue;
617 sfputr(mp->buf, fmtquote(v, 0, 0, n, FMT_SHELL), -1);
618 continue;
619 default:
620 sfputc(mp->buf, c);
621 continue;
622 }
623 break;
624 }
625 return sfstruse(mp->buf);
626 }
627
628 /*
629 * return expanded command/path/value for <view,name,type,opts>
630 * return value valid until next mime*() call
631 */
632
633 char*
mimeview(Mime_t * mp,const char * view,const char * name,const char * type,const char * opts)634 mimeview(Mime_t* mp, const char* view, const char* name, const char* type, const char* opts)
635 {
636 Ent_t* ent;
637 Cap_t* cap;
638 Att_t* att;
639 char* s;
640 int c;
641
642 if (ent = find(mp, type))
643 {
644 cap = ent->cap;
645 if (!view || strcasecmp(view, "test"))
646 while (s = cap->test)
647 {
648 if (s = expand(mp, s, name, type, opts))
649 {
650 Parse_t a1;
651 Parse_t a2;
652 Parse_t a3;
653 Parse_t a4;
654
655 /*
656 * try to do a few common cases here
657 * mailcap consistency is a winning
658 * strategy
659 */
660
661 a1.next = s;
662 if (arg(&a1, -1))
663 {
664 if ((c = *a1.name.data == '!') && --a1.name.size <= 0 && !arg(&a1, -1))
665 goto lose;
666 if (a1.name.size == 6 && strneq(a1.name.data, "strcmp", 6) || a1.name.size == 10 && strneq(a1.name.data, "strcasecmp", 10))
667 {
668 a2.next = a1.next;
669 if (!arg(&a2, -1))
670 goto lose;
671 a3.next = a2.next;
672 if (!arg(&a3, -1))
673 goto lose;
674 if (a2.name.size != a3.name.size)
675 c ^= 0;
676 else c ^= (a1.name.size == 6 ? strncmp : strncasecmp)(a2.name.data, a3.name.data, a2.name.size) == 0;
677 if (c)
678 break;
679 goto skip;
680 }
681 else if (a1.name.size == 4 && strneq(a1.name.data, "test", 4))
682 {
683 if (!arg(&a1, -1))
684 goto lose;
685 a2.next = a1.next;
686 if (!arg(&a2, -1) || a2.name.size > 2 || a2.name.size == 1 && *a2.name.data != '=' || a2.name.size == 2 && (!strneq(a1.name.data, "!=", 2) || !strneq(a2.name.data, "==", 2)))
687 goto lose;
688 a3.next = a2.next;
689 if (!arg(&a3, -1))
690 goto lose;
691 if (*a3.name.data == '`' && *(a3.name.data + a3.name.size - 1) == '`')
692 {
693 a4 = a3;
694 a3 = a1;
695 a1 = a4;
696 }
697 if (*a1.name.data == '`' && *(a1.name.data + a1.name.size - 1) == '`')
698 {
699 a1.next = a1.name.data + 1;
700 if (!arg(&a1, -1) || a1.name.size != 4 || !strneq(a1.name.data, "echo", 4) || !arg(&a1, -1))
701 goto lose;
702 a4.next = a1.next;
703 if (!arg(&a4, 1) || a4.name.size < 21 || !strneq(a4.name.data, "| tr '[A-Z]' '[a-z]'`", 21))
704 goto lose;
705 }
706 else
707 a4.name.size = 0;
708 c = *a2.name.data == '!';
709 if (a1.name.size != a3.name.size)
710 c ^= 0;
711 else c ^= (a4.name.size ? strncasecmp : strncmp)(a1.name.data, a3.name.data, a1.name.size) == 0;
712 if (c)
713 break;
714 goto skip;
715 }
716 }
717 lose:
718 if (!system(s))
719 break;
720 }
721 skip:
722 if (!(cap = cap->next))
723 return 0;
724 }
725 att = &cap->att;
726 if (view && *view && !streq(view, "-"))
727 while (strcasecmp(view, att->name))
728 if (!(att = att->next))
729 return 0;
730 return expand(mp, att->value, name, type, opts);
731 }
732 return 0;
733 }
734
735 /*
736 * lower case identifier prefix strcmp
737 * if e!=0 then it will point to the next char after the match
738 */
739
740 int
mimecmp(const char * s,const char * v,char ** e)741 mimecmp(const char* s, const char* v, char** e)
742 {
743 int n;
744
745 while (isalnum(*v) || *v == *s && (*v == '_' || *v == '-' || *v == '/'))
746 if (n = lower(*s++) - lower(*v++))
747 return n;
748 if (!isalnum(*s) && *s != '_' && *s != '-')
749 {
750 if (e)
751 *e = (char*)s;
752 return 0;
753 }
754 return lower(*s) - lower(*v);
755 }
756
757 static int
_mimecmp(const char * s,const char * v)758 _mimecmp(const char* s, const char* v)
759 {
760 return (mimecmp(s, v, NULL));
761 }
762
763 /*
764 * parse mime headers in strsearch(tab,num,siz) from s
765 * return >0 if mime header consumed
766 */
767
768 int
mimehead(Mime_t * mp,void * tab,size_t num,size_t siz,char * s)769 mimehead(Mime_t* mp, void* tab, size_t num, size_t siz, char* s)
770 {
771 void* p;
772 char* e;
773 Parse_t pp;
774 Mimevalue_f set;
775
776 set = mp->disc->valuef;
777 if (!strncasecmp(s, "original-", 9))
778 s += 9;
779 if (!strncasecmp(s, "content-", 8))
780 {
781 s += 8;
782 if ((p = strsearch(tab, num, siz, _mimecmp, s, &e)) && *e == ':')
783 {
784 pp.next = e + 1;
785 if (arg(&pp, 1))
786 {
787 if ((*set)(mp, p, pp.name.data, pp.name.size, mp->disc))
788 return 0;
789 while (arg(&pp, 0))
790 if (pp.value.size &&
791 (p = strsearch(tab, num, siz, _mimecmp, pp.name.data, &e)) &&
792 (*set)(mp, p, pp.value.data, pp.value.size, mp->disc))
793 return 0;
794 return 1;
795 }
796 }
797 else if (strchr(s, ':'))
798 return 1;
799 }
800 return !strncasecmp(s, "x-", 2);
801 }
802
803 /*
804 * open a mime library handle
805 */
806
807 Mime_t*
mimeopen(Mimedisc_t * disc)808 mimeopen(Mimedisc_t* disc)
809 {
810 Mime_t* mp;
811
812 if (!(mp = newof(0, Mime_t, 1, 0)))
813 return 0;
814 mp->id = lib;
815 mp->disc = disc;
816 mp->dict.key = offsetof(Ent_t, name);
817 mp->dict.comparf = order;
818 mp->dict.freef = drop;
819 if (!(mp->buf = sfstropen()) || !(mp->cap = dtopen(&mp->dict, Dtoset)))
820 {
821 mimeclose(mp);
822 return 0;
823 }
824 return mp;
825 }
826
827 /*
828 * close a mimeopen() handle
829 */
830
831 int
mimeclose(Mime_t * mp)832 mimeclose(Mime_t* mp)
833 {
834 if (mp)
835 {
836 if (mp->buf)
837 sfclose(mp->buf);
838 if (mp->cap)
839 dtclose(mp->cap);
840 if (mp->freef)
841 (*mp->freef)(mp);
842 free(mp);
843 }
844 return 0;
845 }
846