1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-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 <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #ifdef HAVE_LIBGEN_H
29 # include <libgen.h>
30 #endif
31 
32 #ifdef HAVE_STRINGS_H
33 # include <strings.h>
34 #endif
35 
36 #include <mailutils/cctype.h>
37 #include <mailutils/cstr.h>
38 #include <mailutils/body.h>
39 #include <mailutils/filter.h>
40 #include <mailutils/header.h>
41 #include <mailutils/message.h>
42 #include <mailutils/stream.h>
43 #include <mailutils/errno.h>
44 #include <mailutils/util.h>
45 #include <mailutils/io.h>
46 
47 struct _mu_mime_io_buffer
48 {
49   unsigned int refcnt;
50   char *charset;
51   mu_header_t hdr;
52   mu_message_t msg;
53   mu_stream_t stream;	/* output file/decoding stream for saving attachment */
54   mu_stream_t fstream;	/* output file stream for saving attachment */
55 };
56 
57 static int
at_hdr(mu_header_t hdr,const char * content_type,const char * encoding,const char * name,const char * filename)58 at_hdr (mu_header_t hdr, const char *content_type, const char *encoding,
59 	const char *name, const char *filename)
60 {
61   int rc;
62   char *val, *str;
63 
64   if (filename)
65     {
66       str = strrchr (filename, '/');
67       if (str)
68 	filename = str + 1;
69     }
70 
71   if (!name)
72     {
73       name = filename;
74     }
75 
76   if (name)
77     {
78       rc = mu_c_str_escape (name, "\\\"", NULL, &str);
79       if (rc)
80 	return rc;
81       rc = mu_asprintf (&val, "%s; name=\"%s\"", content_type, str);
82       free (str);
83       if (rc)
84 	return rc;
85       rc = mu_header_set_value (hdr, MU_HEADER_CONTENT_TYPE, val, 1);
86       free (val);
87     }
88   else
89     rc = mu_header_set_value (hdr, MU_HEADER_CONTENT_TYPE, content_type, 1);
90 
91   if (rc)
92     return rc;
93 
94   if (filename)
95     {
96       rc = mu_c_str_escape (filename, "\\\"", NULL, &str);
97       if (rc)
98 	return rc;
99       rc = mu_asprintf (&val, "%s; filename=\"%s\"", "attachment", str);
100       free (str);
101       if (rc)
102 	return rc;
103       rc = mu_header_set_value (hdr, MU_HEADER_CONTENT_DISPOSITION, val, 1);
104       free (val);
105     }
106   else
107     rc = mu_header_set_value (hdr, MU_HEADER_CONTENT_DISPOSITION, "attachment",
108 			      1);
109   if (rc)
110     return rc;
111   return mu_header_set_value (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING,
112 			      encoding ? encoding : "8bit", 1);
113 }
114 
115 /* Create in *NEWMSG an empty attachment of given CONTENT_TYPE and ENCODING.
116    NAME, if not NULL, supplies the name of the attachment.
117    FILENAME, if not NULL, gives the file name.
118  */
119 int
mu_attachment_create(mu_message_t * newmsg,const char * content_type,const char * encoding,const char * name,const char * filename)120 mu_attachment_create (mu_message_t *newmsg,
121 		      const char *content_type, const char *encoding,
122 		      const char *name,
123 		      const char *filename)
124 {
125   int rc;
126   mu_header_t hdr;
127 
128   if (newmsg == NULL)
129     return MU_ERR_OUT_PTR_NULL;
130 
131   rc = mu_message_create (newmsg, NULL);
132   if (rc)
133     return rc;
134 
135   rc = mu_header_create (&hdr, NULL, 0);
136   if (rc)
137     {
138       mu_message_destroy (newmsg, NULL);
139       return rc;
140     }
141   mu_message_set_header (*newmsg, hdr, NULL);
142 
143   rc = at_hdr (hdr, content_type, encoding, name, filename);
144 
145   if (rc)
146     mu_message_destroy (newmsg, NULL);
147 
148   return rc;
149 }
150 
151 /* ATT is an attachment created by a previous call to mu_attachment_create().
152 
153    Fills in the attachment body with the data from STREAM using the encoding
154    stored in the Content-Transfer-Encoding header of ATT.
155  */
156 int
mu_attachment_copy_from_stream(mu_message_t att,mu_stream_t stream)157 mu_attachment_copy_from_stream (mu_message_t att, mu_stream_t stream)
158 {
159   mu_body_t body;
160   mu_stream_t bstr;
161   mu_stream_t tstream;
162   mu_header_t hdr;
163   int rc;
164   char *encoding;
165 
166   mu_message_get_header (att, &hdr);
167   rc = mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING,
168 				    &encoding);
169   switch (rc)
170     {
171     case 0:
172       break;
173 
174     case MU_ERR_NOENT:
175       return EINVAL;
176 
177     default:
178       return rc;
179     }
180 
181   mu_message_get_body (att, &body);
182   rc = mu_body_get_streamref (body, &bstr);
183   if (rc == 0)
184     {
185       rc = mu_filter_create (&tstream, stream, encoding, MU_FILTER_ENCODE,
186 			     MU_STREAM_READ);
187       if (rc == 0)
188 	{
189 	  rc = mu_stream_copy (bstr, tstream, 0, NULL);
190 	  mu_stream_unref (tstream);
191 	}
192       mu_stream_unref (bstr);
193     }
194   free (encoding);
195   return rc;
196 }
197 
198 /* ATT is an attachment created by a previous call to mu_attachment_create().
199 
200    Fills in the attachment body with the data from FILENAME using the encoding
201    specified in the Content-Transfer-Encoding header.
202  */
203 int
mu_attachment_copy_from_file(mu_message_t att,char const * filename)204 mu_attachment_copy_from_file (mu_message_t att, char const *filename)
205 {
206   mu_stream_t stream;
207   int rc;
208 
209   rc = mu_file_stream_create (&stream, filename, MU_STREAM_READ);
210   if (rc == 0)
211     {
212       rc = mu_attachment_copy_from_stream (att, stream);
213       mu_stream_unref (stream);
214     }
215   return rc;
216 }
217 
218 int
mu_message_create_attachment(const char * content_type,const char * encoding,const char * filename,mu_message_t * newmsg)219 mu_message_create_attachment (const char *content_type, const char *encoding,
220 			      const char *filename, mu_message_t *newmsg)
221 {
222   int rc;
223   mu_message_t att;
224 
225   if (content_type == NULL)
226     content_type = "text/plain";
227 
228   rc = mu_attachment_create (&att, content_type, encoding, NULL, filename);
229   if (rc == 0)
230     {
231       rc = mu_attachment_copy_from_file (att, filename);
232       if (rc)
233 	mu_message_destroy (&att, NULL);
234     }
235   if (rc == 0)
236     *newmsg = att;
237 
238   return rc;
239 }
240 
241 int
mu_mime_io_buffer_create(mu_mime_io_buffer_t * pinfo)242 mu_mime_io_buffer_create (mu_mime_io_buffer_t *pinfo)
243 {
244   mu_mime_io_buffer_t info;
245 
246   if ((info = calloc (1, sizeof (*info))) == NULL)
247     return ENOMEM;
248   info->refcnt = 1;
249   *pinfo = info;
250   return 0;
251 }
252 
253 int
mu_mime_io_buffer_set_charset(mu_mime_io_buffer_t info,const char * charset)254 mu_mime_io_buffer_set_charset (mu_mime_io_buffer_t info, const char *charset)
255 {
256   char *cp = strdup (charset);
257   if (!cp)
258     return ENOMEM;
259   free (info->charset);
260   info->charset = cp;
261   return 0;
262 }
263 
264 void
mu_mime_io_buffer_sget_charset(mu_mime_io_buffer_t info,const char ** charset)265 mu_mime_io_buffer_sget_charset (mu_mime_io_buffer_t info, const char **charset)
266 {
267   *charset = info->charset;
268 }
269 
270 int
mu_mime_io_buffer_aget_charset(mu_mime_io_buffer_t info,const char ** charset)271 mu_mime_io_buffer_aget_charset (mu_mime_io_buffer_t info, const char **charset)
272 {
273   *charset = strdup (info->charset);
274   if (!charset)
275     return ENOMEM;
276   return 0;
277 }
278 
279 void
mu_mime_io_buffer_destroy(mu_mime_io_buffer_t * pinfo)280 mu_mime_io_buffer_destroy (mu_mime_io_buffer_t *pinfo)
281 {
282   if (pinfo && *pinfo)
283     {
284       mu_mime_io_buffer_t info = *pinfo;
285       free (info->charset);
286       free (info);
287       *pinfo = NULL;
288     }
289 }
290 
291 static void
_attachment_free(struct _mu_mime_io_buffer * info,int free_message)292 _attachment_free (struct _mu_mime_io_buffer *info, int free_message)
293 {
294   if (free_message)
295     {
296       if (info->msg)
297 	mu_message_destroy (&info->msg, NULL);
298       else if (info->hdr)
299 	mu_header_destroy (&info->hdr);
300     }
301   info->msg = NULL;
302   info->hdr = NULL;
303   info->stream = NULL;
304   info->fstream = NULL;
305   if (--info->refcnt == 0)
306     {
307       free (info->charset);
308       free (info);
309     }
310 }
311 
312 static int
_attachment_setup(mu_mime_io_buffer_t * pinfo,mu_message_t msg,mu_stream_t * pstream)313 _attachment_setup (mu_mime_io_buffer_t *pinfo, mu_message_t msg,
314 		   mu_stream_t *pstream)
315 {
316   int ret;
317   mu_body_t body;
318   mu_mime_io_buffer_t info;
319   mu_stream_t stream;
320 
321   if ((ret = mu_message_get_body (msg, &body)) != 0 ||
322       (ret = mu_body_get_streamref (body, &stream)) != 0)
323     return ret;
324   *pstream = stream;
325   if (*pinfo)
326     {
327       info = *pinfo;
328       info->refcnt++;
329     }
330   else
331     {
332       ret = mu_mime_io_buffer_create (&info);
333       if (ret)
334 	return ret;
335     }
336 
337   *pinfo = info;
338   return 0;
339 }
340 
341 int
mu_message_save_attachment(mu_message_t msg,const char * filename,mu_mime_io_buffer_t info)342 mu_message_save_attachment (mu_message_t msg, const char *filename,
343 			    mu_mime_io_buffer_t info)
344 {
345   mu_stream_t istream;
346   int ret;
347   mu_header_t hdr;
348   const char *fname = NULL;
349   char *partname = NULL;
350 
351   if (msg == NULL)
352     return EINVAL;
353 
354   if ((ret = _attachment_setup (&info, msg, &istream)) != 0)
355     return ret;
356 
357   if (ret == 0 && (ret = mu_message_get_header (msg, &hdr)) == 0)
358     {
359       if (filename == NULL)
360 	{
361 	  ret = mu_message_aget_decoded_attachment_name (msg, info->charset,
362 							 &partname, NULL);
363 	  if (partname)
364 	    fname = partname;
365 	}
366       else
367 	fname = filename;
368       if (fname
369 	  && (ret = mu_file_stream_create (&info->fstream, fname,
370 				     MU_STREAM_WRITE | MU_STREAM_CREAT)) == 0)
371 	{
372 	  const char *content_encoding;
373 
374 	  if (mu_header_sget_value (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING,
375 				    &content_encoding))
376 	    content_encoding = "7bit";
377 	  ret = mu_filter_create (&info->stream, istream, content_encoding,
378 				  MU_FILTER_DECODE,
379 				  MU_STREAM_READ);
380 	}
381     }
382   if (info->stream && istream)
383     ret = mu_stream_copy (info->fstream, info->stream, 0, NULL);
384 
385   if (ret != EAGAIN && info)
386     {
387       mu_stream_close (info->fstream);
388       mu_stream_destroy (&info->stream);
389       mu_stream_destroy (&info->fstream);
390     }
391 
392   mu_stream_destroy (&istream);
393   _attachment_free (info, ret); /* FIXME: or 0? */
394 
395   /* Free fname if we allocated it. */
396   if (partname)
397     free (partname);
398 
399   return ret;
400 }
401 
402 int
mu_message_encapsulate(mu_message_t msg,mu_message_t * newmsg,mu_mime_io_buffer_t info)403 mu_message_encapsulate (mu_message_t msg, mu_message_t *newmsg,
404 			mu_mime_io_buffer_t info)
405 {
406   mu_stream_t istream, ostream;
407   int ret = 0;
408   mu_message_t tmsg = NULL;
409 
410   if (newmsg == NULL)
411     return MU_ERR_OUT_PTR_NULL;
412 
413   if (msg == NULL)
414     {
415       mu_header_t hdr;
416 
417       ret = mu_message_create (&tmsg, NULL);
418       if (ret)
419 	return ret;
420       msg = tmsg;
421 #define MSG822_HEADER "Content-Type: message/rfc822\n" \
422  	              "Content-Transfer-Encoding: 7bit\n\n"
423       if ((ret = mu_header_create (&hdr,
424 				   MSG822_HEADER,
425 				   sizeof (MSG822_HEADER) - 1)) == 0)
426 	ret = mu_message_set_header (msg, hdr, NULL);
427 #undef MSG822_HEADER
428       if (ret)
429 	{
430 	  mu_message_destroy (&msg, NULL);
431 	  return ret;
432 	}
433     }
434 
435   if ((ret = _attachment_setup (&info, msg, &ostream)) != 0)
436     {
437       mu_message_destroy (&tmsg, NULL);
438       return ret;
439     }
440   info->msg = msg;
441   if (ret == 0 && (ret = mu_message_get_streamref (msg, &istream)) == 0)
442     {
443       mu_stream_seek (istream, 0, MU_SEEK_SET, NULL);
444       ret = mu_stream_copy (ostream, istream, 0, NULL);
445       mu_stream_destroy (&istream);
446     }
447   if (ret == 0)
448     *newmsg = info->msg;
449   mu_stream_destroy (&ostream);
450   _attachment_free (info, ret && ret != EAGAIN);
451   return ret;
452 }
453 
454 #define MESSAGE_RFC822_STR "message/rfc822"
455 
456 int
mu_message_unencapsulate(mu_message_t msg,mu_message_t * newmsg,mu_mime_io_buffer_t info)457 mu_message_unencapsulate (mu_message_t msg, mu_message_t *newmsg,
458 			  mu_mime_io_buffer_t info)
459 {
460   int ret = 0;
461   mu_header_t hdr;
462   mu_stream_t istream;
463 
464   if (msg == NULL)
465     return EINVAL;
466   if (newmsg == NULL)
467     return MU_ERR_OUT_PTR_NULL;
468 
469   if (info == NULL /* FIXME: not needed? */
470       && (ret = mu_message_get_header (msg, &hdr)) == 0)
471     {
472       const char *s;
473       if (!(mu_header_sget_value (hdr, MU_HEADER_CONTENT_TYPE, &s) == 0 &&
474 	    mu_c_strncasecmp (s, MESSAGE_RFC822_STR,
475 			      sizeof (MESSAGE_RFC822_STR) - 1) == 0))
476 	return EINVAL;
477     }
478   if ((ret = _attachment_setup (&info, msg, &istream)) != 0)
479     return ret;
480   ret = mu_stream_to_message (istream, &info->msg);
481   mu_stream_unref (istream);
482   if (ret == 0)
483     *newmsg = info->msg;
484   _attachment_free (info, ret && ret != EAGAIN);
485   return ret;
486 }
487