diff options
author | rsc <devnull@localhost> | 2005-10-29 16:26:44 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-10-29 16:26:44 +0000 |
commit | 5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch) | |
tree | 8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/vf/vf.c | |
parent | cd3745196389579fb78b9b01ef1daefb5a57aa71 (diff) | |
download | plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2 plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip |
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/vf/vf.c')
-rw-r--r-- | src/cmd/upas/vf/vf.c | 1110 |
1 files changed, 1110 insertions, 0 deletions
diff --git a/src/cmd/upas/vf/vf.c b/src/cmd/upas/vf/vf.c new file mode 100644 index 00000000..db4f5ce8 --- /dev/null +++ b/src/cmd/upas/vf/vf.c @@ -0,0 +1,1110 @@ +/* + * this is a filter that changes mime types and names of + * suspect executable attachments. + */ +#include "common.h" +#include <ctype.h> + +Biobuf in; +Biobuf out; + +typedef struct Mtype Mtype; +typedef struct Hdef Hdef; +typedef struct Hline Hline; +typedef struct Part Part; + +static int badfile(char *name); +static int badtype(char *type); +static void ctype(Part*, Hdef*, char*); +static void cencoding(Part*, Hdef*, char*); +static void cdisposition(Part*, Hdef*, char*); +static int decquoted(char *out, char *in, char *e); +static char* getstring(char *p, String *s, int dolower); +static void init_hdefs(void); +static int isattribute(char **pp, char *attr); +static int latin1toutf(char *out, char *in, char *e); +static String* mkboundary(void); +static Part* part(Part *pp); +static Part* passbody(Part *p, int dobound); +static void passnotheader(void); +static void passunixheader(void); +static Part* problemchild(Part *p); +static void readheader(Part *p); +static Hline* readhl(void); +static void readmtypes(void); +static int save(Part *p, char *file); +static void setfilename(Part *p, char *name); +static char* skiptosemi(char *p); +static char* skipwhite(char *p); +static String* tokenconvert(String *t); +static void writeheader(Part *p, int); + +enum +{ + // encodings + Enone= 0, + Ebase64, + Equoted, + + // disposition possibilities + Dnone= 0, + Dinline, + Dfile, + Dignore, + + PAD64= '=', +}; + +/* + * a message part; either the whole message or a subpart + */ +struct Part +{ + Part *pp; /* parent part */ + Hline *hl; /* linked list of header lines */ + int disposition; + int encoding; + int badfile; + int badtype; + String *boundary; /* boundary for multiparts */ + int blen; + String *charset; /* character set */ + String *type; /* content type */ + String *filename; /* file name */ + Biobuf *tmpbuf; /* diversion input buffer */ +}; + +/* + * a (multi)line header + */ +struct Hline +{ + Hline *next; + String *s; +}; + +/* + * header definitions for parsing + */ +struct Hdef +{ + char *type; + void (*f)(Part*, Hdef*, char*); + int len; +}; + +Hdef hdefs[] = +{ + { "content-type:", ctype, }, + { "content-transfer-encoding:", cencoding, }, + { "content-disposition:", cdisposition, }, + { 0, }, +}; + +/* + * acceptable content types and their extensions + */ +struct Mtype { + Mtype *next; + char *ext; /* extension */ + char *gtype; /* generic content type */ + char *stype; /* specific content type */ + char class; +}; +Mtype *mtypes; + +int justreject; +char *savefile; + +void +main(int argc, char **argv) +{ + ARGBEGIN{ + case 'r': + justreject = 1; + break; + case 's': + savefile = ARGF(); + if(savefile == nil) + exits("usage"); + break; + }ARGEND; + + Binit(&in, 0, OREAD); + Binit(&out, 1, OWRITE); + + init_hdefs(); + readmtypes(); + + /* pass through our standard 'From ' line */ + passunixheader(); + + /* parse with the top level part */ + part(nil); + + exits(0); +} + +void +refuse(void) +{ + postnote(PNGROUP, getpid(), "mail refused: we don't accept executable attachments"); + exits("mail refused: we don't accept executable attachments"); +} + + +/* + * parse a part; returns the ancestor whose boundary terminated + * this part or nil on EOF. + */ +static Part* +part(Part *pp) +{ + Part *p, *np; + + p = mallocz(sizeof *p, 1); + p->pp = pp; + readheader(p); + + if(p->boundary != nil){ + /* the format of a multipart part is always: + * header + * null or ignored body + * boundary + * header + * body + * boundary + * ... + */ + writeheader(p, 1); + np = passbody(p, 1); + if(np != p) + return np; + for(;;){ + np = part(p); + if(np != p) + return np; + } + } else { + /* no boundary */ + /* may still be multipart if this is a forwarded message */ + if(p->type && cistrcmp(s_to_c(p->type), "message/rfc822") == 0){ + /* the format of forwarded message is: + * header + * header + * body + */ + writeheader(p, 1); + passnotheader(); + return part(p); + } else { + /* + * This is the meat. This may be an executable. + * if so, wrap it and change its type + */ + if(p->badtype || p->badfile){ + if(p->badfile == 2){ + if(savefile != nil) + save(p, savefile); + syslog(0, "vf", "vf rejected %s %s", p->type?s_to_c(p->type):"?", + p->filename?s_to_c(p->filename):"?"); + fprint(2, "The mail contained an executable attachment.\n"); + fprint(2, "We refuse all mail containing such.\n"); + refuse(); + } + np = problemchild(p); + if(np != p) + return np; + /* if problemchild returns p, it turns out p is okay: fall thru */ + } + writeheader(p, 1); + return passbody(p, 1); + } + } +} + +/* + * read and parse a complete header + */ +static void +readheader(Part *p) +{ + Hline *hl, **l; + Hdef *hd; + + l = &p->hl; + for(;;){ + hl = readhl(); + if(hl == nil) + break; + *l = hl; + l = &hl->next; + + for(hd = hdefs; hd->type != nil; hd++){ + if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){ + (*hd->f)(p, hd, s_to_c(hl->s)); + break; + } + } + } +} + +/* + * read a possibly multiline header line + */ +static Hline* +readhl(void) +{ + Hline *hl; + String *s; + char *p; + int n; + + p = Brdline(&in, '\n'); + if(p == nil) + return nil; + n = Blinelen(&in); + if(memchr(p, ':', n) == nil){ + Bseek(&in, -n, 1); + return nil; + } + s = s_nappend(s_new(), p, n); + for(;;){ + p = Brdline(&in, '\n'); + if(p == nil) + break; + n = Blinelen(&in); + if(*p != ' ' && *p != '\t'){ + Bseek(&in, -n, 1); + break; + } + s = s_nappend(s, p, n); + } + hl = malloc(sizeof *hl); + hl->s = s; + hl->next = nil; + return hl; +} + +/* + * write out a complete header + */ +static void +writeheader(Part *p, int xfree) +{ + Hline *hl, *next; + + for(hl = p->hl; hl != nil; hl = next){ + Bprint(&out, "%s", s_to_c(hl->s)); + if(xfree) + s_free(hl->s); + next = hl->next; + if(xfree) + free(hl); + } + if(xfree) + p->hl = nil; +} + +/* + * pass a body through. return if we hit one of our ancestors' + * boundaries or EOF. if we hit a boundary, return a pointer to + * that ancestor. if we hit EOF, return nil. + */ +static Part* +passbody(Part *p, int dobound) +{ + Part *pp; + Biobuf *b; + char *cp; + + for(;;){ + if(p->tmpbuf){ + b = p->tmpbuf; + cp = Brdline(b, '\n'); + if(cp == nil){ + Bterm(b); + p->tmpbuf = nil; + goto Stdin; + } + }else{ + Stdin: + b = ∈ + cp = Brdline(b, '\n'); + } + if(cp == nil) + return nil; + for(pp = p; pp != nil; pp = pp->pp) + if(pp->boundary != nil + && strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){ + if(dobound) + Bwrite(&out, cp, Blinelen(b)); + else + Bseek(b, -Blinelen(b), 1); + return pp; + } + Bwrite(&out, cp, Blinelen(b)); + } + return nil; +} + +/* + * save the message somewhere + */ +static vlong bodyoff; /* clumsy hack */ +static int +save(Part *p, char *file) +{ + int fd; + char *cp; + + Bterm(&out); + memset(&out, 0, sizeof(out)); + + fd = open(file, OWRITE); + if(fd < 0) + return -1; + seek(fd, 0, 2); + Binit(&out, fd, OWRITE); + cp = ctime(time(0)); + cp[28] = 0; + Bprint(&out, "From virusfilter %s\n", cp); + writeheader(p, 0); + bodyoff = Boffset(&out); + passbody(p, 1); + Bprint(&out, "\n"); + Bterm(&out); + close(fd); + + memset(&out, 0, sizeof out); + Binit(&out, 1, OWRITE); + return 0; +} + +/* + * write to a file but save the fd for passbody. + */ +static char* +savetmp(Part *p) +{ + char buf[40], *name; + int fd; + + strcpy(buf, "/tmp/vf.XXXXXXXXXXX"); + name = mktemp(buf); + if((fd = create(name, OWRITE|OEXCL, 0666)) < 0){ + fprint(2, "error creating temporary file: %r\n"); + refuse(); + } + close(fd); + if(save(p, name) < 0){ + fprint(2, "error saving temporary file: %r\n"); + refuse(); + } + if(p->tmpbuf){ + fprint(2, "error in savetmp: already have tmp file!\n"); + refuse(); + } + p->tmpbuf = Bopen(name, OREAD|ORCLOSE); + if(p->tmpbuf == nil){ + fprint(2, "error reading tempoary file: %r\n"); + refuse(); + } + Bseek(p->tmpbuf, bodyoff, 0); + return strdup(name); +} + +/* + * XXX save the decoded file, run 9 unzip -tf on it, and then + * look at the file list. + */ +static int +runchecker(Part *p) +{ + int pid; + char *name; + Waitmsg *w; + + if(access("/mail/lib/validateattachment", AEXEC) < 0) + return 0; + + name = savetmp(p); + fprint(2, "run checker %s\n", name); + switch(pid = fork()){ + case -1: + sysfatal("fork: %r"); + case 0: + dup(2, 1); + execl("/mail/lib/validateattachment", "validateattachment", name, nil); + _exits("exec failed"); + } + + /* + * Okay to return on error - will let mail through but wrapped. + */ + w = wait(); + if(w == nil){ + syslog(0, "mail", "vf wait failed: %r"); + return 0; + } + if(w->pid != pid){ + syslog(0, "mail", "vf wrong pid %d != %d", w->pid, pid); + return 0; + } + if(p->filename) + name = s_to_c(p->filename); + if(strstr(w->msg, "discard")){ + syslog(0, "mail", "vf validateattachment rejected %s", name); + refuse(); + } + if(strstr(w->msg, "accept")){ + syslog(0, "mail", "vf validateattachment accepted %s", name); + return 1; + } + free(w); + return 0; +} + +/* + * emit a multipart Part that explains the problem + */ +static Part* +problemchild(Part *p) +{ + Part *np; + Hline *hl; + String *boundary; + char *cp; + + /* + * We don't know whether the attachment is okay. + * If there's an external checker, let it have a crack at it. + */ + if(runchecker(p) > 0) + return p; + +fprint(2, "x\n"); + syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?", + p->filename?s_to_c(p->filename):"?"); +fprint(2, "x\n"); + + boundary = mkboundary(); +fprint(2, "x\n"); + /* print out non-mime headers */ + for(hl = p->hl; hl != nil; hl = hl->next) + if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0) + Bprint(&out, "%s", s_to_c(hl->s)); + +fprint(2, "x\n"); + /* add in our own multipart headers and message */ + Bprint(&out, "Content-Type: multipart/mixed;\n"); + Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary)); + Bprint(&out, "Content-Disposition: inline\n"); + Bprint(&out, "\n"); + Bprint(&out, "This is a multi-part message in MIME format.\n"); + Bprint(&out, "--%s\n", s_to_c(boundary)); + Bprint(&out, "Content-Disposition: inline\n"); + Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n"); + Bprint(&out, "Content-Transfer-Encoding: 7bit\n"); + Bprint(&out, "\n"); + Bprint(&out, "from postmaster@%s:\n", sysname()); + Bprint(&out, "The following attachment had content that we can't\n"); + Bprint(&out, "prove to be harmless. To avoid possible automatic\n"); + Bprint(&out, "execution, we changed the content headers.\n"); + Bprint(&out, "The original header was:\n\n"); + + /* print out original header lines */ + for(hl = p->hl; hl != nil; hl = hl->next) + if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0) + Bprint(&out, "\t%s", s_to_c(hl->s)); + Bprint(&out, "--%s\n", s_to_c(boundary)); + + /* change file name */ + if(p->filename) + s_append(p->filename, ".suspect"); + else + p->filename = s_copy("file.suspect"); + + /* print out new header */ + Bprint(&out, "Content-Type: application/octet-stream\n"); + Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename)); + switch(p->encoding){ + case Enone: + break; + case Ebase64: + Bprint(&out, "Content-Transfer-Encoding: base64\n"); + break; + case Equoted: + Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n"); + break; + } + +fprint(2, "z\n"); + /* pass the body */ + np = passbody(p, 0); + +fprint(2, "w\n"); + /* add the new boundary and the original terminator */ + Bprint(&out, "--%s--\n", s_to_c(boundary)); + if(np && np->boundary){ + cp = Brdline(&in, '\n'); + Bwrite(&out, cp, Blinelen(&in)); + } + +fprint(2, "a %p\n", np); + return np; +} + +static int +isattribute(char **pp, char *attr) +{ + char *p; + int n; + + n = strlen(attr); + p = *pp; + if(cistrncmp(p, attr, n) != 0) + return 0; + p += n; + while(*p == ' ') + p++; + if(*p++ != '=') + return 0; + while(*p == ' ') + p++; + *pp = p; + return 1; +} + +/* + * parse content type header + */ +static void +ctype(Part *p, Hdef *h, char *cp) +{ + String *s; + + cp += h->len; + cp = skipwhite(cp); + + p->type = s_new(); + cp = getstring(cp, p->type, 1); + if(badtype(s_to_c(p->type))) + p->badtype = 1; + + while(*cp){ + if(isattribute(&cp, "boundary")){ + s = s_new(); + cp = getstring(cp, s, 0); + p->boundary = s_reset(p->boundary); + s_append(p->boundary, "--"); + s_append(p->boundary, s_to_c(s)); + p->blen = s_len(p->boundary); + s_free(s); + } else if(cistrncmp(cp, "multipart", 9) == 0){ + /* + * the first unbounded part of a multipart message, + * the preamble, is not displayed or saved + */ + } else if(isattribute(&cp, "name")){ + setfilename(p, cp); + } else if(isattribute(&cp, "charset")){ + if(p->charset == nil) + p->charset = s_new(); + cp = getstring(cp, s_reset(p->charset), 0); + } + + cp = skiptosemi(cp); + } +} + +/* + * parse content encoding header + */ +static void +cencoding(Part *m, Hdef *h, char *p) +{ + p += h->len; + p = skipwhite(p); + if(cistrncmp(p, "base64", 6) == 0) + m->encoding = Ebase64; + else if(cistrncmp(p, "quoted-printable", 16) == 0) + m->encoding = Equoted; +} + +/* + * parse content disposition header + */ +static void +cdisposition(Part *p, Hdef *h, char *cp) +{ + cp += h->len; + cp = skipwhite(cp); + while(*cp){ + if(cistrncmp(cp, "inline", 6) == 0){ + p->disposition = Dinline; + } else if(cistrncmp(cp, "attachment", 10) == 0){ + p->disposition = Dfile; + } else if(cistrncmp(cp, "filename=", 9) == 0){ + cp += 9; + setfilename(p, cp); + } + cp = skiptosemi(cp); + } + +} + +static void +setfilename(Part *p, char *name) +{ + if(p->filename == nil) + p->filename = s_new(); + getstring(name, s_reset(p->filename), 0); + p->filename = tokenconvert(p->filename); + p->badfile = badfile(s_to_c(p->filename)); +} + +static char* +skipwhite(char *p) +{ + while(isspace(*p)) + p++; + return p; +} + +static char* +skiptosemi(char *p) +{ + while(*p && *p != ';') + p++; + while(*p == ';' || isspace(*p)) + p++; + return p; +} + +/* + * parse a possibly "'d string from a header. A + * ';' terminates the string. + */ +static char* +getstring(char *p, String *s, int dolower) +{ + s = s_reset(s); + p = skipwhite(p); + if(*p == '"'){ + p++; + for(;*p && *p != '"'; p++) + if(dolower) + s_putc(s, tolower(*p)); + else + s_putc(s, *p); + if(*p == '"') + p++; + s_terminate(s); + + return p; + } + + for(; *p && !isspace(*p) && *p != ';'; p++) + if(dolower) + s_putc(s, tolower(*p)); + else + s_putc(s, *p); + s_terminate(s); + + return p; +} + +static void +init_hdefs(void) +{ + Hdef *hd; + static int already; + + if(already) + return; + already = 1; + + for(hd = hdefs; hd->type != nil; hd++) + hd->len = strlen(hd->type); +} + +/* + * create a new boundary + */ +static String* +mkboundary(void) +{ + char buf[32]; + int i; + static int already; + + if(already == 0){ + srand((time(0)<<16)|getpid()); + already = 1; + } + strcpy(buf, "upas-"); + for(i = 5; i < sizeof(buf)-1; i++) + buf[i] = 'a' + nrand(26); + buf[i] = 0; + return s_copy(buf); +} + +/* + * skip blank lines till header + */ +static void +passnotheader(void) +{ + char *cp; + int i, n; + + while((cp = Brdline(&in, '\n')) != nil){ + n = Blinelen(&in); + for(i = 0; i < n-1; i++) + if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){ + Bseek(&in, -n, 1); + return; + } + Bwrite(&out, cp, n); + } +} + +/* + * pass unix header lines + */ +static void +passunixheader(void) +{ + char *p; + int n; + + while((p = Brdline(&in, '\n')) != nil){ + n = Blinelen(&in); + if(strncmp(p, "From ", 5) != 0){ + Bseek(&in, -n, 1); + break; + } + Bwrite(&out, p, n); + } +} + +/* + * Read mime types + */ +static void +readmtypes(void) +{ + Biobuf *b; + char *p; + char *f[6]; + Mtype *m; + Mtype **l; + + b = Bopen(unsharp("#9/sys/lib/mimetype"), OREAD); + if(b == nil) + return; + + l = &mtypes; + while((p = Brdline(b, '\n')) != nil){ + if(*p == '#') + continue; + p[Blinelen(b)-1] = 0; + if(tokenize(p, f, nelem(f)) < 5) + continue; + m = mallocz(sizeof *m, 1); + if(m == nil) + goto err; + m->ext = strdup(f[0]); + if(m->ext == 0) + goto err; + m->gtype = strdup(f[1]); + if(m->gtype == 0) + goto err; + m->stype = strdup(f[2]); + if(m->stype == 0) + goto err; + m->class = *f[4]; + *l = m; + l = &(m->next); + } + Bterm(b); + return; +err: + if(m == nil) + return; + free(m->ext); + free(m->gtype); + free(m->stype); + free(m); + Bterm(b); +} + +/* + * if the class is 'm' or 'y', accept it + * if the class is 'p' check a previous extension + * otherwise, filename is bad + */ +static int +badfile(char *name) +{ + char *p; + Mtype *m; + int rv; + + p = strrchr(name, '.'); + if(p == nil) + return 0; + + for(m = mtypes; m != nil; m = m->next) + if(cistrcmp(p, m->ext) == 0){ + switch(m->class){ + case 'm': + case 'y': + return 0; + case 'p': + *p = 0; + rv = badfile(name); + *p = '.'; + return rv; + case 'r': + return 2; + } + } + if(justreject) + return 0; + return 1; +} + +/* + * if the class is 'm' or 'y' or 'p', accept it + * otherwise, filename is bad + */ +static int +badtype(char *type) +{ + Mtype *m; + char *s, *fix; + int rv = 1; + + if(justreject) + return 0; + + fix = s = strchr(type, '/'); + if(s != nil) + *s++ = 0; + else + s = "-"; + + for(m = mtypes; m != nil; m = m->next){ + if(cistrcmp(type, m->gtype) != 0) + continue; + if(cistrcmp(s, m->stype) != 0) + continue; + switch(m->class){ + case 'y': + case 'p': + case 'm': + rv = 0; + break; + } + break; + } + + if(fix != nil) + *fix = '/'; + return rv; +} + +/* rfc2047 non-ascii */ +typedef struct Charset Charset; +struct Charset { + char *name; + int len; + int convert; +} charsets[] = +{ + { "us-ascii", 8, 1, }, + { "utf-8", 5, 0, }, + { "iso-8859-1", 10, 1, }, +}; + +/* + * convert to UTF if need be + */ +static String* +tokenconvert(String *t) +{ + String *s; + char decoded[1024]; + char utfbuf[2*1024]; + int i, len; + char *e; + char *token; + + token = s_to_c(t); + len = s_len(t); + + if(token[0] != '=' || token[1] != '?' || + token[len-2] != '?' || token[len-1] != '=') + goto err; + e = token+len-2; + token += 2; + + // bail if we don't understand the character set + for(i = 0; i < nelem(charsets); i++) + if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0) + if(token[charsets[i].len] == '?'){ + token += charsets[i].len + 1; + break; + } + if(i >= nelem(charsets)) + goto err; + + // bail if it doesn't fit + if(strlen(token) > sizeof(decoded)-1) + goto err; + + // bail if we don't understand the encoding + if(cistrncmp(token, "b?", 2) == 0){ + token += 2; + len = dec64((uchar*)decoded, sizeof(decoded), token, e-token); + decoded[len] = 0; + } else if(cistrncmp(token, "q?", 2) == 0){ + token += 2; + len = decquoted(decoded, token, e); + if(len > 0 && decoded[len-1] == '\n') + len--; + decoded[len] = 0; + } else + goto err; + + s = nil; + switch(charsets[i].convert){ + case 0: + s = s_copy(decoded); + break; + case 1: + s = s_new(); + latin1toutf(utfbuf, decoded, decoded+len); + s_append(s, utfbuf); + break; + } + + return s; +err: + return s_clone(t); +} + +/* + * decode quoted + */ +enum +{ + Self= 1, + Hex= 2, +}; +uchar tableqp[256]; + +static void +initquoted(void) +{ + int c; + + memset(tableqp, 0, 256); + for(c = ' '; c <= '<'; c++) + tableqp[c] = Self; + for(c = '>'; c <= '~'; c++) + tableqp[c] = Self; + tableqp['\t'] = Self; + tableqp['='] = Hex; +} + +static int +hex2int(int x) +{ + if(x >= '0' && x <= '9') + return x - '0'; + if(x >= 'A' && x <= 'F') + return (x - 'A') + 10; + if(x >= 'a' && x <= 'f') + return (x - 'a') + 10; + return 0; +} + +static char* +decquotedline(char *out, char *in, char *e) +{ + int c, soft; + + /* dump trailing white space */ + while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) + e--; + + /* trailing '=' means no newline */ + if(*e == '='){ + soft = 1; + e--; + } else + soft = 0; + + while(in <= e){ + c = (*in++) & 0xff; + switch(tableqp[c]){ + case Self: + *out++ = c; + break; + case Hex: + c = hex2int(*in++)<<4; + c |= hex2int(*in++); + *out++ = c; + break; + } + } + if(!soft) + *out++ = '\n'; + *out = 0; + + return out; +} + +static int +decquoted(char *out, char *in, char *e) +{ + char *p, *nl; + + if(tableqp[' '] == 0) + initquoted(); + + p = out; + while((nl = strchr(in, '\n')) != nil && nl < e){ + p = decquotedline(p, in, nl); + in = nl + 1; + } + if(in < e) + p = decquotedline(p, in, e-1); + + // make sure we end with a new line + if(*(p-1) != '\n'){ + *p++ = '\n'; + *p = 0; + } + + return p - out; +} + +/* translate latin1 directly since it fits neatly in utf */ +static int +latin1toutf(char *out, char *in, char *e) +{ + Rune r; + char *p; + + p = out; + for(; in < e; in++){ + r = (*in) & 0xff; + p += runetochar(p, &r); + } + *p = 0; + return p - out; +} |