edu.stanford.muse.email.ImapPopEmailStore.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.muse.email.ImapPopEmailStore.java

Source

/*
 Copyright (C) 2012 The Stanford MobiSocial Laboratory
    
   Licensed 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 edu.stanford.muse.email;

import com.google.code.samples.oauth2.OAuth2SaslClientFactory;
import com.sun.mail.util.MailSSLSocketFactory;
import edu.stanford.muse.util.Pair;
import edu.stanford.muse.util.Util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import java.security.GeneralSecurityException;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class ImapPopEmailStore extends EmailStore {
    private final static long serialVersionUID = 1L;
    private static Log log = LogFactory.getLog(ImapPopEmailStore.class);

    private ImapPopConnectionOptions connectOptions;
    private static final Properties mstoreProps;
    private transient Store store = null;
    transient public Session session = null;

    static {
        // see http://www.javaworld.com/javatips/jw-javatip115.html
        //    Security.addProvider( new com.sun.net.ssl.internal.ssl.Provider());

        mstoreProps = System.getProperties();

        // see RFC 3501 http://www.faqs.org/rfcs/rfc3501.html

        // IMAP supports 2 kinds of login, plain and authenticated.
        // protocol=imap leaks protocol traffic
        // protocol=imap with plain mode disabled at least protects password by using SASL for password only.
        // protocol=imaps has no issues, so plain mode can be left enabled.
        // some buggy servers have trouble with SASL plain auth, disable it
        mstoreProps.put("mail.imap.auth.plain.disable", "true");

        // see http://java.sun.com/products/javamail/javadocs/com/sun/mail/imap/package-summary.html for imap properties

        mstoreProps.put("mail.imaps.partialfetch", "false"); // sometimes imap servers have a bug: see http://java.sun.com/products/javamail/FAQ.html#imapserverbug
        mstoreProps.put("mail.imap.partialfetch", "false"); // sometimes imap servers have a bug: see http://java.sun.com/products/javamail/FAQ.html#imapserverbug

        mstoreProps.put("mail.imaps.fetchsize", Integer.toString(1024 * 1024 * 8)); // sometimes imap servers have a bug: see http://java.sun.com/products/javamail/FAQ.html#imapserverbug
        mstoreProps.put("mail.imap.fetchsize", Integer.toString(1024 * 1024 * 8)); // sometimes imap servers have a bug: see http://java.sun.com/products/javamail/FAQ.html#imapserverbug

        MailSSLSocketFactory socketFactory = null;
        try {
            // security: try and accept bad certs!
            // csl-mail works fine with imap, but not with imaps
            // mstoreProps.put("mail.imaps.socketFactory.class", "edu.stanford.muse.email.DummySSLSocketFactory");
            mstoreProps.put("mail.imaps.ssl.trust", "*");

            socketFactory = new MailSSLSocketFactory();
            socketFactory.setTrustAllHosts(true);
            mstoreProps.put("mail.imaps.ssl.socketFactory", socketFactory);
        } catch (GeneralSecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //   mstoreProps.put("mail.debug", "true");
    }

    public static final class OAuth2Provider extends Provider {
        private static final long serialVersionUID = 1L;

        public OAuth2Provider() {
            super("Google OAuth2 Provider", 1.0, "Provides the XOAUTH2 SASL Mechanism");
            put("SaslClientFactory.XOAUTH2", "com.google.code.samples.oauth2.OAuth2SaslClientFactory");
        }
    }

    public ImapPopEmailStore(ImapPopConnectionOptions options, String emailAddress) {
        super(options.userName + "@" + options.server, emailAddress);
        this.connectOptions = options;
        this.displayName = computeSimpleDisplayName();
        Security.addProvider(new OAuth2Provider());
    }

    public String getServerHostname() {
        return connectOptions.server;
    }

    private String computeSimpleDisplayName() {
        // first check if we already have an @. If we do, just use it directly. no point trying user@gapps-hosted-domain@gmail.com
        if (this.connectOptions.userName.indexOf("@") >= 0)
            return this.connectOptions.userName;

        if (connectOptions.server.endsWith("gmail.com") || connectOptions.server.endsWith("google.com")
                || connectOptions.server.endsWith("googlemail.com"))
            return this.connectOptions.userName + "@gmail.com";
        if (connectOptions.server.endsWith("yahoo.com"))
            return this.connectOptions.userName + "@yahoo.com";
        if (connectOptions.server.endsWith("ymail.com"))
            return this.connectOptions.userName + "@ymail.com";
        if (connectOptions.server.endsWith("live.com"))
            return this.connectOptions.userName + "@live.com";
        if (connectOptions.server.endsWith("hotmail.com"))
            return this.connectOptions.userName + "@hotmail.com";
        else
            return connectOptions.server;
    }

    /** obliterate any passwords that may be stored */
    public void wipePasswords() {
        connectOptions.wipePasswords();
    }

    /** returns # of messages in folder. -1 if folder cannot be opened.
     * connect should already have been called */
    public Folder openFolderWithoutCount(Store store, String fname) throws MessagingException {
        Folder folder = null;

        if (fname == null)
            fname = "INBOX";

        folder = store.getDefaultFolder();
        folder = folder.getFolder(fname);

        if (folder == null)
            throw new RuntimeException("Invalid folder: " + fname);

        log.info("Opening folder " + Util.blurKeepingExtension(fname) + " in r/o mode...");
        try {
            folder.open(Folder.READ_ONLY);
        } catch (MessagingException me) {
            return null;
        }
        return folder;
    }

    public Pair<Folder, Integer> openFolder(Store store, String fname) throws MessagingException {
        Folder folder = openFolderWithoutCount(store, fname);
        int count = -1; // -1 signals invalid folder
        if (folder != null)
            count = folder.getMessageCount();
        log.info("Opened folder " + Util.blurKeepingExtension(fname) + " message count " + count);
        return new Pair<Folder, Integer>(folder, count);
    }

    public void computeFoldersAndCounts(String cacheDir /*unused */) throws MessagingException {
        if (store == null)
            connect();
        if (!store.isConnected())
            connect();

        doneReadingFolderCounts = false;
        this.folderInfos = new ArrayList<FolderInfo>();
        if ("pop3".equals(connectOptions.protocol) || "pop3s".equals(connectOptions.protocol)) {
            Folder f = store.getDefaultFolder();
            f = f.getFolder("INBOX");
            f.open(Folder.READ_ONLY);
            int count = f.getMessageCount();
            f.close(false);
            this.folderInfos.add(new FolderInfo(getAccountID(), "INBOX", "INBOX", count));
        } else
            collect_folder_names(store, this.folderInfos, store.getDefaultFolder());

        folderBeingScanned = "";

        if (connectOptions.server.endsWith(".pobox.stanford.edu"))
            this.folderInfos.add(new FolderInfo(getAccountID(), "INBOX", "INBOX", 0)); // hack for stanford imap, it lists INBOX as a dir folder! TOFIX

        doneReadingFolderCounts = true;
    }

    /** recursively collect all folder names under f into list */
    private void collect_folder_names(Store store, List<FolderInfo> list, Folder f) throws MessagingException {
        // ignore hidden files
        if (f.getFullName().startsWith("."))
            return;
        if (f.getFullName().indexOf("/.") >= 0 || f.getFullName().indexOf("\\.") >= 0)
            return;

        // hack for csl-mail which takes too long to return all the folders
        if (connectOptions.server.startsWith("csl-mail") && f.getFullName().indexOf("/") >= 0)
            return;

        // TOFIX: apparently imap folders can have both messages and children
        Folder f_children[] = null;
        boolean hasMessages = true, hasChildren = false;
        boolean isPop = "pop3".equals(connectOptions.protocol) || "pop3s".equals(connectOptions.protocol);

        if (!isPop) {
            // if its imap, check for children
            hasChildren = (f.getType() & Folder.HOLDS_FOLDERS) != 0;
            hasMessages = (f.getType() & Folder.HOLDS_MESSAGES) != 0;
        }

        if (hasMessages) {
            folderBeingScanned = f.getFullName();
            Pair<Folder, Integer> pair = openFolder(store, f.getFullName());
            int count = pair.getSecond();
            if (count != -1)
                pair.getFirst().close(false);

            //         System.out.println ("full name = " + Util.blur(f.getFullName()) + " count = " + count);

            list.add(new FolderInfo(getAccountID(), f.getFullName(), f.getFullName(), count));
            folderBeingScanned = null;
        }

        if (hasChildren) {
            f_children = f.list();
            for (Folder child : f_children)
                collect_folder_names(store, list, child);
        }
    }

    //   connects to the store, returns it as well as stores it in this.store
    public Store connect() throws MessagingException {
        if (Util.nullOrEmpty(connectOptions.protocol)) // should be at least imap or pop
            return null;

        // Get a Session object
        // can customize javamail properties here, see e.g. http://java.sun.com/products/javamail/javadocs/com/sun/mail/imap/package-summary.html
        // login form will prepend the magic string xoauth, if oauth is being used
        String oauthToken = "";
        String OAUTH_MAGIC_STRING = "xoauth"; // defined in loginform
        if (connectOptions.password.startsWith(OAUTH_MAGIC_STRING)) {
            if ("xoauth".length() < connectOptions.password.length()) {
                oauthToken = connectOptions.password.substring(OAUTH_MAGIC_STRING.length());
                mstoreProps.put("mail.imaps.sasl.enable", "true");
                mstoreProps.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
                mstoreProps.put(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, oauthToken);
                log.info("Using oauth login for store " + this);
            }
        }

        session = Session.getInstance(mstoreProps, null);
        //   session.setDebug(DEBUG);
        Store st = session.getStore(connectOptions.protocol);
        st.connect(connectOptions.server, connectOptions.port, connectOptions.userName,
                Util.nullOrEmpty(oauthToken) ? connectOptions.password : ""); // no password if oauth
        this.store = st;
        return st;
    }

    @Override
    public String getAccountID() {
        return connectOptions.userName + "." + connectOptions.server;
    }

    public String toString() {
        return "IMAP/POP message store with " + connectOptions;
    }
}