Subversion Repositories planix.SVN

Rev

Details | Last modification | View Log | RSS feed

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