📄 nedmail.c
字号:
#include "common.h"#include <ctype.h>#include <plumb.h>typedef struct Message Message;typedef struct Ctype Ctype;typedef struct Cmd Cmd;char root[Pathlen];char mbname[Elemlen];int rootlen;int didopen;char *user;char wd[2048];String *mbpath;int natural;int doflush;int interrupted;struct Message { Message *next; Message *prev; Message *cmd; Message *child; Message *parent; String *path; int id; int len; int fileno; // number of directory String *info; char *from; char *to; char *cc; char *replyto; char *date; char *subject; char *type; char *disposition; char *filename; char deleted; char stored;};Message top;struct Ctype { char *type; char *ext; int display; char *plumbdest; Ctype *next;};Ctype ctype[] = { { "text/plain", "txt", 1, 0 }, { "text/html", "htm", 1, 0 }, { "text/html", "html", 1, 0 }, { "text/tab-separated-values", "tsv", 1, 0 }, { "text/richtext", "rtx", 1, 0 }, { "text/rtf", "rtf", 1, 0 }, { "text", "txt", 1, 0 }, { "message/rfc822", "msg", 0, 0 }, { "image/bmp", "bmp", 0, "image" }, { "image/jpeg", "jpg", 0, "image" }, { "image/gif", "gif", 0, "image" }, { "image/png", "png", 0, "image" }, { "application/pdf", "pdf", 0, "postscript" }, { "application/postscript", "ps", 0, "postscript" }, { "application/", 0, 0, 0 }, { "image/", 0, 0, 0 }, { "multipart/", "mul", 0, 0 },};Message* acmd(Cmd*, Message*);Message* bcmd(Cmd*, Message*);Message* dcmd(Cmd*, Message*);Message* eqcmd(Cmd*, Message*);Message* hcmd(Cmd*, Message*);Message* Hcmd(Cmd*, Message*);Message* helpcmd(Cmd*, Message*);Message* icmd(Cmd*, Message*);Message* pcmd(Cmd*, Message*);Message* qcmd(Cmd*, Message*);Message* rcmd(Cmd*, Message*);Message* scmd(Cmd*, Message*);Message* ucmd(Cmd*, Message*);Message* wcmd(Cmd*, Message*);Message* xcmd(Cmd*, Message*);Message* ycmd(Cmd*, Message*);Message* pipecmd(Cmd*, Message*);Message* rpipecmd(Cmd*, Message*);Message* bangcmd(Cmd*, Message*);Message* Pcmd(Cmd*, Message*);Message* mcmd(Cmd*, Message*);Message* fcmd(Cmd*, Message*);Message* quotecmd(Cmd*, Message*);struct { char *cmd; int args; Message* (*f)(Cmd*, Message*); char *help;} cmdtab[] = { { "a", 1, acmd, "a reply to sender and recipients" }, { "A", 1, acmd, "A reply to sender and recipients with copy" }, { "b", 0, bcmd, "b print the next 10 headers" }, { "d", 0, dcmd, "d mark for deletion" }, { "f", 0, fcmd, "f file message by from address" }, { "h", 0, hcmd, "h print elided message summary (,h for all)" }, { "help", 0, helpcmd, "help print this info" }, { "H", 0, Hcmd, "H print message's MIME structure " }, { "i", 0, icmd, "i incorporate new mail" }, { "m", 1, mcmd, "m addr forward mail" }, { "M", 1, mcmd, "M addr forward mail with message" }, { "p", 0, pcmd, "p print the processed message" }, { "P", 0, Pcmd, "P print the raw message" }, { "\"", 0, quotecmd, "\" print a quoted version of msg" }, { "q", 0, qcmd, "q exit and remove all deleted mail" }, { "r", 1, rcmd, "r [addr] reply to sender plus any addrs specified" }, { "rf", 1, rcmd, "rf [addr]file message and reply" }, { "R", 1, rcmd, "R [addr] reply including copy of message" }, { "Rf", 1, rcmd, "Rf [addr]file message and reply with copy" }, { "s", 1, scmd, "s file append raw message to file" }, { "u", 0, ucmd, "u remove deletion mark" }, { "w", 1, wcmd, "w file store message contents as file" }, { "x", 0, xcmd, "x exit without flushing deleted messages" }, { "y", 0, ycmd, "y synchronize with mail box" }, { "=", 1, eqcmd, "= print current message number" }, { "|", 1, pipecmd, "|cmd pipe message body to a command" }, { "||", 1, rpipecmd, "||cmd pipe raw message to a command" }, { "!", 1, bangcmd, "!cmd run a command" }, { nil, 0, nil, nil },};enum{ NARG= 32,};struct Cmd { Message *msgs; Message *(*f)(Cmd*, Message*); int an; char *av[NARG]; int delete;};Biobuf out;int startedfs;int reverse;int longestfrom = 12;String* file2string(String*, char*);int dir2message(Message*, int);int filelen(String*, char*);String* extendpath(String*, char*);void snprintheader(char*, int, Message*);void cracktime(char*, char*, int);int cistrncmp(char*, char*, int);int cistrcmp(char*, char*);Reprog* parsesearch(char**);char* parseaddr(char**, Message*, Message*, Message*, Message**);char* parsecmd(char*, Cmd*, Message*, Message*);char* readline(char*, char*, int);void messagecount(Message*);void system(char*, char**, int);void mkid(String*, Message*);int switchmb(char*, char*);void closemb(void);int lineize(char*, char**, int);int rawsearch(Message*, Reprog*);Message* dosingleton(Message*, char*);String* rooted(String*);int plumb(Message*, Ctype*);String* addrecolon(char*);void exitfs(char*);Message* flushdeleted(Message*);voidusage(void){ fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0); fprint(2, " %s -c dir\n", argv0); exits("usage");}voidcatchnote(void*, char *note){ if(strstr(note, "interrupt") != nil){ interrupted = 1; noted(NCONT); } noted(NDFLT);}char *plural(int n){ if (n == 1) return ""; return "s"; }voidmain(int argc, char **argv){ Message *cur, *m, *x; char cmdline[4*1024]; Cmd cmd; Ctype *cp; char *err; int n, cflag; char *av[4]; String *prompt; char *file, *singleton; Binit(&out, 1, OWRITE); file = nil; singleton = nil; reverse = 1; cflag = 0; ARGBEGIN { case 'c': cflag = 1; break; case 'f': file = EARGF(usage()); break; case 's': singleton = EARGF(usage()); break; case 'r': reverse = 0; break; case 'n': natural = 1; reverse = 0; break; default: usage(); break; } ARGEND; user = getlog(); if(user == nil || *user == 0) sysfatal("can't read user name"); if(cflag){ if(argc > 0) creatembox(user, argv[0]); else creatembox(user, nil); exits(0); } if(argc) usage(); if(access("/mail/fs/ctl", 0) < 0){ startedfs = 1; av[0] = "fs"; av[1] = "-p"; av[2] = 0; system("/bin/upas/fs", av, -1); } switchmb(file, singleton); top.path = s_copy(root); for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++) cp->next = cp+1; if(singleton != nil){ cur = dosingleton(&top, singleton); if(cur == nil){ Bprint(&out, "no message\n"); exitfs(0); } pcmd(nil, cur); } else { cur = ⊤ n = dir2message(&top, reverse); if(n < 0) sysfatal("can't read %s", s_to_c(top.path)); Bprint(&out, "%d message%s\n", n, plural(n)); } notify(catchnote); prompt = s_new(); for(;;){ s_reset(prompt); if(cur == &top) s_append(prompt, ": "); else { mkid(prompt, cur); s_append(prompt, ": "); } // leave space at the end of cmd line in case parsecmd needs to // add a space after a '|' or '!' if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil) break; err = parsecmd(cmdline, &cmd, top.child, cur); if(err != nil){ Bprint(&out, "!%s\n", err); continue; } if(singleton != nil && cmd.f == icmd){ Bprint(&out, "!illegal command\n"); continue; } interrupted = 0; if(cmd.msgs == nil || cmd.msgs == &top){ x = (*cmd.f)(&cmd, &top); if(x != nil) cur = x; } else for(m = cmd.msgs; m != nil; m = m->cmd){ x = m; if(cmd.delete){ dcmd(&cmd, x); // dp acts differently than all other commands // since its an old lesk idiom that people love. // it deletes the current message, moves the current // pointer ahead one and prints. if(cmd.f == pcmd){ if(x->next == nil){ Bprint(&out, "!address\n"); cur = x; break; } else x = x->next; } } x = (*cmd.f)(&cmd, x); if(x != nil) cur = x; if(interrupted) break; if(singleton != nil && (cmd.delete || cmd.f == dcmd)) qcmd(nil, nil); } if(doflush) cur = flushdeleted(cur); } qcmd(nil, nil);}//// read the message info//Message*file2message(Message *parent, char *name){ Message *m; String *path; char *f[10]; m = mallocz(sizeof(Message), 1); if(m == nil) return nil; m->path = path = extendpath(parent->path, name); m->fileno = atoi(name); m->info = file2string(path, "info"); lineize(s_to_c(m->info), f, nelem(f)); m->from = f[0]; m->to = f[1]; m->cc = f[2]; m->replyto = f[3]; m->date = f[4]; m->subject = f[5]; m->type = f[6]; m->disposition = f[7]; m->filename = f[8]; m->len = filelen(path, "raw"); if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0) dir2message(m, 0); m->parent = parent; return m;}voidfreemessage(Message *m){ Message *nm, *next; for(nm = m->child; nm != nil; nm = next){ next = nm->next; freemessage(nm); } s_free(m->path); s_free(m->info); free(m);}//// read a directory into a list of messages//intdir2message(Message *parent, int reverse){ int i, n, fd, highest, newmsgs; Dir *d; Message *first, *last, *m; fd = open(s_to_c(parent->path), OREAD); if(fd < 0) return -1; // count current entries first = parent->child; highest = newmsgs = 0; for(last = parent->child; last != nil && last->next != nil; last = last->next) if(last->fileno > highest) highest = last->fileno; if(last != nil) if(last->fileno > highest) highest = last->fileno; n = dirreadall(fd, &d); for(i = 0; i < n; i++){ if((d[i].qid.type & QTDIR) == 0) continue; if(atoi(d[i].name) <= highest) continue; m = file2message(parent, d[i].name); if(m == nil) break; newmsgs++; if(reverse){ m->next = first; if(first != nil) first->prev = m; first = m; } else { if(first == nil) first = m; else last->next = m; m->prev = last; last = m; } } free(d); close(fd); parent->child = first; // renumber and file longest from i = 1; longestfrom = 12; for(m = first; m != nil; m = m->next){ m->id = natural ? m->fileno : i++; n = strlen(m->from); if(n > longestfrom) longestfrom = n; } return newmsgs;}//// point directly to a message//Message*dosingleton(Message *parent, char *path){ char *p, *np; Message *m; // walk down to message and read it if(strlen(path) < rootlen) return nil; if(path[rootlen] != '/') return nil; p = path+rootlen+1; np = strchr(p, '/'); if(np != nil) *np = 0; m = file2message(parent, p); if(m == nil) return nil; parent->child = m; m->id = 1; // walk down to requested component while(np != nil){ *np = '/'; np = strchr(np+1, '/'); if(np != nil) *np = 0; for(m = m->child; m != nil; m = m->next) if(strcmp(path, s_to_c(m->path)) == 0) return m; if(m == nil) return nil; } return m;}//// read a file into a string//String*file2string(String *dir, char *file){ String *s; int fd, n, m; s = extendpath(dir, file); fd = open(s_to_c(s), OREAD); s_grow(s, 512); /* avoid multiple reads on info files */ s_reset(s); if(fd < 0) return s; for(;;){ n = s->end - s->ptr; if(n == 0){ s_grow(s, 128); continue; } m = read(fd, s->ptr, n); if(m <= 0) break; s->ptr += m; if(m < n) break; } s_terminate(s); close(fd); return s;}//// get the length of a file//intfilelen(String *dir, char *file){ String *path; Dir *d; int rv; path = extendpath(dir, file); d = dirstat(s_to_c(path)); if(d == nil){ s_free(path); return -1; } s_free(path); rv = d->length; free(d); return rv;}//// walk the path name an element//String*extendpath(String *dir, char *name){ String *path; if(strcmp(s_to_c(dir), ".") == 0) path = s_new(); else { path = s_copy(s_to_c(dir)); s_append(path, "/"); } s_append(path, name); return path;}intcistrncmp(char *a, char *b, int n){ while(n-- > 0){ if(tolower(*a++) != tolower(*b++)) return -1; } return 0;}intcistrcmp(char *a, char *b){ for(;;){ if(tolower(*a) != tolower(*b++)) return -1; if(*a++ == 0) break; } return 0;}char*nosecs(char *t){ char *p; p = strchr(t, ':'); if(p == nil) return t; p = strchr(p+1, ':'); if(p != nil) *p = 0; return t;}char *months[12] ={ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};intmonth(char *m){ int i; for(i = 0; i < 12; i++) if(cistrcmp(m, months[i]) == 0) return i+1; return 1;}enum{ Yearsecs= 365*24*60*60};voidcracktime(char *d, char *out, int len){ char in[64]; char *f[6]; int n; Tm tm; long now, then; char *dtime; *out = 0; if(d == nil) return; strncpy(in, d, sizeof(in)); in[sizeof(in)-1] = 0; n = getfields(in, f, 6, 1, " \t\r\n"); if(n != 6){ // unknown style snprint(out, 16, "%10.10s", d); return; } now = time(0); memset(&tm, 0, sizeof tm); if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){ // 822 style tm.year = atoi(f[3])-1900; tm.mon = month(f[2]); tm.mday = atoi(f[1]); dtime = nosecs(f[4]); then = tm2sec(&tm); } else if(strchr(f[3], ':') != nil){ // unix style tm.year = atoi(f[5])-1900; tm.mon = month(f[1]); tm.mday = atoi(f[2]); dtime = nosecs(f[3]); then = tm2sec(&tm); } else { then = now; tm = *localtime(now); dtime = ""; } if(now - then < Yearsecs/2) snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime); else snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900);}Ctype*findctype(Message *m){ char *p; char ftype[128]; int n, pfd[2]; Ctype *a, *cp; static Ctype nulltype = { "", 0, 0, 0 }; static Ctype bintype = { "application/octet-stream", "bin", 0, 0 }; for(cp = ctype; cp; cp = cp->next) if(strncmp(cp->type, m->type, strlen(cp->type)) == 0) return cp;/* use file(1) for any unknown mimetypes * * if (strcmp(m->type, bintype.type) != 0) * return &nulltype; */ if(pipe(pfd) < 0) return &bintype; *ftype = 0; switch(fork()){ case -1: break; case 0: close(pfd[1]); close(0); dup(pfd[0], 0); close(1); dup(pfd[0], 1); execl("/bin/file", "file", "-m", s_to_c(extendpath(m->path, "body")), nil); exits(0); default: close(pfd[0]); n = read(pfd[1], ftype, sizeof(ftype)); if(n > 0) ftype[n] = 0; close(pfd[1]); waitpid(); break; } if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil) return &bintype; *p++ = 0; a = mallocz(sizeof(Ctype), 1); a->type = strdup(ftype); a->ext = strdup(p); a->display = 0; a->plumbdest = strdup(ftype); for(cp = ctype; cp->next; cp = cp->next) continue; cp->next = a; a->next = nil; return a;}voidmkid(String *s, Message *m){ char buf[32]; if(m->parent != &top){ mkid(s, m->parent); s_append(s, "."); } sprint(buf, "%d", m->id); s_append(s, buf);}voidsnprintheader(char *buf, int len, Message *m){ char timebuf[32]; String *id; char *p, *q;; // create id id = s_new(); mkid(id, m); if(*m->from == 0){ // no from snprint(buf, len, "%-3s %s %6d %s", s_to_c(id), m->type, m->len, m->filename); } else if(*m->subject){ q = p = strdup(m->subject); while(*p == ' ') p++; if(strlen(p) > 50) p[50] = 0; cracktime(m->date, timebuf, sizeof(timebuf)); snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s", s_to_c(id), m->child ? 'H' : ' ', m->deleted ? 'd' : ' ', m->stored ? 's' : ' ', m->len, timebuf, longestfrom, longestfrom, m->from, p); free(q); } else { cracktime(m->date, timebuf, sizeof(timebuf)); snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s", s_to_c(id), m->child ? 'H' : ' ', m->deleted ? 'd' : ' ', m->stored ? 's' : ' ', m->len, timebuf, m->from); } s_free(id);}char *spaces = " ";voidsnprintHeader(char *buf, int len, int indent, Message *m){ String *id; char typeid[64]; char *p, *e; // create id id = s_new(); mkid(id, m); e = buf + len; snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type); if(indent < 6) p = seprint(buf, e, "%-32s %-6d ", typeid, m->len); else
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -