package railo.runtime.net.mail;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.Normalizer;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import railo.commons.io.CharsetUtil;
import railo.commons.io.IOUtil;
import railo.commons.io.SystemUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.ExceptionUtil;
import railo.commons.lang.Md5;
import railo.commons.lang.StringUtil;
import railo.runtime.exp.PageException;
import railo.runtime.net.imap.ImapClient;
import railo.runtime.net.pop.PopClient;
import railo.runtime.op.Caster;
import railo.runtime.op.Operator;
import railo.runtime.type.Array;
import railo.runtime.type.ArrayImpl;
import railo.runtime.type.Collection;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Query;
import railo.runtime.type.QueryImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.ListUtil;
public abstract class MailClient {
/**
* Simple authenicator implmentation
*/
private final class _Authenticator extends Authenticator {
private String _fldif = null;
private String a = null;
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(_fldif, a);
}
public _Authenticator(String s, String s1) {
_fldif = s;
a = s1;
}
}
private static final Collection.Key DATE = KeyImpl.init("date");
private static final Collection.Key SUBJECT = KeyImpl.init("subject");
private static final Collection.Key SIZE = KeyImpl.init("size");
private static final Collection.Key FROM = KeyImpl.init("from");
private static final Collection.Key MESSAGE_NUMBER = KeyImpl.init("messagenumber");
private static final Collection.Key MESSAGE_ID = KeyImpl.init("messageid");
private static final Collection.Key REPLYTO = KeyImpl.init("replyto");
private static final Collection.Key CC = KeyImpl.init("cc");
private static final Collection.Key BCC = KeyImpl.init("bcc");
private static final Collection.Key TO = KeyImpl.init("to");
private static final Collection.Key UID = KeyImpl.init("uid");
private static final Collection.Key HEADER = KeyImpl.init("header");
private static final Collection.Key BODY = KeyImpl.init("body");
private static final Collection.Key CIDS = KeyImpl.init("cids");
private static final Collection.Key TEXT_BODY = KeyImpl.init("textBody");
private static final Collection.Key HTML_BODY = KeyImpl.init("HTMLBody");
private static final Collection.Key ATTACHMENTS = KeyImpl.init("attachments");
private static final Collection.Key ATTACHMENT_FILES = KeyImpl.init("attachmentfiles");
public static final int TYPE_POP3 = 0;
public static final int TYPE_IMAP = 1;
private String _flddo[] = {"date", "from", "messagenumber", "messageid", "replyto", "subject", "cc", "to", "size", "header", "uid"};
private String _fldnew[] = {"date", "from", "messagenumber", "messageid", "replyto", "subject", "cc", "to", "size", "header", "uid", "body", "textBody", "HTMLBody", "attachments", "attachmentfiles","cids"};
private String server = null;
private String username = null;
private String password = null;
private Session _fldtry = null;
private Store _fldelse = null;
private int port = 0;
private int timeout = 0;
private int startrow = 0;
private int maxrows = 0;
private boolean uniqueFilenames = false;
private Resource attachmentDirectory = null;
public static MailClient getInstance(int type,String server, int port, String username, String password){
if(TYPE_POP3==type)
return new PopClient(server,port,username,password);
if(TYPE_IMAP==type)
return new ImapClient(server,port,username,password);
return null;
}
/**
* constructor of the class
* @param server
* @param port
* @param username
* @param password
*/
public MailClient(String server, int port, String username, String password) {
timeout = 60000;
startrow = 0;
maxrows = -1;
uniqueFilenames = false;
this.server = server;
this.port = port;
this.username = username;
this.password = password;
}
/**
* @param maxrows The maxrows to set.
*/
public void setMaxrows(int maxrows) {
this.maxrows = maxrows;
}
/**
* @param startrow The startrow to set.
*/
public void setStartrow(int startrow) {
this.startrow = startrow;
}
/**
* @param timeout The timeout to set.
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
/**
* @param uniqueFilenames The uniqueFilenames to set.
*/
public void setUniqueFilenames(boolean uniqueFilenames) {
this.uniqueFilenames = uniqueFilenames;
}
/**
* @param attachmentDirectory The attachmentDirectory to set.
*/
public void setAttachmentDirectory(Resource attachmentDirectory) {
this.attachmentDirectory = attachmentDirectory;
}
/**
* connects to pop server
* @throws MessagingException
*/
public void connect() throws MessagingException {
Properties properties = new Properties();
String type=getTypeAsString();
properties.put("mail."+type+".host", server);
properties.put("mail."+type+".port", new Double(port));
properties.put("mail."+type+".connectiontimeout", String.valueOf(timeout));
properties.put("mail."+type+".timeout", String.valueOf(timeout));
//properties.put("mail.mime.charset", "UTF-8");
if(TYPE_IMAP==getType())properties.put("mail.imap.partialfetch", "false" );
_fldtry = username != null ? Session.getInstance(properties, new _Authenticator(username, password)) : Session.getInstance(properties);
_fldelse = _fldtry.getStore(type);
if(!StringUtil.isEmpty(username))_fldelse.connect(server,username,password);
else _fldelse.connect();
}
protected abstract String getTypeAsString();
protected abstract int getType();
/**
* delete all message in ibox that match given criteria
* @param messageNumbers
* @param uIds
* @throws MessagingException
* @throws IOException
*/
public void deleteMails(String as[], String as1[]) throws MessagingException, IOException {
Folder folder;
Message amessage[];
folder = _fldelse.getFolder("INBOX");
folder.open(2);
Map<String, Message> map = getMessages(null,folder, as1, as, startrow, maxrows,false);
Iterator<String> iterator = map.keySet().iterator();
amessage = new Message[map.size()];
int i = 0;
while(iterator.hasNext()) {
amessage[i++] = map.get(iterator.next());
}
try {
folder.setFlags(amessage, new Flags(javax.mail.Flags.Flag.DELETED), true);
}
finally {
folder.close(true);
}
}
/**
* return all messages from inbox
* @param messageNumbers all messages with this ids
* @param uIds all messages with this uids
* @param withBody also return body
* @return all messages from inbox
* @throws MessagingException
* @throws IOException
*/
public Query getMails(String[] messageNumbers, String[] uids, boolean all) throws MessagingException, IOException {
Query qry = new QueryImpl(all ? _fldnew : _flddo, 0, "query");
Folder folder = _fldelse.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
try {
getMessages(qry,folder, uids, messageNumbers, startrow, maxrows,all);
}
finally {
folder.close(false);
}
return qry;
}
private void toQuery(Query qry, Message message, Object uid, boolean all)
{
int row = qry.addRow();
// date
try {
qry.setAtEL(DATE, row, Caster.toDate(message.getSentDate(), true,null,null));
}
catch (MessagingException e) {}
// subject
try {
qry.setAtEL(SUBJECT, row, message.getSubject());
} catch (MessagingException e) {
qry.setAtEL(SUBJECT, row, "MessagingException:"+e.getMessage());
}
// size
try {
qry.setAtEL(SIZE, row, new Double(message.getSize()));
}
catch (MessagingException e) {}
qry.setAtEL(FROM, row, toList(getHeaderEL(message,"from")));
qry.setAtEL(MESSAGE_NUMBER, row, new Double(message.getMessageNumber()));
qry.setAtEL(MESSAGE_ID, row, toList(getHeaderEL(message,"Message-ID")));
String s = toList(getHeaderEL(message,"reply-to"));
if(s.length() == 0) {
s = Caster.toString(qry.getAt(FROM, row,null), "");
}
qry.setAtEL(REPLYTO, row, s);
qry.setAtEL(CC, row, toList(getHeaderEL(message,"cc")));
qry.setAtEL(BCC, row, toList(getHeaderEL(message,"bcc")));
qry.setAtEL(TO, row, toList(getHeaderEL(message,"to")));
qry.setAtEL(UID, row, uid);
StringBuffer content = new StringBuffer();
try {
for(Enumeration enumeration = message.getAllHeaders(); enumeration.hasMoreElements(); content.append('\n')){
Header header = (Header) enumeration.nextElement();
content.append(header.getName());
content.append(": ");
content.append(header.getValue());
}
}
catch (MessagingException e) {}
qry.setAtEL(HEADER, row, content.toString());
if(all) {
getContentEL(qry, message, row);
}
}
private String[] getHeaderEL(Message message, String key) {
try {
return message.getHeader(key);
} catch (MessagingException e) {
return null;
}
}
/**
* gets all messages from given Folder that match given criteria
* @param qry
* @param folder
* @param uIds
* @param messageNumbers
* @param all
* @param startrow
* @param maxrows
* @return
* @return matching Messages
* @throws MessagingException
*/
private Map<String,Message> getMessages(Query qry, Folder folder, String[] uids, String[] messageNumbers, int startRow, int maxRow, boolean all) throws MessagingException
{
Message[] messages = folder.getMessages();
Map<String,Message> map = qry==null?new HashMap<String,Message>():null;
int k = 0;
if(uids != null || messageNumbers != null) {
startRow = 0;
maxRow = -1;
}
Message message;
for(int l = startRow; l < messages.length; l++) {
if(maxRow != -1 && k == maxRow) {
break;
}
message = messages[l];
int messageNumber = message.getMessageNumber();
String id = getId(folder,message);
if(uids == null ? messageNumbers == null || contains(messageNumbers, messageNumber) : contains(uids, id)) {
k++;
if(qry!=null){
toQuery(qry,message,id,all);
}
else map.put(id, message);
}
}
return map;
}
protected abstract String getId(Folder folder,Message message) throws MessagingException ;
private void getContentEL(Query query, Message message, int row) {
try {
getContent(query, message, row);
} catch (Exception e) {
String st = ExceptionUtil.getStacktrace(e,true);
query.setAtEL(BODY, row, st);
}
}
/**
* write content data to query
* @param qry
* @param content
* @param row
* @throws MessagingException
* @throws IOException
*/
private void getContent(Query query, Message message, int row) throws MessagingException, IOException {
StringBuffer body = new StringBuffer();
Struct cids=new StructImpl();
query.setAtEL(CIDS, row, cids);
if(message.isMimeType("text/plain")) {
String content=getConent(message);
query.setAtEL(TEXT_BODY,row,content);
body.append(content);
}
else if(message.isMimeType("text/html")) {
String content=getConent(message);
query.setAtEL(HTML_BODY,row,content);
body.append(content);
}
else {
Object content = message.getContent();
if(content instanceof MimeMultipart) {
Array attachments = new ArrayImpl();
Array attachmentFiles = new ArrayImpl();
getMultiPart(query, row, attachments, attachmentFiles,cids, (MimeMultipart) content, body);
if(attachments.size() > 0) {
try {
query.setAtEL(ATTACHMENTS, row, ListUtil.arrayToList(attachments, "\t"));
}
catch(PageException pageexception) {
}
}
if(attachmentFiles.size() > 0) {
try {
query.setAtEL(ATTACHMENT_FILES, row, ListUtil.arrayToList(attachmentFiles, "\t"));
}
catch(PageException pageexception1) {
}
}
}
}
query.setAtEL(BODY, row, body.toString());
}
private void getMultiPart(Query query, int row, Array attachments, Array attachmentFiles,Struct cids, Multipart multiPart, StringBuffer body) throws MessagingException, IOException {
int j = multiPart.getCount();
for(int k = 0; k < j; k++) {
BodyPart bodypart = multiPart.getBodyPart(k);
Object content;
if(bodypart.getFileName() != null) {
String filename = bodypart.getFileName();
try{
filename=Normalizer.normalize(MimeUtility.decodeText(filename),Normalizer.Form.NFC);
}
catch(Throwable t){}
if(bodypart.getHeader("Content-ID") != null) {
String[] ids = bodypart.getHeader("Content-ID");
String cid=ids[0].substring(1, ids[0].length() - 1);
cids.setEL(KeyImpl.init(filename), cid);
}
if(filename != null && ArrayUtil.find(attachments, filename)==0) {
attachments.appendEL(filename);
if(attachmentDirectory != null) {
Resource file = attachmentDirectory.getRealResource(filename);
int l = 1;
String s2;
for(; uniqueFilenames && file.exists(); file = attachmentDirectory.getRealResource(s2)) {
String as[] = ResourceUtil.splitFileName(filename);
s2 = as.length != 1 ? as[0] + l++ + '.' + as[1] : as[0] + l++;
}
IOUtil.copy(bodypart.getInputStream(), file, true);
attachmentFiles.appendEL(file.getAbsolutePath());
}
}
}
else if(bodypart.isMimeType("text/plain")) {
content=getConent(bodypart);
query.setAtEL(TEXT_BODY,row,content);
if(body.length()==0)body.append(content);
}
else if(bodypart.isMimeType("text/html")) {
content=getConent(bodypart);
query.setAtEL(HTML_BODY,row,content);
if(body.length()==0)body.append(content);
}
else if((content=bodypart.getContent()) instanceof Multipart) {
getMultiPart(query, row, attachments, attachmentFiles,cids, (Multipart) content, body);
}
else if(bodypart.getHeader("Content-ID") != null) {
String[] ids = bodypart.getHeader("Content-ID");
String cid=ids[0].substring(1, ids[0].length() - 1);
String filename = "cid:" + cid;
attachments.appendEL(filename);
if(attachmentDirectory != null) {
filename = "_" + Md5.getDigestAsString(filename);
Resource file = attachmentDirectory.getRealResource(filename);
int l = 1;
String s2;
for(; uniqueFilenames && file.exists(); file = attachmentDirectory.getRealResource(s2)) {
String as[] = ResourceUtil.splitFileName(filename);
s2 = as.length != 1 ? as[0] + l++ + '.' + as[1] : as[0] + l++;
}
IOUtil.copy(bodypart.getInputStream(), file, true);
attachmentFiles.appendEL(file.getAbsolutePath());
}
cids.setEL(KeyImpl.init(filename), cid);
}
}
}
/* *
* writes BodyTag data to query, if there is a problem with encoding, encoding will removed a do it again
* @param qry
* @param columnName
* @param row
* @param bp
* @param body
* @throws IOException
* @throws MessagingException
* /
private void setBody(Query qry, String columnName, int row, BodyPart bp, StringBuffer body) throws IOException, MessagingException {
String content = getConent(bp);
qry.setAtEL(columnName,row,content);
if(body.length()==0)body.append(content);
}*/
private String getConent(Part bp) throws MessagingException {
InputStream is=null;
try {
return getContent(is=bp.getInputStream(), CharsetUtil.toCharset(getCharsetFromContentType(bp.getContentType())));
}
catch(IOException mie) {
IOUtil.closeEL(is);
try {
return getContent(is=bp.getInputStream(), SystemUtil.getCharset());
}
catch (IOException e) {
return "Can't read body of this message:"+e.getMessage();
}
}
finally {
IOUtil.closeEL(is);
}
}
private String getContent(InputStream is,Charset charset) throws IOException {
return MailUtil.decode(IOUtil.toString(is, charset));
}
private static String getCharsetFromContentType(String contentType) {
Array arr=ListUtil.listToArrayRemoveEmpty(contentType,"; ");
for(int i=1;i<=arr.size();i++) {
Array inner = ListUtil.listToArray((String)arr.get(i,null),"= ");
if(inner.size()==2 && ((String)inner.get(1,"")).trim().equalsIgnoreCase("charset")) {
String charset = (String) inner.get(2,"");
charset=charset.trim();
if(!StringUtil.isEmpty(charset)) {
if(StringUtil.startsWith(charset, '"') && StringUtil.endsWith(charset, '"')) {
charset=charset.substring(1,charset.length()-1);
}
if(StringUtil.startsWith(charset, '\'') && StringUtil.endsWith(charset, '\'')) {
charset=charset.substring(1,charset.length()-1);
}
}
return charset;
}
}
return "us-ascii";
}
/**
* checks if a String Array (ids) has one element that is equal to id
* @param ids
* @param id
* @return has element found or not
*/
private boolean contains(String ids[], String id) {
for(int i = 0; i < ids.length; i++) {
if(Operator.compare(ids[i], id) == 0) return true;
}
return false;
}
/**
* checks if a String Array (ids) has one element that is equal to id
* @param ids
* @param id
* @return has element found or not
*/
private boolean contains(String ids[], int id) {
for(int i = 0; i < ids.length; i++) {
if(Operator.compare(ids[i], id) == 0) return true;
}
return false;
}
/**
* translate a String Array to String List
* @param arr Array to translate
* @return List from Array
*/
private String toList(String ids[]) {
if(ids == null) return "";
return ListUtil.arrayToList(ids, ",");
}
/**
* disconnect without a exception
*/
public void disconnectEL() {
try {
if(_fldelse != null)_fldelse.close();
}
catch(Exception exception) {}
}
}