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