randomconfig fixes
[platform/upstream/busybox.git] / mailutils / mime.c
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
25 Options:
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
46 int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
47 int 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
120 static 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
132 static 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
140 enum {
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
157 static 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 /*
318 Usage: 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
335 int reformime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
336 int 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 }