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