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.
9 //config:config REFORMIME
10 //config: bool "reformime (7.6 kb)"
13 //config: Parse MIME-formatted messages.
15 //config:config FEATURE_REFORMIME_COMPAT
16 //config: bool "Accept and ignore options other than -x and -X"
18 //config: depends on REFORMIME
20 //config: Accept (for compatibility only) and ignore options
21 //config: other than -x and -X.
23 //applet:IF_REFORMIME(APPLET(reformime, BB_DIR_BIN, BB_SUID_DROP))
25 //kbuild:lib-$(CONFIG_REFORMIME) += reformime.o mail.o
31 # define dbg_error_msg(...) bb_error_msg(__VA_ARGS__)
33 # define dbg_error_msg(...) ((void)0)
36 static const char *find_token(const char *const string_array
[], const char *key
, const char *defvalue
)
40 for (i
= 0; string_array
[i
] != NULL
; i
++) {
41 if (strcasecmp(string_array
[i
], key
) == 0) {
42 r
= (char *)string_array
[i
+1];
46 return (r
) ? r
: defvalue
;
49 static const char *xfind_token(const char *const string_array
[], const char *key
)
51 const char *r
= find_token(string_array
, key
, NULL
);
54 bb_error_msg_and_die("not found: '%s'", key
);
60 #if ENABLE_FEATURE_REFORMIME_COMPAT
74 static int parse(const char *boundary
, char **argv
)
76 int boundary_len
= strlen(boundary
);
77 char uniq
[sizeof("%%llu.%u") + sizeof(int)*3];
79 dbg_error_msg("BOUNDARY[%s]", boundary
);
81 // prepare unique string pattern
82 sprintf(uniq
, "%%llu.%u", (unsigned)getpid());
83 dbg_error_msg("UNIQ[%s]", uniq
);
87 const char *tokens
[32]; /* 32 is enough */
90 /* Read the header (everything up to two \n) */
92 unsigned header_idx
= 0;
96 int ch
= fgetc(stdin
);
97 if (ch
== '\r') /* Support both line endings */
101 if (ch
== '\n' && last_ch
== ch
)
103 if (!(header_idx
& 0xff))
104 header
= xrealloc(header
, header_idx
+ 0x101);
105 header
[header_idx
++] = last_ch
= ch
;
108 dbg_error_msg("EOF");
111 header
[header_idx
] = '\0';
112 dbg_error_msg("H:'%s'", p
);
115 /* Split to tokens */
120 const char *delims
= ";=\" \t\n";
122 /* Skip to last Content-Type: */
124 while ((p
= strchr(p
, '\n')) != NULL
) {
126 if (strncasecmp(p
, "Content-Type:", sizeof("Content-Type:")-1) == 0)
129 dbg_error_msg("L:'%s'", p
);
131 s
= strtok_r(s
, delims
, &tokstate
);
134 if (ntokens
< ARRAY_SIZE(tokens
) - 1)
136 dbg_error_msg("L[%d]='%s'", ntokens
, s
);
137 s
= strtok_r(NULL
, delims
, &tokstate
);
139 tokens
[ntokens
] = NULL
;
140 dbg_error_msg("EMPTYLINE, ntokens:%d", ntokens
);
145 /* Is it multipart? */
146 type
= find_token(tokens
, "Content-Type:", "text/plain");
147 dbg_error_msg("TYPE:'%s'", type
);
148 if (0 == strncasecmp(type
, "multipart/", 10)) {
150 if (strcasecmp(type
+ 10, "mixed") != 0)
151 bb_error_msg_and_die("no support of content type '%s'", type
);
152 parse(xfind_token(tokens
, "boundary"), argv
);
154 /* No, process one non-multipart section */
159 const char *charset
= find_token(tokens
, "charset", CONFIG_FEATURE_MIME_CHARSET
);
160 const char *encoding
= find_token(tokens
, "Content-Transfer-Encoding:", "7bit");
162 /* Compose target filename */
163 char *filename
= (char *)find_token(tokens
, "filename", NULL
);
165 filename
= xasprintf(uniq
, monotonic_us());
167 filename
= bb_get_last_path_component_strip(xstrdup(filename
));
169 if (option_mask32
& OPT_X
) {
172 /* start external helper */
176 /* child reads from fd[0] */
178 xmove_fd(fd
[0], STDIN_FILENO
);
179 xsetenv("CONTENT_TYPE", type
);
180 xsetenv("CHARSET", charset
);
181 xsetenv("ENCODING", encoding
);
182 xsetenv("FILENAME", filename
);
183 BB_EXECVP_or_die(argv
);
185 /* parent will write to fd[1] */
187 fp
= xfdopen_for_write(fd
[1]);
188 signal(SIGPIPE
, SIG_IGN
);
191 char *fname
= xasprintf("%s%s", *argv
, filename
);
192 fp
= xfopen_for_write(fname
);
199 if (0 == strcasecmp(encoding
, "base64")) {
200 read_base64(stdin
, fp
, '-');
202 if (0 != strcasecmp(encoding
, "7bit")
203 && 0 != strcasecmp(encoding
, "8bit")
205 /* quoted-printable, binary, user-defined are unsupported so far */
206 bb_error_msg_and_die("encoding '%s' not supported", encoding
);
208 /* plain 7bit or 8bit */
209 while ((end
= xmalloc_fgets(stdin
)) != NULL
) {
212 && strncmp(end
+ 2, boundary
, boundary_len
) == 0
222 if (option_mask32
& OPT_X
) {
224 signal(SIGPIPE
, SIG_DFL
);
225 rc
= (wait4pid(pid
) & 0xff);
230 /* Multipart ended? */
231 if (end
&& '-' == end
[2 + boundary_len
] && '-' == end
[2 + boundary_len
+ 1]) {
232 dbg_error_msg("FINISHED MPART:'%s'", end
);
235 dbg_error_msg("FINISHED:'%s'", end
);
237 } /* end of "handle one non-multipart block" */
242 dbg_error_msg("ENDPARSE[%s]", boundary
);
247 //usage:#define reformime_trivial_usage
249 //usage:#define reformime_full_usage "\n\n"
250 //usage: "Parse MIME-encoded message on stdin\n"
251 //usage: "\n -x PREFIX Extract content of MIME sections to files"
252 //usage: "\n -X PROG ARGS Filter content of MIME sections through PROG"
253 //usage: "\n Must be the last option"
255 //usage: "\nOther options are silently ignored"
258 Usage: reformime [options]
259 -d - parse a delivery status notification.
260 -e - extract contents of MIME section.
261 -x - extract MIME section to a file.
262 -X - pipe MIME section to a program.
264 -s n.n.n.n - specify MIME section.
265 -r - rewrite message, filling in missing MIME headers.
266 -r7 - also convert 8bit/raw encoding to quoted-printable, if possible.
267 -r8 - also convert quoted-printable encoding to 8bit, if possible.
268 -c charset - default charset for rewriting, -o, and -O.
269 -m [file] [file]... - create a MIME message digest.
270 -h "header" - decode RFC 2047-encoded header.
271 -o "header" - encode unstructured header using RFC 2047.
272 -O "header" - encode address list header using RFC 2047.
275 int reformime_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
276 int reformime_main(int argc UNUSED_PARAM
, char **argv
)
279 const char *opt_prefix
= "";
284 // N.B. only -x and -X are supported so far
285 opts
= getopt32(argv
, "^"
286 "x:X" IF_FEATURE_REFORMIME_COMPAT("deis:r:c:m:*h:o:O:")
289 IF_FEATURE_REFORMIME_COMPAT(, NULL
, NULL
, &G
.opt_charset
, NULL
, NULL
, NULL
, NULL
)
293 return parse("", (opts
& OPT_X
) ? argv
: (char **)&opt_prefix
);