tools: Simplify the mime-maker container creation.
[gnupg.git] / tools / mime-maker.c
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 <http://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 };
58 typedef struct part_s *part_t;
59
60
61
62 /* Definition of the mime parser object.  */
63 struct mime_maker_context_s
64 {
65   void *cookie;                /* Cookie passed to all callbacks.  */
66
67   unsigned int verbose:1;      /* Enable verbose mode.  */
68   unsigned int debug:1;        /* Enable debug mode.  */
69
70   part_t mail;                 /* The MIME tree.  */
71   part_t current_part;
72
73   int boundary_counter;  /* Used to create easy to read boundaries.  */
74   char *boundary_suffix; /* Random string used in the boundaries.  */
75
76   struct b64state *b64state;     /* NULL or malloced Base64 decoder state.  */
77
78   /* Helper to convey the output stream to recursive functions. */
79   estream_t outfp;
80 };
81
82
83 /* Create a new mime make object.  COOKIE is a values woich will be
84  * used as first argument for all callbacks registered with this
85  * object.  */
86 gpg_error_t
87 mime_maker_new (mime_maker_t *r_maker, void *cookie)
88 {
89   mime_maker_t ctx;
90
91   *r_maker = NULL;
92
93   ctx = xtrycalloc (1, sizeof *ctx);
94   if (!ctx)
95     return gpg_error_from_syserror ();
96   ctx->cookie = cookie;
97
98   *r_maker = ctx;
99   return 0;
100 }
101
102
103 static void
104 release_parts (part_t part)
105 {
106   while (part)
107     {
108       part_t partnext = part->next;
109       while (part->headers)
110         {
111           header_t hdrnext = part->headers->next;
112           xfree (part->headers);
113           part->headers = hdrnext;
114         }
115       release_parts (part->child);
116       xfree (part->boundary);
117       xfree (part->body);
118       xfree (part);
119       part = partnext;
120     }
121 }
122
123
124 /* Release a mime maker object.  */
125 void
126 mime_maker_release (mime_maker_t ctx)
127 {
128   if (!ctx)
129     return;
130
131   release_parts (ctx->mail);
132   xfree (ctx->boundary_suffix);
133   xfree (ctx);
134 }
135
136
137 /* Set verbose and debug mode.  */
138 void
139 mime_maker_set_verbose (mime_maker_t ctx, int level)
140 {
141   if (!level)
142     {
143       ctx->verbose = 0;
144       ctx->debug = 0;
145     }
146   else
147     {
148       ctx->verbose = 1;
149       if (level > 10)
150         ctx->debug = 1;
151     }
152 }
153
154
155 static void
156 dump_parts (part_t part, int level)
157 {
158   header_t hdr;
159
160   for (; part; part = part->next)
161     {
162       log_debug ("%*s[part]\n", level*2, "");
163       for (hdr = part->headers; hdr; hdr = hdr->next)
164         {
165           log_debug ("%*s%s: %s\n", level*2, "", hdr->name, hdr->value);
166         }
167       if (part->body)
168         log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen);
169       if (part->child)
170         {
171           log_debug ("%*s[container]\n", level*2, "");
172           dump_parts (part->child, level+1);
173         }
174     }
175 }
176
177
178 /* Dump the mime tree for debugging.  */
179 void
180 mime_maker_dump_tree (mime_maker_t ctx)
181 {
182   dump_parts (ctx->mail, 0);
183 }
184
185
186 /* Find the parent node for NEEDLE starting at ROOT.  */
187 static part_t
188 find_parent (part_t root, part_t needle)
189 {
190   part_t node, n;
191
192   for (node = root->child; node; node = node->next)
193     {
194       if (node == needle)
195         return root;
196       if ((n = find_parent (node, needle)))
197         return n;
198     }
199   return NULL;
200 }
201
202
203 /* Create a boundary string.  Outr codes is aware of the general
204  * structure of that string (gebins with "=-=") so that
205  * it can protect against accidentally-used boundaries within the
206  * content.   */
207 static char *
208 generate_boundary (mime_maker_t ctx)
209 {
210   if (!ctx->boundary_suffix)
211     {
212       char buffer[12];
213
214       gcry_create_nonce (buffer, sizeof buffer);
215       ctx->boundary_suffix = zb32_encode (buffer, 8 * sizeof buffer);
216       if (!ctx->boundary_suffix)
217         return NULL;
218     }
219
220   ctx->boundary_counter++;
221   return es_bsprintf ("=-=%02d-%s=-=",
222                       ctx->boundary_counter, ctx->boundary_suffix);
223 }
224
225
226 /* Ensure that the context has a MAIL and CURRENT_PART object and
227  * return the parent object if available  */
228 static gpg_error_t
229 ensure_part (mime_maker_t ctx, part_t *r_parent)
230 {
231   if (!ctx->mail)
232     {
233       ctx->mail = xtrycalloc (1, sizeof *ctx->mail);
234       if (!ctx->mail)
235         return gpg_error_from_syserror ();
236       log_assert (!ctx->current_part);
237       ctx->current_part = ctx->mail;
238       ctx->current_part->headers_tail = &ctx->current_part->headers;
239     }
240   log_assert (ctx->current_part);
241   if (r_parent)
242     *r_parent = find_parent (ctx->mail, ctx->current_part);
243
244   return 0;
245 }
246
247
248 /* Transform a header name into a standard capitalized format.
249  * "Content-Type".  Conversion stops at the colon. */
250 static void
251 capitalize_header_name (char *name)
252 {
253   unsigned char *p = name;
254   int first = 1;
255
256   /* Special cases first.  */
257   if (!ascii_strcasecmp (name, "MIME-Version"))
258     {
259       strcpy (name, "MIME-Version");
260       return;
261     }
262
263   /* Regular cases.  */
264   for (; *p && *p != ':'; p++)
265     {
266       if (*p == '-')
267         first = 1;
268       else if (first)
269         {
270           if (*p >= 'a' && *p <= 'z')
271             *p = *p - 'a' + 'A';
272           first = 0;
273         }
274       else if (*p >= 'A' && *p <= 'Z')
275         *p = *p - 'A' + 'a';
276     }
277 }
278
279
280 /* Check whether a header with NAME has already been set into PART.
281  * NAME must be in canonical capitalized format.  Return true or
282  * false. */
283 static int
284 have_header (part_t part, const char *name)
285 {
286   header_t hdr;
287
288   for (hdr = part->headers; hdr; hdr = hdr->next)
289     if (!strcmp (hdr->name, name))
290       return 1;
291   return 0;
292 }
293
294
295 /* Helper to add a header to a part.  */
296 static gpg_error_t
297 add_header (part_t part, const char *name, const char *value)
298 {
299   gpg_error_t err;
300   header_t hdr;
301   size_t namelen;
302   const char *s;
303
304   if (!value)
305     {
306       s = strchr (name, '=');
307       if (!s)
308         return gpg_error (GPG_ERR_INV_ARG);
309       namelen = s - name;
310       value = s+1;
311     }
312   else
313     namelen = strlen (name);
314
315   hdr = xtrymalloc (sizeof *hdr + namelen);
316   if (!hdr)
317     return gpg_error_from_syserror ();
318   hdr->next = NULL;
319   memcpy (hdr->name, name, namelen);
320   hdr->name[namelen] = 0;
321
322   /* Check that the header name is valid.  We allow all lower and
323    * uppercase letters and, except for the first character, digits and
324    * the dash.  */
325   if (strspn (hdr->name, HEADER_NAME_CHARS) != namelen
326       || strchr ("-0123456789", *hdr->name))
327     {
328       xfree (hdr);
329       return gpg_error (GPG_ERR_INV_NAME);
330     }
331
332   capitalize_header_name (hdr->name);
333   hdr->value = xtrystrdup (value);
334   if (!hdr->value)
335     {
336       err = gpg_error_from_syserror ();
337       xfree (hdr);
338       return err;
339     }
340
341   if (part)
342     {
343       *part->headers_tail = hdr;
344       part->headers_tail = &hdr->next;
345     }
346   else
347     xfree (hdr);
348
349   return 0;
350 }
351
352
353 /* Add a header with NAME and VALUE to the current mail.  A LF in the
354  * VALUE will be handled automagically.  If NULL is used for VALUE it
355  * is expected that the NAME has the format "NAME=VALUE" and VALUE is
356  * taken from there.
357  *
358  * If no container has been added, the header will be used for the
359  * regular mail headers and not for a MIME part.  If the current part
360  * is in a container and a body has been added, we append a new part
361  * to the current container.  Thus for a non-MIME mail the caller
362  * needs to call this function followed by a call to add a body.  When
363  * adding a Content-Type the boundary parameter must not be included.
364  */
365 gpg_error_t
366 mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value)
367 {
368   gpg_error_t err;
369   part_t part, parent;
370
371   /* Hack to use this function for a syntax check of NAME and VALUE.  */
372   if (!ctx)
373     return add_header (NULL, name, value);
374
375   err = ensure_part (ctx, &parent);
376   if (err)
377     return err;
378   part = ctx->current_part;
379
380   if ((part->body || part->child) && !parent)
381     {
382       /* We already have a body but no parent.  Adding another part is
383        * thus not possible.  */
384       return gpg_error (GPG_ERR_CONFLICT);
385     }
386   if (part->body || part->child)
387     {
388       /* We already have a body and there is a parent.  We now append
389        * a new part to the current container.  */
390       part = xtrycalloc (1, sizeof *part);
391       if (!part)
392         return gpg_error_from_syserror ();
393       part->headers_tail = &part->headers;
394       log_assert (!ctx->current_part->next);
395       ctx->current_part->next = part;
396       ctx->current_part = part;
397     }
398
399   /* If no NAME and no VALUE has been given we do not add a header.
400    * This can be used to create a new part without any header.  */
401   if (!name && !value)
402     return 0;
403
404   /* If we add Content-Type, make sure that we have a MIME-version
405    * header first; this simply looks better.  */
406   if (!ascii_strcasecmp (name, "Content-Type")
407       && !have_header (ctx->mail, "MIME-Version"))
408     {
409       err = add_header (ctx->mail, "MIME-Version", "1.0");
410       if (err)
411         return err;
412     }
413   return add_header (part, name, value);
414 }
415
416
417 /* Helper for mime_maker_add_{body,stream}.  */
418 static gpg_error_t
419 add_body (mime_maker_t ctx, const void *data, size_t datalen)
420 {
421   gpg_error_t err;
422   part_t part, parent;
423
424   err = ensure_part (ctx, &parent);
425   if (err)
426     return err;
427   part = ctx->current_part;
428   if (part->body)
429     return gpg_error (GPG_ERR_CONFLICT);
430
431   part->body = xtrymalloc (datalen? datalen : 1);
432   if (!part->body)
433     return gpg_error_from_syserror ();
434   part->bodylen = datalen;
435   if (data)
436     memcpy (part->body, data, datalen);
437
438   return 0;
439 }
440
441
442 /* Add STRING as body to the mail or the current MIME container.  A
443  * second call to this function is not allowed.
444  *
445  * FIXME: We may want to have an append_body to add more data to a body.
446  */
447 gpg_error_t
448 mime_maker_add_body (mime_maker_t ctx, const char *string)
449 {
450   return add_body (ctx, string, strlen (string));
451 }
452
453
454 /* This is the same as mime_maker_add_body but takes a stream as
455  * argument.  As of now the stream is copied to the MIME object but
456  * eventually we may delay that and read the stream only at the time
457  * it is needed.  Note that the address of the stream object must be
458  * passed and that the ownership of the stream is transferred to this
459  * MIME object.  To indicate the latter the function will store NULL
460  * at the ADDR_STREAM so that a caller can't use that object anymore
461  * except for es_fclose which accepts a NULL pointer.  */
462 gpg_error_t
463 mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr)
464 {
465   void *data;
466   size_t datalen;
467
468   es_rewind (*stream_addr);
469   if (es_fclose_snatch (*stream_addr, &data, &datalen))
470     return gpg_error_from_syserror ();
471   *stream_addr = NULL;
472   return add_body (ctx, data, datalen);
473 }
474
475
476 /* Add a new MIME container.  A container can be used instead of a
477  * body.  */
478 gpg_error_t
479 mime_maker_add_container (mime_maker_t ctx)
480 {
481   gpg_error_t err;
482   part_t part;
483
484   err = ensure_part (ctx, NULL);
485   if (err)
486     return err;
487   part = ctx->current_part;
488
489   if (part->body)
490     return gpg_error (GPG_ERR_CONFLICT); /* There is already a body. */
491   if (part->child || part->boundary)
492     return gpg_error (GPG_ERR_CONFLICT); /* There is already a container. */
493
494   /* Create a child node.  */
495   part->child = xtrycalloc (1, sizeof *part->child);
496   if (!part->child)
497     return gpg_error_from_syserror ();
498   part->child->headers_tail = &part->child->headers;
499
500   part->boundary = generate_boundary (ctx);
501   if (!part->boundary)
502     {
503       err = gpg_error_from_syserror ();
504       xfree (part->child);
505       part->child = NULL;
506       return err;
507     }
508
509   part = part->child;
510   ctx->current_part = part;
511
512   return 0;
513 }
514
515
516 /* Finish the current container.  */
517 gpg_error_t
518 mime_maker_end_container (mime_maker_t ctx)
519 {
520   gpg_error_t err;
521   part_t parent;
522
523   err = ensure_part (ctx, &parent);
524   if (err)
525     return err;
526   if (!parent)
527     return gpg_error (GPG_ERR_CONFLICT); /* No container.  */
528   while (parent->next)
529     parent = parent->next;
530   ctx->current_part = parent;
531   return 0;
532 }
533
534
535 /* Write the Content-Type header with the boundary value.  */
536 static gpg_error_t
537 write_ct_with_boundary (mime_maker_t ctx,
538                         const char *value, const char *boundary)
539 {
540   const char *s;
541
542   if (!*value)
543     return gpg_error (GPG_ERR_INV_VALUE);  /* Empty string.  */
544
545   for (s=value + strlen (value) - 1;
546        (s >= value
547         && (*s == ' ' || *s == '\t' || *s == '\n'));
548        s--)
549     ;
550   if (!(s >= value))
551     return gpg_error (GPG_ERR_INV_VALUE);  /* Only spaces.  */
552
553   /* Fixme: We should use a dedicated header write functions which
554    * properly wraps the header.  */
555   es_fprintf (ctx->outfp, "Content-Type: %s%s\n\tboundary=\"%s\"\n",
556               value,
557               (*s == ';')? "":";",
558               boundary);
559   return 0;
560 }
561
562
563 /* Recursive worker for mime_maker_make.  */
564 static gpg_error_t
565 write_tree (mime_maker_t ctx, part_t parent, part_t part)
566 {
567   gpg_error_t err;
568   header_t hdr;
569
570   for (; part; part = part->next)
571     {
572       for (hdr = part->headers; hdr; hdr = hdr->next)
573         {
574           if (part->child && !strcmp (hdr->name, "Content-Type"))
575             write_ct_with_boundary (ctx, hdr->value, part->boundary);
576           else
577             es_fprintf (ctx->outfp, "%s: %s\n", hdr->name, hdr->value);
578         }
579       es_fputc ('\n', ctx->outfp);
580       if (part->body)
581         {
582           if (es_write (ctx->outfp, part->body, part->bodylen, NULL))
583             return gpg_error_from_syserror ();
584         }
585       if (part->child)
586         {
587           log_assert (part->boundary);
588           if (es_fprintf (ctx->outfp, "\n--%s\n", part->boundary) < 0)
589             return gpg_error_from_syserror ();
590           err = write_tree (ctx, part, part->child);
591           if (err)
592             return err;
593           if (es_fprintf (ctx->outfp, "\n--%s--\n", part->boundary) < 0)
594             return gpg_error_from_syserror ();
595         }
596
597       if (part->next)
598         {
599           log_assert (parent && parent->boundary);
600           if (es_fprintf (ctx->outfp, "\n--%s\n", parent->boundary) < 0)
601             return gpg_error_from_syserror ();
602         }
603     }
604   return 0;
605 }
606
607
608 /* Add headers we always require.  */
609 static gpg_error_t
610 add_missing_headers (mime_maker_t ctx)
611 {
612   gpg_error_t err;
613
614   if (!ctx->mail)
615     return gpg_error (GPG_ERR_NO_DATA);
616   if (!have_header (ctx->mail, "MIME-Version"))
617     {
618       /* Even if a Content-Type has never been set, we want to
619        * announce that we do MIME.  */
620       err = add_header (ctx->mail, "MIME-Version", "1.0");
621       if (err)
622         goto leave;
623     }
624
625   if (!have_header (ctx->mail, "Date"))
626     {
627       char *p = rfctimestamp (make_timestamp ());
628       if (!p)
629         err = gpg_error_from_syserror ();
630       else
631         err = add_header (ctx->mail, "Date", p);
632       xfree (p);
633       if (err)
634         goto leave;
635     }
636
637
638  leave:
639   return err;
640 }
641
642
643 /* Create message from the tree MIME and write it to FP.  Note that
644  * the output uses only a LF and a later called sendmail(1) is
645  * expected to convert them to network line endings.  */
646 gpg_error_t
647 mime_maker_make (mime_maker_t ctx, estream_t fp)
648 {
649   gpg_error_t err;
650
651   err = add_missing_headers (ctx);
652   if (err)
653     return err;
654
655   ctx->outfp = fp;
656   err = write_tree (ctx, NULL, ctx->mail);
657
658   ctx->outfp = NULL;
659   return err;
660 }