aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/fs/imap4.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/fs/imap4.c
parentcd3745196389579fb78b9b01ef1daefb5a57aa71 (diff)
downloadplan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/fs/imap4.c')
-rw-r--r--src/cmd/upas/fs/imap4.c876
1 files changed, 876 insertions, 0 deletions
diff --git a/src/cmd/upas/fs/imap4.c b/src/cmd/upas/fs/imap4.c
new file mode 100644
index 00000000..152d15cf
--- /dev/null
+++ b/src/cmd/upas/fs/imap4.c
@@ -0,0 +1,876 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include <auth.h>
+#include "dat.h"
+
+#pragma varargck argpos imap4cmd 2
+#pragma varargck type "Z" char*
+
+int doublequote(Fmt*);
+int pipeline = 1;
+
+/* static char Eio[] = "i/o error"; jpc */
+
+typedef struct Imap Imap;
+struct Imap {
+ char *freep; // free this to free the strings below
+
+ char *host;
+ char *user;
+ char *mbox;
+
+ int mustssl;
+ int refreshtime;
+ int debug;
+
+ ulong tag;
+ ulong validity;
+ int nmsg;
+ int size;
+ char *base;
+ char *data;
+
+ vlong *uid;
+ int nuid;
+ int muid;
+
+ Thumbprint *thumb;
+
+ // open network connection
+ Biobuf bin;
+ Biobuf bout;
+ int fd;
+};
+
+static char*
+removecr(char *s)
+{
+ char *r, *w;
+
+ for(r=w=s; *r; r++)
+ if(*r != '\r')
+ *w++ = *r;
+ *w = '\0';
+ return s;
+}
+
+//
+// send imap4 command
+//
+static void
+imap4cmd(Imap *imap, char *fmt, ...)
+{
+ char buf[128], *p;
+ va_list va;
+
+ va_start(va, fmt);
+ p = buf+sprint(buf, "9X%lud ", imap->tag);
+ vseprint(p, buf+sizeof(buf), fmt, va);
+ va_end(va);
+
+ p = buf+strlen(buf);
+ if(p > (buf+sizeof(buf)-3))
+ sysfatal("imap4 command too long");
+
+ if(imap->debug)
+ fprint(2, "-> %s\n", buf);
+ strcpy(p, "\r\n");
+ Bwrite(&imap->bout, buf, strlen(buf));
+ Bflush(&imap->bout);
+}
+
+enum {
+ OK,
+ NO,
+ BAD,
+ BYE,
+ EXISTS,
+ STATUS,
+ FETCH,
+ UNKNOWN,
+};
+
+static char *verblist[] = {
+[OK] "OK",
+[NO] "NO",
+[BAD] "BAD",
+[BYE] "BYE",
+[EXISTS] "EXISTS",
+[STATUS] "STATUS",
+[FETCH] "FETCH",
+};
+
+static int
+verbcode(char *verb)
+{
+ int i;
+ char *q;
+
+ if(q = strchr(verb, ' '))
+ *q = '\0';
+
+ for(i=0; i<nelem(verblist); i++)
+ if(verblist[i] && strcmp(verblist[i], verb)==0){
+ if(q)
+ *q = ' ';
+ return i;
+ }
+ if(q)
+ *q = ' ';
+ return UNKNOWN;
+}
+
+static void
+strupr(char *s)
+{
+ for(; *s; s++)
+ if('a' <= *s && *s <= 'z')
+ *s += 'A'-'a';
+}
+
+static void
+imapgrow(Imap *imap, int n)
+{
+ int i;
+
+ if(imap->data == nil){
+ imap->base = emalloc(n+1);
+ imap->data = imap->base;
+ imap->size = n+1;
+ }
+ if(n >= imap->size){
+ // friggin microsoft - reallocate
+ i = imap->data - imap->base;
+ imap->base = erealloc(imap->base, i+n+1);
+ imap->data = imap->base + i;
+ imap->size = n+1;
+ }
+}
+
+
+//
+// get imap4 response line. there might be various
+// data or other informational lines mixed in.
+//
+static char*
+imap4resp(Imap *imap)
+{
+ char *line, *p, *ep, *op, *q, *r, *en, *verb;
+ int i, n;
+ static char error[256];
+
+ while(p = Brdline(&imap->bin, '\n')){
+ ep = p+Blinelen(&imap->bin);
+ while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
+ *--ep = '\0';
+
+ if(imap->debug)
+ fprint(2, "<- %s\n", p);
+ strupr(p);
+
+ switch(p[0]){
+ case '+':
+ if(imap->tag == 0)
+ fprint(2, "unexpected: %s\n", p);
+ break;
+
+ // ``unsolicited'' information; everything happens here.
+ case '*':
+ if(p[1]!=' ')
+ continue;
+ p += 2;
+ line = p;
+ n = strtol(p, &p, 10);
+ if(*p==' ')
+ p++;
+ verb = p;
+
+ if(p = strchr(verb, ' '))
+ p++;
+ else
+ p = verb+strlen(verb);
+
+ switch(verbcode(verb)){
+ case OK:
+ case NO:
+ case BAD:
+ // human readable text at p;
+ break;
+ case BYE:
+ // early disconnect
+ // human readable text at p;
+ break;
+
+ // * 32 EXISTS
+ case EXISTS:
+ imap->nmsg = n;
+ break;
+
+ // * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964)
+ case STATUS:
+ if(q = strstr(p, "MESSAGES"))
+ imap->nmsg = atoi(q+8);
+ if(q = strstr(p, "UIDVALIDITY"))
+ imap->validity = strtoul(q+11, 0, 10);
+ break;
+
+ case FETCH:
+ // * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031}
+ // <3031 bytes of data>
+ // )
+ if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
+ if((q = strchr(p, '{'))
+ && (n=strtol(q+1, &en, 0), *en=='}')){
+ if(imap->data == nil || n >= imap->size)
+ imapgrow(imap, n);
+ if((i = Bread(&imap->bin, imap->data, n)) != n){
+ snprint(error, sizeof error,
+ "short read %d != %d: %r\n",
+ i, n);
+ return error;
+ }
+ if(imap->debug)
+ fprint(2, "<- read %d bytes\n", n);
+ imap->data[n] = '\0';
+ if(imap->debug)
+ fprint(2, "<- %s\n", imap->data);
+ imap->data += n;
+ imap->size -= n;
+ p = Brdline(&imap->bin, '\n');
+ if(imap->debug)
+ fprint(2, "<- ignoring %.*s\n",
+ Blinelen(&imap->bin), p);
+ }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
+ *r = '\0';
+ q++;
+ n = r-q;
+ if(imap->data == nil || n >= imap->size)
+ imapgrow(imap, n);
+ memmove(imap->data, q, n);
+ imap->data[n] = '\0';
+ imap->data += n;
+ imap->size -= n;
+ }else
+ return "confused about FETCH response";
+ break;
+ }
+
+ // * 1 FETCH (UID 1 RFC822.SIZE 511)
+ if(q=strstr(p, "RFC822.SIZE")){
+ imap->size = atoi(q+11);
+ break;
+ }
+
+ // * 1 FETCH (UID 1 RFC822.HEADER {496}
+ // <496 bytes of data>
+ // )
+ // * 1 FETCH (UID 1 RFC822.HEADER "data")
+ if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
+ if((q = strchr(p, '{'))
+ && (n=strtol(q+1, &en, 0), *en=='}')){
+ if(imap->data == nil || n >= imap->size)
+ imapgrow(imap, n);
+ if((i = Bread(&imap->bin, imap->data, n)) != n){
+ snprint(error, sizeof error,
+ "short read %d != %d: %r\n",
+ i, n);
+ return error;
+ }
+ if(imap->debug)
+ fprint(2, "<- read %d bytes\n", n);
+ imap->data[n] = '\0';
+ if(imap->debug)
+ fprint(2, "<- %s\n", imap->data);
+ imap->data += n;
+ imap->size -= n;
+ p = Brdline(&imap->bin, '\n');
+ if(imap->debug)
+ fprint(2, "<- ignoring %.*s\n",
+ Blinelen(&imap->bin), p);
+ }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
+ *r = '\0';
+ q++;
+ n = r-q;
+ if(imap->data == nil || n >= imap->size)
+ imapgrow(imap, n);
+ memmove(imap->data, q, n);
+ imap->data[n] = '\0';
+ imap->data += n;
+ imap->size -= n;
+ }else
+ return "confused about FETCH response";
+ break;
+ }
+
+ // * 1 FETCH (UID 1)
+ // * 2 FETCH (UID 6)
+ if(q = strstr(p, "UID")){
+ if(imap->nuid < imap->muid)
+ imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
+ break;
+ }
+ }
+
+ if(imap->tag == 0)
+ return line;
+ break;
+
+ case '9': // response to our message
+ op = p;
+ if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
+ while(*p==' ')
+ p++;
+ imap->tag++;
+ return p;
+ }
+ fprint(2, "expected %lud; got %s\n", imap->tag, op);
+ break;
+
+ default:
+ if(imap->debug || *p)
+ fprint(2, "unexpected line: %s\n", p);
+ }
+ }
+ snprint(error, sizeof error, "i/o error: %r\n");
+ return error;
+}
+
+static int
+isokay(char *resp)
+{
+ return strncmp(resp, "OK", 2)==0;
+}
+
+//
+// log in to IMAP4 server, select mailbox, no SSL at the moment
+//
+static char*
+imap4login(Imap *imap)
+{
+ char *s;
+ UserPasswd *up;
+
+ imap->tag = 0;
+ s = imap4resp(imap);
+ if(!isokay(s))
+ return "error in initial IMAP handshake";
+
+ if(imap->user != nil)
+ up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
+ else
+ up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
+ if(up == nil)
+ return "cannot find IMAP password";
+
+ imap->tag = 1;
+ imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
+ free(up);
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+
+ imap4cmd(imap, "SELECT %Z", imap->mbox);
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+
+ return nil;
+}
+
+//
+// push tls onto a connection
+//
+int
+mypushtls(int fd)
+{
+ int p[2];
+ char buf[10];
+
+ if(pipe(p) < 0)
+ return -1;
+
+ switch(fork()){
+ case -1:
+ close(p[0]);
+ close(p[1]);
+ return -1;
+ case 0:
+ close(p[1]);
+ dup(p[0], 0);
+ dup(p[0], 1);
+ sprint(buf, "/fd/%d", fd);
+ execl("/bin/tlsrelay", "tlsrelay", "-f", buf, nil);
+ _exits(nil);
+ default:
+ break;
+ }
+ close(fd);
+ close(p[0]);
+ return p[1];
+}
+
+//
+// dial and handshake with the imap server
+//
+static char*
+imap4dial(Imap *imap)
+{
+ char *err, *port;
+ uchar digest[SHA1dlen];
+ int sfd;
+ TLSconn conn;
+
+ if(imap->fd >= 0){
+ imap4cmd(imap, "noop");
+ if(isokay(imap4resp(imap)))
+ return nil;
+ close(imap->fd);
+ imap->fd = -1;
+ }
+
+ if(imap->mustssl)
+ port = "imaps";
+ else
+ port = "imap4";
+
+ if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
+ return geterrstr();
+
+ if(imap->mustssl){
+ memset(&conn, 0, sizeof conn);
+ sfd = tlsClient(imap->fd, &conn);
+ if(sfd < 0)
+ sysfatal("tlsClient: %r");
+ if(conn.cert==nil || conn.certlen <= 0)
+ sysfatal("server did not provide TLS certificate");
+ sha1(conn.cert, conn.certlen, digest, nil);
+ if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
+ fmtinstall('H', encodefmt);
+ sysfatal("server certificate %.*H not recognized", SHA1dlen, digest);
+ }
+ free(conn.cert);
+ close(imap->fd);
+ imap->fd = sfd;
+
+ if(imap->debug){
+ char fn[128];
+ int fd;
+
+ snprint(fn, sizeof fn, "%s/ctl", conn.dir);
+ fd = open(fn, ORDWR);
+ if(fd < 0)
+ fprint(2, "opening ctl: %r\n");
+ if(fprint(fd, "debug") < 0)
+ fprint(2, "writing ctl: %r\n");
+ close(fd);
+ }
+ }
+ Binit(&imap->bin, imap->fd, OREAD);
+ Binit(&imap->bout, imap->fd, OWRITE);
+
+ if(err = imap4login(imap)) {
+ close(imap->fd);
+ return err;
+ }
+
+ return nil;
+}
+
+//
+// close connection
+//
+#if 0 /* jpc */
+static void
+imap4hangup(Imap *imap)
+{
+ imap4cmd(imap, "LOGOUT");
+ imap4resp(imap);
+ close(imap->fd);
+}
+#endif
+
+//
+// download a single message
+//
+static char*
+imap4fetch(Mailbox *mb, Message *m)
+{
+ int i;
+ char *p, *s, sdigest[2*SHA1dlen+1];
+ Imap *imap;
+
+ imap = mb->aux;
+
+ imap->size = 0;
+
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+
+ p = imap->base;
+ if(p == nil)
+ return "did not get message body";
+
+ removecr(p);
+ free(m->start);
+ m->start = p;
+ m->end = p+strlen(p);
+ m->bend = m->rbend = m->end;
+ m->header = m->start;
+
+ imap->base = nil;
+ imap->data = nil;
+
+ parse(m, 0, mb, 1);
+
+ // digest headers
+ sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
+ for(i = 0; i < SHA1dlen; i++)
+ sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
+ m->sdigest = s_copy(sdigest);
+
+ return nil;
+}
+
+//
+// check for new messages on imap4 server
+// download new messages, mark deleted messages
+//
+static char*
+imap4read(Imap *imap, Mailbox *mb, int doplumb)
+{
+ char *s;
+ int i, ignore, nnew, t;
+ Message *m, *next, **l;
+
+ imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+
+ imap->nuid = 0;
+ imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
+ imap->muid = imap->nmsg;
+
+ if(imap->nmsg > 0){
+ imap4cmd(imap, "UID FETCH 1:* UID");
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+ }
+
+ l = &mb->root->part;
+ for(i=0; i<imap->nuid; i++){
+ ignore = 0;
+ while(*l != nil){
+ if((*l)->imapuid == imap->uid[i]){
+ ignore = 1;
+ l = &(*l)->next;
+ break;
+ }else{
+ // old mail, we don't have it anymore
+ if(doplumb)
+ mailplumb(mb, *l, 1);
+ (*l)->inmbox = 0;
+ (*l)->deleted = 1;
+ l = &(*l)->next;
+ }
+ }
+ if(ignore)
+ continue;
+
+ // new message
+ m = newmessage(mb->root);
+ m->mallocd = 1;
+ m->inmbox = 1;
+ m->imapuid = imap->uid[i];
+
+ // add to chain, will download soon
+ *l = m;
+ l = &m->next;
+ }
+
+ // whatever is left at the end of the chain is gone
+ while(*l != nil){
+ if(doplumb)
+ mailplumb(mb, *l, 1);
+ (*l)->inmbox = 0;
+ (*l)->deleted = 1;
+ l = &(*l)->next;
+ }
+
+ // download new messages
+ t = imap->tag;
+ if(pipeline)
+ switch(rfork(RFPROC|RFMEM)){
+ case -1:
+ sysfatal("rfork: %r");
+ default:
+ break;
+ case 0:
+ for(m = mb->root->part; m != nil; m = m->next){
+ if(m->start != nil)
+ continue;
+ if(imap->debug)
+ fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+ t, (ulong)m->imapuid);
+ Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+ t++, (ulong)m->imapuid);
+ }
+ Bflush(&imap->bout);
+ _exits(nil);
+ }
+
+ nnew = 0;
+ for(m=mb->root->part; m!=nil; m=next){
+ next = m->next;
+ if(m->start != nil)
+ continue;
+
+ if(!pipeline){
+ Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+ (ulong)imap->tag, (ulong)m->imapuid);
+ Bflush(&imap->bout);
+ }
+
+ if(s = imap4fetch(mb, m)){
+ // message disappeared? unchain
+ fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
+ delmessage(mb, m);
+ mb->root->subname--;
+ continue;
+ }
+ nnew++;
+ if(doplumb)
+ mailplumb(mb, m, 0);
+ }
+ if(pipeline)
+ waitpid();
+
+ if(nnew || mb->vers == 0){
+ mb->vers++;
+ henter(PATH(0, Qtop), mb->name,
+ (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+ }
+ return nil;
+}
+
+//
+// sync mailbox
+//
+static void
+imap4purge(Imap *imap, Mailbox *mb)
+{
+ int ndel;
+ Message *m, *next;
+
+ ndel = 0;
+ for(m=mb->root->part; m!=nil; m=next){
+ next = m->next;
+ if(m->deleted && m->refs==0){
+ if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
+ imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
+ if(isokay(imap4resp(imap))){
+ ndel++;
+ delmessage(mb, m);
+ }
+ }else
+ delmessage(mb, m);
+ }
+ }
+
+ if(ndel){
+ imap4cmd(imap, "EXPUNGE");
+ imap4resp(imap);
+ }
+}
+
+//
+// connect to imap4 server, sync mailbox
+//
+static char*
+imap4sync(Mailbox *mb, int doplumb)
+{
+ char *err;
+ Imap *imap;
+
+ imap = mb->aux;
+
+ if(err = imap4dial(imap)){
+ mb->waketime = time(0) + imap->refreshtime;
+ return err;
+ }
+
+ if((err = imap4read(imap, mb, doplumb)) == nil){
+ imap4purge(imap, mb);
+ mb->d->atime = mb->d->mtime = time(0);
+ }
+ /*
+ * don't hang up; leave connection open for next time.
+ */
+ // imap4hangup(imap);
+ mb->waketime = time(0) + imap->refreshtime;
+ return err;
+}
+
+static char Eimap4ctl[] = "bad imap4 control message";
+
+static char*
+imap4ctl(Mailbox *mb, int argc, char **argv)
+{
+ int n;
+ Imap *imap;
+
+ imap = mb->aux;
+ if(argc < 1)
+ return Eimap4ctl;
+
+ if(argc==1 && strcmp(argv[0], "debug")==0){
+ imap->debug = 1;
+ return nil;
+ }
+
+ if(argc==1 && strcmp(argv[0], "nodebug")==0){
+ imap->debug = 0;
+ return nil;
+ }
+
+ if(argc==1 && strcmp(argv[0], "thumbprint")==0){
+ if(imap->thumb)
+ freeThumbprints(imap->thumb);
+ imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
+ }
+ if(strcmp(argv[0], "refresh")==0){
+ if(argc==1){
+ imap->refreshtime = 60;
+ return nil;
+ }
+ if(argc==2){
+ n = atoi(argv[1]);
+ if(n < 15)
+ return Eimap4ctl;
+ imap->refreshtime = n;
+ return nil;
+ }
+ }
+
+ return Eimap4ctl;
+}
+
+//
+// free extra memory associated with mb
+//
+static void
+imap4close(Mailbox *mb)
+{
+ Imap *imap;
+
+ imap = mb->aux;
+ free(imap->freep);
+ free(imap->base);
+ free(imap->uid);
+ if(imap->fd >= 0)
+ close(imap->fd);
+ free(imap);
+}
+
+//
+// open mailboxes of the form /imap/host/user
+//
+char*
+imap4mbox(Mailbox *mb, char *path)
+{
+ char *f[10];
+ int mustssl, nf;
+ Imap *imap;
+
+ quotefmtinstall();
+ fmtinstall('Z', doublequote);
+ if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
+ return Enotme;
+ mustssl = (strncmp(path, "/imaps/", 7) == 0);
+
+ path = strdup(path);
+ if(path == nil)
+ return "out of memory";
+
+ nf = getfields(path, f, 5, 0, "/");
+ if(nf < 3){
+ free(path);
+ return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
+ }
+
+ imap = emalloc(sizeof(*imap));
+ imap->fd = -1;
+ imap->debug = debug;
+ imap->freep = path;
+ imap->mustssl = mustssl;
+ imap->host = f[2];
+ if(nf < 4)
+ imap->user = nil;
+ else
+ imap->user = f[3];
+ if(nf < 5)
+ imap->mbox = "Inbox";
+ else
+ imap->mbox = f[4];
+ imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
+
+ mb->aux = imap;
+ mb->sync = imap4sync;
+ mb->close = imap4close;
+ mb->ctl = imap4ctl;
+ mb->d = emalloc(sizeof(*mb->d));
+ //mb->fetch = imap4fetch;
+
+ return nil;
+}
+
+//
+// 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, '"');
+}