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, &param);
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