mblaze

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

mmime.c (10363B)


      1 #include <sys/stat.h>
      2 #include <sys/types.h>
      3 
      4 #include <dirent.h>
      5 #include <errno.h>
      6 #include <fcntl.h>
      7 #include <limits.h>
      8 #include <pwd.h>
      9 #include <search.h>
     10 #include <stdint.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <time.h>
     15 #include <unistd.h>
     16 
     17 #include "blaze822.h"
     18 
     19 static int cflag;
     20 static int rflag;
     21 static char *tflag = "multipart/mixed";
     22 
     23 int gen_b64(uint8_t *s, off_t size)
     24 {
     25 	static char *b64 =
     26 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
     27 
     28 	off_t i;
     29 	int l;
     30 	uint32_t v;
     31 	for (i = 0, l = 0; i+2 < size; i += 3) {
     32 		v = (s[i] << 16) | (s[i+1] << 8) | s[i+2];
     33 		putc_unlocked(b64[(v & 0xfc0000) >> 18], stdout);
     34 		putc_unlocked(b64[(v & 0x03f000) >> 12], stdout);
     35 		putc_unlocked(b64[(v & 0x000fc0) >>  6], stdout);
     36 		putc_unlocked(b64[(v & 0x3f)], stdout);
     37 		l += 4;
     38 		if (l > 72) {
     39 			l = 0;
     40 			printf("\n");
     41 		}
     42 	}
     43 	if (size - i == 2) { // 2 bytes left, XXX=
     44 		v = (s[size - 2] << 16) | (s[size - 1] << 8);
     45 		putc_unlocked(b64[(v & 0xfc0000) >> 18], stdout);
     46 		putc_unlocked(b64[(v & 0x03f000) >> 12], stdout);
     47 		putc_unlocked(b64[(v & 0x000fc0) >>  6], stdout);
     48 		putc_unlocked('=', stdout);
     49 	} else if (size - i == 1) { // 1 byte left, XX==
     50 		v = s[size - 1] << 16;
     51 		putc_unlocked(b64[(v & 0xfc0000) >> 18], stdout);
     52 		putc_unlocked(b64[(v & 0x03f000) >> 12], stdout);
     53 		putc_unlocked('=', stdout);
     54 		putc_unlocked('=', stdout);
     55 	}
     56 	printf("\n");
     57 	return 0;
     58 }
     59 
     60 size_t
     61 gen_qp(uint8_t *s, off_t size, size_t maxlinelen, size_t linelen)
     62 {
     63 	off_t i;
     64 	int header = linelen > 0;
     65 	char prev = 0;
     66 
     67 	for (i = 0; i < size; i++) {
     68 		// inspect utf8 sequence to not wrap in between multibyte
     69 		int mb;
     70 		if      ((s[i] & 0x80) == 0)    mb = 3;
     71 		else if ((s[i] & 0xc0) == 0x80) mb = 3;
     72 		else if ((s[i] & 0xe0) == 0xc0) mb = 6;
     73 		else if ((s[i] & 0xf0) == 0xe0) mb = 9;
     74 		else if ((s[i] & 0xf8) == 0xf0) mb = 12;
     75 		else mb = 3;
     76 
     77 		if (linelen >= maxlinelen-mb-!!header) {
     78 			linelen = 0;
     79 			prev = '\n';
     80 			if (header) {
     81 				printf("?=\n =?UTF-8?Q?");
     82 				linelen += 11;
     83 			} else {
     84 				puts("=");
     85 			}
     86 		}
     87 
     88 		if ((s[i] > 126) ||
     89 		    (s[i] == '=') ||
     90 		    (linelen == 0 &&
     91 		     (strncmp((char *)s, "From ", 5) == 0 ||
     92 	             (s[i] == '.' && i+1 < size &&
     93 		      (s[i+1] == '\n' || s[i+1] == '\r'))))) {
     94 			printf("=%02X", s[i]);
     95 			linelen += 3;
     96 			prev = s[i];
     97 		} else if (header &&
     98 			   (s[i] == '\n' || s[i] == '\t' || s[i] == '_')) {
     99 			printf("=%02X", s[i]);
    100 			linelen += 3;
    101 			prev = '_';
    102 		} else if (header && s[i] == ' ') {
    103 			putc_unlocked('_', stdout);
    104 			linelen++;
    105 			prev = '_';
    106 		} else if (s[i] < 33 && s[i] != '\n') {
    107 			if ((s[i] == ' ' || s[i] == '\t') &&
    108 			    i+1 < size &&
    109 			    (s[i+1] != '\n' && s[i+1] != '\r')) {
    110 				putc_unlocked(s[i], stdout);
    111 				linelen += 1;
    112 				prev = s[i];
    113 			} else {
    114 				printf("=%02X", s[i]);
    115 				linelen += 3;
    116 				prev = '_';
    117 			}
    118 		} else if (s[i] == '\n') {
    119 			if (prev == ' ' || prev == '\t')
    120 				puts("=");
    121 			putc_unlocked('\n', stdout);
    122 			linelen = 0;
    123 			prev = 0;
    124 		} else {
    125 			putc_unlocked(s[i], stdout);
    126 			linelen++;
    127 			prev = s[i];
    128 		}
    129 	}
    130 	if (linelen > 0 && !header)
    131 		puts("=");
    132 	return linelen;
    133 }
    134 
    135 static const char *
    136 basenam(const char *s)
    137 {
    138 	char *r = strrchr(s, '/');
    139 	return r ? r + 1 : s;
    140 }
    141 
    142 static void
    143 gen_attachment(const char *filename, char *content_disposition)
    144 {
    145 	const char *s = filename;
    146 	int quote = 0;
    147 
    148 	if (!*filename) {
    149 		printf("Content-Disposition: %s\n", content_disposition);
    150 		return;
    151 	}
    152 
    153 	for (s = (char *)filename; *s; s++) {
    154 		if (*s < 32 || *s == '"' || *s >= 127 || s - filename > 35)
    155 			goto rfc2231;
    156 		if (strchr(" ()<>@,;:\\/[]?=", *s))
    157 			quote = 1;
    158 	}
    159 
    160 	// filename SHOULD be an atom if possible
    161 	printf("Content-Disposition: %s; filename=%s%s%s\n", content_disposition,
    162 	    quote ? "\"" : "", filename, quote ? "\"" : "");
    163 	return;
    164 
    165 rfc2231:
    166 	printf("Content-Disposition: %s", content_disposition);
    167 	int i = 0;
    168 	int d = 0;
    169 
    170 	s = filename;
    171 
    172 	while (*s) {
    173 		i = printf(";\n filename*%d*=", d);
    174 		if (d++ == 0) {
    175 			printf("UTF-8''");
    176 			i += 7;
    177 		}
    178 		while (*s && i < 78 - 3) {
    179 			if (*s <= 32 || *s == '"' || *s > 126)
    180 				i += printf("%%%02x", (uint8_t)*s++);
    181 			else
    182 				i += printf("%c", (uint8_t)*s++);
    183 		}
    184 	}
    185 
    186 	printf("\n");
    187 }
    188 
    189 int
    190 gen_file(char *file, char *ct)
    191 {
    192 	uint8_t *content;
    193 	off_t size;
    194 
    195 	char *cd = "attachment";
    196 	char *s = strchr(ct, '#');
    197 	if (s) {
    198 		*s = 0;
    199 		cd = s + 1;
    200 	}
    201 
    202 	const char *filename = basenam(file);
    203 	s = strchr(file, '>');
    204 	if (s) {
    205 		*s = 0;
    206 		filename = s + 1;
    207 	}
    208 
    209 	int r = slurp(file, (char **)&content, &size);
    210 	if (r != 0) {
    211 		fprintf(stderr, "mmime: error attaching file '%s': %s\n",
    212 		    file, strerror(r));
    213 		return -1;
    214 	}
    215 
    216 	if (strcmp(ct, "mblaze/raw") == 0)
    217 		goto raw;
    218 
    219 	off_t bithigh = 0;
    220 	off_t bitlow = 0;
    221 	off_t linelen = 0;
    222 	off_t maxlinelen = 0;
    223 	off_t i;
    224 	for (i = 0; i < size; i++) {
    225 		if (content[i] == '\n') {
    226 			if (maxlinelen < linelen)
    227 				maxlinelen = linelen;
    228 			linelen = 0;
    229 		} else {
    230 			linelen++;
    231 		}
    232 		if (content[i] != '\t' && content[i] != '\n' && content[i] < 32)
    233 			bitlow++;
    234 		if (content[i] > 127)
    235 			bithigh++;
    236 	}
    237 
    238 	gen_attachment(filename, cd);
    239 
    240 	if (strcmp(ct, "message/rfc822") == 0) {
    241 		printf("Content-Type: %s\n", ct);
    242 		printf("Content-Transfer-Encoding: %dbit\n\n",
    243 		    (bitlow > 0 || bithigh > 0) ? 8 : 7);
    244 		fwrite(content, 1, size, stdout);
    245 		return 0;
    246 	}
    247 
    248 	if (bitlow == 0 && bithigh == 0 &&
    249 	    maxlinelen <= 78 && content[size-1] == '\n') {
    250 		if (!ct)
    251 			ct = "text/plain";
    252 		printf("Content-Type: %s\n", ct);
    253 		printf("Content-Transfer-Encoding: 7bit\n\n");
    254 raw:
    255 		fwrite(content, 1, size, stdout);
    256 		return 0;
    257 	} else if (bitlow == 0 && bithigh == 0) {
    258 		if (!ct)
    259 			ct = "text/plain";
    260 		printf("Content-Type: %s\n", ct);
    261 		printf("Content-Transfer-Encoding: quoted-printable\n\n");
    262 		gen_qp(content, size, 78, 0);
    263 		return 0;
    264 	} else if (bitlow > size/10 || bithigh > size/4) {
    265 		if (!ct)
    266 			ct = "application/binary";
    267 		printf("Content-Type: %s\n", ct);
    268 		printf("Content-Transfer-Encoding: base64\n\n");
    269 		return gen_b64(content, size);
    270 	} else {
    271 		if (!ct)
    272 			ct = "text/plain";
    273 		printf("Content-Type: %s\n", ct);
    274 		printf("Content-Transfer-Encoding: quoted-printable\n\n");
    275 		gen_qp(content, size, 78, 0);
    276 		return 0;
    277 	}
    278 }
    279 
    280 void
    281 print_header(char *line) {
    282 	char *s, *e;
    283 	size_t l = strlen(line);
    284 
    285 	if (line[l-1] == '\n')
    286 		line[l-1] = 0;
    287 
    288 	/* iterate word-wise, encode words when needed. */
    289 
    290 	s = line;
    291 
    292 	if (!(*s == ' ' || *s == '\t')) {
    293 		// raw header name
    294 		while (*s && *s != ':')
    295 			putc_unlocked(*s++, stdout);
    296 		if (*s == ':')
    297 			putc_unlocked(*s++, stdout);
    298 	}
    299 
    300 	int prevq = 0;  // was the previous word encoded as qp?
    301 
    302 	size_t linelen = s - line;
    303 
    304 	while (*s) {
    305 		size_t highbit = 0;
    306 		e = s;
    307 		while (*e && *e == ' ')
    308 			e++;
    309 		for (; *e && *e != ' '; e++) {
    310 			if ((uint8_t)*e >= 127)
    311 				highbit++;
    312 		}
    313 
    314 		if (!highbit) {
    315 			if (e-s >= 998)
    316 				goto force_qp;
    317 			if (e-s >= 78 - linelen) {
    318 				// wrap in advance before long word
    319 				printf("\n");
    320 				linelen = 0;
    321 			}
    322 			if (linelen <= 1 && s[0] == ' ' && s[1] == ' ') {
    323 				// space at beginning of line
    324 				goto force_qp;
    325 			}
    326 			if (*s != ' ') {
    327 				printf(" ");
    328 				linelen++;
    329 			}
    330 			fwrite(s, 1, e-s, stdout);
    331 			linelen += e-s;
    332 			prevq = 0;
    333 		} else {
    334 force_qp:
    335 			if (!prevq && *s == ' ')
    336 				s++;
    337 			if (linelen >= 78 - 13 - 4 ||
    338 			    (e-s < (78 - 13)/3 &&
    339 			     e-s >= (78 - linelen - 13)/3)) {
    340 				// wrap in advance
    341 				printf("\n");
    342 				linelen = 0;
    343 			}
    344 			printf(" =?UTF-8?Q?");
    345 			linelen += 11;
    346 			linelen = gen_qp((uint8_t *)s, e-s, 78, linelen);
    347 			printf("?=");
    348 			linelen += 2;
    349 			prevq = 1;
    350 		}
    351 		s = e;
    352 	}
    353 	printf("\n");
    354 }
    355 
    356 int
    357 gen_build()
    358 {
    359 	char sep[100];
    360 	snprintf(sep, sizeof sep, "----_=_%08lx%08lx%08lx_=_",
    361 	    lrand48(), lrand48(), lrand48());
    362 
    363 	char *line = 0;
    364 	size_t linelen = 0;
    365 	int inheader = 1;
    366 	int intext = 0;
    367 
    368 	while (1) {
    369 		ssize_t read = getdelim(&line, &linelen, '\n', stdin);
    370 		if (read == -1) {
    371 			if (feof(stdin))
    372 				break;
    373 			else
    374 				exit(1);
    375 		}
    376 		if (inheader) {
    377 			if (line[0] == '\n') {
    378 				inheader = 0;
    379 				printf("MIME-Version: 1.0\n");
    380 				if (rflag) {
    381 					printf("Content-Type: text/plain; charset=UTF-8\n");
    382 					printf("Content-Transfer-Encoding: quoted-printable\n\n");
    383 
    384 				} else {
    385 					printf("Content-Type: %s; boundary=\"%s\"\n", tflag, sep);
    386 					printf("\n");
    387 					printf("This is a multipart message in MIME format.\n");
    388 				}
    389 			} else {
    390 				print_header(line);
    391 			}
    392 			continue;
    393 		}
    394 
    395 		if (!rflag && line[0] == '#') {
    396 			char *f = strchr(line, ' ');
    397 			if (f) {
    398 				char of = *f;
    399 				*f = 0;
    400 				if (strchr(line, '/')) {
    401 					printf("\n--%s\n", sep);
    402 					if (line[read-1] == '\n')
    403 						line[read-1] = 0;
    404 					gen_file(f+1, (char *)line+1);
    405 					intext = 0;
    406 					continue;
    407 				}
    408 				*f = of;
    409 			}
    410 		}
    411 
    412 		if (!rflag && !intext) {
    413 			printf("\n--%s\n", sep);
    414 			printf("Content-Type: text/plain; charset=UTF-8\n");
    415 			printf("Content-Disposition: inline\n");
    416 			printf("Content-Transfer-Encoding: quoted-printable\n\n");
    417 
    418 			intext = 1;
    419 		}
    420 
    421 		gen_qp((uint8_t *)line, strlen(line), 78, 0);
    422 	}
    423 	if (!rflag && !inheader)
    424 		printf("\n--%s--\n", sep);
    425 
    426 	free(line);
    427 	return 0;
    428 }
    429 
    430 int
    431 check()
    432 {
    433 	off_t bithigh = 0;
    434 	off_t bitlow = 0;
    435 	off_t linelen = 0;
    436 	off_t maxheadlinelen = 0;
    437 	off_t maxbodylinelen = 0;
    438 
    439 	int c;
    440 	int l = -1;
    441 
    442 	while ((c = getchar()) != EOF) {
    443 		if (c == '\n') {
    444 			if (maxheadlinelen < linelen)
    445 				maxheadlinelen = linelen;
    446 			linelen = 0;
    447 			if (l == '\n')
    448 				break;
    449 		} else {
    450 			linelen++;
    451 		}
    452 		if (c != '\t' && c != '\n' && c < 32)
    453 			bitlow++;
    454 		if (c > 127)
    455 			bithigh++;
    456 		l = c;
    457 	}
    458 
    459 	while ((c = getchar()) != EOF) {
    460 		if (c == '\n') {
    461 			if (maxbodylinelen < linelen)
    462 				maxbodylinelen = linelen;
    463 			linelen = 0;
    464 		} else {
    465 			linelen++;
    466 		}
    467 		if (c != '\t' && c != '\n' && c < 32)
    468 			bitlow++;
    469 		if (c > 127)
    470 			bithigh++;
    471 		l = c;
    472 	}
    473 
    474 	if (bitlow == 0 && bithigh == 0 &&
    475 	    maxheadlinelen < 998 && maxbodylinelen <= 78 &&
    476 	    l == '\n')
    477 		return 0;
    478 	else
    479 		return 1;
    480 }
    481 
    482 int
    483 main(int argc, char *argv[])
    484 {
    485 	srand48(time(0) ^ getpid());
    486 
    487 	int c;
    488 	while ((c = getopt(argc, argv, "crt:")) != -1)
    489 		switch (c) {
    490 		case 'r': rflag = 1; break;
    491 		case 'c': cflag = 1; break;
    492 		case 't': tflag = optarg; break;
    493 		default:
    494 usage:
    495 			fprintf(stderr,
    496 "Usage: mmime [-c|-r] [-t CONTENT-TYPE] < message\n");
    497 			exit(1);
    498 		}
    499 
    500 	if (argc != optind)
    501 		goto usage;
    502 
    503 	if (cflag)
    504 		return check();
    505 
    506 	return gen_build();
    507 }