blob: 454e85ccd499fcd184f4fe64c9c50de59bca8ca4 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.hupa.server.handler;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import javax.activation.DataSource;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.BodyPart;
import javax.mail.Flags.Flag;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import javax.mail.internet.MimeMultipart;
import javax.servlet.http.HttpSession;
import net.customware.gwt.dispatch.server.ExecutionContext;
import net.customware.gwt.dispatch.shared.ActionException;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.logging.Log;
import org.apache.hupa.server.FileItemRegistry;
import org.apache.hupa.server.IMAPStoreCache;
import org.apache.hupa.server.preferences.UserPreferencesStorage;
import org.apache.hupa.server.utils.MessageUtils;
import org.apache.hupa.server.utils.RegexPatterns;
import org.apache.hupa.server.utils.SessionUtils;
import org.apache.hupa.shared.SConsts;
import org.apache.hupa.shared.domain.MessageAttachment;
import org.apache.hupa.shared.domain.SmtpMessage;
import org.apache.hupa.shared.domain.User;
import org.apache.hupa.shared.rpc.GenericResult;
import org.apache.hupa.shared.rpc.SendMessage;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPStore;
/**
* Handle sending of email messages
*
*/
public abstract class AbstractSendMessageHandler<A extends SendMessage> extends AbstractSessionHandler<A,GenericResult> {
private final boolean auth;
private final String address;
private final int port;
private boolean useSSL = false;
UserPreferencesStorage userPreferences;
Session session;
@Inject
public AbstractSendMessageHandler(Log logger, IMAPStoreCache store, Provider<HttpSession> provider, UserPreferencesStorage preferences, @Named("SMTPServerAddress") String address, @Named("SMTPServerPort") int port, @Named("SMTPAuth") boolean auth, @Named("SMTPS") boolean useSSL) {
super(store,logger,provider);
this.auth = auth;
this.address = address;
this.port = port;
this.useSSL = useSSL;
this.userPreferences = preferences;
this.session = store.getMailSession();
session.getProperties().put("mail.smtp.auth", auth);
}
@Override
protected GenericResult executeInternal(A action, ExecutionContext context)
throws ActionException {
GenericResult result = new GenericResult();
try {
Message message = createMessage(session, action);
message = fillBody(message,action);
sendMessage(getUser(), message);
saveSentMessage(getUser(), message);
resetAttachments(action);
// TODO: notify the user more accurately where the error is
// if the message was sent and the storage in the sent folder failed, etc.
} catch (AddressException e) {
result.setError("Error while parsing recipient: " + e.getMessage());
logger.error("Error while parsing recipient", e);
} catch (AuthenticationFailedException e) {
result.setError("Error while sending message: SMTP Authentication error.");
logger.error("SMTP Authentication error", e);
} catch (MessagingException e) {
result.setError("Error while sending message: " + e.getMessage());
logger.error("Error while sending message", e);
} catch (Exception e) {
result.setError("Unexpected exception while sendig message: " + e.getMessage());
logger.error("Unexpected exception while sendig message: ", e);
}
return result;
}
/**
* Create basic Message which contains all headers. No body is filled
*
* @param session the Session
* @param action the action
* @return message
* @throws AddressException
* @throws MessagingException
* @throws ActionException
*/
protected Message createMessage(Session session, A action) throws AddressException, MessagingException {
MimeMessage message = new MimeMessage(session);
SmtpMessage m = action.getMessage();
message.setFrom(new InternetAddress(m.getFrom()));
userPreferences.addContact(m.getTo());
userPreferences.addContact(m.getCc());
userPreferences.addContact(m.getBcc());
message.setRecipients(RecipientType.TO, MessageUtils.getRecipients(m.getTo()));
message.setRecipients(RecipientType.CC, MessageUtils.getRecipients(m.getCc()));
message.setRecipients(RecipientType.BCC, MessageUtils.getRecipients(m.getBcc()));
<<<<<<< HEAD
<<<<<<< HEAD
message.setSubject(MessageUtils.encodeTexts(m.getSubject()));
<<<<<<< HEAD
=======
message.setSubject(m.getSubject());
>>>>>>> first commit
=======
message.setSubject(MessageUtils.encodeTexts(m.getSubject()));
>>>>>>> constantly changed by manolo
=======
updateHeaders(message, action);
>>>>>>> Fixes HUPA-96 : pass reference ids when replying. Patch by Zsombor Gegesy
message.saveChanges();
return message;
}
protected void updateHeaders(MimeMessage message, A action) {
if (action.getInReplyTo() != null) {
try {
message.addHeader(SConsts.HEADER_IN_REPLY_TO, action.getInReplyTo());
} catch (MessagingException e) {
logger.error("Error while setting header:" + e.getMessage(), e);
}
}
if (action.getReferences() != null) {
try {
message.addHeader(SConsts.HEADER_REFERENCES, action.getReferences());
} catch (MessagingException e) {
logger.error("Error while setting header:" + e.getMessage(), e);
}
}
}
/**
* Fill the body of the given message with data which the given action contain
*
* @param message the message
* @param action the action
* @return filledMessage
* @throws MessagingException
* @throws ActionException
* @throws IOException
*/
protected Message fillBody(Message message, A action) throws MessagingException, ActionException, IOException {
String html = restoreInlineLinks(action.getMessage().getText());
// TODO: client sends the message as a html document right now,
// the idea is that it should be sent in both formats because
// it is easier to handle html in the browser.
String text = htmlToText(html);
@SuppressWarnings("rawtypes")
List items = getAttachments(action);
return composeMessage(message, text, html, items);
}
protected String restoreInlineLinks(String s) {
return RegexPatterns.replaceAll(s, RegexPatterns.regex_revertInlineImg, RegexPatterns.repl_revertInlineImg);
}
// TODO: just temporary stuff because it has to be done in the client side
protected String htmlToText(String s){
s=s.replaceAll("\n", " ");
s=s.replaceAll("(?si)<br\\s*?/?>", "\n");
s=s.replaceAll("(?si)</div\\s*?>", "\n");
s=s.replaceAll("(\\w)<.*?>(\\w)", "$1 $2");
s=s.replaceAll("<.*?>", "");
s=s.replaceAll("[ \t]+", " ");
return s;
}
/**
* Get the attachments stored in the registry.
*
* @param action
* @return A list of stored attachments
*/
@SuppressWarnings("rawtypes")
protected List getAttachments(A action) throws MessagingException, ActionException {
FileItemRegistry registry = SessionUtils.getSessionRegistry(logger, httpSessionProvider.get());
List<MessageAttachment> attachments = action.getMessage().getMessageAttachments();
ArrayList<FileItem> items = new ArrayList<FileItem>();
if (attachments != null && attachments.size() > 0) {
for (MessageAttachment attachment: attachments) {
FileItem fItem = registry.get(attachment.getName());
if (fItem != null)
items.add(fItem);
}
logger.debug("Found " + items.size() + " attachmets in the registry.");
}
return items;
}
/**
* Remove attachments from the registry
*
* @param action
* @throws MessagingException
* @throws ActionException
*/
protected void resetAttachments(A action) throws MessagingException, ActionException {
SmtpMessage msg = action.getMessage();
List<MessageAttachment> attachments = msg.getMessageAttachments();
if (attachments != null && ! attachments.isEmpty()) {
for(MessageAttachment attach : attachments)
SessionUtils.getSessionRegistry(logger, httpSessionProvider.get()).remove(attach.getName());
}
}
/**
* Send the message using SMTP, if the configuration uses authenticated SMTP, it uses
* the user stored in session to get the given login and password.
*
* @param user
* @param session
* @param message
* @throws MessagingException
*/
protected void sendMessage(User user, Message message) throws MessagingException {
Transport transport = cache.getMailTransport(useSSL);
if (auth) {
logger.debug("Use auth for smtp connection");
transport.connect(address,port,user.getName(), user.getPassword());
} else {
transport.connect(address, port, null,null);
}
Address[] recips = message.getAllRecipients();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < recips.length; i++) {
sb.append(recips[i]);
if (i != recips.length -1) {
sb.append(", ");
}
}
logger.info("Send message from " + message.getFrom()[0].toString()+ " to " + sb.toString());
transport.sendMessage(message, recips);
}
/**
* Save the message in the sent folder
*
* @param user
* @param message
* @throws MessagingException
* @throws IOException
*/
protected void saveSentMessage(User user, Message message) throws MessagingException, IOException {
IMAPStore iStore = cache.get(user);
IMAPFolder folder = (IMAPFolder) iStore.getFolder(user.getSettings().getSentFolderName());
if (folder.exists() || folder.create(IMAPFolder.READ_WRITE)) {
if (folder.isOpen() == false) {
folder.open(Folder.READ_WRITE);
}
// It is necessary to copy the message, before putting it
// in the sent folder. If not, it is not guaranteed that it is
// stored in ascii and is not possible to get the attachments
// size. message.saveChanges() doesn't fix the problem.
// There are tests which demonstrate this.
message = new MimeMessage((MimeMessage)message);
message.setFlag(Flag.SEEN, true);
folder.appendMessages(new Message[] {message});
try {
folder.close(false);
} catch (MessagingException e) {
// we don't care on close
}
}
}
/**
* Fill the body of a message already created.
* The result message depends on the information given.
*
* @param message
* @param text
* @param html
* @param parts
* @return The composed message
* @throws MessagingException
* @throws IOException
*/
@SuppressWarnings("rawtypes")
public static Message composeMessage (Message message, String text, String html, List parts) throws MessagingException, IOException {
MimeBodyPart txtPart = null;
MimeBodyPart htmlPart = null;
MimeMultipart mimeMultipart = null;
if (text == null && html == null) {
text = "";
}
if (text != null) {
txtPart = new MimeBodyPart();
txtPart.setContent(text, "text/plain");
}
if (html != null) {
htmlPart = new MimeBodyPart();
htmlPart.setContent(html, "text/html");
}
if (html != null && text != null) {
mimeMultipart = new MimeMultipart();
mimeMultipart.setSubType("alternative");
mimeMultipart.addBodyPart(txtPart);
mimeMultipart.addBodyPart(htmlPart);
}
if (parts == null || parts.isEmpty()) {
if (mimeMultipart != null) {
message.setContent(mimeMultipart);
} else if (html != null) {
message.setText(html);
message.setHeader("Content-type", "text/html");
} else if (text != null) {
message.setText(text);
}
} else {
MimeBodyPart bodyPart = new MimeBodyPart();
if (mimeMultipart != null) {
bodyPart.setContent(mimeMultipart);
} else if (html != null) {
bodyPart.setText(html);
bodyPart.setHeader("Content-type", "text/html");
} else if (text != null) {
bodyPart.setText(text);
}
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(bodyPart);
for (Object attachment: parts) {
if (attachment instanceof FileItem) {
multipart.addBodyPart(MessageUtils.fileitemToBodypart((FileItem)attachment));
} else {
multipart.addBodyPart((BodyPart)attachment);
}
}
message.setContent(multipart);
}
message.saveChanges();
return message;
}
/**
* DataStore which wrap a FileItem
*
*/
public static class FileItemDataStore implements DataSource {
private FileItem item;
public FileItemDataStore(FileItem item) {
this.item = item;
}
/*
* (non-Javadoc)
* @see javax.activation.DataSource#getContentType()
*/
public String getContentType() {
return item.getContentType();
}
/*
* (non-Javadoc)
* @see javax.activation.DataSource#getInputStream()
*/
public InputStream getInputStream() throws IOException {
return item.getInputStream();
}
/*
* (non-Javadoc)
* @see javax.activation.DataSource#getName()
*/
public String getName() {
String fullName = item.getName();
// Strip path from file
int index = fullName.lastIndexOf(File.separator);
if (index == -1) {
return fullName;
} else {
return fullName.substring(index +1 ,fullName.length());
}
}
/*
* (non-Javadoc)
* @see javax.activation.DataSource#getOutputStream()
*/
public OutputStream getOutputStream() throws IOException {
return null;
}
}
}