Files
applets
arch
archival
console-tools
coreutils
debianutils
docs
e2fsprogs
editors
examples
findutils
include
init
libbb
libpwdgrp
loginutils
mailutils
Config.in
Kbuild
mail.c
mail.h
mime.c
popmaildir.c
sendmail.c
miscutils
modutils
networking
printutils
procps
runit
scripts
selinux
shell
sysklogd
testsuite
util-linux
.gitignore
.indent.pro
AUTHORS
Config.in
INSTALL
LICENSE
Makefile
Makefile.custom
Makefile.flags
Makefile.help
README
TEST_config_rh9
TODO
TODO_config_nommu
TODO_unicode
busybox/mailutils/mime.c
Denys Vlasenko 8531d76a15 *: code shrink and better "died from signal" reporting from wait4pid
function                                             old     new   delta
parse                                                964     967      +3
udhcp_run_script                                     670     665      -5
singlemount                                          911     906      -5
mount_it_now                                         360     355      -5
inotifyd_main                                        521     516      -5
xspawn                                                21       -     -21
------------------------------------------------------------------------------
(add/remove: 0/1 grow/shrink: 1/4 up/down: 3/-41)             Total: -38 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2010-03-18 22:44:00 +01:00

418 lines
12 KiB
C

/* vi: set sw=4 ts=4: */
/*
* makemime: create MIME-encoded message
* reformime: parse MIME-encoded message
*
* Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
*
* Licensed under GPLv2, see file LICENSE in this tarball for details.
*/
#include "libbb.h"
#include "mail.h"
/*
makemime -c type [-o file] [-e encoding] [-C charset] [-N name] \
[-a "Header: Contents"] file
-m [ type ] [-o file] [-e encoding] [-a "Header: Contents"] file
-j [-o file] file1 file2
@file
file: filename - read or write from filename
- - read or write from stdin or stdout
&n - read or write from file descriptor n
\( opts \) - read from child process, that generates [ opts ]
Options:
-c type - create a new MIME section from "file" with this
Content-Type: (default is application/octet-stream).
-C charset - MIME charset of a new text/plain section.
-N name - MIME content name of the new mime section.
-m [ type ] - create a multipart mime section from "file" of this
Content-Type: (default is multipart/mixed).
-e encoding - use the given encoding (7bit, 8bit, quoted-printable,
or base64), instead of guessing. Omit "-e" and use
-c auto to set Content-Type: to text/plain or
application/octet-stream based on picked encoding.
-j file1 file2 - join mime section file2 to multipart section file1.
-o file - write the result to file, instead of stdout (not
allowed in child processes).
-a header - prepend an additional header to the output.
@file - read all of the above options from file, one option or
value on each line.
{which version of makemime is this? What do we support?}
*/
/* In busybox 1.15.0.svn, makemime generates output like this
* (empty lines are shown exactly!):
{headers added with -a HDR}
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="24269534-2145583448-1655890676"
--24269534-2145583448-1655890676
Content-Type: {set by -c, e.g. text/plain}; charset={set by -C, e.g. us-ascii}
Content-Disposition: inline; filename="A"
Content-Transfer-Encoding: base64
...file A contents...
--24269534-2145583448-1655890676
Content-Type: {set by -c, e.g. text/plain}; charset={set by -C, e.g. us-ascii}
Content-Disposition: inline; filename="B"
Content-Transfer-Encoding: base64
...file B contents...
--24269534-2145583448-1655890676--
*/
/* For reference: here is an example email to LKML which has
* 1st unnamed part (so it serves as an email body)
* and one attached file:
...other headers...
Content-Type: multipart/mixed; boundary="=-tOfTf3byOS0vZgxEWcX+"
...other headers...
Mime-Version: 1.0
...other headers...
--=-tOfTf3byOS0vZgxEWcX+
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
...email text...
...email text...
--=-tOfTf3byOS0vZgxEWcX+
Content-Disposition: attachment; filename="xyz"
Content-Type: text/plain; name="xyz"; charset="UTF-8"
Content-Transfer-Encoding: 7bit
...file contents...
...file contents...
--=-tOfTf3byOS0vZgxEWcX+--
...random junk added by mailing list robots and such...
*/
int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int makemime_main(int argc UNUSED_PARAM, char **argv)
{
llist_t *opt_headers = NULL, *l;
const char *opt_output;
#define boundary opt_output
enum {
OPT_c = 1 << 0, // Content-Type:
OPT_e = 1 << 1, // Content-Transfer-Encoding. Ignored. Assumed base64
OPT_o = 1 << 2, // output to
OPT_C = 1 << 3, // charset
OPT_N = 1 << 4, // COMPAT
OPT_a = 1 << 5, // additional headers
OPT_m = 1 << 6, // COMPAT
OPT_j = 1 << 7, // COMPAT
};
INIT_G();
// parse options
opt_complementary = "a::";
opts = getopt32(argv,
"c:e:o:C:N:a:m:j:",
&G.content_type, NULL, &opt_output, &G.opt_charset, NULL, &opt_headers, NULL, NULL
);
//argc -= optind;
argv += optind;
// respect -o output
if (opts & OPT_o)
freopen(opt_output, "w", stdout);
// no files given on command line? -> use stdin
if (!*argv)
*--argv = (char *)"-";
// put additional headers
for (l = opt_headers; l; l = l->link)
puts(l->data);
// make a random string -- it will delimit message parts
srand(monotonic_us());
boundary = xasprintf("%u-%u-%u",
(unsigned)rand(), (unsigned)rand(), (unsigned)rand());
// put multipart header
printf(
"Mime-Version: 1.0\n"
"Content-Type: multipart/mixed; boundary=\"%s\"\n"
, boundary
);
// put attachments
while (*argv) {
printf(
"\n--%s\n"
"Content-Type: %s; charset=%s\n"
"Content-Disposition: inline; filename=\"%s\"\n"
"Content-Transfer-Encoding: base64\n"
, boundary
, G.content_type
, G.opt_charset
, bb_get_last_path_component_strip(*argv)
);
encode_base64(*argv++, (const char *)stdin, "");
}
// put multipart footer
printf("\n--%s--\n" "\n", boundary);
return EXIT_SUCCESS;
#undef boundary
}
static const char *find_token(const char *const string_array[], const char *key, const char *defvalue)
{
const char *r = NULL;
int i;
for (i = 0; string_array[i] != NULL; i++) {
if (strcasecmp(string_array[i], key) == 0) {
r = (char *)string_array[i+1];
break;
}
}
return (r) ? r : defvalue;
}
static const char *xfind_token(const char *const string_array[], const char *key)
{
const char *r = find_token(string_array, key, NULL);
if (r)
return r;
bb_error_msg_and_die("header: %s", key);
}
enum {
OPT_x = 1 << 0,
OPT_X = 1 << 1,
#if ENABLE_FEATURE_REFORMIME_COMPAT
OPT_d = 1 << 2,
OPT_e = 1 << 3,
OPT_i = 1 << 4,
OPT_s = 1 << 5,
OPT_r = 1 << 6,
OPT_c = 1 << 7,
OPT_m = 1 << 8,
OPT_h = 1 << 9,
OPT_o = 1 << 10,
OPT_O = 1 << 11,
#endif
};
static int parse(const char *boundary, char **argv)
{
char *line, *s, *p;
const char *type;
int boundary_len = strlen(boundary);
const char *delims = " ;\"\t\r\n";
const char *uniq;
int ntokens;
const char *tokens[32]; // 32 is enough
// prepare unique string pattern
uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname());
//bb_info_msg("PARSE[%s]", terminator);
while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) {
// seek to start of MIME section
// N.B. to avoid false positives let us seek to the _last_ occurance
p = NULL;
s = line;
while ((s = strcasestr(s, "Content-Type:")) != NULL)
p = s++;
if (!p)
goto next;
//bb_info_msg("L[%s]", p);
// split to tokens
// TODO: strip of comments which are of form: (comment-text)
ntokens = 0;
tokens[ntokens] = NULL;
for (s = strtok(p, delims); s; s = strtok(NULL, delims)) {
tokens[ntokens] = s;
if (ntokens < ARRAY_SIZE(tokens) - 1)
ntokens++;
//bb_info_msg("L[%d][%s]", ntokens, s);
}
tokens[ntokens] = NULL;
//bb_info_msg("N[%d]", ntokens);
// analyse tokens
type = find_token(tokens, "Content-Type:", "text/plain");
//bb_info_msg("T[%s]", type);
if (0 == strncasecmp(type, "multipart/", 10)) {
if (0 == strcasecmp(type+10, "mixed")) {
parse(xfind_token(tokens, "boundary="), argv);
} else
bb_error_msg_and_die("no support of content type '%s'", type);
} else {
pid_t pid = pid;
int rc;
FILE *fp;
// fetch charset
const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET);
// fetch encoding
const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit");
// compose target filename
char *filename = (char *)find_token(tokens, "filename=", NULL);
if (!filename)
filename = xasprintf(uniq, monotonic_us());
else
filename = bb_get_last_path_component_strip(xstrdup(filename));
// start external helper, if any
if (opts & OPT_X) {
int fd[2];
xpipe(fd);
pid = vfork();
if (0 == pid) {
// child reads from fd[0]
close(fd[1]);
xmove_fd(fd[0], STDIN_FILENO);
xsetenv("CONTENT_TYPE", type);
xsetenv("CHARSET", charset);
xsetenv("ENCODING", encoding);
xsetenv("FILENAME", filename);
BB_EXECVP(*argv, argv);
_exit(EXIT_FAILURE);
}
// parent dumps to fd[1]
close(fd[0]);
fp = xfdopen_for_write(fd[1]);
signal(SIGPIPE, SIG_IGN); // ignore EPIPE
// or create a file for dump
} else {
char *fname = xasprintf("%s%s", *argv, filename);
fp = xfopen_for_write(fname);
free(fname);
}
// housekeeping
free(filename);
// dump to fp
if (0 == strcasecmp(encoding, "base64")) {
decode_base64(stdin, fp);
} else if (0 != strcasecmp(encoding, "7bit")
&& 0 != strcasecmp(encoding, "8bit")
) {
// quoted-printable, binary, user-defined are unsupported so far
bb_error_msg_and_die("no support of encoding '%s'", encoding);
} else {
// N.B. we have written redundant \n. so truncate the file
// The following weird 2-tacts reading technique is due to
// we have to not write extra \n at the end of the file
// In case of -x option we could truncate the resulting file as
// fseek(fp, -1, SEEK_END);
// if (ftruncate(fileno(fp), ftell(fp)))
// bb_perror_msg("ftruncate");
// But in case of -X we have to be much more careful. There is
// no means to truncate what we already have sent to the helper.
p = xmalloc_fgets_str(stdin, "\r\n");
while (p) {
s = xmalloc_fgets_str(stdin, "\r\n");
if (s == NULL)
break;
if ('-' == s[0]
&& '-' == s[1]
&& 0 == strncmp(s+2, boundary, boundary_len)
) {
break;
}
fputs(p, fp);
p = s;
}
/*
while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) {
if ('-' == s[0] && '-' == s[1]
&& 0 == strncmp(s+2, boundary, boundary_len))
break;
fprintf(fp, "%s\n", s);
}
// N.B. we have written redundant \n. so truncate the file
fseek(fp, -1, SEEK_END);
if (ftruncate(fileno(fp), ftell(fp)))
bb_perror_msg("ftruncate");
*/
}
fclose(fp);
// finalize helper
if (opts & OPT_X) {
signal(SIGPIPE, SIG_DFL);
// exit if helper exited >0
rc = (wait4pid(pid) & 0xff);
if (rc)
return rc+20;
}
// check multipart finalized
if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) {
free(line);
break;
}
}
next:
free(line);
}
//bb_info_msg("ENDPARSE[%s]", boundary);
return EXIT_SUCCESS;
}
/*
Usage: reformime [options]
-d - parse a delivery status notification.
-e - extract contents of MIME section.
-x - extract MIME section to a file.
-X - pipe MIME section to a program.
-i - show MIME info.
-s n.n.n.n - specify MIME section.
-r - rewrite message, filling in missing MIME headers.
-r7 - also convert 8bit/raw encoding to quoted-printable, if possible.
-r8 - also convert quoted-printable encoding to 8bit, if possible.
-c charset - default charset for rewriting, -o, and -O.
-m [file] [file]... - create a MIME message digest.
-h "header" - decode RFC 2047-encoded header.
-o "header" - encode unstructured header using RFC 2047.
-O "header" - encode address list header using RFC 2047.
*/
int reformime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int reformime_main(int argc UNUSED_PARAM, char **argv)
{
const char *opt_prefix = "";
INIT_G();
// parse options
// N.B. only -x and -X are supported so far
opt_complementary = "x--X:X--x" IF_FEATURE_REFORMIME_COMPAT(":m::");
opts = getopt32(argv,
"x:X" IF_FEATURE_REFORMIME_COMPAT("deis:r:c:m:h:o:O:"),
&opt_prefix
IF_FEATURE_REFORMIME_COMPAT(, NULL, NULL, &G.opt_charset, NULL, NULL, NULL, NULL)
);
//argc -= optind;
argv += optind;
return parse("", (opts & OPT_X) ? argv : (char **)&opt_prefix);
}