1. /*
  2. * Copyright 2002-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.email;
  18. import java.io.ByteArrayInputStream;
  19. import java.io.ByteArrayOutputStream;
  20. import java.io.File;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.OutputStream;
  24. import java.io.PrintStream;
  25. import java.io.UnsupportedEncodingException;
  26. import java.util.Enumeration;
  27. import java.util.Properties;
  28. import java.util.StringTokenizer;
  29. import java.util.Vector;
  30. import java.security.Security;
  31. import java.security.Provider;
  32. import javax.activation.DataHandler;
  33. import javax.activation.FileDataSource;
  34. import javax.mail.Authenticator;
  35. import javax.mail.PasswordAuthentication;
  36. import javax.mail.Session;
  37. import javax.mail.Message;
  38. import javax.mail.Transport;
  39. import javax.mail.MessagingException;
  40. import javax.mail.internet.AddressException;
  41. import javax.mail.internet.InternetAddress;
  42. import javax.mail.internet.MimeBodyPart;
  43. import javax.mail.internet.MimeMessage;
  44. import javax.mail.internet.MimeMultipart;
  45. import org.apache.tools.ant.BuildException;
  46. /**
  47. * Uses the JavaMail classes to send Mime format email.
  48. *
  49. * @since Ant 1.5
  50. */
  51. public class MimeMailer extends Mailer {
  52. /** Default character set */
  53. private static final String DEFAULT_CHARSET
  54. = System.getProperty("file.encoding");
  55. // To work properly with national charsets we have to use
  56. // implementation of interface javax.activation.DataSource
  57. /**
  58. * @since Ant 1.6
  59. */
  60. class StringDataSource implements javax.activation.DataSource {
  61. private String data = null;
  62. private String type = null;
  63. private String charset = null;
  64. private ByteArrayOutputStream out;
  65. public InputStream getInputStream() throws IOException {
  66. if (data == null && out == null) {
  67. throw new IOException("No data");
  68. } else {
  69. if (out != null) {
  70. data = (data != null) ? data.concat(out.toString(charset)) : out.toString(charset);
  71. out = null;
  72. }
  73. return new ByteArrayInputStream(data.getBytes(charset));
  74. }
  75. }
  76. public OutputStream getOutputStream() throws IOException {
  77. if (out == null) {
  78. out = new ByteArrayOutputStream();
  79. }
  80. return out;
  81. }
  82. public void setContentType(String type) {
  83. this.type = type.toLowerCase();
  84. }
  85. public String getContentType() {
  86. if (type != null && type.indexOf("charset") > 0 && type.startsWith("text/")) {
  87. return type;
  88. }
  89. // Must be like "text/plain; charset=windows-1251"
  90. return type != null ? type.concat("; charset=".concat(charset))
  91. : "text/plain".concat("; charset=".concat(charset));
  92. }
  93. public String getName() {
  94. return "StringDataSource";
  95. }
  96. public void setCharset(String charset) {
  97. this.charset = charset;
  98. }
  99. public String getCharset() {
  100. return charset;
  101. }
  102. }
  103. /** Sends the email */
  104. public void send() {
  105. try {
  106. Properties props = new Properties();
  107. props.put("mail.smtp.host", host);
  108. props.put("mail.smtp.port", String.valueOf(port));
  109. // Aside, the JDK is clearly unaware of the Scottish
  110. // 'session', which involves excessive quantities of
  111. // alcohol :-)
  112. Session sesh;
  113. Authenticator auth;
  114. if (SSL) {
  115. try {
  116. Provider p
  117. = (Provider) Class.forName("com.sun.net.ssl.internal.ssl.Provider").newInstance();
  118. Security.addProvider(p);
  119. } catch (Exception e) {
  120. throw new BuildException("could not instantiate ssl "
  121. + "security provider, check that you have JSSE in "
  122. + "your classpath");
  123. }
  124. final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
  125. // SMTP provider
  126. props.put("mail.smtp.socketFactory.class", SSL_FACTORY);
  127. props.put("mail.smtp.socketFactory.fallback", "false");
  128. }
  129. if (user == null && password == null) {
  130. sesh = Session.getDefaultInstance(props, null);
  131. } else {
  132. props.put("mail.smtp.auth", "true");
  133. auth = new SimpleAuthenticator(user, password);
  134. sesh = Session.getInstance(props, auth);
  135. }
  136. //create the message
  137. MimeMessage msg = new MimeMessage(sesh);
  138. MimeMultipart attachments = new MimeMultipart();
  139. //set the sender
  140. if (from.getName() == null) {
  141. msg.setFrom(new InternetAddress(from.getAddress()));
  142. } else {
  143. msg.setFrom(new InternetAddress(from.getAddress(),
  144. from.getName()));
  145. }
  146. // set the reply to addresses
  147. msg.setReplyTo(internetAddresses(replyToList));
  148. msg.setRecipients(Message.RecipientType.TO,
  149. internetAddresses(toList));
  150. msg.setRecipients(Message.RecipientType.CC,
  151. internetAddresses(ccList));
  152. msg.setRecipients(Message.RecipientType.BCC,
  153. internetAddresses(bccList));
  154. // Choosing character set of the mail message
  155. // First: looking it from MimeType
  156. String charset = parseCharSetFromMimeType(message.getMimeType());
  157. if (charset != null) {
  158. // Assign/reassign message charset from MimeType
  159. message.setCharset(charset);
  160. } else {
  161. // Next: looking if charset having explicit definition
  162. charset = message.getCharset();
  163. if (charset == null) {
  164. // Using default
  165. charset = DEFAULT_CHARSET;
  166. message.setCharset(charset);
  167. }
  168. }
  169. // Using javax.activation.DataSource paradigm
  170. StringDataSource sds = new StringDataSource();
  171. sds.setContentType(message.getMimeType());
  172. sds.setCharset(charset);
  173. if (subject != null) {
  174. msg.setSubject(subject, charset);
  175. }
  176. msg.addHeader("Date", getDate());
  177. PrintStream out = new PrintStream(sds.getOutputStream());
  178. message.print(out);
  179. out.close();
  180. MimeBodyPart textbody = new MimeBodyPart();
  181. textbody.setDataHandler(new DataHandler(sds));
  182. attachments.addBodyPart(textbody);
  183. Enumeration e = files.elements();
  184. while (e.hasMoreElements()) {
  185. File file = (File) e.nextElement();
  186. MimeBodyPart body;
  187. body = new MimeBodyPart();
  188. if (!file.exists() || !file.canRead()) {
  189. throw new BuildException("File \"" + file.getAbsolutePath()
  190. + "\" does not exist or is not "
  191. + "readable.");
  192. }
  193. FileDataSource fileData = new FileDataSource(file);
  194. DataHandler fileDataHandler = new DataHandler(fileData);
  195. body.setDataHandler(fileDataHandler);
  196. body.setFileName(file.getName());
  197. attachments.addBodyPart(body);
  198. }
  199. msg.setContent(attachments);
  200. Transport.send(msg);
  201. } catch (MessagingException e) {
  202. throw new BuildException("Problem while sending mime mail:", e);
  203. } catch (IOException e) {
  204. throw new BuildException("Problem while sending mime mail:", e);
  205. }
  206. }
  207. private static InternetAddress[] internetAddresses(Vector list)
  208. throws AddressException, UnsupportedEncodingException {
  209. InternetAddress[] addrs = new InternetAddress[list.size()];
  210. for (int i = 0; i < list.size(); ++i) {
  211. EmailAddress addr = (EmailAddress) list.elementAt(i);
  212. if (addr.getName() == null) {
  213. addrs[i] = new InternetAddress(addr.getAddress());
  214. } else {
  215. addrs[i] = new InternetAddress(addr.getAddress(),
  216. addr.getName());
  217. }
  218. }
  219. return addrs;
  220. }
  221. private String parseCharSetFromMimeType(String type) {
  222. int pos;
  223. if (type == null || (pos = type.indexOf("charset")) < 0) {
  224. return null;
  225. }
  226. // Assuming mime type in form "text/XXXX; charset=XXXXXX"
  227. StringTokenizer token = new StringTokenizer(type.substring(pos), "=; ");
  228. token.nextToken(); // Skip 'charset='
  229. return token.nextToken();
  230. }
  231. static class SimpleAuthenticator extends Authenticator {
  232. private String user = null;
  233. private String password = null;
  234. public SimpleAuthenticator(String user, String password) {
  235. this.user = user;
  236. this.password = password;
  237. }
  238. public PasswordAuthentication getPasswordAuthentication() {
  239. return new PasswordAuthentication(user, password);
  240. }
  241. }
  242. }