summary refs log tree commit diff
path: root/mshow.c
diff options
context:
space:
mode:
Diffstat (limited to 'mshow.c')
-rw-r--r--mshow.c468
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;
+}