2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.tools.ant.taskdefs.email;
21
import java.io.InputStream;
22
import java.io.IOException;
23
import java.io.PrintStream;
24
import java.io.OutputStream;
25
import java.io.ByteArrayInputStream;
26
import java.io.ByteArrayOutputStream;
27
import java.io.UnsupportedEncodingException;
29
import java.util.Enumeration;
30
import java.util.Iterator;
31
import java.util.Locale;
32
import java.util.Properties;
33
import java.util.StringTokenizer;
34
import java.util.Vector;
36
import java.security.Provider;
37
import java.security.Security;
39
import javax.activation.DataHandler;
40
import javax.activation.FileDataSource;
42
import javax.mail.Authenticator;
43
import javax.mail.Address;
44
import javax.mail.Message;
45
import javax.mail.MessagingException;
46
import javax.mail.PasswordAuthentication;
47
import javax.mail.SendFailedException;
48
import javax.mail.Session;
49
import javax.mail.Transport;
50
import javax.mail.internet.AddressException;
51
import javax.mail.internet.InternetAddress;
52
import javax.mail.internet.MimeBodyPart;
53
import javax.mail.internet.MimeMessage;
54
import javax.mail.internet.MimeMultipart;
56
import org.apache.tools.ant.BuildException;
57
import org.apache.tools.ant.Project;
60
* Uses the JavaMail classes to send Mime format email.
64
public class MimeMailer extends Mailer {
65
private static final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
67
private static final String GENERIC_ERROR =
68
"Problem while sending mime mail:";
70
/** Default character set */
71
private static final String DEFAULT_CHARSET
72
= System.getProperty("file.encoding");
74
// To work properly with national charsets we have to use
75
// implementation of interface javax.activation.DataSource
77
* String data source implementation.
80
class StringDataSource implements javax.activation.DataSource {
81
private String data = null;
82
private String type = null;
83
private String charset = null;
84
private ByteArrayOutputStream out;
86
public InputStream getInputStream() throws IOException {
87
if (data == null && out == null) {
88
throw new IOException("No data");
91
String encodedOut = out.toString(charset);
92
data = (data != null) ? data.concat(encodedOut) : encodedOut;
95
return new ByteArrayInputStream(data.getBytes(charset));
98
public OutputStream getOutputStream() throws IOException {
99
out = (out == null) ? new ByteArrayOutputStream() : out;
103
public void setContentType(String type) {
104
this.type = type.toLowerCase(Locale.ENGLISH);
107
public String getContentType() {
108
if (type != null && type.indexOf("charset") > 0
109
&& type.startsWith("text/")) {
112
// Must be like "text/plain; charset=windows-1251"
113
return new StringBuffer(type != null ? type : "text/plain").append(
114
"; charset=").append(charset).toString();
117
public String getName() {
118
return "StringDataSource";
121
public void setCharset(String charset) {
122
this.charset = charset;
125
public String getCharset() {
133
* @throws BuildException if the email can't be sent.
137
Properties props = new Properties();
139
props.put("mail.smtp.host", host);
140
props.put("mail.smtp.port", String.valueOf(port));
142
// Aside, the JDK is clearly unaware of the Scottish
143
// 'session', which involves excessive quantities of
146
Authenticator auth = null;
149
Provider p = (Provider) Class.forName(
150
"com.sun.net.ssl.internal.ssl.Provider").newInstance();
151
Security.addProvider(p);
152
} catch (Exception e) {
153
throw new BuildException("could not instantiate ssl "
154
+ "security provider, check that you have JSSE in "
158
props.put("mail.smtp.socketFactory.class", SSL_FACTORY);
159
props.put("mail.smtp.socketFactory.fallback", "false");
160
if (isPortExplicitlySpecified()) {
161
props.put("mail.smtp.socketFactory.port",
162
String.valueOf(port));
165
if (user != null || password != null) {
166
props.put("mail.smtp.auth", "true");
167
auth = new SimpleAuthenticator(user, password);
169
if (isStartTLSEnabled()) {
170
props.put("mail.smtp.starttls.enable", "true");
172
sesh = Session.getInstance(props, auth);
175
MimeMessage msg = new MimeMessage(sesh);
176
MimeMultipart attachments = new MimeMultipart();
179
if (from.getName() == null) {
180
msg.setFrom(new InternetAddress(from.getAddress()));
182
msg.setFrom(new InternetAddress(from.getAddress(),
185
// set the reply to addresses
186
msg.setReplyTo(internetAddresses(replyToList));
187
msg.setRecipients(Message.RecipientType.TO,
188
internetAddresses(toList));
189
msg.setRecipients(Message.RecipientType.CC,
190
internetAddresses(ccList));
191
msg.setRecipients(Message.RecipientType.BCC,
192
internetAddresses(bccList));
194
// Choosing character set of the mail message
195
// First: looking it from MimeType
196
String charset = parseCharSetFromMimeType(message.getMimeType());
197
if (charset != null) {
198
// Assign/reassign message charset from MimeType
199
message.setCharset(charset);
201
// Next: looking if charset having explicit definition
202
charset = message.getCharset();
203
if (charset == null) {
205
charset = DEFAULT_CHARSET;
206
message.setCharset(charset);
209
// Using javax.activation.DataSource paradigm
210
StringDataSource sds = new StringDataSource();
211
sds.setContentType(message.getMimeType());
212
sds.setCharset(charset);
214
if (subject != null) {
215
msg.setSubject(subject, charset);
217
msg.addHeader("Date", getDate());
219
if (headers != null) {
220
for (Iterator iter = headers.iterator(); iter.hasNext();) {
221
Header h = (Header) iter.next();
222
msg.addHeader(h.getName(), h.getValue());
225
PrintStream out = new PrintStream(sds.getOutputStream());
229
MimeBodyPart textbody = new MimeBodyPart();
230
textbody.setDataHandler(new DataHandler(sds));
231
attachments.addBodyPart(textbody);
233
Enumeration e = files.elements();
235
while (e.hasMoreElements()) {
236
File file = (File) e.nextElement();
240
body = new MimeBodyPart();
241
if (!file.exists() || !file.canRead()) {
242
throw new BuildException("File \"" + file.getAbsolutePath()
243
+ "\" does not exist or is not "
246
FileDataSource fileData = new FileDataSource(file);
247
DataHandler fileDataHandler = new DataHandler(fileData);
249
body.setDataHandler(fileDataHandler);
250
body.setFileName(file.getName());
251
attachments.addBodyPart(body);
253
msg.setContent(attachments);
255
// Send the message using SMTP, or SMTPS if the host uses SSL
256
Transport transport = sesh.getTransport(SSL ? "smtps" : "smtp");
257
transport.connect(host, user, password);
258
transport.sendMessage(msg, msg.getAllRecipients());
259
} catch (SendFailedException sfe) {
260
if (!shouldIgnoreInvalidRecipients()) {
261
throw new BuildException(GENERIC_ERROR, sfe);
262
} else if (sfe.getValidSentAddresses() == null
263
|| sfe.getValidSentAddresses().length == 0) {
264
throw new BuildException("Couldn't reach any recipient",
267
Address[] invalid = sfe.getInvalidAddresses();
268
if (invalid == null) {
269
invalid = new Address[0];
271
for (int i = 0; i < invalid.length; i++) {
272
didntReach(invalid[i], "invalid", sfe);
274
Address[] validUnsent = sfe.getValidUnsentAddresses();
275
if (validUnsent == null) {
276
validUnsent = new Address[0];
278
for (int i = 0; i < validUnsent.length; i++) {
279
didntReach(validUnsent[i], "valid", sfe);
283
} catch (MessagingException e) {
284
throw new BuildException(GENERIC_ERROR, e);
285
} catch (IOException e) {
286
throw new BuildException(GENERIC_ERROR, e);
290
private static InternetAddress[] internetAddresses(Vector list)
291
throws AddressException, UnsupportedEncodingException {
292
InternetAddress[] addrs = new InternetAddress[list.size()];
294
for (int i = 0; i < list.size(); ++i) {
295
EmailAddress addr = (EmailAddress) list.elementAt(i);
297
String name = addr.getName();
298
addrs[i] = (name == null)
299
? new InternetAddress(addr.getAddress())
300
: new InternetAddress(addr.getAddress(), name);
305
private String parseCharSetFromMimeType(String type) {
309
int pos = type.indexOf("charset");
313
// Assuming mime type in form "text/XXXX; charset=XXXXXX"
314
StringTokenizer token = new StringTokenizer(type.substring(pos), "=; ");
315
token.nextToken(); // Skip 'charset='
316
return token.nextToken();
319
private void didntReach(Address addr, String category,
320
MessagingException ex) {
321
String msg = "Failed to send mail to " + category + " address "
322
+ addr + " because of " + ex.getMessage();
324
task.log(msg, Project.MSG_WARN);
326
System.err.println(msg);
330
static class SimpleAuthenticator extends Authenticator {
331
private String user = null;
332
private String password = null;
333
public SimpleAuthenticator(String user, String password) {
335
this.password = password;
337
public PasswordAuthentication getPasswordAuthentication() {
339
return new PasswordAuthentication(user, password);
2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.tools.ant.taskdefs.email;
21
import java.io.InputStream;
22
import java.io.IOException;
23
import java.io.PrintStream;
24
import java.io.OutputStream;
25
import java.io.ByteArrayInputStream;
26
import java.io.ByteArrayOutputStream;
27
import java.io.UnsupportedEncodingException;
29
import java.util.Enumeration;
30
import java.util.Iterator;
31
import java.util.Locale;
32
import java.util.Properties;
33
import java.util.StringTokenizer;
34
import java.util.Vector;
36
import java.security.Provider;
37
import java.security.Security;
39
import javax.activation.DataHandler;
40
import javax.activation.FileDataSource;
42
import javax.mail.Authenticator;
43
import javax.mail.Address;
44
import javax.mail.Message;
45
import javax.mail.MessagingException;
46
import javax.mail.PasswordAuthentication;
47
import javax.mail.SendFailedException;
48
import javax.mail.Session;
49
import javax.mail.Transport;
50
import javax.mail.internet.AddressException;
51
import javax.mail.internet.InternetAddress;
52
import javax.mail.internet.MimeBodyPart;
53
import javax.mail.internet.MimeMessage;
54
import javax.mail.internet.MimeMultipart;
56
import org.apache.tools.ant.BuildException;
57
import org.apache.tools.ant.Project;
60
* Uses the JavaMail classes to send Mime format email.
64
public class MimeMailer extends Mailer {
65
private static final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
67
private static final String GENERIC_ERROR =
68
"Problem while sending mime mail:";
70
/** Default character set */
71
private static final String DEFAULT_CHARSET
72
= System.getProperty("file.encoding");
74
// To work properly with national charsets we have to use
75
// implementation of interface javax.activation.DataSource
77
* String data source implementation.
80
class StringDataSource implements javax.activation.DataSource {
81
private String data = null;
82
private String type = null;
83
private String charset = null;
84
private ByteArrayOutputStream out;
86
public InputStream getInputStream() throws IOException {
87
if (data == null && out == null) {
88
throw new IOException("No data");
91
String encodedOut = out.toString(charset);
92
data = (data != null) ? data.concat(encodedOut) : encodedOut;
95
return new ByteArrayInputStream(data.getBytes(charset));
98
public OutputStream getOutputStream() throws IOException {
99
out = (out == null) ? new ByteArrayOutputStream() : out;
103
public void setContentType(String type) {
104
this.type = type.toLowerCase(Locale.ENGLISH);
107
public String getContentType() {
108
if (type != null && type.indexOf("charset") > 0
109
&& type.startsWith("text/")) {
112
// Must be like "text/plain; charset=windows-1251"
113
return new StringBuffer(type != null ? type : "text/plain").append(
114
"; charset=").append(charset).toString();
117
public String getName() {
118
return "StringDataSource";
121
public void setCharset(String charset) {
122
this.charset = charset;
125
public String getCharset() {
133
* @throws BuildException if the email can't be sent.
137
Properties props = new Properties();
139
props.put("mail.smtp.host", host);
140
props.put("mail.smtp.port", String.valueOf(port));
142
// Aside, the JDK is clearly unaware of the Scottish
143
// 'session', which involves excessive quantities of
146
Authenticator auth = null;
149
Provider p = (Provider) Class.forName(
150
"com.sun.net.ssl.internal.ssl.Provider").newInstance();
151
Security.addProvider(p);
152
} catch (Exception e) {
153
throw new BuildException("could not instantiate ssl "
154
+ "security provider, check that you have JSSE in "
158
props.put("mail.smtp.socketFactory.class", SSL_FACTORY);
159
props.put("mail.smtp.socketFactory.fallback", "false");
160
if (isPortExplicitlySpecified()) {
161
props.put("mail.smtp.socketFactory.port",
162
String.valueOf(port));
165
if (user != null || password != null) {
166
props.put("mail.smtp.auth", "true");
167
auth = new SimpleAuthenticator(user, password);
169
if (isStartTLSEnabled()) {
170
props.put("mail.smtp.starttls.enable", "true");
172
sesh = Session.getInstance(props, auth);
175
MimeMessage msg = new MimeMessage(sesh);
176
MimeMultipart attachments = new MimeMultipart();
179
if (from.getName() == null) {
180
msg.setFrom(new InternetAddress(from.getAddress()));
182
msg.setFrom(new InternetAddress(from.getAddress(),
185
// set the reply to addresses
186
msg.setReplyTo(internetAddresses(replyToList));
187
msg.setRecipients(Message.RecipientType.TO,
188
internetAddresses(toList));
189
msg.setRecipients(Message.RecipientType.CC,
190
internetAddresses(ccList));
191
msg.setRecipients(Message.RecipientType.BCC,
192
internetAddresses(bccList));
194
// Choosing character set of the mail message
195
// First: looking it from MimeType
196
String charset = parseCharSetFromMimeType(message.getMimeType());
197
if (charset != null) {
198
// Assign/reassign message charset from MimeType
199
message.setCharset(charset);
201
// Next: looking if charset having explicit definition
202
charset = message.getCharset();
203
if (charset == null) {
205
charset = DEFAULT_CHARSET;
206
message.setCharset(charset);
209
// Using javax.activation.DataSource paradigm
210
StringDataSource sds = new StringDataSource();
211
sds.setContentType(message.getMimeType());
212
sds.setCharset(charset);
214
if (subject != null) {
215
msg.setSubject(subject, charset);
217
msg.addHeader("Date", getDate());
219
if (headers != null) {
220
for (Iterator iter = headers.iterator(); iter.hasNext();) {
221
Header h = (Header) iter.next();
222
msg.addHeader(h.getName(), h.getValue());
225
PrintStream out = new PrintStream(sds.getOutputStream());
229
MimeBodyPart textbody = new MimeBodyPart();
230
textbody.setDataHandler(new DataHandler(sds));
231
attachments.addBodyPart(textbody);
233
Enumeration e = files.elements();
235
while (e.hasMoreElements()) {
236
File file = (File) e.nextElement();
240
body = new MimeBodyPart();
241
if (!file.exists() || !file.canRead()) {
242
throw new BuildException("File \"" + file.getAbsolutePath()
243
+ "\" does not exist or is not "
246
FileDataSource fileData = new FileDataSource(file);
247
DataHandler fileDataHandler = new DataHandler(fileData);
249
body.setDataHandler(fileDataHandler);
250
body.setFileName(file.getName());
251
attachments.addBodyPart(body);
253
msg.setContent(attachments);
255
// Send the message using SMTP, or SMTPS if the host uses SSL
256
Transport transport = sesh.getTransport(SSL ? "smtps" : "smtp");
257
transport.connect(host, user, password);
258
transport.sendMessage(msg, msg.getAllRecipients());
259
} catch (SendFailedException sfe) {
260
if (!shouldIgnoreInvalidRecipients()) {
261
throw new BuildException(GENERIC_ERROR, sfe);
262
} else if (sfe.getValidSentAddresses() == null
263
|| sfe.getValidSentAddresses().length == 0) {
264
throw new BuildException("Couldn't reach any recipient",
267
Address[] invalid = sfe.getInvalidAddresses();
268
if (invalid == null) {
269
invalid = new Address[0];
271
for (int i = 0; i < invalid.length; i++) {
272
didntReach(invalid[i], "invalid", sfe);
274
Address[] validUnsent = sfe.getValidUnsentAddresses();
275
if (validUnsent == null) {
276
validUnsent = new Address[0];
278
for (int i = 0; i < validUnsent.length; i++) {
279
didntReach(validUnsent[i], "valid", sfe);
283
} catch (MessagingException e) {
284
throw new BuildException(GENERIC_ERROR, e);
285
} catch (IOException e) {
286
throw new BuildException(GENERIC_ERROR, e);
290
private static InternetAddress[] internetAddresses(Vector list)
291
throws AddressException, UnsupportedEncodingException {
292
final int size = list.size();
293
InternetAddress[] addrs = new InternetAddress[size];
295
for (int i = 0; i < size; ++i) {
296
EmailAddress addr = (EmailAddress) list.elementAt(i);
298
String name = addr.getName();
299
addrs[i] = (name == null)
300
? new InternetAddress(addr.getAddress())
301
: new InternetAddress(addr.getAddress(), name);
306
private String parseCharSetFromMimeType(String type) {
310
int pos = type.indexOf("charset");
314
// Assuming mime type in form "text/XXXX; charset=XXXXXX"
315
StringTokenizer token = new StringTokenizer(type.substring(pos), "=; ");
316
token.nextToken(); // Skip 'charset='
317
return token.nextToken();
320
private void didntReach(Address addr, String category,
321
MessagingException ex) {
322
String msg = "Failed to send mail to " + category + " address "
323
+ addr + " because of " + ex.getMessage();
325
task.log(msg, Project.MSG_WARN);
327
System.err.println(msg);
331
static class SimpleAuthenticator extends Authenticator {
332
private String user = null;
333
private String password = null;
334
public SimpleAuthenticator(String user, String password) {
336
this.password = password;
338
public PasswordAuthentication getPasswordAuthentication() {
340
return new PasswordAuthentication(user, password);