3
0

makemime.c 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * makemime: create MIME-encoded message
  4. *
  5. * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
  6. *
  7. * Licensed under GPLv2, see file LICENSE in this source tree.
  8. */
  9. //config:config MAKEMIME
  10. //config: bool "makemime (5.6 kb)"
  11. //config: default y
  12. //config: help
  13. //config: Create MIME-formatted messages.
  14. //applet:IF_MAKEMIME(APPLET(makemime, BB_DIR_BIN, BB_SUID_DROP))
  15. //kbuild:lib-$(CONFIG_MAKEMIME) += makemime.o mail.o
  16. #include "libbb.h"
  17. #include "mail.h"
  18. #if 0
  19. # define dbg_error_msg(...) bb_error_msg(__VA_ARGS__)
  20. #else
  21. # define dbg_error_msg(...) ((void)0)
  22. #endif
  23. /*
  24. makemime -c type [-o file] [-e encoding] [-C charset] [-N name] \
  25. [-a "Header: Contents"] file
  26. -m [ type ] [-o file] [-e encoding] [-a "Header: Contents"] file
  27. -j [-o file] file1 file2
  28. @file
  29. file: filename - read or write from filename
  30. - - read or write from stdin or stdout
  31. &n - read or write from file descriptor n
  32. \( opts \) - read from child process, that generates [ opts ]
  33. Options:
  34. -c type - create a new MIME section from "file" with this
  35. Content-Type: (default is application/octet-stream).
  36. -C charset - MIME charset of a new text/plain section.
  37. -N name - MIME content name of the new mime section.
  38. -m [ type ] - create a multipart mime section from "file" of this
  39. Content-Type: (default is multipart/mixed).
  40. -e encoding - use the given encoding (7bit, 8bit, quoted-printable,
  41. or base64), instead of guessing. Omit "-e" and use
  42. -c auto to set Content-Type: to text/plain or
  43. application/octet-stream based on picked encoding.
  44. -j file1 file2 - join mime section file2 to multipart section file1.
  45. -o file - write the result to file, instead of stdout (not
  46. allowed in child processes).
  47. -a header - prepend an additional header to the output.
  48. @file - read all of the above options from file, one option or
  49. value on each line.
  50. {which version of makemime is this? What do we support?}
  51. */
  52. /* man makemime:
  53. * -c TYPE: create a (non-multipart) MIME section with Content-Type: TYPE
  54. * makemime -c TYPE [-e ENCODING] [-o OUTFILE] [-C CHARSET] [-N NAME] [-a HEADER...] FILE
  55. * The -C option sets the MIME charset attribute for text/plain content.
  56. * The -N option sets the name attribute for Content-Type:
  57. * Encoding must be one of the following: 7bit, 8bit, quoted-printable, or base64.
  58. * -m multipart/TYPE: create a multipart MIME collection with Content-Type: multipart/TYPE
  59. * makemime -m multipart/TYPE [-e ENCODING] [-o OUTFILE] [-a HEADER...] FILE
  60. * Type must be either "multipart/mixed", "multipart/alternative", or some other MIME multipart content type.
  61. * Additionally, encoding can only be "7bit" or "8bit", and will default to "8bit" if not specified.
  62. * Finally, filename must be a MIME-formatted section, NOT a regular file.
  63. * The -m option creates an initial multipart MIME collection, that contains only one MIME section, taken from filename.
  64. * The collection is written to standard output, or the pipe or to outputfile.
  65. * -j FILE1: add a section to a multipart MIME collection
  66. * makemime -j FILE1 [-o OUTFILE] FILE2
  67. * FILE1 must be a MIME collection that was previously created by the -m option.
  68. * FILE2 must be a MIME section that was previously created by the -c option.
  69. * The -j options adds the MIME section in FILE2 to the MIME collection in FILE1.
  70. */
  71. /* In busybox 1.15.0.svn, makemime generates output like this
  72. * (empty lines are shown exactly!):
  73. {headers added with -a HDR}
  74. Mime-Version: 1.0
  75. Content-Type: multipart/mixed; boundary="24269534-2145583448-1655890676"
  76. --24269534-2145583448-1655890676
  77. Content-Type: {set by -c, e.g. text/plain}; charset={set by -C, e.g. us-ascii}
  78. Content-Disposition: inline; filename="A"
  79. Content-Transfer-Encoding: base64
  80. ...file A contents...
  81. --24269534-2145583448-1655890676
  82. Content-Type: {set by -c, e.g. text/plain}; charset={set by -C, e.g. us-ascii}
  83. Content-Disposition: inline; filename="B"
  84. Content-Transfer-Encoding: base64
  85. ...file B contents...
  86. --24269534-2145583448-1655890676--
  87. *
  88. * For reference: here is an example email to LKML which has
  89. * 1st unnamed part (so it serves as an email body)
  90. * and one attached file:
  91. ...other headers...
  92. Content-Type: multipart/mixed; boundary="=-tOfTf3byOS0vZgxEWcX+"
  93. ...other headers...
  94. Mime-Version: 1.0
  95. ...other headers...
  96. --=-tOfTf3byOS0vZgxEWcX+
  97. Content-Type: text/plain
  98. Content-Transfer-Encoding: 7bit
  99. ...email text...
  100. ...email text...
  101. --=-tOfTf3byOS0vZgxEWcX+
  102. Content-Disposition: attachment; filename="xyz"
  103. Content-Type: text/plain; name="xyz"; charset="UTF-8"
  104. Content-Transfer-Encoding: 7bit
  105. ...file contents...
  106. ...file contents...
  107. --=-tOfTf3byOS0vZgxEWcX+--
  108. ...random junk added by mailing list robots and such...
  109. */
  110. //usage:#define makemime_trivial_usage
  111. //usage: "[OPTIONS] [FILE]..."
  112. //usage:#define makemime_full_usage "\n\n"
  113. //usage: "Create multipart MIME-encoded message from FILEs\n"
  114. /* //usage: "Transfer encoding is base64, disposition is inline (not attachment)\n" */
  115. //usage: "\n -o FILE Output. Default: stdout"
  116. //usage: "\n -a HDR Add header(s). Examples:"
  117. //usage: "\n \"From: user@host.org\", \"Date: `date -R`\""
  118. //usage: "\n -c CT Content type. Default: application/octet-stream"
  119. //usage: "\n -C CS Charset. Default: " CONFIG_FEATURE_MIME_CHARSET
  120. /* //usage: "\n -e ENC Transfer encoding. Ignored. base64 is assumed" */
  121. //usage: "\n"
  122. //usage: "\nOther options are silently ignored"
  123. /*
  124. * -c [Content-Type] should create just one MIME section
  125. * with "Content-Type:", "Content-Transfer-Encoding:", and HDRs from "-a HDR".
  126. * NB: without "Content-Disposition:" auto-added, unlike we do now
  127. * NB2: -c has *optional* param which nevertheless _can_ be specified after a space :(
  128. *
  129. * -m [multipart/mixed] should create multipart MIME section
  130. * with "Content-Type:", "Content-Transfer-Encoding:", and HDRs from "-a HDR",
  131. * and add FILE to it _verbatim_:
  132. * HEADERS
  133. *
  134. * --=_1_1321709112_1605
  135. * FILE_CONTENTS
  136. * --=_1_1321709112_1605
  137. * without any encoding of FILE_CONTENTS. (Basically, it expects that FILE
  138. * is the result of "makemime -c").
  139. *
  140. * -j MULTIPART_FILE1 SINGLE_FILE2 should output MULTIPART_FILE1 + SINGLE_FILE2
  141. *
  142. * Our current behavior is a mutant "-m + -c + -j" one: we create multipart MIME
  143. * and we put "-c" encoded FILEs into many multipart sections.
  144. */
  145. int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  146. int makemime_main(int argc UNUSED_PARAM, char **argv)
  147. {
  148. unsigned opts;
  149. llist_t *opt_headers = NULL, *l;
  150. const char *opt_output;
  151. const char *content_type = "application/octet-stream";
  152. #define boundary opt_output
  153. enum {
  154. OPT_c = 1 << 0, // create (non-multipart) section
  155. OPT_e = 1 << 1, // Content-Transfer-Encoding. Ignored. Assumed base64
  156. OPT_o = 1 << 2, // output to
  157. OPT_C = 1 << 3, // charset
  158. OPT_N = 1 << 4, // COMPAT
  159. OPT_a = 1 << 5, // additional headers
  160. //OPT_m = 1 << 6, // create mutipart section
  161. //OPT_j = 1 << 7, // join section to multipart section
  162. };
  163. INIT_G();
  164. // parse options
  165. opts = getopt32(argv,
  166. "c:e:o:C:N:a:*", // "m:j:",
  167. &content_type, NULL, &opt_output, &G.opt_charset, NULL, &opt_headers //, NULL, NULL
  168. );
  169. //argc -= optind;
  170. argv += optind;
  171. // respect -o output
  172. if (opts & OPT_o)
  173. freopen(opt_output, "w", stdout);
  174. // no files given on command line? -> use stdin
  175. if (!*argv)
  176. *--argv = (char *)"-";
  177. // put additional headers
  178. for (l = opt_headers; l; l = l->link)
  179. puts(l->data);
  180. // make a random string -- it will delimit message parts
  181. srand(monotonic_us());
  182. boundary = xasprintf("%u-%u-%u",
  183. (unsigned)rand(), (unsigned)rand(), (unsigned)rand());
  184. // put multipart header
  185. printf(
  186. "Mime-Version: 1.0\n"
  187. "Content-Type: multipart/mixed; boundary=\"%s\"\n"
  188. , boundary
  189. );
  190. // put attachments
  191. while (*argv) {
  192. printf(
  193. "\n--%s\n"
  194. "Content-Type: %s; charset=%s\n"
  195. "Content-Disposition: inline; filename=\"%s\"\n"
  196. "Content-Transfer-Encoding: base64\n"
  197. , boundary
  198. , content_type
  199. , G.opt_charset
  200. , bb_get_last_path_component_strip(*argv)
  201. );
  202. printfile_base64(*argv++);
  203. }
  204. // put multipart footer
  205. printf("\n--%s--\n" "\n", boundary);
  206. return EXIT_SUCCESS;
  207. #undef boundary
  208. }