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 enum {
9 Accept = 0xA,
10 Discard = 0xD,
11 };
13 Biobuf in;
14 Biobuf out;
16 typedef struct Mtype Mtype;
17 typedef struct Hdef Hdef;
18 typedef struct Hline Hline;
19 typedef struct Part Part;
21 static int badfile(char *name);
22 static int badtype(char *type);
23 static void ctype(Part*, Hdef*, char*);
24 static void cencoding(Part*, Hdef*, char*);
25 static void cdisposition(Part*, Hdef*, char*);
26 static int decquoted(char *out, char *in, char *e);
27 static char* getstring(char *p, String *s, int dolower);
28 static void init_hdefs(void);
29 static int isattribute(char **pp, char *attr);
30 static int latin1toutf(char *out, char *in, char *e);
31 static String* mkboundary(void);
32 static Part* part(Part *pp);
33 static Part* passbody(Part *p, int dobound);
34 static void passnotheader(void);
35 static void passunixheader(void);
36 static Part* problemchild(Part *p);
37 static void readheader(Part *p);
38 static Hline* readhl(void);
39 static void readmtypes(void);
40 static int save(Part *p, char *file);
41 static void setfilename(Part *p, char *name);
42 static char* skiptosemi(char *p);
43 static char* skipwhite(char *p);
44 static String* tokenconvert(String *t);
45 static void writeheader(Part *p, int);
47 enum
48 {
49 /* encodings */
50 Enone= 0,
51 Ebase64,
52 Equoted,
54 /* disposition possibilities */
55 Dnone= 0,
56 Dinline,
57 Dfile,
58 Dignore,
60 PAD64= '='
61 };
63 /*
64 * a message part; either the whole message or a subpart
65 */
66 struct Part
67 {
68 Part *pp; /* parent part */
69 Hline *hl; /* linked list of header lines */
70 int disposition;
71 int encoding;
72 int badfile;
73 int badtype;
74 String *boundary; /* boundary for multiparts */
75 int blen;
76 String *charset; /* character set */
77 String *type; /* content type */
78 String *filename; /* file name */
79 Biobuf *tmpbuf; /* diversion input buffer */
80 };
82 /*
83 * a (multi)line header
84 */
85 struct Hline
86 {
87 Hline *next;
88 String *s;
89 };
91 /*
92 * header definitions for parsing
93 */
94 struct Hdef
95 {
96 char *type;
97 void (*f)(Part*, Hdef*, char*);
98 int len;
99 };
101 Hdef hdefs[] =
103 { "content-type:", ctype, },
104 { "content-transfer-encoding:", cencoding, },
105 { "content-disposition:", cdisposition, },
106 { 0, }
107 };
109 /*
110 * acceptable content types and their extensions
111 */
112 struct Mtype {
113 Mtype *next;
114 char *ext; /* extension */
115 char *gtype; /* generic content type */
116 char *stype; /* specific content type */
117 char class;
118 };
119 Mtype *mtypes;
121 int justreject;
122 char *savefile;
124 void
125 usage(void)
127 fprint(2, "usage: upas/vf [-r] [-s savefile]\n");
128 exits("usage");
131 void
132 main(int argc, char **argv)
134 ARGBEGIN{
135 case 'r':
136 justreject = 1;
137 break;
138 case 's':
139 savefile = EARGF(usage());
140 break;
141 default:
142 usage();
143 }ARGEND;
145 if(argc)
146 usage();
148 Binit(&in, 0, OREAD);
149 Binit(&out, 1, OWRITE);
151 init_hdefs();
152 readmtypes();
154 /* pass through our standard 'From ' line */
155 passunixheader();
157 /* parse with the top level part */
158 part(nil);
160 exits(0);
163 void
164 refuse(void)
166 postnote(PNGROUP, getpid(), "mail refused: we don't accept executable attachments");
167 exits("mail refused: we don't accept executable attachments");
171 /*
172 * parse a part; returns the ancestor whose boundary terminated
173 * this part or nil on EOF.
174 */
175 static Part*
176 part(Part *pp)
178 Part *p, *np;
180 p = mallocz(sizeof *p, 1);
181 p->pp = pp;
182 readheader(p);
184 if(p->boundary != nil){
185 /* the format of a multipart part is always:
186 * header
187 * null or ignored body
188 * boundary
189 * header
190 * body
191 * boundary
192 * ...
193 */
194 writeheader(p, 1);
195 np = passbody(p, 1);
196 if(np != p)
197 return np;
198 for(;;){
199 np = part(p);
200 if(np != p)
201 return np;
203 } else {
204 /* no boundary */
205 /* may still be multipart if this is a forwarded message */
206 if(p->type && cistrcmp(s_to_c(p->type), "message/rfc822") == 0){
207 /* the format of forwarded message is:
208 * header
209 * header
210 * body
211 */
212 writeheader(p, 1);
213 passnotheader();
214 return part(p);
215 } else {
216 /*
217 * This is the meat. This may be an executable.
218 * if so, wrap it and change its type
219 */
220 if(p->badtype || p->badfile){
221 if(p->badfile == 2){
222 if(savefile != nil)
223 save(p, savefile);
224 syslog(0, "vf", "vf rejected %s %s", p->type?s_to_c(p->type):"?",
225 p->filename?s_to_c(p->filename):"?");
226 fprint(2, "The mail contained an executable attachment.\n");
227 fprint(2, "We refuse all mail containing such.\n");
228 refuse();
230 np = problemchild(p);
231 if(np != p)
232 return np;
233 /* if problemchild returns p, it turns out p is okay: fall thru */
235 writeheader(p, 1);
236 return passbody(p, 1);
241 /*
242 * read and parse a complete header
243 */
244 static void
245 readheader(Part *p)
247 Hline *hl, **l;
248 Hdef *hd;
250 l = &p->hl;
251 for(;;){
252 hl = readhl();
253 if(hl == nil)
254 break;
255 *l = hl;
256 l = &hl->next;
258 for(hd = hdefs; hd->type != nil; hd++){
259 if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){
260 (*hd->f)(p, hd, s_to_c(hl->s));
261 break;
267 /*
268 * read a possibly multiline header line
269 */
270 static Hline*
271 readhl(void)
273 Hline *hl;
274 String *s;
275 char *p;
276 int n;
278 p = Brdline(&in, '\n');
279 if(p == nil)
280 return nil;
281 n = Blinelen(&in);
282 if(memchr(p, ':', n) == nil){
283 Bseek(&in, -n, 1);
284 return nil;
286 s = s_nappend(s_new(), p, n);
287 for(;;){
288 p = Brdline(&in, '\n');
289 if(p == nil)
290 break;
291 n = Blinelen(&in);
292 if(*p != ' ' && *p != '\t'){
293 Bseek(&in, -n, 1);
294 break;
296 s = s_nappend(s, p, n);
298 hl = malloc(sizeof *hl);
299 hl->s = s;
300 hl->next = nil;
301 return hl;
304 /*
305 * write out a complete header
306 */
307 static void
308 writeheader(Part *p, int xfree)
310 Hline *hl, *next;
312 for(hl = p->hl; hl != nil; hl = next){
313 Bprint(&out, "%s", s_to_c(hl->s));
314 if(xfree)
315 s_free(hl->s);
316 next = hl->next;
317 if(xfree)
318 free(hl);
320 if(xfree)
321 p->hl = nil;
324 /*
325 * pass a body through. return if we hit one of our ancestors'
326 * boundaries or EOF. if we hit a boundary, return a pointer to
327 * that ancestor. if we hit EOF, return nil.
328 */
329 static Part*
330 passbody(Part *p, int dobound)
332 Part *pp;
333 Biobuf *b;
334 char *cp;
336 for(;;){
337 if(p->tmpbuf){
338 b = p->tmpbuf;
339 cp = Brdline(b, '\n');
340 if(cp == nil){
341 Bterm(b);
342 p->tmpbuf = nil;
343 goto Stdin;
345 }else{
346 Stdin:
347 b = &in;
348 cp = Brdline(b, '\n');
350 if(cp == nil)
351 return nil;
352 for(pp = p; pp != nil; pp = pp->pp)
353 if(pp->boundary != nil
354 && strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){
355 if(dobound)
356 Bwrite(&out, cp, Blinelen(b));
357 else
358 Bseek(b, -Blinelen(b), 1);
359 return pp;
361 Bwrite(&out, cp, Blinelen(b));
363 return nil;
366 /*
367 * save the message somewhere
368 */
369 static vlong bodyoff; /* clumsy hack */
370 static int
371 save(Part *p, char *file)
373 int fd;
374 char *cp;
376 Bterm(&out);
377 memset(&out, 0, sizeof(out));
379 fd = open(file, OWRITE);
380 if(fd < 0)
381 return -1;
382 seek(fd, 0, 2);
383 Binit(&out, fd, OWRITE);
384 cp = ctime(time(0));
385 cp[28] = 0;
386 Bprint(&out, "From virusfilter %s\n", cp);
387 writeheader(p, 0);
388 bodyoff = Boffset(&out);
389 passbody(p, 1);
390 Bprint(&out, "\n");
391 Bterm(&out);
392 close(fd);
394 memset(&out, 0, sizeof out);
395 Binit(&out, 1, OWRITE);
396 return 0;
399 /*
400 * write to a file but save the fd for passbody.
401 */
402 static char*
403 savetmp(Part *p)
405 char buf[40], *name;
406 int fd;
408 strcpy(buf, "/var/tmp/vf.XXXXXXXXXXX");
409 if((fd = mkstemp(buf)) < 0){
410 fprint(2, "error creating temporary file: %r\n");
411 refuse();
413 name = buf;
414 close(fd);
415 if(save(p, name) < 0){
416 fprint(2, "error saving temporary file: %r\n");
417 refuse();
419 if(p->tmpbuf){
420 fprint(2, "error in savetmp: already have tmp file!\n");
421 refuse();
423 p->tmpbuf = Bopen(name, OREAD|ORCLOSE);
424 if(p->tmpbuf == nil){
425 fprint(2, "error reading tempoary file: %r\n");
426 refuse();
428 Bseek(p->tmpbuf, bodyoff, 0);
429 return strdup(name);
432 /*
433 * Run the external checker to do content-based checks.
434 */
435 static int
436 runchecker(Part *p)
438 int pid;
439 char *name;
440 Waitmsg *w;
441 static char *val;
443 if(val == nil)
444 val = unsharp("#9/mail/lib/validateattachment");
445 if(val == nil || access(val, AEXEC) < 0)
446 return 0;
448 name = savetmp(p);
449 fprint(2, "run checker %s\n", name);
450 switch(pid = fork()){
451 case -1:
452 sysfatal("fork: %r");
453 case 0:
454 dup(2, 1);
455 execl(val, "validateattachment", name, nil);
456 _exits("exec failed");
459 /*
460 * Okay to return on error - will let mail through but wrapped.
461 */
462 w = wait();
463 remove(name);
464 if(w == nil){
465 syslog(0, "mail", "vf wait failed: %r");
466 return 0;
468 if(w->pid != pid){
469 syslog(0, "mail", "vf wrong pid %d != %d", w->pid, pid);
470 return 0;
472 if(p->filename)
473 name = s_to_c(p->filename);
474 if(atoi(w->msg) == Discard){
475 syslog(0, "mail", "vf validateattachment rejected %s", name);
476 refuse();
478 if(atoi(w->msg) == Accept){
479 syslog(0, "mail", "vf validateattachment accepted %s", name);
480 return 1;
482 free(w);
483 return 0;
486 /*
487 * emit a multipart Part that explains the problem
488 */
489 static Part*
490 problemchild(Part *p)
492 Part *np;
493 Hline *hl;
494 String *boundary;
495 char *cp;
497 /*
498 * We don't know whether the attachment is okay.
499 * If there's an external checker, let it have a crack at it.
500 */
501 if(runchecker(p) > 0)
502 return p;
504 if(justreject)
505 return p;
507 syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?",
508 p->filename?s_to_c(p->filename):"?");
510 boundary = mkboundary();
511 /* print out non-mime headers */
512 for(hl = p->hl; hl != nil; hl = hl->next)
513 if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0)
514 Bprint(&out, "%s", s_to_c(hl->s));
516 /* add in our own multipart headers and message */
517 Bprint(&out, "Content-Type: multipart/mixed;\n");
518 Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary));
519 Bprint(&out, "Content-Disposition: inline\n");
520 Bprint(&out, "\n");
521 Bprint(&out, "This is a multi-part message in MIME format.\n");
522 Bprint(&out, "--%s\n", s_to_c(boundary));
523 Bprint(&out, "Content-Disposition: inline\n");
524 Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
525 Bprint(&out, "Content-Transfer-Encoding: 7bit\n");
526 Bprint(&out, "\n");
527 Bprint(&out, "from postmaster@%s:\n", sysname());
528 Bprint(&out, "The following attachment had content that we can't\n");
529 Bprint(&out, "prove to be harmless. To avoid possible automatic\n");
530 Bprint(&out, "execution, we changed the content headers.\n");
531 Bprint(&out, "The original header was:\n\n");
533 /* print out original header lines */
534 for(hl = p->hl; hl != nil; hl = hl->next)
535 if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0)
536 Bprint(&out, "\t%s", s_to_c(hl->s));
537 Bprint(&out, "--%s\n", s_to_c(boundary));
539 /* change file name */
540 if(p->filename)
541 s_append(p->filename, ".suspect");
542 else
543 p->filename = s_copy("file.suspect");
545 /* print out new header */
546 Bprint(&out, "Content-Type: application/octet-stream\n");
547 Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename));
548 switch(p->encoding){
549 case Enone:
550 break;
551 case Ebase64:
552 Bprint(&out, "Content-Transfer-Encoding: base64\n");
553 break;
554 case Equoted:
555 Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n");
556 break;
559 /* pass the body */
560 np = passbody(p, 0);
562 /* add the new boundary and the original terminator */
563 Bprint(&out, "--%s--\n", s_to_c(boundary));
564 if(np && np->boundary){
565 cp = Brdline(&in, '\n');
566 Bwrite(&out, cp, Blinelen(&in));
569 return np;
572 static int
573 isattribute(char **pp, char *attr)
575 char *p;
576 int n;
578 n = strlen(attr);
579 p = *pp;
580 if(cistrncmp(p, attr, n) != 0)
581 return 0;
582 p += n;
583 while(*p == ' ')
584 p++;
585 if(*p++ != '=')
586 return 0;
587 while(*p == ' ')
588 p++;
589 *pp = p;
590 return 1;
593 /*
594 * parse content type header
595 */
596 static void
597 ctype(Part *p, Hdef *h, char *cp)
599 String *s;
601 cp += h->len;
602 cp = skipwhite(cp);
604 p->type = s_new();
605 cp = getstring(cp, p->type, 1);
606 if(badtype(s_to_c(p->type)))
607 p->badtype = 1;
609 while(*cp){
610 if(isattribute(&cp, "boundary")){
611 s = s_new();
612 cp = getstring(cp, s, 0);
613 p->boundary = s_reset(p->boundary);
614 s_append(p->boundary, "--");
615 s_append(p->boundary, s_to_c(s));
616 p->blen = s_len(p->boundary);
617 s_free(s);
618 } else if(cistrncmp(cp, "multipart", 9) == 0){
619 /*
620 * the first unbounded part of a multipart message,
621 * the preamble, is not displayed or saved
622 */
623 } else if(isattribute(&cp, "name")){
624 setfilename(p, cp);
625 } else if(isattribute(&cp, "charset")){
626 if(p->charset == nil)
627 p->charset = s_new();
628 cp = getstring(cp, s_reset(p->charset), 0);
631 cp = skiptosemi(cp);
635 /*
636 * parse content encoding header
637 */
638 static void
639 cencoding(Part *m, Hdef *h, char *p)
641 p += h->len;
642 p = skipwhite(p);
643 if(cistrncmp(p, "base64", 6) == 0)
644 m->encoding = Ebase64;
645 else if(cistrncmp(p, "quoted-printable", 16) == 0)
646 m->encoding = Equoted;
649 /*
650 * parse content disposition header
651 */
652 static void
653 cdisposition(Part *p, Hdef *h, char *cp)
655 cp += h->len;
656 cp = skipwhite(cp);
657 while(*cp){
658 if(cistrncmp(cp, "inline", 6) == 0){
659 p->disposition = Dinline;
660 } else if(cistrncmp(cp, "attachment", 10) == 0){
661 p->disposition = Dfile;
662 } else if(cistrncmp(cp, "filename=", 9) == 0){
663 cp += 9;
664 setfilename(p, cp);
666 cp = skiptosemi(cp);
671 static void
672 setfilename(Part *p, char *name)
674 if(p->filename == nil)
675 p->filename = s_new();
676 getstring(name, s_reset(p->filename), 0);
677 p->filename = tokenconvert(p->filename);
678 p->badfile = badfile(s_to_c(p->filename));
681 static char*
682 skipwhite(char *p)
684 while(isspace(*p))
685 p++;
686 return p;
689 static char*
690 skiptosemi(char *p)
692 while(*p && *p != ';')
693 p++;
694 while(*p == ';' || isspace(*p))
695 p++;
696 return p;
699 /*
700 * parse a possibly "'d string from a header. A
701 * ';' terminates the string.
702 */
703 static char*
704 getstring(char *p, String *s, int dolower)
706 s = s_reset(s);
707 p = skipwhite(p);
708 if(*p == '"'){
709 p++;
710 for(;*p && *p != '"'; p++)
711 if(dolower)
712 s_putc(s, tolower(*p));
713 else
714 s_putc(s, *p);
715 if(*p == '"')
716 p++;
717 s_terminate(s);
719 return p;
722 for(; *p && !isspace(*p) && *p != ';'; p++)
723 if(dolower)
724 s_putc(s, tolower(*p));
725 else
726 s_putc(s, *p);
727 s_terminate(s);
729 return p;
732 static void
733 init_hdefs(void)
735 Hdef *hd;
736 static int already;
738 if(already)
739 return;
740 already = 1;
742 for(hd = hdefs; hd->type != nil; hd++)
743 hd->len = strlen(hd->type);
746 /*
747 * create a new boundary
748 */
749 static String*
750 mkboundary(void)
752 char buf[32];
753 int i;
754 static int already;
756 if(already == 0){
757 srand((time(0)<<16)|getpid());
758 already = 1;
760 strcpy(buf, "upas-");
761 for(i = 5; i < sizeof(buf)-1; i++)
762 buf[i] = 'a' + nrand(26);
763 buf[i] = 0;
764 return s_copy(buf);
767 /*
768 * skip blank lines till header
769 */
770 static void
771 passnotheader(void)
773 char *cp;
774 int i, n;
776 while((cp = Brdline(&in, '\n')) != nil){
777 n = Blinelen(&in);
778 for(i = 0; i < n-1; i++)
779 if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){
780 Bseek(&in, -n, 1);
781 return;
783 Bwrite(&out, cp, n);
787 /*
788 * pass unix header lines
789 */
790 static void
791 passunixheader(void)
793 char *p;
794 int n;
796 while((p = Brdline(&in, '\n')) != nil){
797 n = Blinelen(&in);
798 if(strncmp(p, "From ", 5) != 0){
799 Bseek(&in, -n, 1);
800 break;
802 Bwrite(&out, p, n);
806 /*
807 * Read mime types
808 */
809 static void
810 readmtypes(void)
812 Biobuf *b;
813 char *p;
814 char *f[6];
815 Mtype *m;
816 Mtype **l;
818 b = Bopen(unsharp("#9/lib/mimetype"), OREAD);
819 if(b == nil)
820 return;
822 l = &mtypes;
823 while((p = Brdline(b, '\n')) != nil){
824 if(*p == '#')
825 continue;
826 p[Blinelen(b)-1] = 0;
827 if(tokenize(p, f, nelem(f)) < 5)
828 continue;
829 m = mallocz(sizeof *m, 1);
830 if(m == nil)
831 goto err;
832 m->ext = strdup(f[0]);
833 if(m->ext == 0)
834 goto err;
835 m->gtype = strdup(f[1]);
836 if(m->gtype == 0)
837 goto err;
838 m->stype = strdup(f[2]);
839 if(m->stype == 0)
840 goto err;
841 m->class = *f[4];
842 *l = m;
843 l = &(m->next);
845 Bterm(b);
846 return;
847 err:
848 if(m == nil)
849 return;
850 free(m->ext);
851 free(m->gtype);
852 free(m->stype);
853 free(m);
854 Bterm(b);
857 /*
858 * if the class is 'm' or 'y', accept it
859 * if the class is 'p' check a previous extension
860 * otherwise, filename is bad
861 */
862 static int
863 badfile(char *name)
865 char *p;
866 Mtype *m;
867 int rv;
869 p = strrchr(name, '.');
870 if(p == nil)
871 return 0;
873 for(m = mtypes; m != nil; m = m->next)
874 if(cistrcmp(p, m->ext) == 0){
875 switch(m->class){
876 case 'm':
877 case 'y':
878 return 0;
879 case 'p':
880 *p = 0;
881 rv = badfile(name);
882 *p = '.';
883 return rv;
884 case 'r':
885 return 2;
888 return 1;
891 /*
892 * if the class is 'm' or 'y' or 'p', accept it
893 * otherwise, filename is bad
894 */
895 static int
896 badtype(char *type)
898 Mtype *m;
899 char *s, *fix;
900 int rv = 1;
902 fix = s = strchr(type, '/');
903 if(s != nil)
904 *s++ = 0;
905 else
906 s = "-";
908 for(m = mtypes; m != nil; m = m->next){
909 if(cistrcmp(type, m->gtype) != 0)
910 continue;
911 if(cistrcmp(s, m->stype) != 0)
912 continue;
913 switch(m->class){
914 case 'y':
915 case 'p':
916 case 'm':
917 rv = 0;
918 break;
920 break;
923 if(fix != nil)
924 *fix = '/';
925 return rv;
928 /* rfc2047 non-ascii */
929 typedef struct Charset Charset;
930 struct Charset {
931 char *name;
932 int len;
933 int convert;
934 } charsets[] =
936 { "us-ascii", 8, 1, },
937 { "utf-8", 5, 0, },
938 { "iso-8859-1", 10, 1, }
939 };
941 /*
942 * convert to UTF if need be
943 */
944 static String*
945 tokenconvert(String *t)
947 String *s;
948 char decoded[1024];
949 char utfbuf[2*1024];
950 int i, len;
951 char *e;
952 char *token;
954 token = s_to_c(t);
955 len = s_len(t);
957 if(token[0] != '=' || token[1] != '?' ||
958 token[len-2] != '?' || token[len-1] != '=')
959 goto err;
960 e = token+len-2;
961 token += 2;
963 /* bail if we don't understand the character set */
964 for(i = 0; i < nelem(charsets); i++)
965 if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
966 if(token[charsets[i].len] == '?'){
967 token += charsets[i].len + 1;
968 break;
970 if(i >= nelem(charsets))
971 goto err;
973 /* bail if it doesn't fit */
974 if(strlen(token) > sizeof(decoded)-1)
975 goto err;
977 /* bail if we don't understand the encoding */
978 if(cistrncmp(token, "b?", 2) == 0){
979 token += 2;
980 len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
981 decoded[len] = 0;
982 } else if(cistrncmp(token, "q?", 2) == 0){
983 token += 2;
984 len = decquoted(decoded, token, e);
985 if(len > 0 && decoded[len-1] == '\n')
986 len--;
987 decoded[len] = 0;
988 } else
989 goto err;
991 s = nil;
992 switch(charsets[i].convert){
993 case 0:
994 s = s_copy(decoded);
995 break;
996 case 1:
997 s = s_new();
998 latin1toutf(utfbuf, decoded, decoded+len);
999 s_append(s, utfbuf);
1000 break;
1003 return s;
1004 err:
1005 return s_clone(t);
1009 * decode quoted
1011 enum
1013 Self= 1,
1014 Hex= 2
1016 uchar tableqp[256];
1018 static void
1019 initquoted(void)
1021 int c;
1023 memset(tableqp, 0, 256);
1024 for(c = ' '; c <= '<'; c++)
1025 tableqp[c] = Self;
1026 for(c = '>'; c <= '~'; c++)
1027 tableqp[c] = Self;
1028 tableqp['\t'] = Self;
1029 tableqp['='] = Hex;
1032 static int
1033 hex2int(int x)
1035 if(x >= '0' && x <= '9')
1036 return x - '0';
1037 if(x >= 'A' && x <= 'F')
1038 return (x - 'A') + 10;
1039 if(x >= 'a' && x <= 'f')
1040 return (x - 'a') + 10;
1041 return 0;
1044 static char*
1045 decquotedline(char *out, char *in, char *e)
1047 int c, soft;
1049 /* dump trailing white space */
1050 while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
1051 e--;
1053 /* trailing '=' means no newline */
1054 if(*e == '='){
1055 soft = 1;
1056 e--;
1057 } else
1058 soft = 0;
1060 while(in <= e){
1061 c = (*in++) & 0xff;
1062 switch(tableqp[c]){
1063 case Self:
1064 *out++ = c;
1065 break;
1066 case Hex:
1067 c = hex2int(*in++)<<4;
1068 c |= hex2int(*in++);
1069 *out++ = c;
1070 break;
1073 if(!soft)
1074 *out++ = '\n';
1075 *out = 0;
1077 return out;
1080 static int
1081 decquoted(char *out, char *in, char *e)
1083 char *p, *nl;
1085 if(tableqp[' '] == 0)
1086 initquoted();
1088 p = out;
1089 while((nl = strchr(in, '\n')) != nil && nl < e){
1090 p = decquotedline(p, in, nl);
1091 in = nl + 1;
1093 if(in < e)
1094 p = decquotedline(p, in, e-1);
1096 /* make sure we end with a new line */
1097 if(*(p-1) != '\n'){
1098 *p++ = '\n';
1099 *p = 0;
1102 return p - out;
1105 /* translate latin1 directly since it fits neatly in utf */
1106 static int
1107 latin1toutf(char *out, char *in, char *e)
1109 Rune r;
1110 char *p;
1112 p = out;
1113 for(; in < e; in++){
1114 r = (*in) & 0xff;
1115 p += runetochar(p, &r);
1117 *p = 0;
1118 return p - out;