Improved SMTP email robustness and added JUnit test for it

This commit is contained in:
Ziver Koc 2017-01-20 00:38:47 +01:00
parent b109d6ae5c
commit 3d04dba4fd
3 changed files with 239 additions and 66 deletions

View file

@ -1,11 +1,13 @@
package zutil.net.smtp;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;
import static zutil.net.smtp.SMTPClient.NEWLINE;
import sun.net.smtp.SmtpClient;
/**
* Simplifies sending of a email
@ -13,16 +15,20 @@ import sun.net.smtp.SmtpClient;
* @author Ziver
*/
public class Email {
public enum ContentType{
public enum ContentType{
PLAIN, HTML
}
private static final SimpleDateFormat dateFormatter =
new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
private static final Pattern PATTERN_NEWLINE = Pattern.compile("(\\r\\n|\\n)");
private String from;
private String niceFrom = null;
private String to;
private String replyTo = null;
private String fromAddress;
private String fromName = null;
private String toAddress;
private String toName = null;
private String replyToAddress = null;
private Date date = null;
private ContentType type = ContentType.PLAIN;
private String subject;
private String message;
@ -33,33 +39,66 @@ public class Email {
public void setFrom(String address){
from = address;
this.fromAddress = sanitizeParam(address);
}
public void setFrom(String address, String niceName){
from = address;
niceFrom = niceName;
fromAddress = sanitizeParam(address);
fromName = sanitizeParam(niceName);
}
public String getFromAddress(){
return from;
return fromAddress;
}
public void setReplyTo(String rpt){
replyTo = rpt;
public String getFromName() {
return fromName;
}
public void setReplyTo(String address){
this.replyToAddress = sanitizeParam(address);
}
public void setTo(String t){
to = t;
public String getReplyToAddress() {
return replyToAddress;
}
public void setTo(String address){
this.toAddress = sanitizeParam(address);
}
public String getTo(){
return to;
public void setTo(String address, String niceName){
this.toAddress = sanitizeParam(address);
this.toName = sanitizeParam(niceName);
}
public String getToAddress(){
return toAddress;
}
public String getToName() {
return toName;
}
public void setDate(Date date){
this.date = date;
}
public void setContentType(ContentType t){
type = t;
}
public void setSubject(String s){
subject = s;
public void setSubject(String subject){
this.subject = sanitizeParam(subject);
}
public String getSubject(){
return subject;
}
public void setMessage(String msg){
message = msg;
message = msg.replaceAll("(\\r\\n|\\n)", NEWLINE);
message = message.replaceAll(NEWLINE+"\\.", NEWLINE +"..");
}
public String getMessage(){
return message;
}
private String sanitizeParam(String param){
return PATTERN_NEWLINE.matcher(param).replaceAll("");
}
/**
@ -67,33 +106,48 @@ public class Email {
*
* @throws IllegalArgumentException if from address and to address has not been set
*/
public void write(PrintStream out) throws IOException{
if(from == null)
public void write(Writer out) throws IOException{
if(fromAddress == null)
throw new IllegalArgumentException("From value cannot be null!");
if(to == null)
if(toAddress == null)
throw new IllegalArgumentException("To value cannot be null!");
//************ Headers
if (niceFrom!=null)
out.println("From: \""+niceFrom+"\" <"+from+">");
// From
if (fromName !=null)
out.write("From: "+ fromName +" <"+ fromAddress +">"+ NEWLINE);
else
out.println("From: <"+from+">");
if ( replyTo != null )
out.println("Reply-To: <"+replyTo+">");
out.println("To: " + to);
out.println("Subject: "+subject);
out.write("From: "+ fromAddress + NEWLINE);
// Reply-To
if ( replyToAddress != null )
out.write("Reply-To: <"+ replyToAddress +">"+ NEWLINE);
// To
if (toName !=null)
out.write("To: "+ toName +" <"+ toAddress +">"+ NEWLINE);
else
out.write("To: "+ toAddress + NEWLINE);
// Date
out.println("Date: "+dateFormatter.format(new Date(System.currentTimeMillis())));
if (date != null)
out.write("Date: "+dateFormatter.format(date) + NEWLINE);
else
out.write("Date: "+dateFormatter.format(new Date(System.currentTimeMillis())) + NEWLINE);
// Content type
switch( type ){
case HTML:
out.println("Content-Type: text/html;"); break;
out.write("Content-Type: text/html;"+ NEWLINE); break;
default:
out.println("Content-Type: text/plain;"); break;
out.write("Content-Type: text/plain;"+ NEWLINE); break;
}
out.println();
// Subject
out.write("Subject: "+(subject!=null ? subject : "") + NEWLINE);
out.write(NEWLINE);
//*********** Mesasge
out.println( message );
out.write( message );
}
}

View file

@ -26,10 +26,7 @@ package zutil.net.smtp;
import zutil.log.LogUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.logging.Level;
@ -46,7 +43,7 @@ import java.util.logging.Logger;
public class SMTPClient {
private static final Logger logger = LogUtil.getLogger();
private static final String NEWLINE = "\r\n";
protected static final String NEWLINE = "\r\n";
private static final String CMD_HELO = "HELO";
private static final String CMD_FROM = "MAIL FROM";
private static final String CMD_TO = "RCPT TO";
@ -59,7 +56,7 @@ public class SMTPClient {
private Socket socket;
private BufferedReader in;
private PrintStream out;
private Writer out;
/**
@ -77,7 +74,7 @@ public class SMTPClient {
public SMTPClient(String host, int port) throws IOException {
socket = new Socket(host, port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintStream(socket.getOutputStream());
out = new OutputStreamWriter(socket.getOutputStream());
readCommand();
sendCommand(CMD_HELO + " " + InetAddress.getLocalHost().getHostName());
@ -92,26 +89,12 @@ public class SMTPClient {
* @param msg the email body message
*/
public synchronized void send(String from, String to, String subj, String msg) throws IOException{
if(from == null)
throw new IllegalArgumentException("From value cannot be null!");
if(to == null)
throw new IllegalArgumentException("To value cannot be null!");
try{
// Pre metadata
sendCommand(CMD_FROM + ":" + from);
sendCommand(CMD_TO + ":" + to);
sendCommand(CMD_DATA);
// Message headers and body
out.println("From: "+from);
out.println("To: "+to);
out.println("Subject: "+subj);
out.println("");
out.println(msg);
sendCommand(CMD_DATA_END);
reset();
}catch(IOException e){
logger.log(Level.SEVERE, null, e);
}
Email email = new Email();
email.setFrom(from);
email.setTo(to);
email.setSubject(subj);
email.setMessage(msg);
send(email);
}
/**
@ -122,15 +105,16 @@ public class SMTPClient {
public synchronized void send(Email email) throws IOException{
if(email.getFromAddress() == null)
throw new IllegalArgumentException("From value cannot be null!");
if(email.getTo() == null)
if(email.getToAddress() == null)
throw new IllegalArgumentException("To value cannot be null!");
try{
// Pre metadata
sendCommand(CMD_FROM + ":" + email.getFromAddress());
sendCommand(CMD_TO + ":" + email.getTo());
sendCommand(CMD_TO + ":" + email.getToAddress());
sendCommand(CMD_DATA);
// Message headers and body
email.write(out);
out.write(NEWLINE);
sendCommand(CMD_DATA_END);
reset();
}catch(IOException e){
@ -148,7 +132,7 @@ public class SMTPClient {
*/
public synchronized int sendCommand(String cmd) throws IOException{
logger.finest(">> "+cmd);
out.print(cmd + NEWLINE);
out.write(cmd + NEWLINE);
String reply = readCommand();
return parseReturnCode(reply);
}

View file

@ -0,0 +1,135 @@
package zutil.net.smtp;
import org.junit.Test;
import zutil.io.StringOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
import java.util.GregorianCalendar;
import static org.junit.Assert.*;
import static zutil.net.smtp.SMTPClient.NEWLINE;
/**
* Created by Ziver on 2017-01-19.
*/
public class EmailTest {
@Test
public void sanitizingFrom(){
Email email = new Email();
email.setFrom("aa\n@aa.aa"+NEWLINE);
assertEquals("aa@aa.aa", email.getFromAddress());
email.setFrom("aa\n@aa.aa"+NEWLINE, "aa\n bb"+NEWLINE);
assertEquals("aa@aa.aa", email.getFromAddress());
assertEquals("aa bb", email.getFromName());
}
@Test
public void sanitizingReplyTo(){
Email email = new Email();
email.setReplyTo("aa\n@aa.aa"+NEWLINE);
assertEquals("aa@aa.aa", email.getReplyToAddress());
}
@Test
public void sanitizingTo(){
Email email = new Email();
email.setTo("aa\n@aa.aa"+NEWLINE);
assertEquals("aa@aa.aa", email.getToAddress());
email.setTo("aa\n@aa.aa"+NEWLINE, "aa\n bb"+NEWLINE);
assertEquals("aa@aa.aa", email.getToAddress());
assertEquals("aa bb", email.getToName());
}
@Test
public void sanitizingSubject(){
Email email = new Email();
email.setSubject("aa\n aa aa"+NEWLINE);
assertEquals("aa aa aa", email.getSubject());
}
@Test
public void sanitizingMessage(){
Email email = new Email();
email.setMessage("aa\nbb"+ NEWLINE +"cc\n");
assertEquals("aa"+ NEWLINE +"bb"+ NEWLINE +"cc"+ NEWLINE,
email.getMessage());
email.setMessage("aa"+ NEWLINE +"."+ NEWLINE +"bb");
assertEquals("aa"+ NEWLINE +".."+ NEWLINE +"bb",
email.getMessage());
email.setMessage("aa\n.\nbb");
assertEquals("aa"+ NEWLINE +".."+ NEWLINE +"bb",
email.getMessage());
}
@Test
public void simpleEmail() throws IOException {
Email email = new Email();
email.setFrom("test@example.com");
email.setTo("to@example.com");
email.setDate(getDate());
email.setMessage("message");
assertEquals(
"From: test@example.com" + NEWLINE +
"To: to@example.com" + NEWLINE +
"Date: on, 22 nov 2000 15:20:55 +0100" + NEWLINE +
"Content-Type: text/plain;" + NEWLINE +
"Subject: " + NEWLINE +
NEWLINE +
"message",
getEmailString(email));
}
@Test
public void fullEmail() throws IOException {
Email email = new Email();
email.setFrom("test@example.com", "Test Tester");
email.setTo("to@example.com", "To Totter");
email.setDate(getDate());
email.setContentType(Email.ContentType.HTML);
email.setReplyTo("mokey@example.org");
email.setSubject("Title");
email.setMessage("<html>\n<body>\n<b>message</b>\n</body>\n</html>");
assertEquals(
"From: Test Tester <test@example.com>" + NEWLINE +
"Reply-To: <mokey@example.org>" + NEWLINE +
"To: To Totter <to@example.com>" + NEWLINE +
"Date: on, 22 nov 2000 15:20:55 +0100" + NEWLINE +
"Content-Type: text/html;" + NEWLINE +
"Subject: Title" + NEWLINE +
NEWLINE +
"<html>"+NEWLINE+"<body>"+NEWLINE+"<b>message</b>"+NEWLINE+"</body>"+NEWLINE+"</html>",
getEmailString(email));
}
private String getEmailString(Email email) throws IOException {
StringOutputStream buff = new StringOutputStream();
OutputStreamWriter out = new OutputStreamWriter(buff);
email.write(out);
out.flush();
return buff.toString();
}
private Date getDate(){
GregorianCalendar date = new GregorianCalendar(2000,10,22, 15,20,55);
return date.getTime();
}
}