1 package net.jaekl.qd.util;
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStreamReader;
6 import java.io.PrintWriter;
7 import java.net.InetAddress;
8 import java.net.Socket;
9 import java.net.UnknownHostException;
10 import java.util.ArrayList;
11 import java.util.List;
13 public class SendMail {
14 private static final String RESP_220 = "220"; // server ready
15 private static final String RESP_221 = "221"; // goodbye
16 private static final String RESP_250 = "250"; // success
17 private static final String RESP_251 = "251"; // mail address change (we don't support this)
18 private static final String RESP_354 = "354"; // OK, proceed with DATA transmission
27 ArrayList<String> m_to;
28 ArrayList<String> m_cc;
29 ArrayList<String> m_bcc;
30 ArrayList<MimePart> m_part;
36 m_smtpHost = "localhost";
38 m_from = "noreply@localhost";
40 m_to = new ArrayList<String>();
41 m_cc = new ArrayList<String>();
42 m_bcc = new ArrayList<String>();
43 m_part = new ArrayList<MimePart>();
46 public void setSmtpHost(String hostName) { m_smtpHost = hostName; }
47 public void setSmtpPort(int portNum) { m_smtpPort = portNum; }
48 public void setFrom(String addr) { m_from = addr; }
49 public void setSubject(String subject) { m_subject = subject; }
50 public void addTo(String addr) { m_to.add(addr); }
51 public void addCc(String addr) { m_cc.add(addr); }
52 public void addBcc(String addr) { m_bcc.add(addr); }
53 public void addPart(MimePart part) { m_part.add(part); }
55 public void send() throws MailException
58 Socket sock = new Socket(m_smtpHost, m_smtpPort);
59 PrintWriter pw = new PrintWriter(sock.getOutputStream(), true);
60 BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
65 catch (IOException exc) {
66 throw new MailException(exc);
70 String getHostName() throws UnknownHostException {
71 if (null == m_hostName) {
72 m_hostName = InetAddress.getLocalHost().getHostName();
77 void send(PrintWriter pw, BufferedReader br) throws MailException
83 line = readResponse(br);
84 validateResponse("", RESP_220, line);
86 cmd = "HELO " + getHostName();
87 validateResponse(cmd, RESP_250, line);
89 cmd = "MAIL FROM: " + m_from;
90 line = sendLine(pw, br, cmd);
91 validateResponse(cmd, RESP_250, line);
93 sendRcpt(pw, br, m_to);
94 sendRcpt(pw, br, m_cc);
95 sendRcpt(pw, br, m_bcc);
98 line = sendLine(pw, br, cmd);
99 validateResponse(cmd, RESP_354, line);
101 line = sendData(pw, br);
102 validateResponse(cmd, RESP_250, line);
105 line = sendLine(pw, br, cmd);
106 validateResponse(cmd, RESP_221, line);
108 catch (IOException exc) {
109 throw new MailException(exc);
113 // Send the DATA content of the email
114 String sendData(PrintWriter pw, BufferedReader br) throws MailException, IOException
116 sendLine(pw, "From: " + m_from);
117 sendAddressee(pw, "To", m_to);
118 sendAddressee(pw, "Cc", m_cc);
119 sendAddressee(pw, "Bcc", m_bcc);
120 sendLine(pw, "Subject: " + m_subject);
127 String result = sendLine(pw, br, "\r\n.\r\n");
131 void sendMimeHeaders(PrintWriter pw)
133 sendLine(pw, "MIME-Version: 1.0");
134 sendLine(pw, "Content-Type: multipart/mixed; boundary=" + getBoundary());
137 void sendMimeParts(PrintWriter pw) {
138 for (MimePart part : m_part) {
139 sendLine(pw, "--" + getBoundary());
140 sendLine(pw, "Content-Type: " + part.getMimeType()); // TODO: Add support for encodings
142 sendLine(pw, part.getContent());
144 sendLine(pw, "--" + getBoundary() + "--");
147 boolean isBoundaryOk(String candidate) {
148 for (MimePart part : m_part) {
149 if (part.getContent().contains(candidate)) {
158 if (null != m_boundary) {
162 StringBuilder sb = new StringBuilder("snip_snip");
163 if (isBoundaryOk(sb.toString())) {
164 return sb.toString();
171 if (isBoundaryOk(sb.toString())) {
172 m_boundary = sb.toString();
184 void sendAddressee(PrintWriter pw, String designator, List<String> addrs) throws MailException
186 for (String addr : addrs) {
187 sendLine(pw, designator + ": " + addr);
191 void validateResponse(String cmd, String actual, String expected) throws MailException
193 if (! actual.startsWith(expected)) {
194 throw new MailException(cmd, expected, actual);
198 void sendRcpt(PrintWriter pw, BufferedReader br, List<String> addrs) throws IOException, MailException
200 for (String to : addrs) {
201 String cmd = "RCPT TO: " + to;
202 String line = sendLine(pw, br, cmd);
203 if (line.startsWith(RESP_251)) {
204 throw new MailException("Apology: no support for 251 mail forwarding response to RCPT command. Mail will not be delivered: " + line);
206 if (!line.startsWith(RESP_250)) {
207 throw new MailException(cmd, RESP_250, line);
213 // RFC 5321 specifies that all response codes are three digits
214 // at the start of a new line.
215 // These digits may optionally be followed by a space and/or text.
216 // If the response is spread over more than one line, then all but
217 // the last line will have a '-' immediately after the 3rd digit.
219 // This function returns true iff. this line is a response code
220 // that is not continued on the next line.
221 boolean isFinalResponseCode(String line)
227 String trimmed = line.trim();
229 if (trimmed.length() < 3) {
232 if (! ( (Character.isDigit(trimmed.charAt(0)))
233 && (Character.isDigit(trimmed.charAt(1)))
234 && (Character.isDigit(trimmed.charAt(2))) ) )
239 if (3 == trimmed.length()) {
243 if (' ' == trimmed.charAt(3)) {
247 if ('-' == trimmed.charAt(3)) {
248 return false; // This is a response code, but it's continued on the next line
251 // This doesn't look like a response code
255 int responseCode(String line)
257 // RFC 5321 specifies that all response codes are three digits
258 // at the start of a new line.
259 // These digits may optionally be followed by a space and/or text.
261 if ((null == line) || (line.length() < 3)) {
265 return Integer.parseInt(line.substring(0, 3));
268 String readResponse(BufferedReader br) throws IOException
272 while (null != (line = br.readLine())) {
273 if (isFinalResponseCode(line)) {
280 void sendLine(PrintWriter pw, String line)
282 pw.write(line + "\r\n");
285 String sendLine(PrintWriter pw, BufferedReader br, String line) throws IOException
288 return readResponse(br);