001/*
002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 */
019
020package org.nuxeo.ecm.platform.mail.utils;
021
022import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.CORE_SESSION_KEY;
023import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.EMAILS_LIMIT_PROPERTY_NAME;
024import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.EMAIL_PROPERTY_NAME;
025import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.HOST_PROPERTY_NAME;
026import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.IMAP;
027import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.IMAPS;
028import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.LEAVE_ON_SERVER_KEY;
029import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.MIMETYPE_SERVICE_KEY;
030import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PARENT_PATH_KEY;
031import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PASSWORD_PROPERTY_NAME;
032import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.POP3S;
033import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PORT_PROPERTY_NAME;
034import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PROTOCOL_TYPE_KEY;
035import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PROTOCOL_TYPE_PROPERTY_NAME;
036import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SOCKET_FACTORY_FALLBACK_PROPERTY_NAME;
037import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SOCKET_FACTORY_PORT_PROPERTY_NAME;
038import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SSL_PROTOCOLS_PROPERTY_NAME;
039import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.STARTTLS_ENABLE_PROPERTY_NAME;
040
041import java.util.ArrayList;
042import java.util.List;
043import java.util.Properties;
044import java.util.concurrent.CopyOnWriteArrayList;
045
046import javax.mail.FetchProfile;
047import javax.mail.Flags;
048import javax.mail.Flags.Flag;
049import javax.mail.Folder;
050import javax.mail.Message;
051import javax.mail.MessagingException;
052import javax.mail.Session;
053import javax.mail.Store;
054
055import org.apache.commons.lang3.StringUtils;
056import org.apache.commons.logging.Log;
057import org.apache.commons.logging.LogFactory;
058import org.nuxeo.ecm.core.api.CoreSession;
059import org.nuxeo.ecm.core.api.DocumentModel;
060import org.nuxeo.ecm.core.api.trash.TrashService;
061import org.nuxeo.ecm.platform.mail.action.ExecutionContext;
062import org.nuxeo.ecm.platform.mail.action.MessageActionPipe;
063import org.nuxeo.ecm.platform.mail.action.Visitor;
064import org.nuxeo.ecm.platform.mail.listener.MailEventListener;
065import org.nuxeo.ecm.platform.mail.service.MailService;
066import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
067import org.nuxeo.runtime.api.Framework;
068
069import com.sun.mail.imap.IMAPFolder;
070
071/**
072 * Helper for Mail Core.
073 *
074 * @author Catalin Baican
075 */
076public final class MailCoreHelper {
077
078    private static final Log log = LogFactory.getLog(MailEventListener.class);
079
080    public static final String PIPE_NAME = "nxmail";
081
082    public static final String INBOX = "INBOX";
083
084    /**
085     * @deprecated since 10.3, use {@link TrashService} instead
086     */
087    @Deprecated
088    public static final String DELETED_LIFECYCLE_STATE = "deleted";
089
090    public static final long EMAILS_LIMIT_DEFAULT = 100;
091
092    private static MailService mailService;
093
094    private static MimetypeRegistry mimeService;
095
096    public static final String IMAP_DEBUG = "org.nuxeo.mail.imap.debug";
097
098    protected static final CopyOnWriteArrayList<String> processingMailBoxes = new CopyOnWriteArrayList<>();
099
100    private MailCoreHelper() {
101    }
102
103    private static MailService getMailService() {
104        if (mailService == null) {
105            mailService = Framework.getService(MailService.class);
106        }
107        return mailService;
108    }
109
110    private static MimetypeRegistry getMimeService() {
111        if (mimeService == null) {
112            mimeService = Framework.getService(MimetypeRegistry.class);
113        }
114        return mimeService;
115    }
116
117    /**
118     * Creates MailMessage documents for every unread mail found in the INBOX. The parameters needed to connect to the
119     * email INBOX are retrieved from the MailFolder document passed as a parameter.
120     */
121    public static void checkMail(DocumentModel currentMailFolder, CoreSession coreSession) throws MessagingException {
122
123        if (processingMailBoxes.addIfAbsent(currentMailFolder.getId())) {
124            try {
125                doCheckMail(currentMailFolder, coreSession);
126            } finally {
127                processingMailBoxes.remove(currentMailFolder.getId());
128            }
129        } else {
130            log.info("Mailbox " + currentMailFolder.getPathAsString() + " is already being processed");
131        }
132    }
133
134    protected static void doCheckMail(DocumentModel currentMailFolder, CoreSession coreSession)
135            throws MessagingException {
136        String email = (String) currentMailFolder.getPropertyValue(EMAIL_PROPERTY_NAME);
137        String password = (String) currentMailFolder.getPropertyValue(PASSWORD_PROPERTY_NAME);
138        if (!StringUtils.isEmpty(email) && !StringUtils.isEmpty(password)) {
139            mailService = getMailService();
140
141            MessageActionPipe pipe = mailService.getPipe(PIPE_NAME);
142
143            Visitor visitor = new Visitor(pipe);
144            Thread.currentThread().setContextClassLoader(Framework.class.getClassLoader());
145
146            // initialize context
147            ExecutionContext initialExecutionContext = new ExecutionContext();
148
149            initialExecutionContext.put(MIMETYPE_SERVICE_KEY, getMimeService());
150
151            initialExecutionContext.put(PARENT_PATH_KEY, currentMailFolder.getPathAsString());
152
153            initialExecutionContext.put(CORE_SESSION_KEY, coreSession);
154
155            initialExecutionContext.put(LEAVE_ON_SERVER_KEY, Boolean.TRUE); // TODO should be an attribute in 'protocol'
156                                                                            // schema
157
158            Folder rootFolder = null;
159            Store store = null;
160            try {
161                String protocolType = (String) currentMailFolder.getPropertyValue(PROTOCOL_TYPE_PROPERTY_NAME);
162                initialExecutionContext.put(PROTOCOL_TYPE_KEY, protocolType);
163                // log.debug(PROTOCOL_TYPE_KEY + ": " + (String) initialExecutionContext.get(PROTOCOL_TYPE_KEY));
164
165                String host = (String) currentMailFolder.getPropertyValue(HOST_PROPERTY_NAME);
166                String port = (String) currentMailFolder.getPropertyValue(PORT_PROPERTY_NAME);
167                Boolean socketFactoryFallback = (Boolean) currentMailFolder.getPropertyValue(SOCKET_FACTORY_FALLBACK_PROPERTY_NAME);
168                String socketFactoryPort = (String) currentMailFolder.getPropertyValue(SOCKET_FACTORY_PORT_PROPERTY_NAME);
169                Boolean starttlsEnable = (Boolean) currentMailFolder.getPropertyValue(STARTTLS_ENABLE_PROPERTY_NAME);
170                String sslProtocols = (String) currentMailFolder.getPropertyValue(SSL_PROTOCOLS_PROPERTY_NAME);
171                Long emailsLimit = (Long) currentMailFolder.getPropertyValue(EMAILS_LIMIT_PROPERTY_NAME);
172                long emailsLimitLongValue = emailsLimit == null ? EMAILS_LIMIT_DEFAULT : emailsLimit.longValue();
173
174                String imapDebug = Framework.getProperty(IMAP_DEBUG, "false");
175
176                Properties properties = new Properties();
177                properties.put("mail.store.protocol", protocolType);
178                // properties.put("mail.host", host);
179                // Is IMAP connection
180                if (IMAP.equals(protocolType)) {
181                    properties.put("mail.imap.host", host);
182                    properties.put("mail.imap.port", port);
183                    properties.put("mail.imap.starttls.enable", starttlsEnable.toString());
184                    properties.put("mail.imap.debug", imapDebug);
185                    properties.put("mail.imap.partialfetch", "false");
186                } else if (IMAPS.equals(protocolType)) {
187                    properties.put("mail.imaps.host", host);
188                    properties.put("mail.imaps.port", port);
189                    properties.put("mail.imaps.starttls.enable", starttlsEnable.toString());
190                    properties.put("mail.imaps.ssl.protocols", sslProtocols);
191                    properties.put("mail.imaps.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
192                    properties.put("mail.imaps.socketFactory.fallback", socketFactoryFallback.toString());
193                    properties.put("mail.imaps.socketFactory.port", socketFactoryPort);
194                    properties.put("mail.imap.partialfetch", "false");
195                    properties.put("mail.imaps.partialfetch", "false");
196                } else if (POP3S.equals(protocolType)) {
197                    properties.put("mail.pop3s.host", host);
198                    properties.put("mail.pop3s.port", port);
199                    properties.put("mail.pop3s.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
200                    properties.put("mail.pop3s.socketFactory.fallback", socketFactoryFallback.toString());
201                    properties.put("mail.pop3s.socketFactory.port", socketFactoryPort);
202                    properties.put("mail.pop3s.ssl.protocols", sslProtocols);
203                } else {
204                    // Is POP3 connection
205                    properties.put("mail.pop3.host", host);
206                    properties.put("mail.pop3.port", port);
207                }
208
209                properties.put("user", email);
210                properties.put("password", password);
211
212                Session session = Session.getInstance(properties);
213
214                store = session.getStore();
215                store.connect(email, password);
216
217                String folderName = INBOX; // TODO should be an attribute in 'protocol' schema
218                rootFolder = store.getFolder(folderName);
219
220                // need RW access to update message flags
221                rootFolder.open(Folder.READ_WRITE);
222
223                Message[] allMessages = rootFolder.getMessages();
224                // VDU
225                log.debug("nbr of messages in folder:" + allMessages.length);
226
227                FetchProfile fetchProfile = new FetchProfile();
228                fetchProfile.add(FetchProfile.Item.FLAGS);
229                fetchProfile.add(FetchProfile.Item.ENVELOPE);
230                fetchProfile.add(FetchProfile.Item.CONTENT_INFO);
231                fetchProfile.add("Message-ID");
232                fetchProfile.add("Content-Transfer-Encoding");
233
234                rootFolder.fetch(allMessages, fetchProfile);
235
236                if (rootFolder instanceof IMAPFolder) {
237                    // ((IMAPFolder)rootFolder).doCommand(FetchProfile)
238                }
239
240                List<Message> unreadMessagesList = new ArrayList<>();
241                for (Message message : allMessages) {
242                    Flags flags = message.getFlags();
243                    int unreadMessagesListSize = unreadMessagesList.size();
244                    if (flags != null && !flags.contains(Flag.SEEN) && unreadMessagesListSize < emailsLimitLongValue) {
245                        unreadMessagesList.add(message);
246                        if (unreadMessagesListSize == emailsLimitLongValue - 1) {
247                            break;
248                        }
249                    }
250                }
251
252                Message[] unreadMessagesArray = unreadMessagesList.toArray(new Message[unreadMessagesList.size()]);
253
254                // perform email import
255                visitor.visit(unreadMessagesArray, initialExecutionContext);
256
257                // perform flag update globally
258                Flags flags = new Flags();
259                flags.add(Flag.SEEN);
260
261                boolean leaveOnServer = (Boolean) initialExecutionContext.get(LEAVE_ON_SERVER_KEY);
262                if ((IMAP.equals(protocolType) || IMAPS.equals(protocolType)) && leaveOnServer) {
263                    flags.add(Flag.SEEN);
264                } else {
265                    flags.add(Flag.DELETED);
266                }
267                rootFolder.setFlags(unreadMessagesArray, flags, true);
268
269            } finally {
270                if (rootFolder != null && rootFolder.isOpen()) {
271                    rootFolder.close(true);
272                }
273                if (store != null) {
274                    store.close();
275                }
276            }
277        }
278    }
279
280}