Blob


1 /*
2 * this is a filter that changes mime types and names of
3 * suspect executable attachments.
4 */
5 #include "common.h"
6 #include <ctype.h>
8 Biobuf in;
9 Biobuf out;
11 typedef struct Mtype Mtype;
12 typedef struct Hdef Hdef;
13 typedef struct Hline Hline;
14 typedef struct Part Part;
16 static int badfile(char *name);
17 static int badtype(char *type);
18 static void ctype(Part*, Hdef*, char*);
19 static void cencoding(Part*, Hdef*, char*);
20 static void cdisposition(Part*, Hdef*, char*);
21 static int decquoted(char *out, char *in, char *e);
22 static char* getstring(char *p, String *s, int dolower);
23 static void init_hdefs(void);
24 static int isattribute(char **pp, char *attr);
25 static int latin1toutf(char *out, char *in, char *e);
26 static String* mkboundary(void);
27 static Part* part(Part *pp);
28 static Part* passbody(Part *p, int dobound);
29 static void passnotheader(void);
30 static void passunixheader(void);
31 static Part* problemchild(Part *p);
32 static void readheader(Part *p);
33 static Hline* readhl(void);
34 static void readmtypes(void);
35 static int save(Part *p, char *file);
36 static void setfilename(Part *p, char *name);
37 static char* skiptosemi(char *p);
38 static char* skipwhite(char *p);
39 static String* tokenconvert(String *t);
40 static void writeheader(Part *p, int);
42 enum
43 {
44 // encodings
45 Enone= 0,
46 Ebase64,
47 Equoted,
49 // disposition possibilities
50 Dnone= 0,
51 Dinline,
52 Dfile,
53 Dignore,
55 PAD64= '=',
56 };
58 /*
59 * a message part; either the whole message or a subpart
60 */
61 struct Part
62 {
63 Part *pp; /* parent part */
64 Hline *hl; /* linked list of header lines */
65 int disposition;
66 int encoding;
67 int badfile;
68 int badtype;
69 String *boundary; /* boundary for multiparts */
70 int blen;
71 String *charset; /* character set */
72 String *type; /* content type */
73 String *filename; /* file name */
74 Biobuf *tmpbuf; /* diversion input buffer */
75 };
77 /*
78 * a (multi)line header
79 */
80 struct Hline
81 {
82 Hline *next;
83 String *s;
84 };
86 /*
87 * header definitions for parsing
88 */
89 struct Hdef
90 {
91 char *type;
92 void (*f)(Part*, Hdef*, char*);
93 int len;
94 };
96 Hdef hdefs[] =
97 {
98 { "content-type:", ctype, },
99 { "content-transfer-encoding:", cencoding, },
100 { "content-disposition:", cdisposition, },
101 { 0, },
102 };
104 /*
105 * acceptable content types and their extensions
106 */
107 struct Mtype {
108 Mtype *next;
109 char *ext; /* extension */
110 char *gtype; /* generic content type */
111 char *stype; /* specific content type */
112 char class;
113 };
114 Mtype *mtypes;
116 int justreject;
117 char *savefile;
119 void
120 main(int argc, char **argv)
122 ARGBEGIN{
123 case 'r':
124 justreject = 1;
125 break;
126 case 's':
127 savefile = ARGF();
128 if(savefile == nil)
129 exits("usage");
130 break;
131 }ARGEND;
133 Binit(&in, 0, OREAD);
134 Binit(&out, 1, OWRITE);
136 init_hdefs();
137 readmtypes();
139 /* pass through our standard 'From ' line */
140 passunixheader();
142 /* parse with the top level part */
143 part(nil);
145 exits(0);
148 void
149 refuse(void)
151 postnote(PNGROUP, getpid(), "mail refused: we don't accept executable attachments");
152 exits("mail refused: we don't accept executable attachments");
156 /*
157 * parse a part; returns the ancestor whose boundary terminated
158 * this part or nil on EOF.
159 */
160 static Part*
161 part(Part *pp)
163 Part *p, *np;
165 p = mallocz(sizeof *p, 1);
166 p->pp = pp;
167 readheader(p);
169 if(p->boundary != nil){
170 /* the format of a multipart part is always:
171 * header
172 * null or ignored body
173 * boundary
174 * header
175 * body
176 * boundary
177 * ...
178 */
179 writeheader(p, 1);
180 np = passbody(p, 1);
181 if(np != p)
182 return np;
183 for(;;){
184 np = part(p);
185 if(np != p)
186 return np;
188 } else {
189 /* no boundary */
190 /* may still be multipart if this is a forwarded message */
191 if(p->type && cistrcmp(s_to_c(p->type), "message/rfc822") == 0){
192 /* the format of forwarded message is:
193 * header
194 * header
195 * body
196 */
197 writeheader(p, 1);
198 passnotheader();
199 return part(p);
200 } else {
201 /*
202 * This is the meat. This may be an executable.
203 * if so, wrap it and change its type
204 */
205 if(p->badtype || p->badfile){
206 if(p->badfile == 2){
207 if(savefile != nil)
208 save(p, savefile);
209 syslog(0, "vf", "vf rejected %s %s", p->type?s_to_c(p->type):"?",
210 p->filename?s_to_c(p->filename):"?");
211 fprint(2, "The mail contained an executable attachment.\n");
212 fprint(2, "We refuse all mail containing such.\n");
213 refuse();
215 np = problemchild(p);
216 if(np != p)
217 return np;
218 /* if problemchild returns p, it turns out p is okay: fall thru */
220 writeheader(p, 1);
221 return passbody(p, 1);
226 /*
227 * read and parse a complete header
228 */
229 static void
230 readheader(Part *p)
232 Hline *hl, **l;
233 Hdef *hd;
235 l = &p->hl;
236 for(;;){
237 hl = readhl();
238 if(hl == nil)
239 break;
240 *l = hl;
241 l = &hl->next;
243 for(hd = hdefs; hd->type != nil; hd++){
244 if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){
245 (*hd->f)(p, hd, s_to_c(hl->s));
246 break;
252 /*
253 * read a possibly multiline header line
254 */
255 static Hline*
256 readhl(void)
258 Hline *hl;
259 String *s;
260 char *p;
261 int n;
263 p = Brdline(&in, '\n');
264 if(p == nil)
265 return nil;
266 n = Blinelen(&in);
267 if(memchr(p, ':', n) == nil){
268 Bseek(&in, -n, 1);
269 return nil;
271 s = s_nappend(s_new(), p, n);
272 for(;;){
273 p = Brdline(&in, '\n');
274 if(p == nil)
275 break;
276 n = Blinelen(&in);
277 if(*p != ' ' && *p != '\t'){
278 Bseek(&in, -n, 1);
279 break;
281 s = s_nappend(s, p, n);
283 hl = malloc(sizeof *hl);
284 hl->s = s;
285 hl->next = nil;
286 return hl;
289 /*
290 * write out a complete header
291 */
292 static void
293 writeheader(Part *p, int xfree)
295 Hline *hl, *next;
297 for(hl = p->hl; hl != nil; hl = next){
298 Bprint(&out, "%s", s_to_c(hl->s));
299 if(xfree)
300 s_free(hl->s);
301 next = hl->next;
302 if(xfree)
303 free(hl);
305 if(xfree)
306 p->hl = nil;
309 /*
310 * pass a body through. return if we hit one of our ancestors'
311 * boundaries or EOF. if we hit a boundary, return a pointer to
312 * that ancestor. if we hit EOF, return nil.
313 */
314 static Part*
315 passbody(Part *p, int dobound)
317 Part *pp;
318 Biobuf *b;
319 char *cp;
321 for(;;){
322 if(p->tmpbuf){
323 b = p->tmpbuf;
324 cp = Brdline(b, '\n');
325 if(cp == nil){
326 Bterm(b);
327 p->tmpbuf = nil;
328 goto Stdin;
330 }else{
331 Stdin:
332 b = &in;
333 cp = Brdline(b, '\n');
335 if(cp == nil)
336 return nil;
337 for(pp = p; pp != nil; pp = pp->pp)
338 if(pp->boundary != nil
339 && strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){
340 if(dobound)
341 Bwrite(&out, cp, Blinelen(b));
342 else
343 Bseek(b, -Blinelen(b), 1);
344 return pp;
346 Bwrite(&out, cp, Blinelen(b));
348 return nil;
351 /*
352 * save the message somewhere
353 */
354 static vlong bodyoff; /* clumsy hack */
355 static int
356 save(Part *p, char *file)
358 int fd;
359 char *cp;
361 Bterm(&out);
362 memset(&out, 0, sizeof(out));
364 fd = open(file, OWRITE);
365 if(fd < 0)
366 return -1;
367 seek(fd, 0, 2);
368 Binit(&out, fd, OWRITE);
369 cp = ctime(time(0));
370 cp[28] = 0;
371 Bprint(&out, "From virusfilter %s\n", cp);
372 writeheader(p, 0);
373 bodyoff = Boffset(&out);
374 passbody(p, 1);
375 Bprint(&out, "\n");
376 Bterm(&out);
377 close(fd);
379 memset(&out, 0, sizeof out);
380 Binit(&out, 1, OWRITE);
381 return 0;
384 /*
385 * write to a file but save the fd for passbody.
386 */
387 static char*
388 savetmp(Part *p)
390 char buf[40], *name;
391 int fd;
393 strcpy(buf, "/tmp/vf.XXXXXXXXXXX");
394 name = mktemp(buf);
395 if((fd = create(name, OWRITE|OEXCL, 0666)) < 0){
396 fprint(2, "error creating temporary file: %r\n");
397 refuse();
399 close(fd);
400 if(save(p, name) < 0){
401 fprint(2, "error saving temporary file: %r\n");
402 refuse();
404 if(p->tmpbuf){
405 fprint(2, "error in savetmp: already have tmp file!\n");
406 refuse();
408 p->tmpbuf = Bopen(name, OREAD|ORCLOSE);
409 if(p->tmpbuf == nil){
410 fprint(2, "error reading tempoary file: %r\n");
411 refuse();
413 Bseek(p->tmpbuf, bodyoff, 0);
414 return strdup(name);
417 /*
418 * XXX save the decoded file, run 9 unzip -tf on it, and then
419 * look at the file list.
420 */
421 static int
422 runchecker(Part *p)
424 int pid;
425 char *name;
426 Waitmsg *w;
428 if(access("/mail/lib/validateattachment", AEXEC) < 0)
429 return 0;
431 name = savetmp(p);
432 fprint(2, "run checker %s\n", name);
433 switch(pid = fork()){
434 case -1:
435 sysfatal("fork: %r");
436 case 0:
437 dup(2, 1);
438 execl("/mail/lib/validateattachment", "validateattachment", name, nil);
439 _exits("exec failed");
442 /*
443 * Okay to return on error - will let mail through but wrapped.
444 */
445 w = wait();
446 if(w == nil){
447 syslog(0, "mail", "vf wait failed: %r");
448 return 0;
450 if(w->pid != pid){
451 syslog(0, "mail", "vf wrong pid %d != %d", w->pid, pid);
452 return 0;
454 if(p->filename)
455 name = s_to_c(p->filename);
456 if(strstr(w->msg, "discard")){
457 syslog(0, "mail", "vf validateattachment rejected %s", name);
458 refuse();
460 if(strstr(w->msg, "accept")){
461 syslog(0, "mail", "vf validateattachment accepted %s", name);
462 return 1;
464 free(w);
465 return 0;
468 /*
469 * emit a multipart Part that explains the problem
470 */
471 static Part*
472 problemchild(Part *p)
474 Part *np;
475 Hline *hl;
476 String *boundary;
477 char *cp;
479 /*
480 * We don't know whether the attachment is okay.
481 * If there's an external checker, let it have a crack at it.
482 */
483 if(runchecker(p) > 0)
484 return p;
486 fprint(2, "x\n");
487 syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?",
488 p->filename?s_to_c(p->filename):"?");
489 fprint(2, "x\n");
491 boundary = mkboundary();
492 fprint(2, "x\n");
493 /* print out non-mime headers */
494 for(hl = p->hl; hl != nil; hl = hl->next)
495 if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0)
496 Bprint(&out, "%s", s_to_c(hl->s));
498 fprint(2, "x\n");
499 /* add in our own multipart headers and message */
500 Bprint(&out, "Content-Type: multipart/mixed;\n");
501 Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary));
502 Bprint(&out, "Content-Disposition: inline\n");
503 Bprint(&out, "\n");
504 Bprint(&out, "This is a multi-part message in MIME format.\n");
505 Bprint(&out, "--%s\n", s_to_c(boundary));
506 Bprint(&out, "Content-Disposition: inline\n");
507 Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
508 Bprint(&out, "Content-Transfer-Encoding: 7bit\n");
509 Bprint(&out, "\n");
510 Bprint(&out, "from postmaster@%s:\n", sysname());
511 Bprint(&out, "The following attachment had content that we can't\n");
512 Bprint(&out, "prove to be harmless. To avoid possible automatic\n");
513 Bprint(&out, "execution, we changed the content headers.\n");
514 Bprint(&out, "The original header was:\n\n");
516 /* print out original header lines */
517 for(hl = p->hl; hl != nil; hl = hl->next)
518 if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0)
519 Bprint(&out, "\t%s", s_to_c(hl->s));
520 Bprint(&out, "--%s\n", s_to_c(boundary));
522 /* change file name */
523 if(p->filename)
524 s_append(p->filename, ".suspect");
525 else
526 p->filename = s_copy("file.suspect");
528 /* print out new header */
529 Bprint(&out, "Content-Type: application/octet-stream\n");
530 Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename));
531 switch(p->encoding){
532 case Enone:
533 break;
534 case Ebase64:
535 Bprint(&out, "Content-Transfer-Encoding: base64\n");
536 break;
537 case Equoted:
538 Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n");
539 break;
542 fprint(2, "z\n");
543 /* pass the body */
544 np = passbody(p, 0);
546 fprint(2, "w\n");
547 /* add the new boundary and the original terminator */
548 Bprint(&out, "--%s--\n", s_to_c(boundary));
549 if(np && np->boundary){
550 cp = Brdline(&in, '\n');
551 Bwrite(&out, cp, Blinelen(&in));
554 fprint(2, "a %p\n", np);
555 return np;
558 static int
559 isattribute(char **pp, char *attr)
561 char *p;
562 int n;
564 n = strlen(attr);
565 p = *pp;
566 if(cistrncmp(p, attr, n) != 0)
567 return 0;
568 p += n;
569 while(*p == ' ')
570 p++;
571 if(*p++ != '=')
572 return 0;
573 while(*p == ' ')
574 p++;
575 *pp = p;
576 return 1;
579 /*
580 * parse content type header
581 */
582 static void
583 ctype(Part *p, Hdef *h, char *cp)
585 String *s;
587 cp += h->len;
588 cp = skipwhite(cp);
590 p->type = s_new();
591 cp = getstring(cp, p->type, 1);
592 if(badtype(s_to_c(p->type)))
593 p->badtype = 1;
595 while(*cp){
596 if(isattribute(&cp, "boundary")){
597 s = s_new();
598 cp = getstring(cp, s, 0);
599 p->boundary = s_reset(p->boundary);
600 s_append(p->boundary, "--");
601 s_append(p->boundary, s_to_c(s));
602 p->blen = s_len(p->boundary);
603 s_free(s);
604 } else if(cistrncmp(cp, "multipart", 9) == 0){
605 /*
606 * the first unbounded part of a multipart message,
607 * the preamble, is not displayed or saved
608 */
609 } else if(isattribute(&cp, "name")){
610 setfilename(p, cp);
611 } else if(isattribute(&cp, "charset")){
612 if(p->charset == nil)
613 p->charset = s_new();
614 cp = getstring(cp, s_reset(p->charset), 0);
617 cp = skiptosemi(cp);
621 /*
622 * parse content encoding header
623 */
624 static void
625 cencoding(Part *m, Hdef *h, char *p)
627 p += h->len;
628 p = skipwhite(p);
629 if(cistrncmp(p, "base64", 6) == 0)
630 m->encoding = Ebase64;
631 else if(cistrncmp(p, "quoted-printable", 16) == 0)
632 m->encoding = Equoted;
635 /*
636 * parse content disposition header
637 */
638 static void
639 cdisposition(Part *p, Hdef *h, char *cp)
641 cp += h->len;
642 cp = skipwhite(cp);
643 while(*cp){
644 if(cistrncmp(cp, "inline", 6) == 0){
645 p->disposition = Dinline;
646 } else if(cistrncmp(cp, "attachment", 10) == 0){
647 p->disposition = Dfile;
648 } else if(cistrncmp(cp, "filename=", 9) == 0){
649 cp += 9;
650 setfilename(p, cp);
652 cp = skiptosemi(cp);
657 static void
658 setfilename(Part *p, char *name)
660 if(p->filename == nil)
661 p->filename = s_new();
662 getstring(name, s_reset(p->filename), 0);
663 p->filename = tokenconvert(p->filename);
664 p->badfile = badfile(s_to_c(p->filename));
667 static char*
668 skipwhite(char *p)
670 while(isspace(*p))
671 p++;
672 return p;
675 static char*
676 skiptosemi(char *p)
678 while(*p && *p != ';')
679 p++;
680 while(*p == ';' || isspace(*p))
681 p++;
682 return p;
685 /*
686 * parse a possibly "'d string from a header. A
687 * ';' terminates the string.
688 */
689 static char*
690 getstring(char *p, String *s, int dolower)
692 s = s_reset(s);
693 p = skipwhite(p);
694 if(*p == '"'){
695 p++;
696 for(;*p && *p != '"'; p++)
697 if(dolower)
698 s_putc(s, tolower(*p));
699 else
700 s_putc(s, *p);
701 if(*p == '"')
702 p++;
703 s_terminate(s);
705 return p;
708 for(; *p && !isspace(*p) && *p != ';'; p++)
709 if(dolower)
710 s_putc(s, tolower(*p));
711 else
712 s_putc(s, *p);
713 s_terminate(s);
715 return p;
718 static void
719 init_hdefs(void)
721 Hdef *hd;
722 static int already;
724 if(already)
725 return;
726 already = 1;
728 for(hd = hdefs; hd->type != nil; hd++)
729 hd->len = strlen(hd->type);
732 /*
733 * create a new boundary
734 */
735 static String*
736 mkboundary(void)
738 char buf[32];
739 int i;
740 static int already;
742 if(already == 0){
743 srand((time(0)<<16)|getpid());
744 already = 1;
746 strcpy(buf, "upas-");
747 for(i = 5; i < sizeof(buf)-1; i++)
748 buf[i] = 'a' + nrand(26);
749 buf[i] = 0;
750 return s_copy(buf);
753 /*
754 * skip blank lines till header
755 */
756 static void
757 passnotheader(void)
759 char *cp;
760 int i, n;
762 while((cp = Brdline(&in, '\n')) != nil){
763 n = Blinelen(&in);
764 for(i = 0; i < n-1; i++)
765 if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){
766 Bseek(&in, -n, 1);
767 return;
769 Bwrite(&out, cp, n);
773 /*
774 * pass unix header lines
775 */
776 static void
777 passunixheader(void)
779 char *p;
780 int n;
782 while((p = Brdline(&in, '\n')) != nil){
783 n = Blinelen(&in);
784 if(strncmp(p, "From ", 5) != 0){
785 Bseek(&in, -n, 1);
786 break;
788 Bwrite(&out, p, n);
792 /*
793 * Read mime types
794 */
795 static void
796 readmtypes(void)
798 Biobuf *b;
799 char *p;
800 char *f[6];
801 Mtype *m;
802 Mtype **l;
804 b = Bopen(unsharp("#9/sys/lib/mimetype"), OREAD);
805 if(b == nil)
806 return;
808 l = &mtypes;
809 while((p = Brdline(b, '\n')) != nil){
810 if(*p == '#')
811 continue;
812 p[Blinelen(b)-1] = 0;
813 if(tokenize(p, f, nelem(f)) < 5)
814 continue;
815 m = mallocz(sizeof *m, 1);
816 if(m == nil)
817 goto err;
818 m->ext = strdup(f[0]);
819 if(m->ext == 0)
820 goto err;
821 m->gtype = strdup(f[1]);
822 if(m->gtype == 0)
823 goto err;
824 m->stype = strdup(f[2]);
825 if(m->stype == 0)
826 goto err;
827 m->class = *f[4];
828 *l = m;
829 l = &(m->next);
831 Bterm(b);
832 return;
833 err:
834 if(m == nil)
835 return;
836 free(m->ext);
837 free(m->gtype);
838 free(m->stype);
839 free(m);
840 Bterm(b);
843 /*
844 * if the class is 'm' or 'y', accept it
845 * if the class is 'p' check a previous extension
846 * otherwise, filename is bad
847 */
848 static int
849 badfile(char *name)
851 char *p;
852 Mtype *m;
853 int rv;
855 p = strrchr(name, '.');
856 if(p == nil)
857 return 0;
859 for(m = mtypes; m != nil; m = m->next)
860 if(cistrcmp(p, m->ext) == 0){
861 switch(m->class){
862 case 'm':
863 case 'y':
864 return 0;
865 case 'p':
866 *p = 0;
867 rv = badfile(name);
868 *p = '.';
869 return rv;
870 case 'r':
871 return 2;
874 if(justreject)
875 return 0;
876 return 1;
879 /*
880 * if the class is 'm' or 'y' or 'p', accept it
881 * otherwise, filename is bad
882 */
883 static int
884 badtype(char *type)
886 Mtype *m;
887 char *s, *fix;
888 int rv = 1;
890 if(justreject)
891 return 0;
893 fix = s = strchr(type, '/');
894 if(s != nil)
895 *s++ = 0;
896 else
897 s = "-";
899 for(m = mtypes; m != nil; m = m->next){
900 if(cistrcmp(type, m->gtype) != 0)
901 continue;
902 if(cistrcmp(s, m->stype) != 0)
903 continue;
904 switch(m->class){
905 case 'y':
906 case 'p':
907 case 'm':
908 rv = 0;
909 break;
911 break;
914 if(fix != nil)
915 *fix = '/';
916 return rv;
919 /* rfc2047 non-ascii */
920 typedef struct Charset Charset;
921 struct Charset {
922 char *name;
923 int len;
924 int convert;
925 } charsets[] =
927 { "us-ascii", 8, 1, },
928 { "utf-8", 5, 0, },
929 { "iso-8859-1", 10, 1, },
930 };
932 /*
933 * convert to UTF if need be
934 */
935 static String*
936 tokenconvert(String *t)
938 String *s;
939 char decoded[1024];
940 char utfbuf[2*1024];
941 int i, len;
942 char *e;
943 char *token;
945 token = s_to_c(t);
946 len = s_len(t);
948 if(token[0] != '=' || token[1] != '?' ||
949 token[len-2] != '?' || token[len-1] != '=')
950 goto err;
951 e = token+len-2;
952 token += 2;
954 // bail if we don't understand the character set
955 for(i = 0; i < nelem(charsets); i++)
956 if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
957 if(token[charsets[i].len] == '?'){
958 token += charsets[i].len + 1;
959 break;
961 if(i >= nelem(charsets))
962 goto err;
964 // bail if it doesn't fit
965 if(strlen(token) > sizeof(decoded)-1)
966 goto err;
968 // bail if we don't understand the encoding
969 if(cistrncmp(token, "b?", 2) == 0){
970 token += 2;
971 len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
972 decoded[len] = 0;
973 } else if(cistrncmp(token, "q?", 2) == 0){
974 token += 2;
975 len = decquoted(decoded, token, e);
976 if(len > 0 && decoded[len-1] == '\n')
977 len--;
978 decoded[len] = 0;
979 } else
980 goto err;
982 s = nil;
983 switch(charsets[i].convert){
984 case 0:
985 s = s_copy(decoded);
986 break;
987 case 1:
988 s = s_new();
989 latin1toutf(utfbuf, decoded, decoded+len);
990 s_append(s, utfbuf);
991 break;
994 return s;
995 err:
996 return s_clone(t);
999 /*
1000 * decode quoted
1002 enum
1004 Self= 1,
1005 Hex= 2,
1007 uchar tableqp[256];
1009 static void
1010 initquoted(void)
1012 int c;
1014 memset(tableqp, 0, 256);
1015 for(c = ' '; c <= '<'; c++)
1016 tableqp[c] = Self;
1017 for(c = '>'; c <= '~'; c++)
1018 tableqp[c] = Self;
1019 tableqp['\t'] = Self;
1020 tableqp['='] = Hex;
1023 static int
1024 hex2int(int x)
1026 if(x >= '0' && x <= '9')
1027 return x - '0';
1028 if(x >= 'A' && x <= 'F')
1029 return (x - 'A') + 10;
1030 if(x >= 'a' && x <= 'f')
1031 return (x - 'a') + 10;
1032 return 0;
1035 static char*
1036 decquotedline(char *out, char *in, char *e)
1038 int c, soft;
1040 /* dump trailing white space */
1041 while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1042 e--;
1044 /* trailing '=' means no newline */
1045 if(*e == '='){
1046 soft = 1;
1047 e--;
1048 } else
1049 soft = 0;
1051 while(in <= e){
1052 c = (*in++) & 0xff;
1053 switch(tableqp[c]){
1054 case Self:
1055 *out++ = c;
1056 break;
1057 case Hex:
1058 c = hex2int(*in++)<<4;
1059 c |= hex2int(*in++);
1060 *out++ = c;
1061 break;
1064 if(!soft)
1065 *out++ = '\n';
1066 *out = 0;
1068 return out;
1071 static int
1072 decquoted(char *out, char *in, char *e)
1074 char *p, *nl;
1076 if(tableqp[' '] == 0)
1077 initquoted();
1079 p = out;
1080 while((nl = strchr(in, '\n')) != nil && nl < e){
1081 p = decquotedline(p, in, nl);
1082 in = nl + 1;
1084 if(in < e)
1085 p = decquotedline(p, in, e-1);
1087 // make sure we end with a new line
1088 if(*(p-1) != '\n'){
1089 *p++ = '\n';
1090 *p = 0;
1093 return p - out;
1096 /* translate latin1 directly since it fits neatly in utf */
1097 static int
1098 latin1toutf(char *out, char *in, char *e)
1100 Rune r;
1101 char *p;
1103 p = out;
1104 for(; in < e; in++){
1105 r = (*in) & 0xff;
1106 p += runetochar(p, &r);
1108 *p = 0;
1109 return p - out;