mblaze/mshow.c
Christian Neukirchen 113676c48e mshow: -R to render all text/plain parts
(should be extended to text/html later if they are the only ones...)
2016-07-20 16:58:06 +02:00

527 lines
10 KiB
C

#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 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)))) {
char *charset = 0, *cs, *cse;
if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) {
charset = strndup(cs, cse-cs);
printf(" charset=\"%s\"", charset);
setenv("PIPE_CHARSET", charset, 1);
free(charset);
}
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
reply_mime(int depth, char *ct, char *body, size_t bodylen)
{
(void) depth;
char *mt = mimetype(ct);
char *tlmt = tlmimetype(ct);
if (strncmp(ct, "text/plain", 10) == 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);
}
free(mt);
free(tlmt);
return MIME_CONTINUE;
}
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);
}
void
reply(char *file)
{
struct message *msg = blaze822_file(file);
if (!msg)
return;
walk_mime(msg, 0, reply_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, "mshow: %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:Rn")) != -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 'R': Rflag = 1; 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 if (Rflag) { // render for reply
blaze822_loop(argc-optind, argv+optind, reply);
} else { // show
if (!(qflag || rflag)) {
char *f = getenv("MAILFILTER");
if (!f)
f = blaze822_home_file(".santoku/filter");
if (f)
filters = blaze822(f);
}
if (argc == optind) {
char *cur[] = { "." };
blaze822_loop(1, cur, show);
} else {
blaze822_loop(argc-optind, argv+optind, show);
}
if (!nflag) // don't set cur
blaze822_seq_setcur(newcur);
}
return 0;
}