Add minimalistic protected-headers support
[gpgol.git] / src / rfc2047parse.c
1 /* @file rfc2047parse.c
2  * @brief Parsercode for rfc2047
3  *
4  * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
5  * Software engineering by Intevation GmbH
6  *
7  * This file is part of GpgOL.
8  *
9  * GpgOL is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * GpgOL is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, see <http://www.gnu.org/licenses/>.
21  */
22
23 /* This code is heavily based (mostly verbatim copy with glib
24  *  dependencies removed) on GMime rev 496313fb
25  * modified by aheinecke@intevation.de
26  *
27  * Copyright (C) 2000-2014 Jeffrey Stedfast
28  *
29  *  This library is free software; you can redistribute it and/or
30  *  modify it under the terms of the GNU Lesser General Public License
31  *  as published by the Free Software Foundation; either version 2.1
32  *  of the License, or (at your option) any later version.
33  */
34
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
38
39 #include <stdbool.h>
40 #include "common_indep.h"
41 #include <ctype.h>
42
43 #ifdef HAVE_W32_SYSTEM
44 # include "mlang-charset.h"
45 #endif
46
47 #include "gmime-table-private.h"
48
49 /* mabye we need this at some point later? */
50 #define G_MIME_RFC2047_WORKAROUNDS 1
51
52
53 static unsigned char gmime_base64_rank[256] = {
54     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
55     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
56     255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
57     52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,  0,255,255,
58     255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
59     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
60     255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
61     41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
62     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
63     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
64     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
65     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
66     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
67     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
68     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
69     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
70 };
71
72 typedef struct _rfc2047_token {
73     struct _rfc2047_token *next;
74     char *charset;
75     const char *text;
76     size_t length;
77     char encoding;
78     char is_8bit;
79 } rfc2047_token;
80
81 static rfc2047_token *
82 rfc2047_token_new (const char *text, size_t len)
83 {
84   rfc2047_token *token;
85
86   TSTART;
87   token = xmalloc (sizeof (rfc2047_token));
88   memset (token, 0, sizeof (rfc2047_token));
89   token->length = len;
90   token->text = text;
91
92   TRETURN token;
93 }
94
95 static rfc2047_token *
96 rfc2047_token_new_encoded_word (const char *word, size_t len)
97 {
98   rfc2047_token *token;
99   const char *payload;
100   char *charset;
101   const char *inptr;
102   const char *tmpchar;
103   char *buf, *lang;
104   char encoding;
105   size_t n;
106
107   TSTART;
108   /* check that this could even be an encoded-word token */
109   if (len < 7 || strncmp (word, "=?", 2) != 0 || strncmp (word + len - 2, "?=", 2) != 0)
110     {
111       TRETURN NULL;
112     }
113
114   /* skip over '=?' */
115   inptr = word + 2;
116   tmpchar = inptr;
117
118   if (*tmpchar == '?' || *tmpchar == '*') {
119       /* this would result in an empty charset */
120       TRETURN NULL;
121   }
122
123   /* skip to the end of the charset */
124   if (!(inptr = memchr (inptr, '?', len - 2)) || inptr[2] != '?')
125     {
126       TRETURN NULL;
127     }
128
129   /* copy the charset into a buffer */
130   n = (size_t) (inptr - tmpchar);
131   buf = xmalloc (n + 1);
132   memcpy (buf, tmpchar, n);
133   buf[n] = '\0';
134   charset = buf;
135
136   /* rfc2231 updates rfc2047 encoded words...
137    * The ABNF given in RFC 2047 for encoded-words is:
138    *   encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
139    * This specification changes this ABNF to:
140    *   encoded-word := "=?" charset ["*" language] "?" encoding "?" encoded-text "?="
141    */
142
143   /* trim off the 'language' part if it's there... */
144   if ((lang = strchr (charset, '*')))
145     *lang = '\0';
146
147   /* skip over the '?' */
148   inptr++;
149
150   /* make sure the first char after the encoding is another '?' */
151   if (inptr[1] != '?')
152     {
153       TRETURN NULL;
154     }
155
156   switch (*inptr++) {
157     case 'B': case 'b':
158       encoding = 'B';
159       break;
160     case 'Q': case 'q':
161       encoding = 'Q';
162       break;
163     default:
164       TRETURN NULL;
165   }
166
167   /* the payload begins right after the '?' */
168   payload = inptr + 1;
169
170   /* find the end of the payload */
171   inptr = word + len - 2;
172
173   /* make sure that we don't have something like: =?iso-8859-1?Q?= */
174   if (payload > inptr)
175     {
176       TRETURN NULL;
177     }
178
179   token = rfc2047_token_new (payload, inptr - payload);
180   token->charset = charset;
181   token->encoding = encoding;
182
183   TRETURN token;
184 }
185
186 static void
187 rfc2047_token_free (rfc2047_token * tok)
188 {
189   TSTART;
190   if (!tok)
191     {
192       TRETURN;
193     }
194   xfree (tok->charset);
195   xfree (tok);
196   TRETURN;
197 }
198
199 static rfc2047_token *
200 tokenize_rfc2047_phrase (const char *in, size_t *len)
201 {
202   bool enable_rfc2047_workarounds = G_MIME_RFC2047_WORKAROUNDS;
203   rfc2047_token list, *lwsp, *token, *tail;
204   register const char *inptr = in;
205   bool encoded = false;
206   const char *text, *word;
207   bool ascii;
208   size_t n;
209
210   TSTART;
211   tail = (rfc2047_token *) &list;
212   list.next = NULL;
213   lwsp = NULL;
214
215   while (*inptr != '\0') {
216       text = inptr;
217       while (is_lwsp (*inptr))
218         inptr++;
219
220       if (inptr > text)
221         lwsp = rfc2047_token_new (text, inptr - text);
222       else
223         lwsp = NULL;
224
225       word = inptr;
226       ascii = true;
227       if (is_atom (*inptr)) {
228           if (enable_rfc2047_workarounds) {
229               /* Make an extra effort to detect and
230                * separate encoded-word tokens that
231                * have been merged with other
232                * words. */
233
234               if (!strncmp (inptr, "=?", 2)) {
235                   inptr += 2;
236
237                   /* skip past the charset (if one is even declared, sigh) */
238                   while (*inptr && *inptr != '?') {
239                       ascii = ascii && is_ascii (*inptr);
240                       inptr++;
241                   }
242
243                   /* sanity check encoding type */
244                   if (inptr[0] != '?' || !strchr ("BbQq", inptr[1]) || inptr[2] != '?')
245                     goto non_rfc2047;
246
247                   inptr += 3;
248
249                   /* find the end of the rfc2047 encoded word token */
250                   while (*inptr && strncmp (inptr, "?=", 2) != 0) {
251                       ascii = ascii && is_ascii (*inptr);
252                       inptr++;
253                   }
254
255                   if (*inptr == '\0') {
256                       /* didn't find an end marker... */
257                       inptr = word + 2;
258                       ascii = true;
259
260                       goto non_rfc2047;
261                   }
262
263                   inptr += 2;
264               } else {
265 non_rfc2047:
266                   /* stop if we encounter a possible rfc2047 encoded
267                    * token even if it's inside another word, sigh. */
268                   while (is_atom (*inptr) && strncmp (inptr, "=?", 2) != 0)
269                     inptr++;
270               }
271           } else {
272               while (is_atom (*inptr))
273                 inptr++;
274           }
275
276           n = (size_t) (inptr - word);
277           if ((token = rfc2047_token_new_encoded_word (word, n))) {
278               /* rfc2047 states that you must ignore all
279                * whitespace between encoded words */
280               if (!encoded && lwsp != NULL) {
281                   tail->next = lwsp;
282                   tail = lwsp;
283               } else if (lwsp != NULL) {
284                   rfc2047_token_free (lwsp);
285               }
286
287               tail->next = token;
288               tail = token;
289
290               encoded = true;
291           } else {
292               /* append the lwsp and atom tokens */
293               if (lwsp != NULL) {
294                   tail->next = lwsp;
295                   tail = lwsp;
296               }
297
298               token = rfc2047_token_new (word, n);
299               token->is_8bit = ascii ? 0 : 1;
300               tail->next = token;
301               tail = token;
302
303               encoded = false;
304           }
305       } else {
306           /* append the lwsp token */
307           if (lwsp != NULL) {
308               tail->next = lwsp;
309               tail = lwsp;
310           }
311
312           ascii = true;
313           while (*inptr && !is_lwsp (*inptr) && !is_atom (*inptr)) {
314               ascii = ascii && is_ascii (*inptr);
315               inptr++;
316           }
317
318           n = (size_t) (inptr - word);
319           token = rfc2047_token_new (word, n);
320           token->is_8bit = ascii ? 0 : 1;
321
322           tail->next = token;
323           tail = token;
324
325           encoded = false;
326       }
327   }
328
329   *len = (size_t) (inptr - in);
330
331   TRETURN list.next;
332 }
333
334 static void
335 rfc2047_token_list_free (rfc2047_token * tokens)
336 {
337   TSTART;
338   rfc2047_token * cur = tokens;
339   while (cur)
340     {
341       rfc2047_token *next = cur->next;
342       rfc2047_token_free (cur);
343       cur = next;
344     }
345   TRETURN;
346 }
347
348 /* this decodes rfc2047's version of quoted-printable */
349 static size_t
350 quoted_decode (const unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
351 {
352   register const unsigned char *inptr;
353   register unsigned char *outptr;
354   const unsigned char *inend;
355   unsigned char c, c1;
356   unsigned int saved;
357   int need;
358
359   TSTART;
360   if (len == 0)
361     {
362       TRETURN 0;
363     }
364
365   inend = in + len;
366   outptr = out;
367   inptr = in;
368
369   need = *state;
370   saved = *save;
371
372   if (need > 0) {
373       if (isxdigit ((int) *inptr)) {
374           if (need == 1) {
375               c = toupper ((int) (saved & 0xff));
376               c1 = toupper ((int) *inptr++);
377               saved = 0;
378               need = 0;
379
380               goto decode;
381           }
382
383           saved = 0;
384           need = 0;
385
386           goto equals;
387       }
388
389       /* last encoded-word ended in a malformed quoted-printable sequence */
390       *outptr++ = '=';
391
392       if (need == 1)
393         *outptr++ = (char) (saved & 0xff);
394
395       saved = 0;
396       need = 0;
397   }
398
399   while (inptr < inend) {
400       c = *inptr++;
401       if (c == '=') {
402 equals:
403           if (inend - inptr >= 2) {
404               if (isxdigit ((int) inptr[0]) && isxdigit ((int) inptr[1])) {
405                   c = toupper (*inptr++);
406                   c1 = toupper (*inptr++);
407 decode:
408                   *outptr++ = (((c >= 'A' ? c - 'A' + 10 : c - '0') & 0x0f) << 4)
409                     | ((c1 >= 'A' ? c1 - 'A' + 10 : c1 - '0') & 0x0f);
410               } else {
411                   /* malformed quoted-printable sequence? */
412                   *outptr++ = '=';
413               }
414           } else {
415               /* truncated payload, maybe it was split across encoded-words? */
416               if (inptr < inend) {
417                   if (isxdigit ((int) *inptr)) {
418                       saved = *inptr;
419                       need = 1;
420                       break;
421                   } else {
422                       /* malformed quoted-printable sequence? */
423                       *outptr++ = '=';
424                   }
425               } else {
426                   saved = 0;
427                   need = 2;
428                   break;
429               }
430           }
431       } else if (c == '_') {
432           /* _'s are an rfc2047 shortcut for encoding spaces */
433           *outptr++ = ' ';
434       } else {
435           *outptr++ = c;
436       }
437   }
438
439   *state = need;
440   *save = saved;
441
442   TRETURN (size_t) (outptr - out);
443 }
444
445 /**
446  * g_mime_encoding_base64_decode_step:
447  * @inbuf: input buffer
448  * @inlen: input buffer length
449  * @outbuf: output buffer
450  * @state: holds the number of bits that are stored in @save
451  * @save: leftover bits that have not yet been decoded
452  *
453  * Decodes a chunk of base64 encoded data.
454  *
455  * Returns: the number of bytes decoded (which have been dumped in
456  * @outbuf).
457  **/
458 size_t
459 g_mime_encoding_base64_decode_step (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save)
460 {
461   register const unsigned char *inptr;
462   register unsigned char *outptr;
463   const unsigned char *inend;
464   register unsigned int saved;
465   unsigned char c;
466   int npad, n, i;
467
468   TSTART;
469   inend = inbuf + inlen;
470   outptr = outbuf;
471   inptr = inbuf;
472
473   npad = (*state >> 8) & 0xff;
474   n = *state & 0xff;
475   saved = *save;
476
477   /* convert 4 base64 bytes to 3 normal bytes */
478   while (inptr < inend) {
479       c = gmime_base64_rank[*inptr++];
480       if (c != 0xff) {
481           saved = (saved << 6) | c;
482           n++;
483           if (n == 4) {
484               *outptr++ = saved >> 16;
485               *outptr++ = saved >> 8;
486               *outptr++ = saved;
487               n = 0;
488
489               if (npad > 0) {
490                   outptr -= npad;
491                   npad = 0;
492               }
493           }
494       }
495   }
496
497   /* quickly scan back for '=' on the end somewhere */
498   /* fortunately we can drop 1 output char for each trailing '=' (up to 2) */
499   for (i = 2; inptr > inbuf && i; ) {
500       inptr--;
501       if (gmime_base64_rank[*inptr] != 0xff) {
502           if (*inptr == '=' && outptr > outbuf) {
503               if (n == 0) {
504                   /* we've got a complete quartet so it's
505                      safe to drop an output character. */
506                   outptr--;
507               } else if (npad < 2) {
508                   /* keep a record of the number of ='s at
509                      the end of the input stream, up to 2 */
510                   npad++;
511               }
512           }
513
514           i--;
515       }
516   }
517
518   *state = (npad << 8) | n;
519   *save = n ? saved : 0;
520
521   TRETURN (outptr - outbuf);
522 }
523
524 static size_t
525 rfc2047_token_decode (rfc2047_token *token, unsigned char *outbuf, int *state, unsigned int *save)
526 {
527   const unsigned char *inbuf = (const unsigned char *) token->text;
528   size_t len = token->length;
529
530   TSTART;
531   if (token->encoding == 'B')
532     {
533       TRETURN g_mime_encoding_base64_decode_step (inbuf, len, outbuf, state, save);
534     }
535   else
536     {
537       TRETURN quoted_decode (inbuf, len, outbuf, state, save);
538     }
539 }
540
541 static char *
542 rfc2047_decode_tokens (rfc2047_token *tokens, size_t buflen)
543 {
544   rfc2047_token *token, *next;
545   size_t outlen, len, tmplen;
546   unsigned char *outptr;
547   const char *charset;
548   char *outbuf;
549   char *decoded;
550   char encoding;
551   unsigned int save;
552   int state;
553   char *str;
554
555   TSTART;
556   decoded = xmalloc (buflen + 1);
557   memset (decoded, 0, buflen + 1);
558   tmplen = 76;
559   outbuf = xmalloc (tmplen);
560
561   token = tokens;
562   while (token != NULL) {
563       next = token->next;
564
565       if (token->encoding) {
566           /* In order to work around broken mailers, we need to combine
567            * the raw decoded content of runs of identically encoded word
568            * tokens before converting into UTF-8. */
569           encoding = token->encoding;
570           charset = token->charset;
571           len = token->length;
572           state = 0;
573           save = 0;
574
575           /* find the end of the run (and measure the buffer length we'll need) */
576           while (next && next->encoding == encoding && !strcmp (next->charset, charset)) {
577               len += next->length;
578               next = next->next;
579           }
580
581           /* make sure our temporary output buffer is large enough... */
582           if (len > tmplen)
583             {
584               outbuf = xrealloc (outbuf, len + 1);
585               tmplen = len + 1;
586             }
587
588           /* base64 / quoted-printable decode each of the tokens... */
589           outptr = outbuf;
590           outlen = 0;
591           do {
592               /* Note: by not resetting state/save each loop, we effectively
593                * treat the payloads as one continuous block, thus allowing
594                * us to handle cases where a hex-encoded triplet of a
595                * quoted-printable encoded payload is split between 2 or more
596                * encoded-word tokens. */
597               len = rfc2047_token_decode (token, outptr, &state, &save);
598               token = token->next;
599               outptr += len;
600               outlen += len;
601           } while (token != next);
602           outptr = outbuf;
603
604           /* convert the raw decoded text into UTF-8 */
605           if (!strcasecmp (charset, "UTF-8")) {
606               strncat (decoded, (char *) outptr, outlen);
607           } else {
608 #ifndef BUILD_TESTS
609               str = ansi_charset_to_utf8 (charset, outptr, outlen, 0);
610 #else
611               log_debug ("%s:%s: Conversion not available for testing",
612                          SRCNAME, __func__);
613               str = strdup (outptr);
614 #endif
615               if (!str)
616                 {
617                   log_error ("%s:%s: Failed conversion from: %s for word: %s.",
618                              SRCNAME, __func__, charset, anonstr (outptr));
619                 }
620               else
621                 {
622                   strcat (decoded, str);
623                   xfree (str);
624                 }
625           }
626       } else {
627           strncat (decoded, token->text, token->length);
628       }
629       if (token && token->is_8bit)
630       {
631         /* We don't support this. */
632         log_error ("%s:%s: Unknown 8bit encoding detected.",
633                    SRCNAME, __func__);
634       }
635
636       token = next;
637   }
638
639   xfree (outbuf);
640
641   TRETURN decoded;
642 }
643
644
645 /**
646  * g_mime_utils_header_decode_phrase:
647  * @phrase: header to decode
648  *
649  * Decodes an rfc2047 encoded 'phrase' header.
650  *
651  * Note: See g_mime_set_user_charsets() for details on how charset
652  * conversion is handled for unencoded 8bit text and/or wrongly
653  * specified rfc2047 encoded-word tokens.
654  *
655  * Returns: a newly allocated UTF-8 string representing the the decoded
656  * header.
657  **/
658 static char *
659 g_mime_utils_header_decode_phrase (const char *phrase)
660 {
661   rfc2047_token *tokens;
662   char *decoded;
663   size_t len;
664
665   TSTART;
666   tokens = tokenize_rfc2047_phrase (phrase, &len);
667   decoded = rfc2047_decode_tokens (tokens, len);
668   rfc2047_token_list_free (tokens);
669
670   TRETURN decoded;
671 }
672
673 /* Try to parse an rfc 2047 filename for attachment handling.
674    Returns the parsed string. On errors the input string is just
675    copied with strdup */
676 char *
677 rfc2047_parse (const char *input)
678 {
679   char *decoded;
680   TSTART;
681   if (!input)
682     {
683       TRETURN xstrdup ("");
684     }
685
686   log_data ("%s:%s: Input: \"%s\"",
687             SRCNAME, __func__, input);
688
689   decoded = g_mime_utils_header_decode_phrase (input);
690
691   log_data ("%s:%s: Decoded: \"%s\"",
692             SRCNAME, __func__, decoded);
693
694   if (!decoded || !strlen (decoded))
695     {
696       xfree (decoded);
697       TRETURN xstrdup (input);
698     }
699   TRETURN decoded;
700 }