busybox/mailutils/makemime.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * makemime: create MIME-encoded message
   4 *
   5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
   6 *
   7 * Licensed under GPLv2, see file LICENSE in this source tree.
   8 */
   9//config:config MAKEMIME
  10//config:       bool "makemime (5.4 kb)"
  11//config:       default y
  12//config:       help
  13//config:       Create MIME-formatted messages.
  14
  15//applet:IF_MAKEMIME(APPLET(makemime, BB_DIR_BIN, BB_SUID_DROP))
  16
  17//kbuild:lib-$(CONFIG_MAKEMIME) += makemime.o mail.o
  18
  19#include "libbb.h"
  20#include "mail.h"
  21
  22#if 0
  23# define dbg_error_msg(...) bb_error_msg(__VA_ARGS__)
  24#else
  25# define dbg_error_msg(...) ((void)0)
  26#endif
  27
  28/*
  29  makemime -c type [-o file] [-e encoding] [-C charset] [-N name] \
  30                   [-a "Header: Contents"] file
  31           -m [ type ] [-o file] [-e encoding] [-a "Header: Contents"] file
  32           -j [-o file] file1 file2
  33           @file
  34
  35   file:  filename    - read or write from filename
  36          -           - read or write from stdin or stdout
  37          &n          - read or write from file descriptor n
  38          \( opts \)  - read from child process, that generates [ opts ]
  39
  40Options:
  41  -c type         - create a new MIME section from "file" with this
  42                    Content-Type: (default is application/octet-stream).
  43  -C charset      - MIME charset of a new text/plain section.
  44  -N name         - MIME content name of the new mime section.
  45  -m [ type ]     - create a multipart mime section from "file" of this
  46                    Content-Type: (default is multipart/mixed).
  47  -e encoding     - use the given encoding (7bit, 8bit, quoted-printable,
  48                    or base64), instead of guessing.  Omit "-e" and use
  49                    -c auto to set Content-Type: to text/plain or
  50                    application/octet-stream based on picked encoding.
  51  -j file1 file2  - join mime section file2 to multipart section file1.
  52  -o file         - write the result to file, instead of stdout (not
  53                    allowed in child processes).
  54  -a header       - prepend an additional header to the output.
  55
  56  @file - read all of the above options from file, one option or
  57          value on each line.
  58  {which version of makemime is this? What do we support?}
  59*/
  60/* man makemime:
  61
  62 * -c TYPE: create a (non-multipart) MIME section with Content-Type: TYPE
  63 * makemime -c TYPE [-e ENCODING] [-o OUTFILE] [-C CHARSET] [-N NAME] [-a HEADER...] FILE
  64 * The -C option sets the MIME charset attribute for text/plain content.
  65 * The -N option sets the name attribute for Content-Type:
  66 * Encoding must be one of the following: 7bit, 8bit, quoted-printable, or base64.
  67
  68 * -m multipart/TYPE: create a multipart MIME collection with Content-Type: multipart/TYPE
  69 * makemime -m multipart/TYPE [-e ENCODING] [-o OUTFILE] [-a HEADER...] FILE
  70 * Type must be either "multipart/mixed", "multipart/alternative", or some other MIME multipart content type.
  71 * Additionally, encoding can only be "7bit" or "8bit", and will default to "8bit" if not specified.
  72 * Finally, filename must be a MIME-formatted section, NOT a regular file.
  73 * The -m option creates an initial multipart MIME collection, that contains only one MIME section, taken from filename.
  74 * The collection is written to standard output, or the pipe or to outputfile.
  75
  76 * -j FILE1: add a section to a multipart MIME collection
  77 * makemime -j FILE1 [-o OUTFILE] FILE2
  78 * FILE1 must be a MIME collection that was previously created by the -m option.
  79 * FILE2 must be a MIME section that was previously created by the -c option.
  80 * The -j options adds the MIME section in FILE2 to the MIME collection in FILE1.
  81 */
  82
  83
  84/* In busybox 1.15.0.svn, makemime generates output like this
  85 * (empty lines are shown exactly!):
  86{headers added with -a HDR}
  87Mime-Version: 1.0
  88Content-Type: multipart/mixed; boundary="24269534-2145583448-1655890676"
  89
  90--24269534-2145583448-1655890676
  91Content-Type: {set by -c, e.g. text/plain}; charset={set by -C, e.g. us-ascii}
  92Content-Disposition: inline; filename="A"
  93Content-Transfer-Encoding: base64
  94
  95...file A contents...
  96--24269534-2145583448-1655890676
  97Content-Type: {set by -c, e.g. text/plain}; charset={set by -C, e.g. us-ascii}
  98Content-Disposition: inline; filename="B"
  99Content-Transfer-Encoding: base64
 100
 101...file B contents...
 102--24269534-2145583448-1655890676--
 103
 104 *
 105 * For reference: here is an example email to LKML which has
 106 * 1st unnamed part (so it serves as an email body)
 107 * and one attached file:
 108...other headers...
 109Content-Type: multipart/mixed; boundary="=-tOfTf3byOS0vZgxEWcX+"
 110...other headers...
 111Mime-Version: 1.0
 112...other headers...
 113
 114
 115--=-tOfTf3byOS0vZgxEWcX+
 116Content-Type: text/plain
 117Content-Transfer-Encoding: 7bit
 118
 119...email text...
 120...email text...
 121
 122
 123--=-tOfTf3byOS0vZgxEWcX+
 124Content-Disposition: attachment; filename="xyz"
 125Content-Type: text/plain; name="xyz"; charset="UTF-8"
 126Content-Transfer-Encoding: 7bit
 127
 128...file contents...
 129...file contents...
 130
 131--=-tOfTf3byOS0vZgxEWcX+--
 132
 133...random junk added by mailing list robots and such...
 134*/
 135
 136//usage:#define makemime_trivial_usage
 137//usage:       "[OPTIONS] [FILE]..."
 138//usage:#define makemime_full_usage "\n\n"
 139//usage:       "Create multipart MIME-encoded message from FILEs\n"
 140/* //usage:    "Transfer encoding is base64, disposition is inline (not attachment)\n" */
 141//usage:     "\n        -o FILE Output. Default: stdout"
 142//usage:     "\n        -a HDR  Add header(s). Examples:"
 143//usage:     "\n                \"From: user@host.org\", \"Date: `date -R`\""
 144//usage:     "\n        -c CT   Content type. Default: application/octet-stream"
 145//usage:     "\n        -C CS   Charset. Default: " CONFIG_FEATURE_MIME_CHARSET
 146/* //usage:  "\n        -e ENC  Transfer encoding. Ignored. base64 is assumed" */
 147//usage:     "\n"
 148//usage:     "\nOther options are silently ignored"
 149
 150/*
 151 * -c [Content-Type] should create just one MIME section
 152 * with "Content-Type:", "Content-Transfer-Encoding:", and HDRs from "-a HDR".
 153 * NB: without "Content-Disposition:" auto-added, unlike we do now
 154 * NB2: -c has *optional* param which nevertheless _can_ be specified after a space :(
 155 *
 156 * -m [multipart/mixed] should create multipart MIME section
 157 * with "Content-Type:", "Content-Transfer-Encoding:", and HDRs from "-a HDR",
 158 * and add FILE to it _verbatim_:
 159 *  HEADERS
 160 *
 161 *  --=_1_1321709112_1605
 162 *  FILE_CONTENTS
 163 *  --=_1_1321709112_1605
 164 * without any encoding of FILE_CONTENTS. (Basically, it expects that FILE
 165 * is the result of "makemime -c").
 166 *
 167 * -j MULTIPART_FILE1 SINGLE_FILE2 should output MULTIPART_FILE1 + SINGLE_FILE2
 168 *
 169 * Our current behavior is a mutant "-m + -c + -j" one: we create multipart MIME
 170 * and we put "-c" encoded FILEs into many multipart sections.
 171 */
 172
 173int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 174int makemime_main(int argc UNUSED_PARAM, char **argv)
 175{
 176        unsigned opts;
 177        llist_t *opt_headers = NULL, *l;
 178        const char *opt_output;
 179        const char *content_type = "application/octet-stream";
 180#define boundary opt_output
 181        enum {
 182                OPT_c = 1 << 0,         // create (non-multipart) section
 183                OPT_e = 1 << 1,         // Content-Transfer-Encoding. Ignored. Assumed base64
 184                OPT_o = 1 << 2,         // output to
 185                OPT_C = 1 << 3,         // charset
 186                OPT_N = 1 << 4,         // COMPAT
 187                OPT_a = 1 << 5,         // additional headers
 188                //OPT_m = 1 << 6,         // create mutipart section
 189                //OPT_j = 1 << 7,         // join section to multipart section
 190        };
 191
 192        INIT_G();
 193
 194        // parse options
 195        opts = getopt32(argv,
 196                "c:e:o:C:N:a:*", // "m:j:",
 197                &content_type, NULL, &opt_output, &G.opt_charset, NULL, &opt_headers //, NULL, NULL
 198        );
 199        //argc -= optind;
 200        argv += optind;
 201
 202        // respect -o output
 203        if (opts & OPT_o)
 204                freopen(opt_output, "w", stdout);
 205
 206        // no files given on command line? -> use stdin
 207        if (!*argv)
 208                *--argv = (char *)"-";
 209
 210        // put additional headers
 211        for (l = opt_headers; l; l = l->link)
 212                puts(l->data);
 213
 214        // make a random string -- it will delimit message parts
 215        srand(monotonic_us());
 216        boundary = xasprintf("%u-%u-%u",
 217                        (unsigned)rand(), (unsigned)rand(), (unsigned)rand());
 218
 219        // put multipart header
 220        printf(
 221                "Mime-Version: 1.0\n"
 222                "Content-Type: multipart/mixed; boundary=\"%s\"\n"
 223                , boundary
 224        );
 225
 226        // put attachments
 227        while (*argv) {
 228                printf(
 229                        "\n--%s\n"
 230                        "Content-Type: %s; charset=%s\n"
 231                        "Content-Disposition: inline; filename=\"%s\"\n"
 232                        "Content-Transfer-Encoding: base64\n"
 233                        , boundary
 234                        , content_type
 235                        , G.opt_charset
 236                        , bb_get_last_path_component_strip(*argv)
 237                );
 238                printfile_base64(*argv++);
 239        }
 240
 241        // put multipart footer
 242        printf("\n--%s--\n" "\n", boundary);
 243
 244        return EXIT_SUCCESS;
 245#undef boundary
 246}
 247