Line data Source code
1 : /* mime-maker.c - Create MIME structures
2 : * Copyright (C) 2016 g10 Code GmbH
3 : *
4 : * This file is part of GnuPG.
5 : *
6 : * GnuPG is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation; either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * GnuPG is distributed in the hope that it will be useful,
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : * GNU General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program; if not, see <https://www.gnu.org/licenses/>.
18 : */
19 :
20 : #include <config.h>
21 : #include <stdio.h>
22 : #include <stdlib.h>
23 : #include <string.h>
24 :
25 : #include "util.h"
26 : #include "zb32.h"
27 : #include "mime-maker.h"
28 :
29 :
30 : /* All valid characters in a header name. */
31 : #define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \
32 : "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
33 : "-01234567890")
34 :
35 : /* An object to store an header. Also used for a list of headers. */
36 : struct header_s
37 : {
38 : struct header_s *next;
39 : char *value; /* Malloced value. */
40 : char name[1]; /* Name. */
41 : };
42 : typedef struct header_s *header_t;
43 :
44 :
45 : /* An object to store a MIME part. A part is the header plus the
46 : * content (body). */
47 : struct part_s
48 : {
49 : struct part_s *next; /* Next part in the current container. */
50 : struct part_s *child; /* Child container. */
51 : char *boundary; /* Malloced boundary string. */
52 : header_t headers; /* List of headers. */
53 : header_t *headers_tail;/* Address of last header in chain. */
54 : size_t bodylen; /* Length of BODY. */
55 : char *body; /* Malloced buffer with the body. This is the
56 : * non-encoded value. */
57 : unsigned int partid; /* The part ID. */
58 : };
59 : typedef struct part_s *part_t;
60 :
61 :
62 :
63 : /* Definition of the mime parser object. */
64 : struct mime_maker_context_s
65 : {
66 : void *cookie; /* Cookie passed to all callbacks. */
67 :
68 : unsigned int verbose:1; /* Enable verbose mode. */
69 : unsigned int debug:1; /* Enable debug mode. */
70 :
71 : part_t mail; /* The MIME tree. */
72 : part_t current_part;
73 :
74 : unsigned int partid_counter; /* Counter assign part ids. */
75 :
76 : int boundary_counter; /* Used to create easy to read boundaries. */
77 : char *boundary_suffix; /* Random string used in the boundaries. */
78 :
79 : struct b64state *b64state; /* NULL or malloced Base64 decoder state. */
80 :
81 : /* Helper to convey the output stream to recursive functions. */
82 : estream_t outfp;
83 : };
84 :
85 :
86 : /* Create a new mime make object. COOKIE is a values woich will be
87 : * used as first argument for all callbacks registered with this
88 : * object. */
89 : gpg_error_t
90 0 : mime_maker_new (mime_maker_t *r_maker, void *cookie)
91 : {
92 : mime_maker_t ctx;
93 :
94 0 : *r_maker = NULL;
95 :
96 0 : ctx = xtrycalloc (1, sizeof *ctx);
97 0 : if (!ctx)
98 0 : return gpg_error_from_syserror ();
99 0 : ctx->cookie = cookie;
100 :
101 0 : *r_maker = ctx;
102 0 : return 0;
103 : }
104 :
105 :
106 : static void
107 0 : release_parts (part_t part)
108 : {
109 0 : while (part)
110 : {
111 0 : part_t partnext = part->next;
112 0 : while (part->headers)
113 : {
114 0 : header_t hdrnext = part->headers->next;
115 0 : xfree (part->headers);
116 0 : part->headers = hdrnext;
117 : }
118 0 : release_parts (part->child);
119 0 : xfree (part->boundary);
120 0 : xfree (part->body);
121 0 : xfree (part);
122 0 : part = partnext;
123 : }
124 0 : }
125 :
126 :
127 : /* Release a mime maker object. */
128 : void
129 0 : mime_maker_release (mime_maker_t ctx)
130 : {
131 0 : if (!ctx)
132 0 : return;
133 :
134 0 : release_parts (ctx->mail);
135 0 : xfree (ctx->boundary_suffix);
136 0 : xfree (ctx);
137 : }
138 :
139 :
140 : /* Set verbose and debug mode. */
141 : void
142 0 : mime_maker_set_verbose (mime_maker_t ctx, int level)
143 : {
144 0 : if (!level)
145 : {
146 0 : ctx->verbose = 0;
147 0 : ctx->debug = 0;
148 : }
149 : else
150 : {
151 0 : ctx->verbose = 1;
152 0 : if (level > 10)
153 0 : ctx->debug = 1;
154 : }
155 0 : }
156 :
157 :
158 : static void
159 0 : dump_parts (part_t part, int level)
160 : {
161 : header_t hdr;
162 :
163 0 : for (; part; part = part->next)
164 : {
165 0 : log_debug ("%*s[part %u]\n", level*2, "", part->partid);
166 0 : for (hdr = part->headers; hdr; hdr = hdr->next)
167 : {
168 0 : log_debug ("%*s%s: %s\n", level*2, "", hdr->name, hdr->value);
169 : }
170 0 : if (part->body)
171 0 : log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen);
172 0 : if (part->child)
173 : {
174 0 : log_debug ("%*s[container]\n", level*2, "");
175 0 : dump_parts (part->child, level+1);
176 : }
177 : }
178 0 : }
179 :
180 :
181 : /* Dump the mime tree for debugging. */
182 : void
183 0 : mime_maker_dump_tree (mime_maker_t ctx)
184 : {
185 0 : dump_parts (ctx->mail, 0);
186 0 : }
187 :
188 :
189 : /* Find the parent node for NEEDLE starting at ROOT. */
190 : static part_t
191 0 : find_parent (part_t root, part_t needle)
192 : {
193 : part_t node, n;
194 :
195 0 : for (node = root->child; node; node = node->next)
196 : {
197 0 : if (node == needle)
198 0 : return root;
199 0 : if ((n = find_parent (node, needle)))
200 0 : return n;
201 : }
202 0 : return NULL;
203 : }
204 :
205 : /* Find the part node from the PARTID. */
206 : static part_t
207 0 : find_part (part_t root, unsigned int partid)
208 : {
209 : part_t node, n;
210 :
211 0 : for (node = root->child; node; node = node->next)
212 : {
213 0 : if (node->partid == partid)
214 0 : return root;
215 0 : if ((n = find_part (node, partid)))
216 0 : return n;
217 : }
218 0 : return NULL;
219 : }
220 :
221 :
222 : /* Create a boundary string. Outr codes is aware of the general
223 : * structure of that string (gebins with "=-=") so that
224 : * it can protect against accidentally-used boundaries within the
225 : * content. */
226 : static char *
227 0 : generate_boundary (mime_maker_t ctx)
228 : {
229 0 : if (!ctx->boundary_suffix)
230 : {
231 : char buffer[12];
232 :
233 0 : gcry_create_nonce (buffer, sizeof buffer);
234 0 : ctx->boundary_suffix = zb32_encode (buffer, 8 * sizeof buffer);
235 0 : if (!ctx->boundary_suffix)
236 0 : return NULL;
237 : }
238 :
239 0 : ctx->boundary_counter++;
240 0 : return es_bsprintf ("=-=%02d-%s=-=",
241 : ctx->boundary_counter, ctx->boundary_suffix);
242 : }
243 :
244 :
245 : /* Ensure that the context has a MAIL and CURRENT_PART object and
246 : * return the parent object if available */
247 : static gpg_error_t
248 0 : ensure_part (mime_maker_t ctx, part_t *r_parent)
249 : {
250 0 : if (!ctx->mail)
251 : {
252 0 : ctx->mail = xtrycalloc (1, sizeof *ctx->mail);
253 0 : if (!ctx->mail)
254 0 : return gpg_error_from_syserror ();
255 0 : log_assert (!ctx->current_part);
256 0 : ctx->current_part = ctx->mail;
257 0 : ctx->current_part->headers_tail = &ctx->current_part->headers;
258 : }
259 0 : log_assert (ctx->current_part);
260 0 : if (r_parent)
261 0 : *r_parent = find_parent (ctx->mail, ctx->current_part);
262 :
263 0 : return 0;
264 : }
265 :
266 :
267 : /* Transform a header name into a standard capitalized format.
268 : * "Content-Type". Conversion stops at the colon. */
269 : static void
270 0 : capitalize_header_name (char *name)
271 : {
272 0 : unsigned char *p = name;
273 0 : int first = 1;
274 :
275 : /* Special cases first. */
276 0 : if (!ascii_strcasecmp (name, "MIME-Version"))
277 : {
278 0 : strcpy (name, "MIME-Version");
279 0 : return;
280 : }
281 :
282 : /* Regular cases. */
283 0 : for (; *p && *p != ':'; p++)
284 : {
285 0 : if (*p == '-')
286 0 : first = 1;
287 0 : else if (first)
288 : {
289 0 : if (*p >= 'a' && *p <= 'z')
290 0 : *p = *p - 'a' + 'A';
291 0 : first = 0;
292 : }
293 0 : else if (*p >= 'A' && *p <= 'Z')
294 0 : *p = *p - 'A' + 'a';
295 : }
296 : }
297 :
298 :
299 : /* Check whether a header with NAME has already been set into PART.
300 : * NAME must be in canonical capitalized format. Return true or
301 : * false. */
302 : static int
303 0 : have_header (part_t part, const char *name)
304 : {
305 : header_t hdr;
306 :
307 0 : for (hdr = part->headers; hdr; hdr = hdr->next)
308 0 : if (!strcmp (hdr->name, name))
309 0 : return 1;
310 0 : return 0;
311 : }
312 :
313 :
314 : /* Helper to add a header to a part. */
315 : static gpg_error_t
316 0 : add_header (part_t part, const char *name, const char *value)
317 : {
318 : gpg_error_t err;
319 : header_t hdr;
320 : size_t namelen;
321 : const char *s;
322 : char *p;
323 :
324 0 : if (!value)
325 : {
326 0 : s = strchr (name, '=');
327 0 : if (!s)
328 0 : return gpg_error (GPG_ERR_INV_ARG);
329 0 : namelen = s - name;
330 0 : value = s+1;
331 : }
332 : else
333 0 : namelen = strlen (name);
334 :
335 0 : hdr = xtrymalloc (sizeof *hdr + namelen);
336 0 : if (!hdr)
337 0 : return gpg_error_from_syserror ();
338 0 : hdr->next = NULL;
339 0 : memcpy (hdr->name, name, namelen);
340 0 : hdr->name[namelen] = 0;
341 :
342 : /* Check that the header name is valid. We allow all lower and
343 : * uppercase letters and, except for the first character, digits and
344 : * the dash. */
345 0 : if (strspn (hdr->name, HEADER_NAME_CHARS) != namelen
346 0 : || strchr ("-0123456789", *hdr->name))
347 : {
348 0 : xfree (hdr);
349 0 : return gpg_error (GPG_ERR_INV_NAME);
350 : }
351 :
352 0 : capitalize_header_name (hdr->name);
353 0 : hdr->value = xtrystrdup (value);
354 0 : if (!hdr->value)
355 : {
356 0 : err = gpg_error_from_syserror ();
357 0 : xfree (hdr);
358 0 : return err;
359 : }
360 :
361 0 : for (p = hdr->value + strlen (hdr->value) - 1;
362 0 : (p >= hdr->value
363 0 : && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r'));
364 0 : p--)
365 0 : *p = 0;
366 0 : if (!(p >= hdr->value))
367 : {
368 0 : xfree (hdr->value);
369 0 : xfree (hdr);
370 0 : return gpg_error (GPG_ERR_INV_VALUE); /* Only spaces. */
371 : }
372 :
373 0 : if (part)
374 : {
375 0 : *part->headers_tail = hdr;
376 0 : part->headers_tail = &hdr->next;
377 : }
378 : else
379 0 : xfree (hdr);
380 :
381 0 : return 0;
382 : }
383 :
384 :
385 : /* Add a header with NAME and VALUE to the current mail. A LF in the
386 : * VALUE will be handled automagically. If NULL is used for VALUE it
387 : * is expected that the NAME has the format "NAME=VALUE" and VALUE is
388 : * taken from there.
389 : *
390 : * If no container has been added, the header will be used for the
391 : * regular mail headers and not for a MIME part. If the current part
392 : * is in a container and a body has been added, we append a new part
393 : * to the current container. Thus for a non-MIME mail the caller
394 : * needs to call this function followed by a call to add a body. When
395 : * adding a Content-Type the boundary parameter must not be included.
396 : */
397 : gpg_error_t
398 0 : mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value)
399 : {
400 : gpg_error_t err;
401 : part_t part, parent;
402 :
403 : /* Hack to use this function for a syntax check of NAME and VALUE. */
404 0 : if (!ctx)
405 0 : return add_header (NULL, name, value);
406 :
407 0 : err = ensure_part (ctx, &parent);
408 0 : if (err)
409 0 : return err;
410 0 : part = ctx->current_part;
411 :
412 0 : if ((part->body || part->child) && !parent)
413 : {
414 : /* We already have a body but no parent. Adding another part is
415 : * thus not possible. */
416 0 : return gpg_error (GPG_ERR_CONFLICT);
417 : }
418 0 : if (part->body || part->child)
419 : {
420 : /* We already have a body and there is a parent. We now append
421 : * a new part to the current container. */
422 0 : part = xtrycalloc (1, sizeof *part);
423 0 : if (!part)
424 0 : return gpg_error_from_syserror ();
425 0 : part->partid = ++ctx->partid_counter;
426 0 : part->headers_tail = &part->headers;
427 0 : log_assert (!ctx->current_part->next);
428 0 : ctx->current_part->next = part;
429 0 : ctx->current_part = part;
430 : }
431 :
432 : /* If no NAME and no VALUE has been given we do not add a header.
433 : * This can be used to create a new part without any header. */
434 0 : if (!name && !value)
435 0 : return 0;
436 :
437 : /* If we add Content-Type, make sure that we have a MIME-version
438 : * header first; this simply looks better. */
439 0 : if (!ascii_strcasecmp (name, "Content-Type")
440 0 : && !have_header (ctx->mail, "MIME-Version"))
441 : {
442 0 : err = add_header (ctx->mail, "MIME-Version", "1.0");
443 0 : if (err)
444 0 : return err;
445 : }
446 0 : return add_header (part, name, value);
447 : }
448 :
449 :
450 : /* Helper for mime_maker_add_{body,stream}. */
451 : static gpg_error_t
452 0 : add_body (mime_maker_t ctx, const void *data, size_t datalen)
453 : {
454 : gpg_error_t err;
455 : part_t part, parent;
456 :
457 0 : err = ensure_part (ctx, &parent);
458 0 : if (err)
459 0 : return err;
460 0 : part = ctx->current_part;
461 0 : if (part->body)
462 0 : return gpg_error (GPG_ERR_CONFLICT);
463 :
464 0 : part->body = xtrymalloc (datalen? datalen : 1);
465 0 : if (!part->body)
466 0 : return gpg_error_from_syserror ();
467 0 : part->bodylen = datalen;
468 0 : if (data)
469 0 : memcpy (part->body, data, datalen);
470 :
471 0 : return 0;
472 : }
473 :
474 :
475 : /* Add STRING as body to the mail or the current MIME container. A
476 : * second call to this function is not allowed.
477 : *
478 : * FIXME: We may want to have an append_body to add more data to a body.
479 : */
480 : gpg_error_t
481 0 : mime_maker_add_body (mime_maker_t ctx, const char *string)
482 : {
483 0 : return add_body (ctx, string, strlen (string));
484 : }
485 :
486 :
487 : /* This is the same as mime_maker_add_body but takes a stream as
488 : * argument. As of now the stream is copied to the MIME object but
489 : * eventually we may delay that and read the stream only at the time
490 : * it is needed. Note that the address of the stream object must be
491 : * passed and that the ownership of the stream is transferred to this
492 : * MIME object. To indicate the latter the function will store NULL
493 : * at the ADDR_STREAM so that a caller can't use that object anymore
494 : * except for es_fclose which accepts a NULL pointer. */
495 : gpg_error_t
496 0 : mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr)
497 : {
498 : void *data;
499 : size_t datalen;
500 :
501 0 : es_rewind (*stream_addr);
502 0 : if (es_fclose_snatch (*stream_addr, &data, &datalen))
503 0 : return gpg_error_from_syserror ();
504 0 : *stream_addr = NULL;
505 0 : return add_body (ctx, data, datalen);
506 : }
507 :
508 :
509 : /* Add a new MIME container. A container can be used instead of a
510 : * body. */
511 : gpg_error_t
512 0 : mime_maker_add_container (mime_maker_t ctx)
513 : {
514 : gpg_error_t err;
515 : part_t part;
516 :
517 0 : err = ensure_part (ctx, NULL);
518 0 : if (err)
519 0 : return err;
520 0 : part = ctx->current_part;
521 :
522 0 : if (part->body)
523 0 : return gpg_error (GPG_ERR_CONFLICT); /* There is already a body. */
524 0 : if (part->child || part->boundary)
525 0 : return gpg_error (GPG_ERR_CONFLICT); /* There is already a container. */
526 :
527 : /* Create a child node. */
528 0 : part->child = xtrycalloc (1, sizeof *part->child);
529 0 : if (!part->child)
530 0 : return gpg_error_from_syserror ();
531 0 : part->child->headers_tail = &part->child->headers;
532 :
533 0 : part->boundary = generate_boundary (ctx);
534 0 : if (!part->boundary)
535 : {
536 0 : err = gpg_error_from_syserror ();
537 0 : xfree (part->child);
538 0 : part->child = NULL;
539 0 : return err;
540 : }
541 :
542 0 : part = part->child;
543 0 : part->partid = ++ctx->partid_counter;
544 0 : ctx->current_part = part;
545 :
546 0 : return 0;
547 : }
548 :
549 :
550 : /* Finish the current container. */
551 : gpg_error_t
552 0 : mime_maker_end_container (mime_maker_t ctx)
553 : {
554 : gpg_error_t err;
555 : part_t parent;
556 :
557 0 : err = ensure_part (ctx, &parent);
558 0 : if (err)
559 0 : return err;
560 0 : if (!parent)
561 0 : return gpg_error (GPG_ERR_CONFLICT); /* No container. */
562 0 : while (parent->next)
563 0 : parent = parent->next;
564 0 : ctx->current_part = parent;
565 0 : return 0;
566 : }
567 :
568 :
569 : /* Return the part-ID of the current part. */
570 : unsigned int
571 0 : mime_maker_get_partid (mime_maker_t ctx)
572 : {
573 0 : if (ensure_part (ctx, NULL))
574 0 : return 0; /* Ooops. */
575 0 : return ctx->current_part->partid;
576 : }
577 :
578 :
579 : /* Write a header and handle emdedded LFs. If BOUNDARY is not NULL it
580 : * is appended to the value. */
581 : /* Fixme: Add automatic line wrapping. */
582 : static gpg_error_t
583 0 : write_header (mime_maker_t ctx, const char *name, const char *value,
584 : const char *boundary)
585 : {
586 : const char *s;
587 :
588 0 : es_fprintf (ctx->outfp, "%s: ", name);
589 :
590 : /* Note that add_header made sure that VALUE does not end with a LF.
591 : * Thus we can assume that a LF is followed by non-whitespace. */
592 0 : for (s = value; *s; s++)
593 : {
594 0 : if (*s == '\n')
595 0 : es_fputs ("\r\n\t", ctx->outfp);
596 : else
597 0 : es_fputc (*s, ctx->outfp);
598 : }
599 0 : if (boundary)
600 : {
601 0 : if (s > value && s[-1] != ';')
602 0 : es_fputc (';', ctx->outfp);
603 0 : es_fprintf (ctx->outfp, "\r\n\tboundary=\"%s\"", boundary);
604 : }
605 :
606 0 : es_fputs ("\r\n", ctx->outfp);
607 :
608 0 : return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
609 : }
610 :
611 :
612 : static gpg_error_t
613 0 : write_gap (mime_maker_t ctx)
614 : {
615 0 : es_fputs ("\r\n", ctx->outfp);
616 0 : return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
617 : }
618 :
619 :
620 : static gpg_error_t
621 0 : write_boundary (mime_maker_t ctx, const char *boundary, int last)
622 : {
623 0 : es_fprintf (ctx->outfp, "\r\n--%s%s\r\n", boundary, last?"--":"");
624 0 : return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
625 : }
626 :
627 :
628 : /* Fixme: Apply required encoding. */
629 : static gpg_error_t
630 0 : write_body (mime_maker_t ctx, const void *body, size_t bodylen)
631 : {
632 : const char *s;
633 :
634 0 : for (s = body; bodylen; s++, bodylen--)
635 : {
636 0 : if (*s == '\n' && !(s > (const char *)body && s[-1] == '\r'))
637 0 : es_fputc ('\r', ctx->outfp);
638 0 : es_fputc (*s, ctx->outfp);
639 : }
640 :
641 0 : return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
642 : }
643 :
644 :
645 : /* Recursive worker for mime_maker_make. */
646 : static gpg_error_t
647 0 : write_tree (mime_maker_t ctx, part_t parent, part_t part)
648 : {
649 : gpg_error_t err;
650 : header_t hdr;
651 :
652 0 : for (; part; part = part->next)
653 : {
654 0 : for (hdr = part->headers; hdr; hdr = hdr->next)
655 : {
656 0 : if (part->child && !strcmp (hdr->name, "Content-Type"))
657 0 : err = write_header (ctx, hdr->name, hdr->value, part->boundary);
658 : else
659 0 : err = write_header (ctx, hdr->name, hdr->value, NULL);
660 0 : if (err)
661 0 : return err;
662 : }
663 0 : err = write_gap (ctx);
664 0 : if (err)
665 0 : return err;
666 0 : if (part->body)
667 : {
668 0 : err = write_body (ctx, part->body, part->bodylen);
669 0 : if (err)
670 0 : return err;
671 : }
672 0 : if (part->child)
673 : {
674 0 : log_assert (part->boundary);
675 0 : err = write_boundary (ctx, part->boundary, 0);
676 0 : if (!err)
677 0 : err = write_tree (ctx, part, part->child);
678 0 : if (!err)
679 0 : err = write_boundary (ctx, part->boundary, 1);
680 0 : if (err)
681 0 : return err;
682 : }
683 :
684 0 : if (part->next)
685 : {
686 0 : log_assert (parent && parent->boundary);
687 0 : err = write_boundary (ctx, parent->boundary, 0);
688 0 : if (err)
689 0 : return err;
690 : }
691 : }
692 0 : return 0;
693 : }
694 :
695 :
696 : /* Add headers we always require. */
697 : static gpg_error_t
698 0 : add_missing_headers (mime_maker_t ctx)
699 : {
700 : gpg_error_t err;
701 :
702 0 : if (!ctx->mail)
703 0 : return gpg_error (GPG_ERR_NO_DATA);
704 0 : if (!have_header (ctx->mail, "MIME-Version"))
705 : {
706 : /* Even if a Content-Type has never been set, we want to
707 : * announce that we do MIME. */
708 0 : err = add_header (ctx->mail, "MIME-Version", "1.0");
709 0 : if (err)
710 0 : goto leave;
711 : }
712 :
713 0 : if (!have_header (ctx->mail, "Date"))
714 : {
715 0 : char *p = rfctimestamp (make_timestamp ());
716 0 : if (!p)
717 0 : err = gpg_error_from_syserror ();
718 : else
719 0 : err = add_header (ctx->mail, "Date", p);
720 0 : xfree (p);
721 0 : if (err)
722 0 : goto leave;
723 : }
724 :
725 :
726 : leave:
727 0 : return err;
728 : }
729 :
730 :
731 : /* Create message from the tree MIME and write it to FP. Note that
732 : * the output uses only a LF and a later called sendmail(1) is
733 : * expected to convert them to network line endings. */
734 : gpg_error_t
735 0 : mime_maker_make (mime_maker_t ctx, estream_t fp)
736 : {
737 : gpg_error_t err;
738 :
739 0 : err = add_missing_headers (ctx);
740 0 : if (err)
741 0 : return err;
742 :
743 0 : ctx->outfp = fp;
744 0 : err = write_tree (ctx, NULL, ctx->mail);
745 :
746 0 : ctx->outfp = NULL;
747 0 : return err;
748 : }
749 :
750 :
751 : /* Create a stream object from the MIME part identified by PARTID and
752 : * store it at R_STREAM. If PARTID identifies a container the entire
753 : * tree is returned. Using that function may read stream objects
754 : * which have been added as MIME bodies. The caller must close the
755 : * stream object. */
756 : gpg_error_t
757 0 : mime_maker_get_part (mime_maker_t ctx, unsigned int partid, estream_t *r_stream)
758 : {
759 : gpg_error_t err;
760 : part_t part;
761 : estream_t fp;
762 :
763 0 : *r_stream = NULL;
764 :
765 : /* When the entire tree is requested, we make sure that all missing
766 : * headers are applied. We don't do that if only a part is
767 : * requested because the additional headers (like Date:) will only
768 : * be added to part 0 headers anyway. */
769 0 : if (!partid)
770 : {
771 0 : err = add_missing_headers (ctx);
772 0 : if (err)
773 0 : return err;
774 0 : part = ctx->mail;
775 : }
776 : else
777 0 : part = find_part (ctx->mail, partid);
778 :
779 : /* For now we use a memory stream object; however it would also be
780 : * possible to create an object created on the fly while the caller
781 : * is reading the returned stream. */
782 0 : fp = es_fopenmem (0, "w+b");
783 0 : if (!fp)
784 0 : return gpg_error_from_syserror ();
785 :
786 0 : ctx->outfp = fp;
787 0 : err = write_tree (ctx, NULL, part);
788 0 : ctx->outfp = NULL;
789 :
790 0 : if (!err)
791 : {
792 0 : es_rewind (fp);
793 0 : *r_stream = fp;
794 : }
795 : else
796 0 : es_fclose (fp);
797 :
798 0 : return err;
799 : }
|