1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 2003-2021 Free Software Foundation, Inc.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 3 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General
15 Public License along with this library. If not, see
16 <http://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <string.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <ctype.h>
27 #include <mailutils/stream.h>
28 #include <mailutils/filter.h>
29 #include <mailutils/errno.h>
30 #include <mailutils/mime.h>
31 #include <mailutils/util.h>
32
33 int
getword(char ** pret,const char ** pstr,int delim)34 getword (char **pret, const char **pstr, int delim)
35 {
36 size_t len;
37 char *ret;
38 const char *start = *pstr;
39 const char *end = strchr (start, delim);
40
41 free (*pret);
42 *pret = NULL;
43 if (!end)
44 return MU_ERR_BAD_2047_INPUT;
45 len = end - start;
46 ret = malloc (len + 1);
47 if (!ret)
48 return ENOMEM;
49 memcpy (ret, start, len);
50 ret[len] = 0;
51 *pstr = end + 1;
52 *pret = ret;
53 return 0;
54 }
55
56 static int
_rfc2047_decode_param(const char * tocode,const char * input,struct mu_mime_param * param)57 _rfc2047_decode_param (const char *tocode, const char *input,
58 struct mu_mime_param *param)
59 {
60 int status = 0;
61 const char *fromstr;
62 size_t run_count = 0;
63 char *fromcode = NULL;
64 char *encoding_type = NULL;
65 char *encoded_text = NULL;
66 char *tocodetmp = NULL;
67 mu_stream_t str;
68
69 memset (param, 0, sizeof (*param));
70
71 status = mu_memory_stream_create (&str, MU_STREAM_RDWR);
72 if (status)
73 return status;
74
75 if (tocode && (param->cset = strdup (tocode)) == NULL)
76 {
77 mu_stream_destroy (&str);
78 return ENOMEM;
79 }
80
81 fromstr = input;
82
83 while (*fromstr)
84 {
85 if (strncmp (fromstr, "=?", 2) == 0)
86 {
87 mu_stream_t filter = NULL;
88 mu_stream_t in_stream = NULL;
89 const char *filter_type = NULL;
90 size_t size;
91 const char *sp = fromstr + 2;
92 char *lang;
93
94 status = getword (&fromcode, &sp, '?');
95 if (status)
96 break;
97 lang = strchr (fromcode, '*');
98 if (lang)
99 *lang++ = 0;
100 if (!param->cset)
101 {
102 param->cset = strdup (fromcode);
103 if (!param->cset)
104 {
105 status = ENOMEM;
106 break;
107 }
108 }
109 if (lang && !param->lang && (param->lang = strdup (lang)) == NULL)
110 {
111 status = ENOMEM;
112 break;
113 }
114 if (!tocode)
115 {
116 if ((tocodetmp = strdup (fromcode)) == NULL)
117 {
118 status = ENOMEM;
119 break;
120 }
121 tocode = tocodetmp;
122 }
123 status = getword (&encoding_type, &sp, '?');
124 if (status)
125 break;
126 status = getword (&encoded_text, &sp, '?');
127 if (status)
128 break;
129 if (sp == NULL || sp[0] != '=')
130 {
131 status = MU_ERR_BAD_2047_INPUT;
132 break;
133 }
134
135 size = strlen (encoded_text);
136
137 switch (encoding_type[0])
138 {
139 case 'b':
140 case 'B':
141 filter_type = "base64";
142 break;
143
144 case 'q':
145 case 'Q':
146 filter_type = "Q";
147 break;
148
149 default:
150 status = MU_ERR_BAD_2047_INPUT;
151 break;
152 }
153
154 if (status != 0)
155 break;
156
157 mu_static_memory_stream_create (&in_stream, encoded_text, size);
158 mu_stream_seek (in_stream, 0, MU_SEEK_SET, NULL);
159 status = mu_decode_filter (&filter, in_stream, filter_type,
160 fromcode, tocode);
161 mu_stream_unref (in_stream);
162 if (status != 0)
163 break;
164 status = mu_stream_copy (str, filter, 0, NULL);
165 mu_stream_destroy (&filter);
166
167 if (status)
168 break;
169
170 fromstr = sp + 1;
171 run_count = 1;
172 }
173 else if (run_count)
174 {
175 if (*fromstr == ' ' || *fromstr == '\t')
176 {
177 run_count++;
178 fromstr++;
179 continue;
180 }
181 else
182 {
183 if (--run_count)
184 {
185 status = mu_stream_write (str, fromstr - run_count,
186 run_count, NULL);
187 if (status)
188 break;
189 run_count = 0;
190 }
191 status = mu_stream_write (str, fromstr, 1, NULL);
192 if (status)
193 break;
194 fromstr++;
195 }
196 }
197 else
198 {
199 status = mu_stream_write (str, fromstr, 1, NULL);
200 if (status)
201 break;
202 fromstr++;
203 }
204 }
205
206 if (status == 0 && *fromstr)
207 status = mu_stream_write (str, fromstr, strlen (fromstr), NULL);
208
209 free (fromcode);
210 free (encoding_type);
211 free (encoded_text);
212 free (tocodetmp);
213
214 if (status == 0)
215 {
216 mu_off_t size;
217
218 mu_stream_size (str, &size);
219 param->value = malloc (size + 1);
220 if (!param->value)
221 status = ENOMEM;
222 else
223 {
224 mu_stream_seek (str, 0, MU_SEEK_SET, NULL);
225 status = mu_stream_read (str, param->value, size, NULL);
226 param->value[size] = 0;
227 }
228 }
229
230 mu_stream_destroy (&str);
231 return status;
232 }
233
234 int
mu_rfc2047_decode_param(const char * tocode,const char * input,struct mu_mime_param ** param_ptr)235 mu_rfc2047_decode_param (const char *tocode, const char *input,
236 struct mu_mime_param **param_ptr)
237 {
238 int rc;
239 struct mu_mime_param *p;
240
241 if (!input)
242 return EINVAL;
243 if (!param_ptr)
244 return MU_ERR_OUT_PTR_NULL;
245 p = malloc (sizeof (*p));
246 if (!p)
247 return errno;
248 rc = _rfc2047_decode_param (tocode, input, p);
249 if (rc == 0)
250 *param_ptr = p;
251 else
252 mu_mime_param_free (p);
253 return rc;
254 }
255
256 int
mu_rfc2047_decode(const char * tocode,const char * input,char ** ptostr)257 mu_rfc2047_decode (const char *tocode, const char *input, char **ptostr)
258 {
259 int rc;
260 struct mu_mime_param param;
261
262 if (!input)
263 return EINVAL;
264 if (!ptostr)
265 return MU_ERR_OUT_PTR_NULL;
266 rc = _rfc2047_decode_param (tocode, input, ¶m);
267 free (param.cset);
268 free (param.lang);
269 if (rc == 0)
270 *ptostr = param.value;
271 return rc;
272 }
273
274 /**
275 Encode a header according to RFC 2047
276
277 @param charset
278 Charset of the text to encode
279 @param encoding
280 Requested encoding (must be "base64" or "quoted-printable")
281 @param text
282 Actual text to encode
283 @param result [OUT]
284 Encoded string
285
286 @return 0 on success
287 */
288
289 #define MAX_ENCODED_WORD 75
290
291 int
mu_rfc2047_encode(const char * charset,const char * encoding,const char * text,char ** result)292 mu_rfc2047_encode (const char *charset, const char *encoding,
293 const char *text, char **result)
294 {
295 mu_stream_t input_stream;
296 mu_stream_t inter_stream;
297 int rc;
298
299 if (charset == NULL || encoding == NULL || text == NULL)
300 return EINVAL;
301
302 if (strlen (charset) > MAX_ENCODED_WORD - 8)
303 return EINVAL;
304
305 if (strcmp (encoding, "base64") == 0)
306 encoding = "B";
307 else if (strcmp (encoding, "quoted-printable") == 0)
308 encoding = "Q";
309 else if (encoding[1] || !strchr ("BQ", encoding[0]))
310 return MU_ERR_BAD_2047_ENCODING;
311
312
313 rc = mu_static_memory_stream_create (&input_stream, text, strlen (text));
314 if (rc)
315 return rc;
316 rc = mu_filter_create (&inter_stream, input_stream,
317 encoding, MU_FILTER_ENCODE, MU_STREAM_READ);
318 mu_stream_unref (input_stream);
319 if (rc == 0)
320 {
321 mu_stream_t output_stream;
322 rc = mu_memory_stream_create (&output_stream, MU_STREAM_RDWR);
323 if (rc == 0)
324 {
325 /* FIXME: The following implementation does not conform to the
326 following requirement of RFC 2047:
327
328 Each 'encoded-word' MUST represent an integral number of
329 characters. A multi-octet character may not be split across
330 adjacent 'encoded-word's. */
331 char buf[MAX_ENCODED_WORD];
332 size_t start, bs, n;
333 char putback[2];
334 int pbi = 0;
335
336 start = snprintf (buf, sizeof buf, "=?%s?%s?", charset, encoding);
337 bs = sizeof buf - start - 2;
338
339 /* The 'encoded-text' in an 'encoded-word' must be self-contained;
340 'encoded-text' MUST NOT be continued from one 'encoded-word' to
341 another. */
342 if (encoding[0] == 'B')
343 {
344 /* This implies that the 'encoded-text' portion of a "B"
345 'encoded-word' will be a multiple of 4 characters long; */
346 bs -= bs % 4;
347 }
348
349 while (1)
350 {
351 if (pbi)
352 {
353 int i;
354
355 for (i = 0; i < pbi; i++)
356 buf[start + i] = putback[pbi - i];
357 }
358 rc = mu_stream_read (inter_stream, buf + start + pbi, bs - pbi,
359 &n);
360 if (rc)
361 break;
362 n += pbi;
363 pbi = 0;
364 if (n == 0)
365 break;
366
367 if (encoding[0] == 'Q')
368 {
369 /* [...] for a "Q" 'encoded-word', any "=" character that
370 appears in the 'encoded-text' portion will be followed
371 by two hexadecimal characters. */
372 /* This means that two last characters of buf must not
373 be '=' */
374 if (buf[n + start - 1] == '=')
375 {
376 putback[pbi++] = buf[start + --n];
377 }
378 else if (buf[n + start - 2] == '=')
379 {
380 putback[pbi++] = buf[start + --n];
381 putback[pbi++] = buf[start + --n];
382 }
383 }
384
385 rc = mu_stream_write (output_stream, buf, n + start, NULL);
386 if (rc)
387 break;
388 rc = mu_stream_write (output_stream, "?=", 2, NULL);
389 if (rc)
390 break;
391 if (n == bs)
392 rc = mu_stream_write (output_stream, "\n ", 2, NULL);
393 else
394 break;
395 }
396
397 if (rc == 0)
398 {
399 mu_off_t sz;
400 char *ptr;
401
402 mu_stream_size (output_stream, &sz);
403 ptr = malloc (sz + 1);
404 if (!ptr)
405 rc = ENOMEM;
406 else
407 {
408 if ((rc = mu_stream_seek (output_stream, 0, MU_SEEK_SET,
409 NULL)) == 0
410 && (rc = mu_stream_read (output_stream, ptr, sz, NULL))
411 == 0)
412 {
413 ptr[sz] = 0;
414 *result = ptr;
415 }
416 }
417 }
418
419 mu_stream_destroy (&output_stream);
420 }
421 mu_stream_destroy (&inter_stream);
422 }
423 else
424 mu_stream_destroy (&input_stream);
425
426 return rc;
427 }
428