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