mblaze

Unix utilities to deal with Maildir - my mirror
git clone https://pi.duncano.de/git/mblaze.git

mshow.c (16613B)


      1 #include <sys/stat.h>
      2 #include <sys/types.h>
      3 
      4 #include <ctype.h>
      5 #include <errno.h>
      6 #include <fcntl.h>
      7 #include <fnmatch.h>
      8 #include <iconv.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <strings.h>
     13 #include <time.h>
     14 #include <unistd.h>
     15 
     16 #include "blaze822.h"
     17 
     18 static int rflag;
     19 static int Rflag;
     20 static int qflag;
     21 static int Fflag;
     22 static int Hflag;
     23 static int Lflag;
     24 static int Nflag;
     25 static int tflag;
     26 static int nflag;
     27 static char defaulthflags[] = "from:subject:to:cc:date:reply-to:";
     28 static char *hflag = defaulthflags;
     29 static char *xflag;
     30 static char *Oflag;
     31 
     32 static char fallback_ct[] = "text/plain";
     33 
     34 struct message *filters;
     35 
     36 static int mimecount;
     37 static int safe_output;
     38 
     39 static char defaultAflags[] = "text/plain:text/html";
     40 static char *Aflag = defaultAflags;
     41 
     42 static int
     43 printable(int c)
     44 {
     45 	return (unsigned)c-0x20 < 0x5f;
     46 }
     47 
     48 size_t
     49 print_ascii(char *body, size_t bodylen)
     50 {
     51 	if (safe_output) {
     52 		safe_u8putstr(body, bodylen, stdout);
     53 		return bodylen;
     54 	} else {
     55 		return fwrite(body, 1, bodylen, stdout);
     56 	}
     57 }
     58 
     59 void
     60 printhdr(char *hdr)
     61 {
     62 	int uc = 1;
     63 
     64 	while (*hdr && *hdr != ':' && printable(*hdr)) {
     65 		putc(uc ? toupper(*hdr) : *hdr, stdout);
     66 		uc = (*hdr == '-');
     67 		hdr++;
     68 	}
     69 
     70 	if (*hdr) {
     71 		print_ascii(hdr, strlen(hdr));
     72 		fputc('\n', stdout);
     73 	}
     74 }
     75 
     76 void
     77 print_u8recode(char *body, size_t bodylen, char *srcenc)
     78 {
     79 	iconv_t ic;
     80 
     81 	ic = iconv_open("UTF-8", srcenc);
     82 	if (ic == (iconv_t)-1) {
     83 		printf("unsupported encoding: %s\n", srcenc);
     84 		return;
     85 	}
     86 
     87 	char final_char = 0;
     88 
     89 	char buf[4096];
     90 	while (bodylen > 0) {
     91 		char *bufptr = buf;
     92 		size_t buflen = sizeof buf;
     93 		size_t r = iconv(ic, &body, &bodylen, &bufptr, &buflen);
     94 
     95 		if (bufptr != buf) {
     96 			print_ascii(buf, bufptr-buf);
     97 			final_char = bufptr[-1];
     98 		}
     99 
    100 		if (r != (size_t)-1) {  // done, flush iconv
    101 			bufptr = buf;
    102 			buflen = sizeof buf;
    103 			r = iconv(ic, 0, 0, &bufptr, &buflen);
    104 			if (bufptr != buf) {
    105 				print_ascii(buf, bufptr-buf);
    106 				final_char = bufptr[-1];
    107 			}
    108 			if (r != (size_t)-1)
    109 				break;
    110 		}
    111 
    112 		if (r == (size_t)-1 && errno != E2BIG) {
    113 			perror("iconv");
    114 			break;
    115 		}
    116 	}
    117 
    118 	if (final_char != '\n')
    119 		printf("\n");
    120 
    121 	iconv_close(ic);
    122 }
    123 
    124 char *
    125 mimetype(char *ct)
    126 {
    127 	char *s;
    128 
    129 	if (!ct)
    130 		return 0;
    131 	for (s = ct; *s && *s != ';' && *s != ' ' && *s != '\t'; s++)
    132 		;
    133 
    134 	return strndup(ct, s-ct);
    135 }
    136 
    137 char *
    138 tlmimetype(char *ct)
    139 {
    140 	char *s;
    141 
    142 	if (!ct)
    143 		return 0;
    144 	for (s = ct; *s && *s != ';' && *s != ' ' && *s != '\t' && *s != '/'; s++)
    145 		;
    146 
    147 	return strndup(ct, s-ct);
    148 }
    149 
    150 char *
    151 mime_filename(struct message *msg)
    152 {
    153 	static char buf[512];
    154 	char *v;
    155 	char *filename = 0;
    156 
    157 	if ((v = blaze822_hdr(msg, "content-disposition"))) {
    158 		if (blaze822_mime2231_parameter(v, "filename",
    159 		    buf, sizeof buf, "UTF-8"))
    160 			filename = buf;
    161 	} else if ((v = blaze822_hdr(msg, "content-type"))) {
    162 		if (blaze822_mime2231_parameter(v, "name",
    163 		    buf, sizeof buf, "UTF-8"))
    164 			filename = buf;
    165 	}
    166 
    167 	return filename;
    168 }
    169 
    170 static void choose_alternative(struct message *msg, int depth);
    171 
    172 void
    173 print_filename(char *filename)
    174 {
    175 	if (filename) {
    176 		printf(" name=\"");
    177 		safe_u8putstr(filename, strlen(filename), stdout);
    178 		printf("\"");
    179 	}
    180 }
    181 
    182 blaze822_mime_action
    183 render_mime(int depth, struct message *msg, char *body, size_t bodylen)
    184 {
    185 	char *ct = blaze822_hdr(msg, "content-type");
    186 	if (!ct)
    187 		ct = fallback_ct;
    188 	char *mt = mimetype(ct);
    189 	char *tlmt = tlmimetype(ct);
    190 	char *filename = mime_filename(msg);
    191 
    192 	mimecount++;
    193 
    194 	if (!Nflag) {
    195 		int i;
    196 		for (i = 0; i < depth+1; i++)
    197 			printf("--- ");
    198 		printf("%d: %s size=%zd", mimecount, mt, bodylen);
    199 		print_filename(filename);
    200 	}
    201 
    202 	char *cmd;
    203 	blaze822_mime_action r = MIME_CONTINUE;
    204 
    205 	if (filters &&
    206 	    ((cmd = blaze822_chdr(filters, mt)) ||
    207 	    (cmd = blaze822_chdr(filters, tlmt)))) {
    208 		char *charset = 0, *cs, *cse;
    209 		if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) {
    210 			charset = strndup(cs, cse-cs);
    211 			printf(" charset=\"%s\"", charset);
    212 			setenv("PIPE_CHARSET", charset, 1);
    213 			free(charset);
    214 		}
    215 		setenv("PIPE_CONTENTTYPE", ct, 1);
    216 
    217 		char *output;
    218 		size_t outlen;
    219 		int e = filter(body, bodylen, cmd, &output, &outlen);
    220 
    221 		if (e == 0 || e == 62) { // replace output (62 == raw)
    222 			if (!Nflag)
    223 				printf(" render=\"%s\" ---\n", cmd);
    224 			if (outlen) {
    225 				if (e == 0)
    226 					print_ascii(output, outlen);
    227 				else
    228 					fwrite(output, 1, outlen, stdout);
    229 				if (output[outlen-1] != '\n')
    230 					putchar('\n');
    231 			}
    232 		} else if (e == 63) { // skip filter
    233 			free(output);
    234 			goto nofilter;
    235 		} else if (e == 64) { // decode output again
    236 			if (!Nflag)
    237 				printf(" filter=\"%s\" ---\n", cmd);
    238 			struct message *imsg = blaze822_mem(output, outlen);
    239 			if (imsg)
    240 				blaze822_walk_mime(imsg, depth+1, render_mime);
    241 			blaze822_free(imsg);
    242 		} else if (e >= 65 && e <= 80) { // choose N-64th part
    243 			struct message *imsg = 0;
    244 			int n = e - 64;
    245 			printf(" selector=\"%s\" part=%d ---\n", cmd, n);
    246 			while (blaze822_multipart(msg, &imsg)) {
    247 				if (--n == 0)
    248 					blaze822_walk_mime(imsg, depth+1, render_mime);
    249 			}
    250 			blaze822_free(imsg);
    251 		} else {
    252 			printf(" filter=\"%s\" FAILED status=%d", cmd, e);
    253 			free(output);
    254 			goto nofilter;
    255 		}
    256 
    257 		free(output);
    258 
    259 		r = MIME_PRUNE;
    260 	} else {
    261 nofilter:
    262 		if (!Nflag)
    263 			printf(" ---\n");
    264 
    265 		if (strncmp(ct, "text/", 5) == 0) {
    266 			char *charset = 0, *cs, *cse;
    267 			if (blaze822_mime_parameter(ct, "charset", &cs, &cse))
    268 				charset = strndup(cs, cse-cs);
    269 			if (!charset ||
    270 			    strcasecmp(charset, "utf-8") == 0 ||
    271 			    strcasecmp(charset, "utf8") == 0 ||
    272 			    strcasecmp(charset, "us-ascii") == 0) {
    273 				print_ascii(body, bodylen);
    274 				if (bodylen > 0 && body[bodylen-1] != '\n')
    275 					putchar('\n');
    276 			} else {
    277 				print_u8recode(body, bodylen, charset);
    278 			}
    279 			free(charset);
    280 		} else if (strncmp(ct, "message/rfc822", 14) == 0) {
    281 			struct message *imsg = blaze822_mem(body, bodylen);
    282 			char *h = 0;
    283 			while (imsg && (h = blaze822_next_header(imsg, h))) {
    284 				char d[4096];
    285 				blaze822_decode_rfc2047(d, h, sizeof d, "UTF-8");
    286 				printhdr(d);
    287 			}
    288 			printf("\n");
    289 		} else if (strncmp(ct, "multipart/alternative", 21) == 0) {
    290 			choose_alternative(msg, depth);
    291 
    292 			r = MIME_PRUNE;
    293 		} else if (strncmp(ct, "multipart/", 10) == 0) {
    294 			; // default blaze822_mime_walk action
    295 		} else {
    296 			printf("no filter or default handler\n");
    297 		}
    298 	}
    299 
    300 	free(mt);
    301 	free(tlmt);
    302 
    303 	return r;
    304 }
    305 
    306 static void
    307 choose_alternative(struct message *msg, int depth)
    308 {
    309 	int n = 1;
    310 	int m = 0;
    311 	char *p = Aflag + strlen(Aflag);
    312 
    313 	struct message *imsg = 0;
    314 	while (blaze822_multipart(msg, &imsg)) {
    315 		m++;
    316 		char *ict = blaze822_hdr(imsg, "content-type");
    317 		if (!ict)
    318 			ict = fallback_ct;
    319 		char *imt = mimetype(ict);
    320 
    321 		char *s = strstr(Aflag, imt);
    322 		if (s && s < p &&
    323 		    (s[strlen(imt)] == 0 || s[strlen(imt)] == ':')) {
    324 			p = s;
    325 			n = m;
    326 		}
    327 
    328 		free(imt);
    329 	}
    330 	blaze822_free(imsg);
    331 
    332 	imsg = 0;
    333 	while (blaze822_multipart(msg, &imsg))
    334 		if (--n == 0)
    335 			blaze822_walk_mime(imsg, depth+1, render_mime);
    336 	blaze822_free(imsg);
    337 }
    338 
    339 blaze822_mime_action
    340 reply_mime(int depth, struct message *msg, char *body, size_t bodylen)
    341 {
    342 	(void)depth;
    343 
    344 	char *ct = blaze822_hdr(msg, "content-type");
    345 	char *mt = mimetype(ct);
    346 	char *tlmt = tlmimetype(ct);
    347 
    348 	if (!ct || strncmp(ct, "text/plain", 10) == 0) {
    349 		char *charset = 0, *cs, *cse;
    350 		if (blaze822_mime_parameter(ct, "charset", &cs, &cse))
    351 			charset = strndup(cs, cse-cs);
    352 		if (!charset ||
    353 		    strcasecmp(charset, "utf-8") == 0 ||
    354 		    strcasecmp(charset, "utf8") == 0 ||
    355 		    strcasecmp(charset, "us-ascii") == 0)
    356 			print_ascii(body, bodylen);
    357 		else
    358 			print_u8recode(body, bodylen, charset);
    359 		free(charset);
    360 	}
    361 
    362 	free(mt);
    363 	free(tlmt);
    364 
    365 	return MIME_CONTINUE;
    366 }
    367 
    368 blaze822_mime_action
    369 list_mime(int depth, struct message *msg, char *body, size_t bodylen)
    370 {
    371 	(void)body;
    372 
    373 	char *ct = blaze822_hdr(msg, "content-type");
    374 	if (!ct)
    375 		ct = fallback_ct;
    376 	char *mt = mimetype(ct);
    377 	char *filename = mime_filename(msg);
    378 
    379 	printf("  %*.s%d: %s size=%zd", depth*2, "", ++mimecount, mt, bodylen);
    380 	print_filename(filename);
    381 	printf("\n");
    382 
    383 	return MIME_CONTINUE;
    384 }
    385 
    386 void
    387 list(char *file)
    388 {
    389 	struct message *msg = blaze822_file(file);
    390 	if (!msg)
    391 		return;
    392 	mimecount = 0;
    393 	printf("%s\n", file);
    394 	blaze822_walk_mime(msg, 0, list_mime);
    395 }
    396 
    397 void
    398 reply(char *file)
    399 {
    400 	struct message *msg = blaze822_file(file);
    401 	if (!msg)
    402 		return;
    403 	blaze822_walk_mime(msg, 0, reply_mime);
    404 }
    405 
    406 static int extract_argc;
    407 static char **extract_argv;
    408 static int extract_stdout;
    409 
    410 
    411 static const char *
    412 basenam(const char *s)
    413 {
    414 	char *r = strrchr(s, '/');
    415 	return r ? r + 1 : s;
    416 }
    417 
    418 static int
    419 writefile(char *name, char *buf, ssize_t len)
    420 {
    421 	int fd = open(basenam(name), O_CREAT | O_EXCL | O_WRONLY, 0666);
    422 	if (fd == -1) {
    423 		perror("open");
    424 		return -1;
    425 	}
    426 
    427 	ssize_t wr = 0, n;
    428 	do {
    429 		if ((n = write(fd, buf + wr, len - wr)) == -1) {
    430 			if (errno == EINTR) {
    431 				continue;
    432 			} else {
    433 				perror("write");
    434 				return -1;
    435 			}
    436 		}
    437 		wr += n;
    438 	} while (wr < len);
    439 
    440 	close(fd);
    441 	return 0;
    442 }
    443 
    444 blaze822_mime_action
    445 extract_mime(int depth, struct message *msg, char *body, size_t bodylen)
    446 {
    447 	(void)depth;
    448 
    449 	char *filename = mime_filename(msg);
    450 
    451 	mimecount++;
    452 
    453 	if (extract_argc == 0) {
    454 		if (extract_stdout) { // output all parts
    455 			fwrite(body, 1, bodylen, stdout);
    456 		} else { // extract all named attachments
    457 			if (filename) {
    458 				safe_u8putstr(filename, strlen(filename), stdout);
    459 				printf("\n");
    460 				writefile(filename, body, bodylen);
    461 			}
    462 		}
    463 	} else {
    464 		int i;
    465 		for (i = 0; i < extract_argc; i++) {
    466 			char *a = extract_argv[i];
    467 			char *b;
    468 			errno = 0;
    469 			long d = strtol(a, &b, 10);
    470 			if (errno == 0 && !*b && d == mimecount) {
    471 				// extract by id
    472 				if (extract_stdout) {
    473 					if (rflag) {
    474 						fwrite(blaze822_orig_header(msg),
    475 						    1, blaze822_headerlen(msg),
    476 						    stdout);
    477 						if (blaze822_orig_header(msg)[
    478 						        blaze822_headerlen(msg)]
    479 						    == '\r')
    480 							printf("\r\n\r\n");
    481 						else
    482 							printf("\n\n");
    483 						fwrite(blaze822_body(msg),
    484 						    1, blaze822_bodylen(msg),
    485 						    stdout);
    486 					} else {
    487 						fwrite(body, 1, bodylen, stdout);
    488 					}
    489 				} else {
    490 					char buf[255];
    491 					char *bufptr;
    492 					if (filename) {
    493 						bufptr = filename;
    494 					} else {
    495 						snprintf(buf, sizeof buf,
    496 						    "attachment%d", mimecount);
    497 						bufptr = buf;
    498 					}
    499 					printf("%s\n", bufptr);
    500 					writefile(bufptr, body, bodylen);
    501 				}
    502 			} else if (filename &&
    503 			    fnmatch(a, filename, FNM_PATHNAME) == 0) {
    504 				// extract by name
    505 				if (extract_stdout) {
    506 					if (rflag) {
    507 						fwrite(blaze822_orig_header(msg),
    508 						    1, blaze822_headerlen(msg),
    509 						    stdout);
    510 						printf("\n\n");
    511 						fwrite(blaze822_body(msg),
    512 						    1, blaze822_bodylen(msg),
    513 						    stdout);
    514 					} else {
    515 						fwrite(body, 1, bodylen, stdout);
    516 					}
    517 				} else {
    518 					safe_u8putstr(filename, strlen(filename), stdout);
    519 					printf("\n");
    520 					writefile(filename, body, bodylen);
    521 				}
    522 			}
    523 		}
    524 	}
    525 
    526 	return MIME_CONTINUE;
    527 }
    528 
    529 void
    530 extract_cb(char *file)
    531 {
    532 	struct message *msg = blaze822_file(file);
    533 	if (!msg)
    534 		return;
    535 	mimecount = 0;
    536 	blaze822_walk_mime(msg, 0, extract_mime);
    537 }
    538 
    539 void
    540 extract(char *file, int argc, char **argv, int use_stdout)
    541 {
    542 	extract_argc = argc;
    543 	extract_argv = argv;
    544 	extract_stdout = use_stdout;
    545 	blaze822_loop1(file, extract_cb);
    546 }
    547 
    548 static char *newcur;
    549 
    550 static void
    551 print_date_header(char *v)
    552 {
    553 	static time_t now = -1;
    554 	if (now == -1) {
    555 		setenv("TZ", "", 1);
    556 		tzset();
    557 		now = time(0);
    558 	}
    559 
    560 	printf("Date: ");
    561 	print_ascii(v, strlen(v));
    562 
    563 	time_t t = blaze822_date(v);
    564 	if (t == -1) {
    565 		printf(" (invalid)");
    566 	} else {
    567 		printf(" (");
    568 		time_t d = t < now ? now - t : t - now;
    569 
    570 		char l;
    571 		if (d > 60*60*24*7*52) l = 'y';
    572 		else if (d > 60*60*24*7) l = 'w';
    573 		else if (d > 60*60*24) l = 'd';
    574 		else if (d > 60*60) l = 'h';
    575 		else if (d > 60) l = 'm';
    576 		else l = 's';
    577 		int p = 3;
    578 
    579 		long z;
    580 		switch (l) {
    581 		case 'y':
    582 			z = d / (60*60*24*7*52);
    583 			d = d % (60*60*24*7*52);
    584 			if (z > 0) {
    585 				printf("%ld year%s", z, z > 1 ? "s" : "");
    586 				if (!--p) break;
    587 				printf(", ");
    588 			}
    589 		/* FALL THROUGH */
    590 		case 'w':
    591 			z = d / (60*60*24*7);
    592 			d = d % (60*60*24*7);
    593 			if (z > 0) {
    594 				printf("%ld week%s", z, z > 1 ? "s" : "");
    595 				if (!--p) break;
    596 				printf(", ");
    597 			}
    598 		/* FALL THROUGH */
    599 		case 'd':
    600 			z = d / (60*60*24);
    601 			d = d % (60*60*24);
    602 			if (z > 0) {
    603 				printf("%ld day%s", z, z > 1 ? "s" : "");
    604 				if (!--p) break;
    605 				printf(", ");
    606 			}
    607 		/* FALL THROUGH */
    608 		case 'h':
    609 			z = d / (60*60);
    610 			d = d % (60*60);
    611 			if (z > 0) {
    612 				printf("%ld hour%s", z, z > 1 ? "s" : "");
    613 				if (!--p) break;
    614 				printf(", ");
    615 			}
    616 		/* FALL THROUGH */
    617 		case 'm':
    618 			z = d / (60);
    619 			d = d % (60);
    620 			if (z > 0) {
    621 				printf("%ld minute%s", z, z > 1 ? "s" : "");
    622 				if (!--p) break;
    623 				printf(", ");
    624 			}
    625 		/* FALL THROUGH */
    626 		case 's':
    627 			z = d;
    628 			printf("%ld second%s", z, z > 1 ? "s" : "");
    629 		}
    630 
    631 		if (t < now)
    632 			printf(" ago)");
    633 		else
    634 			printf(" in the future)");
    635 	}
    636 	printf("\n");
    637 }
    638 
    639 static void
    640 print_decode_header(char *h, char *v)
    641 {
    642 	char d[16384];
    643 	blaze822_decode_rfc2047(d, v, sizeof d, "UTF-8");
    644 	printhdr(h);
    645 	fputc(':', stdout);
    646 	fputc(' ', stdout);
    647 	print_ascii(d, strlen(d));
    648 	fputc('\n', stdout);
    649 }
    650 
    651 void
    652 show(char *file)
    653 {
    654 	struct message *msg;
    655 
    656 	while (*file == ' ' || *file == '\t')
    657 		file++;
    658 
    659 	if (newcur) {
    660 		printf("\014\n");
    661 		free(newcur);
    662 	}
    663 	newcur = strdup(file);
    664 
    665 	if (qflag && !Hflag)
    666 		msg = blaze822(file);
    667 	else
    668 		msg = blaze822_file(file);
    669 	if (!msg) {
    670 		fprintf(stderr, "mshow: %s: %s\n", file, strerror(errno));
    671 		return;
    672 	}
    673 
    674 	if (Hflag) {  // raw headers
    675 		fwrite(blaze822_orig_header(msg), 1, blaze822_headerlen(msg),
    676 		    stdout);
    677 		printf("\n");
    678 	} else if (Lflag) {  // all headers
    679 		char *h = 0;
    680 		while ((h = blaze822_next_header(msg, h))) {
    681 			char d[4096];
    682 			blaze822_decode_rfc2047(d, h, sizeof d, "UTF-8");
    683 			printhdr(d);
    684 		}
    685 	} else {  // selected headers
    686 		char *h = hflag;
    687 		char *v;
    688 		while (*h) {
    689 			char *n = strchr(h, ':');
    690 			if (n)
    691 				*n = 0;
    692 			v = blaze822_chdr(msg, h);
    693 			if (v) {
    694 				if (strcasecmp("date", h) == 0)
    695 					print_date_header(v);
    696 				else
    697 					print_decode_header(h, v);
    698 			}
    699 			if (n) {
    700 				*n = ':';
    701 				h = n + 1;
    702 			} else {
    703 				break;
    704 			}
    705 		}
    706 	}
    707 
    708 	if (qflag)  // no body
    709 		goto done;
    710 
    711 	printf("\n");
    712 
    713 	if (rflag) {  // raw body
    714 		print_ascii(blaze822_body(msg), blaze822_bodylen(msg));
    715 		goto done;
    716 	}
    717 
    718 	mimecount = 0;
    719 	blaze822_walk_mime(msg, 0, render_mime);
    720 
    721 done:
    722 	blaze822_free(msg);
    723 }
    724 
    725 int
    726 main(int argc, char *argv[])
    727 {
    728 	pid_t pid1 = -1, pid2 = -1;
    729 
    730 	int c;
    731 	while ((c = getopt(argc, argv, "h:A:qrtFHLNx:O:Rn")) != -1)
    732 		switch (c) {
    733 		case 'h': hflag = optarg; break;
    734 		case 'A': Aflag = optarg; break;
    735 		case 'q': qflag = 1; break;
    736 		case 'r': rflag = 1; break;
    737 		case 'F': Fflag = 1; break;
    738 		case 'H': Hflag = 1; break;
    739 		case 'L': Lflag = 1; break;
    740 		case 'N': Nflag = 1; break;
    741 		case 't': tflag = 1; break;
    742 		case 'x': xflag = optarg; break;
    743 		case 'O': Oflag = optarg; break;
    744 		case 'R': Rflag = 1; break;
    745 		case 'n': nflag = 1; break;
    746 		default:
    747 			fprintf(stderr,
    748 			    "Usage: mshow [-h headers] [-A mimetypes] [-nqrFHLN] [msgs...]\n"
    749 			    "	    mshow -x msg parts...\n"
    750 			    "	    mshow -O msg parts...\n"
    751 			    "	    mshow -t msgs...\n"
    752 			    "	    mshow -R msg\n"
    753 			);
    754 			exit(1);
    755 		}
    756 
    757 	if (!rflag && !Oflag && !Rflag)
    758 		safe_output = 1;
    759 
    760 	if (safe_output && isatty(1)) {
    761 		char *pg;
    762 		pg = getenv("MBLAZE_PAGER");
    763 		if (!pg) {
    764 			pg = getenv("PAGER");
    765 			if (pg && strcmp(pg, "less") == 0) {
    766 				static char lesscmd[] = "less -RFXe";
    767 				pg = lesscmd;
    768 			}
    769 		}
    770 		if (pg && *pg && strcmp(pg, "cat") != 0) {
    771 			pid2 = pipeto(pg);
    772 			if (pid2 < 0)
    773 				fprintf(stderr,
    774 				    "mshow: spawning pager '%s': %s\n",
    775 				    pg, strerror(errno));
    776 			else if (!getenv("MBLAZE_NOCOLOR"))
    777 				pid1 = pipeto("mcolor");  // ignore error
    778 		}
    779 	}
    780 
    781 	if (xflag) { // extract
    782 		extract(xflag, argc-optind, argv+optind, 0);
    783 	} else if (Oflag) { // extract to stdout
    784 		extract(Oflag, argc-optind, argv+optind, 1);
    785 	} else if (tflag) { // list
    786 		if (argc == optind && isatty(0))
    787 			blaze822_loop1(".", list);
    788 		else
    789 			blaze822_loop(argc-optind, argv+optind, list);
    790 	} else if (Rflag) { // render for reply
    791 		blaze822_loop(argc-optind, argv+optind, reply);
    792 	} else { // show
    793 		if (!(qflag || rflag || Fflag)) {
    794 			char *f = getenv("MAILFILTER");
    795 			if (!f)
    796 				f = blaze822_home_file("filter");
    797 			if (f)
    798 				filters = blaze822(f);
    799 		}
    800 		if (argc == optind && isatty(0))
    801 			blaze822_loop1(".", show);
    802 		else
    803 			blaze822_loop(argc-optind, argv+optind, show);
    804 		if (!nflag) // don't set cur
    805 			blaze822_seq_setcur(newcur);
    806 	}
    807 
    808 	if (pid2 > 0)
    809 		pipeclose(pid2);
    810 	if (pid1 > 0)
    811 		pipeclose(pid1);
    812 
    813 	return 0;
    814 }