1 /*:ts=8*/
2 /*****************************************************************************
3 * FIDOGATE --- Gateway UNIX Mail/News <-> FTN NetMail/EchoMail
4 *
5 *
6 * MIME stuff
7 *
8 *****************************************************************************
9 * Copyright (C) 1990-2002
10 * _____ _____
11 * | |___ | Martin Junius <mj@fidogate.org>
12 * | | | | | | Radiumstr. 18
13 * |_|_|_|@home| D-51069 Koeln, Germany
14 *
15 * This file is part of FIDOGATE.
16 *
17 * FIDOGATE is free software; you can redistribute it and/or modify it
18 * under the terms of the GNU General Public License as published by the
19 * Free Software Foundation; either version 2, or (at your option) any
20 * later version.
21 *
22 * FIDOGATE is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with FIDOGATE; see the file COPYING. If not, write to the Free
29 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
30 *****************************************************************************/
31
32 #include "fidogate.h"
33
34 static int is_qpx(int);
35 static int x2toi(char *);
36 void mime_free(void);
37
38 static MIMEInfo *mime_list = NULL;
39
mime_get_main_charset(RFCHeader * header)40 static char *mime_get_main_charset(RFCHeader *header)
41 {
42 MIMEInfo *mime;
43 char *charset;
44 mime = get_mime(s_header_getcomplete(header, "MIME-Version"),
45 s_header_getcomplete(header, "Content-Type"),
46 s_header_getcomplete(header, "Content-Transfer-Encoding"));
47 charset = strsave(mime->type_charset);
48 mime_free();
49 return charset;
50 }
51
is_qpx(int c)52 static int is_qpx(int c)
53 {
54 return isxdigit(c) /**is_digit(c) || (c>='A' && c<='F')**/ ;
55 }
56
x2toi(char * s)57 static int x2toi(char *s)
58 {
59 int val = 0;
60 int n;
61
62 n = toupper(*s) - (isalpha(*s) ? 'A' - 10 : '0');
63 val = val * 16 + n;
64 s++;
65 n = toupper(*s) - (isalpha(*s) ? 'A' - 10 : '0');
66 val = val * 16 + n;
67
68 return val;
69 }
70
71 /*
72 * Dequote string with MIME-style quoted-printable =XX
73 */
mime_dequote(char * d,size_t n,char * s)74 char *mime_dequote(char *d, size_t n, char *s)
75 {
76 int i, c = 0;
77
78 for (i = 0; i < n - 1 && *s; i++, s++) {
79 if (s[0] == '=') { /* MIME quoted printable */
80 if (is_qpx(s[1]) && is_qpx(s[2])) { /* =XX */
81 c = x2toi(s + 1);
82 s += 2;
83 } else if (s[1] == '\n' || /* =<LF> and */
84 (s[1] == '\r' && s[2] == '\n')) { /* =<CR><LF> */
85 break;
86 }
87 } else if (s[0] == '_') { /* Underscore */
88 c = ' ';
89 } else { /* Nothing special to do */
90 c = *s;
91 }
92 d[i] = c;
93 }
94 d[i] = 0;
95
96 return d;
97 }
98
99 /*
100 * Decode MIME RFC1522 header
101 *
102 */
103 #define MIME_HEADER_CODE_START "=?"
104 #define MIME_HEADER_CODE_MIDDLE_QP "?Q?"
105 #define MIME_HEADER_CODE_MIDDLE_B64 "?B?"
106 #define MIME_HEADER_CODE_END "?="
107 /*
108 * no \r, see
109 * https://github.com/ykaliuta/fidogate/commit/a727f4bae629905392e8e174c003bfa38bcbf386
110 */
111 #define MIME_HEADER_STR_DELIM "\n"
112 #define MIME_ENC_STRING_LIMIT 80
113 #define MIME_MAX_ENC_LEN 31
114
115 #define MAX_UTF8_LEN 4
116
117 /* A..Z -- 0x00..0x19
118 * a..z -- 0x1a..0x33
119 * 0..9 -- 0x34..0x3d
120 * + -- 0x3e
121 * / -- 0x3f
122 * = -- special
123 */
mime_b64toint(char c)124 int mime_b64toint(char c)
125 {
126 if (('A' <= c) && ('Z' >= c))
127 return (c - 'A');
128 else if (('a' <= c) && ('z' >= c))
129 return (0x1a + c - 'a');
130 else if (('0' <= c) && ('9' >= c))
131 return (0x34 + c - '0');
132 else if ('+' == c)
133 return 0x3e;
134 else if ('/' == c)
135 return 0x3f;
136 else if ('=' == c)
137 return 0x40;
138 else
139 return ERROR;
140 }
141
mime_inttob64(int a)142 char mime_inttob64(int a)
143 {
144 char c = (char)(a & 0x0000003f);
145 if (c <= 0x19)
146 return c + 'A';
147 if (c <= 0x33)
148 return c - 0x1a + 'a';
149 if (c <= 0x3d)
150 return c - 0x34 + '0';
151 if (c == 0x3e)
152 return '+';
153 else
154 return '/';
155 }
156
mime_qptoint(char c)157 int mime_qptoint(char c)
158 {
159 if (('0' <= c) && ('9' >= c))
160 return (c - '0');
161 else if (('A' <= c) && ('F' >= c))
162 return (0x0a + c - 'A');
163 else if (('a' <= c) && ('f' >= c))
164 return (0x0a + c - 'a');
165 else
166 return ERROR;
167 }
168
169 /* rfc 2045 */
mime_qp_is_plain(int c)170 static bool mime_qp_is_plain(int c)
171 {
172 return ((c >= '%') && (c < '@') && (c != '='))
173 || ((c >= 'A') && (c <= 'Z'))
174 || ((c >= 'a') && (c <= 'z'));
175 }
176
177 #define B64_ENC_CHUNK 3
178 #define B64_NLET_PER_CHUNK 4
179
180 /*
181 * dst must have space for at least 4 octets
182 * dst will not be NUL-terminated
183 * Max 3 source octets encoded
184 */
mime_b64_encode_chunk(char * dst,unsigned char * src,unsigned len)185 static size_t mime_b64_encode_chunk(char *dst, unsigned char *src, unsigned len)
186 {
187 int padding;
188
189 if (len == 0)
190 return 0;
191
192 if (len > B64_ENC_CHUNK)
193 len = B64_ENC_CHUNK;
194
195 padding = B64_ENC_CHUNK - len;
196
197 dst[0] = mime_inttob64(src[0] >> 2);
198 dst[1] = mime_inttob64(src[0] << 4);
199
200 if (len < 2)
201 goto out;
202
203 dst[1] = mime_inttob64((src[0] << 4) | (src[1] >> 4));
204 dst[2] = mime_inttob64(src[1] << 2);
205
206 if (len < 3)
207 goto out;
208
209 dst[2] = mime_inttob64((src[1] << 2) | (src[2] >> 6));
210 dst[3] = mime_inttob64(src[2]);
211
212 out:
213 for (; padding > 0; padding--)
214 dst[B64_NLET_PER_CHUNK - padding] = '=';
215
216 return len;
217 }
218
219 struct mime_word_enc_state {
220 char *encoded_line;
221 char *charset;
222 size_t charset_len;
223 size_t size;
224 size_t inc;
225 size_t pos; /* position in the string */
226 size_t vpos; /* visual position in the line */
227 size_t limit; /* in 7 bit chars, no line endings */
228 size_t rem_len;
229 char *encoding;
230 size_t max_mb_chunks; /* max decoder's chunks to flush mb seq */
231 size_t (*calc_len)(char *token, size_t len);
232 size_t (*encode)(struct mime_word_enc_state * state, char *p, size_t len);
233 char rem[B64_ENC_CHUNK];
234 bool is_mime; /* previous word is not plain */
235 };
236
mime_b64_calc_len(char * t,size_t len)237 static size_t mime_b64_calc_len(char *t, size_t len)
238 {
239 size_t res;
240
241 /* only b64 now */
242
243 res = len / 3 * 4;
244 res += len % 3 ? 4 : 0;
245
246 return res;
247 }
248
mime_inc_size(struct mime_word_enc_state * state,size_t inc)249 static void mime_inc_size(struct mime_word_enc_state *state, size_t inc)
250 {
251 state->size += inc;
252 state->encoded_line = xrealloc(state->encoded_line, state->size);
253 }
254
mime_check_and_inc_size(struct mime_word_enc_state * state,size_t inc)255 static void mime_check_and_inc_size(struct mime_word_enc_state *state,
256 size_t inc)
257 {
258 if (state->pos + inc <= state->size)
259 return;
260
261 mime_inc_size(state, inc);
262 }
263
mime_strcat(struct mime_word_enc_state * state,char * str)264 static void mime_strcat(struct mime_word_enc_state *state, char *str)
265 {
266 size_t len;
267
268 strcpy(state->encoded_line + state->pos, str);
269 len = strlen(str);
270 state->pos += len;
271 state->vpos += len;
272 }
273
mime_strlcpy(struct mime_word_enc_state * state,char * str,size_t len)274 static void mime_strlcpy(struct mime_word_enc_state *state,
275 char *str, size_t len)
276 {
277 strncpy(state->encoded_line + state->pos, str, len);
278
279 state->pos += len;
280 state->vpos += len;
281
282 mime_check_and_inc_size(state, 1);
283 *(state->encoded_line + state->pos) = '\0';
284 }
285
286 /*
287 * Calculates length of the word if it have been encoded.
288 * Takes into account the previous state:
289 * - if previous state mime-encoded and adding 7 bit, that must be
290 * closed;
291 * - if previous state mime-encoded and adding mime, that is just
292 * connected to the code, space encoded, no mime-start;
293 * - if previous state is 7 bit and adding 7 bit -- just connect as
294 * well;
295 * - if previous state is 7 bit and adding mime, it should start with
296 * new mime-start;
297 * If current last word is mime, account 2 chars for mime-end before
298 * the new line.
299 */
mime_word_calc_len(struct mime_word_enc_state * state,char * token,size_t len,char ** start)300 static size_t mime_word_calc_len(struct mime_word_enc_state *state,
301 char *token, size_t len, char **start)
302 { /* first non-space */
303 size_t encoded_len;
304 char *p;
305 bool prev_mime = state->is_mime;
306 size_t charset_len = state->charset_len;
307 bool is_7bit;
308
309 for (p = token; p - token < len; p++) {
310 if (!isspace(*p))
311 break;
312 }
313
314 is_7bit = charset_is_7bit(token, len);
315
316 if (prev_mime) {
317 if (is_7bit) {
318 encoded_len = strlen(MIME_HEADER_CODE_END) + len;
319 } else {
320 encoded_len = state->calc_len(token, len); /* with spaces */
321 }
322 } else {
323 if (is_7bit) {
324 encoded_len = len;
325 } else {
326 encoded_len = 1 + /* space */
327 strlen(MIME_HEADER_CODE_START) + charset_len + strlen(MIME_HEADER_CODE_MIDDLE_B64) + state->calc_len(p, len); /* without spaces */
328 }
329 }
330
331 encoded_len += state->rem_len ? B64_NLET_PER_CHUNK : 0;
332
333 *start = p;
334 return encoded_len;
335 }
336
mime_flush_reminder(struct mime_word_enc_state * state)337 static void mime_flush_reminder(struct mime_word_enc_state *state)
338 {
339 if (state->rem_len == 0)
340 return;
341
342 mime_b64_encode_chunk(state->encoded_line + state->pos,
343 (unsigned char *)state->rem, state->rem_len);
344
345 state->pos += B64_NLET_PER_CHUNK;
346 state->vpos += B64_NLET_PER_CHUNK;
347 state->rem_len = 0;
348 }
349
mime_add_reminder(struct mime_word_enc_state * state,char ** p,size_t * len,size_t limit)350 static size_t mime_add_reminder(struct mime_word_enc_state *state,
351 char **p, size_t *len, size_t limit)
352 {
353 size_t ret;
354 size_t rem_left;
355
356 if (state->rem_len == 0)
357 return 0;
358
359 if (state->rem_len + *len < B64_ENC_CHUNK) {
360 memcpy(&state->rem[state->rem_len], *p, *len);
361
362 state->rem_len += *len;
363 (*p) += *len;
364
365 ret = *len;
366 *len = 0;
367 return ret;
368 }
369
370 if (state->vpos + B64_NLET_PER_CHUNK > limit)
371 return 0;
372
373 rem_left = sizeof(state->rem) - state->rem_len;
374
375 memcpy(&state->rem[state->rem_len], *p, rem_left);
376 (*p) += rem_left;
377 *len -= rem_left;
378 state->rem_len += rem_left;
379
380 mime_flush_reminder(state);
381
382 return rem_left;
383 }
384
mime_save_reminder(struct mime_word_enc_state * state,char * p,size_t len)385 static size_t mime_save_reminder(struct mime_word_enc_state *state,
386 char *p, size_t len)
387 {
388 if (len > sizeof(state->rem)) {
389 fprintf(stderr, "ERROR: too big reminder: %zu\n", len);
390 abort();
391 }
392
393 memcpy(state->rem, p, len);
394 state->rem_len = len;
395
396 return len;
397 }
398
mime_word_end(struct mime_word_enc_state * state)399 static void mime_word_end(struct mime_word_enc_state *state)
400 {
401 if (!state->is_mime)
402 return;
403
404 mime_strcat(state, MIME_HEADER_CODE_END);
405
406 state->is_mime = false;
407 }
408
mime_line_break(struct mime_word_enc_state * state)409 static void mime_line_break(struct mime_word_enc_state *state)
410 {
411 state->size += state->inc;
412 state->encoded_line = xrealloc(state->encoded_line, state->size);
413
414 mime_word_end(state);
415
416 mime_strcat(state, MIME_HEADER_STR_DELIM);
417
418 state->vpos = 0;
419 state->is_mime = false;
420 }
421
mime_word_start(struct mime_word_enc_state * state)422 static void mime_word_start(struct mime_word_enc_state *state)
423 {
424 size_t len;
425
426 len = 1 + strlen(MIME_HEADER_CODE_START)
427 + state->charset_len + strlen(MIME_HEADER_CODE_MIDDLE_B64)
428 + B64_NLET_PER_CHUNK /* at least one */
429 + strlen(MIME_HEADER_CODE_END);
430
431 if (state->vpos + len > state->limit)
432 mime_line_break(state);
433
434 mime_strcat(state, " ");
435 mime_strcat(state, MIME_HEADER_CODE_START);
436 mime_strcat(state, state->charset);
437 mime_strcat(state, state->encoding);
438
439 state->is_mime = true;
440 }
441
mime_switch_to_plain(struct mime_word_enc_state * state)442 static void mime_switch_to_plain(struct mime_word_enc_state *state)
443 {
444 size_t end_len = strlen(MIME_HEADER_CODE_END);
445 size_t extra_len = state->rem_len ? B64_NLET_PER_CHUNK : 0;
446
447 if (!state->is_mime)
448 return;
449
450 if (state->vpos + extra_len + end_len > state->limit) {
451 mime_line_break(state);
452 mime_word_start(state);
453 }
454
455 mime_flush_reminder(state);
456 mime_word_end(state);
457 }
458
459 /* Get size of unfinished multibyte sequence. Only utf-8 supported */
mime_get_mb_tail(struct mime_word_enc_state * state,char * p)460 static size_t mime_get_mb_tail(struct mime_word_enc_state *state, char *p)
461 {
462 unsigned char c;
463 char *save_p;
464
465 if (state->max_mb_chunks == 0)
466 return 0;
467
468 if (!strieq(state->charset, "utf-8"))
469 return 0;
470
471 save_p = p;
472 for (c = *p;
473 ((c & 0x80) == 0x80) && ((c & 0x40) == 0);
474 c = *++p)
475 ;
476 return p - save_p;
477 }
478
479 /*
480 * Encodes part of word, that fits the current line
481 */
mime_word_enc_b64(struct mime_word_enc_state * state,char * p,size_t len)482 static size_t mime_word_enc_b64(struct mime_word_enc_state *state,
483 char *p, size_t len)
484 {
485 size_t limit;
486 size_t left;
487 size_t done;
488 size_t chunk;
489 size_t to_encode;
490 bool end_of_line = false;
491
492 limit = state->limit - strlen(MIME_HEADER_CODE_END);
493
494 done = mime_add_reminder(state, &p, &len, limit);
495
496 left = len;
497
498 while ((left >= B64_ENC_CHUNK) && !end_of_line) {
499
500 /* space to flush multibyte and encode current */
501 if (state->vpos
502 + (state->max_mb_chunks + 1) * B64_NLET_PER_CHUNK > limit) {
503
504 end_of_line = true;
505
506 /* flush multibyte sequence */
507 to_encode = mime_get_mb_tail(state, p);
508 } else {
509 to_encode = left;
510 }
511
512 chunk = mime_b64_encode_chunk(state->encoded_line + state->pos,
513 (unsigned char *)p, to_encode);
514
515 left -= chunk;
516 done += chunk;
517 p += chunk;
518
519 if (chunk > 0) {
520 state->pos += B64_NLET_PER_CHUNK;
521 state->vpos += B64_NLET_PER_CHUNK;
522 }
523
524 }
525
526 *(state->encoded_line + state->pos) = '\0';
527
528 if (left < B64_ENC_CHUNK)
529 done += mime_save_reminder(state, p, left);
530
531 return done;
532 }
533
mime_max_mb_chunks_b64(char * charset)534 static size_t mime_max_mb_chunks_b64(char *charset)
535 {
536 if (!strieq(charset, "utf-8"))
537 return 0;
538
539 return ((MAX_UTF8_LEN - 1) + (B64_ENC_CHUNK - 1)) / B64_ENC_CHUNK;
540 }
541
mime_7bit_try(struct mime_word_enc_state * state,char * token,size_t len)542 static int mime_7bit_try(struct mime_word_enc_state *state,
543 char *token, size_t len)
544 {
545 size_t encoded_len;
546 char *p = NULL;
547 int i;
548
549 /* flushes reminder */
550 mime_switch_to_plain(state);
551
552 if (len > state->limit)
553 return ERROR;
554
555 encoded_len = mime_word_calc_len(state, token, len, &p);
556
557 if (state->vpos + encoded_len > state->limit)
558 mime_line_break(state);
559
560 for (i = 0; i < p - token; i++) {
561 if (token[i] != '\n')
562 continue;
563
564 /* replace original spaces if there is \n */
565 mime_strcat(state, " ");
566 len -= p - token;
567 token = p;
568 }
569
570 mime_strlcpy(state, token, len);
571 return OK;
572 }
573
574 #define QP_NLET_MAX 3 /* =XX */
575
mime_qp_calc_len(char * s,size_t len)576 static size_t mime_qp_calc_len(char *s, size_t len)
577 {
578 size_t sum = 0;
579
580 for (; len > 0; --len, s++) {
581 if (mime_qp_is_plain(*s))
582 sum += 1;
583 else
584 sum += QP_NLET_MAX;
585 }
586
587 return sum;
588 }
589
590 /*
591 * Encodes one octet, @out should point to a buffer enough to store 3
592 * bytes;
593 *
594 * Returns amount of bytes written
595 */
_mime_qp_encode_octet(char * out,unsigned char in)596 static size_t _mime_qp_encode_octet(char *out, unsigned char in)
597 {
598 int len;
599 char buf[QP_NLET_MAX + 1]; /* max encoded length =XX\0 */
600
601 if (mime_qp_is_plain(in)) {
602 *out = in;
603 return sizeof(*out);
604 }
605
606 len = snprintf(buf, sizeof(buf), "=%02X", in);
607 memcpy(out, buf, len);
608 return len;
609 }
610
mime_qp_encode_octet(struct mime_word_enc_state * state,unsigned char in)611 static void mime_qp_encode_octet(struct mime_word_enc_state *state,
612 unsigned char in)
613 {
614 size_t produced;
615
616 produced = _mime_qp_encode_octet(state->encoded_line + state->pos, in);
617 state->pos += produced;
618 state->vpos += produced;
619 }
620
mime_word_enc_qp_flush_mb(struct mime_word_enc_state * state,char * p)621 static size_t mime_word_enc_qp_flush_mb(struct mime_word_enc_state *state,
622 char *p)
623 {
624 size_t to_encode = mime_get_mb_tail(state, p);
625 int i;
626
627 for (i = 0; i < to_encode; i++)
628 mime_qp_encode_octet(state, p[i]);
629
630 return to_encode;
631 }
632
633 /*
634 * Encodes the word, extending the line if necessary
635 */
mime_word_enc_qp(struct mime_word_enc_state * state,char * p,size_t len)636 static size_t mime_word_enc_qp(struct mime_word_enc_state *state,
637 char *p, size_t len)
638 {
639 size_t limit;
640 size_t left;
641
642 limit = state->limit - strlen(MIME_HEADER_CODE_END);
643
644 for (left = len; left > 0; left--, p++) {
645 /* space to flush multibyte and encode current */
646 if (state->vpos + (state->max_mb_chunks + 1) * QP_NLET_MAX > limit) {
647 /* flush multibyte sequence */
648 left -= mime_word_enc_qp_flush_mb(state, p);
649 break;
650 }
651
652 mime_qp_encode_octet(state, *p);
653 }
654
655 *(state->encoded_line + state->pos) = '\0';
656
657 return len - left;
658 }
659
mime_max_mb_chunks_qp(char * charset)660 static size_t mime_max_mb_chunks_qp(char *charset)
661 {
662 if (!strieq(charset, "utf-8"))
663 return 0;
664
665 /* QP encoding chunk is one byte, encoded is QP_NLET_MAX */
666 return MAX_UTF8_LEN - 1;
667 }
668
mime_8bit_calc_len(char * s,size_t len)669 static size_t mime_8bit_calc_len(char *s, size_t len)
670 {
671 return len;
672 }
673
674 /*
675 * Encodes word if necessary (non-7bit) and adds to the header line.
676 * Makes extented line if needed.
677 * Takes token with the leading spaces when exist
678 */
mime_word_enc(struct mime_word_enc_state * state,char * token,size_t len)679 static void mime_word_enc(struct mime_word_enc_state *state,
680 char *token, size_t len)
681 {
682 char *p;
683 size_t left;
684 size_t consumed;
685
686 /* simple case, no encoding */
687 if (charset_is_7bit(token, len)) {
688
689 /* switches to plain mode with flushing reminder */
690 if (mime_7bit_try(state, token, len) == OK)
691 return;
692 }
693
694 /* encode if non-7bit or very long */
695 mime_word_calc_len(state, token, len, &p);
696
697 left = len;
698 if (!state->is_mime)
699 left -= (p - token); /* skip spaces */
700 else
701 p = token; /* encode with the spaces */
702
703 for (;;) {
704 if (!state->is_mime) {
705 mime_word_start(state);
706 }
707
708 consumed = state->encode(state, p, left);
709
710 p += consumed;
711 left -= consumed;
712 if (left == 0)
713 break;
714
715 mime_line_break(state);
716 }
717 }
718
mime_header_enc_start(struct mime_word_enc_state * state,char * charset,int type)719 static void mime_header_enc_start(struct mime_word_enc_state *state,
720 char *charset, int type)
721 {
722 size_t size = MIME_STRING_LIMIT + sizeof(MIME_HEADER_STR_DELIM); /* \0 counted */
723 char *p;
724
725 p = xmalloc(size);
726 *p = '\0';
727 state->size = size;
728 state->encoded_line = p;
729 state->charset = charset;
730 state->charset_len = strlen(charset);
731 state->inc = size - 1; /* only one \0 */
732 state->pos = 0;
733 state->vpos = 0;
734 state->limit = MIME_STRING_LIMIT;
735 state->rem_len = 0;
736 state->is_mime = false;
737
738 switch (type) {
739 case MIME_QP:
740 state->encoding = MIME_HEADER_CODE_MIDDLE_QP;
741 state->calc_len = mime_qp_calc_len;
742 state->encode = mime_word_enc_qp;
743 state->max_mb_chunks = mime_max_mb_chunks_qp(charset);
744 break;
745 case MIME_B64:
746 state->encoding = MIME_HEADER_CODE_MIDDLE_B64;
747 state->calc_len = mime_b64_calc_len;
748 state->encode = mime_word_enc_b64;
749 state->max_mb_chunks = mime_max_mb_chunks_b64(charset);
750 break;
751 default:
752 state->encoding = NULL;
753 state->calc_len = mime_8bit_calc_len;
754 state->encode = NULL;
755 }
756 }
757
mime_header_enc_end(struct mime_word_enc_state * state)758 static char *mime_header_enc_end(struct mime_word_enc_state *state)
759 {
760 size_t len;
761 size_t extra_len; /* for \n and \0 */
762
763 if (state->is_mime) {
764 len = strlen(MIME_HEADER_CODE_END);
765 if (state->rem_len)
766 len += B64_NLET_PER_CHUNK;
767
768 if (state->vpos + len > state->limit) {
769 mime_line_break(state);
770 mime_word_start(state);
771 }
772
773 mime_flush_reminder(state);
774 mime_word_end(state);
775 }
776
777 extra_len = strlen(MIME_HEADER_STR_DELIM) + 1;
778 mime_check_and_inc_size(state, extra_len);
779
780 if (state->encoded_line[state->pos - 1] != '\n') {
781 strcat(state->encoded_line, MIME_HEADER_STR_DELIM);
782 state->pos += strlen(MIME_HEADER_STR_DELIM);
783 }
784
785 state->encoded_line[state->pos++] = '\0';
786
787 return state->encoded_line;
788 }
789
790 /*
791 * Mime encodes 8bit header, splitting lines when necessary.
792 * src must be NUL-terminated
793 *
794 * Result is \n terminated
795 *
796 * Encoding is done word by word, 7bit words are not encoded
797 */
mime_header_enc(char ** dst,char * src,char * charset,int enc)798 int mime_header_enc(char **dst, char *src, char *charset, int enc)
799 {
800 struct mime_word_enc_state state;
801 char *token;
802 char *token_end;
803
804 debug(6, "MIME: %s: to encode (%s): %s", __func__, charset, src);
805
806 mime_header_enc_start(&state, charset, enc);
807
808 /* strtok does not work on RO strings */
809
810 token = src;
811 token_end = strchr(src, ' ');
812 if (token_end == NULL) {
813 fglog("ERROR: misformatted header: %s\n", src);
814 return ERROR;
815 }
816
817 while (token != NULL) {
818 mime_word_enc(&state, token, token_end - token);
819
820 token = token_end;
821
822 /* first skip spaces */
823 while ((*token_end == ' ' || *token_end == '\t' || *token == '\n')
824 && *token_end != '\0')
825 token_end++;
826
827 if (*token_end == '\0') {
828 token = NULL;
829 continue;
830 }
831
832 /* then find next space or EOL */
833 while (*token_end != ' ' && *token_end != '\t' && *token != '\n'
834 && *token_end != '\0')
835 token_end++;
836 }
837
838 mime_header_enc_end(&state);
839 *dst = state.encoded_line;
840
841 debug(6, "MIME: %s: encoded: %s", __func__, *dst);
842
843 return OK;
844 }
845
mime_b64_encode_tl(Textlist * in,Textlist * out)846 void mime_b64_encode_tl(Textlist * in, Textlist * out)
847 {
848 TextlistCharIterator iter;
849 /* + \n\0, no \r needed */
850 char buf[MIME_STRING_LIMIT + 2];
851 char ibuf[B64_ENC_CHUNK];
852 size_t len;
853 size_t pos;
854
855 tl_init(out);
856 tl_char_iterator_start(&iter, in);
857 pos = 0;
858
859 len = tl_char_iterator_next(&iter, ibuf, sizeof(ibuf));
860
861 while (len > 0) {
862 if (pos + B64_NLET_PER_CHUNK > MIME_STRING_LIMIT) {
863 buf[pos++] = '\n';
864 buf[pos++] = '\0';
865
866 tl_append(out, buf);
867 pos = 0;
868 }
869
870 mime_b64_encode_chunk(buf + pos, (unsigned char *)ibuf, len);
871 pos += B64_NLET_PER_CHUNK;
872 len = tl_char_iterator_next(&iter, ibuf, sizeof(ibuf));
873 }
874
875 buf[pos++] = '\n';
876 buf[pos++] = '\0';
877
878 tl_append(out, buf);
879 }
880
mime_b64_decode(char ** dst,char * src,size_t len)881 int mime_b64_decode(char **dst, char *src, size_t len)
882 {
883 char *buf;
884 size_t buflen;
885 size_t i;
886 int v1, v2, v3, v4;
887 char *d;
888 int rc = OK;
889
890 if (0 != (len % 4))
891 return ERROR;
892
893 buflen = ((len / 4) * 3) + 1;
894 buf = xmalloc(buflen);
895 memset(buf, 0, buflen);
896 i = 0;
897 d = buf;
898 while (len > i) {
899 v1 = mime_b64toint(src[i++]);
900 if (ERROR == v1) {
901 rc = ERROR;
902 break;
903 }
904
905 v2 = mime_b64toint(src[i++]);
906 if (ERROR == v2) {
907 rc = ERROR;
908 break;
909 }
910
911 v3 = mime_b64toint(src[i++]);
912 if (ERROR == v3) {
913 rc = ERROR;
914 break;
915 }
916
917 v4 = mime_b64toint(src[i++]);
918 if (ERROR == v4) {
919 rc = ERROR;
920 break;
921 }
922
923 *d++ = (v1 << 2) | (v2 >> 4);
924 if (0x40 > v3) {
925 *d++ = ((v2 << 4) & 0xf0) | (v3 >> 2);
926 if (0x40 > v4)
927 *d++ = ((v3 << 6) & 0xc0) | v4;
928 } else if (0x40 > v4) {
929 rc = ERROR;
930 break;
931 }
932 }
933
934 if (ERROR == rc)
935 xfree(buf);
936 else
937 *dst = buf;
938 return rc;
939 }
940
mime_qp_encode_tl(Textlist * in,Textlist * out)941 void mime_qp_encode_tl(Textlist * in, Textlist * out)
942 {
943 TextlistCharIterator iter;
944 /* + =\n\0 */
945 char buf[MIME_STRING_LIMIT + 4];
946 char ibuf[1];
947 size_t len;
948 size_t pos;
949 size_t len_encoded;
950
951 tl_init(out);
952 tl_char_iterator_start(&iter, in);
953 pos = 0;
954
955 len = tl_char_iterator_next(&iter, ibuf, sizeof(ibuf));
956
957 while (len > 0) {
958 if (pos + QP_NLET_MAX > MIME_STRING_LIMIT) {
959 buf[pos++] = '=';
960 buf[pos++] = '\n';
961 buf[pos++] = '\0';
962
963 tl_append(out, buf);
964 pos = 0;
965 }
966
967 len_encoded = _mime_qp_encode_octet(buf + pos, ibuf[0]);
968 pos += len_encoded;
969 len = tl_char_iterator_next(&iter, ibuf, sizeof(ibuf));
970 }
971
972 buf[pos++] = '\n';
973 buf[pos++] = '\0';
974
975 tl_append(out, buf);
976 }
977
mime_qp_decode(char ** dst,char * src,size_t len)978 int mime_qp_decode(char **dst, char *src, size_t len)
979 {
980 char *buf;
981 size_t i;
982 int vh, vl;
983 char *d;
984 char *p1, *p2;
985 int j;
986 int rc = OK;
987
988 p1 = src;
989 j = 0;
990 while (NULL != (p2 = strchr(p1, '_'))) {
991 p1 = ++p2;
992 j++;
993 }
994
995 buf = xmalloc(len + 1);
996 memset(buf, 0, len + 1);
997
998 i = 0;
999 d = buf;
1000 while (len > i) {
1001 if (src[i] == '\n' || src[i] == '\r') {
1002 *d = '\n';
1003 break;
1004 }
1005
1006 if ('_' == src[i]) {
1007 *d++ = ' ';
1008 i++;
1009 continue;
1010 }
1011
1012 if ('=' != src[i]) {
1013 *d++ = src[i++] & 0x7F;
1014 continue;
1015 }
1016
1017 i++;
1018
1019 if (src[i] == '\n' || src[i] == '\r') {
1020 *d = '\n';
1021 break;
1022 }
1023
1024 vh = mime_qptoint(src[i++]);
1025 if (ERROR == vh) {
1026 rc = ERROR;
1027 break;
1028 }
1029
1030 vl = mime_qptoint(src[i++]);
1031 if (ERROR == vl) {
1032 rc = ERROR;
1033 break;
1034 }
1035
1036 *d++ = (char)(((vh << 4) & 0xf0) | (vl & 0x0f));
1037 }
1038
1039 if (ERROR == rc)
1040 xfree(buf);
1041 else
1042 *dst = buf;
1043 return rc;
1044 }
1045
1046 /*
1047 * mime word is a mime encoded string, separated by space
1048 * from other mime words.
1049 * The function takes non-whitespace starting string
1050 * and decodes it from mime (quoted-printable or base64),
1051 * if it is properly encoded.
1052 * Leaves the string as is, if cannot recode.
1053 * Allocates buffer,
1054 * return the buffer,
1055 * the byte length of the decoded string (no final '\0'),
1056 * and the charset (from the mime info)
1057 */
mime_handle_mimed_word(char * s,char ** out,size_t * out_len,bool * is_mime,char * charset,size_t ch_size,char * to)1058 static int mime_handle_mimed_word(char *s, char **out, size_t *out_len,
1059 bool *is_mime,
1060 char *charset, size_t ch_size, char *to)
1061 {
1062 char *p;
1063 size_t len;
1064 size_t tmp_len;
1065 char *buf;
1066 char *beg;
1067 char *end;
1068 int (*decoder)(char **dst, char *src, size_t len) = NULL;
1069
1070 /* May be MIME (b64 or qp) */
1071 p = strchr(s + strlen(MIME_HEADER_CODE_START), '?');
1072 if (p == NULL)
1073 goto fallback;
1074
1075 tmp_len = p - (s + strlen(MIME_HEADER_CODE_START));
1076 if (tmp_len > MIME_MAX_ENC_LEN) {
1077 fglog("ERROR: charset name's length too long, %zd. Do not recode",
1078 tmp_len);
1079 goto fallback;
1080 }
1081
1082 tmp_len = MIN(tmp_len + 1, ch_size);
1083 snprintf(charset, tmp_len, "%s", s + strlen(MIME_HEADER_CODE_START));
1084
1085 /* Check if b64 or qp */
1086 if (strnieq(p,
1087 MIME_HEADER_CODE_MIDDLE_QP,
1088 strlen(MIME_HEADER_CODE_MIDDLE_QP))) {
1089 /* May be qp */
1090 decoder = mime_qp_decode;
1091 beg = p + strlen(MIME_HEADER_CODE_MIDDLE_QP);
1092
1093 } else if (strnieq(p,
1094 MIME_HEADER_CODE_MIDDLE_B64,
1095 strlen(MIME_HEADER_CODE_MIDDLE_B64))) {
1096 /* May be b64 */
1097 decoder = mime_b64_decode;
1098 beg = p + strlen(MIME_HEADER_CODE_MIDDLE_B64);
1099 } else {
1100 fglog
1101 ("ERROR: subject looks like mime, but does not have proper header");
1102 goto fallback;
1103 }
1104
1105 end = strchr(beg, '?');
1106
1107 if (end == NULL ||
1108 !strnieq(end, MIME_HEADER_CODE_END, strlen(MIME_HEADER_CODE_END))) {
1109 fglog
1110 ("ERROR: subject looks like mime, but does not have proper ending");
1111 goto fallback;
1112 }
1113 if (decoder(&buf, beg, end - beg) == ERROR) {
1114 fglog("ERROR: subject mime deconding failed");
1115 goto fallback;
1116 }
1117
1118 *out_len = strlen(buf);
1119 *out = buf;
1120 *is_mime = true;
1121 return end + strlen(MIME_HEADER_CODE_END) - s;
1122
1123 fallback:
1124 /*
1125 * Keep it as is.
1126 * Will work correctly with utf-8 plain headers, iconv and utf-8
1127 * internal charset.
1128 */
1129 debug(6, "Could not unmime subject '%s', leaving as is", s);
1130 len = strlen(s);
1131 *out = xmalloc(len);
1132 memcpy(*out, s, len); /* no '\0' */
1133 snprintf(charset, ch_size, "%s", to);
1134 *is_mime = false;
1135 *out_len = len;
1136
1137 return len;
1138 }
1139
mime_handle_plain_word(char * s,char ** out,size_t * out_len,char * charset,size_t ch_size,char * to,char * ch_fallback,RFCHeader * header)1140 static int mime_handle_plain_word(char *s, char **out, size_t *out_len,
1141 char *charset, size_t ch_size, char *to,
1142 char *ch_fallback, RFCHeader *header)
1143 {
1144 char *plain_charset = ch_fallback;
1145 char *mime_charset = NULL;
1146 char *p;
1147 size_t len;
1148 char *res;
1149
1150 /*
1151 * If there are several words, handle only one.
1152 * Reading code should combine splitted lines, so no \n.
1153 * TODO: \t can be checked as well
1154 */
1155 p = strchr(s, ' ');
1156 if (p != NULL)
1157 len = p - s;
1158 else
1159 len = strlen(s);
1160
1161 /*
1162 * There must be no plain 8 bit headers except utf-8.
1163 * If there are, do some heuristics:
1164 * - if body charset supplied and is not 7 bit us-ascii, use that;
1165 * - othewise it's broken most probably, assume the precompiled
1166 * default.
1167 */
1168 if (!charset_is_7bit(s, len) && charset_is_valid_utf8(s, len)) {
1169 plain_charset = "utf-8";
1170 } else {
1171 mime_charset = mime_get_main_charset(header);
1172
1173 if ((mime_charset != NULL) &&
1174 (strcmp(mime_charset, CHARSET_STD7BIT) != 0)) {
1175 plain_charset = mime_charset;
1176 }
1177 }
1178
1179 strncpy(charset, plain_charset, ch_size - 1);
1180 charset[ch_size - 1] = '\0';
1181
1182 free(mime_charset);
1183
1184 res = xmalloc(len + 1);
1185 str_copy(res, len + 1, s);
1186
1187 *out_len = len;
1188 *out = res;
1189
1190 return *out_len;
1191 }
1192
mime_handle_brace(char * s,char ** out,size_t * out_len,bool * is_mime,char * charset,size_t ch_size,char * to)1193 static int mime_handle_brace(char *s, char **out, size_t *out_len,
1194 bool *is_mime, char *charset, size_t ch_size,
1195 char *to)
1196 {
1197 char *b;
1198 char *brace = "(";
1199
1200 /* client code requires malloc()ed buffer */
1201 b = strsave(brace);
1202 snprintf(charset, ch_size, "%s", "us-ascii");
1203
1204 *out = b;
1205 *out_len = strlen(b);
1206 *is_mime = false;
1207
1208 return strlen(brace);
1209 }
1210
1211 /*
1212 * Fetches the mime word from the string. It can be mime-encoded
1213 * or plain part, separated with "space", or mime in comment (rfc2047 5(2))
1214 * _Must_ start with non-space.
1215 * For mime-encoded parts decode it and fetch the charset.
1216 * For plain parts copy them as is, take the global charset.
1217 * Allocates the final buffer.
1218 * @returns the number of bytes, handled in the source string.
1219 */
mime_handle_word(char * s,char ** out,size_t * out_len,bool * is_mime,char * charset,size_t ch_size,char * to,char * ch_fallback,RFCHeader * header)1220 static int mime_handle_word(char *s, char **out, size_t *out_len,
1221 bool *is_mime, char *charset, size_t ch_size,
1222 char *to, char *ch_fallback, RFCHeader *header)
1223 {
1224 /* mime word can be inside "comment" not separated by whitespace */
1225 if (strnieq(s,
1226 "(" MIME_HEADER_CODE_START,
1227 strlen("(" MIME_HEADER_CODE_START))) {
1228 return mime_handle_brace(s, out, out_len,
1229 is_mime, charset, ch_size, to);
1230 }
1231
1232 if (strnieq(s, MIME_HEADER_CODE_START, strlen(MIME_HEADER_CODE_START))) {
1233 return mime_handle_mimed_word(s,
1234 out, out_len,
1235 is_mime, charset, ch_size, to);
1236 }
1237
1238 *is_mime = false;
1239 return mime_handle_plain_word(s, out, out_len,
1240 charset, ch_size, to, ch_fallback, header);
1241 }
1242
1243 /* source @s must be '\0'-terminated */
mime_header_dec(char * d,size_t d_max,char * s,char * to,char * ch_fallback,RFCHeader * header)1244 char *mime_header_dec(char *d, size_t d_max, char *s, char *to,
1245 char *ch_fallback, RFCHeader *header)
1246 {
1247 char *save_d = d;
1248 bool is_mime = false;
1249 bool is_prev_mime = false;
1250 char *buf;
1251 size_t len;
1252 char charset[MIME_MAX_ENC_LEN + 1];
1253 int rc;
1254 size_t d_left = d_max - 1;
1255 size_t s_handled;
1256 char *recoded;
1257 size_t recoded_len;
1258
1259 while (d_left != 0 && *s != '\0') {
1260 if (isspace(*s)) {
1261 /* just skip spaces between mime words */
1262 if (!is_mime) {
1263 *d++ = *s;
1264 d_left--;
1265 }
1266 s++;
1267 continue;
1268 }
1269
1270 is_prev_mime = is_mime;
1271
1272 /* it means mime "word" -- encoded or plain part */
1273 s_handled = mime_handle_word(s, &buf, &len, &is_mime,
1274 charset, sizeof(charset), to, ch_fallback,
1275 header);
1276
1277 /* keep at least one space between mime and non-mime words */
1278 if (is_prev_mime
1279 && !is_mime
1280 /* and not comment's `()` end */
1281 && !((s_handled == 1) && (*s == ')'))) {
1282
1283 *d++ = ' ';
1284 d_left--;
1285
1286 if (d_left == 0)
1287 break;
1288 }
1289
1290 debug(6, "header charset: %s", charset);
1291 rc = charset_recode_buf(&recoded, &recoded_len, buf, len, charset, to);
1292 if (rc != OK) {
1293 debug(6, "Could not recode header %s", buf);
1294 goto out;
1295 }
1296
1297 free(buf);
1298
1299 if (recoded_len > d_left) {
1300 fglog("ERROR: header buffer too small");
1301 recoded_len = d_left;
1302 }
1303
1304 memcpy(d, recoded, recoded_len);
1305 free(recoded);
1306
1307 d += recoded_len;
1308 d_left -= recoded_len;
1309
1310 s += s_handled;
1311 }
1312
1313 out:
1314 *d = '\0';
1315 return save_d;
1316 }
1317
1318 /* takes space-stripped string */
1319 /* Now is not used */
1320 /*
1321
1322 static char* mime_fetch_attribute(char *str, char *attr)
1323 {
1324 int attr_len = strlen(attr);
1325 char *tmp_str = NULL;
1326
1327 do
1328 {
1329 if(strnieq(str, attr, attr_len))
1330 {
1331 tmp_str = xmalloc(attr_len + 1);
1332 strncpy(tmp_str, str, attr_len);
1333 tmp_str[attr_len] = '\0';
1334 break;
1335 }
1336 str = strchr(str, ';');
1337 if(str != NULL)
1338 str++;
1339 } while (str != NULL);
1340
1341 return tmp_str;
1342 }
1343 */
1344
mime_parse_header(Textlist * line,char * str)1345 static int mime_parse_header(Textlist * line, char *str)
1346 {
1347 char *p = NULL;
1348
1349 if (line == NULL || str == NULL)
1350 return ERROR;
1351
1352 debug(6, "Parsing header %s", str);
1353 for (p = strtok(str, ";"); p != NULL; p = strtok(NULL, ";")) {
1354 debug(6, "Recording header attribute %s", p);
1355 p = strip_space(p);
1356 tl_append(line, p);
1357 }
1358 return OK;
1359 }
1360
mime_attr_value(char * str)1361 static char *mime_attr_value(char *str)
1362 {
1363 char *p, *q = NULL;
1364
1365 if (str == NULL)
1366 return NULL;
1367
1368 p = strchr(str, '=');
1369 if (p != NULL) {
1370 if (*(++p) == '\"')
1371 p++;
1372 for (q = p; *q != '\0'; q++)
1373 if (*q == '\"' || is_space(*q))
1374 break;
1375 *q = 0;
1376 p = strsave(p);
1377 }
1378 return p;
1379 }
1380
1381 /*
1382 * Return MIME header
1383 */
1384
get_mime_disposition(char * ver,char * type,char * enc,char * disp)1385 static MIMEInfo *get_mime_disposition(char *ver, char *type, char *enc,
1386 char *disp)
1387 {
1388 MIMEInfo *mime;
1389
1390 Textlist header_line = { NULL, NULL };
1391 Textline *tmp_line;
1392 char *tmp_str = NULL;
1393
1394 mime = (MIMEInfo *) s_alloc(sizeof(*mime));
1395
1396 mime->version = ver;
1397 mime->type = type;
1398 mime->type_type = NULL;
1399 mime->type_charset = NULL;
1400 mime->type_boundary = NULL;
1401 mime->encoding = enc;
1402 mime->disposition = disp;
1403 mime->disposition_filename = NULL;
1404
1405 if (type != NULL) {
1406 tmp_str = s_copy(type);
1407 mime_parse_header(&header_line, tmp_str);
1408 if (header_line.first != NULL && header_line.first->line != NULL)
1409 mime->type_type = s_copy(header_line.first->line);
1410
1411 tmp_line = tl_get(&header_line, "charset", strlen("charset"));
1412 if (tmp_line != NULL) {
1413 tmp_str = mime_attr_value(tmp_line->line);
1414 mime->type_charset = s_copy(tmp_str);
1415 xfree(tmp_str);
1416 }
1417 tmp_line = tl_get(&header_line, "boundary", strlen("boundary"));
1418 if (tmp_line != NULL) {
1419 tmp_str = mime_attr_value(tmp_line->line);
1420 mime->type_boundary = s_copy(tmp_str);
1421 xfree(tmp_str);
1422 }
1423 tl_clear(&header_line);
1424 }
1425
1426 if (disp != NULL) {
1427 tmp_str = s_copy(disp);
1428 /* tl_append() inside makes copies */
1429 mime_parse_header(&header_line, tmp_str);
1430
1431 tmp_line = tl_get(&header_line, "filename", strlen("filename"));
1432 if (tmp_line != NULL) {
1433 tmp_str = mime_attr_value(tmp_line->line);
1434 mime->disposition_filename = s_copy(tmp_str);
1435 xfree(tmp_str);
1436 }
1437
1438 tl_clear(&header_line);
1439 }
1440
1441 debug(6, "RFC MIME-Version: %s",
1442 mime->version ? mime->version : "-NONE-");
1443 debug(6, "RFC Content-Type: %s",
1444 mime->type ? mime->type : "-NONE-");
1445 debug(6, " type = %s",
1446 mime->type_type ? mime->type_type : "");
1447 debug(6, " charset = %s",
1448 mime->type_charset ? mime->type_charset : "");
1449 debug(6, " boundary = %s",
1450 mime->type_boundary ? mime->type_boundary : "");
1451 debug(6, "RFC Content-Transfer-Encoding: %s",
1452 mime->encoding ? mime->encoding : "-NONE-");
1453 debug(6, "RFC Content-Disposition: %s",
1454 mime->disposition ? mime->disposition : "-NONE-");
1455 debug(6, " filename = %s",
1456 mime->disposition_filename ? mime->disposition_filename : "");
1457
1458 return mime;
1459 }
1460
get_mime(char * ver,char * type,char * enc)1461 MIMEInfo *get_mime(char *ver, char *type, char *enc)
1462 {
1463 return get_mime_disposition(ver, type, enc, NULL);
1464 }
1465
1466 /* TODO smth like s_header_getcomplete() */
1467
get_mime_from_header(RFCHeader * header)1468 MIMEInfo *get_mime_from_header(RFCHeader *header)
1469 {
1470 return get_mime_disposition(header_get(header, "Mime-Version"),
1471 header_get(header, "Content-Type"),
1472 header_get(header,
1473 "Content-Transfer-Encoding"),
1474 header_get(header,
1475 "Content-Disposition"));
1476 }
1477
1478 /* Garantee [^\r]\n\0 in the end of string since our
1479 output subroutine adds '\r'
1480 str should have space for 1 extra symbol */
1481
mime_debody_flush_str(Textlist * body,char * str)1482 static char *mime_debody_flush_str(Textlist * body, char *str)
1483 {
1484 int len;
1485 if (str == NULL || body == NULL)
1486 return NULL;
1487
1488 len = strlen(str);
1489 if (len < 2)
1490 return NULL;
1491
1492 if (str[len - 2] == '\r') {
1493 str[len - 2] = '\n';
1494 str[len - 1] = '\0';
1495 } else if (str[len - 1] != '\n') {
1496 str[len] = '\n';
1497 str[len + 1] = '\0';
1498 }
1499
1500 tl_append(body, str);
1501 str[0] = '\0';
1502
1503 return str;
1504 }
1505
mime_debody_qp(Textlist * body)1506 static Textlist *mime_debody_qp(Textlist * body)
1507 {
1508 char *dec_str, *tmp_str;
1509 long len;
1510
1511 Textlist *dec_body;
1512 Textlist *full_line;
1513
1514 Textline *line, *subline;
1515
1516 dec_body = xmalloc(sizeof(*dec_body));
1517 memset(dec_body, 0, sizeof(*dec_body));
1518
1519 full_line = xmalloc(sizeof(*full_line));
1520 memset(full_line, 0, sizeof(*full_line));
1521
1522 for (line = body->first; line != NULL; line = line->next) {
1523 len = strlen(line->line);
1524 if (mime_qp_decode(&dec_str, line->line, len) == ERROR)
1525 goto fail;
1526
1527 strip_crlf(dec_str);
1528 tl_append(full_line, dec_str);
1529 xfree(dec_str);
1530
1531 if (streq(line->line + (len - 2), "=\n")
1532 || streq(line->line + (len - 3), "=\r\n"))
1533 continue;
1534
1535 len = tl_size(full_line);
1536
1537 tmp_str = xmalloc(len + 2);
1538 memset(tmp_str, 0, len + 2);
1539
1540 for (subline = full_line->first; subline != NULL;
1541 subline = subline->next)
1542 strcat(tmp_str, subline->line);
1543
1544 tmp_str[len] = '\n';
1545
1546 tl_append(dec_body, tmp_str);
1547 xfree(tmp_str);
1548 tl_clear(full_line);
1549 }
1550
1551 tl_clear(full_line);
1552 xfree(full_line);
1553 return dec_body;
1554
1555 fail:
1556 fglog("ERROR: decoding quoted-printable, skipping section");
1557 tl_clear(dec_body);
1558 xfree(dec_body);
1559 tl_clear(full_line);
1560 xfree(full_line);
1561 return NULL;
1562 }
1563
1564 /* decode base64 body */
1565
mime_debody_base64(Textlist * body)1566 static Textlist *mime_debody_base64(Textlist * body)
1567 {
1568 char *out_str;
1569 char *dec_str;
1570 char *out_ptr;
1571 char *dec_ptr, *dec_prev_ptr;
1572 char *to_free;
1573
1574 Textlist *dec_body = NULL;
1575 Textline *line;
1576
1577 int max_len = MAX_LINE_LENGTH;
1578 int left;
1579 int enc_len = 0, dec_str_len = 0;
1580
1581 dec_body = xmalloc(sizeof(*dec_body));
1582 memset(dec_body, 0, sizeof(*dec_body));
1583
1584 /* len + \n + \0 */
1585 out_ptr = out_str = xmalloc(max_len + 2);
1586 out_str[0] = '\0';
1587 left = max_len;
1588
1589 for (line = body->first; line != NULL; line = line->next) {
1590
1591 enc_len = (xstrnlen(line->line, MIME_ENC_STRING_LIMIT) / 4) * 4;
1592 if (mime_b64_decode(&dec_str, line->line, enc_len) == ERROR)
1593 goto exit;
1594
1595 to_free = dec_str;
1596
1597 dec_prev_ptr = dec_str;
1598 while ((dec_ptr = strchr(dec_prev_ptr, '\n')) != NULL) {
1599 dec_str_len = dec_ptr - dec_prev_ptr + 1;
1600 if (dec_str_len <= left) {
1601 strncat(out_str, dec_prev_ptr, dec_str_len);
1602 dec_prev_ptr = dec_ptr + 1;
1603 } else {
1604 strncat(out_str, dec_prev_ptr, left);
1605 dec_prev_ptr += left;
1606 }
1607
1608 mime_debody_flush_str(dec_body, out_str);
1609 left = max_len;
1610 }
1611
1612 dec_str_len = strlen(dec_str) - (dec_prev_ptr - dec_str);
1613 do {
1614 if (dec_str_len < left) {
1615 strncat(out_str, dec_prev_ptr, dec_str_len);
1616 out_ptr += dec_str_len;
1617 left -= dec_str_len;
1618 dec_str_len = 0;
1619 } else {
1620 strncat(out_str, dec_prev_ptr, left);
1621 dec_prev_ptr += left;
1622 dec_str_len -= left;
1623
1624 mime_debody_flush_str(dec_body, out_str);
1625 left = max_len;
1626
1627 }
1628 } while (dec_str_len > 0);
1629
1630 free(to_free);
1631 }
1632 if (left != max_len)
1633 mime_debody_flush_str(dec_body, out_str);
1634
1635 exit:
1636 xfree(out_str);
1637 return dec_body;
1638 }
1639
1640 static Textlist *mime_debody_section(Textlist *body, RFCHeader* header,
1641 char *to, char *ch_fallback);
1642
mime_debody_multipart(Textlist * body,MIMEInfo * mime,char * to,char * ch_fallback)1643 static Textlist *mime_debody_multipart(Textlist * body, MIMEInfo * mime,
1644 char *to, char *ch_fallback)
1645 {
1646 RFCHeader *header;
1647 Textline *line;
1648
1649 Textlist *dec_body, *ptr_body, tmp_body = { NULL, NULL };
1650
1651 char *boundary = NULL;
1652 char *fin_boundary = NULL;
1653
1654 if (mime->type_boundary == NULL)
1655 return NULL;
1656
1657 dec_body = xmalloc(sizeof(*dec_body));
1658 dec_body->first = NULL;
1659 dec_body->last = NULL;
1660
1661 /* --boundary\0 */
1662 boundary = xmalloc(strlen(mime->type_boundary) + 3);
1663 strcpy(boundary, "--");
1664 strcat(boundary, mime->type_boundary);
1665
1666 /* --boundary--\0 */
1667 fin_boundary = xmalloc(strlen(boundary) + 3);
1668 strcpy(fin_boundary, boundary);
1669 strcat(fin_boundary, "--");
1670
1671 for (line = body->first; line != NULL; line = line->next)
1672 if (strneq(line->line, boundary, strlen(boundary)))
1673 break;
1674
1675 for (line = line->next; line != NULL; line = line->next) {
1676 if (!strneq(line->line, boundary, strlen(boundary))) {
1677 tl_append(&tmp_body, line->line);
1678 } else {
1679 header = header_read_list(&tmp_body);
1680 header_delete_from_body(&tmp_body);
1681
1682 ptr_body = mime_debody_section(&tmp_body, header, to, ch_fallback);
1683 if (ptr_body != NULL) {
1684 tl_addtl_move(dec_body, ptr_body);
1685 if (ptr_body != &tmp_body)
1686 xfree(ptr_body);
1687 }
1688 tl_clear(&tmp_body);
1689 header_free(header);
1690
1691 if (strneq(line->line, fin_boundary, strlen(fin_boundary)))
1692 break;
1693 }
1694 }
1695
1696 xfree(boundary);
1697 xfree(fin_boundary);
1698 return dec_body;
1699 }
1700
mime_decharset_section(Textlist * body,MIMEInfo * mime,char * to)1701 static int mime_decharset_section(Textlist * body, MIMEInfo * mime, char *to)
1702 {
1703
1704 char *res_str = NULL;
1705 size_t res_len, src_len;
1706 int rc;
1707 Textline *line;
1708 char *tmp;
1709
1710 if (body == NULL || mime == NULL)
1711 return ERROR;
1712
1713 /* if no charset, us-ascii assumed */
1714 if (mime->type_charset == NULL || strieq(mime->type_charset, to))
1715 return OK;
1716
1717 for (line = body->first; line != NULL; line = line->next) {
1718 src_len = strlen(line->line) + 1; /* recode final \0 also */
1719
1720 rc = charset_recode_buf(&res_str, &res_len, line->line, src_len,
1721 mime->type_charset, to);
1722
1723 if (rc == ERROR) {
1724 goto exit; /* something really wrong */
1725 }
1726
1727 tmp = line->line;
1728 line->line = res_str;
1729 free(tmp);
1730 }
1731
1732 rc = OK;
1733 exit:
1734 return rc;
1735 }
1736
mime_debody_section(Textlist * body,RFCHeader * header,char * to,char * ch_fallback)1737 static Textlist *mime_debody_section(Textlist *body, RFCHeader *header,
1738 char *to, char *ch_fallback)
1739 {
1740
1741 Textlist *dec_body;
1742 MIMEInfo *mime;
1743
1744 mime = get_mime_from_header(header);
1745 if (mime->type_charset == NULL)
1746 mime->type_charset = ch_fallback;
1747
1748 if ((mime->type_type == NULL) || strieq(mime->type_type, "text/plain")
1749 || (mime->type == NULL)) {
1750 if ((mime->encoding == NULL) || strieq(mime->encoding, "8bit")
1751 || strieq(mime->encoding, "7bit")) {
1752
1753 dec_body = tl_dup(body);
1754
1755 } else if (strieq(mime->encoding, "base64")) {
1756 dec_body = mime_debody_base64(body);
1757 } else if (strieq(mime->encoding, "quoted-printable")) {
1758 dec_body = mime_debody_qp(body);
1759 } else {
1760 fglog("WARNING: Skipped unsupported transfer encoding %s",
1761 mime->encoding);
1762 dec_body = NULL;
1763 goto exit;
1764 }
1765 mime_decharset_section(dec_body, mime, to);
1766 } else if(strnieq(mime->type_type,
1767 "multipart/", sizeof("multipart/") - 1)) {
1768 dec_body = mime_debody_multipart(body, mime, to, ch_fallback);
1769 } else {
1770 fglog("WARNING: Skipped unsupported mime type %s", mime->type_type);
1771 dec_body = NULL;
1772 goto exit;
1773 }
1774 exit:
1775 mime_free();
1776 return dec_body;
1777
1778 }
1779
mime_body_dec(Textlist * body,RFCHeader * header,char * to,char * ch_fallback)1780 Textlist *mime_body_dec(Textlist *body, RFCHeader *header,
1781 char *to, char *ch_fallback)
1782 {
1783 Textlist *dec_body;
1784
1785 dec_body = mime_debody_section(body, header, to, ch_fallback);
1786 if (dec_body == NULL)
1787 return NULL;
1788
1789 if (dec_body->first == NULL) {
1790 fglog("ERROR: could not decode mime body");
1791 xfree(dec_body);
1792 return NULL;
1793 }
1794
1795 return dec_body;
1796 }
1797
mime_free(void)1798 void mime_free(void)
1799 {
1800 MIMEInfo *mime, *n;
1801
1802 for (mime = mime_list; mime; mime = n) {
1803 n = mime->next;
1804
1805 xfree(mime->version);
1806 xfree(mime->type);
1807 xfree(mime->type_type);
1808 xfree(mime->type_charset);
1809 xfree(mime->type_boundary);
1810 xfree(mime->encoding);
1811 xfree(mime->disposition);
1812 xfree(mime->disposition_filename);
1813 xfree(mime);
1814 }
1815
1816 }
1817