1 /* vi: set sw=4 ts=4: */
3 * reformime: parse MIME-encoded message
5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
7 * Licensed under GPLv2, see file LICENSE in this source tree.
10 //kbuild:lib-$(CONFIG_REFORMIME) += reformime.o mail.o
16 # define dbg_error_msg(...) bb_error_msg(__VA_ARGS__)
18 # define dbg_error_msg(...) ((void)0)
21 static const char *find_token(const char *const string_array
[], const char *key
, const char *defvalue
)
25 for (i
= 0; string_array
[i
] != NULL
; i
++) {
26 if (strcasecmp(string_array
[i
], key
) == 0) {
27 r
= (char *)string_array
[i
+1];
31 return (r
) ? r
: defvalue
;
34 static const char *xfind_token(const char *const string_array
[], const char *key
)
36 const char *r
= find_token(string_array
, key
, NULL
);
39 bb_error_msg_and_die("not found: '%s'", key
);
45 #if ENABLE_FEATURE_REFORMIME_COMPAT
59 static int parse(const char *boundary
, char **argv
)
61 int boundary_len
= strlen(boundary
);
62 char uniq
[sizeof("%%llu.%u") + sizeof(int)*3];
64 dbg_error_msg("BOUNDARY[%s]", boundary
);
66 // prepare unique string pattern
67 sprintf(uniq
, "%%llu.%u", (unsigned)getpid());
68 dbg_error_msg("UNIQ[%s]", uniq
);
72 const char *tokens
[32]; /* 32 is enough */
75 /* Read the header (everything up to two \n) */
77 unsigned header_idx
= 0;
81 int ch
= fgetc(stdin
);
82 if (ch
== '\r') /* Support both line endings */
86 if (ch
== '\n' && last_ch
== ch
)
88 if (!(header_idx
& 0xff))
89 header
= xrealloc(header
, header_idx
+ 0x101);
90 header
[header_idx
++] = last_ch
= ch
;
96 header
[header_idx
] = '\0';
97 dbg_error_msg("H:'%s'", p
);
100 /* Split to tokens */
104 const char *delims
= ";=\" \t\n";
106 /* Skip to last Content-Type: */
108 while ((p
= strchr(p
, '\n')) != NULL
) {
110 if (strncasecmp(p
, "Content-Type:", sizeof("Content-Type:")-1) == 0)
113 dbg_error_msg("L:'%s'", p
);
115 s
= strtok(s
, delims
);
118 if (ntokens
< ARRAY_SIZE(tokens
) - 1)
120 dbg_error_msg("L[%d]='%s'", ntokens
, s
);
121 s
= strtok(NULL
, delims
);
123 tokens
[ntokens
] = NULL
;
124 dbg_error_msg("EMPTYLINE, ntokens:%d", ntokens
);
129 /* Is it multipart? */
130 type
= find_token(tokens
, "Content-Type:", "text/plain");
131 dbg_error_msg("TYPE:'%s'", type
);
132 if (0 == strncasecmp(type
, "multipart/", 10)) {
134 if (strcasecmp(type
+ 10, "mixed") != 0)
135 bb_error_msg_and_die("no support of content type '%s'", type
);
136 parse(xfind_token(tokens
, "boundary"), argv
);
139 /* No, process one non-multipart section */
144 const char *charset
= find_token(tokens
, "charset", CONFIG_FEATURE_MIME_CHARSET
);
145 const char *encoding
= find_token(tokens
, "Content-Transfer-Encoding:", "7bit");
147 /* Compose target filename */
148 char *filename
= (char *)find_token(tokens
, "filename", NULL
);
150 filename
= xasprintf(uniq
, monotonic_us());
152 filename
= bb_get_last_path_component_strip(xstrdup(filename
));
157 /* start external helper */
161 /* child reads from fd[0] */
163 xmove_fd(fd
[0], STDIN_FILENO
);
164 xsetenv("CONTENT_TYPE", type
);
165 xsetenv("CHARSET", charset
);
166 xsetenv("ENCODING", encoding
);
167 xsetenv("FILENAME", filename
);
168 BB_EXECVP_or_die(argv
);
170 /* parent will write to fd[1] */
172 fp
= xfdopen_for_write(fd
[1]);
173 signal(SIGPIPE
, SIG_IGN
);
176 char *fname
= xasprintf("%s%s", *argv
, filename
);
177 fp
= xfopen_for_write(fname
);
184 if (0 == strcasecmp(encoding
, "base64")) {
185 read_base64(stdin
, fp
, '-');
187 if (0 != strcasecmp(encoding
, "7bit")
188 && 0 != strcasecmp(encoding
, "8bit")
190 /* quoted-printable, binary, user-defined are unsupported so far */
191 bb_error_msg_and_die("encoding '%s' not supported", encoding
);
193 /* plain 7bit or 8bit */
194 while ((end
= xmalloc_fgets(stdin
)) != NULL
) {
197 && strncmp(end
+ 2, boundary
, boundary_len
) == 0
209 signal(SIGPIPE
, SIG_DFL
);
210 rc
= (wait4pid(pid
) & 0xff);
215 /* Multipart ended? */
216 if (end
&& '-' == end
[2 + boundary_len
] && '-' == end
[2 + boundary_len
+ 1]) {
217 dbg_error_msg("FINISHED MPART:'%s'", end
);
220 dbg_error_msg("FINISHED:'%s'", end
);
222 } /* end of "handle one non-multipart block" */
227 dbg_error_msg("ENDPARSE[%s]", boundary
);
232 //usage:#define reformime_trivial_usage
234 //usage:#define reformime_full_usage "\n\n"
235 //usage: "Parse MIME-encoded message on stdin\n"
236 //usage: "\n -x PREFIX Extract content of MIME sections to files"
237 //usage: "\n -X PROG ARGS Filter content of MIME sections through PROG"
238 //usage: "\n Must be the last option"
240 //usage: "\nOther options are silently ignored"
243 Usage: reformime [options]
244 -d - parse a delivery status notification.
245 -e - extract contents of MIME section.
246 -x - extract MIME section to a file.
247 -X - pipe MIME section to a program.
249 -s n.n.n.n - specify MIME section.
250 -r - rewrite message, filling in missing MIME headers.
251 -r7 - also convert 8bit/raw encoding to quoted-printable, if possible.
252 -r8 - also convert quoted-printable encoding to 8bit, if possible.
253 -c charset - default charset for rewriting, -o, and -O.
254 -m [file] [file]... - create a MIME message digest.
255 -h "header" - decode RFC 2047-encoded header.
256 -o "header" - encode unstructured header using RFC 2047.
257 -O "header" - encode address list header using RFC 2047.
260 int reformime_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
261 int reformime_main(int argc UNUSED_PARAM
, char **argv
)
263 const char *opt_prefix
= "";
268 // N.B. only -x and -X are supported so far
269 opt_complementary
= "x--X:X--x" IF_FEATURE_REFORMIME_COMPAT(":m::");
270 opts
= getopt32(argv
,
271 "x:X" IF_FEATURE_REFORMIME_COMPAT("deis:r:c:m:h:o:O:"),
273 IF_FEATURE_REFORMIME_COMPAT(, NULL
, NULL
, &G
.opt_charset
, NULL
, NULL
, NULL
, NULL
)
277 return parse("", (opts
& OPT_X
) ? argv
: (char **)&opt_prefix
);