org.gcaldaemon.core.GmailEntry.java Source code

Java tutorial

Introduction

Here is the source code for org.gcaldaemon.core.GmailEntry.java

Source

//
// GCALDaemon is an OS-independent Java program that offers two-way
// synchronization between Google Calendar and various iCalalendar (RFC 2445)
// compatible calendar applications (Sunbird, Rainlendar, iCal, Lightning, etc).
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
// 
// Project home:
// http://gcaldaemon.sourceforge.net
//
package org.gcaldaemon.core;

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.Flags.Flag;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Pooled Gmail connection.
 * 
 * Created: Jan 03, 2007 12:50:56 PM
 * 
 * @author Andras Berkes
 */
public final class GmailEntry {

    // --- LDAP CONSTANTS ---

    private static final int HTTP_CONNECTION_TIMEOUT = 10000;
    private static final int HTTP_WAIT_TIMEOUT = 60000;
    private static final String USER_AGENT = "Mozilla/5.0 (Windows; U;"
            + " Windows NT 5.1; hu; rv:1.8.0.8) Gecko/20061025 Thunderbird/1.5.0.8";

    // --- LDAP VARIABLES ---

    private String oldContactURL = "http://mail.google.com/mail/?ui=1&view=fec";
    private String newContactURL = "http://mail.google.com/mail/contacts/data/export?"
            + "exportType=ALL&out=GMAIL_CSV";
    private String gmailURL = "https://mail.google.com/mail";
    private String logoutURL = "https://mail.google.com/mail?logout";
    private String refererURL = "https://www.google.com/accounts/ServiceLogin?"
            + "service=mail&passive=true&rm=false&"
            + "continue=https%3A%2F%2Fmail.google.com%2Fmail%3Fui%3Dhtml%26zy%3Dl";

    private String gmailAt = null;

    // --- PACKAGE-PRIVATE VARIABLES ---

    String username;
    long lastUsage;

    // --- LOGGER ---

    private static final Log log = LogFactory.getLog(GmailEntry.class);

    // --- HTTP CONNECTION HANDLER OF THE LDAP SERVICE ---

    private static final MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
    private static final HttpClient httpClient = new HttpClient(connectionManager);

    static final void globalInit() {
        try {

            // Set proxy
            HttpConnectionManagerParams params = connectionManager.getParams();
            params.setConnectionTimeout(HTTP_CONNECTION_TIMEOUT);
            params.setSoTimeout(HTTP_WAIT_TIMEOUT);
            String proxyHost = System.getProperty("http.proxyHost");
            String proxyPort = System.getProperty("http.proxyPort");
            if (proxyHost != null && proxyPort != null) {
                httpClient.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort));
                String username = System.getProperty("http.proxyUserName");
                String password = System.getProperty("http.proxyPassword");
                if (username != null && password != null) {
                    Credentials credentials = new UsernamePasswordCredentials(username, password);
                    httpClient.getState().setProxyCredentials(AuthScope.ANY, credentials);
                }
            }
        } catch (Exception setupError) {
            log.warn("Unable to init proxy!", setupError);
        }
    }

    // --- SMTP CONNECTION VARIABLES ---

    private Session smtpSession;

    // --- IMAP CONNECTION VARIABLES ---

    private Store mailbox;

    // --- CONSTRUCTOR ---

    private final boolean ldap;
    private final boolean smtp;
    private final boolean imap;

    GmailEntry(boolean ldap, boolean smtp, boolean imap) {
        this.ldap = ldap;
        this.smtp = smtp;
        this.imap = imap;
    }

    // --- CONNECT ALL SERVICES ---

    private boolean connected;

    final void connect(String username, String password) throws Exception {

        // Login to LDAP service
        if (ldap) {
            connectLDAP(username, password);
        }

        // Login to SMTP service
        if (smtp) {
            connectSMTP(username, password);
        }

        // Login to IMAP service
        if (imap) {
            connectIMAP(username, password);
        }
        connected = true;
    }

    public final boolean isConnected() {
        return connected;
    }

    // --- LDAP CONNECT ---

    private final void connectLDAP(String username, String password) throws Exception {

        // Google Apps For Your Domain support
        String domain = null;
        int i = username.indexOf('@');
        String loginUsername = username;
        String loginURL = "https://www.google.com/accounts/ServiceLoginAuth";
        if (i != -1) {
            if (username.indexOf("gmail.com") == -1) {
                domain = username.substring(i + 1);
                loginUsername = username.substring(0, i);

                // Use Google Apps URLs instead of 'gmail.com' URLs
                gmailURL = "http://mail.google.com/a/" + domain + '/';
                loginURL = "https://www.google.com/a/" + domain + "/ServiceLogin";
                logoutURL = "http://mail.google.com/a/" + domain + "/?logout";
                refererURL = "https://www.google.com/a/" + domain
                        + "/ServiceLogin?service=mail&continue=https%3A%2F%2Fwww.google.com%3A443%2Fa%2F" + domain
                        + "%2FDashboard&passive=true";
                oldContactURL = "http://mail.google.com/a/" + domain + "/?ui=1&view=fec";
                newContactURL = "http://mail.google.com/a/" + domain + "/mail/contacts/data/export?"
                        + "exportType=ALL&out=GMAIL_CSV";
            }
        }

        // Default 'gmail.com' login fields
        PostMethod post = new PostMethod(loginURL);
        String usernameField = "Email";
        String passwordField = "Passwd";
        if (domain != null) {

            // Google Apps For Your Domain login fields
            usernameField = "userName";
            passwordField = "password";
        }

        // Create login request
        NameValuePair[] data = { new NameValuePair("service", "mail"),
                new NameValuePair(usernameField, loginUsername), new NameValuePair(passwordField, password),
                new NameValuePair("null", "Sign in"), new NameValuePair("continue", gmailURL) };
        post.addRequestHeader("User-Agent", USER_AGENT);
        post.addRequestHeader("referer", refererURL);
        post.addRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        post.setRequestBody(data);

        // Send login form to Gmail
        int httpStatus = httpClient.executeMethod(post);
        if (httpStatus == -1) {
            throw new Exception("Login failed!\r\n" + "Invalid HTTP status code (check username/password)!");
        }
        String response = post.getResponseBodyAsString();
        post.releaseConnection();
        if (response.toLowerCase().indexOf("errormsg") != -1) {
            log.warn("Response from Google:\r\n" + response);
            throw new Exception("Login failed (check username/password)!");
        }

        // Check redirection #1
        GetMethod get = null;
        String newLocationURL;
        i = response.toLowerCase().indexOf("moved temporarily");
        if (i != -1) {
            i = response.toLowerCase().indexOf("href");
            if (i != -1) {
                int n = response.indexOf('"', i + 7);

                // Get new URL #1
                newLocationURL = response.substring(i + 6, n);
                get = new GetMethod(newLocationURL);
                get.addRequestHeader("User-Agent", USER_AGENT);
                try {
                    httpStatus = httpClient.executeMethod(get);

                    // Get 'GMAIL_AT' cookie's value
                    gmailAt = getGmailAt(get.getRequestHeaders());
                    response = get.getResponseBodyAsString();
                    get.releaseConnection();
                } catch (IOException e) {
                    log.error("Failed to redirect URL!", e);
                }
            }
        }

        // Finish login (cookie handshake)
        get = new GetMethod(gmailURL);
        get.addRequestHeader("User-Agent", USER_AGENT);
        get.addRequestHeader("referer", refererURL);
        get.addRequestHeader("Content-Type", "text/html");
        try {
            httpStatus = httpClient.executeMethod(get);

            // Get 'GMAIL_AT' cookie's value
            gmailAt = getGmailAt(get.getRequestHeaders());

            // Check redirection #2
            if (httpStatus == HttpStatus.SC_MOVED_PERMANENTLY || httpStatus == HttpStatus.SC_MOVED_TEMPORARILY
                    || httpStatus == HttpStatus.SC_SEE_OTHER || httpStatus == HttpStatus.SC_TEMPORARY_REDIRECT) {

                // Get new URL #2
                Header newLocationHeader = get.getResponseHeader("location");
                if (newLocationHeader != null) {
                    newLocationURL = newLocationHeader.getValue();
                    get.releaseConnection();
                    get = new GetMethod(newLocationURL);
                    try {
                        httpStatus = httpClient.executeMethod(get);

                        // Check redirection #3
                        if (httpStatus == HttpStatus.SC_MOVED_PERMANENTLY
                                || httpStatus == HttpStatus.SC_MOVED_TEMPORARILY
                                || httpStatus == HttpStatus.SC_SEE_OTHER
                                || httpStatus == HttpStatus.SC_TEMPORARY_REDIRECT) {

                            // Get new URL #3
                            newLocationHeader = get.getResponseHeader("location");
                            if (newLocationHeader != null) {
                                newLocationURL = newLocationHeader.getValue();
                                get.releaseConnection();
                                get = new GetMethod(newLocationURL);
                                try {
                                    httpStatus = httpClient.executeMethod(get);

                                    // Get 'GMAIL_AT' cookie's value
                                    gmailAt = getGmailAt(get.getRequestHeaders());
                                    get.releaseConnection();
                                } catch (IOException e) {
                                    log.warn("Failed to redirect URL!", e);
                                }
                            } else {
                                log.warn("Missing location URL!");
                            }
                        }
                    } catch (IOException e) {
                        log.warn("Failed to redirect URL!", e);
                    }
                } else {
                    log.warn("Missing location header!");
                }
            }
        } catch (IOException e) {
            log.error("Failed to open URL", e);
        }
    }

    private static final String getGmailAt(Header[] headers) {
        if (headers != null) {
            String value;
            int i, n, k;

            // Loop on headers
            for (i = 0; i < headers.length; i++) {
                value = headers[i].getValue();
                n = value.indexOf("GMAIL_AT");
                if (n != -1) {

                    // Found 'GMAIL_AT' cookie
                    k = value.indexOf(';', n);
                    if (k == -1) {
                        return value.substring(n + 9);
                    } else {
                        return value.substring(n + 9, k);
                    }
                }
            }
        }
        return null;
    }

    // --- SMTP CONNECT ---

    private final void connectSMTP(String username, String password) throws Exception {

        // Create SMTP session
        Properties props = (Properties) System.getProperties().clone();
        props.setProperty("mail.transport.protocol", "smtp");
        props.put("mail.smtp.host", "smtp.gmail.com");
        props.put("mail.smtp.port", "465");
        props.put("mail.smtp.user", username);
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.socketFactory.port", "465");
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.put("mail.smtp.socketFactory.fallback", "false");
        props.setProperty("mail.smtp.quitwait", "false");

        // Connect to Gmail via SMTP+TLS
        GmailAuthenticator login = new GmailAuthenticator(username, password);
        smtpSession = Session.getDefaultInstance(props, login);
        Transport smtpTransport = smtpSession.getTransport("smtp");
        smtpTransport.connect();

        log.debug("Gmail's SMTP service has been connected successfully.");
    }

    private final class GmailAuthenticator extends Authenticator {

        private final String username;
        private final String password;

        private GmailAuthenticator(String username, String password) {
            this.username = username;
            this.password = password;
        }

        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(username, password);
        }

    }

    // --- IMAP CONNECT ---

    private final void connectIMAP(String username, String password) throws Exception {

        // Create IMAP session
        Properties props = (Properties) System.getProperties().clone();
        props.put("mail.imap.host", "imap.gmail.com");
        props.put("mail.imap.port", "993");
        props.put("mail.imap.user", username);
        props.put("mail.imap.auth", "true");
        props.put("mail.imap.socketFactory.port", "993");
        props.put("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.put("mail.imap.socketFactory.fallback", "false");

        // Connect to Gmail via IMAP+SSL
        GmailAuthenticator login = new GmailAuthenticator(username, password);
        Session session = Session.getDefaultInstance(props, login);
        mailbox = session.getStore("imaps");
        mailbox.connect("imap.gmail.com", 993, username, password);

        log.debug("Gmail's IMAP service has been connected successfully.");
    }

    // --- DISCONNECT ALL SERVICES ---

    final void disconnect() {

        // Disconnect LDAP service
        if (ldap) {
            try {
                GetMethod get = new GetMethod(logoutURL);
                get.addRequestHeader("referer", refererURL);
                get.addRequestHeader("Content-Type", "text/html");
                httpClient.getState()
                        .addCookie(new Cookie("gmail.google.com", "GMAIL_LOGIN",
                                "T" + Calendar.getInstance().getTimeInMillis() + "/"
                                        + Calendar.getInstance().getTimeInMillis() + "/"
                                        + Calendar.getInstance().getTimeInMillis(),
                                "/", 999, true));
                httpClient.executeMethod(get);
                get.releaseConnection();
            } catch (Exception ioException) {
                log.debug("Unable to disconnect LDAP service!", ioException);
            }
        }

        // Disconnect SMTP service
        if (smtp) {
            try {
                smtpSession.getTransport("smtp").close();
            } catch (Exception ioException) {
                log.debug("Unable to disconnect SMTP service!", ioException);
            }
        }

        // Disconnect IMAP service
        if (imap) {
            try {
                mailbox.close();
            } catch (Exception ioException) {
                log.debug("Unable to disconnect IMAP service!", ioException);
            }
        }
    }

    // --- CONTACT LOADER [LDAP SERVICE] ---

    public final String downloadCSV() throws Exception {

        // Use the new download URL
        GetMethod get = null;
        try {
            get = new GetMethod(newContactURL);
            get.addRequestHeader("User-Agent", USER_AGENT);
            get.addRequestHeader("referer", gmailURL);
            get.addRequestHeader("Content-Type", "text/html");
            int status = httpClient.executeMethod(get);
            String csv = get.getResponseBodyAsString();
            if (status == 200 && csv.toLowerCase().indexOf("<html") == -1) {
                return csv;
            }
        } finally {
            if (get != null) {
                try {
                    get.releaseConnection();
                } catch (Exception ignored) {
                }
            }
        }

        // Use the deprecated download URL
        PostMethod post = null;
        try {
            post = new PostMethod(oldContactURL);
            post.addRequestHeader("User-Agent", USER_AGENT);
            post.addRequestHeader("referer", gmailURL);
            post.addRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            NameValuePair[] data2 = { new NameValuePair("at", gmailAt), new NameValuePair("ecf", "g"),
                    new NameValuePair("ac", "Export Contacts") };
            post.setRequestBody(data2);
            int status = httpClient.executeMethod(post);
            String csv = post.getResponseBodyAsString();
            if (status == 200 && csv.toLowerCase().indexOf("<html") == -1) {
                return csv;
            }
        } finally {
            if (post != null) {
                try {
                    post.releaseConnection();
                } catch (Exception ignored) {
                }
            }
        }

        log.warn("Incompatible Gmail interface -" + " unable to download contacts!");
        return null;
    }

    // --- SEND MAIL [SMTP] ---

    public final void send(String to, String cc, String bcc, String subject, String memo, boolean html)
            throws Exception {
        MimeMessage mimeMessage = new MimeMessage(smtpSession);

        // Add 'to' fields
        StringTokenizer st = new StringTokenizer(to, ", ");
        while (st.hasMoreTokens()) {
            mimeMessage.addRecipients(Message.RecipientType.TO, st.nextToken());
        }

        // Add 'cc' fields
        if (cc != null && cc.length() != 0) {
            st = new StringTokenizer(cc, ", ");
            while (st.hasMoreTokens()) {
                mimeMessage.addRecipients(Message.RecipientType.CC, st.nextToken());
            }
        }

        // Add 'bcc' fields
        if (bcc != null && bcc.length() != 0) {
            st = new StringTokenizer(bcc, ", ");
            while (st.hasMoreTokens()) {
                mimeMessage.addRecipients(Message.RecipientType.BCC, st.nextToken());
            }
        }

        // Set message
        if (html) {
            mimeMessage.setHeader("Content-Type", "text/html");
            mimeMessage.setContent(memo.trim(), "text/html; charset=UTF-8");
        } else {
            mimeMessage.setHeader("Content-Type", "text/plain");
            mimeMessage.setContent(memo.trim(), "text/plain; charset=UTF-8");
        }

        // Set 'from', 'subject' and date fields
        mimeMessage.setFrom(new InternetAddress(username));
        mimeMessage.setSubject(subject.trim(), "UTF-8");
        mimeMessage.setSentDate(new Date());
        Transport.send(mimeMessage);
    }

    // --- RECEIVE UNREAD MAILS [IMAP] ---

    public final GmailMessage[] receive(String title) throws Exception {

        // Open 'INBOX' folder
        Folder inbox = mailbox.getFolder("INBOX");
        inbox.open(Folder.READ_WRITE);
        Message[] messages = inbox.getMessages();
        if (messages == null || messages.length == 0) {
            return new GmailMessage[0];
        }

        // Loop on messages
        LinkedList list = new LinkedList();
        for (int i = 0; i < messages.length; i++) {
            Message msg = messages[i];
            if (!msg.isSet(Flag.SEEN)) {
                String subject = msg.getSubject();
                if (title == null || title.length() == 0 || title.equals(subject)) {
                    GmailMessage gm = new GmailMessage();
                    Address[] from = msg.getFrom();
                    msg.setFlag(Flag.SEEN, true);
                    if (from == null || from.length == 0) {
                        continue;
                    }
                    gm.subject = subject;
                    gm.from = from[0].toString();
                    gm.memo = String.valueOf(msg.getContent());
                    list.addLast(gm);
                }
            }
        }
        inbox.close(true);

        // Return the array of the messages
        GmailMessage[] array = new GmailMessage[list.size()];
        list.toArray(array);
        return array;
    }

}