]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/imap4d/msg.c
ip/torrent: remove unneeded assignment
[plan9front.git] / sys / src / cmd / ip / imap4d / msg.c
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 }