/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1985-2007 AT&T Intellectual Property * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.opensource.org/licenses/cpl1.0.txt * * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * Glenn Fowler * * David Korn * * Phong Vo * * * ***********************************************************************/ #pragma prototyped /* * Glenn Fowler * AT&T Research * * mime/mailcap support library */ static const char id[] = "\n@(#)$Id: mime library (AT&T Research) 2002-10-29 $\0\n"; static const char lib[] = "libast:mime"; #include "mimelib.h" typedef struct Att_s { struct Att_s* next; char* name; char* value; } Att_t; typedef struct Cap_s { struct Cap_s* next; unsigned long flags; Att_t att; char* test; char data[1]; } Cap_t; typedef struct { Dtlink_t link; Cap_t* cap; Cap_t* pac; char name[1]; } Ent_t; typedef struct { char* data; int size; } String_t; typedef struct { char* next; String_t name; String_t value; } Parse_t; typedef struct { const char* pattern; int prefix; Sfio_t* fp; int hit; } Walk_t; /* * convert c to lower case */ static int lower(register int c) { return isupper(c) ? tolower(c) : c; } /* * Ent_t case insensitive comparf */ static int order(Dt_t* dt, void* a, void* b, Dtdisc_t* disc) { return strcasecmp(a, b); } /* * Cap_t free */ static void dropcap(register Cap_t* cap) { register Att_t* att; while (att = cap->att.next) { cap->att.next = att->next; free(att); } free(cap); } /* * Ent_t freef */ static void drop(Dt_t* dt, void* object, Dtdisc_t* disc) { register Ent_t* ent = (Ent_t*)object; register Cap_t* cap; while (cap = ent->cap) { ent->cap = cap->next; dropcap(cap); } free(ent); } /* * add mime type entry in s to mp */ int mimeset(Mime_t* mp, register char* s, unsigned long flags) { register Ent_t* ent; register Cap_t* cap; register Att_t* att; register char* t; register char* v; register char* k; char* x; Att_t* tta; int q; for (; isspace(*s); s++); if (*s && *s != '#') { cap = 0; for (v = s; *v && *v != ';'; v++) if (isspace(*v) || *v == '/' && *(v + 1) == '*') *v = 0; if (*v) { *v++ = 0; do { for (; isspace(*v); v++); if (cap) { for (t = v; *t && !isspace(*t) && *t != '='; t++); for (k = t; isspace(*t); t++); if (!*t || *t == '=' || *t == ';') { if (*t) while (isspace(*++t)); *k = 0; k = v; v = t; } else k = 0; } if (*v == '"') q = *v++; else q = 0; for (t = v; *t; t++) if (*t == '\\') { switch (*(t + 1)) { case 0: case '\\': case '%': *t = *(t + 1); break; default: *t = ' '; break; } if (!*++t) break; } else if (*t == q) { *t = ' '; q = 0; } else if (*t == ';' && !q) { *t = ' '; break; } for (; t > v && isspace(*(t - 1)); t--); if (t <= v && (!cap || !k)) break; if (!cap) { if (!(cap = newof(0, Cap_t, 1, strlen(v) + 1))) return -1; if (*t) *t++ = 0; tta = &cap->att; tta->name = "default"; x = strcopy(tta->value = cap->data, v) + 1; } else if (k) { if (*t) *t++ = 0; if (!(att = newof(0, Att_t, 1, 0))) return -1; x = strcopy(att->name = x, k) + 1; x = strcopy(att->value = x, v) + 1; tta = tta->next = att; if (!strcasecmp(k, "test")) cap->test = att->value; } } while (*(v = t)); } ent = (Ent_t*)dtmatch(mp->cap, s); if (cap) { if (ent) { register Cap_t* dup; register Cap_t* pud; for (pud = 0, dup = ent->cap; dup; pud = dup, dup = dup->next) if (!cap->test && !dup->test || cap->test && dup->test && streq(cap->test, dup->test)) { if (flags & MIME_REPLACE) { if (pud) pud->next = cap; else ent->cap = cap; if (!(cap->next = dup->next)) ent->pac = cap; cap = dup; } dropcap(cap); return 0; } ent->pac = ent->pac->next = cap; } else if (!(ent = newof(0, Ent_t, 1, strlen(s) + 1))) return -1; else { strcpy(ent->name, s); ent->cap = ent->pac = cap; dtinsert(mp->cap, ent); } } else if (ent && (flags & MIME_REPLACE)) dtdelete(mp->cap, ent); } return 0; } /* * load mime type files into mp */ int mimeload(Mime_t* mp, const char* file, unsigned long flags) { register char* s; register char* t; register char* e; register int n; Sfio_t* fp; if (!(s = (char*)file)) { flags |= MIME_LIST; if (!(s = getenv(MIME_FILES_ENV))) s = MIME_FILES; } for (;;) { if (!(flags & MIME_LIST)) e = 0; else if (e = strchr(s, ':')) { /* * ok, so ~ won't work for the last list element * we do it for MIME_FILES_ENV anyway */ if ((strneq(s, "~/", n = 2) || strneq(s, "$HOME/", n = 6) || strneq(s, "${HOME}/", n = 8)) && (t = getenv("HOME"))) { sfputr(mp->buf, t, -1); s += n - 1; } sfwrite(mp->buf, s, e - s); if (!(s = sfstruse(mp->buf))) return -1; } if (fp = tokline(s, SF_READ, NiL)) { while (t = sfgetr(fp, '\n', 1)) if (mimeset(mp, t, flags)) break; sfclose(fp); } else if (!(flags & MIME_LIST)) return -1; if (!e) break; s = e + 1; } return 0; } /* * mimelist walker */ static int list(Dt_t* dt, void* object, void* context) { register Walk_t* wp = (Walk_t*)context; register Ent_t* ent = (Ent_t*)object; register Cap_t* cap; register Att_t* att; if (!wp->pattern || !strncasecmp(ent->name, wp->pattern, wp->prefix) && (!ent->name[wp->prefix] || ent->name[wp->prefix] == '/')) { wp->hit++; for (cap = ent->cap; cap; cap = cap->next) { sfprintf(wp->fp, "%s", ent->name); for (att = &cap->att; att; att = att->next) { sfprintf(wp->fp, "\n\t"); if (att != &cap->att) { sfprintf(wp->fp, "%s", att->name); if (*att->value) sfprintf(wp->fp, " = "); } sfputr(wp->fp, att->value, -1); } sfprintf(wp->fp, "\n"); } } return 0; } /* * find entry matching type * if exact match fails then left and right x- and right version number * permutations are attempted */ static Ent_t* find(Mime_t* mp, const char* type) { register char* lp; register char* rp; register char* rb; register char* rv; register int rc; register int i; char* s; Ent_t* ent; char buf[256]; static const char* prefix[] = { "", "", "x-", "x-", "" }; if ((ent = (Ent_t*)dtmatch(mp->cap, type)) || !(rp = strchr(lp = (char*)type, '/')) || strlen(lp) >= sizeof(buf)) return ent; strcpy(buf, type); rp = buf + (rp - lp); *rp++ = 0; if (*rp == 'x' && *(rp + 1) == '-') rp += 2; lp = buf; if (*lp == 'x' && *(lp + 1) == '-') lp += 2; rb = rp; for (rv = rp + strlen(rp); rv > rp && (isdigit(*(rv - 1)) || *(rv - 1) == '.'); rv--); rc = *rv; do { rp = rb; do { for (i = 0; i < elementsof(prefix) - 1; i++) { sfprintf(mp->buf, "%s%s/%s%s", prefix[i], lp, prefix[i + 1], rp); if (!(s = sfstruse(mp->buf))) return 0; if (ent = (Ent_t*)dtmatch(mp->cap, s)) return ent; if (rc) { *rv = 0; sfprintf(mp->buf, "%s%s/%s%s", prefix[i], lp, prefix[i + 1], rp); if (!(s = sfstruse(mp->buf))) return 0; if (ent = (Ent_t*)dtmatch(mp->cap, s)) return ent; *rv = rc; } } while (*rp && *rp++ != '-'); } while (*rp); while (*lp && *lp++ != '-'); } while (*lp); return (Ent_t*)dtmatch(mp->cap, buf); } /* * list mime for pat on fp */ int mimelist(Mime_t* mp, Sfio_t* fp, register const char* pattern) { Ent_t* ent; Walk_t ws; ws.fp = fp; ws.hit = 0; ws.prefix = 0; if (ws.pattern = pattern) { while (*pattern && *pattern++ != '/'); if (!*pattern || *pattern == '*' && !*(pattern + 1)) ws.prefix = pattern - ws.pattern; else if (ent = find(mp, ws.pattern)) { ws.pattern = 0; list(mp->cap, ent, &ws); return ws.hit; } } dtwalk(mp->cap, list, &ws); return ws.hit; } /* * get next arg in pp * 0 returned if no more args */ static int arg(register Parse_t* pp, int first) { register char* s; register int c; register int q; int x; for (s = pp->next; isspace(*s) && *s != '\n'; s++); if (!*s || *s == '\n') { pp->next = s; return 0; } pp->name.data = s; pp->value.data = 0; q = 0; x = 0; while ((c = *s++) && c != ';' && c != '\n') { if (c == '"') { q = 1; if (pp->value.data) { pp->value.data = s; if (x) x = -1; else x = 1; } else if (!x && pp->name.data == (s - 1)) { x = 1; pp->name.data = s; } do { if (!(c = *s++) || c == '\n') { s--; break; } } while (c != '"'); if (first < 0 || x > 0) { c = ';'; break; } } else if (c == '=' && !first) { first = 1; pp->name.size = s - pp->name.data - 1; pp->value.data = s; } else if (first >= 0 && isspace(c)) break; } pp->next = s - (c != ';'); if (first >= 0 || !q) for (s--; s > pp->name.data && isspace(*(s - 1)); s--); if (pp->value.data) pp->value.size = s - pp->value.data - (q && first < 0); else { pp->value.size = 0; pp->name.size = s - pp->name.data - (q && first < 0); } if (first >= 0 && pp->name.size > 0 && pp->name.data[pp->name.size - 1] == ':') return 0; return pp->name.size > 0; } /* * low level for mimeview() */ static char* expand(Mime_t* mp, register char* s, const char* name, const char* type, const char* opts) { register char* t; register int c; Parse_t pp; mp->disc->flags |= MIME_PIPE; for (;;) { switch (c = *s++) { case 0: case '\n': break; case '%': switch (c = *s++) { case 's': sfputr(mp->buf, (char*)name, -1); mp->disc->flags &= ~MIME_PIPE; continue; case 't': sfputr(mp->buf, (char*)type, -1); continue; case '{': for (t = s; *s && *s != '}'; s++); if (*s && (c = s++ - t) && (pp.next = (char*)opts)) while (arg(&pp, 0)) if (pp.name.size == c && !strncasecmp(pp.name.data, t, c)) { if (pp.value.size) sfwrite(mp->buf, pp.value.data, pp.value.size); break; } continue; } /*FALLTHROUGH*/ default: sfputc(mp->buf, c); continue; } break; } return sfstruse(mp->buf); } /* * return expanded command/path/value for * return value valid until next mime*() call */ char* mimeview(Mime_t* mp, const char* view, const char* name, const char* type, const char* opts) { register Ent_t* ent; register Cap_t* cap; register Att_t* att; register char* s; int c; if (ent = find(mp, type)) { cap = ent->cap; if (!view || strcasecmp(view, "test")) while (s = cap->test) { if (s = expand(mp, s, name, type, opts)) { Parse_t a1; Parse_t a2; Parse_t a3; Parse_t a4; /* * try to do a few common cases here * mailcap consistency is a winning * strategy */ a1.next = s; if (arg(&a1, -1)) { if ((c = *a1.name.data == '!') && --a1.name.size <= 0 && !arg(&a1, -1)) goto lose; if (a1.name.size == 6 && strneq(a1.name.data, "strcmp", 6) || a1.name.size == 10 && strneq(a1.name.data, "strcasecmp", 10)) { a2.next = a1.next; if (!arg(&a2, -1)) goto lose; a3.next = a2.next; if (!arg(&a3, -1)) goto lose; if (a2.name.size != a3.name.size) c ^= 0; else c ^= (a1.name.size == 6 ? strncmp : strncasecmp)(a2.name.data, a3.name.data, a2.name.size) == 0; if (c) break; goto skip; } else if (a1.name.size == 4 && strneq(a1.name.data, "test", 4)) { if (!arg(&a1, -1)) goto lose; a2.next = a1.next; 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))) goto lose; a3.next = a2.next; if (!arg(&a3, -1)) goto lose; if (*a3.name.data == '`' && *(a3.name.data + a3.name.size - 1) == '`') { a4 = a3; a3 = a1; a1 = a4; } if (*a1.name.data == '`' && *(a1.name.data + a1.name.size - 1) == '`') { a1.next = a1.name.data + 1; if (!arg(&a1, -1) || a1.name.size != 4 || !strneq(a1.name.data, "echo", 4) || !arg(&a1, -1)) goto lose; a4.next = a1.next; if (!arg(&a4, 1) || a4.name.size < 21 || !strneq(a4.name.data, "| tr '[A-Z]' '[a-z]'`", 21)) goto lose; } else a4.name.size = 0; c = *a2.name.data == '!'; if (a1.name.size != a3.name.size) c ^= 0; else c ^= (a4.name.size ? strncasecmp : strncmp)(a1.name.data, a3.name.data, a1.name.size) == 0; if (c) break; goto skip; } } lose: if (!system(s)) break; } skip: if (!(cap = cap->next)) return 0; } att = &cap->att; if (view && *view && !streq(view, "-")) while (strcasecmp(view, att->name)) if (!(att = att->next)) return 0; return expand(mp, att->value, name, type, opts); } return 0; } /* * lower case identifier prefix strcmp * if e!=0 then it will point to the next char after the match */ int mimecmp(register const char* s, register const char* v, char** e) { register int n; while (isalnum(*v) || *v == *s && (*v == '_' || *v == '-' || *v == '/')) if (n = lower(*s++) - lower(*v++)) return n; if (!isalnum(*s) && *s != '_' && *s != '-') { if (e) *e = (char*)s; return 0; } return lower(*s) - lower(*v); } /* * parse mime headers in strsearch(tab,num,siz) from s * return >0 if mime header consumed */ int mimehead(Mime_t* mp, void* tab, size_t num, size_t siz, register char* s) { register void* p; char* e; Parse_t pp; Mimevalue_f set; set = mp->disc->valuef; if (!strncasecmp(s, "original-", 9)) s += 9; if (!strncasecmp(s, "content-", 8)) { s += 8; if ((p = strsearch(tab, num, siz, (Strcmp_f)mimecmp, s, &e)) && *e == ':') { pp.next = e + 1; if (arg(&pp, 1)) { if ((*set)(mp, p, pp.name.data, pp.name.size, mp->disc)) return 0; while (arg(&pp, 0)) if (pp.value.size && (p = strsearch(tab, num, siz, (Strcmp_f)mimecmp, pp.name.data, &e)) && (*set)(mp, p, pp.value.data, pp.value.size, mp->disc)) return 0; return 1; } } else if (strchr(s, ':')) return 1; } return !strncasecmp(s, "x-", 2); } /* * open a mime library handle */ Mime_t* mimeopen(Mimedisc_t* disc) { register Mime_t* mp; if (!(mp = newof(0, Mime_t, 1, 0))) return 0; mp->id = lib; mp->disc = disc; mp->dict.key = offsetof(Ent_t, name); mp->dict.comparf = order; mp->dict.freef = drop; if (!(mp->buf = sfstropen()) || !(mp->cap = dtopen(&mp->dict, Dtorder))) { mimeclose(mp); return 0; } return mp; } /* * close a mimeopen() handle */ int mimeclose(Mime_t* mp) { if (mp) { if (mp->buf) sfclose(mp->buf); if (mp->cap) dtclose(mp->cap); if (mp->freef) (*mp->freef)(mp); free(mp); } return 0; }