mime.c - rohrpost - A commandline mail client to change the world as we see it.
 (HTM) git clone git://r-36.net/rohrpost
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       mime.c (26891B)
       ---
            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 *nvalue, *dvalue;
          532         int i, n;
          533         //char *key;
          534         //int klen;
          535 
          536         reorder = llist_new();
          537         //printf("mime_sanitizeparams: start\n");
          538         n = 0;
          539         forllist(params, param) {
          540                 if (n == 0) {
          541                         //printf("first key: %s\n", param->key);
          542                         n++;
          543                         continue;
          544                 }
          545 
          546                 //key = param->key;
          547                 //printf("key = %s\n", key);
          548                 //klen = strlen(key);
          549 
          550                 nvalue = strrchr(param->key, '*');
          551                 if (nvalue == NULL)
          552                         continue;
          553                 for (i = 1; nvalue[i]; i++)
          554                         if (!isdigit(nvalue[i]))
          555                                 break;
          556                 if (nvalue[i])
          557                         continue;
          558                 //printf("nvalue = %s\n", nvalue);
          559 
          560                 dvalue = mime_decodeparam((char *)param->data);
          561                 if (dvalue != NULL) {
          562                         //printf("decoded: %s\n", dvalue);
          563                         free(param->data);
          564                         param->data = dvalue;
          565                         param->datalen = strlen(dvalue)+1;
          566                 }
          567 
          568                 nvalue[0] = '\0';
          569                 //printf("key after = %s\n", key);
          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                                 //printf("nparam->key = %s\n", nparam->key);
          584                                 llist_add(hits, nparam->key, nparam->data,
          585                                                 nparam->datalen);
          586                         }
          587                 }
          588                 if (hits->len < 1) {
          589                         llist_free(hits);
          590                         continue;
          591                 }
          592 
          593                 nparam = llistelem_new(param->key, NULL, 0);
          594                 hits = llist_internsort(hits, mime_paramsort);
          595 
          596                 forllist(hits, hit) {
          597                         nparam->data = memdupcat(nparam->data,
          598                                         nparam->datalen, hit->data,
          599                                         hit->datalen);
          600                         nparam->datalen += hit->datalen-1;
          601                 }
          602 
          603                 params = llist_listdel(params, hits);
          604                 llist_free(hits);
          605                 llist_addelem(params, nparam);
          606         }
          607         llist_free(reorder);
          608 
          609         return params;
          610 }
          611 
          612 llist_t *
          613 mime_parseheader(char *field)
          614 {
          615         char *tok, *buf, *key, *value, *sep, *eq, quot;
          616         llist_t *ret;
          617         int tlen;
          618 
          619         buf = memdups(field);
          620 
          621         tok = buf;
          622         ret = llist_new();
          623         //printf("mime_parseheader: buf = '%s'\n", buf);
          624         while (tok[0] != '\0') {
          625                 key = NULL;
          626                 value = NULL;
          627 
          628                 /*
          629                  * 0.) Sanitize the beginning and the end.
          630                  */
          631                 while (isspace(tok[0]))
          632                         tok++;
          633                 tlen = strlen(tok);
          634                 while (isspace(tok[tlen-1])) {
          635                         tok[tlen-1] = '\0';
          636                         tlen--;
          637                 }
          638                 //printf("mime_parseheader: after sanitize: tok = '%s'\n", tok);
          639 
          640                 /*
          641                  * 1.) ([\t\r\v\f ]*)key
          642                  */
          643                 key = tok + strspn(tok, "\t\r\v\f ");
          644                 //printf("mime_parseheader: key = '%s'\n", tok);
          645 
          646                 /*
          647                  * 2.) key
          648                  */
          649                 tok = key + strcspn(key, "\t\r\v\f =;");
          650                 if (tok[0] == ';' || tok[0] == '\0') {
          651                         quot = tok[0];
          652                         tok[0] = '\0';
          653                         if (strlen(key) > 0) {
          654                                 //printf("mime_parseheader: add key '%s'\n", key);
          655                                 llist_add(ret, key, NULL, 0);
          656                         }
          657                         if (quot != '\0')
          658                                 tok++;
          659                         continue;
          660                 }
          661 
          662                 //printf("mime_parseheader: tok = '%s'\n", tok);
          663                 if (tok[0] == '=') {
          664                         eq = tok;
          665                 } else {
          666                         /*
          667                          * 3.) key([\t\r\v\f ]*)=
          668                          */
          669                         tok[0] = '\0';
          670                         eq = tok + 1 + strspn(tok+1, "\t\r\v\f ;");
          671                         if (eq[0] == ';') {
          672                                 if (strlen(key) > 0)
          673                                         llist_add(ret, key, NULL, 0);
          674                                 tok++;
          675                                 continue;
          676                         }
          677 
          678                         if (eq[0] != '=') {
          679                                 /*
          680                                  * 3.1.) key;
          681                                  */
          682                                 if (strlen(key) > 0)
          683                                         llist_add(ret, key, NULL, 0);
          684                                 tok++;
          685                                 continue;
          686                         }
          687                 }
          688                 tok[0] = '\0';
          689                 /*
          690                  * 4.) key=([\t\r\v\f ]*)("|)value
          691                  */
          692                 tok = eq + 1 + strspn(eq+1, "\t\r\v\f ");
          693                 switch (tok[0]) {
          694                 case '"':
          695                 case '\'':
          696                         quot = tok[0];
          697                         for (sep = tok+1; sep[0] != '\0'; sep++) {
          698                                 if (sep[0] == quot) {
          699                                         sep[0] = '\0';
          700                                         sep++;
          701                                         break;
          702                                 }
          703                                 if (sep[0] == '\\' && sep[1] != '\0')
          704                                         memmove(&sep[1], sep, strlen(&sep[1]));
          705                         }
          706                         value = &tok[1];
          707                         tok = sep;
          708 
          709                         sep = tok + strcspn(tok, ";");
          710                         if (sep[0] == ';') {
          711                                 tok = sep + 1;
          712                         } else {
          713                                 tok = sep;
          714                         }
          715                         break;
          716                 default:
          717                         /*
          718                          * 4.1.) value
          719                          */
          720                         value = tok;
          721                         sep = tok + strcspn(tok, "\t\r\v\f ;");
          722                         if (sep[0] == ';') {
          723                                 sep[0] = '\0';
          724                                 tok = sep + 1;
          725                         } else {
          726                                 tok = sep;
          727                         }
          728                         break;
          729                 }
          730 
          731                 //printf("mime_parseheader: add %s = '%s'\n", key, value);
          732                 llist_add(ret, key, value, strlen(value)+1);
          733         }
          734         free(buf);
          735 
          736         //printf("ret->len = %d\n", ret->len);
          737         if (ret->len > 0)
          738                 return mime_sanitizeparams(ret);
          739 
          740         llist_free(ret);
          741         return NULL;
          742 }
          743 
          744 void
          745 mime_mkpartidsintern(mime_t *mime, char *sect, int pid)
          746 {
          747         llistelem_t *part;
          748 
          749         mime->partid = smprintf("%s%d", sect, pid);
          750         sect = smprintf("%s.", mime->partid);
          751 
          752         pid = 1;
          753         forllist(mime->parts, part) {
          754                 mime_mkpartidsintern((mime_t *)part->data, sect, pid);
          755                 pid++;
          756         }
          757         free(sect);
          758 }
          759 
          760 void
          761 mime_mkpartids(mime_t *mime)
          762 {
          763         int pid;
          764         llistelem_t *part;
          765 
          766         mime->partid = memdupz("0", 1);
          767         pid = 1;
          768         forllist(mime->parts, part)
          769                 mime_mkpartidsintern((mime_t *)part->data, "", pid++);
          770 }
          771 
          772 /*
          773  * This functions searches for the next boundary occurence. It will
          774  * return *choice = 1, if it was an end boundary; otherwise 0.
          775  */
          776 char *
          777 mime_sgetbound(char *bound, char **p, char *max, int *len, int *choice)
          778 {
          779         char *ret, *op;
          780         int slen, isenl, isend, sublen;
          781 
          782         ret = NULL;
          783 
          784         //printf("bound = '%s'\n", bound);
          785         //printf("p = '%s'\n", *p);
          786         slen = strlen(bound);
          787         *choice = 0;
          788         isenl = 0;
          789         isend = 0;
          790         sublen = 0;
          791 
          792         for (;;) {
          793                 op = memmem(*p, (max-(*p)), bound, slen);
          794                 if (op == NULL)
          795                         return ret;
          796 
          797                 if (!strncmp(op+slen, "--", 2)) {
          798                         isend = 1;
          799                         if (op[slen+2] == '\n')
          800                                 isenl = 1;
          801                 } else if (op[slen] == '\n') {
          802                         isenl = 1;
          803                 }
          804                 //printf("isenl = %d, isend = %d\n", isenl, isend);
          805 
          806                 if (op == *p)
          807                         break;
          808 
          809                 if (op > (*p + 1) && op[-2] == '\r' && op[-1] == '\n')
          810                         sublen = 2;
          811                 if (op > *p && op[-2] != '\r' && op[-1] == '\n')
          812                         sublen = 1;
          813                 //printf("sublen = %d\n", sublen);
          814                 break;
          815         }
          816 
          817         if (isend) {
          818                 *choice = 1;
          819                 slen += 2;
          820         }
          821 
          822         *len = op - *p - sublen;
          823         ret = memdupz(*p, *len);
          824 
          825         *p = op + slen + (isend * 2) + (2 - isenl);
          826 
          827         //printf("p = '%s'\n", *p);
          828 
          829         return ret;
          830 }
          831 
          832 mime_t *
          833 mime_preparepart(mime_t *mime)
          834 {
          835         llistelem_t *hdr, *field;
          836         llist_t *hdrf;
          837 
          838         //printf("mime = %p\n", mime);
          839         hdr = llist_ciget(mime->hdrs, "content-type");
          840         if (hdr != NULL && hdr->data != NULL && strlen(hdr->data) > 0) {
          841                 //printf("content-type: %s\n", (char *)hdr->data);
          842                 hdrf = mime_parseheader(hdr->data);
          843                 //printf("hdrf = %p\n", hdrf);
          844                 //printf("%s\n", hdrf->first->key);
          845                 if (hdrf != NULL) {
          846                         if (!strncasecmp(hdrf->first->key, "multipart", 9)) {
          847                                 //printf("is multipart\n");
          848                                 field = llist_ciget(hdrf, "boundary");
          849                                 if (field == NULL) {
          850                                         return NULL;
          851                                         //die("Could not find boundary "
          852                                         //        "in multipart!\n");
          853                                 }
          854                                 mime->boundary = smprintf("--%s",
          855                                                 (char *)field->data);
          856                                 //printf("boundary: \"%s\"\n", mime->boundary);
          857                         }
          858                         mime->ct = memdups(hdrf->first->key);
          859 
          860                         field = llist_ciget(hdrf, "charset");
          861                         if (field != NULL && field->data != NULL) {
          862                                 mime->charset = memdupz(field->data,
          863                                                 field->datalen);
          864                         }
          865 
          866                         llist_free(hdrf);
          867                 }
          868         }
          869 
          870         if (mime->ct == NULL)
          871                 mime->ct = memdupz("text/plain", 10);
          872         //printf("mime->ct = %s\n", mime->ct);
          873         if (mime->charset == NULL)
          874                 mime->charset = memdupz("iso8859-1", 9);
          875         //printf("mime->charset = %s\n", mime->charset);
          876 
          877         hdr = llist_ciget(mime->hdrs, "Content-Transfer-Encoding");
          878         if (hdr != NULL && hdr->data != NULL) {
          879                 mime->cte = memdupz(hdr->data, hdr->datalen);
          880         } else {
          881                 mime->cte = memdupz("7bit", 4);
          882         }
          883         //printf("mime->cte = %s\n", mime->cte);
          884 
          885         return mime;
          886 }
          887 
          888 mime_t *
          889 mime_parsebufintern(mime_t *mime, char *str, int len)
          890 {
          891         int i, partlen, isend, blen;
          892         char *p, *rp, buf[1025], *key, *value, *tvalue, *part;
          893         llistelem_t *hdr;
          894         mime_t *partm;
          895 
          896         rp = str;
          897         p = str;
          898         for (; (rp = sgets(buf, sizeof(buf)-1, &p));) {
          899                 blen = strlen(buf);
          900                 if (buf[blen-1] == '\r')
          901                         buf[blen-1] = '\0';
          902                 //printf("line '%s'\n", buf);
          903 
          904                 switch (mime->state) {
          905                 case HEADERVALUE:
          906                         switch (buf[0]) {
          907                         case ' ':
          908                         case '\t':
          909                         case '\r':
          910                         case '\f':
          911                         case '\v':
          912                                 //printf("hdrvalue: %s (%d)\n", buf,
          913                                 //                (int)strlen(buf));
          914                                 /*
          915                                  * " value"
          916                                  */
          917                                 sscanf(buf, "%*[ \t\r\v\f]%1024m[^\n]",
          918                                                 &value);
          919                                 if (value != NULL && hdr != NULL) {
          920                                         if (hdr->data != NULL) {
          921                                                 part = memdup(value, strlen(value)+1);
          922 
          923                                                 /* Adding a space. */
          924                                                 hdr->data = memdupcat(hdr->data,
          925                                                                 hdr->datalen-1,
          926                                                                 " ", 1);
          927                                                 hdr->datalen++;
          928 
          929                                                 /* Adding the next line. */
          930                                                 i = strlen(part);
          931                                                 key = memdupcat(hdr->data,
          932                                                                 hdr->datalen-1,
          933                                                                 part, i+1);
          934                                                 free(part);
          935                                                 hdr->data = key;
          936                                                 hdr->datalen += i;
          937                                                 //printf("%s = %s\n", hdr->key,
          938                                                 //        (char *)hdr->data);
          939                                         }
          940                                         free(value);
          941                                 }
          942                                 goto mimeparsebufagain;
          943                         default:
          944                                 break;
          945                         }
          946 
          947                         if (hdr != NULL)
          948                                 hdr = NULL;
          949                         mime->state = HEADER;
          950                         /* FALL THROUGH: No header value found. */
          951                 case HEADER:
          952                         //printf("hdr: %s\n", buf);
          953 
          954                         /*
          955                          * End of headers.
          956                          */
          957                         if (strlen(buf) == 0) {
          958                                 //printf("end of headers '%c' + '%c'\n", p[0], p[1]);
          959                                 /*
          960                                  * Heuristics for ugly e-mail generators
          961                                  * follow.
          962                                  */
          963                                 /*
          964                                  * Does the line begin with "--"? Looks
          965                                  * like a boundary. Go to next body part.
          966                                  */
          967                                 /*
          968                                 if (p[0] == '-' && p[1] == '-') {
          969                                         mime->rawhdrs = memdupz(str, (p - str));
          970                                         mime->rawhdrslen = p - str;
          971                                         goto mimeparsebufbodyparse;
          972                                 }*/
          973                                 /*
          974                                  * TODO: Find Apple and Google developers
          975                                  *       and teach them how to not do this.
          976                                  * Does the line have some "header: value\n"
          977                                  * form? Go on parsing headers.
          978                                  */
          979                                 /*for (key = p; key[0] != '\n'; key++) {
          980                                         //printf("key[0] = '%c'\n", key[0]);
          981                                         if (key[0] == ':' && key[1] == ' ')
          982                                                 break;
          983                                         if (key[0] == ' ') {
          984                                                 mime->rawhdrs = memdupz(str, (p - str));
          985                                                 mime->rawhdrslen = p - str;
          986                                                 goto mimeparsebufbodyparse;
          987                                         }
          988                                 }*/
          989                                 /*
          990                                  * A line simply ended with no header.
          991                                  * That is suspicious.
          992                                  */
          993                                 /*
          994                                 if (key[0] == '\n') {
          995                                         mime->rawhdrs = memdupz(str, (p - str));
          996                                         mime->rawhdrslen = p - str;
          997                                         goto mimeparsebufbodyparse;
          998                                 }*/
          999                                 mime->rawhdrs = memdupz(str, (p - str));
         1000                                 mime->rawhdrslen = p - str;
         1001                                 goto mimeparsebufbodyparse;
         1002                         }
         1003 
         1004                         /*
         1005                          * "key: value"
         1006                          */
         1007                         key = NULL;
         1008                         value = NULL;
         1009                         tvalue = NULL;
         1010                         sscanf(buf, "%1024m[^: \t\r\v\f]:"
         1011                                         "%1024m[^\n]", &key, &value);
         1012                         if (value == NULL)
         1013                                 value = memdupz(" ", 2);
         1014                         //printf("%s = %s\n", key, value);
         1015                         if (key != NULL && value != NULL) {
         1016                                 tvalue = value + strspn(value,
         1017                                                 " \t\r\v\f");
         1018                                 hdr = llistelem_new(key, tvalue,
         1019                                                 strlen(tvalue)+1);
         1020                                 llist_addelem(mime->hdrs, hdr);
         1021                                 mime->state = HEADERVALUE;
         1022                         }
         1023                         if (key != NULL)
         1024                                 free(key);
         1025                         if (value != NULL)
         1026                                 free(value);
         1027                         break;
         1028                 default:
         1029 mimeparsebufagain:
         1030                         break;
         1031                 }
         1032         }
         1033         //printf("return mime_preparepart\n");
         1034         return mime_preparepart(mime);
         1035 
         1036 mimeparsebufbodyparse:
         1037         //printf("body parsing begins.\n");
         1038         mime = mime_preparepart(mime);
         1039         if (mime == NULL)
         1040                 return NULL;
         1041 
         1042         /*
         1043          * It is not a multipart message, so take the remainder
         1044          * of the given message.
         1045          */
         1046         if (mime->boundary == NULL) {
         1047                 //printf("No boundary there. Taking the remainder.\n");
         1048                 partlen = str - p + len;
         1049                 mime->body = memdupz(p, partlen);
         1050                 mime->bodylen = partlen;
         1051                 //printf("strlen = %ld; partlen = %d;\n", strlen(mime->body),
         1052                 //                partlen);
         1053                 //printf("mime->body = \"%s\"\n", mime->body);
         1054 
         1055                 return mime;
         1056         } else {
         1057                 //printf("There is a boundary.\n");
         1058         }
         1059 
         1060         partlen = 0;
         1061         //printf("p = \"%s\"\n", p);
         1062         mime->body = mime_sgetbound(mime->boundary, &p, str + len - 1,
         1063                         &partlen, &isend);
         1064         mime->bodylen = partlen;
         1065         if (isend) {
         1066                 /*
         1067                  * This is an end boundary at the beginning
         1068                  * of a multipart message. Abort.
         1069                  */
         1070                 //die("End boundary at beginning of multipart.\n");
         1071                 return mime;
         1072         }
         1073         if (mime->body == NULL) {
         1074                 //die("Could not find beginning MIME content.\n");
         1075                 return mime;
         1076         }
         1077         //printf("mime->body = \"%s\"\n", mime->body);
         1078 
         1079         for(;;) {
         1080                 partlen = 0;
         1081                 part = mime_sgetbound(mime->boundary, &p, str + len - 1,
         1082                                 &partlen, &isend);
         1083                 //printf("part = \"%s\"\n", part);
         1084                 if (part == NULL) {
         1085                         /*
         1086                          * There maybe no ending boundary. Some e-mail
         1087                          * signing applications forget this.
         1088                          */
         1089                         if (p < (str + len - 1)) {
         1090                                 partlen = str - p + len;
         1091                                 part = memdupz(p, partlen);
         1092                                 p = str + len - 1;
         1093                         } else {
         1094                                 break;
         1095                         }
         1096                 }
         1097 
         1098                 partm = mime_new();
         1099                 partm = mime_parsebufintern(partm, part, partlen);
         1100                 if (partm != NULL)
         1101                         llist_addraw(mime->parts, NULL, partm, sizeof(partm));
         1102                 free(part);
         1103 
         1104                 if (isend)
         1105                         break;
         1106         }
         1107 
         1108         return mime;
         1109 }
         1110 
         1111 mime_t *
         1112 mime_parsebuf(char *str, int len)
         1113 {
         1114         mime_t *ret, *pret;
         1115 
         1116         ret = mime_new();
         1117         pret = mime_parsebufintern(ret, str, len);
         1118         if (pret == NULL) {
         1119                 mime_free(ret);
         1120                 return NULL;
         1121         }
         1122 
         1123         mime_mkpartids(ret);
         1124 
         1125         return ret;
         1126 }
         1127 
         1128 char *
         1129 mime_searchsplit(char *data, int klen)
         1130 {
         1131         char *p, *op;
         1132         int incomment;
         1133 
         1134         if (strlen(data) + klen <= 74)
         1135                 return NULL;
         1136 
         1137         p = &data[73 - klen];
         1138         op = p;
         1139         incomment = 0;
         1140 
         1141         for (;;) {
         1142                 switch (p[0]) {
         1143                 case '"':
         1144                 case '\'':
         1145                         /*
         1146                          * This is meant to be broken.
         1147                          * It's just heuristics.
         1148                          */
         1149                         incomment = !incomment;
         1150                         break;
         1151                 case ' ':
         1152                 case '\t':
         1153                 case '\f':
         1154                 case '\n':
         1155                 case '\r':
         1156                         if (incomment)
         1157                                 break;
         1158                         return p;
         1159                 case '\0':
         1160                         return &data[73 - klen];
         1161                 }
         1162 
         1163                 if (p == data) {
         1164                         p = op;
         1165                         op = NULL;
         1166                         continue;
         1167                 }
         1168 
         1169                 if (op != NULL) {
         1170                         p--;
         1171                 } else {
         1172                         p++;
         1173                 }
         1174         }
         1175 
         1176         return NULL;
         1177 }
         1178 
         1179 char *
         1180 mime_printheader(llistelem_t *hdr)
         1181 {
         1182         char *buf, *sp, *osp;
         1183         int blen, splen;
         1184 
         1185         blen = 0;
         1186         sp = mime_searchsplit((char *)hdr->data, strlen(hdr->key) + 2);
         1187         if (sp != NULL) {
         1188                 buf = smprintf("%s: ", hdr->key);
         1189                 blen = strlen(buf);
         1190 
         1191                 buf = memdupcat(buf, blen, (char *)hdr->data,
         1192                                 (sp - (char *)hdr->data));
         1193                 blen += (sp - (char *)hdr->data);
         1194                 buf = memdupcat(buf, blen, "\r\n", 2);
         1195                 blen += 2;
         1196 
         1197                 for (osp = sp;; osp = sp) {
         1198                         sp = mime_searchsplit(osp, 8);
         1199                         if (sp == NULL)
         1200                                 break;
         1201 
         1202                         buf = memdupcat(buf, blen, "\t", 1);
         1203                         blen += 1;
         1204                         buf = memdupcat(buf, blen, osp, (sp - osp));
         1205                         blen += (sp - osp);
         1206                         buf = memdupcat(buf, blen, "\r\n", 2);
         1207                         blen += 2;
         1208                 }
         1209 
         1210                 if (strlen(osp) > 0) {
         1211                         buf = memdupcat(buf, blen, "\t", 1);
         1212                         blen += 1;
         1213                         splen = strlen(osp);
         1214                         buf = memdupcat(buf, blen, osp, splen);
         1215                         blen += splen;
         1216                         buf = memdupcat(buf, blen, "\r\n", 2);
         1217                 }
         1218         } else {
         1219                 buf = smprintf("%s: %s\r\n", hdr->key, (char *)hdr->data);
         1220         }
         1221 
         1222         return buf;
         1223 }
         1224 
         1225 char *
         1226 mime_printbuf(mime_t *mime, int *len)
         1227 {
         1228         llistelem_t *hdr;
         1229         char *ret, *abuf;
         1230         int rlen, alen;
         1231 
         1232         rlen = 0;
         1233         ret = NULL;
         1234 
         1235         forllist(mime->hdrs, hdr) {
         1236                 abuf = mime_printheader(hdr);
         1237                 alen = strlen(abuf);
         1238 
         1239                 ret = memdupcat(ret, rlen, abuf, alen);
         1240                 rlen += alen;
         1241                 free(abuf);
         1242                 /*
         1243                  * TODO: Add part handling.
         1244                  */
         1245         }
         1246 
         1247         ret = memdupcat(ret, rlen, "\r\n", 2);
         1248         rlen += 2;
         1249 
         1250         return ret;
         1251 }
         1252 
         1253 void
         1254 printtabs(int depth)
         1255 {
         1256         for (; depth; depth--)
         1257                 printf("\t");
         1258 }
         1259 
         1260 void
         1261 mime_printintern(mime_t *mime, int depth)
         1262 {
         1263         llistelem_t *elem;
         1264 
         1265         printtabs(depth);
         1266         printf("partid: %s\n", mime->partid);
         1267         printtabs(depth);
         1268         printf("hdr:\n");
         1269         forllist(mime->hdrs, elem) {
         1270                 printtabs(depth);
         1271                 printf("%s = %s\n", elem->key, (char *)elem->data);
         1272         }
         1273 
         1274         printtabs(depth);
         1275         printf("body:\n");
         1276         printtabs(depth);
         1277         printf("%d\n", mime->bodylen);
         1278         printf("%s", mime->body);
         1279 
         1280         if (mime->parts->len > 0) {
         1281                 printtabs(depth);
         1282                 printf("parts:\n");
         1283                 forllist(mime->parts, elem)
         1284                         mime_printintern((mime_t *)elem->data, depth+1);
         1285         }
         1286 }
         1287 
         1288 void
         1289 mime_print(mime_t *mime)
         1290 {
         1291         mime_printintern(mime, 0);
         1292 }
         1293 
         1294 char *
         1295 mime_decodepartencoding(mime_t *mime, int *len)
         1296 {
         1297         char *ret;
         1298 
         1299         //printf("ct = \"%s\"\n", mime->ct);
         1300         //printf("cte = \"%s\"\n", mime->cte);
         1301         ret = NULL;
         1302         if (!strcasecmp(mime->cte, "base64")) {
         1303                 *len = mime->bodylen;
         1304                 ret = b64dec(mime->body, len);
         1305         } else if (!strcasecmp(mime->cte, "quoted-printable")) {
         1306                 *len = mime->bodylen;
         1307                 ret = qpdec(mime->body, len, 0);
         1308         } else if (!strncasecmp(mime->ct, "text/", 5)) {
         1309                 /* Convert CRLF to LF. */
         1310                 *len = mime->bodylen;
         1311                 ret = dosdec(mime->body, len);
         1312         }
         1313 
         1314         if (ret == NULL && mime->body != NULL && mime->bodylen > 0) {
         1315                 *len = mime->bodylen;
         1316                 ret = memdupz(mime->body, mime->bodylen);
         1317         }
         1318 
         1319         return ret;
         1320 }
         1321 
         1322 char *
         1323 mime_decodepart(mime_t *mime, int *len)
         1324 {
         1325         char *ret, *cret;
         1326 
         1327         if (mime->bodylen == 0) {
         1328                 *len = 0;
         1329                 return memdupz("", 1);
         1330         }
         1331 
         1332         ret = mime_decodepartencoding(mime, len);
         1333         if (ret == NULL) {
         1334                 *len = 0;
         1335                 return memdupz("", 1);
         1336         }
         1337 
         1338         if (strcasecmp(mime->cte, "binary")) {
         1339                 if (strcasecmp(mime->charset, "utf-8")) {
         1340                         cret = mime_iconv(ret, mime->charset, "UTF-8");
         1341                         if (cret != NULL) {
         1342                                 free(ret);
         1343                                 ret = cret;
         1344                         }
         1345                         *len = strlen(ret);
         1346                 }
         1347         }
         1348 
         1349         return ret;
         1350 }
         1351 
         1352 char *
         1353 mime_filename(mime_t *mime)
         1354 {
         1355         char *filename;
         1356         llistelem_t *hdr, *name;
         1357         llist_t *hdrp;
         1358 
         1359         filename = NULL;
         1360 
         1361         /*
         1362          * 1.) The standard.
         1363          */
         1364         hdr = llist_ciget(mime->hdrs, "Content-Disposition");
         1365         if (hdr != NULL && hdr->data != NULL) {
         1366                 hdrp = mime_parseheader((char *)hdr->data);
         1367                 if (hdrp != NULL) {
         1368                         name = llist_ciget(hdrp, "filename");
         1369                         if (name != NULL && name->data != NULL) {
         1370                                 filename = mime_guessheader(
         1371                                                 (char *)name->data);
         1372                         }
         1373                         llist_free(hdrp);
         1374                 }
         1375 
         1376                 if (filename != NULL)
         1377                         return filename;
         1378         }
         1379 
         1380         /*
         1381          * 2.) The modern age.
         1382          */
         1383         hdr = llist_ciget(mime->hdrs, "Content-Type");
         1384         if (hdr != NULL && hdr->data != NULL) {
         1385                 hdrp = mime_parseheader((char *)hdr->data);
         1386                 if (hdrp != NULL) {
         1387                         name = llist_ciget(hdrp, "name");
         1388                         if (name != NULL && name->data != NULL) {
         1389                                 filename = mime_guessheader(
         1390                                                 (char *)name->data);
         1391                         }
         1392                         llist_free(hdrp);
         1393                 }
         1394 
         1395                 if (filename != NULL)
         1396                         return filename;
         1397         }
         1398 
         1399         return NULL;
         1400 }
         1401 
         1402 
         1403 char *
         1404 mime_mkfilename(char *id, mime_t *mime)
         1405 {
         1406         char *filename;
         1407         llistelem_t *hdr;
         1408 
         1409         filename = mime_filename(mime);
         1410         if (filename != NULL)
         1411                 return filename;
         1412 
         1413         /*
         1414          * 3.) The ugly.
         1415          */
         1416         hdr = llist_ciget(mime->hdrs, "Content-Description");
         1417         if (hdr != NULL && hdr->data != NULL) {
         1418                 filename = mime_guessheader((char *)hdr->data);
         1419                 if (filename != NULL)
         1420                         return filename;
         1421         }
         1422 
         1423         /*
         1424          * 4.) Last resort.
         1425          */
         1426         if (id == NULL)
         1427                 id = "000";
         1428         return smprintf("%s.%s.part", id, mime->partid);
         1429 }
         1430 
         1431 char *
         1432 mime_mkboundary(void)
         1433 {
         1434         srand(time(NULL));
         1435         return smprintf("=--= _TUlNRSBTdWNrcyEK/%x_ =--=", rand());
         1436 }
         1437