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
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(register 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(register Cap_t* cap)
108{
109	register 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	register Ent_t*	ent = (Ent_t*)object;
127	register 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, register char* s, unsigned long flags)
143{
144	register Ent_t*	ent;
145	register Cap_t*	cap;
146	register Att_t*	att;
147	register char*	t;
148	register char*	v;
149	register 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				register Cap_t*	dup;
246				register 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	register char*	s;
289	register char*	t;
290	register char*	e;
291	register 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	register Walk_t*	wp = (Walk_t*)context;
344	register Ent_t*		ent = (Ent_t*)object;
345	register Cap_t*		cap;
346	register 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	register char*	lp;
381	register char*	rp;
382	register char*	rb;
383	register char*	rv;
384	register int	rc;
385	register 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, register 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(register Parse_t* pp, int first)
473{
474	register char*	s;
475	register int	c;
476	register 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, register char* s, const char* name, const char* type, const char* opts)
551{
552	register char*	t;
553	register char*	v;
554	register int	c;
555	register int	e;
556	register 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
633char*
634mimeview(Mime_t* mp, const char* view, const char* name, const char* type, const char* opts)
635{
636	register Ent_t*	ent;
637	register Cap_t*	cap;
638	register Att_t*	att;
639	register 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
740int
741mimecmp(register const char* s, register const char* v, char** e)
742{
743	register 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/*
758 * parse mime headers in strsearch(tab,num,siz) from s
759 * return >0 if mime header consumed
760 */
761
762int
763mimehead(Mime_t* mp, void* tab, size_t num, size_t siz, register char* s)
764{
765	register void*	p;
766	char*		e;
767	Parse_t		pp;
768	Mimevalue_f	set;
769
770	set = mp->disc->valuef;
771	if (!strncasecmp(s, "original-", 9))
772		s += 9;
773	if (!strncasecmp(s, "content-", 8))
774	{
775		s += 8;
776		if ((p = strsearch(tab, num, siz, (Strcmp_f)mimecmp, s, &e)) && *e == ':')
777		{
778			pp.next = e + 1;
779			if (arg(&pp, 1))
780			{
781				if ((*set)(mp, p, pp.name.data, pp.name.size, mp->disc))
782					return 0;
783				while (arg(&pp, 0))
784					if (pp.value.size &&
785					    (p = strsearch(tab, num, siz, (Strcmp_f)mimecmp, pp.name.data, &e)) &&
786					    (*set)(mp, p, pp.value.data, pp.value.size, mp->disc))
787						return 0;
788				return 1;
789			}
790		}
791		else if (strchr(s, ':'))
792			return 1;
793	}
794	return !strncasecmp(s, "x-", 2);
795}
796
797/*
798 * open a mime library handle
799 */
800
801Mime_t*
802mimeopen(Mimedisc_t* disc)
803{
804	register Mime_t*	mp;
805
806	if (!(mp = newof(0, Mime_t, 1, 0)))
807		return 0;
808	mp->id = lib;
809	mp->disc = disc;
810	mp->dict.key = offsetof(Ent_t, name);
811	mp->dict.comparf = order;
812	mp->dict.freef = drop;
813	if (!(mp->buf = sfstropen()) || !(mp->cap = dtopen(&mp->dict, Dtoset)))
814	{
815		mimeclose(mp);
816		return 0;
817	}
818	return mp;
819}
820
821/*
822 * close a mimeopen() handle
823 */
824
825int
826mimeclose(Mime_t* mp)
827{
828	if (mp)
829	{
830		if (mp->buf)
831			sfclose(mp->buf);
832		if (mp->cap)
833			dtclose(mp->cap);
834		if (mp->freef)
835			(*mp->freef)(mp);
836		free(mp);
837	}
838	return 0;
839}
840