rohrpost

A commandline mail client to change the world as we see it.
git clone git://r-36.net/rohrpost
Log | Files | Refs | LICENSE

mime.c (25707B)


      1 /*
      2  * Copy me if you can.
      3  * by 20h
      4  */
      5 
      6 #include <unistd.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 #include <strings.h>
     11 #include <ctype.h>
     12 #include <iconv.h>
     13 #include <errno.h>
     14 #include <time.h>
     15 
     16 #include "ind.h"
     17 #include "llist.h"
     18 #include "mime.h"
     19 #include "parser.h"
     20 #include "base64.h"
     21 #include "quote.h"
     22 #include "param.h"
     23 #include "dos.h"
     24 
     25 enum {
     26 	HEADER = 0x01,
     27 	HEADERVALUE,
     28 };
     29 
     30 mime_t *
     31 mime_new(void)
     32 {
     33 	mime_t *part;
     34 
     35 	part = mallocz(sizeof(mime_t), 2);
     36 	part->hdrs = llist_new();
     37 	part->parts = llist_new();
     38 	part->state = HEADER;
     39 
     40 	return part;
     41 }
     42 
     43 void
     44 mime_subfree(mime_t *mime, llistelem_t *elem)
     45 {
     46 	forllist(mime->parts, elem) {
     47 		if (elem->key == NULL)
     48 			mime_free((mime_t *)elem->data);
     49 		elem->data = NULL;
     50 	}
     51 	llist_free(mime->parts);
     52 }
     53 
     54 void
     55 mime_free(mime_t *mime)
     56 {
     57 	mime_subfree(mime, NULL);
     58 	llist_free(mime->hdrs);
     59 
     60 	if (mime->body != NULL)
     61 		free(mime->body);
     62 	if (mime->partid != NULL)
     63 		free(mime->partid);
     64 	if (mime->ct != NULL)
     65 		free(mime->ct);
     66 	if (mime->cte != NULL)
     67 		free(mime->cte);
     68 	if (mime->charset != NULL)
     69 		free(mime->charset);
     70 	if (mime->boundary != NULL)
     71 		free(mime->boundary);
     72 	if (mime->rawhdrs != NULL)
     73 		free(mime->rawhdrs);
     74 	free(mime);
     75 }
     76 
     77 struct tm *
     78 mime_parsedate(char *str)
     79 {
     80 	struct tm tim;
     81 
     82 	memset(&tim, 0, sizeof(tim));
     83 	if (strptime(str, "%a, %d %b %Y %T %z", &tim) != NULL)
     84 		return memdup(&tim, sizeof(tim));
     85 
     86 	if (!strncmp(str, "Date: ", 6))
     87 		str += 6;
     88 
     89 	/*
     90 	 * Malformatted dates seen in the wild.
     91 	 */
     92 	if (strptime(str, "%a, %d %b %Y %T %Z", &tim) != NULL)
     93 		return memdup(&tim, sizeof(tim));
     94 
     95 	if (strptime(str, "%d %b %Y %T %z", &tim) != NULL)
     96 		return memdup(&tim, sizeof(tim));
     97 
     98 	if (strptime(str, "%a, %d %b %Y, %T %z", &tim) != NULL)
     99 		return memdup(&tim, sizeof(tim));
    100 
    101 	if (strptime(str, "%d.%m.%Y", &tim) != NULL)
    102 		return memdup(&tim, sizeof(tim));
    103 
    104 	return memdup(&tim, sizeof(tim));
    105 }
    106 
    107 char *
    108 mime_iconv(char *str, char *from, char *to)
    109 {
    110 	iconv_t *ifd;
    111 	size_t left, avail, nconv;
    112 	char *outb, *strp, *outbp;
    113 	int olen, sd;
    114 
    115 	ifd = iconv_open(to, from);
    116 	if (ifd == (iconv_t)-1)
    117 		return NULL;
    118 
    119 	//printf("mime_iconv: '%s'; from='%s'\n", str, from);
    120 
    121 	left = strlen(str);
    122 	olen = left / 2;
    123 	avail = olen;
    124 	outb = mallocz(olen+1, 1);
    125 	outbp = outb;
    126 	strp = str;
    127 	for (;;) {
    128 		nconv = iconv(ifd, &strp, &left, &outbp, &avail);
    129 		if (nconv == (size_t)-1) {
    130 			if (errno == E2BIG) {
    131 				olen += 5;
    132 				sd = outbp - outb;
    133 				outb = reallocz(outb, olen+1, 0);
    134 				outbp = &outb[sd];
    135 				avail += 5;
    136 				continue;
    137 			}
    138 			if (errno == EILSEQ || errno == EINVAL)
    139 				return NULL;
    140 			free(outb);
    141 			iconv_close(ifd);
    142 			return NULL;
    143 		}
    144 		break;
    145 	}
    146 
    147 	iconv_close(ifd);
    148 	if (outbp != NULL)
    149 		outbp[0] = '\0';
    150 	return outb;
    151 }
    152 
    153 char *
    154 mime_decodeheaderext(char *value)
    155 {
    156 	char *work, *cret, *ret, *cs, *str, *enc, *ast, *dstr;
    157 	int len, slen;
    158 
    159 	len = strlen(value);
    160 
    161 	ret = memdupz(value, len);
    162 	work = memdupz(value, len);
    163 
    164 	if (!(work[0] == '=' && work[1] == '?' && work[len-1] == '='
    165 				&& work[len-2] == '?')) {
    166 		free(work);
    167 		return ret;
    168 	}
    169 	cs = &work[2];
    170 
    171 	work[len-2] = '\0';
    172 	enc = strchr(&work[2], '?');
    173 	if (enc == NULL) {
    174 		free(work);
    175 		return ret;
    176 	}
    177 	enc[0] = '\0';
    178 	enc++;
    179 	str = strchr(enc, '?');
    180 	if (str == NULL) {
    181 		free(work);
    182 		return ret;
    183 	}
    184 	str[0] = '\0';
    185 	str++;
    186 
    187 	/*
    188 	 * RFC 2231 :(
    189 	 * See: https://en.wikipedia.org/wiki/Mr._Mime
    190 	 */
    191 	ast = strchr(enc, '*');
    192 	if (ast != NULL)
    193 		ast[0] = '\0';
    194 
    195 	slen = strlen(str);
    196 	if (slen == 0) {
    197 		free(work);
    198 		free(ret);
    199 		return memdupz("", 1);
    200 	}
    201 
    202 	cret = NULL;
    203 	switch(enc[0]) {
    204 	case 'B':
    205 	case 'b':
    206 		cret = b64dec(str, &slen);
    207 		break;
    208 	case 'Q':
    209 	case 'q':
    210 		cret = qpdec(str, &slen, 1);
    211 		break;
    212 	}
    213 
    214 	//printf("mime_decodeheader: mime_iconv str='%s'; cret='%s';\n", str, cret);
    215 	if (cret != NULL) {
    216 		free(ret);
    217 		if (strcasecmp(cs, "utf-8")) {
    218 			dstr = mime_iconv(cret, cs, "UTF-8");
    219 			if (dstr == NULL) {
    220 				str = smprintf("ERR(%s)", str);
    221 			} else {
    222 				str = dstr;
    223 			}
    224 			free(cret);
    225 		} else {
    226 			str = cret;
    227 		}
    228 	} else {
    229 		str = ret;
    230 	}
    231 	free(work);
    232 
    233 	return str;
    234 }
    235 
    236 int
    237 mime_isextws(char *str, int len)
    238 {
    239 	int i;
    240 
    241 	for (i = 0; i < len; i++) {
    242 		switch (str[i]) {
    243 		case '\n':
    244 		case '\r':
    245 		case ' ':
    246 		case '\t':
    247 			break;
    248 		default:
    249 			return 0;
    250 		}
    251 	}
    252 	return 1;
    253 }
    254 
    255 char *
    256 mime_decodeheader(char *value)
    257 {
    258 	char *work, *extp, *extw, *extb, *exte, *extr, *ret, *q1, *q2;
    259 	int vlen, rlen, elen, wasenc, i;
    260 
    261 	//printf("mime_decodeheader\n");
    262 	ret = NULL;
    263 	rlen = 0;
    264 	vlen = strlen(value);
    265 	work = memdup(value, vlen+1);
    266 	extp = work;
    267 	wasenc = 0;
    268 
    269 	/*
    270 	 * Avoid being tricked by malformed headers.
    271 	 */
    272 	for (i = 0; i < 32; i++) {
    273 		extb = strstr(extp, "=?");
    274 		if (extb != NULL) {
    275 			elen = extb - extp;
    276 			if (extp != extb && (!wasenc ||
    277 					!mime_isextws(extp, elen))) {
    278 				extw = memdupz(extp, elen);
    279 				ret = memdupcat(ret, rlen, extw, elen+1);
    280 				free(extw);
    281 				rlen += elen;
    282 			}
    283 
    284 			exte = NULL;
    285 			q1 = strchr(&extb[2], '?');
    286 			if (q1 != NULL) {
    287 				q2 = strchr(&q1[1], '?');
    288 				if (q2 != NULL)
    289 					exte = strstr(&q2[1], "?=");
    290 			}
    291 			if (exte != NULL) {
    292 				elen = &exte[2] - extb;
    293 				extw = memdupz(extb, elen);
    294 				extr = mime_decodeheaderext(extw);
    295 				free(extw);
    296 				elen = strlen(extr);
    297 				ret = memdupcat(ret, rlen, extr, elen+1);
    298 				rlen += elen;
    299 				free(extr);
    300 				extp = &exte[2];
    301 				wasenc = 1;
    302 				continue;
    303 			}
    304 		}
    305 		break;
    306 	}
    307 	if ((extp - work) < vlen)
    308 		ret = memdupcat(ret, rlen, extp, strlen(extp)+1);
    309 	free(work);
    310 
    311 	/* Remove any space character, like newline. */
    312 	if (ret != NULL)
    313 		strnormalizespace(ret);
    314 
    315 	return ret;
    316 }
    317 
    318 char *cstries[] = {
    319 	"utf-8",
    320 	"iso-8859-1",
    321 	"windows-1252",
    322 	"koi8",
    323 	"euc-jp"
    324 	"shift_jis",
    325 	"big5",
    326 	"iso-8859-15",
    327 	NULL
    328 };
    329 
    330 char *
    331 mime_guesscharset(char *str)
    332 {
    333 	int i, eq;
    334 	char *itry;
    335 
    336 	for (i = 0; i < nelem(cstries); i++) {
    337 		itry = mime_iconv(str, cstries[i], cstries[i]);
    338 		if (itry == NULL)
    339 			continue;
    340 		eq = strcmp(str, itry);
    341 		free(itry);
    342 		if (!eq)
    343 			break;
    344 	}
    345 
    346 	return cstries[i];
    347 }
    348 
    349 char *
    350 mime_guessheader(char *value)
    351 {
    352 	char *nvalue, *gcs;
    353 
    354 	gcs = NULL;
    355 
    356 	//printf("mime_guessheader '%s'\n", value);
    357 
    358 	nvalue = value;
    359 	if (!strisascii(value)) {
    360 		/*
    361 		 * Guessing begins. Some major MUA developers did not read any
    362 		 * RFCs.
    363 		 */
    364 
    365 		gcs = mime_guesscharset(value);
    366 		if (gcs != NULL) {
    367 			nvalue = mime_iconv(value, gcs, "utf-8");
    368 			if (nvalue == NULL) {
    369 				nvalue = value;
    370 				gcs = NULL;
    371 			}
    372 		}
    373 	}
    374 
    375 	value = mime_decodeheader(nvalue);
    376 	if (gcs != NULL)
    377 		free(nvalue);
    378 	return value;
    379 }
    380 
    381 char *
    382 mime_decodeparam(char *value)
    383 {
    384 	char *work, *cret, *ret, *cs, *str, *lang, *dstr;
    385 	int len, slen;
    386 
    387 	len = strlen(value);
    388 	ret = memdup(value, len+1);
    389 	work = memdup(value, len+1);
    390 
    391 	cs = work;
    392 	lang = strchr(work, '\'');
    393 	if (lang == NULL) {
    394 		free(work);
    395 		return ret;
    396 	}
    397 	lang[0] = '\0';
    398 	lang++;
    399 	str = strchr(lang, '\'');
    400 	if (str == NULL) {
    401 		free(work);
    402 		return ret;
    403 	}
    404 	str[0] = '\0';
    405 	str++;
    406 
    407 	slen = strlen(str);
    408 	cret = paramdec(str, &slen);
    409 
    410 	if (cret != NULL) {
    411 		if (strcasecmp(cs, "utf-8")) {
    412 			free(ret);
    413 			dstr = mime_iconv(cret, cs, "UTF-8");
    414 			if (dstr == NULL) {
    415 				str = smprintf("ERR(%s)", str);
    416 			} else {
    417 				str = dstr;
    418 			}
    419 			free(cret);
    420 		} else {
    421 			free(ret);
    422 			str = cret;
    423 		}
    424 	} else {
    425 		str = ret;
    426 	}
    427 	free(work);
    428 
    429 	return str;
    430 }
    431 
    432 char *
    433 mime_encodestring(char *value)
    434 {
    435 	char *b64, *ret;
    436 
    437 	if (strisascii(value))
    438 		return memdups(value);
    439 
    440 	b64 = b64enc(value, strlen(value));
    441 	ret = smprintf("=?UTF-8?b?%s?=", b64);
    442 	free(b64);
    443 
    444 	return ret;
    445 }
    446 
    447 char *
    448 mime_encodeheader(char *header, char *value)
    449 {
    450 	char *ret, *b64, *p, *mp, *str;
    451 	int hlen, lmax, isascii, firstline, slen;
    452 
    453 	isascii = 0;
    454 
    455 	/*
    456 	 * RFC 2047:
    457 	 *  One encoded word should be at max. 75 characters.
    458 	 *  One encoded line is limited to 76 characters.
    459 	 */
    460 	hlen = strlen(header) + 2;
    461 	if (strisascii(value)) {
    462 		isascii = 1;
    463 		lmax = 75 - hlen;
    464 	} else {
    465 		lmax = 63 - hlen;
    466 	}
    467 	slen = strlen(value);
    468 
    469 	ret = NULL;
    470 	for (p = value, firstline = 1; slen > 0; slen -= lmax, p = mp) {
    471 		if (firstline) {
    472 			lmax += hlen;
    473 			firstline = 0;
    474 		}
    475 
    476 		mp = findlimitws(p, lmax);
    477 		if (mp == NULL) {
    478 			str = memdupz(p, slen);
    479 		} else {
    480 			str = memdupz(p, mp - p);
    481 		}
    482 
    483 		if (!isascii) {
    484 			b64 = b64enc(str, strlen(str));
    485 			free(str);
    486 			mp = smprintf("=?UTF-8?b?%s?=", b64);
    487 			free(b64);
    488 			str = mp;
    489 		}
    490 
    491 		if (ret != NULL) {
    492 			mp = smprintf("%s %s", ret, str);
    493 			free(ret);
    494 			ret = mp;
    495 		} else {
    496 			ret = smprintf("%s", str);
    497 		}
    498 	}
    499 
    500 	return ret;
    501 }
    502 
    503 int
    504 mime_paramsort(llistelem_t *elem1, llistelem_t *elem2)
    505 {
    506 	int a, b;
    507 	char *n1, *n2;
    508 
    509 	n1 = strrchr(elem1->key, '*');
    510 	if (n1 == NULL)
    511 		a = -1;
    512 	else
    513 		a = atoi(&n1[1]);
    514 	n2 = strrchr(elem2->key, '*');
    515 	if (n2 == NULL)
    516 		b = -1;
    517 	else
    518 		b = atoi(&n2[1]);
    519 
    520 	return a - b;
    521 }
    522 
    523 /*
    524  * Order and concatenate ordered params.
    525  */
    526 llist_t *
    527 mime_sanitizeparams(llist_t *params)
    528 {
    529 	llistelem_t *param, *hit, *nparam;
    530 	llist_t *reorder, *hits;
    531 	char *key, *nvalue;
    532 	int klen, i, n;
    533 
    534 	reorder = llist_new();
    535 	//printf("mime_sanitizeparams: start\n");
    536 	n = 0;
    537 	forllist(params, param) {
    538 		if (n == 0) {
    539 			//printf("first key: %s\n", param->key);
    540 			n++;
    541 			continue;
    542 		}
    543 
    544 		key = param->key;
    545 		klen = strlen(key);
    546 
    547 		//printf("key = %s\n", key);
    548 		if (key[klen-2] == '*') {
    549 			nvalue = mime_decodeparam((char *)param->data);
    550 			if (nvalue != NULL) {
    551 				//printf("decoded: %s\n", nvalue);
    552 				free(param->data);
    553 				param->data = nvalue;
    554 				param->datalen = strlen(nvalue)+1;
    555 			}
    556 			key[klen-2] = '\0';
    557 			//printf("key after = %s\n", key);
    558 		}
    559 
    560 		nvalue = strrchr(param->key, '*');
    561 		if (nvalue == NULL)
    562 			continue;
    563 		for (i = 1; nvalue[i]; i++)
    564 			if (!isdigit(nvalue[i]))
    565 				break;
    566 		if (nvalue[i])
    567 			continue;
    568 
    569 		nvalue[0] = '\0';
    570 		if (llist_get(reorder, param->key) != NULL)
    571 			llist_add(reorder, param->key, NULL, 0);
    572 		nvalue[0] = '*';
    573 	}
    574 
    575 	/*
    576 	 * Sort and concatenate the return list.
    577 	 */
    578 	forllist(reorder, param) {
    579 		hits = llist_new();
    580 		forllist(params, nparam) {
    581 			if (!strncmp(nparam->key, param->key,
    582 						strlen(param->key))) {
    583 				llist_add(hits, nparam->key, nparam->data,
    584 						nparam->datalen);
    585 			}
    586 		}
    587 		if (hits->len < 1) {
    588 			llist_free(hits);
    589 			continue;
    590 		}
    591 
    592 		nparam = llistelem_new(param->key, NULL, 0);
    593 		hits = llist_internsort(hits, mime_paramsort);
    594 		forllist(hits, hit) {
    595 			nparam->data = memdupcat(nparam->data,
    596 					nparam->datalen, hit->data,
    597 					hit->datalen);
    598 			nparam->datalen += hit->datalen-1;
    599 		}
    600 
    601 		params = llist_listdel(params, hits);
    602 		llist_free(hits);
    603 		llist_addelem(params, nparam);
    604 	}
    605 	llist_free(reorder);
    606 
    607 	return params;
    608 }
    609 
    610 llist_t *
    611 mime_parseheader(char *field)
    612 {
    613 	char *tok, *buf, *key, *value, *sep, *eq, quot;
    614 	llist_t *ret;
    615 	int tlen;
    616 
    617 	buf = memdups(field);
    618 
    619 	tok = buf;
    620 	ret = llist_new();
    621 	//printf("mime_parseheader: buf = '%s'\n", buf);
    622 	while (tok[0] != '\0') {
    623 		key = NULL;
    624 		value = NULL;
    625 
    626 		/*
    627 		 * 0.) Sanitize the beginning and the end.
    628 		 */
    629 		while (isspace(tok[0]))
    630 			tok++;
    631 		tlen = strlen(tok);
    632 		while (isspace(tok[tlen-1])) {
    633 			tok[tlen-1] = '\0';
    634 			tlen--;
    635 		}
    636 		//printf("mime_parseheader: after sanitize: tok = '%s'\n", tok);
    637 
    638 		/*
    639 		 * 1.) ([\t\r\v\f ]*)key
    640 		 */
    641 		key = tok + strspn(tok, "\t\r\v\f ");
    642 		//printf("mime_parseheader: key = '%s'\n", tok);
    643 
    644 		/*
    645 		 * 2.) key
    646 		 */
    647 		tok = key + strcspn(key, "\t\r\v\f =;");
    648 		if (tok[0] == ';' || tok[0] == '\0') {
    649 			quot = tok[0];
    650 			tok[0] = '\0';
    651 			if (strlen(key) > 0) {
    652 				//printf("mime_parseheader: add key '%s'\n", key);
    653 				llist_add(ret, key, NULL, 0);
    654 			}
    655 			if (quot != '\0')
    656 				tok++;
    657 			continue;
    658 		}
    659 
    660 		//printf("mime_parseheader: tok = '%s'\n", tok);
    661 		if (tok[0] == '=') {
    662 			eq = tok;
    663 		} else {
    664 			/*
    665 			 * 3.) key([\t\r\v\f ]*)=
    666 			 */
    667 			tok[0] = '\0';
    668 			eq = tok + 1 + strspn(tok+1, "\t\r\v\f ;");
    669 			if (eq[0] == ';') {
    670 				if (strlen(key) > 0)
    671 					llist_add(ret, key, NULL, 0);
    672 				tok++;
    673 				continue;
    674 			}
    675 
    676 			if (eq[0] != '=') {
    677 				/*
    678 				 * 3.1.) key;
    679 				 */
    680 				if (strlen(key) > 0)
    681 					llist_add(ret, key, NULL, 0);
    682 				tok++;
    683 				continue;
    684 			}
    685 		}
    686 		tok[0] = '\0';
    687 		/*
    688 		 * 4.) key=([\t\r\v\f ]*)("|)value
    689 		 */
    690 		tok = eq + 1 + strspn(eq+1, "\t\r\v\f ");
    691 		switch (tok[0]) {
    692 		case '"':
    693 		case '\'':
    694 			quot = tok[0];
    695 			for (sep = tok+1; sep[0] != '\0'; sep++) {
    696 				if (sep[0] == quot) {
    697 					sep[0] = '\0';
    698 					sep++;
    699 					break;
    700 				}
    701 				if (sep[0] == '\\' && sep[1] != '\0')
    702 					memmove(&sep[1], sep, strlen(&sep[1]));
    703 			}
    704 			value = &tok[1];
    705 			tok = sep;
    706 
    707 			sep = tok + strcspn(tok, ";");
    708 			if (sep[0] == ';') {
    709 				tok = sep + 1;
    710 			} else {
    711 				tok = sep;
    712 			}
    713 			break;
    714 		default:
    715 			/*
    716 			 * 4.1.) value
    717 			 */
    718 			value = tok;
    719 			sep = tok + strcspn(tok, "\t\r\v\f ;");
    720 			if (sep[0] == ';') {
    721 				sep[0] = '\0';
    722 				tok = sep + 1;
    723 			} else {
    724 				tok = sep;
    725 			}
    726 			break;
    727 		}
    728 
    729 		//printf("mime_parseheader: add %s = '%s'\n", key, value);
    730 		llist_add(ret, key, value, strlen(value)+1);
    731 	}
    732 	free(buf);
    733 
    734 	//printf("ret->len = %d\n", ret->len);
    735 	if (ret->len > 0)
    736 		return mime_sanitizeparams(ret);
    737 
    738 	llist_free(ret);
    739 	return NULL;
    740 }
    741 
    742 void
    743 mime_mkpartidsintern(mime_t *mime, char *sect, int pid)
    744 {
    745 	llistelem_t *part;
    746 
    747 	mime->partid = smprintf("%s%d", sect, pid);
    748 	sect = smprintf("%s.", mime->partid);
    749 
    750 	pid = 1;
    751 	forllist(mime->parts, part) {
    752 		mime_mkpartidsintern((mime_t *)part->data, sect, pid);
    753 		pid++;
    754 	}
    755 	free(sect);
    756 }
    757 
    758 void
    759 mime_mkpartids(mime_t *mime)
    760 {
    761 	int pid;
    762 	llistelem_t *part;
    763 
    764 	mime->partid = memdupz("0", 1);
    765 	pid = 1;
    766 	forllist(mime->parts, part)
    767 		mime_mkpartidsintern((mime_t *)part->data, "", pid++);
    768 }
    769 
    770 /*
    771  * This functions searches for the next boundary occurence. It will
    772  * return *choice = 1, if it was an end boundary; otherwise 0.
    773  */
    774 char *
    775 mime_sgetbound(char *bound, char **p, char *max, int *len, int *choice)
    776 {
    777 	char *ret, *op;
    778 	int slen, isenl, isend, sublen;
    779 
    780 	ret = NULL;
    781 
    782 	//printf("bound = '%s'\n", bound);
    783 	//printf("p = '%s'\n", *p);
    784 	slen = strlen(bound);
    785 	*choice = 0;
    786 	isenl = 0;
    787 	isend = 0;
    788 	sublen = 0;
    789 
    790 	for (;;) {
    791 		op = memmem(*p, (max-(*p)), bound, slen);
    792 		if (op == NULL)
    793 			return ret;
    794 
    795 		if (!strncmp(op+slen, "--", 2)) {
    796 			isend = 1;
    797 			if (op[slen+2] == '\n')
    798 				isenl = 1;
    799 		} else if (op[slen] == '\n') {
    800 			isenl = 1;
    801 		}
    802 		//printf("isenl = %d, isend = %d\n", isenl, isend);
    803 
    804 		if (op == *p)
    805 			break;
    806 
    807 		if (op > (*p + 1) && op[-2] == '\r' && op[-1] == '\n')
    808 			sublen = 2;
    809 		if (op > *p && op[-2] != '\r' && op[-1] == '\n')
    810 			sublen = 1;
    811 		//printf("sublen = %d\n", sublen);
    812 		break;
    813 	}
    814 
    815 	if (isend) {
    816 		*choice = 1;
    817 		slen += 2;
    818 	}
    819 
    820 	*len = op - *p - sublen;
    821 	ret = memdupz(*p, *len);
    822 
    823 	*p = op + slen + (isend * 2) + (2 - isenl);
    824 
    825 	//printf("p = '%s'\n", *p);
    826 
    827 	return ret;
    828 }
    829 
    830 mime_t *
    831 mime_preparepart(mime_t *mime)
    832 {
    833 	llistelem_t *hdr, *field;
    834 	llist_t *hdrf;
    835 
    836 	//printf("mime = %p\n", mime);
    837 	hdr = llist_ciget(mime->hdrs, "content-type");
    838 	if (hdr != NULL && hdr->data != NULL && strlen(hdr->data) > 0) {
    839 		//printf("content-type: %s\n", (char *)hdr->data);
    840 		hdrf = mime_parseheader(hdr->data);
    841 		//printf("hdrf = %p\n", hdrf);
    842 		//printf("%s\n", hdrf->first->key);
    843 		if (hdrf != NULL) {
    844 			if (!strncasecmp(hdrf->first->key, "multipart", 9)) {
    845 				//printf("is multipart\n");
    846 				field = llist_ciget(hdrf, "boundary");
    847 				if (field == NULL) {
    848 					return NULL;
    849 					//die("Could not find boundary "
    850 					//	"in multipart!\n");
    851 				}
    852 				mime->boundary = smprintf("--%s",
    853 						(char *)field->data);
    854 				//printf("boundary: \"%s\"\n", mime->boundary);
    855 			}
    856 			mime->ct = memdups(hdrf->first->key);
    857 
    858 			field = llist_ciget(hdrf, "charset");
    859 			if (field != NULL && field->data != NULL) {
    860 				mime->charset = memdupz(field->data,
    861 						field->datalen);
    862 			}
    863 
    864 			llist_free(hdrf);
    865 		}
    866 	}
    867 
    868 	if (mime->ct == NULL)
    869 		mime->ct = memdupz("text/plain", 10);
    870 	//printf("mime->ct = %s\n", mime->ct);
    871 	if (mime->charset == NULL)
    872 		mime->charset = memdupz("iso8859-1", 9);
    873 	//printf("mime->charset = %s\n", mime->charset);
    874 
    875 	hdr = llist_ciget(mime->hdrs, "Content-Transfer-Encoding");
    876 	if (hdr != NULL && hdr->data != NULL) {
    877 		mime->cte = memdupz(hdr->data, hdr->datalen);
    878 	} else {
    879 		mime->cte = memdupz("7bit", 4);
    880 	}
    881 	//printf("mime->cte = %s\n", mime->cte);
    882 
    883 	return mime;
    884 }
    885 
    886 mime_t *
    887 mime_parsebufintern(mime_t *mime, char *str, int len)
    888 {
    889 	int i, partlen, isend, blen;
    890 	char *p, *rp, buf[1025], *key, *value, *tvalue, *part;
    891 	llistelem_t *hdr;
    892 	mime_t *partm;
    893 
    894 	rp = str;
    895 	p = str;
    896 	for (; (rp = sgets(buf, sizeof(buf)-1, &p));) {
    897 		//printf("line '%s'\n", buf);
    898 		blen = strlen(buf);
    899 		if (buf[blen-1] == '\r')
    900 			buf[blen-1] = '\0';
    901 
    902 		switch (mime->state) {
    903 		case HEADERVALUE:
    904 			switch (buf[0]) {
    905 			case ' ':
    906 			case '\t':
    907 			case '\r':
    908 			case '\f':
    909 			case '\v':
    910 				//printf("hdrvalue: %s (%d)\n", buf,
    911 				//		(int)strlen(buf));
    912 				/*
    913 				 * " value"
    914 				 */
    915 				sscanf(buf, "%*[ \t\r\v\f]%1024m[^\n]",
    916 						&value);
    917 				if (value != NULL && hdr != NULL) {
    918 					if (hdr->data != NULL) {
    919 						part = memdup(value, strlen(value)+1);
    920 
    921 						/* Adding a space. */
    922 						hdr->data = memdupcat(hdr->data,
    923 								hdr->datalen-1,
    924 								" ", 1);
    925 						hdr->datalen++;
    926 
    927 						/* Adding the next line. */
    928 						i = strlen(part);
    929 						key = memdupcat(hdr->data,
    930 								hdr->datalen-1,
    931 								part, i+1);
    932 						free(part);
    933 						hdr->data = key;
    934 						hdr->datalen += i;
    935 						//printf("%s = %s\n", hdr->key,
    936 						//	(char *)hdr->data);
    937 					}
    938 					free(value);
    939 				}
    940 				goto mimeparsebufagain;
    941 			default:
    942 				break;
    943 			}
    944 
    945 			if (hdr != NULL)
    946 				hdr = NULL;
    947 			mime->state = HEADER;
    948 			/* FALL THROUGH: No header value found. */
    949 		case HEADER:
    950 			//printf("hdr: %s\n", buf);
    951 
    952 			/*
    953 			 * End of headers.
    954 			 */
    955 			if (strlen(buf) == 0) {
    956 				mime->rawhdrs = memdupz(str, (p - str));
    957 				mime->rawhdrslen = p - str;
    958 				goto mimeparsebufbodyparse;
    959 			}
    960 
    961 			/*
    962 			 * "key: value"
    963 			 */
    964 			key = NULL;
    965 			value = NULL;
    966 			tvalue = NULL;
    967 			sscanf(buf, "%1024m[^: \t\r\v\f]:"
    968 					"%1024m[^\n]", &key, &value);
    969 			if (value == NULL)
    970 				value = memdupz(" ", 2);
    971 			//printf("%s = %s\n", key, value);
    972 			if (key != NULL && value != NULL) {
    973 				tvalue = value + strspn(value,
    974 						" \t\r\v\f");
    975 				hdr = llistelem_new(key, tvalue,
    976 						strlen(tvalue)+1);
    977 				llist_addelem(mime->hdrs, hdr);
    978 				mime->state = HEADERVALUE;
    979 			}
    980 			if (key != NULL)
    981 				free(key);
    982 			if (value != NULL)
    983 				free(value);
    984 			break;
    985 		default:
    986 mimeparsebufagain:
    987 			break;
    988 		}
    989 	}
    990 	//printf("return mime_preparepart\n");
    991 	return mime_preparepart(mime);
    992 
    993 mimeparsebufbodyparse:
    994 	//printf("body parsing begins.\n");
    995 	mime = mime_preparepart(mime);
    996 	if (mime == NULL)
    997 		return NULL;
    998 
    999 	/*
   1000 	 * It is not a multipart message, so take the remainder
   1001 	 * of the given message.
   1002 	 */
   1003 	if (mime->boundary == NULL) {
   1004 		//printf("No boundary there. Taking the remainder.\n");
   1005 		partlen = str - p + len;
   1006 		mime->body = memdupz(p, partlen);
   1007 		mime->bodylen = partlen;
   1008 		//printf("strlen = %ld; partlen = %d;\n", strlen(mime->body),
   1009 		//		partlen);
   1010 		//printf("mime->body = \"%s\"\n", mime->body);
   1011 
   1012 		return mime;
   1013 	} else {
   1014 		//printf("There is a boundary.\n");
   1015 	}
   1016 
   1017 	partlen = 0;
   1018 	//printf("p = \"%s\"\n", p);
   1019 	mime->body = mime_sgetbound(mime->boundary, &p, str + len - 1,
   1020 			&partlen, &isend);
   1021 	mime->bodylen = partlen;
   1022 	if (isend) {
   1023 		/*
   1024 		 * This is an end boundary at the beginning
   1025 		 * of a multipart message. Abort.
   1026 		 */
   1027 		//die("End boundary at beginning of multipart.\n");
   1028 		return mime;
   1029 	}
   1030 	if (mime->body == NULL) {
   1031 		//die("Could not find beginning MIME content.\n");
   1032 		return mime;
   1033 	}
   1034 	//printf("mime->body = \"%s\"\n", mime->body);
   1035 
   1036 	for(;;) {
   1037 		partlen = 0;
   1038 		part = mime_sgetbound(mime->boundary, &p, str + len - 1,
   1039 				&partlen, &isend);
   1040 		//printf("part = \"%s\"\n", part);
   1041 		if (part == NULL) {
   1042 			/*
   1043 			 * There maybe no ending boundary. Some e-mail
   1044 			 * signing applications forget this.
   1045 			 */
   1046 			if (p < (str + len - 1)) {
   1047 				partlen = str - p + len;
   1048 				part = memdupz(p, partlen);
   1049 				p = str + len - 1;
   1050 			} else {
   1051 				break;
   1052 			}
   1053 		}
   1054 
   1055 		partm = mime_new();
   1056 		partm = mime_parsebufintern(partm, part, partlen);
   1057 		if (partm != NULL)
   1058 			llist_addraw(mime->parts, NULL, partm, sizeof(partm));
   1059 		free(part);
   1060 
   1061 		if (isend)
   1062 			break;
   1063 	}
   1064 
   1065 	return mime;
   1066 }
   1067 
   1068 mime_t *
   1069 mime_parsebuf(char *str, int len)
   1070 {
   1071 	mime_t *ret, *pret;
   1072 
   1073 	ret = mime_new();
   1074 	pret = mime_parsebufintern(ret, str, len);
   1075 	if (pret == NULL) {
   1076 		mime_free(ret);
   1077 		return NULL;
   1078 	}
   1079 
   1080 	mime_mkpartids(ret);
   1081 
   1082 	return ret;
   1083 }
   1084 
   1085 char *
   1086 mime_searchsplit(char *data, int klen)
   1087 {
   1088 	char *p, *op;
   1089 	int incomment;
   1090 
   1091 	if (strlen(data) + klen <= 74)
   1092 		return NULL;
   1093 
   1094 	p = &data[73 - klen];
   1095 	op = p;
   1096 	incomment = 0;
   1097 
   1098 	for (;;) {
   1099 		switch (p[0]) {
   1100 		case '"':
   1101 		case '\'':
   1102 			/*
   1103 			 * This is meant to be broken.
   1104 			 * It's just heuristics.
   1105 			 */
   1106 			incomment = !incomment;
   1107 			break;
   1108 		case ' ':
   1109 		case '\t':
   1110 		case '\f':
   1111 		case '\n':
   1112 		case '\r':
   1113 			if (incomment)
   1114 				break;
   1115 			return p;
   1116 		case '\0':
   1117 			return &data[73 - klen];
   1118 		}
   1119 
   1120 		if (p == data) {
   1121 			p = op;
   1122 			op = NULL;
   1123 			continue;
   1124 		}
   1125 
   1126 		if (op != NULL) {
   1127 			p--;
   1128 		} else {
   1129 			p++;
   1130 		}
   1131 	}
   1132 
   1133 	return NULL;
   1134 }
   1135 
   1136 char *
   1137 mime_printheader(llistelem_t *hdr)
   1138 {
   1139 	char *buf, *sp, *osp;
   1140 	int blen, splen;
   1141 
   1142 	blen = 0;
   1143 	sp = mime_searchsplit((char *)hdr->data, strlen(hdr->key) + 2);
   1144 	if (sp != NULL) {
   1145 		buf = smprintf("%s: ", hdr->key);
   1146 		blen = strlen(buf);
   1147 
   1148 		buf = memdupcat(buf, blen, (char *)hdr->data,
   1149 				(sp - (char *)hdr->data));
   1150 		blen += (sp - (char *)hdr->data);
   1151 		buf = memdupcat(buf, blen, "\r\n", 2);
   1152 		blen += 2;
   1153 
   1154 		for (osp = sp;; osp = sp) {
   1155 			sp = mime_searchsplit(osp, 8);
   1156 			if (sp == NULL)
   1157 				break;
   1158 
   1159 			buf = memdupcat(buf, blen, "\t", 1);
   1160 			blen += 1;
   1161 			buf = memdupcat(buf, blen, osp, (sp - osp));
   1162 			blen += (sp - osp);
   1163 			buf = memdupcat(buf, blen, "\r\n", 2);
   1164 			blen += 2;
   1165 		}
   1166 
   1167 		if (strlen(osp) > 0) {
   1168 			buf = memdupcat(buf, blen, "\t", 1);
   1169 			blen += 1;
   1170 			splen = strlen(osp);
   1171 			buf = memdupcat(buf, blen, osp, splen);
   1172 			blen += splen;
   1173 			buf = memdupcat(buf, blen, "\r\n", 2);
   1174 		}
   1175 	} else {
   1176 		buf = smprintf("%s: %s\r\n", hdr->key, (char *)hdr->data);
   1177 	}
   1178 
   1179 	return buf;
   1180 }
   1181 
   1182 char *
   1183 mime_printbuf(mime_t *mime, int *len)
   1184 {
   1185 	llistelem_t *hdr;
   1186 	char *ret, *abuf;
   1187 	int rlen, alen;
   1188 
   1189 	rlen = 0;
   1190 	ret = NULL;
   1191 
   1192 	forllist(mime->hdrs, hdr) {
   1193 		abuf = mime_printheader(hdr);
   1194 		alen = strlen(abuf);
   1195 
   1196 		ret = memdupcat(ret, rlen, abuf, alen);
   1197 		rlen += alen;
   1198 		free(abuf);
   1199 		/*
   1200 		 * TODO: Add part handling.
   1201 		 */
   1202 	}
   1203 
   1204 	ret = memdupcat(ret, rlen, "\r\n", 2);
   1205 	rlen += 2;
   1206 
   1207 	return ret;
   1208 }
   1209 
   1210 void
   1211 printtabs(int depth)
   1212 {
   1213 	for (; depth; depth--)
   1214 		printf("\t");
   1215 }
   1216 
   1217 void
   1218 mime_printintern(mime_t *mime, int depth)
   1219 {
   1220 	llistelem_t *elem;
   1221 
   1222 	printtabs(depth);
   1223 	printf("partid: %s\n", mime->partid);
   1224 	printtabs(depth);
   1225 	printf("hdr:\n");
   1226 	forllist(mime->hdrs, elem) {
   1227 		printtabs(depth);
   1228 		printf("%s = %s\n", elem->key, (char *)elem->data);
   1229 	}
   1230 
   1231 	printtabs(depth);
   1232 	printf("body:\n");
   1233 	printtabs(depth);
   1234 	printf("%d\n", mime->bodylen);
   1235 	printf("%s", mime->body);
   1236 
   1237 	if (mime->parts->len > 0) {
   1238 		printtabs(depth);
   1239 		printf("parts:\n");
   1240 		forllist(mime->parts, elem)
   1241 			mime_printintern((mime_t *)elem->data, depth+1);
   1242 	}
   1243 }
   1244 
   1245 void
   1246 mime_print(mime_t *mime)
   1247 {
   1248 	mime_printintern(mime, 0);
   1249 }
   1250 
   1251 char *
   1252 mime_decodepartencoding(mime_t *mime, int *len)
   1253 {
   1254 	char *ret;
   1255 
   1256 	//printf("ct = \"%s\"\n", mime->ct);
   1257 	//printf("cte = \"%s\"\n", mime->cte);
   1258 	ret = NULL;
   1259 	if (!strcasecmp(mime->cte, "base64")) {
   1260 		*len = mime->bodylen;
   1261 		ret = b64dec(mime->body, len);
   1262 	} else if (!strcasecmp(mime->cte, "quoted-printable")) {
   1263 		*len = mime->bodylen;
   1264 		ret = qpdec(mime->body, len, 0);
   1265 	} else if (!strncasecmp(mime->ct, "text/", 5)) {
   1266 		/* Convert CRLF to LF. */
   1267 		*len = mime->bodylen;
   1268 		ret = dosdec(mime->body, len);
   1269 	}
   1270 
   1271 	if (ret == NULL && mime->body != NULL && mime->bodylen > 0) {
   1272 		*len = mime->bodylen;
   1273 		ret = memdupz(mime->body, mime->bodylen);
   1274 	}
   1275 
   1276 	return ret;
   1277 }
   1278 
   1279 char *
   1280 mime_decodepart(mime_t *mime, int *len)
   1281 {
   1282 	char *ret, *cret;
   1283 
   1284 	if (mime->bodylen == 0) {
   1285 		*len = 0;
   1286 		return memdupz("", 1);
   1287 	}
   1288 
   1289 	ret = mime_decodepartencoding(mime, len);
   1290 	if (ret == NULL) {
   1291 		*len = 0;
   1292 		return memdupz("", 1);
   1293 	}
   1294 
   1295 	if (strcasecmp(mime->cte, "binary")) {
   1296 		if (strcasecmp(mime->charset, "utf-8")) {
   1297 			cret = mime_iconv(ret, mime->charset, "UTF-8");
   1298 			if (cret != NULL) {
   1299 				free(ret);
   1300 				ret = cret;
   1301 			}
   1302 			*len = strlen(ret);
   1303 		}
   1304 	}
   1305 
   1306 	return ret;
   1307 }
   1308 
   1309 char *
   1310 mime_filename(mime_t *mime)
   1311 {
   1312 	char *filename;
   1313 	llistelem_t *hdr, *name;
   1314 	llist_t *hdrp;
   1315 
   1316 	filename = NULL;
   1317 
   1318 	/*
   1319 	 * 1.) The standard.
   1320 	 */
   1321 	hdr = llist_ciget(mime->hdrs, "Content-Disposition");
   1322 	if (hdr != NULL && hdr->data != NULL) {
   1323 		hdrp = mime_parseheader((char *)hdr->data);
   1324 		if (hdrp != NULL) {
   1325 			name = llist_ciget(hdrp, "filename");
   1326 			if (name != NULL && name->data != NULL) {
   1327 				filename = mime_guessheader(
   1328 						(char *)name->data);
   1329 			}
   1330 			llist_free(hdrp);
   1331 		}
   1332 
   1333 		if (filename != NULL)
   1334 			return filename;
   1335 	}
   1336 
   1337 	/*
   1338 	 * 2.) The modern age.
   1339 	 */
   1340 	hdr = llist_ciget(mime->hdrs, "Content-Type");
   1341 	if (hdr != NULL && hdr->data != NULL) {
   1342 		hdrp = mime_parseheader((char *)hdr->data);
   1343 		if (hdrp != NULL) {
   1344 			name = llist_ciget(hdrp, "name");
   1345 			if (name != NULL && name->data != NULL) {
   1346 				filename = mime_guessheader(
   1347 						(char *)name->data);
   1348 			}
   1349 			llist_free(hdrp);
   1350 		}
   1351 
   1352 		if (filename != NULL)
   1353 			return filename;
   1354 	}
   1355 
   1356 	return NULL;
   1357 }
   1358 
   1359 
   1360 char *
   1361 mime_mkfilename(char *id, mime_t *mime)
   1362 {
   1363 	char *filename;
   1364 	llistelem_t *hdr;
   1365 
   1366 	filename = mime_filename(mime);
   1367 	if (filename != NULL)
   1368 		return filename;
   1369 
   1370 	/*
   1371 	 * 3.) The ugly.
   1372 	 */
   1373 	hdr = llist_ciget(mime->hdrs, "Content-Description");
   1374 	if (hdr != NULL && hdr->data != NULL) {
   1375 		filename = mime_guessheader((char *)hdr->data);
   1376 		if (filename != NULL)
   1377 			return filename;
   1378 	}
   1379 
   1380 	/*
   1381 	 * 4.) Last resort.
   1382 	 */
   1383 	if (id == NULL)
   1384 		id = "000";
   1385 	return smprintf("%s.%s.part", id, mime->partid);
   1386 }
   1387 
   1388 char *
   1389 mime_mkboundary(void)
   1390 {
   1391 	srand(time(NULL));
   1392 	return smprintf("=--= _TUlNRSBTdWNrcyEK/%x_ =--=", rand());
   1393 }
   1394