xref: /plan9/sys/src/cmd/ip/imap4d/msg.c (revision 223a736ebd2849388a6a0145cd1e22a96bd28460)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <libsec.h>
5 #include "imap4d.h"
6 
7 static void	body64(int in, int out);
8 static void	cleanupHeader(Header *h);
9 static char	*domBang(char *s);
10 static void	freeMAddr(MAddr *a);
11 static void	freeMimeHdr(MimeHdr *mh);
12 static char	*headAddrSpec(char *e, char *w);
13 static MAddr	*headAddresses(void);
14 static MAddr	*headAddress(void);
15 static char	*headAtom(char *disallowed);
16 static int	headChar(int eat);
17 static char	*headDomain(char *e);
18 static MAddr	*headMAddr(MAddr *old);
19 static char	*headPhrase(char *e, char *w);
20 static char	*headQuoted(int start, int stop);
21 static char	*headSkipWhite(int);
22 static void	headSkip(void);
23 static char	*headSubDomain(void);
24 static char	*headText(void);
25 static void	headToEnd(void);
26 static char	*headWord(void);
27 static void	mimeDescription(Header *h);
28 static void	mimeDisposition(Header *h);
29 static void	mimeEncoding(Header *h);
30 static void	mimeId(Header *h);
31 static void	mimeLanguage(Header *h);
32 static void	mimeMd5(Header *h);
33 static MimeHdr	*mimeParams(void);
34 static void	mimeType(Header *h);
35 static MimeHdr	*mkMimeHdr(char *s, char *t, MimeHdr *next);
36 static void	msgAddDate(Msg *m);
37 static void	msgAddHead(Msg *m, char *head, char *body);
38 static int	msgBodySize(Msg *m);
39 static int	msgHeader(Msg *m, Header *h, char *file);
40 static long	msgReadFile(Msg *m, char *file, char **ss);
41 static int	msgUnix(Msg *m, int top);
42 static void	stripQuotes(char *q);
43 static MAddr	*unixFrom(char *s);
44 
45 /*
46  * stop list for header fields
47  */
48 static char	*headFieldStop = ":";
49 static char	*mimeTokenStop = "()<>@,;:\\\"/[]?=";
50 static char	*headAtomStop = "()<>@,;:\\\".[]";
51 static uchar	*headStr;
52 static uchar	*lastWhite;
53 
54 long
55 selectFields(char *dst, long n, char *hdr, SList *fields, int matches)
56 {
57 	SList *f;
58 	uchar *start;
59 	char *s;
60 	long m, nf;
61 
62 	headStr = (uchar*)hdr;
63 	m = 0;
64 	for(;;){
65 		start = headStr;
66 		s = headAtom(headFieldStop);
67 		if(s == nil)
68 			break;
69 		headSkip();
70 		for(f = fields; f != nil; f = f->next){
71 			if(cistrcmp(s, f->s) == !matches){
72 				nf = headStr - start;
73 				if(m + nf > n)
74 					return 0;
75 				memmove(&dst[m], start, nf);
76 				m += nf;
77 			}
78 		}
79 		free(s);
80 	}
81 	if(m + 3 > n)
82 		return 0;
83 	dst[m++] = '\r';
84 	dst[m++] = '\n';
85 	dst[m] = '\0';
86 	return m;
87 }
88 
89 void
90 freeMsg(Msg *m)
91 {
92 	Msg *k, *last;
93 
94 	free(m->iBuf);
95 	freeMAddr(m->to);
96 	if(m->replyTo != m->from)
97 		freeMAddr(m->replyTo);
98 	if(m->sender != m->from)
99 		freeMAddr(m->sender);
100 	if(m->from != m->unixFrom)
101 		freeMAddr(m->from);
102 	freeMAddr(m->unixFrom);
103 	freeMAddr(m->cc);
104 	freeMAddr(m->bcc);
105 	free(m->unixDate);
106 	cleanupHeader(&m->head);
107 	cleanupHeader(&m->mime);
108 	for(k = m->kids; k != nil; ){
109 		last = k;
110 		k = k->next;
111 		freeMsg(last);
112 	}
113 	free(m);
114 }
115 
116 ulong
117 msgSize(Msg *m)
118 {
119 	return m->head.size + m->size;
120 }
121 
122 int
123 infoIsNil(char *s)
124 {
125 	return s == nil || s[0] == '\0';
126 }
127 
128 char*
129 maddrStr(MAddr *a)
130 {
131 	char *host, *addr;
132 	int n;
133 
134 	host = a->host;
135 	if(host == nil)
136 		host = "";
137 	n = strlen(a->box) + strlen(host) + 2;
138 	if(a->personal != nil)
139 		n += strlen(a->personal) + 3;
140 	addr = emalloc(n);
141 	if(a->personal != nil)
142 		snprint(addr, n, "%s <%s@%s>", a->personal, a->box, host);
143 	else
144 		snprint(addr, n, "%s@%s", a->box, host);
145 	return addr;
146 }
147 
148 /*
149  * return actual name of f in m's fs directory
150  * this is special cased when opening m/rawbody, m/mimeheader, or m/rawheader,
151  * if the message was corrupted.  in that case,
152  * a temporary file is made to hold the base64 encoding of m/raw.
153  */
154 int
155 msgFile(Msg *m, char *f)
156 {
157 	Tm tm;
158 	char buf[64];
159 	int fd, fd1;
160 
161 	if(!m->bogus
162 	|| strcmp(f, "rawbody") != 0 && strcmp(f, "rawheader") != 0 && strcmp(f, "mimeheader") != 0){
163 		if(strlen(f) > NAMELEN)
164 			bye("internal error: msgFile name too long");
165 		strcpy(m->efs, f);
166 		return cdOpen(m->fsDir, m->fs, OREAD);
167 	}
168 	fd = imapTmp();
169 	if(fd < 0)
170 		return -1;
171 	if(strcmp(f, "mimeheader") == 0)
172 		return fd;
173 	if(strcmp(f, "rawheader") == 0){
174 		date2tm(&tm, m->unixDate);
175 		rfc822date(buf, sizeof(buf), &tm);
176 		fprint(fd, "Date: %s\r\n", buf);
177 		fprint(fd, "From: imap4 daemon <%s@%s>\r\n", username, site);
178 		fprint(fd, "Subject: This message was illegal or corrupted; the bytes follow\r\n");
179 		fprint(fd, "Content-Type: application/octet-stream\r\n");
180 		fprint(fd, "Content-Transfer-Encoding: base64\r\n");
181 	}else{
182 		fd1 = msgFile(m, "raw");
183 		if(fd1 < 0){
184 			close(fd);
185 			return -1;
186 		}
187 		body64(fd1, fd);
188 		close(fd1);
189 	}
190 	seek(fd, 0, 0);
191 	return fd;
192 }
193 
194 int
195 msgIsMulti(Header *h)
196 {
197 	return h->type != nil && cistrcmp("multipart", h->type->s) == 0;
198 }
199 
200 int
201 msgIsRfc822(Header *h)
202 {
203 	return h->type != nil && cistrcmp("message", h->type->s) == 0 && cistrcmp("rfc822", h->type->t) == 0;
204 }
205 
206 /*
207  * check if a message has been deleted by someone else
208  */
209 void
210 msgDead(Msg *m)
211 {
212 	Dir d;
213 
214 	if(m->expunged)
215 		return;
216 	*m->efs = '\0';
217 	if(cdDirstat(m->fsDir, m->fs, &d) < 0)
218 		m->expunged = 1;
219 }
220 
221 /*
222  * make sure the message has valid associated info
223  * used for ISubject, IDigest, IInReplyTo, IMessageId.
224  */
225 int
226 msgInfo(Msg *m)
227 {
228 	char *s;
229 	int i;
230 
231 	if(m->info[0] != nil)
232 		return 1;
233 
234 	i = msgReadFile(m, "info", &m->iBuf);
235 	if(i < 0)
236 		return 0;
237 
238 	s = m->iBuf;
239 	for(i = 0; i < IMax; i++){
240 		m->info[i] = s;
241 		s = strchr(s, '\n');
242 		if(s == nil)
243 			break;
244 		*s++ = '\0';
245 	}
246 	for(; i < IMax; i++)
247 		m->info[i] = nil;
248 
249 	for(i = 0; i < IMax; i++)
250 		if(infoIsNil(m->info[i]))
251 			m->info[i] = nil;
252 
253 	return 1;
254 }
255 
256 /*
257  * make sure the message has valid mime structure
258  * and sub-messages
259  */
260 int
261 msgStruct(Msg *m, int top)
262 {
263 	Msg *k, head, *last;
264 	Dir d[NDirs];
265 	char *s;
266 	ulong max, id;
267 	int i, nd, fd, ns;
268 
269 	if(m->kids != nil)
270 		return 1;
271 
272 	if(m->expunged
273 	|| !msgInfo(m)
274 	|| !msgUnix(m, top)
275 	|| !msgBodySize(m)
276 	|| !msgHeader(m, &m->mime, "mimeheader")
277 	|| (top || msgIsRfc822(&m->mime)) && !msgHeader(m, &m->head, "rawheader")){
278 		if(top && m->bogus == 1){
279 			m->bogus = 2;
280 			return msgStruct(m, top);
281 		}
282 		msgDead(m);
283 		return 0;
284 	}
285 
286 	/*
287 	 * if a message has no kids, it has a kid which is just the body of the real message
288 	 */
289 	if(!msgIsMulti(&m->head) && !msgIsRfc822(&m->head) && !msgIsRfc822(&m->mime)){
290 		k = MKZ(Msg);
291 		k->id = 1;
292 		k->fsDir = m->fsDir;
293 		ns = m->efs - m->fs;
294 		k->fs = emalloc(ns + NAMELEN+1);
295 		memmove(k->fs, m->fs, ns);
296 		k->efs = k->fs + ns;
297 		*k->efs = '\0';
298 		k->size = m->size;
299 		m->kids = k;
300 		return 1;
301 	}
302 
303 	/*
304 	 * read in all child messages messages
305 	 */
306 	fd = msgFile(m, "");
307 	if(fd < 0){
308 		msgDead(m);
309 		return 0;
310 	}
311 
312 	max = 0;
313 	head.next = nil;
314 	last = &head;
315 	while((nd = dirread(fd, d, sizeof(Dir) * NDirs)) >= sizeof(Dir)){
316 		nd /= sizeof(Dir);
317 		for(i = 0; i < nd; i++){
318 			s = d[i].name;
319 			id = strtol(s, &s, 10);
320 			if(id <= max || *s != '\0'
321 			|| (d[i].mode & CHDIR) != CHDIR)
322 				continue;
323 
324 			max = id;
325 
326 			k = MKZ(Msg);
327 			k->id = id;
328 			k->fsDir = m->fsDir;
329 			ns = strlen(m->fs) + 2*(NAMELEN+1);
330 			k->fs = emalloc(ns);
331 			k->efs = seprint(k->fs, k->fs + ns, "%s%lud/", m->fs, id);
332 			k->prev = last;
333 			k->size = ~0UL;
334 			k->lines = ~0UL;
335 			last->next = k;
336 			last = k;
337 		}
338 	}
339 	close(fd);
340 	m->kids = head.next;
341 
342 	/*
343 	 * if kids fail, just wack them
344 	 */
345 	top = top && msgIsRfc822(&m->head);
346 	for(k = m->kids; k != nil; k = k->next){
347 		if(!msgStruct(k, top)){
348 			for(k = m->kids; k != nil; ){
349 				last = k;
350 				k = k->next;
351 				freeMsg(last);
352 			}
353 			m->kids = nil;
354 			break;
355 		}
356 	}
357 	return 1;
358 }
359 
360 static long
361 msgReadFile(Msg *m, char *file, char **ss)
362 {
363 	Dir d;
364 	char *s, buf[BufSize];
365 	long n, nn;
366 	int fd;
367 
368 	fd = msgFile(m, file);
369 	if(fd < 0){
370 		msgDead(m);
371 		return 0;
372 	}
373 
374 	n = read(fd, buf, BufSize);
375 	if(n < BufSize){
376 		close(fd);
377 		if(n < 0){
378 			*ss = nil;
379 			return -1;
380 		}
381 		s = emalloc(n + 1);
382 		memmove(s, buf, n);
383 		s[n] = '\0';
384 		*ss = s;
385 		return n;
386 	}
387 
388 	if(dirfstat(fd, &d) < 0){
389 		close(fd);
390 		return 0;
391 	}
392 	nn = d.length;
393 	s = emalloc(nn + 1);
394 	memmove(s, buf, n);
395 	if(nn > n)
396 		nn = read(fd, s+n, nn-n) + n;
397 	close(fd);
398 	if(nn != d.length){
399 		free(s);
400 		return 0;
401 	}
402 	s[nn] = '\0';
403 	*ss = s;
404 	return nn;
405 }
406 
407 static void
408 freeMAddr(MAddr *a)
409 {
410 	MAddr *p;
411 
412 	while(a != nil){
413 		p = a;
414 		a = a->next;
415 		free(p->personal);
416 		free(p->box);
417 		free(p->host);
418 		free(p);
419 	}
420 }
421 
422 /*
423  * the message is corrupted or illegal.
424  * reset message fields.  msgStruct will reparse the message,
425  * relying on msgFile to make up corrected body parts.
426  */
427 static int
428 msgBogus(Msg *m)
429 {
430 	if(!m->bogus)
431 		m->bogus = 1;
432 	m->lines = ~0;
433 	free(m->head.buf);
434 	free(m->mime.buf);
435 	memset(&m->head, 0, sizeof(Header));
436 	memset(&m->mime, 0, sizeof(Header));
437 	return 0;
438 }
439 
440 //
441 //  stolen from upas/marshall; base64 encodes from one fd to another.
442 //
443 //  the size of buf is very important to enc64.  Anything other than
444 //  a multiple of 3 will cause enc64 to output a termination sequence.
445 //  To ensure that a full buf corresponds to a multiple of complete lines,
446 //  we make buf a multiple of 3*18 since that's how many enc64 sticks on
447 //  a single line.  This avoids short lines in the output which is pleasing
448 //  but not necessary.
449 //
450 static int
451 enc64x18(char *out, int lim, uchar *in, int n)
452 {
453 	int m, mm, nn;
454 
455 	nn = 0;
456 	for(; n > 0; n -= m){
457 		m = 18 * 3;
458 		if(m > n)
459 			m = n;
460 		mm = enc64(out, lim - nn, in, m);
461 		in += m;
462 		out += mm;
463 		*out++ = '\r';
464 		*out++ = '\n';
465 		nn += mm + 2;
466 	}
467 	return nn;
468 }
469 
470 static void
471 body64(int in, int out)
472 {
473 	uchar buf[3*18*54];
474 	char obuf[3*18*54*2];
475 	int m, n;
476 
477 	for(;;){
478 		n = read(in, buf, sizeof(buf));
479 		if(n < 0)
480 			return;
481 		if(n == 0)
482 			break;
483 		m = enc64x18(obuf, sizeof(obuf), buf, n);
484 		if(write(out, obuf, m) < 0)
485 			return;
486 	}
487 }
488 
489 /*
490  * read in the message body to count \n without a preceeding \r
491  */
492 static int
493 msgBodySize(Msg *m)
494 {
495 	Dir d;
496 	char buf[BufSize + 2], *s, *se;
497 	ulong size, lines, bad;
498 	int n, fd, c;
499 
500 	if(m->lines != ~0UL)
501 		return 1;
502 	fd = msgFile(m, "rawbody");
503 	if(fd < 0)
504 		return 0;
505 	if(dirfstat(fd, &d) < 0){
506 		close(fd);
507 		return 0;
508 	}
509 
510 	size = 0;
511 	lines = 0;
512 	bad = 0;
513 	buf[0] = ' ';
514 	for(;;){
515 		n = read(fd, &buf[1], BufSize);
516 		if(n <= 0)
517 			break;
518 		size += n;
519 		se = &buf[n + 1];
520 		for(s = &buf[1]; s < se; s++){
521 			c = *s;
522 			if(c == '\0'){
523 				close(fd);
524 				return msgBogus(m);
525 			}
526 			if(c != '\n')
527 				continue;
528 			if(s[-1] != '\r')
529 				bad++;
530 			lines++;
531 		}
532 		buf[0] = buf[n];
533 	}
534 	if(size != d.length)
535 		bye("bad length reading rawbody");
536 	size += bad;
537 	m->size = size;
538 	m->lines = lines;
539 	close(fd);
540 	return 1;
541 }
542 
543 /*
544  * retrieve information from the unixheader file
545  */
546 static int
547 msgUnix(Msg *m, int top)
548 {
549 	Tm tm;
550 	char *s, *ss;
551 
552 	if(m->unixDate != nil)
553 		return 1;
554 
555 	if(!top){
556 		m->unixDate = estrdup("");
557 		m->unixFrom = unixFrom(nil);
558 		return 1;
559 	}
560 
561 	if(msgReadFile(m, "unixheader", &ss) < 0)
562 		return 0;
563 	s = ss;
564 	s = strchr(s, ' ');
565 	if(s == nil){
566 		free(ss);
567 		return 0;
568 	}
569 	s++;
570 	m->unixFrom = unixFrom(s);
571 	s = (char*)headStr;
572 	if(date2tm(&tm, s) == nil)
573 		s = m->info[IUnixDate];
574 	m->unixDate = estrdup(s);
575 	free(ss);
576 	return 1;
577 }
578 
579 /*
580  * parse the address in the unix header
581  * last line of defence, so must return something
582  */
583 static MAddr *
584 unixFrom(char *s)
585 {
586 	MAddr *a;
587 	char *e, *t;
588 
589 	if(s == nil)
590 		s = "UnknownUser";
591 	headStr = (uchar*)s;
592 	t = emalloc(strlen(s) + 2);
593 	e = headAddrSpec(t, nil);
594 	if(e == nil)
595 		a = unixFrom(nil);
596 	else{
597 		if(*e != '\0')
598 			*e++ = '\0';
599 		else
600 			e = site;
601 		a = MKZ(MAddr);
602 
603 		a->box = estrdup(t);
604 		a->host = estrdup(e);
605 	}
606 	free(t);
607 	return a;
608 }
609 
610 /*
611  * read in the entire header,
612  * and parse out any existing mime headers
613  */
614 static int
615 msgHeader(Msg *m, Header *h, char *file)
616 {
617 	char *s, *ss, *t, *te;
618 	ulong lines, n, nn;
619 	long ns;
620 	int dated, c;
621 
622 	if(h->buf != nil)
623 		return 1;
624 
625 	ns = msgReadFile(m, file, &ss);
626 	if(ns < 0)
627 		return 0;
628 	s = ss;
629 	n = ns;
630 
631 	/*
632 	 * count lines ending with \n and \r\n
633 	 * add an extra line at the end, since upas/fs headers
634 	 * don't have a terminating \r\n
635 	 */
636 	lines = 1;
637 	te = s + ns;
638 	for(t = s; t < te; t++){
639 		c = *t;
640 		if(c == '\0')
641 			return msgBogus(m);
642 		if(c != '\n')
643 			continue;
644 		if(t == s || t[-1] != '\r')
645 			n++;
646 		lines++;
647 	}
648 	if(t > s && t[-1] != '\n'){
649 		if(t[-1] != '\r')
650 			n++;
651 		n++;
652 	}
653 	n += 2;
654 	h->buf = emalloc(n + 1);
655 	h->size = n;
656 	h->lines = lines;
657 
658 	/*
659 	 * make sure all headers end in \r\n
660 	 */
661 	nn = 0;
662 	for(t = s; t < te; t++){
663 		c = *t;
664 		if(c == '\n'){
665 			if(!nn || h->buf[nn - 1] != '\r')
666 				h->buf[nn++] = '\r';
667 			lines++;
668 		}
669 		h->buf[nn++] = c;
670 	}
671 	if(nn && h->buf[nn-1] != '\n'){
672 		if(h->buf[nn-1] != '\r')
673 			h->buf[nn++] = '\r';
674 		h->buf[nn++] = '\n';
675 	}
676 	h->buf[nn++] = '\r';
677 	h->buf[nn++] = '\n';
678 	h->buf[nn] = '\0';
679 	if(nn != n)
680 		bye("misconverted header %d %d", nn, n);
681 	free(s);
682 
683 	/*
684 	 * and parse some mime headers
685 	 */
686 	headStr = (uchar*)h->buf;
687 	dated = 0;
688 	while(s = headAtom(headFieldStop)){
689 		if(cistrcmp(s, "content-type") == 0)
690 			mimeType(h);
691 		else if(cistrcmp(s, "content-transfer-encoding") == 0)
692 			mimeEncoding(h);
693 		else if(cistrcmp(s, "content-id") == 0)
694 			mimeId(h);
695 		else if(cistrcmp(s, "content-description") == 0)
696 			mimeDescription(h);
697 		else if(cistrcmp(s, "content-disposition") == 0)
698 			mimeDisposition(h);
699 		else if(cistrcmp(s, "content-md5") == 0)
700 			mimeMd5(h);
701 		else if(cistrcmp(s, "content-language") == 0)
702 			mimeLanguage(h);
703 		else if(h == &m->head && cistrcmp(s, "from") == 0)
704 			m->from = headMAddr(m->from);
705 		else if(h == &m->head && cistrcmp(s, "to") == 0)
706 			m->to = headMAddr(m->to);
707 		else if(h == &m->head && cistrcmp(s, "reply-to") == 0)
708 			m->replyTo = headMAddr(m->replyTo);
709 		else if(h == &m->head && cistrcmp(s, "sender") == 0)
710 			m->sender = headMAddr(m->sender);
711 		else if(h == &m->head && cistrcmp(s, "cc") == 0)
712 			m->cc = headMAddr(m->cc);
713 		else if(h == &m->head && cistrcmp(s, "bcc") == 0)
714 			m->bcc = headMAddr(m->bcc);
715 		else if(h == &m->head && cistrcmp(s, "date") == 0)
716 			dated = 1;
717 		headSkip();
718 		free(s);
719 	}
720 
721 	if(h == &m->head){
722 		if(m->from == nil){
723 			m->from = m->unixFrom;
724 			s = maddrStr(m->from);
725 			msgAddHead(m, "From", s);
726 			free(s);
727 		}
728 		if(m->sender == nil)
729 			m->sender = m->from;
730 		if(m->replyTo == nil)
731 			m->replyTo = m->from;
732 
733 		if(infoIsNil(m->info[IDate]))
734 			m->info[IDate] = m->unixDate;
735 		if(!dated)
736 			msgAddDate(m);
737 	}
738 	return 1;
739 }
740 
741 /*
742  * prepend head: body to the cached header
743  */
744 static void
745 msgAddHead(Msg *m, char *head, char *body)
746 {
747 	char *s;
748 	long size, n;
749 
750 	n = strlen(head) + strlen(body) + 4;
751 	size = m->head.size + n;
752 	s = emalloc(size + 1);
753 	snprint(s, size + 1, "%s: %s\r\n%s", head, body, m->head.buf);
754 	free(m->head.buf);
755 	m->head.buf = s;
756 	m->head.size = size;
757 	m->head.lines++;
758 }
759 
760 static void
761 msgAddDate(Msg *m)
762 {
763 	Tm tm;
764 	char buf[64];
765 
766 	date2tm(&tm, m->info[IDate]);
767 	rfc822date(buf, sizeof(buf), &tm);
768 	msgAddHead(m, "Date", buf);
769 }
770 
771 static MimeHdr*
772 mkMimeHdr(char *s, char *t, MimeHdr *next)
773 {
774 	MimeHdr *mh;
775 
776 	mh = MK(MimeHdr);
777 	mh->s = s;
778 	mh->t = t;
779 	mh->next = next;
780 	return mh;
781 }
782 
783 static void
784 freeMimeHdr(MimeHdr *mh)
785 {
786 	MimeHdr *last;
787 
788 	while(mh != nil){
789 		last = mh;
790 		mh = mh->next;
791 		free(last->s);
792 		free(last->t);
793 		free(last);
794 	}
795 }
796 
797 static void
798 cleanupHeader(Header *h)
799 {
800 	freeMimeHdr(h->type);
801 	freeMimeHdr(h->id);
802 	freeMimeHdr(h->description);
803 	freeMimeHdr(h->encoding);
804 	freeMimeHdr(h->md5);
805 	freeMimeHdr(h->disposition);
806 	freeMimeHdr(h->language);
807 }
808 
809 /*
810  * parser for rfc822 & mime header fields
811  */
812 
813 /*
814  * type		: 'content-type' ':' token '/' token params
815  */
816 static void
817 mimeType(Header *h)
818 {
819 	char *s, *t;
820 
821 	if(headChar(1) != ':')
822 		return;
823 	s = headAtom(mimeTokenStop);
824 	if(s == nil || headChar(1) != '/'){
825 		free(s);
826 		return;
827 	}
828 	t = headAtom(mimeTokenStop);
829 	if(t == nil){
830 		free(s);
831 		return;
832 	}
833 	h->type = mkMimeHdr(s, t, mimeParams());
834 }
835 
836 /*
837  * params	:
838  *		| params ';' token '=' token
839  * 		| params ';' token '=' quoted-str
840  */
841 static MimeHdr*
842 mimeParams(void)
843 {
844 	MimeHdr head, *last;
845 	char *s, *t;
846 
847 	head.next = nil;
848 	last = &head;
849 	for(;;){
850 		if(headChar(1) != ';')
851 			break;
852 		s = headAtom(mimeTokenStop);
853 		if(s == nil || headChar(1) != '='){
854 			free(s);
855 			break;
856 		}
857 		if(headChar(0) == '"'){
858 			t = headQuoted('"', '"');
859 			stripQuotes(t);
860 		}else
861 			t = headAtom(mimeTokenStop);
862 		if(t == nil){
863 			free(s);
864 			break;
865 		}
866 		last->next = mkMimeHdr(s, t, nil);
867 		last = last->next;
868 	}
869 	return head.next;
870 }
871 
872 /*
873  * encoding	: 'content-transfer-encoding' ':' token
874  */
875 static void
876 mimeEncoding(Header *h)
877 {
878 	char *s;
879 
880 	if(headChar(1) != ':')
881 		return;
882 	s = headAtom(mimeTokenStop);
883 	if(s == nil)
884 		return;
885 	h->encoding = mkMimeHdr(s, nil, nil);
886 }
887 
888 /*
889  * mailaddr	: ':' addresses
890  */
891 static MAddr*
892 headMAddr(MAddr *old)
893 {
894 	MAddr *a;
895 
896 	if(headChar(1) != ':')
897 		return old;
898 
899 	if(headChar(0) == '\n')
900 		return old;
901 
902 	a = headAddresses();
903 	if(a == nil)
904 		return old;
905 
906 	freeMAddr(old);
907 	return a;
908 }
909 
910 /*
911  * addresses	: address | addresses ',' address
912  */
913 static MAddr*
914 headAddresses(void)
915 {
916 	MAddr *addr, *tail, *a;
917 
918 	addr = headAddress();
919 	if(addr == nil)
920 		return nil;
921 	tail = addr;
922 	while(headChar(0) == ','){
923 		headChar(1);
924 		a = headAddress();
925 		if(a == nil){
926 			freeMAddr(addr);
927 			return nil;
928 		}
929 		tail->next = a;
930 		tail = a;
931 	}
932 	return addr;
933 }
934 
935 /*
936  * address	: mailbox | group
937  * group	: phrase ':' mboxes ';' | phrase ':' ';'
938  * mailbox	: addr-spec
939  *		| optphrase '<' addr-spec '>'
940  *		| optphrase '<' route ':' addr-spec '>'
941  * optphrase	: | phrase
942  * route	: '@' domain
943  *		| route ',' '@' domain
944  * personal names are the phrase before '<',
945  * or a comment before or after a simple addr-spec
946  */
947 static MAddr*
948 headAddress(void)
949 {
950 	MAddr *addr;
951 	uchar *hs;
952 	char *s, *e, *w, *personal;
953 	int c;
954 
955 	s = emalloc(strlen((char*)headStr) + 2);
956 	e = s;
957 	personal = headSkipWhite(1);
958 	c = headChar(0);
959 	if(c == '<')
960 		w = nil;
961 	else{
962 		w = headWord();
963 		c = headChar(0);
964 	}
965 	if(c == '.' || c == '@' || c == ',' || c == '\n' || c == '\0'){
966 		lastWhite = headStr;
967 		e = headAddrSpec(s, w);
968 		if(personal == nil){
969 			hs = headStr;
970 			headStr = lastWhite;
971 			personal = headSkipWhite(1);
972 			headStr = hs;
973 		}
974 	}else{
975 		if(c != '<' || w != nil){
976 			free(personal);
977 			if(!headPhrase(e, w)){
978 				free(s);
979 				return nil;
980 			}
981 
982 			/*
983 			 * ignore addresses with groups,
984 			 * so the only thing left if <
985 			 */
986 			c = headChar(1);
987 			if(c != '<'){
988 				free(s);
989 				return nil;
990 			}
991 			personal = estrdup(s);
992 		}else
993 			headChar(1);
994 
995 		/*
996 		 * after this point, we need to free personal before returning.
997 		 * set e to nil to everything afterwards fails.
998 		 *
999 		 * ignore routes, they are useless, and heavily discouraged in rfc1123.
1000 		 * imap4 reports them up to, but not including, the terminating :
1001 		 */
1002 		e = s;
1003 		c = headChar(0);
1004 		if(c == '@'){
1005 			for(;;){
1006 				c = headChar(1);
1007 				if(c != '@'){
1008 					e = nil;
1009 					break;
1010 				}
1011 				headDomain(e);
1012 				c = headChar(1);
1013 				if(c != ','){
1014 					e = s;
1015 					break;
1016 				}
1017 			}
1018 			if(c != ':')
1019 				e = nil;
1020 		}
1021 
1022 		if(e != nil)
1023 			e = headAddrSpec(s, nil);
1024 		if(headChar(1) != '>')
1025 			e = nil;
1026 	}
1027 
1028 	/*
1029 	 * e points to @host, or nil if an error occured
1030 	 */
1031 	if(e == nil){
1032 		free(personal);
1033 		addr = nil;
1034 	}else{
1035 		if(*e != '\0')
1036 			*e++ = '\0';
1037 		else
1038 			e = site;
1039 		addr = MKZ(MAddr);
1040 
1041 		addr->personal = personal;
1042 		addr->box = estrdup(s);
1043 		addr->host = estrdup(e);
1044 	}
1045 	free(s);
1046 	return addr;
1047 }
1048 
1049 /*
1050  * phrase	: word
1051  *		| phrase word
1052  * w is the optional initial word of the phrase
1053  * returns the end of the phrase, or nil if a failure occured
1054  */
1055 static char*
1056 headPhrase(char *e, char *w)
1057 {
1058 	int c;
1059 
1060 	for(;;){
1061 		if(w == nil){
1062 			w = headWord();
1063 			if(w == nil)
1064 				return nil;
1065 		}
1066 		if(w[0] == '"')
1067 			stripQuotes(w);
1068 		strcpy(e, w);
1069 		free(w);
1070 		w = nil;
1071 		e = strchr(e, '\0');
1072 		c = headChar(0);
1073 		if(c <= ' ' || strchr(headAtomStop, c) != nil && c != '"')
1074 			break;
1075 		*e++ = ' ';
1076 		*e = '\0';
1077 	}
1078 	return e;
1079 }
1080 
1081 /*
1082  * addr-spec	: local-part '@' domain
1083  *		| local-part			extension to allow ! and local names
1084  * local-part	: word
1085  *		| local-part '.' word
1086  *
1087  * if no '@' is present, rewrite d!e!f!u as @d,@e:u@f,
1088  * where d, e, f are valid domain components.
1089  * the @d,@e: is ignored, since routes are ignored.
1090  * perhaps they should be rewritten as e!f!u@d, but that is inconsistent with upas.
1091  *
1092  * returns a pointer to '@', the end if none, or nil if there was an error
1093  */
1094 static char*
1095 headAddrSpec(char *e, char *w)
1096 {
1097 	char *s, *at, *b, *bang, *dom;
1098 	int c;
1099 
1100 	s = e;
1101 	for(;;){
1102 		if(w == nil){
1103 			w = headWord();
1104 			if(w == nil)
1105 				return nil;
1106 		}
1107 		strcpy(e, w);
1108 		free(w);
1109 		w = nil;
1110 		e = strchr(e, '\0');
1111 		lastWhite = headStr;
1112 		c = headChar(0);
1113 		if(c != '.')
1114 			break;
1115 		headChar(1);
1116 		*e++ = '.';
1117 		*e = '\0';
1118 	}
1119 
1120 	if(c != '@'){
1121 		/*
1122 		 * extenstion: allow name without domain
1123 		 * check for domain!xxx
1124 		 */
1125 		bang = domBang(s);
1126 		if(bang == nil)
1127 			return e;
1128 
1129 		/*
1130 		 * if dom1!dom2!xxx, ignore dom1!
1131 		 */
1132 		dom = s;
1133 		for(; b = domBang(bang + 1); bang = b)
1134 			dom = bang + 1;
1135 
1136 		/*
1137 		 * convert dom!mbox into mbox@dom
1138 		 */
1139 		*bang = '@';
1140 		strrev(dom, bang);
1141 		strrev(bang+1, e);
1142 		strrev(dom, e);
1143 		bang = &dom[e - bang - 1];
1144 		if(dom > s){
1145 			bang -= dom - s;
1146 			for(e = s; *e = *dom; e++)
1147 				dom++;
1148 		}
1149 
1150 		/*
1151 		 * eliminate a trailing '.'
1152 		 */
1153 		if(e[-1] == '.')
1154 			e[-1] = '\0';
1155 		return bang;
1156 	}
1157 	headChar(1);
1158 
1159 	at = e;
1160 	*e++ = '@';
1161 	*e = '\0';
1162 	if(!headDomain(e))
1163 		return nil;
1164 	return at;
1165 }
1166 
1167 /*
1168  * find the ! in domain!rest, where domain must have at least
1169  * on internal '.'
1170  */
1171 static char*
1172 domBang(char *s)
1173 {
1174 	int dot, c;
1175 
1176 	dot = 0;
1177 	for(; c = *s; s++){
1178 		if(c == '!'){
1179 			if(!dot || dot == 1 && s[-1] == '.' || s[1] == '\0')
1180 				return nil;
1181 			return s;
1182 		}
1183 		if(c == '"')
1184 			break;
1185 		if(c == '.')
1186 			dot++;
1187 	}
1188 	return nil;
1189 }
1190 
1191 /*
1192  * domain	: sub-domain
1193  *		| domain '.' sub-domain
1194  * returns the end of the domain, or nil if a failure occured
1195  */
1196 static char*
1197 headDomain(char *e)
1198 {
1199 	char *w;
1200 
1201 	for(;;){
1202 		w = headSubDomain();
1203 		if(w == nil)
1204 			return nil;
1205 		strcpy(e, w);
1206 		free(w);
1207 		e = strchr(e, '\0');
1208 		lastWhite = headStr;
1209 		if(headChar(0) != '.')
1210 			break;
1211 		headChar(1);
1212 		*e++ = '.';
1213 		*e = '\0';
1214 	}
1215 	return e;
1216 }
1217 
1218 /*
1219  * id		: 'content-id' ':' msg-id
1220  * msg-id	: '<' addr-spec '>'
1221  */
1222 static void
1223 mimeId(Header *h)
1224 {
1225 	char *s, *e, *w;
1226 
1227 	if(headChar(1) != ':')
1228 		return;
1229 	if(headChar(1) != '<')
1230 		return;
1231 
1232 	s = emalloc(strlen((char*)headStr) + 3);
1233 	e = s;
1234 	*e++ = '<';
1235 	e = headAddrSpec(e, nil);
1236 	if(e == nil || headChar(1) != '>'){
1237 		free(s);
1238 		return;
1239 	}
1240 	e = strchr(e, '\0');
1241 	*e++ = '>';
1242 	e[0] = '\0';
1243 	w = strdup(s);
1244 	free(s);
1245 	h->id = mkMimeHdr(w, nil, nil);
1246 }
1247 
1248 /*
1249  * description	: 'content-description' ':' *text
1250  */
1251 static void
1252 mimeDescription(Header *h)
1253 {
1254 	if(headChar(1) != ':')
1255 		return;
1256 	headSkipWhite(0);
1257 	h->description = mkMimeHdr(headText(), nil, nil);
1258 }
1259 
1260 /*
1261  * disposition	: 'content-disposition' ':' token params
1262  */
1263 static void
1264 mimeDisposition(Header *h)
1265 {
1266 	char *s;
1267 
1268 	if(headChar(1) != ':')
1269 		return;
1270 	s = headAtom(mimeTokenStop);
1271 	if(s == nil)
1272 		return;
1273 	h->disposition = mkMimeHdr(s, nil, mimeParams());
1274 }
1275 
1276 /*
1277  * md5		: 'content-md5' ':' token
1278  */
1279 static void
1280 mimeMd5(Header *h)
1281 {
1282 	char *s;
1283 
1284 	if(headChar(1) != ':')
1285 		return;
1286 	s = headAtom(mimeTokenStop);
1287 	if(s == nil)
1288 		return;
1289 	h->md5 = mkMimeHdr(s, nil, nil);
1290 }
1291 
1292 /*
1293  * language	: 'content-language' ':' langs
1294  * langs	: token
1295  *		| langs commas token
1296  * commas	: ','
1297  *		| commas ','
1298  */
1299 static void
1300 mimeLanguage(Header *h)
1301 {
1302 	MimeHdr head, *last;
1303 	char *s;
1304 
1305 	head.next = nil;
1306 	last = &head;
1307 	for(;;){
1308 		s = headAtom(mimeTokenStop);
1309 		if(s == nil)
1310 			break;
1311 		last->next = mkMimeHdr(s, nil, nil);
1312 		last = last->next;
1313 		while(headChar(0) != ',')
1314 			headChar(1);
1315 	}
1316 	h->language = head.next;
1317 }
1318 
1319 /*
1320  * token	: 1*<char 33-255, except "()<>@,;:\\\"/[]?=" aka mimeTokenStop>
1321  * atom		: 1*<chars 33-255, except "()<>@,;:\\\".[]" aka headAtomStop>
1322  * note this allows 8 bit characters, which occur in utf.
1323  */
1324 static char*
1325 headAtom(char *disallowed)
1326 {
1327 	char *s;
1328 	int c, ns, as;
1329 
1330 	headSkipWhite(0);
1331 
1332 	s = emalloc(StrAlloc);
1333 	as = StrAlloc;
1334 	ns = 0;
1335 	for(;;){
1336 		c = *headStr++;
1337 		if(c <= ' ' || strchr(disallowed, c) != nil){
1338 			headStr--;
1339 			break;
1340 		}
1341 		s[ns++] = c;
1342 		if(ns >= as){
1343 			as += StrAlloc;
1344 			s = erealloc(s, as);
1345 		}
1346 	}
1347 	if(ns == 0){
1348 		free(s);
1349 		return 0;
1350 	}
1351 	s[ns] = '\0';
1352 	return s;
1353 }
1354 
1355 /*
1356  * sub-domain	: atom | domain-lit
1357  */
1358 static char *
1359 headSubDomain(void)
1360 {
1361 	if(headChar(0) == '[')
1362 		return headQuoted('[', ']');
1363 	return headAtom(headAtomStop);
1364 }
1365 
1366 /*
1367  * word	: atom | quoted-str
1368  */
1369 static char *
1370 headWord(void)
1371 {
1372 	if(headChar(0) == '"')
1373 		return headQuoted('"', '"');
1374 	return headAtom(headAtomStop);
1375 }
1376 
1377 /*
1378  * q is a quoted string.  remove enclosing " and and \ escapes
1379  */
1380 static void
1381 stripQuotes(char *q)
1382 {
1383 	char *s;
1384 	int c;
1385 
1386 	if(q == nil)
1387 		return;
1388 	s = q++;
1389 	while(c = *q++){
1390 		if(c == '\\'){
1391 			c = *q++;
1392 			if(!c)
1393 				return;
1394 		}
1395 		*s++ = c;
1396 	}
1397 	s[-1] = '\0';
1398 }
1399 
1400 /*
1401  * quoted-str	: '"' *(any char but '"\\\r', or '\' any char, or linear-white-space) '"'
1402  * domain-lit	: '[' *(any char but '[]\\\r', or '\' any char, or linear-white-space) ']'
1403  */
1404 static char *
1405 headQuoted(int start, int stop)
1406 {
1407 	char *s;
1408 	int c, ns, as;
1409 
1410 	if(headChar(1) != start)
1411 		return nil;
1412 	s = emalloc(StrAlloc);
1413 	as = StrAlloc;
1414 	ns = 0;
1415 	s[ns++] = start;
1416 	for(;;){
1417 		c = *headStr;
1418 		if(c == stop){
1419 			headStr++;
1420 			break;
1421 		}
1422 		if(c == '\0'){
1423 			free(s);
1424 			return nil;
1425 		}
1426 		if(c == '\r'){
1427 			headStr++;
1428 			continue;
1429 		}
1430 		if(c == '\n'){
1431 			headStr++;
1432 			while(*headStr == ' ' || *headStr == '\t' || *headStr == '\r' || *headStr == '\n')
1433 				headStr++;
1434 			c = ' ';
1435 		}else if(c == '\\'){
1436 			headStr++;
1437 			s[ns++] = c;
1438 			c = *headStr;
1439 			if(c == '\0'){
1440 				free(s);
1441 				return nil;
1442 			}
1443 			headStr++;
1444 		}else
1445 			headStr++;
1446 		s[ns++] = c;
1447 		if(ns + 1 >= as){	/* leave room for \c or "0 */
1448 			as += StrAlloc;
1449 			s = erealloc(s, as);
1450 		}
1451 	}
1452 	s[ns++] = stop;
1453 	s[ns] = '\0';
1454 	return s;
1455 }
1456 
1457 /*
1458  * headText	: contents of rest of header line
1459  */
1460 static char *
1461 headText(void)
1462 {
1463 	uchar *v;
1464 	char *s;
1465 
1466 	v = headStr;
1467 	headToEnd();
1468 	s = emalloc(headStr - v + 1);
1469 	memmove(s, v, headStr - v);
1470 	s[headStr - v] = '\0';
1471 	return s;
1472 }
1473 
1474 /*
1475  * white space is ' ' '\t' or nested comments.
1476  * skip white space.
1477  * if com and a comment is seen,
1478  * return it's contents and stop processing white space.
1479  */
1480 static char*
1481 headSkipWhite(int com)
1482 {
1483 	char *s;
1484 	int c, incom, as, ns;
1485 
1486 	s = nil;
1487 	as = StrAlloc;
1488 	ns = 0;
1489 	if(com)
1490 		s = emalloc(StrAlloc);
1491 	incom = 0;
1492 	for(; c = *headStr; headStr++){
1493 		switch(c){
1494 		case ' ':
1495 		case '\t':
1496 		case '\r':
1497 			c = ' ';
1498 			break;
1499 		case '\n':
1500 			c = headStr[1];
1501 			if(c != ' ' && c != '\t')
1502 				goto breakout;
1503 			c = ' ';
1504 			break;
1505 		case '\\':
1506 			if(com && incom)
1507 				s[ns++] = c;
1508 			c = headStr[1];
1509 			if(c == '\0')
1510 				goto breakout;
1511 			headStr++;
1512 			break;
1513 		case '(':
1514 			incom++;
1515 			if(incom == 1)
1516 				continue;
1517 			break;
1518 		case ')':
1519 			incom--;
1520 			if(com && !incom){
1521 				s[ns] = '\0';
1522 				return s;
1523 			}
1524 			break;
1525 		default:
1526 			if(!incom)
1527 				goto breakout;
1528 			break;
1529 		}
1530 		if(com && incom && (c != ' ' || ns > 0 && s[ns-1] != ' ')){
1531 			s[ns++] = c;
1532 			if(ns + 1 >= as){	/* leave room for \c or 0 */
1533 				as += StrAlloc;
1534 				s = erealloc(s, as);
1535 			}
1536 		}
1537 	}
1538 breakout:;
1539 	free(s);
1540 	return nil;
1541 }
1542 
1543 /*
1544  * return the next non-white character
1545  */
1546 static int
1547 headChar(int eat)
1548 {
1549 	int c;
1550 
1551 	headSkipWhite(0);
1552 	c = *headStr;
1553 	if(eat && c != '\0' && c != '\n')
1554 		headStr++;
1555 	return c;
1556 }
1557 
1558 static void
1559 headToEnd(void)
1560 {
1561 	uchar *s;
1562 	int c;
1563 
1564 	for(;;){
1565 		s = headStr;
1566 		c = *s++;
1567 		while(c == '\r')
1568 			c = *s++;
1569 		if(c == '\n'){
1570 			c = *s++;
1571 			if(c != ' ' && c != '\t')
1572 				return;
1573 		}
1574 		if(c == '\0')
1575 			return;
1576 		headStr = s;
1577 	}
1578 }
1579 
1580 static void
1581 headSkip(void)
1582 {
1583 	int c;
1584 
1585 	while(c = *headStr){
1586 		headStr++;
1587 		if(c == '\n'){
1588 			c = *headStr;
1589 			if(c == ' ' || c == '\t')
1590 				continue;
1591 			return;
1592 		}
1593 	}
1594 }
1595