| /****************************************************************
|
| * 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;
|
| }
|
|
|
| }
|
|
|
| }
|