busybox/mailutils/mime.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * makemime: create MIME-encoded message
   4 * reformime: parse MIME-encoded message
   5 *
   6 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
   7 *
   8 * Licensed under GPLv2, see file LICENSE in this tarball for details.
   9 */
  10#include "libbb.h"
  11#include "mail.h"
  12
  13/*
  14  makemime -c type [-o file] [-e encoding] [-C charset] [-N name] \
  15                   [-a "Header: Contents"] file
  16           -m [ type ] [-o file] [-e encoding] [-a "Header: Contents"] file
  17           -j [-o file] file1 file2
  18           @file
  19
  20   file:  filename    - read or write from filename
  21          -           - read or write from stdin or stdout
  22          &n          - read or write from file descriptor n
  23          \( opts \)  - read from child process, that generates [ opts ]
  24
  25Options:
  26
  27  -c type         - create a new MIME section from "file" with this
  28                    Content-Type: (default is application/octet-stream).
  29  -C charset      - MIME charset of a new text/plain section.
  30  -N name         - MIME content name of the new mime section.
  31  -m [ type ]     - create a multipart mime section from "file" of this
  32                    Content-Type: (default is multipart/mixed).
  33  -e encoding     - use the given encoding (7bit, 8bit, quoted-printable,
  34                    or base64), instead of guessing.  Omit "-e" and use
  35                    -c auto to set Content-Type: to text/plain or
  36                    application/octet-stream based on picked encoding.
  37  -j file1 file2  - join mime section file2 to multipart section file1.
  38  -o file         - write ther result to file, instead of stdout (not
  39                    allowed in child processes).
  40  -a header       - prepend an additional header to the output.
  41
  42  @file - read all of the above options from file, one option or
  43          value on each line.
  44*/
  45
  46int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  47int makemime_main(int argc UNUSED_PARAM, char **argv)
  48{
  49        llist_t *opt_headers = NULL, *l;
  50        const char *opt_output;
  51#define boundary opt_output
  52
  53        enum {
  54                OPT_c = 1 << 0,         // Content-Type:
  55                OPT_e = 1 << 1,         // Content-Transfer-Encoding. Ignored. Assumed base64
  56                OPT_o = 1 << 2,         // output to
  57                OPT_C = 1 << 3,         // charset
  58                OPT_N = 1 << 4,         // COMPAT
  59                OPT_a = 1 << 5,         // additional headers
  60                OPT_m = 1 << 6,         // COMPAT
  61                OPT_j = 1 << 7,         // COMPAT
  62        };
  63
  64        INIT_G();
  65
  66        // parse options
  67        opt_complementary = "a::";
  68        opts = getopt32(argv,
  69                "c:e:o:C:N:a:m:j:",
  70                &G.content_type, NULL, &opt_output, &G.opt_charset, NULL, &opt_headers, NULL, NULL
  71        );
  72        //argc -= optind;
  73        argv += optind;
  74
  75        // respect -o output
  76        if (opts & OPT_o)
  77                freopen(opt_output, "w", stdout);
  78
  79        // no files given on command line? -> use stdin
  80        if (!*argv)
  81                *--argv = (char *)"-";
  82
  83        // put additional headers
  84        for (l = opt_headers; l; l = l->link)
  85                puts(l->data);
  86
  87        // make a random string -- it will delimit message parts
  88        srand(monotonic_us());
  89        boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
  90
  91        // put multipart header
  92        printf(
  93                "Mime-Version: 1.0\n"
  94                "Content-Type: multipart/mixed; boundary=\"%s\"\n"
  95                , boundary
  96        );
  97
  98        // put attachments
  99        while (*argv) {
 100                printf(
 101                        "\n--%s\n"
 102                        "Content-Type: %s; charset=%s\n"
 103                        "Content-Disposition: inline; filename=\"%s\"\n"
 104                        "Content-Transfer-Encoding: base64\n"
 105                        , boundary
 106                        , G.content_type
 107                        , G.opt_charset
 108                        , bb_get_last_path_component_strip(*argv)
 109                );
 110                encode_base64(*argv++, (const char *)stdin, "");
 111        }
 112
 113        // put multipart footer
 114        printf("\n--%s--\n" "\n", boundary);
 115
 116        return EXIT_SUCCESS;
 117#undef boundary
 118}
 119
 120static const char *find_token(const char *const string_array[], const char *key, const char *defvalue)
 121{
 122        const char *r = NULL;
 123        for (int i = 0; string_array[i] != 0; i++) {
 124                if (strcasecmp(string_array[i], key) == 0) {
 125                        r = (char *)string_array[i+1];
 126                        break;
 127                }
 128        }
 129        return (r) ? r : defvalue;
 130}
 131
 132static const char *xfind_token(const char *const string_array[], const char *key)
 133{
 134        const char *r = find_token(string_array, key, NULL);
 135        if (r)
 136                return r;
 137        bb_error_msg_and_die("header: %s", key);
 138}
 139
 140enum {
 141        OPT_x = 1 << 0,
 142        OPT_X = 1 << 1,
 143#if ENABLE_FEATURE_REFORMIME_COMPAT
 144        OPT_d = 1 << 2,
 145        OPT_e = 1 << 3,
 146        OPT_i = 1 << 4,
 147        OPT_s = 1 << 5,
 148        OPT_r = 1 << 6,
 149        OPT_c = 1 << 7,
 150        OPT_m = 1 << 8,
 151        OPT_h = 1 << 9,
 152        OPT_o = 1 << 10,
 153        OPT_O = 1 << 11,
 154#endif
 155};
 156
 157static int parse(const char *boundary, char **argv)
 158{
 159        char *line, *s, *p;
 160        const char *type;
 161        int boundary_len = strlen(boundary);
 162        const char *delims = " ;\"\t\r\n";
 163        const char *uniq;
 164        int ntokens;
 165        const char *tokens[32]; // 32 is enough
 166
 167        // prepare unique string pattern
 168        uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname());
 169
 170//bb_info_msg("PARSE[%s]", terminator);
 171
 172        while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) {
 173
 174                // seek to start of MIME section
 175                // N.B. to avoid false positives let us seek to the _last_ occurance
 176                p = NULL;
 177                s = line;
 178                while ((s=strcasestr(s, "Content-Type:")) != NULL)
 179                        p = s++;
 180                if (!p)
 181                        goto next;
 182//bb_info_msg("L[%s]", p);
 183
 184                // split to tokens
 185                // TODO: strip of comments which are of form: (comment-text)
 186                ntokens = 0;
 187                tokens[ntokens] = NULL;
 188                for (s = strtok(p, delims); s; s = strtok(NULL, delims)) {
 189                        tokens[ntokens] = s;
 190                        if (ntokens < ARRAY_SIZE(tokens) - 1)
 191                                ntokens++;
 192//bb_info_msg("L[%d][%s]", ntokens, s);
 193                }
 194                tokens[ntokens] = NULL;
 195//bb_info_msg("N[%d]", ntokens);
 196
 197                // analyse tokens
 198                type = find_token(tokens, "Content-Type:", "text/plain");
 199//bb_info_msg("T[%s]", type);
 200                if (0 == strncasecmp(type, "multipart/", 10)) {
 201                        if (0 == strcasecmp(type+10, "mixed")) {
 202                                parse(xfind_token(tokens, "boundary="), argv);
 203                        } else
 204                                bb_error_msg_and_die("no support of content type '%s'", type);
 205                } else {
 206                        pid_t pid = pid;
 207                        int rc;
 208                        FILE *fp;
 209                        // fetch charset
 210                        const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET);
 211                        // fetch encoding
 212                        const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit");
 213                        // compose target filename
 214                        char *filename = (char *)find_token(tokens, "filename=", NULL);
 215                        if (!filename)
 216                                filename = xasprintf(uniq, monotonic_us());
 217                        else
 218                                filename = bb_get_last_path_component_strip(xstrdup(filename));
 219
 220                        // start external helper, if any
 221                        if (opts & OPT_X) {
 222                                int fd[2];
 223                                xpipe(fd);
 224                                pid = vfork();
 225                                if (0 == pid) {
 226                                        // child reads from fd[0]
 227                                        xdup2(fd[0], STDIN_FILENO);
 228                                        close(fd[0]); close(fd[1]);
 229                                        xsetenv("CONTENT_TYPE", type);
 230                                        xsetenv("CHARSET", charset);
 231                                        xsetenv("ENCODING", encoding);
 232                                        xsetenv("FILENAME", filename);
 233                                        BB_EXECVP(*argv, argv);
 234                                        _exit(EXIT_FAILURE);
 235                                }
 236                                // parent dumps to fd[1]
 237                                close(fd[0]);
 238                                fp = fdopen(fd[1], "w");
 239                                signal(SIGPIPE, SIG_IGN); // ignore EPIPE
 240                        // or create a file for dump
 241                        } else {
 242                                char *fname = xasprintf("%s%s", *argv, filename);
 243                                fp = xfopen_for_write(fname);
 244                                free(fname);
 245                        }
 246
 247                        // housekeeping
 248                        free(filename);
 249
 250                        // dump to fp
 251                        if (0 == strcasecmp(encoding, "base64")) {
 252                                decode_base64(stdin, fp);
 253                        } else if (0 != strcasecmp(encoding, "7bit")
 254                                && 0 != strcasecmp(encoding, "8bit")) {
 255                                // quoted-printable, binary, user-defined are unsupported so far
 256                                bb_error_msg_and_die("no support of encoding '%s'", encoding);
 257                        } else {
 258                                // N.B. we have written redundant \n. so truncate the file
 259                                // The following weird 2-tacts reading technique is due to
 260                                // we have to not write extra \n at the end of the file
 261                                // In case of -x option we could truncate the resulting file as
 262                                // fseek(fp, -1, SEEK_END);
 263                                // if (ftruncate(fileno(fp), ftell(fp)))
 264                                //      bb_perror_msg("ftruncate");
 265                                // But in case of -X we have to be much more careful. There is
 266                                // no means to truncate what we already have sent to the helper.
 267                                p = xmalloc_fgets_str(stdin, "\r\n");
 268                                while (p) {
 269                                        if ((s = xmalloc_fgets_str(stdin, "\r\n")) == NULL)
 270                                                break;
 271                                        if ('-' == s[0] && '-' == s[1]
 272                                                && 0 == strncmp(s+2, boundary, boundary_len))
 273                                                break;
 274                                        fputs(p, fp);
 275                                        p = s;
 276                                }
 277
 278/*
 279                                while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) {
 280                                        if ('-' == s[0] && '-' == s[1]
 281                                                && 0 == strncmp(s+2, boundary, boundary_len))
 282                                                break;
 283                                        fprintf(fp, "%s\n", s);
 284                                }
 285                                // N.B. we have written redundant \n. so truncate the file
 286                                fseek(fp, -1, SEEK_END);
 287                                if (ftruncate(fileno(fp), ftell(fp)))
 288                                        bb_perror_msg("ftruncate");
 289*/
 290                        }
 291                        fclose(fp);
 292
 293                        // finalize helper
 294                        if (opts & OPT_X) {
 295                                signal(SIGPIPE, SIG_DFL);
 296                                // exit if helper exited >0
 297                                rc = wait4pid(pid);
 298                                if (rc)
 299                                        return rc+20;
 300                        }
 301
 302                        // check multipart finalized
 303                        if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) {
 304                                free(line);
 305                                break;
 306                        }
 307                }
 308 next:
 309                free(line);
 310        }
 311
 312//bb_info_msg("ENDPARSE[%s]", boundary);
 313
 314        return EXIT_SUCCESS;
 315}
 316
 317/*
 318Usage: reformime [options]
 319    -d - parse a delivery status notification.
 320    -e - extract contents of MIME section.
 321    -x - extract MIME section to a file.
 322    -X - pipe MIME section to a program.
 323    -i - show MIME info.
 324    -s n.n.n.n - specify MIME section.
 325    -r - rewrite message, filling in missing MIME headers.
 326    -r7 - also convert 8bit/raw encoding to quoted-printable, if possible.
 327    -r8 - also convert quoted-printable encoding to 8bit, if possible.
 328    -c charset - default charset for rewriting, -o, and -O.
 329    -m [file] [file]... - create a MIME message digest.
 330    -h "header" - decode RFC 2047-encoded header.
 331    -o "header" - encode unstructured header using RFC 2047.
 332    -O "header" - encode address list header using RFC 2047.
 333*/
 334
 335int reformime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 336int reformime_main(int argc UNUSED_PARAM, char **argv)
 337{
 338        const char *opt_prefix = "";
 339
 340        INIT_G();
 341
 342        // parse options
 343        // N.B. only -x and -X are supported so far
 344        opt_complementary = "x--X:X--x" USE_FEATURE_REFORMIME_COMPAT(":m::");
 345        opts = getopt32(argv,
 346                "x:X" USE_FEATURE_REFORMIME_COMPAT("deis:r:c:m:h:o:O:"),
 347                &opt_prefix
 348                USE_FEATURE_REFORMIME_COMPAT(, NULL, NULL, &G.opt_charset, NULL, NULL, NULL, NULL)
 349        );
 350        //argc -= optind;
 351        argv += optind;
 352
 353        return parse("", (opts & OPT_X) ? argv : (char **)&opt_prefix);
 354}
 355