2 * Locking here is not quite right.
3 * Calling qlock(&z->lk) can block the proc,
4 * and when it comes back, boxes and msgs might have been freed
5 * (if the refresh proc was holding the lock and in the middle of a
6 * redial). I've tried to be careful about not assuming boxes continue
7 * to exist across imap commands, but maybe this isn't really tenable.
8 * Maybe instead we should ref count the boxes and messages.
18 int ticks; /* until boom! */
44 "NonJunk", FlagNonJunk,
45 "\\Answered", FlagReplied,
46 "\\Flagged", FlagFlagged,
47 "\\Deleted", FlagDeleted,
49 "\\Recent", FlagRecent,
51 "\\NoInferiors", FlagNoInferiors,
52 "\\NoSelect", FlagNoSelect,
53 "\\Marked", FlagMarked,
54 "\\UnMarked", FlagUnMarked
59 static char *tag = "#";
61 static void checkbox(Imap*, Box*);
62 static char* copyaddrs(Sx*);
63 static void freeup(UserPasswd*);
64 static int getbox(Imap*, Box*);
65 static int getboxes(Imap*);
66 static char* gsub(char*, char*, char*);
67 static int imapcmd(Imap*, Box*, char*, ...);
68 static Sx* imapcmdsx(Imap*, Box*, char*, ...);
69 static Sx* imapcmdsx0(Imap*, char*, ...);
70 static Sx* imapvcmdsx(Imap*, Box*, char*, va_list);
71 static Sx* imapvcmdsx0(Imap*, char*, va_list);
72 static int imapdial(char*, int);
73 static int imaplogin(Imap*);
74 static int imapquote(Fmt*);
75 static int imapreconnect(Imap*);
76 static void imaprefreshthread(void*);
77 static void imaptimerproc(void*);
78 static Sx* imapwaitsx(Imap*);
79 static int isatom(Sx *v, char *name);
80 static int islist(Sx *v);
81 static int isnil(Sx *v);
82 static int isnumber(Sx *sx);
83 static int isstring(Sx *sx);
84 static int ioimapdial(Ioproc*, char*, int);
85 static char* nstring(Sx*);
86 static void unexpected(Imap*, Sx*);
87 static Sx* zBrdsx(Imap*);
90 * Imap connection maintenance and login.
94 imapconnect(char *server, int mode, char *root)
98 fmtinstall('H', encodefmt);
99 fmtinstall('Z', imapquote);
101 z = emalloc(sizeof *z);
102 z->server = estrdup(server);
105 if(root[0] != 0 && root[strlen(root)-1] != '/')
106 z->root = smprint("%s/", root);
112 z->autoreconnect = 0;
116 if(imapreconnect(z) < 0){
122 z->autoreconnect = 1;
125 proccreate(imaptimerproc, z, STACK);
126 mailthread(imaprefreshthread, z);
132 imaphangup(Imap *z, int ticks)
142 imapreconnect(Imap *z)
146 z->autoreconnect = 0;
156 fprint(2, "dial %s...\n", z->server);
157 if((z->fd = ioimapdial(z->io, z->server, z->mode)) < 0)
160 Binit(&z->b, z->fd, OREAD);
161 if((sx = zBrdsx(z)) == nil){
162 werrstr("no greeting");
166 fprint(2, "<I %#$\n", sx);
167 if(sx->nsx >= 2 && isatom(sx->sx[0], "*") && isatom(sx->sx[1], "PREAUTH")){
172 werrstr("bad greeting - %#$", sx);
180 if(getboxes(z) < 0 || getbox(z, z->inbox) < 0)
182 z->autoreconnect = 1;
192 z->autoreconnect = 1;
203 if((up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", z->server)) == nil){
204 werrstr("getuserpasswd - %r");
208 sx = imapcmdsx(z, nil, "LOGIN %Z %Z", up->user, up->passwd);
214 werrstr("login rejected - %#$", sx);
226 for(i=0; i<nboxes; i++){
228 boxes[i]->exists = 0;
229 boxes[i]->maxseen = 0;
231 if(imapcmd(z, nil, "LIST %Z *", z->root) < 0)
233 if(z->root != nil && imapcmd(z, nil, "LIST %Z INBOX", "") < 0)
235 if(z->nextbox && z->nextbox->mark)
237 for(r=boxes, w=boxes, e=boxes+nboxes; r<e; r++){
239 {fprint(2, "*** free box %s %s\n", (*r)->name, (*r)->imapname);
250 getbox(Imap *z, Box *b)
258 for(i=0; i<b->nmsg; i++)
259 b->msg[i]->imapid = 0;
260 if(imapcmd(z, b, "UID FETCH 1:* FLAGS") < 0)
262 for(r=b->msg, w=b->msg, e=b->msg+b->nmsg; r<e; r++){
263 if((*r)->imapid == 0)
270 b->nmsg = w - b->msg;
277 freeup(UserPasswd *up)
279 memset(up->user, 0, strlen(up->user));
280 memset(up->passwd, 0, strlen(up->passwd));
285 imaptimerproc(void *v)
299 checkbox(Imap *z, Box *b)
301 if(imapcmd(z, b, "NOOP") >= 0){
306 if(b==z->box && b->exists > b->maxseen){
307 imapcmd(z, b, "UID FETCH %d:* FULL",
314 imaprefreshthread(void *v)
326 checkbox(z, z->inbox);
332 * Run a single command and return the Sx. Does NOT redial.
335 imapvcmdsx0(Imap *z, char *fmt, va_list arg)
345 if(z->fd < 0 || !z->connected)
348 prefix = strlen(tag)+1;
350 fmtprint(&f, "%s ", tag);
351 fmtvprint(&f, fmt, arg);
352 fmtprint(&f, "\r\n");
357 fprint(2, "I> %s\n", s);
359 if(iowrite(z->io, z->fd, s, len) < 0){
370 imapcmdsx0(Imap *z, char *fmt, ...)
376 sx = imapvcmdsx0(z, fmt, arg);
382 * Run a single command on box b. Does redial.
385 imapvcmdsx(Imap *z, Box *b, char *fmt, va_list arg)
393 if(z->fd < 0 || !z->connected){
395 if(!z->autoreconnect)
397 if(imapreconnect(z) < 0)
399 if(b && z->nextbox == nil) /* box disappeared on reconnect */
403 if(b && b != z->box){
405 z->box->imapinit = 0;
407 if((sx=imapcmdsx0(z, "SELECT %Z", b->imapname)) == nil){
409 if(tries++ == 0 && (z->fd < 0 || !z->connected))
416 if((sx=imapvcmdsx0(z, fmt, arg)) == nil){
417 if(tries++ == 0 && (z->fd < 0 || !z->connected))
425 imapcmd(Imap *z, Box *b, char *fmt, ...)
431 sx = imapvcmdsx(z, b, fmt, arg);
435 if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){
445 imapcmdsx(Imap *z, Box *b, char *fmt, ...)
451 sx = imapvcmdsx(z, b, fmt, arg);
461 while((sx = zBrdsx(z)) != nil){
463 fprint(2, "<| %#$\n", sx);
464 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && cistrcmp(sx->sx[0]->data, tag) == 0)
466 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && strcmp(sx->sx[0]->data, "*") == 0)
468 if(sx->type == SxList && sx->nsx == 0){
479 * Imap interface to mail file system.
483 _bodyname(char *buf, char *ebuf, Part *p, char *extra)
486 fprint(2, "***** BUFFER TOO SMALL\n");
491 _bodyname(buf, ebuf, p->parent, "");
493 seprint(buf, ebuf, ".%d", p->pix+1);
496 seprint(buf, ebuf, "%s", extra);
500 bodyname(Part *p, char *extra)
502 static char buf[256];
503 memset(buf, 0, sizeof buf); /* can't see why this is necessary, but it is */
504 _bodyname(buf, buf+sizeof buf, p, extra);
505 return buf+1; /* buf[0] == '.' */
509 fetch1(Imap *z, Part *p, char *s)
512 imapcmd(z, p->msg->box, "UID FETCH %d BODY[%s]",
513 p->msg->imapuid, bodyname(p, s));
518 imapfetchrawheader(Imap *z, Part *p)
520 fetch1(z, p, ".HEADER");
524 imapfetchrawmime(Imap *z, Part *p)
526 fetch1(z, p, ".MIME");
530 imapfetchrawbody(Imap *z, Part *p)
532 fetch1(z, p, ".TEXT");
536 imapfetchraw(Imap *z, Part *p)
542 imaplistcmd(Imap *z, Box *box, char *before, Msg **m, uint nm, char *after)
552 fmtprint(&fmt, "%s ", before);
556 fmtprint(&fmt, "%ud", m[i]->imapuid);
558 fmtprint(&fmt, " %s", after);
559 cmd = fmtstrflush(&fmt);
562 if(imapcmd(z, box, "%s", cmd) < 0)
569 imapcopylist(Imap *z, char *nbox, Msg **m, uint nm)
578 if(strcmp(nbox, "mbox") == 0)
579 name = estrdup("INBOX");
581 p = esmprint("%s%s", z->root, nbox);
582 name = esmprint("%Z", p);
585 rv = imaplistcmd(z, m[0]->box, "UID COPY", m, nm, name);
592 imapremovelist(Imap *z, Msg **m, uint nm)
600 rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, "+FLAGS.SILENT (\\Deleted)");
601 /* careful - box might be gone; use z->box instead */
602 if(rv == 0 && z->box)
603 rv = imapcmd(z, z->box, "EXPUNGE");
609 imapflaglist(Imap *z, int op, int flag, Msg **m, uint nm)
623 fmtprint(&fmt, "%sFLAGS (", mod);
625 for(i=0; i<nelem(flagstab); i++){
626 if(flagstab[i].flag & flag){
627 fmtprint(&fmt, "%s%s", sep, flagstab[i].name);
632 s = fmtstrflush(&fmt);
635 rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, s);
642 imapsearchbox(Imap *z, Box *b, char *search, Msg ***mm)
650 if(imapcmd(z, b, "UID SEARCH CHARSET UTF-8 TEXT %Z", search) < 0){
661 m = emalloc(nuid*sizeof m[0]);
663 for(i=0; i<nuid; i++)
664 if((m[nm] = msgbyimapuid(b, uid[i], 0)) != nil)
672 imapcheckbox(Imap *z, Box *b)
682 * Imap utility routines
685 _ioimapdial(va_list *arg)
690 server = va_arg(*arg, char*);
691 mode = va_arg(*arg, int);
692 return imapdial(server, mode);
695 ioimapdial(Ioproc *io, char *server, int mode)
697 return iocall(io, _ioimapdial, server, mode);
701 _ioBrdsx(va_list *arg)
706 b = va_arg(*arg, Biobuf*);
707 sx = va_arg(*arg, Sx**);
709 if((*sx) && (*sx)->type == SxList && (*sx)->nsx == 0){
716 ioBrdsx(Ioproc *io, Biobuf *b)
720 iocall(io, _ioBrdsx, b, &sx);
727 if(z->ticks && --z->ticks==0){
732 return ioBrdsx(z->io, &z->b);
736 imapdial(char *server, int mode)
745 return dial(netmkaddr(server, "tcp", "143"), nil, nil, nil);
748 werrstr("starttls not supported");
754 fd[0] = dup(p[0], -1);
755 fd[1] = dup(p[0], -1);
757 tmp = esmprint("%s:993", server);
758 if(threadspawnl(fd, "/usr/sbin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0
759 && threadspawnl(fd, "/usr/bin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0){
775 fd[0] = dup(p[0], -1);
776 fd[1] = dup(p[0], -1);
778 if(threadspawnl(fd, "/usr/local/plan9/bin/rc", "rc", "-c", server, nil) < 0){
805 if(r=='\\' || r=='"')
817 s = va_arg(f->args, char*);
818 if(s == nil || *s == '\0')
819 return fmtstrcpy(f, "\"\"");
822 if(f->flags&FmtSharp)
825 w = chartorune(&r, t);
826 quotes |= needtoquote(r);
829 return fmtstrcpy(f, s);
833 w = chartorune(&r, t);
834 if(needtoquote(r) == Qbackslash)
838 return fmtrune(f, '"');
859 * Check S expression against format string.
862 sxmatch(Sx *sx, char *fmt)
866 for(i=0; fmt[i]; i++){
868 fmt--; /* like i-- but better */
869 if(i == sx->nsx && fmt[i+1] == '*')
875 if(sx->sx[i]->type == SxAtom && strcmp(sx->sx[i]->data, "NIL") == 0){
877 free(sx->sx[i]->data);
878 sx->sx[i]->data = nil;
879 sx->sx[i]->type = SxList;
883 else if(fmt[i] == 'S'){
884 free(sx->sx[i]->data);
885 sx->sx[i]->data = nil;
886 sx->sx[i]->type = SxString;
889 if(sx->sx[i]->type == SxAtom && fmt[i]=='S')
890 sx->sx[i]->type = SxString;
891 if(sx->sx[i]->type != fmttype(fmt[i])){
892 fprint(2, "sxmatch: %$ not %c\n", sx->sx[i], fmt[i]);
902 * Check string against format string.
905 stringmatch(char *fmt, char *s)
907 for(; *fmt && *s; fmt++, s++){
914 if(*s < '0' || *s > '9')
918 if(*s < 'A' || *s > 'Z')
922 if(*s < 'a' || *s > 'z')
926 if(*s != '-' && *s != '+')
941 * Parse simple S expressions and IMAP elements.
944 isatom(Sx *v, char *name)
948 if(v == nil || v->type != SxAtom)
951 if(cistrncmp(v->data, name, n) == 0)
952 if(v->data[n] == 0 || (n>0 && v->data[n-1] == '['))
960 if(sx->type == SxAtom)
962 return sx->type == SxString;
968 return sx->type == SxNumber;
975 (v->type==SxList && v->nsx == 0) ||
976 (v->type==SxAtom && strcmp(v->data, "NIL") == 0);
982 return isnil(v) || v->type==SxList;
990 if(v->type != SxList){
991 warn("malformed flags: %$", v);
995 for(i=0; i<v->nsx; i++){
996 if(v->sx[i]->type != SxAtom)
998 for(j=0; j<nelem(flagstab); j++)
999 if(cistrcmp(v->sx[i]->data, flagstab[j].name) == 0)
1000 f |= flagstab[j].flag;
1005 static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
1011 for(i=0; months[i]; i+=3)
1012 if(memcmp(s, months+i, 3) == 0)
1025 if(v->type != SxString || !stringmatch("01-Aaa-1111 01:11:11 +1111", v->data)){
1027 warn("bad date: %$", v);
1031 /* cannot use atoi because 09 is malformed octal! */
1032 memset(&tm, 0, sizeof tm);
1034 tm.mday = strtol(p, 0, 10);
1035 tm.mon = parsemon(p+3);
1038 tm.year = strtol(p+7, 0, 10) - 1900;
1039 tm.hour = strtol(p+12, 0, 10);
1040 tm.min = strtol(p+15, 0, 10);
1041 tm.sec = strtol(p+18, 0, 10);
1042 strcpy(tm.zone, "GMT");
1045 delta = ((p[22]-'0')*10+p[23]-'0')*3600 + ((p[24]-'0')*10+p[25]-'0')*60;
1056 if(v->type != SxNumber)
1062 hash(DigestState *ds, char *tag, char *val)
1066 md5((uchar*)tag, strlen(tag)+1, nil, ds);
1067 md5((uchar*)val, strlen(val)+1, nil, ds);
1071 parseenvelope(Sx *v)
1077 if(v->type != SxList || !sxmatch(v, "SSLLLLLLSS")){
1078 warn("bad envelope: %$", v);
1082 hdr = emalloc(sizeof *hdr);
1083 hdr->date = nstring(v->sx[0]);
1084 hdr->subject = unrfc2047(nstring(v->sx[1]));
1085 hdr->from = copyaddrs(v->sx[2]);
1086 hdr->sender = copyaddrs(v->sx[3]);
1087 hdr->replyto = copyaddrs(v->sx[4]);
1088 hdr->to = copyaddrs(v->sx[5]);
1089 hdr->cc = copyaddrs(v->sx[6]);
1090 hdr->bcc = copyaddrs(v->sx[7]);
1091 hdr->inreplyto = unrfc2047(nstring(v->sx[8]));
1092 hdr->messageid = unrfc2047(nstring(v->sx[9]));
1094 memset(&ds, 0, sizeof ds);
1095 hash(&ds, "date", hdr->date);
1096 hash(&ds, "subject", hdr->subject);
1097 hash(&ds, "from", hdr->from);
1098 hash(&ds, "sender", hdr->sender);
1099 hash(&ds, "replyto", hdr->replyto);
1100 hash(&ds, "to", hdr->to);
1101 hash(&ds, "cc", hdr->cc);
1102 hash(&ds, "bcc", hdr->bcc);
1103 hash(&ds, "inreplyto", hdr->inreplyto);
1104 hash(&ds, "messageid", hdr->messageid);
1105 md5(0, 0, digest, &ds);
1106 hdr->digest = esmprint("%.16H", digest);
1119 if('A' <= *t && *t <= 'Z')
1137 * substitute all occurrences of a with b in s.
1140 gsub(char *s, char *a, char *b)
1142 char *p, *t, *w, *last;
1146 for(p=s; (p=strstr(p, a)) != nil; p+=strlen(a))
1150 t = emalloc(strlen(s)+n*strlen(b)+1);
1152 for(p=s; last=p, (p=strstr(p, a)) != nil; p+=strlen(a)){
1153 memmove(w, last, p-last);
1155 memmove(w, b, strlen(b));
1164 * Table-driven IMAP "unexpected response" parser.
1165 * All the interesting data is in the unexpected responses.
1167 static void xlist(Imap*, Sx*);
1168 static void xrecent(Imap*, Sx*);
1169 static void xexists(Imap*, Sx*);
1170 static void xok(Imap*, Sx*);
1171 static void xflags(Imap*, Sx*);
1172 static void xfetch(Imap*, Sx*);
1173 static void xexpunge(Imap*, Sx*);
1174 static void xbye(Imap*, Sx*);
1175 static void xsearch(Imap*, Sx*);
1181 void (*fn)(Imap*, Sx*);
1183 0, "BYE", nil, xbye,
1184 0, "FLAGS", "AAL", xflags,
1185 0, "LIST", "AALSS", xlist,
1187 0, "SEARCH", "AAN*", xsearch,
1189 1, "EXISTS", "ANA", xexists,
1190 1, "EXPUNGE", "ANA", xexpunge,
1191 1, "FETCH", "ANAL", xfetch,
1192 1, "RECENT", "ANA", xrecent
1196 unexpected(Imap *z, Sx *sx)
1201 if(sx->nsx >= 3 && sx->sx[1]->type == SxNumber && sx->sx[2]->type == SxAtom){
1203 name = sx->sx[2]->data;
1204 }else if(sx->nsx >= 2 && sx->sx[1]->type == SxAtom){
1206 name = sx->sx[1]->data;
1210 for(i=0; i<nelem(unextab); i++){
1211 if(unextab[i].num == num && cistrcmp(unextab[i].name, name) == 0){
1212 if(unextab[i].fmt && !sxmatch(sx, unextab[i].fmt)){
1213 warn("malformed %s: %$", name, sx);
1216 unextab[i].fn(z, sx);
1231 xlist(Imap *z, Sx *sx)
1237 s = estrdup(sx->sx[4]->data);
1238 if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0){
1239 s = gsub(s, "/", "_");
1240 s = gsub(s, sx->sx[3]->data, "/");
1244 * INBOX is the special imap name for the main mailbox.
1245 * All other mailbox names have the root prefix removed, if applicable.
1248 if(cistrcmp(s, "INBOX") == 0){
1251 s = estrdup("mbox");
1252 } else if(z->root && strstr(s, z->root) == s) {
1253 t = estrdup(s+strlen(z->root));
1259 * Plan 9 calls the main mailbox mbox.
1260 * Rename any existing mbox by appending a $.
1262 if(!inbox && strncmp(s, "mbox", 4) == 0 && alldollars(s+4)){
1263 t = emalloc(strlen(s)+2);
1273 box->imapname = estrdup(sx->sx[4]->data);
1277 box->flags = parseflags(sx->sx[2]);
1281 xrecent(Imap *z, Sx *sx)
1284 z->box->recent = sx->sx[1]->number;
1288 xexists(Imap *z, Sx *sx)
1291 z->box->exists = sx->sx[1]->number;
1292 if(z->box->exists < z->box->maxseen)
1293 z->box->maxseen = z->box->exists;
1298 xflags(Imap *z, Sx *sx)
1301 * This response contains in sx->sx[2] the list of flags
1302 * that can be validly attached to messages in z->box.
1303 * We don't have any use for this list, since we
1304 * use only the standard flags.
1309 xbye(Imap *z, Sx *sx)
1317 xexpunge(Imap *z, Sx *sx)
1322 if((b=z->box) == nil)
1324 n = sx->sx[1]->number;
1325 for(i=0; i<b->nmsg; i++){
1326 if(b->msg[i]->imapid == n){
1327 msgplumb(b->msg[i], 1);
1330 memmove(b->msg+i, b->msg+i+1, (b->nmsg-i)*sizeof b->msg[0]);
1336 if(b->msg[i]->imapid > n)
1337 b->msg[i]->imapid--;
1343 xsearch(Imap *z, Sx *sx)
1348 z->uid = emalloc((sx->nsx-2)*sizeof z->uid[0]);
1349 z->nuid = sx->nsx-2;
1350 for(i=0; i<z->nuid; i++)
1351 z->uid[i] = sx->sx[i+2]->number;
1355 * Table-driven FETCH message info parser.
1357 static void xmsgflags(Msg*, Sx*, Sx*);
1358 static void xmsgdate(Msg*, Sx*, Sx*);
1359 static void xmsgrfc822size(Msg*, Sx*, Sx*);
1360 static void xmsgenvelope(Msg*, Sx*, Sx*);
1361 static void xmsgbody(Msg*, Sx*, Sx*);
1362 static void xmsgbodydata(Msg*, Sx*, Sx*);
1366 void (*fn)(Msg*, Sx*, Sx*);
1369 "INTERNALDATE", xmsgdate,
1370 "RFC822.SIZE", xmsgrfc822size,
1371 "ENVELOPE", xmsgenvelope,
1373 "BODY[", xmsgbodydata
1377 xfetch(Imap *z, Sx *sx)
1383 warn("FETCH but no open box: %$", sx);
1387 /* * 152 FETCH (UID 185 FLAGS () ...) */
1388 if(sx->sx[3]->nsx%2){
1389 warn("malformed FETCH: %$", sx);
1393 n = sx->sx[1]->number;
1395 for(i=0; i<sx->nsx; i+=2){
1396 if(isatom(sx->sx[i], "UID")){
1397 if(sx->sx[i+1]->type == SxNumber){
1398 uid = sx->sx[i+1]->number;
1403 /* This happens: too bad.
1404 warn("FETCH without UID: %$", sx);
1409 msg = msgbyimapuid(z->box, uid, 1);
1410 if(msg->imapid && msg->imapid != n)
1411 warn("msg id mismatch: want %d have %d", msg->id, n);
1413 for(i=0; i<sx->nsx; i+=2){
1414 for(j=0; j<nelem(msgtab); j++)
1415 if(isatom(sx->sx[i], msgtab[j].name))
1416 msgtab[j].fn(msg, sx->sx[i], sx->sx[i+1]);
1421 xmsgflags(Msg *msg, Sx *k, Sx *v)
1424 msg->flags = parseflags(v);
1428 xmsgdate(Msg *msg, Sx *k, Sx *v)
1431 msg->date = parsedate(v);
1435 xmsgrfc822size(Msg *msg, Sx *k, Sx *v)
1438 msg->size = parsenumber(v);
1457 char *name, *email, *host, *mbox;
1466 for(i=0; i<v->nsx; i++){
1467 if(!sxmatch(v->sx[i], "SSSS"))
1468 warn("bad address: %$", v->sx[i]);
1469 name = unrfc2047(nstring(v->sx[i]->sx[0]));
1470 /* ignore sx[1] - route */
1471 mbox = unrfc2047(nstring(v->sx[i]->sx[2]));
1472 host = unrfc2047(nstring(v->sx[i]->sx[3]));
1473 if(mbox == nil || host == nil){ /* rfc822 group syntax */
1479 email = esmprint("%s@%s", mbox, host);
1482 fmtprint(&fmt, "%s%q %q", sep, name ? name : "", email ? email : "");
1487 s = fmtstrflush(&fmt);
1489 sysfatal("out of memory");
1494 xmsgenvelope(Msg *msg, Sx *k, Sx *v)
1496 hdrfree(msg->part[0]->hdr);
1497 msg->part[0]->hdr = parseenvelope(v);
1504 "charset", offsetof(Part, charset),
1505 "name", offsetof(Part, filename)
1509 parseparams(Part *part, Sx *v)
1517 warn("bad message params: %$", v);
1520 for(i=0; i<v->nsx; i+=2){
1521 s = nstring(v->sx[i]);
1522 t = nstring(v->sx[i+1]);
1523 for(j=0; j<nelem(paramtab); j++){
1524 if(cistrcmp(paramtab[j].name, s) == 0){
1525 p = (char**)((char*)part+paramtab[j].offset);
1538 parsestructure(Part *part, Sx *v)
1545 if(v->type != SxList){
1547 warn("bad structure: %$", v);
1550 if(islist(v->sx[0])){
1552 for(i=0; i<v->nsx && islist(v->sx[i]); i++)
1553 parsestructure(partcreate(part->msg, part), v->sx[i]);
1555 if(i != v->nsx-1 || !isstring(v->sx[i])){
1556 warn("bad multipart structure: %$", v);
1557 part->type = estrdup("multipart/mixed");
1560 s = nstring(v->sx[i]);
1562 part->type = esmprint("multipart/%s", s);
1567 if(!isstring(v->sx[0]) || v->nsx < 2)
1569 s = nstring(v->sx[0]);
1570 t = nstring(v->sx[1]);
1574 part->type = esmprint("%s/%s", s, t);
1575 if(v->nsx < 7 || !islist(v->sx[2]) || !isstring(v->sx[3])
1576 || !isstring(v->sx[4]) || !isstring(v->sx[5]) || !isnumber(v->sx[6]))
1578 parseparams(part, v->sx[2]);
1579 part->idstr = nstring(v->sx[3]);
1580 part->desc = nstring(v->sx[4]);
1581 part->encoding = nstring(v->sx[5]);
1582 part->size = v->sx[6]->number;
1583 if(strcmp(s, "message") == 0 && strcmp(t, "rfc822") == 0){
1584 if(v->nsx < 10 || !islist(v->sx[7]) || !islist(v->sx[8]) || !isnumber(v->sx[9]))
1586 part->hdr = parseenvelope(v->sx[7]);
1587 parsestructure(partcreate(part->msg, part), v->sx[8]);
1588 part->lines = v->sx[9]->number;
1590 if(strcmp(s, "text") == 0){
1591 if(v->nsx < 8 || !isnumber(v->sx[7]))
1593 part->lines = v->sx[7]->number;
1598 xmsgbody(Msg *msg, Sx *k, Sx *v)
1600 if(v->type != SxList){
1601 warn("bad body: %$", v);
1605 * To follow the structure exactly we should
1606 * be doing this to partcreate(msg, msg->part[0]),
1607 * and we should leave msg->part[0] with type message/rfc822,
1608 * but the extra layer is redundant - what else would be in a mailbox?
1610 parsestructure(msg->part[0], v);
1611 if(msg->box->maxseen < msg->imapid)
1612 msg->box->maxseen = msg->imapid;
1613 if(msg->imapuid >= msg->box->uidnext)
1614 msg->box->uidnext = msg->imapuid+1;
1619 xmsgbodydata(Msg *msg, Sx *k, Sx *v)
1626 name += 5; /* body[ */
1627 p = strchr(name, ']');
1631 /* now name is something like 1 or 3.2.MIME - walk down parts from root */
1632 part = msg->part[0];
1635 while('1' <= name[0] && name[0] <= '9'){
1636 i = strtol(name, &p, 10);
1640 warn("bad body name: %$", k);
1643 if((part = subpart(part, i-1)) == nil){
1644 warn("unknown body part: %$", k);
1651 if(cistrcmp(name, "") == 0){
1653 part->raw = nstring(v);
1655 }else if(cistrcmp(name, "HEADER") == 0){
1656 free(part->rawheader);
1657 part->rawheader = nstring(v);
1658 nocr(part->rawheader);
1659 }else if(cistrcmp(name, "MIME") == 0){
1660 free(part->mimeheader);
1661 part->mimeheader = nstring(v);
1662 nocr(part->mimeheader);
1663 }else if(cistrcmp(name, "TEXT") == 0){
1664 free(part->rawbody);
1665 part->rawbody = nstring(v);
1666 nocr(part->rawbody);
1671 * Table-driven OK info parser.
1673 static void xokuidvalidity(Imap*, Sx*);
1674 static void xokpermflags(Imap*, Sx*);
1675 static void xokunseen(Imap*, Sx*);
1676 static void xokreadwrite(Imap*, Sx*);
1677 static void xokreadonly(Imap*, Sx*);
1682 void (*fn)(Imap*, Sx*);
1684 "UIDVALIDITY", 'N', xokuidvalidity,
1685 "PERMANENTFLAGS", 'L', xokpermflags,
1686 "UNSEEN", 'N', xokunseen,
1687 "READ-WRITE", 0, xokreadwrite,
1688 "READ-ONLY", 0, xokreadonly
1692 xok(Imap *z, Sx *sx)
1698 if(sx->nsx >= 4 && sx->sx[2]->type == SxAtom && sx->sx[2]->data[0] == '['){
1699 if(sx->sx[3]->type == SxAtom && sx->sx[3]->data[0] == ']')
1701 else if(sx->sx[4]->type == SxAtom && sx->sx[4]->data[0] == ']')
1704 warn("cannot parse OK: %$", sx);
1707 name = sx->sx[2]->data+1;
1708 for(i=0; i<nelem(oktab); i++){
1709 if(cistrcmp(name, oktab[i].name) == 0){
1710 if(oktab[i].fmt && (arg==nil || arg->type != fmttype(oktab[i].fmt))){
1711 warn("malformed %s: %$", name, arg);
1714 oktab[i].fn(z, arg);
1721 xokuidvalidity(Imap *z, Sx *sx)
1726 if((b=z->box) == nil)
1728 if(b->validity != sx->number){
1729 b->validity = sx->number;
1731 for(i=0; i<b->nmsg; i++)
1740 xokpermflags(Imap *z, Sx *sx)
1742 /* z->permflags = parseflags(sx); */
1746 xokunseen(Imap *z, Sx *sx)
1748 /* z->unseen = sx->number; */
1752 xokreadwrite(Imap *z, Sx *sx)
1754 /* z->boxmode = ORDWR; */
1758 xokreadonly(Imap *z, Sx *sx)
1760 /* z->boxmode = OREAD; */