blob: 5e28ef7299ded186c5d9793c61e6881cee5e4ec1 [file] [log] [blame]
Denys Vlasenkod616ab62011-05-22 03:46:33 +02001/* 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 source tree.
9 */
10
11//kbuild:lib-$(CONFIG_REFORMIME) += reformime.o mail.o
12
13#include "libbb.h"
14#include "mail.h"
15
16#if 0
17# define dbg_error_msg(...) bb_error_msg(__VA_ARGS__)
18#else
19# define dbg_error_msg(...) ((void)0)
20#endif
21
22static const char *find_token(const char *const string_array[], const char *key, const char *defvalue)
23{
24 const char *r = NULL;
25 int i;
26 for (i = 0; string_array[i] != NULL; i++) {
27 if (strcasecmp(string_array[i], key) == 0) {
28 r = (char *)string_array[i+1];
29 break;
30 }
31 }
32 return (r) ? r : defvalue;
33}
34
35static const char *xfind_token(const char *const string_array[], const char *key)
36{
37 const char *r = find_token(string_array, key, NULL);
38 if (r)
39 return r;
40 bb_error_msg_and_die("not found: '%s'", key);
41}
42
43enum {
44 OPT_x = 1 << 0,
45 OPT_X = 1 << 1,
46#if ENABLE_FEATURE_REFORMIME_COMPAT
47 OPT_d = 1 << 2,
48 OPT_e = 1 << 3,
49 OPT_i = 1 << 4,
50 OPT_s = 1 << 5,
51 OPT_r = 1 << 6,
52 OPT_c = 1 << 7,
53 OPT_m = 1 << 8,
54 OPT_h = 1 << 9,
55 OPT_o = 1 << 10,
56 OPT_O = 1 << 11,
57#endif
58};
59
60static int parse(const char *boundary, char **argv)
61{
62 int boundary_len = strlen(boundary);
63 char uniq[sizeof("%%llu.%u") + sizeof(int)*3];
64
65 dbg_error_msg("BOUNDARY[%s]", boundary);
66
67 // prepare unique string pattern
68 sprintf(uniq, "%%llu.%u", (unsigned)getpid());
69 dbg_error_msg("UNIQ[%s]", uniq);
70
71 while (1) {
72 char *header;
73 const char *tokens[32]; /* 32 is enough */
74 const char *type;
75
76 /* Read the header (everything up to two \n) */
77 {
78 unsigned header_idx = 0;
79 int last_ch = 0;
80 header = NULL;
81 while (1) {
82 int ch = fgetc(stdin);
83 if (ch == '\r') /* Support both line endings */
84 continue;
85 if (ch == EOF)
86 break;
87 if (ch == '\n' && last_ch == ch)
88 break;
89 if (!(header_idx & 0xff))
90 header = xrealloc(header, header_idx + 0x101);
91 header[header_idx++] = last_ch = ch;
92 }
93 if (!header) {
94 dbg_error_msg("EOF");
95 break;
96 }
97 header[header_idx] = '\0';
98 dbg_error_msg("H:'%s'", p);
99 }
100
101 /* Split to tokens */
102 {
103 char *s, *p;
104 unsigned ntokens;
105 const char *delims = ";=\" \t\n";
106
107 /* Skip to last Content-Type: */
108 s = p = header;
109 while ((p = strchr(p, '\n')) != NULL) {
110 p++;
111 if (strncasecmp(p, "Content-Type:", sizeof("Content-Type:")-1) == 0)
112 s = p;
113 }
114 dbg_error_msg("L:'%s'", p);
115 ntokens = 0;
116 s = strtok(s, delims);
117 while (s) {
118 tokens[ntokens] = s;
119 if (ntokens < ARRAY_SIZE(tokens) - 1)
120 ntokens++;
121 dbg_error_msg("L[%d]='%s'", ntokens, s);
122 s = strtok(NULL, delims);
123 }
124 tokens[ntokens] = NULL;
125 dbg_error_msg("EMPTYLINE, ntokens:%d", ntokens);
126 if (ntokens == 0)
127 break;
128 }
129
130 /* Is it multipart? */
131 type = find_token(tokens, "Content-Type:", "text/plain");
132 dbg_error_msg("TYPE:'%s'", type);
133 if (0 == strncasecmp(type, "multipart/", 10)) {
134 /* Yes, recurse */
135 if (strcasecmp(type + 10, "mixed") != 0)
136 bb_error_msg_and_die("no support of content type '%s'", type);
137 parse(xfind_token(tokens, "boundary"), argv);
138
139 } else {
140 /* No, process one non-multipart section */
141 char *end;
142 pid_t pid = pid;
143 FILE *fp;
144
145 const char *charset = find_token(tokens, "charset", CONFIG_FEATURE_MIME_CHARSET);
146 const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit");
147
148 /* Compose target filename */
149 char *filename = (char *)find_token(tokens, "filename", NULL);
150 if (!filename)
151 filename = xasprintf(uniq, monotonic_us());
152 else
153 filename = bb_get_last_path_component_strip(xstrdup(filename));
154
155 if (opts & OPT_X) {
156 int fd[2];
157
158 /* start external helper */
159 xpipe(fd);
160 pid = vfork();
161 if (0 == pid) {
162 /* child reads from fd[0] */
163 close(fd[1]);
164 xmove_fd(fd[0], STDIN_FILENO);
165 xsetenv("CONTENT_TYPE", type);
166 xsetenv("CHARSET", charset);
167 xsetenv("ENCODING", encoding);
168 xsetenv("FILENAME", filename);
169 BB_EXECVP_or_die(argv);
170 }
171 /* parent will write to fd[1] */
172 close(fd[0]);
173 fp = xfdopen_for_write(fd[1]);
174 signal(SIGPIPE, SIG_IGN);
175 } else {
176 /* write to file */
177 char *fname = xasprintf("%s%s", *argv, filename);
178 fp = xfopen_for_write(fname);
179 free(fname);
180 }
181 free(filename);
182
183 /* write to fp */
184 end = NULL;
185 if (0 == strcasecmp(encoding, "base64")) {
186 read_base64(stdin, fp, '-');
187 } else
188 if (0 != strcasecmp(encoding, "7bit")
189 && 0 != strcasecmp(encoding, "8bit")
190 ) {
191 /* quoted-printable, binary, user-defined are unsupported so far */
192 bb_error_msg_and_die("encoding '%s' not supported", encoding);
193 } else {
194 /* plain 7bit or 8bit */
195 while ((end = xmalloc_fgets(stdin)) != NULL) {
196 if ('-' == end[0]
197 && '-' == end[1]
198 && strncmp(end + 2, boundary, boundary_len) == 0
199 ) {
200 break;
201 }
202 fputs(end, fp);
203 }
204 }
205 fclose(fp);
206
207 /* Wait for child */
208 if (opts & OPT_X) {
209 int rc;
210 signal(SIGPIPE, SIG_DFL);
211 rc = (wait4pid(pid) & 0xff);
212 if (rc != 0)
213 return rc + 20;
214 }
215
216 /* Multipart ended? */
217 if (end && '-' == end[2 + boundary_len] && '-' == end[2 + boundary_len + 1]) {
218 dbg_error_msg("FINISHED MPART:'%s'", end);
219 break;
220 }
221 dbg_error_msg("FINISHED:'%s'", end);
222 free(end);
223 } /* end of "handle one non-multipart block" */
224
225 free(header);
226 } /* while (1) */
227
228 dbg_error_msg("ENDPARSE[%s]", boundary);
229
230 return EXIT_SUCCESS;
231}
232
233//usage:#define reformime_trivial_usage
234//usage: "[OPTIONS]"
235//usage:#define reformime_full_usage "\n\n"
236//usage: "Parse MIME-encoded message on stdin\n"
Denys Vlasenkod616ab62011-05-22 03:46:33 +0200237//usage: "\n -x PREFIX Extract content of MIME sections to files"
238//usage: "\n -X PROG ARGS Filter content of MIME sections through PROG"
239//usage: "\n Must be the last option"
240//usage: "\n"
241//usage: "\nOther options are silently ignored"
242
243/*
244Usage: reformime [options]
245 -d - parse a delivery status notification.
246 -e - extract contents of MIME section.
247 -x - extract MIME section to a file.
248 -X - pipe MIME section to a program.
249 -i - show MIME info.
250 -s n.n.n.n - specify MIME section.
251 -r - rewrite message, filling in missing MIME headers.
252 -r7 - also convert 8bit/raw encoding to quoted-printable, if possible.
253 -r8 - also convert quoted-printable encoding to 8bit, if possible.
254 -c charset - default charset for rewriting, -o, and -O.
255 -m [file] [file]... - create a MIME message digest.
256 -h "header" - decode RFC 2047-encoded header.
257 -o "header" - encode unstructured header using RFC 2047.
258 -O "header" - encode address list header using RFC 2047.
259*/
260
261int reformime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
262int reformime_main(int argc UNUSED_PARAM, char **argv)
263{
264 const char *opt_prefix = "";
265
266 INIT_G();
267
268 // parse options
269 // N.B. only -x and -X are supported so far
270 opt_complementary = "x--X:X--x" IF_FEATURE_REFORMIME_COMPAT(":m::");
271 opts = getopt32(argv,
272 "x:X" IF_FEATURE_REFORMIME_COMPAT("deis:r:c:m:h:o:O:"),
273 &opt_prefix
274 IF_FEATURE_REFORMIME_COMPAT(, NULL, NULL, &G.opt_charset, NULL, NULL, NULL, NULL)
275 );
276 argv += optind;
277
278 return parse("", (opts & OPT_X) ? argv : (char **)&opt_prefix);
279}