utf8ify_rfc822_string() is in libcitadel now
[citadel.git] / libcitadel / lib / decode.c
1 // Copyright (c) 1996-2022 by the citadel.org team
2 //
3 // This program is open source software.  Use, duplication, or disclosure
4 // are subject to the terms of the GNU General Public License v3.
5
6
7 #include <stdlib.h>
8 #include <unistd.h>
9 #include <stdio.h>
10 #include <signal.h>
11 #include <sys/types.h>
12 #include <ctype.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <errno.h>
16 #include <limits.h>
17 #include <iconv.h>
18 #include "libcitadel.h"
19
20 #if TIME_WITH_SYS_TIME
21 # include <sys/time.h>
22 # include <time.h>
23 #endif
24
25
26 // This is the non-define version in case it is needed for debugging
27 #if 0
28 inline void FindNextEnd (char *bptr, char *end) {
29         /* Find the next ?Q? */
30         end = strchr(bptr + 2, '?');
31         if (end == NULL) return NULL;
32         if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && 
33             (*(end + 2) == '?')) {
34                 /* skip on to the end of the cluster, the next ?= */
35                 end = strstr(end + 3, "?=");
36         }
37         else
38                 /* sort of half valid encoding, try to find an end. */
39                 end = strstr(bptr, "?=");
40 }
41 #endif
42
43 #define FindNextEnd(bptr, end) { \
44         end = strchr(bptr + 2, '?'); \
45         if (end != NULL) { \
46                 if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && (*(end + 2) == '?')) { \
47                         end = strstr(end + 3, "?="); \
48                 } else end = strstr(bptr, "?="); \
49         } \
50 }
51
52 // Handle subjects with RFC2047 encoding such as:
53 // =?koi8-r?B?78bP0s3Mxc7JxSDXz9rE1dvO2c3JINvB0sHNySDP?=
54 void utf8ify_rfc822_string(char *buf) {
55         char *start, *end, *next, *nextend, *ptr;
56         char newbuf[1024];
57         char charset[128];
58         char encoding[16];
59         char istr[1024];
60         iconv_t ic = (iconv_t)(-1) ;
61         char *ibuf;                     // Buffer of characters to be converted
62         char *obuf;                     // Buffer for converted characters
63         size_t ibuflen;                 // Length of input buffer
64         size_t obuflen;                 // Length of output buffer
65         char *isav;                     // Saved pointer to input buffer
66         char *osav;                     // Saved pointer to output buffer
67         int passes = 0;
68         int i, len, delta;
69         int illegal_non_rfc2047_encoding = 0;
70
71         // Sometimes, badly formed messages contain strings which were simply
72         // written out directly in some foreign character set instead of
73         // using RFC2047 encoding.  This is illegal but we will attempt to
74         // handle it anyway by converting from a user-specified default
75         // charset to UTF-8 if we see any nonprintable characters.
76         len = strlen(buf);
77         for (i=0; i<len; ++i) {
78                 if ((buf[i] < 32) || (buf[i] > 126)) {
79                         illegal_non_rfc2047_encoding = 1;
80                         i = len;        // take a shortcut, it won't be more than one.
81                 }
82         }
83         if (illegal_non_rfc2047_encoding) {
84                 const char *default_header_charset = "iso-8859-1";
85                 if ( (strcasecmp(default_header_charset, "UTF-8")) && (strcasecmp(default_header_charset, "us-ascii")) ) {
86                         ctdl_iconv_open("UTF-8", default_header_charset, &ic);
87                         if (ic != (iconv_t)(-1) ) {
88                                 ibuf = malloc(1024);
89                                 isav = ibuf;
90                                 safestrncpy(ibuf, buf, 1024);
91                                 ibuflen = strlen(ibuf);
92                                 obuflen = 1024;
93                                 obuf = (char *) malloc(obuflen);
94                                 osav = obuf;
95                                 iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
96                                 osav[1024-obuflen] = 0;
97                                 strcpy(buf, osav);
98                                 free(osav);
99                                 iconv_close(ic);
100                                 free(isav);
101                         }
102                 }
103         }
104
105         // pre evaluate the first pair
106         nextend = end = NULL;
107         len = strlen(buf);
108         start = strstr(buf, "=?");
109         if (start != NULL) 
110                 FindNextEnd (start, end);
111
112         while ((start != NULL) && (end != NULL)) {
113                 next = strstr(end, "=?");
114                 if (next != NULL)
115                         FindNextEnd(next, nextend);
116                 if (nextend == NULL)
117                         next = NULL;
118
119                 // did we find two partitions
120                 if ((next != NULL) && ((next - end) > 2)) {
121                         ptr = end + 2;
122                         while ((ptr < next) && 
123                                (isspace(*ptr) ||
124                                 (*ptr == '\r') ||
125                                 (*ptr == '\n') || 
126                                 (*ptr == '\t')))
127                                 ptr ++;
128                         // did we find a gab just filled with blanks?
129                         if (ptr == next) {
130                                 memmove(end + 2, next, len - (next - start));
131
132                                 // now terminate the gab at the end
133                                 delta = (next - end) - 2;
134                                 len -= delta;
135                                 buf[len] = '\0';
136
137                                 // move next to its new location.
138                                 next -= delta;
139                                 nextend -= delta;
140                         }
141                 }
142                 // our next-pair is our new first pair now.
143                 start = next;
144                 end = nextend;
145         }
146
147         // Now we handle foreign character sets properly encoded in RFC2047 format.
148         start = strstr(buf, "=?");
149         FindNextEnd((start != NULL)? start : buf, end);
150         while (start != NULL && end != NULL && end > start) {
151                 extract_token(charset, start, 1, '?', sizeof charset);
152                 extract_token(encoding, start, 2, '?', sizeof encoding);
153                 extract_token(istr, start, 3, '?', sizeof istr);
154
155                 ibuf = malloc(1024);
156                 isav = ibuf;
157                 if (!strcasecmp(encoding, "B")) {       // base64
158                         ibuflen = CtdlDecodeBase64(ibuf, istr, strlen(istr));
159                 }
160                 else if (!strcasecmp(encoding, "Q")) {  // quoted-printable
161                         size_t len;
162                         unsigned long pos;
163                         
164                         len = strlen(istr);
165                         pos = 0;
166                         while (pos < len) {
167                                 if (istr[pos] == '_') istr[pos] = ' ';
168                                 pos++;
169                         }
170                         ibuflen = CtdlDecodeQuotedPrintable(ibuf, istr, len);
171                 }
172                 else {
173                         strcpy(ibuf, istr);             // unknown encoding
174                         ibuflen = strlen(istr);
175                 }
176
177                 ctdl_iconv_open("UTF-8", charset, &ic);
178                 if (ic != (iconv_t)(-1) ) {
179                         obuflen = 1024;
180                         obuf = (char *) malloc(obuflen);
181                         osav = obuf;
182                         iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
183                         osav[1024-obuflen] = 0;
184
185                         end = start;
186                         end++;
187                         strcpy(start, "");
188                         remove_token(end, 0, '?');
189                         remove_token(end, 0, '?');
190                         remove_token(end, 0, '?');
191                         remove_token(end, 0, '?');
192                         strcpy(end, &end[1]);
193
194                         snprintf(newbuf, sizeof newbuf, "%s%s%s", buf, osav, end);
195                         strcpy(buf, newbuf);
196                         free(osav);
197                         iconv_close(ic);
198                 }
199                 else {
200                         end = start;
201                         end++;
202                         strcpy(start, "");
203                         remove_token(end, 0, '?');
204                         remove_token(end, 0, '?');
205                         remove_token(end, 0, '?');
206                         remove_token(end, 0, '?');
207                         strcpy(end, &end[1]);
208
209                         snprintf(newbuf, sizeof newbuf, "%s(unreadable)%s", buf, end);
210                         strcpy(buf, newbuf);
211                 }
212
213                 free(isav);
214
215                 // Since spammers will go to all sorts of absurd lengths to get their
216                 // messages through, there are LOTS of corrupt headers out there.
217                 // So, prevent a really badly formed RFC2047 header from throwing
218                 // this function into an infinite loop.
219                 ++passes;
220                 if (passes > 20) return;
221
222                 start = strstr(buf, "=?");
223                 FindNextEnd((start != NULL)? start : buf, end);
224         }
225
226 }