1 /***********************************************************************
3 * This software is part of the ast package *
4 * Copyright (c) 1985-2010 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
13 * Information and Software Systems Research *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
19 * Phong Vo <kpv@research.att.com> *
21 ***********************************************************************/
28 * mime/mailcap support library
31 static const char id
[] = "\n@(#)$Id: mime library (AT&T Research) 2002-10-29 $\0\n";
33 static const char lib
[] = "libast:mime";
83 * convert c to lower case
89 return isupper(c
) ? tolower(c
) : c
;
93 * Ent_t case insensitive comparf
97 order(Dt_t
* dt
, void* a
, void* b
, Dtdisc_t
* disc
)
99 return strcasecmp(a
, b
);
107 dropcap(register Cap_t
* cap
)
111 while (att
= cap
->att
.next
)
113 cap
->att
.next
= att
->next
;
124 drop(Dt_t
* dt
, void* object
, Dtdisc_t
* disc
)
126 register Ent_t
* ent
= (Ent_t
*)object
;
129 while (cap
= ent
->cap
)
131 ent
->cap
= cap
->next
;
138 * add mime type entry in s to mp
142 mimeset(Mime_t
* mp
, register char* s
, unsigned long flags
)
154 for (; isspace(*s
); s
++);
158 for (v
= s
; *v
&& *v
!= ';'; v
++)
159 if (isspace(*v
) || *v
== '/' && *(v
+ 1) == '*')
166 for (; isspace(*v
); v
++);
169 for (t
= v
; *t
&& !isspace(*t
) && *t
!= '='; t
++);
170 for (k
= t
; isspace(*t
); t
++);
171 if (!*t
|| *t
== '=' || *t
== ';')
174 while (isspace(*++t
));
208 else if (*t
== ';' && !q
)
213 for (; t
> v
&& isspace(*(t
- 1)); t
--);
214 if (t
<= v
&& (!cap
|| !k
))
218 if (!(cap
= newof(0, Cap_t
, 1, strlen(v
) + 1)))
223 tta
->name
= "default";
224 x
= strcopy(tta
->value
= cap
->data
, v
) + 1;
230 if (!(att
= newof(0, Att_t
, 1, 0)))
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
;
240 ent
= (Ent_t
*)dtmatch(mp
->cap
, s
);
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
))
251 if (flags
& MIME_REPLACE
)
257 if (!(cap
->next
= dup
->next
))
264 ent
->pac
= ent
->pac
->next
= cap
;
266 else if (!(ent
= newof(0, Ent_t
, 1, strlen(s
) + 1)))
270 strcpy(ent
->name
, s
);
271 ent
->cap
= ent
->pac
= cap
;
272 dtinsert(mp
->cap
, ent
);
275 else if (ent
&& (flags
& MIME_REPLACE
))
276 dtdelete(mp
->cap
, ent
);
282 * load mime type files into mp
286 mimeload(Mime_t
* mp
, const char* file
, unsigned long flags
)
294 if (!(s
= (char*)file
))
297 if (!(s
= getenv(MIME_FILES_ENV
)))
302 if (!(flags
& MIME_LIST
))
304 else if (e
= strchr(s
, ':'))
307 * ok, so ~ won't work for the last list element
308 * we do it for MIME_FILES_ENV anyway
311 if ((strneq(s
, "~/", n
= 2) || strneq(s
, "$HOME/", n
= 6) || strneq(s
, "${HOME}/", n
= 8)) && (t
= getenv("HOME")))
313 sfputr(mp
->buf
, t
, -1);
316 sfwrite(mp
->buf
, s
, e
- s
);
317 if (!(s
= sfstruse(mp
->buf
)))
320 if (fp
= tokline(s
, SF_READ
, NiL
))
322 while (t
= sfgetr(fp
, '\n', 1))
323 if (mimeset(mp
, t
, flags
))
327 else if (!(flags
& MIME_LIST
))
341 list(Dt_t
* dt
, void* object
, void* context
)
343 register Walk_t
* wp
= (Walk_t
*)context
;
344 register Ent_t
* ent
= (Ent_t
*)object
;
348 if (!wp
->pattern
|| !strncasecmp(ent
->name
, wp
->pattern
, wp
->prefix
) && (!ent
->name
[wp
->prefix
] || ent
->name
[wp
->prefix
] == '/'))
351 for (cap
= ent
->cap
; cap
; cap
= cap
->next
)
353 sfprintf(wp
->fp
, "%s", ent
->name
);
354 for (att
= &cap
->att
; att
; att
= att
->next
)
356 sfprintf(wp
->fp
, "\n\t");
357 if (att
!= &cap
->att
)
359 sfprintf(wp
->fp
, "%s", att
->name
);
361 sfprintf(wp
->fp
, " = ");
363 sfputr(wp
->fp
, att
->value
, -1);
365 sfprintf(wp
->fp
, "\n");
372 * find entry matching type
373 * if exact match fails then left and right x- and right version number
374 * permutations are attempted
378 find(Mime_t
* mp
, const char* type
)
390 static const char* prefix
[] = { "", "", "x-", "x-", "" };
392 if ((ent
= (Ent_t
*)dtmatch(mp
->cap
, type
)) ||
393 !(rp
= strchr(lp
= (char*)type
, '/')) ||
394 strlen(lp
) >= sizeof(buf
))
397 rp
= buf
+ (rp
- lp
);
399 if (*rp
== 'x' && *(rp
+ 1) == '-')
402 if (*lp
== 'x' && *(lp
+ 1) == '-')
405 for (rv
= rp
+ strlen(rp
); rv
> rp
&& (isdigit(*(rv
- 1)) || *(rv
- 1) == '.'); rv
--);
412 for (i
= 0; i
< elementsof(prefix
) - 1; i
++)
414 sfprintf(mp
->buf
, "%s%s/%s%s", prefix
[i
], lp
, prefix
[i
+ 1], rp
);
415 if (!(s
= sfstruse(mp
->buf
)))
417 if (ent
= (Ent_t
*)dtmatch(mp
->cap
, s
))
422 sfprintf(mp
->buf
, "%s%s/%s%s", prefix
[i
], lp
, prefix
[i
+ 1], rp
);
423 if (!(s
= sfstruse(mp
->buf
)))
425 if (ent
= (Ent_t
*)dtmatch(mp
->cap
, s
))
430 while (*rp
&& *rp
++ != '-');
432 while (*lp
&& *lp
++ != '-');
434 return (Ent_t
*)dtmatch(mp
->cap
, buf
);
438 * list mime <type,data> for pat on fp
442 mimelist(Mime_t
* mp
, Sfio_t
* fp
, register const char* pattern
)
450 if (ws
.pattern
= pattern
)
452 while (*pattern
&& *pattern
++ != '/');
453 if (!*pattern
|| *pattern
== '*' && !*(pattern
+ 1))
454 ws
.prefix
= pattern
- ws
.pattern
;
455 else if (ent
= find(mp
, ws
.pattern
))
458 list(mp
->cap
, ent
, &ws
);
462 dtwalk(mp
->cap
, list
, &ws
);
468 * 0 returned if no more args
472 arg(register Parse_t
* pp
, int first
)
479 for (s
= pp
->next
; isspace(*s
) && *s
!= '\n'; s
++);
480 if (!*s
|| *s
== '\n')
489 while ((c
= *s
++) && c
!= ';' && c
!= '\n')
502 else if (!x
&& pp
->name
.data
== (s
- 1))
509 if (!(c
= *s
++) || c
== '\n')
515 if (first
< 0 || x
> 0)
521 else if (c
== '=' && !first
)
524 pp
->name
.size
= s
- pp
->name
.data
- 1;
527 else if (first
>= 0 && isspace(c
))
530 pp
->next
= s
- (c
!= ';');
531 if (first
>= 0 || !q
)
532 for (s
--; s
> pp
->name
.data
&& isspace(*(s
- 1)); s
--);
534 pp
->value
.size
= s
- pp
->value
.data
- (q
&& first
< 0);
538 pp
->name
.size
= s
- pp
->name
.data
- (q
&& first
< 0);
540 if (first
>= 0 && pp
->name
.size
> 0 && pp
->name
.data
[pp
->name
.size
- 1] == ':')
542 return pp
->name
.size
> 0;
546 * low level for mimeview()
550 expand(Mime_t
* mp
, register char* s
, const char* name
, const char* type
, const char* opts
)
556 mp
->disc
->flags
|= MIME_PIPE
;
568 sfputr(mp
->buf
, (char*)name
, -1);
569 mp
->disc
->flags
&= ~MIME_PIPE
;
572 sfputr(mp
->buf
, (char*)type
, -1);
575 for (t
= s
; *s
&& *s
!= '}'; s
++);
576 if (*s
&& (c
= s
++ - t
) && (pp
.next
= (char*)opts
))
578 if (pp
.name
.size
== c
&& !strncasecmp(pp
.name
.data
, t
, c
))
581 sfwrite(mp
->buf
, pp
.value
.data
, pp
.value
.size
);
593 return sfstruse(mp
->buf
);
597 * return expanded command/path/value for <view,name,type,opts>
598 * return value valid until next mime*() call
602 mimeview(Mime_t
* mp
, const char* view
, const char* name
, const char* type
, const char* opts
)
610 if (ent
= find(mp
, type
))
613 if (!view
|| strcasecmp(view
, "test"))
614 while (s
= cap
->test
)
616 if (s
= expand(mp
, s
, name
, type
, opts
))
624 * try to do a few common cases here
625 * mailcap consistency is a winning
632 if ((c
= *a1
.name
.data
== '!') && --a1
.name
.size
<= 0 && !arg(&a1
, -1))
634 if (a1
.name
.size
== 6 && strneq(a1
.name
.data
, "strcmp", 6) || a1
.name
.size
== 10 && strneq(a1
.name
.data
, "strcasecmp", 10))
642 if (a2
.name
.size
!= a3
.name
.size
)
644 else c
^= (a1
.name
.size
== 6 ? strncmp
: strncasecmp
)(a2
.name
.data
, a3
.name
.data
, a2
.name
.size
) == 0;
649 else if (a1
.name
.size
== 4 && strneq(a1
.name
.data
, "test", 4))
654 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)))
659 if (*a3
.name
.data
== '`' && *(a3
.name
.data
+ a3
.name
.size
- 1) == '`')
665 if (*a1
.name
.data
== '`' && *(a1
.name
.data
+ a1
.name
.size
- 1) == '`')
667 a1
.next
= a1
.name
.data
+ 1;
668 if (!arg(&a1
, -1) || a1
.name
.size
!= 4 || !strneq(a1
.name
.data
, "echo", 4) || !arg(&a1
, -1))
671 if (!arg(&a4
, 1) || a4
.name
.size
< 21 || !strneq(a4
.name
.data
, "| tr '[A-Z]' '[a-z]'`", 21))
676 c
= *a2
.name
.data
== '!';
677 if (a1
.name
.size
!= a3
.name
.size
)
679 else c
^= (a4
.name
.size
? strncasecmp
: strncmp
)(a1
.name
.data
, a3
.name
.data
, a1
.name
.size
) == 0;
690 if (!(cap
= cap
->next
))
694 if (view
&& *view
&& !streq(view
, "-"))
695 while (strcasecmp(view
, att
->name
))
696 if (!(att
= att
->next
))
698 return expand(mp
, att
->value
, name
, type
, opts
);
704 * lower case identifier prefix strcmp
705 * if e!=0 then it will point to the next char after the match
709 mimecmp(register const char* s
, register const char* v
, char** e
)
713 while (isalnum(*v
) || *v
== *s
&& (*v
== '_' || *v
== '-' || *v
== '/'))
714 if (n
= lower(*s
++) - lower(*v
++))
716 if (!isalnum(*s
) && *s
!= '_' && *s
!= '-')
722 return lower(*s
) - lower(*v
);
726 * parse mime headers in strsearch(tab,num,siz) from s
727 * return >0 if mime header consumed
731 mimehead(Mime_t
* mp
, void* tab
, size_t num
, size_t siz
, register char* s
)
738 set
= mp
->disc
->valuef
;
739 if (!strncasecmp(s
, "original-", 9))
741 if (!strncasecmp(s
, "content-", 8))
744 if ((p
= strsearch(tab
, num
, siz
, (Strcmp_f
)mimecmp
, s
, &e
)) && *e
== ':')
749 if ((*set
)(mp
, p
, pp
.name
.data
, pp
.name
.size
, mp
->disc
))
753 (p
= strsearch(tab
, num
, siz
, (Strcmp_f
)mimecmp
, pp
.name
.data
, &e
)) &&
754 (*set
)(mp
, p
, pp
.value
.data
, pp
.value
.size
, mp
->disc
))
759 else if (strchr(s
, ':'))
762 return !strncasecmp(s
, "x-", 2);
766 * open a mime library handle
770 mimeopen(Mimedisc_t
* disc
)
774 if (!(mp
= newof(0, Mime_t
, 1, 0)))
778 mp
->dict
.key
= offsetof(Ent_t
, name
);
779 mp
->dict
.comparf
= order
;
780 mp
->dict
.freef
= drop
;
781 if (!(mp
->buf
= sfstropen()) || !(mp
->cap
= dtopen(&mp
->dict
, Dtorder
)))
790 * close a mimeopen() handle
794 mimeclose(Mime_t
* mp
)