aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/marshal/marshal.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-10-29 16:26:44 +0000
committerrsc <devnull@localhost>2005-10-29 16:26:44 +0000
commit5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch)
tree8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/marshal/marshal.c
parentcd3745196389579fb78b9b01ef1daefb5a57aa71 (diff)
downloadplan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/marshal/marshal.c')
-rw-r--r--src/cmd/upas/marshal/marshal.c1857
1 files changed, 1857 insertions, 0 deletions
diff --git a/src/cmd/upas/marshal/marshal.c b/src/cmd/upas/marshal/marshal.c
new file mode 100644
index 00000000..cfc186c0
--- /dev/null
+++ b/src/cmd/upas/marshal/marshal.c
@@ -0,0 +1,1857 @@
+#include "common.h"
+#include <ctype.h>
+
+typedef struct Attach Attach;
+typedef struct Alias Alias;
+typedef struct Addr Addr;
+typedef struct Ctype Ctype;
+
+struct Attach {
+ Attach *next;
+ char *path;
+ char *type;
+ int tinline;
+ Ctype *ctype;
+};
+
+struct Alias
+{
+ Alias *next;
+ int n;
+ Addr *addr;
+};
+
+struct Addr
+{
+ Addr *next;
+ char *v;
+};
+
+enum {
+ Hfrom,
+ Hto,
+ Hcc,
+ Hbcc,
+ Hsender,
+ Hreplyto,
+ Hinreplyto,
+ Hdate,
+ Hsubject,
+ Hmime,
+ Hpriority,
+ Hmsgid,
+ Hcontent,
+ Hx,
+ Hprecedence,
+ Nhdr,
+};
+
+enum {
+ PGPsign = 1,
+ PGPencrypt = 2,
+};
+
+char *hdrs[Nhdr] = {
+[Hfrom] "from:",
+[Hto] "to:",
+[Hcc] "cc:",
+[Hbcc] "bcc:",
+[Hreplyto] "reply-to:",
+[Hinreplyto] "in-reply-to:",
+[Hsender] "sender:",
+[Hdate] "date:",
+[Hsubject] "subject:",
+[Hpriority] "priority:",
+[Hmsgid] "message-id:",
+[Hmime] "mime-",
+[Hcontent] "content-",
+[Hx] "x-",
+[Hprecedence] "precedence",
+};
+
+struct Ctype {
+ char *type;
+ char *ext;
+ int display;
+};
+
+Ctype ctype[] = {
+ { "text/plain", "txt", 1, },
+ { "text/html", "html", 1, },
+ { "text/html", "htm", 1, },
+ { "text/tab-separated-values", "tsv", 1, },
+ { "text/richtext", "rtx", 1, },
+ { "message/rfc822", "txt", 1, },
+ { "", 0, 0, },
+};
+
+Ctype *mimetypes;
+
+int pid = -1;
+int pgppid = -1;
+
+Attach* mkattach(char*, char*, int);
+int readheaders(Biobuf*, int*, String**, Addr**, int);
+void body(Biobuf*, Biobuf*, int);
+char* mkboundary(void);
+int printdate(Biobuf*);
+int printfrom(Biobuf*);
+int printto(Biobuf*, Addr*);
+int printcc(Biobuf*, Addr*);
+int printsubject(Biobuf*, char*);
+int printinreplyto(Biobuf*, char*);
+int sendmail(Addr*, Addr*, int*, char*);
+void attachment(Attach*, Biobuf*);
+int cistrncmp(char*, char*, int);
+int cistrcmp(char*, char*);
+char* waitforsubprocs(void);
+int enc64(char*, int, uchar*, int);
+Addr* expand(int, char**);
+Alias* readaliases(void);
+Addr* expandline(String**, Addr*);
+void Bdrain(Biobuf*);
+void freeaddr(Addr *);
+int pgpopts(char*);
+int pgpfilter(int*, int, int);
+void readmimetypes(void);
+char* estrdup(char*);
+void* emalloc(int);
+void* erealloc(void*, int);
+void freeaddr(Addr*);
+void freeaddrs(Addr*);
+void freealias(Alias*);
+void freealiases(Alias*);
+int doublequote(Fmt*);
+
+int rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag;
+int pgpflag = 0;
+char *user;
+char *login;
+Alias *aliases;
+int rfc822syntaxerror;
+char lastchar;
+char *replymsg;
+
+enum
+{
+ Ok = 0,
+ Nomessage = 1,
+ Nobody = 2,
+ Error = -1,
+};
+
+#pragma varargck type "Z" char*
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-Fr#xn] [-s subject] [-c ccrecipient] [-t type] [-aA attachment] [-p[es]] [-R replymsg] -8 | recipient-list\n",
+ argv0);
+ exits("usage");
+}
+
+void
+fatal(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ if(pid >= 0)
+ postnote(PNPROC, pid, "die");
+ if(pgppid >= 0)
+ postnote(PNPROC, pgppid, "die");
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ fprint(2, "%s: %s\n", argv0, buf);
+ holdoff(holding);
+ exits(buf);
+}
+
+void
+main(int argc, char **argv)
+{
+ Attach *first, **l, *a;
+ char *subject, *type, *boundary;
+ int flags, fd;
+ Biobuf in, out, *b;
+ Addr *to;
+ Addr *cc;
+ String *file, *hdrstring;
+ int noinput, headersrv;
+ int ccargc;
+ char *ccargv[32];
+
+ noinput = 0;
+ subject = nil;
+ first = nil;
+ l = &first;
+ type = nil;
+ hdrstring = nil;
+ ccargc = 0;
+
+ quotefmtinstall();
+ fmtinstall('Z', doublequote);
+
+ ARGBEGIN{
+ case 't':
+ type = ARGF();
+ if(type == nil)
+ usage();
+ break;
+ case 'a':
+ flags = 0;
+ goto aflag;
+ case 'A':
+ flags = 1;
+ aflag:
+ a = mkattach(ARGF(), type, flags);
+ if(a == nil)
+ exits("bad args");
+ type = nil;
+ *l = a;
+ l = &a->next;
+ break;
+ case 'C':
+ if(ccargc >= nelem(ccargv)-1)
+ sysfatal("too many cc's");
+ ccargv[ccargc] = ARGF();
+ if(ccargv[ccargc] == nil)
+ usage();
+ ccargc++;
+ break;
+ case 'R':
+ replymsg = ARGF();
+ break;
+ case 's':
+ subject = ARGF();
+ break;
+ case 'F':
+ Fflag = 1; // file message
+ break;
+ case 'r':
+ rflag = 1; // for sendmail
+ break;
+ case 'd':
+ dflag = 1; // for sendmail
+ break;
+ case '#':
+ lbflag = 1; // for sendmail
+ break;
+ case 'x':
+ xflag = 1; // for sendmail
+ break;
+ case 'n': // no standard input
+ nflag = 1;
+ break;
+ case '8': // read recipients from rfc822 header
+ eightflag = 1;
+ break;
+ case 'p': // pgp flag: encrypt, sign, or both
+ if(pgpopts(ARGF()) < 0)
+ sysfatal("bad pgp options");
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND;
+
+ login = getlog();
+ user = getenv("upasname");
+ if(user == nil || *user == 0)
+ user = login;
+ if(user == nil || *user == 0)
+ sysfatal("can't read user name");
+
+ if(Binit(&in, 0, OREAD) < 0)
+ sysfatal("can't Binit 0: %r");
+
+ if(nflag && eightflag)
+ sysfatal("can't use both -n and -8");
+ if(eightflag && argc >= 1)
+ usage();
+ else if(!eightflag && argc < 1)
+ usage();
+
+ aliases = readaliases();
+ if(!eightflag){
+ to = expand(argc, argv);
+ cc = expand(ccargc, ccargv);
+ } else {
+ to = nil;
+ cc = nil;
+ }
+
+ flags = 0;
+ headersrv = Nomessage;
+ if(!nflag && !xflag && !lbflag &&!dflag) {
+ // pass through headers, keeping track of which we've seen,
+ // perhaps building to list.
+ holding = holdon();
+ headersrv = readheaders(&in, &flags, &hdrstring, eightflag ? &to : nil, 1);
+ if(rfc822syntaxerror){
+ Bdrain(&in);
+ fatal("rfc822 syntax error, message not sent");
+ }
+ if(to == nil){
+ Bdrain(&in);
+ fatal("no addresses found, message not sent");
+ }
+
+ switch(headersrv){
+ case Error: // error
+ fatal("reading");
+ break;
+ case Nomessage: // no message, just exit mimicking old behavior
+ noinput = 1;
+ if(first == nil)
+ exits(0);
+ break;
+ }
+ }
+
+ fd = sendmail(to, cc, &pid, Fflag ? argv[0] : nil);
+ if(fd < 0)
+ sysfatal("execing sendmail: %r\n:");
+ if(xflag || lbflag || dflag){
+ close(fd);
+ exits(waitforsubprocs());
+ }
+
+ if(Binit(&out, fd, OWRITE) < 0)
+ fatal("can't Binit 1: %r");
+
+ if(!nflag){
+ if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring))
+ fatal("write error");
+ s_free(hdrstring);
+ hdrstring = nil;
+
+ // read user's standard headers
+ file = s_new();
+ mboxpath("headers", user, file, 0);
+ b = Bopen(s_to_c(file), OREAD);
+ if(b != nil){
+ switch(readheaders(b, &flags, &hdrstring, nil, 0)){
+ case Error: // error
+ fatal("reading");
+ }
+ Bterm(b);
+ if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring))
+ fatal("write error");
+ s_free(hdrstring);
+ hdrstring = nil;
+ }
+ }
+
+ // add any headers we need
+ if((flags & (1<<Hdate)) == 0)
+ if(printdate(&out) < 0)
+ fatal("writing");
+ if((flags & (1<<Hfrom)) == 0)
+ if(printfrom(&out) < 0)
+ fatal("writing");
+ if((flags & (1<<Hto)) == 0)
+ if(printto(&out, to) < 0)
+ fatal("writing");
+ if((flags & (1<<Hcc)) == 0)
+ if(printcc(&out, cc) < 0)
+ fatal("writing");
+ if((flags & (1<<Hsubject)) == 0 && subject != nil)
+ if(printsubject(&out, subject) < 0)
+ fatal("writing");
+ if(replymsg != nil)
+ if(printinreplyto(&out, replymsg) < 0)
+ fatal("writing");
+ Bprint(&out, "MIME-Version: 1.0\n");
+
+ if(pgpflag){ // interpose pgp process between us and sendmail to handle body
+ Bflush(&out);
+ Bterm(&out);
+ fd = pgpfilter(&pgppid, fd, pgpflag);
+ if(Binit(&out, fd, OWRITE) < 0)
+ fatal("can't Binit 1: %r");
+ }
+
+ // if attachments, stick in multipart headers
+ boundary = nil;
+ if(first != nil){
+ boundary = mkboundary();
+ Bprint(&out, "Content-Type: multipart/mixed;\n");
+ Bprint(&out, "\tboundary=\"%s\"\n\n", boundary);
+ Bprint(&out, "This is a multi-part message in MIME format.\n");
+ Bprint(&out, "--%s\n", boundary);
+ Bprint(&out, "Content-Disposition: inline\n");
+ }
+
+ if(!nflag){
+ if(!noinput && headersrv == Ok){
+ body(&in, &out, 1);
+ }
+ } else
+ Bprint(&out, "\n");
+ holdoff(holding);
+
+ Bflush(&out);
+ for(a = first; a != nil; a = a->next){
+ if(lastchar != '\n')
+ Bprint(&out, "\n");
+ Bprint(&out, "--%s\n", boundary);
+ attachment(a, &out);
+ }
+
+ if(first != nil){
+ if(lastchar != '\n')
+ Bprint(&out, "\n");
+ Bprint(&out, "--%s--\n", boundary);
+ }
+
+ Bterm(&out);
+ close(fd);
+ exits(waitforsubprocs());
+}
+
+// evaluate pgp option string
+int
+pgpopts(char *s)
+{
+ if(s == nil || s[0] == '\0')
+ return -1;
+ while(*s){
+ switch(*s++){
+ case 's': case 'S':
+ pgpflag |= PGPsign;
+ break;
+ case 'e': case 'E':
+ pgpflag |= PGPencrypt;
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+// read headers from stdin into a String, expanding local aliases,
+// keep track of which headers are there, which addresses we have
+// remove Bcc: line.
+int
+readheaders(Biobuf *in, int *fp, String **sp, Addr **top, int strict)
+{
+ Addr *to;
+ String *s, *sline;
+ char *p;
+ int i, seen, hdrtype;
+
+ s = s_new();
+ sline = nil;
+ to = nil;
+ hdrtype = -1;
+ seen = 0;
+ for(;;) {
+ if((p = Brdline(in, '\n')) != nil) {
+ seen = 1;
+ p[Blinelen(in)-1] = 0;
+
+ // coalesce multiline headers
+ if((*p == ' ' || *p == '\t') && sline){
+ s_append(sline, "\n");
+ s_append(sline, p);
+ p[Blinelen(in)-1] = '\n';
+ continue;
+ }
+ }
+
+ // process the current header, it's all been read
+ if(sline) {
+ assert(hdrtype != -1);
+ if(top){
+ switch(hdrtype){
+ case Hto:
+ case Hcc:
+ case Hbcc:
+ to = expandline(&sline, to);
+ break;
+ }
+ }
+ if(top==nil || hdrtype!=Hbcc){
+ s_append(s, s_to_c(sline));
+ s_append(s, "\n");
+ }
+ s_free(sline);
+ sline = nil;
+ }
+
+ if(p == nil)
+ break;
+
+ // if no :, it's not a header, seek back and break
+ if(strchr(p, ':') == nil){
+ p[Blinelen(in)-1] = '\n';
+ Bseek(in, -Blinelen(in), 1);
+ break;
+ }
+
+ sline = s_copy(p);
+
+ // classify the header. If we don't recognize it, break. This is
+ // to take care of user's that start messages with lines that contain
+ // ':'s but that aren't headers. This is a bit hokey. Since I decided
+ // to let users type headers, I need some way to distinguish. Therefore,
+ // marshal tries to know all likely headers and will indeed screw up if
+ // the user types an unlikely one. -- presotto
+ hdrtype = -1;
+ for(i = 0; i < nelem(hdrs); i++){
+ if(cistrncmp(hdrs[i], p, strlen(hdrs[i])) == 0){
+ *fp |= 1<<i;
+ hdrtype = i;
+ break;
+ }
+ }
+ if(strict){
+ if(hdrtype == -1){
+ p[Blinelen(in)-1] = '\n';
+ Bseek(in, -Blinelen(in), 1);
+ break;
+ }
+ } else
+ hdrtype = 0;
+ p[Blinelen(in)-1] = '\n';
+ }
+
+ *sp = s;
+ if(top)
+ *top = to;
+
+ if(seen == 0){
+ if(Blinelen(in) == 0)
+ return Nomessage;
+ else
+ return Ok;
+ }
+ if(p == nil)
+ return Nobody;
+ return Ok;
+}
+
+// pass the body to sendmail, make sure body starts and ends with a newline
+void
+body(Biobuf *in, Biobuf *out, int docontenttype)
+{
+ char *buf, *p;
+ int i, n, len;
+
+ n = 0;
+ len = 16*1024;
+ buf = emalloc(len);
+
+ // first char must be newline
+ i = Bgetc(in);
+ if(i > 0){
+ if(i != '\n')
+ buf[n++] = '\n';
+ buf[n++] = i;
+ } else {
+ buf[n++] = '\n';
+ }
+
+ // read into memory
+ if(docontenttype){
+ while(docontenttype){
+ if(n == len){
+ len += len>>2;
+ buf = realloc(buf, len);
+ if(buf == nil)
+ sysfatal("%r");
+ }
+ p = buf+n;
+ i = Bread(in, p, len - n);
+ if(i < 0)
+ fatal("input error2");
+ if(i == 0)
+ break;
+ n += i;
+ for(; i > 0; i--)
+ if((*p++ & 0x80) && docontenttype){
+ Bprint(out, "Content-Type: text/plain; charset=\"UTF-8\"\n");
+ Bprint(out, "Content-Transfer-Encoding: 8bit\n");
+ docontenttype = 0;
+ break;
+ }
+ }
+ if(docontenttype){
+ Bprint(out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
+ Bprint(out, "Content-Transfer-Encoding: 7bit\n");
+ }
+ }
+
+ // write what we already read
+ if(Bwrite(out, buf, n) < 0)
+ fatal("output error");
+ if(n > 0)
+ lastchar = buf[n-1];
+ else
+ lastchar = '\n';
+
+
+ // pass the rest
+ for(;;){
+ n = Bread(in, buf, len);
+ if(n < 0)
+ fatal("input error2");
+ if(n == 0)
+ break;
+ if(Bwrite(out, buf, n) < 0)
+ fatal("output error");
+ lastchar = buf[n-1];
+ }
+}
+
+// pass the body to sendmail encoding with base64
+//
+// the size of buf is very important to enc64. Anything other than
+// a multiple of 3 will cause enc64 to output a termination sequence.
+// To ensure that a full buf corresponds to a multiple of complete lines,
+// we make buf a multiple of 3*18 since that's how many enc64 sticks on
+// a single line. This avoids short lines in the output which is pleasing
+// but not necessary.
+//
+void
+body64(Biobuf *in, Biobuf *out)
+{
+ uchar buf[3*18*54];
+ char obuf[3*18*54*2];
+ int m, n;
+
+ Bprint(out, "\n");
+ for(;;){
+ n = Bread(in, buf, sizeof(buf));
+ fprint(2,"read %d bytes\n",n);
+ if(n < 0)
+ fatal("input error");
+ if(n == 0)
+ break;
+ m = enc64(obuf, sizeof(obuf), buf, n);
+ fprint(2,"encoded %d bytes\n",m);
+ fprint(2,"writing to %x\n",out);
+ if((n=Bwrite(out, obuf, m)) < 0)
+ fatal("output error");
+ fprint(2,"wrote %d bytes\n",n);
+ }
+ lastchar = '\n';
+ fprint(2,"done with attachment\n");
+}
+
+// pass message to sendmail, make sure body starts with a newline
+void
+copy(Biobuf *in, Biobuf *out)
+{
+ char buf[4*1024];
+ int n;
+
+ for(;;){
+ n = Bread(in, buf, sizeof(buf));
+ if(n < 0)
+ fatal("input error");
+ if(n == 0)
+ break;
+ if(Bwrite(out, buf, n) < 0)
+ fatal("output error");
+ }
+}
+
+void
+attachment(Attach *a, Biobuf *out)
+{
+ Biobuf *f;
+ char *p;
+
+ // if it's already mime encoded, just copy
+ if(strcmp(a->type, "mime") == 0){
+ f = Bopen(a->path, OREAD);
+ if(f == nil){
+ /* hack: give marshal time to stdin, before we kill it (for dead.letter) */
+ sleep(500);
+ postnote(PNPROC, pid, "interrupt");
+ sysfatal("opening %s: %r", a->path);
+ }
+ copy(f, out);
+ Bterm(f);
+ }
+
+ // if it's not already mime encoded ...
+ if(strcmp(a->type, "text/plain") != 0)
+ Bprint(out, "Content-Type: %s\n", a->type);
+
+ if(a->tinline){
+ Bprint(out, "Content-Disposition: inline\n");
+ } else {
+ p = strrchr(a->path, '/');
+ if(p == nil)
+ p = a->path;
+ else
+ p++;
+ Bprint(out, "Content-Disposition: attachment; filename=%Z\n", p);
+ }
+
+ f = Bopen(a->path, OREAD);
+ if(f == nil){
+ /* hack: give marshal time to stdin, before we kill it (for dead.letter) */
+ sleep(500);
+ postnote(PNPROC, pid, "interrupt");
+ sysfatal("opening %s: %r", a->path);
+ }
+
+ /* dump our local 'From ' line when passing along mail messages */
+ if(strcmp(a->type, "message/rfc822") == 0){
+ p = Brdline(f, '\n');
+ if(strncmp(p, "From ", 5) != 0)
+ Bseek(f, 0, 0);
+ }
+ if(a->ctype->display){
+ body(f, out, strcmp(a->type, "text/plain") == 0);
+ } else {
+ Bprint(out, "Content-Transfer-Encoding: base64\n");
+ body64(f, out);
+ }
+ Bterm(f);
+}
+
+char *ascwday[] =
+{
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+char *ascmon[] =
+{
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+int
+printdate(Biobuf *b)
+{
+ Tm *tm;
+ int tz;
+
+ tm = localtime(time(0));
+ tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);
+
+ return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n",
+ ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900+tm->year,
+ tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz);
+}
+
+int
+printfrom(Biobuf *b)
+{
+ return Bprint(b, "From: %s\n", user);
+}
+
+int
+printto(Biobuf *b, Addr *a)
+{
+ int i;
+
+ if(Bprint(b, "To: %s", a->v) < 0)
+ return -1;
+ i = 0;
+ for(a = a->next; a != nil; a = a->next)
+ if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
+ return -1;
+ if(Bprint(b, "\n") < 0)
+ return -1;
+ return 0;
+}
+
+int
+printcc(Biobuf *b, Addr *a)
+{
+ int i;
+
+ if(a == nil)
+ return 0;
+ if(Bprint(b, "CC: %s", a->v) < 0)
+ return -1;
+ i = 0;
+ for(a = a->next; a != nil; a = a->next)
+ if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
+ return -1;
+ if(Bprint(b, "\n") < 0)
+ return -1;
+ return 0;
+}
+
+int
+printsubject(Biobuf *b, char *subject)
+{
+ return Bprint(b, "Subject: %s\n", subject);
+}
+
+int
+printinreplyto(Biobuf *out, char *dir)
+{
+ String *s = s_copy(dir);
+ char buf[256];
+ int fd;
+ int n;
+
+ s_append(s, "/messageid");
+ fd = open(s_to_c(s), OREAD);
+ s_free(s);
+ if(fd < 0)
+ return 0;
+ n = read(fd, buf, sizeof(buf)-1);
+ close(fd);
+ if(n <= 0)
+ return 0;
+ buf[n] = 0;
+ return Bprint(out, "In-Reply-To: %s\n", buf);
+}
+
+Attach*
+mkattach(char *file, char *type, int tinline)
+{
+ Ctype *c;
+ Attach *a;
+ char ftype[64];
+ char *p;
+ int n, pfd[2];
+
+ if(file == nil)
+ return nil;
+ if(access(file, 4) == -1){
+ fprint(2, "%s: %s can't read file\n", argv0, file);
+ return nil;
+ }
+ a = emalloc(sizeof(*a));
+ a->path = file;
+ a->next = nil;
+ a->type = type;
+ a->tinline = tinline;
+ a->ctype = nil;
+ if(type != nil){
+ for(c = ctype; ; c++)
+ if(strncmp(type, c->type, strlen(c->type)) == 0){
+ a->ctype = c;
+ break;
+ }
+ return a;
+ }
+
+ // pick a type depending on extension
+ p = strchr(file, '.');
+ if(p != nil)
+ p++;
+
+ // check the builtin extensions
+ if(p != nil){
+ for(c = ctype; c->ext != nil; c++)
+ if(strcmp(p, c->ext) == 0){
+ a->type = c->type;
+ a->ctype = c;
+ return a;
+ }
+ }
+
+ // try the mime types file
+ if(p != nil){
+ if(mimetypes == nil)
+ readmimetypes();
+ for(c = mimetypes; c != nil && c->ext != nil; c++)
+ if(strcmp(p, c->ext) == 0){
+ a->type = c->type;
+ a->ctype = c;
+ return a;
+ }
+ }
+
+ // run file to figure out the type
+ a->type = "application/octet-stream"; // safest default
+ if(pipe(pfd) < 0)
+ return a;
+ switch(fork()){
+ case -1:
+ break;
+ case 0:
+ close(pfd[1]);
+ close(0);
+ dup(pfd[0], 0);
+ close(1);
+ dup(pfd[0], 1);
+ execl(unsharp("#9/bin/file"), "file", "-m", file, nil);
+ exits(0);
+ default:
+ close(pfd[0]);
+ n = read(pfd[1], ftype, sizeof(ftype));
+ if(n > 0){
+ ftype[n-1] = 0;
+ a->type = estrdup(ftype);
+ }
+ close(pfd[1]);
+ waitpid();
+ break;
+ }
+
+ for(c = ctype; ; c++)
+ if(strncmp(a->type, c->type, strlen(c->type)) == 0){
+ a->ctype = c;
+ break;
+ }
+
+ return a;
+}
+
+char*
+mkboundary(void)
+{
+ char buf[32];
+ int i;
+
+ srand((time(0)<<16)|getpid());
+ strcpy(buf, "upas-");
+ for(i = 5; i < sizeof(buf)-1; i++)
+ buf[i] = 'a' + nrand(26);
+ buf[i] = 0;
+ return estrdup(buf);
+}
+
+// copy types to two fd's
+static void
+tee(int in, int out1, int out2)
+{
+ char buf[8*1024];
+ int n;
+
+ for(;;){
+ n = read(in, buf, sizeof(buf));
+ if(n <= 0)
+ break;
+ if(write(out1, buf, n) < 0)
+ break;
+ if(write(out2, buf, n) < 0)
+ break;
+ }
+}
+
+// print the unix from line
+int
+printunixfrom(int fd)
+{
+ Tm *tm;
+ int tz;
+
+ tm = localtime(time(0));
+ tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);
+
+ return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n",
+ user,
+ ascwday[tm->wday], ascmon[tm->mon], tm->mday,
+ tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900+tm->year);
+}
+
+char *specialfile[] =
+{
+ "pipeto",
+ "pipefrom",
+ "L.mbox",
+ "forward",
+ "names"
+};
+
+// return 1 if this is a special file
+static int
+special(String *s)
+{
+ char *p;
+ int i;
+
+ p = strrchr(s_to_c(s), '/');
+ if(p == nil)
+ p = s_to_c(s);
+ else
+ p++;
+ for(i = 0; i < nelem(specialfile); i++)
+ if(strcmp(p, specialfile[i]) == 0)
+ return 1;
+ return 0;
+}
+
+// open the folder using the recipients account name
+static int
+openfolder(char *rcvr)
+{
+ char *p;
+ int c;
+ String *file;
+ Dir *d;
+ int fd;
+ int scarey;
+
+ file = s_new();
+ mboxpath("f", user, file, 0);
+
+ // if $mail/f exists, store there, otherwise in $mail
+ d = dirstat(s_to_c(file));
+ if(d == nil || d->qid.type != QTDIR){
+ scarey = 1;
+ file->ptr -= 1;
+ } else {
+ s_putc(file, '/');
+ scarey = 0;
+ }
+ free(d);
+
+ p = strrchr(rcvr, '!');
+ if(p != nil)
+ rcvr = p+1;
+
+ while(*rcvr && *rcvr != '@'){
+ c = *rcvr++;
+ if(c == '/')
+ c = '_';
+ s_putc(file, c);
+ }
+ s_terminate(file);
+
+ if(scarey && special(file)){
+ fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file));
+ s_free(file);
+ return -1;
+ }
+
+ fd = open(s_to_c(file), OWRITE);
+ if(fd < 0)
+ fd = create(s_to_c(file), OWRITE, 0660);
+
+ s_free(file);
+ return fd;
+}
+
+// start up sendmail and return an fd to talk to it with
+int
+sendmail(Addr *to, Addr *cc, int *pid, char *rcvr)
+{
+ char **av, **v;
+ int ac, fd;
+ int pfd[2];
+ String *cmd;
+ Addr *a;
+
+ fd = -1;
+ if(rcvr != nil)
+ fd = openfolder(rcvr);
+
+ ac = 0;
+ for(a = to; a != nil; a = a->next)
+ ac++;
+ for(a = cc; a != nil; a = a->next)
+ ac++;
+ v = av = emalloc(sizeof(char*)*(ac+20));
+ ac = 0;
+ v[ac++] = "sendmail";
+ if(xflag)
+ v[ac++] = "-x";
+ if(rflag)
+ v[ac++] = "-r";
+ if(lbflag)
+ v[ac++] = "-#";
+ if(dflag)
+ v[ac++] = "-d";
+ for(a = to; a != nil; a = a->next)
+ v[ac++] = a->v;
+ for(a = cc; a != nil; a = a->next)
+ v[ac++] = a->v;
+ v[ac] = 0;
+
+ if(pipe(pfd) < 0)
+ fatal("%r");
+ switch(*pid = rfork(RFFDG|RFPROC)){ // jpc - removed |RFENVG|RFREND|
+ case -1:
+ fatal("%r");
+ break;
+ case 0:
+ if(holding)
+ close(holding);
+ close(pfd[1]);
+ dup(pfd[0], 0);
+ close(pfd[0]);
+
+ if(rcvr != nil){
+ if(pipe(pfd) < 0)
+ fatal("%r");
+ switch(fork()){
+ case -1:
+ fatal("%r");
+ break;
+ case 0:
+ close(pfd[0]);
+ seek(fd, 0, 2);
+ printunixfrom(fd);
+ tee(0, pfd[1], fd);
+ write(fd, "\n", 1);
+ exits(0);
+ default:
+ close(fd);
+ close(pfd[1]);
+ dup(pfd[0], 0);
+ break;
+ }
+ }
+
+ if(replymsg != nil)
+ putenv("replymsg", replymsg);
+
+ cmd = mboxpath("pipefrom", login, s_new(), 0);
+ exec(s_to_c(cmd), av);
+ exec(unsharp("#9/bin/myupassend"), av);
+ exec(unsharp("#9/bin/upas/send"), av);
+ fatal("execing: %r");
+ break;
+ default:
+ if(rcvr != nil)
+ close(fd);
+ close(pfd[0]);
+ break;
+ }
+ return pfd[1];
+}
+
+// start up pgp process and return an fd to talk to it with.
+// its standard output will be the original fd, which goes to sendmail.
+int
+pgpfilter(int *pid, int fd, int pgpflag)
+{
+ char **av, **v;
+ int ac;
+ int pfd[2];
+
+ v = av = emalloc(sizeof(char*)*8);
+ ac = 0;
+ v[ac++] = "pgp";
+ if(pgpflag & PGPsign)
+ v[ac++] = "-s";
+ if(pgpflag & PGPencrypt)
+ v[ac++] = "-e";
+ v[ac] = 0;
+
+ if(pipe(pfd) < 0)
+ fatal("%r");
+ switch(*pid = fork()){
+ case -1:
+ fatal("%r");
+ break;
+ case 0:
+ close(pfd[1]);
+ dup(pfd[0], 0);
+ close(pfd[0]);
+ dup(fd, 1);
+ close(fd);
+
+ exec("/bin/upas/pgp", av);
+ fatal("execing: %r");
+ break;
+ default:
+ close(pfd[0]);
+ break;
+ }
+ close(fd);
+ return pfd[1];
+}
+
+// wait for sendmail and pgp to exit; exit here if either failed
+char*
+waitforsubprocs(void)
+{
+ Waitmsg *w;
+ char *err;
+
+ err = nil;
+ while((w = wait()) != nil){
+ if(w->pid == pid || w->pid == pgppid){
+ if(w->msg[0] != 0)
+ err = estrdup(w->msg);
+ }
+ free(w);
+ }
+ if(err)
+ exits(err);
+ return nil;
+}
+
+int
+cistrncmp(char *a, char *b, int n)
+{
+ while(n-- > 0){
+ if(tolower(*a++) != tolower(*b++))
+ return -1;
+ }
+ return 0;
+}
+
+int
+cistrcmp(char *a, char *b)
+{
+ for(;;){
+ if(tolower(*a) != tolower(*b++))
+ return -1;
+ if(*a++ == 0)
+ break;
+ }
+ return 0;
+}
+
+static uchar t64d[256];
+static char t64e[64];
+
+static void
+init64(void)
+{
+ int c, i;
+
+ memset(t64d, 255, 256);
+ memset(t64e, '=', 64);
+ i = 0;
+ for(c = 'A'; c <= 'Z'; c++){
+ t64e[i] = c;
+ t64d[c] = i++;
+ }
+ for(c = 'a'; c <= 'z'; c++){
+ t64e[i] = c;
+ t64d[c] = i++;
+ }
+ for(c = '0'; c <= '9'; c++){
+ t64e[i] = c;
+ t64d[c] = i++;
+ }
+ t64e[i] = '+';
+ t64d['+'] = i++;
+ t64e[i] = '/';
+ t64d['/'] = i;
+}
+
+int
+enc64(char *out, int lim, uchar *in, int n)
+{
+ int i;
+ ulong b24;
+ char *start = out;
+ char *e = out + lim;
+
+ if(t64e[0] == 0)
+ init64();
+ for(i = 0; i < n/3; i++){
+ b24 = (*in++)<<16;
+ b24 |= (*in++)<<8;
+ b24 |= *in++;
+ if(out + 5 >= e)
+ goto exhausted;
+ *out++ = t64e[(b24>>18)];
+ *out++ = t64e[(b24>>12)&0x3f];
+ *out++ = t64e[(b24>>6)&0x3f];
+ *out++ = t64e[(b24)&0x3f];
+ if((i%18) == 17)
+ *out++ = '\n';
+ }
+
+ switch(n%3){
+ case 2:
+ b24 = (*in++)<<16;
+ b24 |= (*in)<<8;
+ if(out + 4 >= e)
+ goto exhausted;
+ *out++ = t64e[(b24>>18)];
+ *out++ = t64e[(b24>>12)&0x3f];
+ *out++ = t64e[(b24>>6)&0x3f];
+ break;
+ case 1:
+ b24 = (*in)<<16;
+ if(out + 4 >= e)
+ goto exhausted;
+ *out++ = t64e[(b24>>18)];
+ *out++ = t64e[(b24>>12)&0x3f];
+ *out++ = '=';
+ break;
+ case 0:
+ if((i%18) != 0)
+ *out++ = '\n';
+ *out = 0;
+ return out - start;
+ }
+exhausted:
+ *out++ = '=';
+ *out++ = '\n';
+ *out = 0;
+ return out - start;
+}
+
+void
+freealias(Alias *a)
+{
+ freeaddrs(a->addr);
+ free(a);
+}
+
+void
+freealiases(Alias *a)
+{
+ Alias *next;
+
+ while(a != nil){
+ next = a->next;
+ freealias(a);
+ a = next;
+ }
+}
+
+//
+// read alias file
+//
+Alias*
+readaliases(void)
+{
+ Alias *a, **l, *first;
+ Addr *addr, **al;
+ String *file, *line, *token;
+ // jpc - static int already;
+ Sinstack *sp;
+
+ first = nil;
+ file = s_new();
+ line = s_new();
+ token = s_new();
+
+ // open and get length
+ mboxpath("names", login, file, 0);
+ sp = s_allocinstack(s_to_c(file));
+ if(sp == nil)
+ goto out;
+
+ l = &first;
+
+ // read a line at a time.
+ while(s_rdinstack(sp, s_restart(line))!=nil) {
+ s_restart(line);
+ a = emalloc(sizeof(Alias));
+ al = &a->addr;
+ for(;;){
+ if(s_parse(line, s_restart(token))==0)
+ break;
+ addr = emalloc(sizeof(Addr));
+ addr->v = strdup(s_to_c(token));
+ addr->next = 0;
+ *al = addr;
+ al = &addr->next;
+ }
+ if(a->addr == nil || a->addr->next == nil){
+ freealias(a);
+ continue;
+ }
+ a->next = nil;
+ *l = a;
+ l = &a->next;
+ }
+ s_freeinstack(sp);
+
+out:
+ s_free(file);
+ s_free(line);
+ s_free(token);
+ return first;
+}
+
+Addr*
+newaddr(char *name)
+{
+ Addr *a;
+
+ a = emalloc(sizeof(*a));
+ a->next = nil;
+ a->v = estrdup(name);
+ if(a->v == nil)
+ sysfatal("%r");
+ return a;
+}
+
+//
+// expand personal aliases since the names are meaningless in
+// other contexts
+//
+Addr*
+_expand(Addr *old, int *changedp)
+{
+ Alias *al;
+ Addr *first, *next, **l, *a;
+
+ *changedp = 0;
+ first = nil;
+ l = &first;
+ for(;old != nil; old = next){
+ next = old->next;
+ for(al = aliases; al != nil; al = al->next){
+ if(strcmp(al->addr->v, old->v) == 0){
+ for(a = al->addr->next; a != nil; a = a->next){
+ *l = newaddr(a->v);
+ if(*l == nil)
+ sysfatal("%r");
+ l = &(*l)->next;
+ *changedp = 1;
+ }
+ break;
+ }
+ }
+ if(al != nil){
+ freeaddr(old);
+ continue;
+ }
+ *l = old;
+ old->next = nil;
+ l = &(*l)->next;
+ }
+ return first;
+}
+
+Addr*
+rexpand(Addr *old)
+{
+ int i, changed;
+
+ changed = 0;
+ for(i=0; i<32; i++){
+ old = _expand(old, &changed);
+ if(changed == 0)
+ break;
+ }
+ return old;
+}
+
+Addr*
+unique(Addr *first)
+{
+ Addr *a, **l, *x;
+
+ for(a = first; a != nil; a = a->next){
+ for(l = &a->next; *l != nil;){
+ if(strcmp(a->v, (*l)->v) == 0){
+ x = *l;
+ *l = x->next;
+ freeaddr(x);
+ } else
+ l = &(*l)->next;
+ }
+ }
+ return first;
+}
+
+Addr*
+expand(int ac, char **av)
+{
+ Addr *first, **l;
+ int i;
+
+ first = nil;
+
+ // make a list of the starting addresses
+ l = &first;
+ for(i = 0; i < ac; i++){
+ *l = newaddr(av[i]);
+ if(*l == nil)
+ sysfatal("%r");
+ l = &(*l)->next;
+ }
+
+ // recurse till we don't change any more
+ return unique(rexpand(first));
+}
+
+Addr*
+concataddr(Addr *a, Addr *b)
+{
+ Addr *oa;
+
+ if(a == nil)
+ return b;
+
+ oa = a;
+ for(; a->next; a=a->next)
+ ;
+ a->next = b;
+ return oa;
+}
+
+void
+freeaddr(Addr *ap)
+{
+ free(ap->v);
+ free(ap);
+}
+
+void
+freeaddrs(Addr *ap)
+{
+ Addr *next;
+
+ for(; ap; ap=next) {
+ next = ap->next;
+ freeaddr(ap);
+ }
+}
+
+String*
+s_copyn(char *s, int n)
+{
+ return s_nappend(s_reset(nil), s, n);
+}
+
+// fetch the next token from an RFC822 address string
+// we assume the header is RFC822-conformant in that
+// we recognize escaping anywhere even though it is only
+// supposed to be in quoted-strings, domain-literals, and comments.
+//
+// i'd use yylex or yyparse here, but we need to preserve
+// things like comments, which i think it tosses away.
+//
+// we're not strictly RFC822 compliant. we misparse such nonsense as
+//
+// To: gre @ (Grace) plan9 . (Emlin) bell-labs.com
+//
+// make sure there's no whitespace in your addresses and
+// you'll be fine.
+//
+enum {
+ Twhite,
+ Tcomment,
+ Twords,
+ Tcomma,
+ Tleftangle,
+ Trightangle,
+ Terror,
+ Tend,
+};
+//char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"};
+#define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r')
+int
+get822token(String **tok, char *p, char **pp)
+{
+ char *op;
+ int type;
+ int quoting;
+
+ op = p;
+ switch(*p){
+ case '\0':
+ *tok = nil;
+ *pp = nil;
+ return Tend;
+
+ case ' ': // get whitespace
+ case '\t':
+ case '\n':
+ case '\r':
+ type = Twhite;
+ while(ISWHITE(*p))
+ p++;
+ break;
+
+ case '(': // get comment
+ type = Tcomment;
+ for(p++; *p && *p != ')'; p++)
+ if(*p == '\\') {
+ if(*(p+1) == '\0') {
+ *tok = nil;
+ return Terror;
+ }
+ p++;
+ }
+
+ if(*p != ')') {
+ *tok = nil;
+ return Terror;
+ }
+ p++;
+ break;
+ case ',':
+ type = Tcomma;
+ p++;
+ break;
+ case '<':
+ type = Tleftangle;
+ p++;
+ break;
+ case '>':
+ type = Trightangle;
+ p++;
+ break;
+ default: // bunch of letters, perhaps quoted strings tossed in
+ type = Twords;
+ quoting = 0;
+ for(; *p && (quoting || (!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) {
+ if(*p == '"')
+ quoting = !quoting;
+ if(*p == '\\') {
+ if(*(p+1) == '\0') {
+ *tok = nil;
+ return Terror;
+ }
+ p++;
+ }
+ }
+ break;
+ }
+
+ if(pp)
+ *pp = p;
+ *tok = s_copyn(op, p-op);
+ return type;
+}
+
+// expand local aliases in an RFC822 mail line
+// add list of expanded addresses to to.
+Addr*
+expandline(String **s, Addr *to)
+{
+ Addr *na, *nto, *ap;
+ char *p;
+ int tok, inangle, hadangle, nword;
+ String *os, *ns, *stok, *lastword, *sinceword;
+
+ os = s_copy(s_to_c(*s));
+ p = strchr(s_to_c(*s), ':');
+ assert(p != nil);
+ p++;
+
+ ns = s_copyn(s_to_c(*s), p-s_to_c(*s));
+ stok = nil;
+ nto = nil;
+ //
+ // the only valid mailbox namings are word
+ // and word* < addr >
+ // without comments this would be simple.
+ // we keep the following:
+ // lastword - current guess at the address
+ // sinceword - whitespace and comment seen since lastword
+ //
+ lastword = s_new();
+ sinceword = s_new();
+ inangle = 0;
+ nword = 0;
+ hadangle = 0;
+ for(;;) {
+ stok = nil;
+ switch(tok = get822token(&stok, p, &p)){
+ default:
+ abort();
+ case Tcomma:
+ case Tend:
+ if(inangle)
+ goto Error;
+ if(nword != 1)
+ goto Error;
+ na = rexpand(newaddr(s_to_c(lastword)));
+ s_append(ns, na->v);
+ s_append(ns, s_to_c(sinceword));
+ for(ap=na->next; ap; ap=ap->next) {
+ s_append(ns, ", ");
+ s_append(ns, ap->v);
+ }
+ nto = concataddr(na, nto);
+ if(tok == Tcomma){
+ s_append(ns, ",");
+ s_free(stok);
+ }
+ if(tok == Tend)
+ goto Break2;
+ inangle = 0;
+ nword = 0;
+ hadangle = 0;
+ s_reset(sinceword);
+ s_reset(lastword);
+ break;
+ case Twhite:
+ case Tcomment:
+ s_append(sinceword, s_to_c(stok));
+ s_free(stok);
+ break;
+ case Trightangle:
+ if(!inangle)
+ goto Error;
+ inangle = 0;
+ hadangle = 1;
+ s_append(sinceword, s_to_c(stok));
+ s_free(stok);
+ break;
+ case Twords:
+ case Tleftangle:
+ if(hadangle)
+ goto Error;
+ if(tok != Tleftangle && inangle && s_len(lastword))
+ goto Error;
+ if(tok == Tleftangle) {
+ inangle = 1;
+ nword = 1;
+ }
+ s_append(ns, s_to_c(lastword));
+ s_append(ns, s_to_c(sinceword));
+ s_reset(sinceword);
+ if(tok == Tleftangle) {
+ s_append(ns, "<");
+ s_reset(lastword);
+ } else {
+ s_free(lastword);
+ lastword = stok;
+ }
+ if(!inangle)
+ nword++;
+ break;
+ case Terror: // give up, use old string, addrs
+ Error:
+ ns = os;
+ os = nil;
+ freeaddrs(nto);
+ nto = nil;
+ werrstr("rfc822 syntax error");
+ rfc822syntaxerror = 1;
+ goto Break2;
+ }
+ }
+Break2:
+ s_free(*s);
+ s_free(os);
+ *s = ns;
+ nto = concataddr(nto, to);
+ return nto;
+}
+
+void
+Bdrain(Biobuf *b)
+{
+ char buf[8192];
+
+ while(Bread(b, buf, sizeof buf) > 0)
+ ;
+}
+
+void
+readmimetypes(void)
+{
+ Biobuf *b;
+ char *p;
+ char *f[6];
+ char type[256];
+ static int alloced, inuse;
+
+ if(mimetypes == 0){
+ alloced = 256;
+ mimetypes = emalloc(alloced*sizeof(Ctype));
+ mimetypes[0].ext = "";
+ }
+
+ b = Bopen(unsharp("#9/sys/lib/mimetype"), OREAD);
+ if(b == nil)
+ return;
+ for(;;){
+ p = Brdline(b, '\n');
+ if(p == nil)
+ break;
+ p[Blinelen(b)-1] = 0;
+ if(tokenize(p, f, 6) < 4)
+ continue;
+ if(strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 || strcmp(f[2], "-") == 0)
+ continue;
+ if(inuse + 1 >= alloced){
+ alloced += 256;
+ mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype));
+ }
+ snprint(type, sizeof(type), "%s/%s", f[1], f[2]);
+ mimetypes[inuse].type = estrdup(type);
+ mimetypes[inuse].ext = estrdup(f[0]+1);
+ mimetypes[inuse].display = !strcmp(type, "text/plain");
+ inuse++;
+
+ // always make sure there's a terminator
+ mimetypes[inuse].ext = 0;
+ }
+ Bterm(b);
+}
+
+char*
+estrdup(char *x)
+{
+ x = strdup(x);
+ if(x == nil)
+ fatal("memory");
+ return x;
+}
+
+void*
+emalloc(int n)
+{
+ void *x;
+
+ x = malloc(n);
+ if(x == nil)
+ fatal("%r");
+ return x;
+}
+
+void*
+erealloc(void *x, int n)
+{
+ x = realloc(x, n);
+ if(x == nil)
+ fatal("%r");
+ return x;
+}
+
+//
+// Formatter for %"
+// Use double quotes to protect white space, frogs, \ and "
+//
+enum
+{
+ Qok = 0,
+ Qquote,
+ Qbackslash,
+};
+
+static int
+needtoquote(Rune r)
+{
+ if(r >= Runeself)
+ return Qquote;
+ if(r <= ' ')
+ return Qquote;
+ if(r=='\\' || r=='"')
+ return Qbackslash;
+ return Qok;
+}
+
+int
+doublequote(Fmt *f)
+{
+ char *s, *t;
+ int w, quotes;
+ Rune r;
+
+ s = va_arg(f->args, char*);
+ if(s == nil || *s == '\0')
+ return fmtstrcpy(f, "\"\"");
+
+ quotes = 0;
+ for(t=s; *t; t+=w){
+ w = chartorune(&r, t);
+ quotes |= needtoquote(r);
+ }
+ if(quotes == 0)
+ return fmtstrcpy(f, s);
+
+ fmtrune(f, '"');
+ for(t=s; *t; t+=w){
+ w = chartorune(&r, t);
+ if(needtoquote(r) == Qbackslash)
+ fmtrune(f, '\\');
+ fmtrune(f, r);
+ }
+ return fmtrune(f, '"');
+}