1/***********************************************************************
2*                                                                      *
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                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
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
31static const char id[] = "\n@(#)$Id: mime library (AT&T Research) 2002-10-29 $\0\n";
32
33static const char lib[] = "libast:mime";
34
35#include "mimelib.h"
36
37typedef struct Att_s
38{
39	struct Att_s*	next;
40	char*		name;
41	char*		value;
42} Att_t;
43
44typedef 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
53typedef struct
54{
55	Dtlink_t	link;
56	Cap_t*		cap;
57	Cap_t*		pac;
58	char		name[1];
59} Ent_t;
60
61typedef struct
62{
63	char*		data;
64	int		size;
65} String_t;
66
67typedef struct
68{
69	char*		next;
70	String_t	name;
71	String_t	value;
72} Parse_t;
73
74typedef 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
86static int
87lower(int c)
88{
89	return isupper(c) ? tolower(c) : c;
90}
91
92/*
93 * Ent_t case insensitive comparf
94 */
95
96static int
97order(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
106static void
107dropcap(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
123static void
124drop(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
141int
142mimeset(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
285int
286mimeload(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
340static int
341list(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
377static Ent_t*
378find(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
441int
442mimelist(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
471static int
472arg(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
549static char*
550expand(Mime_t* mp, char* s, const char* name, const char* type, const char* opts)
551{
552	char*	t;
553	int	c;
554	Parse_t		pp;
555
556	mp->disc->flags |= MIME_PIPE;
557	for (;;)
558	{
559		switch (c = *s++)
560		{
561		case 0:
562		case '\n':
563			break;
564		case '%':
565			switch (c = *s++)
566			{
567			case 's':
568				sfputr(mp->buf, (char*)name, -1);
569				mp->disc->flags &= ~MIME_PIPE;
570				continue;
571			case 't':
572				sfputr(mp->buf, (char*)type, -1);
573				continue;
574			case '{':
575				for (t = s; *s && *s != '}'; s++);
576				if (*s && (c = s++ - t) && (pp.next = (char*)opts))
577					while (arg(&pp, 0))
578						if (pp.name.size == c && !strncasecmp(pp.name.data, t, c))
579						{
580							if (pp.value.size)
581								sfwrite(mp->buf, pp.value.data, pp.value.size);
582							break;
583						}
584				continue;
585			}
586			/*FALLTHROUGH*/
587		default:
588			sfputc(mp->buf, c);
589			continue;
590		}
591		break;
592	}
593	return sfstruse(mp->buf);
594}
595
596/*
597 * return expanded command/path/value for <view,name,type,opts>
598 * return value valid until next mime*() call
599 */
600
601char*
602mimeview(Mime_t* mp, const char* view, const char* name, const char* type, const char* opts)
603{
604	Ent_t*	ent;
605	Cap_t*	cap;
606	Att_t*	att;
607	char*	s;
608	int		c;
609
610	if (ent = find(mp, type))
611	{
612		cap = ent->cap;
613		if (!view || strcasecmp(view, "test"))
614			while (s = cap->test)
615			{
616				if (s = expand(mp, s, name, type, opts))
617				{
618					Parse_t	a1;
619					Parse_t	a2;
620					Parse_t	a3;
621					Parse_t	a4;
622
623					/*
624					 * try to do a few common cases here
625					 * mailcap consistency is a winning
626					 * strategy
627					 */
628
629					a1.next = s;
630					if (arg(&a1, -1))
631					{
632						if ((c = *a1.name.data == '!') && --a1.name.size <= 0 && !arg(&a1, -1))
633							goto lose;
634						if (a1.name.size == 6 && strneq(a1.name.data, "strcmp", 6) || a1.name.size == 10 && strneq(a1.name.data, "strcasecmp", 10))
635						{
636							a2.next = a1.next;
637							if (!arg(&a2, -1))
638								goto lose;
639							a3.next = a2.next;
640							if (!arg(&a3, -1))
641								goto lose;
642							if (a2.name.size != a3.name.size)
643								c ^= 0;
644							else c ^= (a1.name.size == 6 ? strncmp : strncasecmp)(a2.name.data, a3.name.data, a2.name.size) == 0;
645							if (c)
646								break;
647							goto skip;
648						}
649						else if (a1.name.size == 4 && strneq(a1.name.data, "test", 4))
650						{
651							if (!arg(&a1, -1))
652								goto lose;
653							a2.next = a1.next;
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)))
655								goto lose;
656							a3.next = a2.next;
657							if (!arg(&a3, -1))
658								goto lose;
659							if (*a3.name.data == '`' && *(a3.name.data + a3.name.size - 1) == '`')
660							{
661								a4 = a3;
662								a3 = a1;
663								a1 = a4;
664							}
665							if (*a1.name.data == '`' && *(a1.name.data + a1.name.size - 1) == '`')
666							{
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))
669									goto lose;
670								a4.next = a1.next;
671								if (!arg(&a4, 1) || a4.name.size < 21 || !strneq(a4.name.data, "| tr '[A-Z]' '[a-z]'`", 21))
672									goto lose;
673							}
674							else
675								a4.name.size = 0;
676							c = *a2.name.data == '!';
677							if (a1.name.size != a3.name.size)
678								c ^= 0;
679							else c ^= (a4.name.size ? strncasecmp : strncmp)(a1.name.data, a3.name.data, a1.name.size) == 0;
680							if (c)
681								break;
682							goto skip;
683						}
684					}
685				lose:
686					if (!system(s))
687						break;
688				}
689			skip:
690				if (!(cap = cap->next))
691					return 0;
692			}
693		att = &cap->att;
694		if (view && *view && !streq(view, "-"))
695			while (strcasecmp(view, att->name))
696				if (!(att = att->next))
697					return 0;
698		return expand(mp, att->value, name, type, opts);
699	}
700	return 0;
701}
702
703/*
704 * lower case identifier prefix strcmp
705 * if e!=0 then it will point to the next char after the match
706 */
707
708int
709mimecmp(const char* s, const char* v, char** e)
710{
711	int	n;
712
713	while (isalnum(*v) || *v == *s && (*v == '_' || *v == '-' || *v == '/'))
714		if (n = lower(*s++) - lower(*v++))
715			return n;
716	if (!isalnum(*s) && *s != '_' && *s != '-')
717	{
718		if (e)
719			*e = (char*)s;
720		return 0;
721	}
722	return lower(*s) - lower(*v);
723}
724
725static int
726_mimecmp(const char* s, const char* v)
727{
728	return (mimecmp(s, v, NULL));
729}
730
731/*
732 * parse mime headers in strsearch(tab,num,siz) from s
733 * return >0 if mime header consumed
734 */
735
736int
737mimehead(Mime_t* mp, void* tab, size_t num, size_t siz, char* s)
738{
739	void*	p;
740	char*		e;
741	Parse_t		pp;
742	Mimevalue_f	set;
743
744	set = mp->disc->valuef;
745	if (!strncasecmp(s, "original-", 9))
746		s += 9;
747	if (!strncasecmp(s, "content-", 8))
748	{
749		s += 8;
750		if ((p = strsearch(tab, num, siz, _mimecmp, s, &e)) && *e == ':')
751		{
752			pp.next = e + 1;
753			if (arg(&pp, 1))
754			{
755				if ((*set)(mp, p, pp.name.data, pp.name.size, mp->disc))
756					return 0;
757				while (arg(&pp, 0))
758					if (pp.value.size &&
759					    (p = strsearch(tab, num, siz, _mimecmp, pp.name.data, &e)) &&
760					    (*set)(mp, p, pp.value.data, pp.value.size, mp->disc))
761						return 0;
762				return 1;
763			}
764		}
765		else if (strchr(s, ':'))
766			return 1;
767	}
768	return !strncasecmp(s, "x-", 2);
769}
770
771/*
772 * open a mime library handle
773 */
774
775Mime_t*
776mimeopen(Mimedisc_t* disc)
777{
778	Mime_t*	mp;
779
780	if (!(mp = newof(0, Mime_t, 1, 0)))
781		return 0;
782	mp->id = lib;
783	mp->disc = disc;
784	mp->dict.key = offsetof(Ent_t, name);
785	mp->dict.comparf = order;
786	mp->dict.freef = drop;
787	if (!(mp->buf = sfstropen()) || !(mp->cap = dtopen(&mp->dict, Dtorder)))
788	{
789		mimeclose(mp);
790		return 0;
791	}
792	return mp;
793}
794
795/*
796 * close a mimeopen() handle
797 */
798
799int
800mimeclose(Mime_t* mp)
801{
802	if (mp)
803	{
804		if (mp->buf)
805			sfclose(mp->buf);
806		if (mp->cap)
807			dtclose(mp->cap);
808		if (mp->freef)
809			(*mp->freef)(mp);
810		free(mp);
811	}
812	return 0;
813}
814