diff options
Diffstat (limited to 'mshow.c')
-rw-r--r-- | mshow.c | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/mshow.c b/mshow.c new file mode 100644 index 0000000..1a48787 --- /dev/null +++ b/mshow.c @@ -0,0 +1,468 @@ +#include <sys/stat.h> +#include <sys/types.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <iconv.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "blaze822.h" + +static int rflag; +static int qflag; +static int Hflag; +static int Lflag; +static int tflag; +static int nflag; +static char defaulthflags[] = "from:subject:to:cc:date:"; +static char *hflag = defaulthflags; +static char *xflag; +static char *Oflag; + +struct message *filters; + +static int mimecount; + +void +printhdr(char *hdr) +{ + int uc = 1; + + while (*hdr && *hdr != ':') { + putc(uc ? toupper(*hdr) : *hdr, stdout); + uc = (*hdr == '-'); + hdr++; + } + + if (*hdr) { + printf("%s\n", hdr); + } +} + +void +print_u8recode(char *body, size_t bodylen, char *srcenc) +{ + iconv_t ic; + + ic = iconv_open("UTF-8", srcenc); + if (ic == (iconv_t)-1) { + printf("unsupported encoding: %s\n", srcenc); + return; + } + + char final_char = 0; + + char buf[4096]; + while (bodylen > 0) { + char *bufptr = buf; + size_t buflen = sizeof buf; + size_t r = iconv(ic, &body, &bodylen, &bufptr, &buflen); + + if (bufptr != buf) { + fwrite(buf, 1, bufptr-buf, stdout); + final_char = bufptr[-1]; + } + + if (r != (size_t)-1) { // done, flush iconv + bufptr = buf; + buflen = sizeof buf; + r = iconv(ic, 0, 0, &bufptr, &buflen); + if (bufptr != buf) { + fwrite(buf, 1, bufptr-buf, stdout); + final_char = bufptr[-1]; + } + if (r != (size_t)-1) + break; + } + + if (r == (size_t)-1 && errno != E2BIG) { + perror("iconv"); + break; + } + } + + if (final_char != '\n') + printf("\n"); + + iconv_close(ic); +} + +char * +mimetype(char *ct) +{ + char *s; + for (s = ct; *s && *s != ';' && *s != ' ' && *s != '\t'; s++) + ; + + return strndup(ct, s-ct); +} + +char * +tlmimetype(char *ct) +{ + char *s; + for (s = ct; *s && *s != ';' && *s != ' ' && *s != '\t' && *s != '/'; s++) + ; + + return strndup(ct, s-ct); +} + +typedef enum { + MIME_CONTINUE, + MIME_STOP, + MIME_PRUNE, +} mime_action; + +typedef mime_action (*mime_callback)(int, char *, char *, size_t); + +mime_action +render_mime(int depth, char *ct, char *body, size_t bodylen) +{ + char *mt = mimetype(ct); + char *tlmt = tlmimetype(ct); + + char *filename = 0, *fn, *fne; + if (blaze822_mime_parameter(ct, "name", &fn, &fne)) + filename = strndup(fn, fne-fn); + + mimecount++; + + int i; + for (i = 0; i < depth+1; i++) + printf("--- "); + printf("%d: %s size=%zd", mimecount, mt, bodylen); + if (filename) { + printf(" name=%s", filename); + free(filename); + } + + char *cmd; + mime_action r = MIME_CONTINUE; + + if (filters && + ((cmd = blaze822_chdr(filters, mt)) || + (cmd = blaze822_chdr(filters, tlmt)))) { + printf(" filter=\"%s\" ---\n", cmd); + FILE *p; + fflush(stdout); + p = popen(cmd, "w"); + if (!p) { + perror("popen"); + goto nofilter; + } + fwrite(body, 1, bodylen, p); + if (pclose(p) != 0) { + perror("pclose"); + goto nofilter; + } + r = MIME_PRUNE; + } else { +nofilter: + printf(" ---\n"); + + if (strncmp(ct, "text/", 5) == 0) { + char *charset = 0, *cs, *cse; + if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) + charset = strndup(cs, cse-cs); + if (!charset || + strcasecmp(charset, "utf-8") == 0 || + strcasecmp(charset, "utf8") == 0 || + strcasecmp(charset, "us-ascii") == 0) + fwrite(body, 1, bodylen, stdout); + else + print_u8recode(body, bodylen, charset); + free(charset); + } else if (strncmp(ct, "message/rfc822", 14) == 0) { + struct message *imsg = blaze822_mem(body, bodylen); + char *h = 0; + if (imsg) { + while ((h = blaze822_next_header(imsg, h))) + printf("%s\n", h); + printf("\n"); + } + } else if (strncmp(ct, "multipart/", 10) == 0) { + ; + } else { + printf("no filter or default handler\n"); + } + } + + free(mt); + free(tlmt); + + return r; +} + +mime_action +list_mime(int depth, char *ct, char *body, size_t bodylen) +{ + (void) body; + + char *mt = mimetype(ct); + char *fn, *fne; + + printf("%*.s%d: %s size=%zd", depth*2, "", ++mimecount, mt, bodylen); + if (blaze822_mime_parameter(ct, "name", &fn, &fne)) { + printf(" name="); + fwrite(fn, 1, fne-fn, stdout); + } + printf("\n"); + + return MIME_CONTINUE; +} + +mime_action +walk_mime(struct message *msg, int depth, mime_callback visit) +{ + char *ct, *body, *bodychunk; + size_t bodylen; + + mime_action r = MIME_CONTINUE; + + if (blaze822_mime_body(msg, &ct, &body, &bodylen, &bodychunk)) { + + mime_action r = visit(depth, ct, body, bodylen); + + if (r == MIME_CONTINUE) { + if (strncmp(ct, "multipart/", 10) == 0) { + struct message *imsg = 0; + while (blaze822_multipart(msg, &imsg)) { + r = walk_mime(imsg, depth+1, visit); + if (r == MIME_STOP) + break; + } + } else if (strncmp(ct, "message/rfc822", 14) == 0) { + struct message *imsg = blaze822_mem(body, bodylen); + if (imsg) + walk_mime(imsg, depth+1, visit); + } + } + + free(bodychunk); + } + + return r; +} + +void +list(char *file) +{ + struct message *msg = blaze822_file(file); + if (!msg) + return; + mimecount = 0; + walk_mime(msg, 0, list_mime); +} + +static int extract_argc; +static char **extract_argv; +static int extract_stdout; + +static int +writefile(char *name, char *buf, ssize_t len) +{ + int fd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd == -1) { + perror("open"); + return -1; + } + if (write(fd, buf, len) != len) { + // XXX partial write + perror("write"); + return -1; + } + close(fd); + return 0; +} + +mime_action +extract_mime(int depth, char *ct, char *body, size_t bodylen) +{ + (void) body; + (void) depth; + + char *filename = 0, *fn, *fne; + if (blaze822_mime_parameter(ct, "name", &fn, &fne)) + filename = strndup(fn, fne-fn); + + mimecount++; + + if (extract_argc == 0) { + if (extract_stdout) { // output all parts + fwrite(body, 1, bodylen, stdout); + } else { // extract all named attachments + if (filename) { + printf("%s\n", filename); + writefile(filename, body, bodylen); + } + } + } else { + int i; + for (i = 0; i < extract_argc; i++) { + char *a = extract_argv[i]; + char *b; + errno = 0; + long d = strtol(a, &b, 10); + if (errno == 0 && !*b && d == mimecount) { + // extract by id + if (extract_stdout) { + fwrite(body, 1, bodylen, stdout); + } else { + char buf[255]; + if (!filename) { + snprintf(buf, sizeof buf, + "attachment%d", mimecount); + filename = buf; + } + printf("%s\n", filename); + writefile(filename, body, bodylen); + } + } else if (filename && strcmp(a, filename) == 0) { + // extract by name + if (extract_stdout) { + fwrite(body, 1, bodylen, stdout); + } else { + printf("%s\n", filename); + writefile(filename, body, bodylen); + } + } + } + } + + free(filename); + return MIME_CONTINUE; +} + +void +extract(char *file, int argc, char **argv, int use_stdout) +{ + struct message *msg = blaze822_file(file); + if (!msg) + return; + mimecount = 0; + extract_argc = argc; + extract_argv = argv; + extract_stdout = use_stdout; + walk_mime(msg, 0, extract_mime); +} + +static char *newcur; + +void +show(char *file) +{ + struct message *msg; + + while (*file == ' ' || *file == '\t') + file++; + + if (newcur) { + printf("\014\n"); + free(newcur); + } + newcur = strdup(file); + + if (qflag) + msg = blaze822(file); + else + msg = blaze822_file(file); + if (!msg) { + fprintf(stderr, "show: %s: %s\n", file, strerror(errno)); + return; + } + + if (Hflag) { // raw headers + size_t hl = blaze822_headerlen(msg); + char *header = malloc(hl); + if (!header) + return; + int fd = open(file, O_RDONLY); + if (fd == -1) + return; + hl = read(fd, header, hl); + fwrite(header, 1, hl, stdout); + } else if (Lflag) { // all headers + char *h = 0; + while ((h = blaze822_next_header(msg, h))) { + char d[4096]; + blaze822_decode_rfc2047(d, h, sizeof d, "UTF-8"); + printhdr(d); + } + } else { // selected headers + char *h = hflag; + char *v; + while (*h) { + char *n = strchr(h, ':'); + if (n) + *n = 0; + v = blaze822_chdr(msg, h); + if (v) { + printhdr(h); + printf(": %s\n", v); + } + if (n) { + *n = ':'; + h = n + 1; + } else { + break; + } + } + } + + if (qflag) // no body + goto done; + + printf("\n"); + + if (rflag || !blaze822_check_mime(msg)) { // raw body + fwrite(blaze822_body(msg), 1, blaze822_bodylen(msg), stdout); + goto done; + } + + mimecount = 0; + walk_mime(msg, 0, render_mime); + +done: + blaze822_free(msg); +} + +int +main(int argc, char *argv[]) +{ + int c; + while ((c = getopt(argc, argv, "h:qrtHLx:O:n")) != -1) + switch(c) { + case 'h': hflag = optarg; break; + case 'q': qflag = 1; break; + case 'r': rflag = 1; break; + case 'H': Hflag = 1; break; + case 'L': Lflag = 1; break; + case 't': tflag = 1; break; + case 'x': xflag = optarg; break; + case 'O': Oflag = optarg; break; + case 'n': nflag = 1; break; + default: + // XXX usage + exit(1); + } + + if (xflag) { // extract + extract(xflag, argc-optind, argv+optind, 0); + } else if (Oflag) { // extract to stdout + extract(Oflag, argc-optind, argv+optind, 1); + } else if (tflag) { // list + blaze822_loop(argc-optind, argv+optind, list); + } else { // show + if (!(qflag || rflag)) + filters = blaze822("filters"); + blaze822_loop(argc-optind, argv+optind, show); + if (!nflag) // don't set cur + blaze822_seq_setcur(newcur); + } + + return 0; +} |