updates to gmail
authoradam <adam@megacz.com>
Sun, 5 Sep 2004 21:34:03 +0000 (21:34 +0000)
committeradam <adam@megacz.com>
Sun, 5 Sep 2004 21:34:03 +0000 (21:34 +0000)
darcs-hash:20040905213403-5007d-47554737c1d85913293c110d85f7fddaae382835.gz

src/org/ibex/mail/protocol/GMail.java

index a476ed6..2753f30 100644 (file)
@@ -1,6 +1,7 @@
 package org.ibex.mail.protocol;
 import org.ibex.io.*;
 import org.ibex.crypto.*;
+import org.ibex.mail.protocol.*;
 import org.ibex.jinetd.Listener;
 import org.ibex.jinetd.Worker;
 import org.ibex.mail.*;
@@ -15,17 +16,27 @@ import java.io.*;
 
 public class GMail extends Account {
 
-    public static final String login = "https://www.google.com/accounts/ServiceLoginBoxAuth";
-    public static final String gmail = "https://gmail.google.com/gmail";
-
+    private      GMailIMAP imap = new GMailIMAP();
     private HTTP.Cookie.Jar jar = new HTTP.Cookie.Jar();
-    private String captchaString = null;
-    private String ctoken = null;
-    private String email = null;
-    private String password = null;
+    private      String captcha = null;
+    private       String ctoken = null;
+    private        String email = null;
+    private     String password = null;
+    private     boolean invalid = false;
+    private     String[] labels = new String[0];
+    private    String[] queries = new String[0];
+    private Summary[] summaries = new Summary[0];
 
+    // Constructor, Pooling  ///////////////////////////////////////////////////////////////////////////
 
-    // Constructor //////////////////////////////////////////////////////////////////////////////
+    public GMail(String email, String pass) throws IOException {
+        super(email.substring(0, email.indexOf('@')), Address.parse(email));
+        this.email = email; this.password = pass;
+        Log.warn(GMail.class, "logging in " + email);
+        getCookies();
+    }
+
+    public void close() { cache.remove(email, password); invalid = true; }
 
     private static Hash cache = new Hash();
     static { HTTP.userAgent = "Mozilla/5.0 (compatible;)"; }
@@ -33,7 +44,6 @@ public class GMail extends Account {
         try {
             GMail g = (GMail)cache.get(email, pass);
             if (g == null) cache.put(email, pass, g = new GMail(email, pass));
-            g.init();
             return g;
         } catch (Exception e) {
             Log.error(GMail.class, e);
@@ -41,96 +51,134 @@ public class GMail extends Account {
         }
     }
 
-    public GMail(String email, String pass) throws IOException {
-        super(email.substring(0, email.indexOf('@')), Address.parse(email));
-        this.email = email; this.password = pass;
-        Log.warn(GMail.class, "logging in " + email);
-    }
+    // IMAP Interface //////////////////////////////////////////////////////////////////////////////
+
+    public IMAP.Server getIMAP() { return imap; }
+    private class GMailIMAP implements IMAP.Server {
 
-    void init() throws IOException {
-        if (success) return;
-        Vector v = makeRequest();
-        Message[] m = new Message[v.size()];
-        v.copyInto(m);
-        final Mailbox inbox = new MessageArrayMailbox(m);
-        this.root =
-            new Mailbox.Default() {
-                public void add(Message m)             { throw new RuntimeException("not supported"); }
-                public void add(Message m, int i)      { throw new RuntimeException("not supported"); }
-                public Mailbox.Iterator iterator()     { return new Mailbox.Iterator.NullIterator(); }
-                public int              uidNext()      { return 500; }
-                public String[] children() {
-                    if (success) return new String[] { "INBOX" };
-                    return new String[] { "Captcha" };
+        private String query = "?search=inbox&start=0&view=tl";
+        private int validity = new Random().nextInt();
+
+        private IMAP.Client client;
+        public void setClient(IMAP.Client client) { this.client = client; }
+
+        public String[]  capability() { return new String[] { }; }
+        public Hashtable id(Hashtable clientId) { return null; }
+        public void      logout() { GMail.this.close(); }
+        public void      subscribe(String mailbox) { }
+        public void      unsubscribe(String mailbox) { }
+
+        public void      unselect() { summaries = new Summary[0]; }
+        public void      close() { summaries = new Summary[0]; }
+
+        public void      noop() { check(); }
+        public void      expunge() { }
+
+        public void      setFlags(Query q, int flags, boolean uid, boolean silent) { }
+        public void      removeFlags(Query q, int flags, boolean uid, boolean silent) { }
+        public void      addFlags(Query q, int flags, boolean uid, boolean silent) { }
+
+        public void      rename(String from, String to) { }
+        public void      delete(String m) { }
+        public void      create(String m) {
+            String[] newqueries = new String[queries.length+1];
+            System.arraycopy(queries, 0, newqueries, 0, queries.length);
+            newqueries[queries.length] = m;
+            queries = newqueries;
+        }
+
+        public void      append(String m, int flags, Date arrival, String body) { }
+        public void      copy(Query q, String to) { }
+
+        public void      check() { query(query); }
+        public void      select(String mailbox, boolean examineOnly) {
+            for(int i=0; i<labels.length; i++) {
+                if (labels[i].equals(mailbox)) {
+                    String oldquery = query;
+                    if (mailbox.equalsIgnoreCase("inbox")) query = "?search=inbox&start=0&view=tl";
+                    else query = "?search=cat&cat="+mailbox+"&inbox&start=0&view=tl";
+                    if (!query.equals(oldquery)) check();
+                    return;
                 }
-                public Mailbox slash(String name, boolean create) { return inbox; }
-            };
-        Log.warn(GMail.class, "    succeeded for " + email);
-    }
+            }
+            String oldquery = query;
+            query = "?search=query&q="+URLEncoder.encode(mailbox)+"&start=0&view=tl";
+            if (!query.equals(oldquery)) check();
+        }
 
+        public void      lsub(String start, String ref) { list(true); }
+        public void      list(String start, String ref) { list(false); }
+        private void list(boolean lsub) {
+            client.list('/', "inbox", lsub, false);
+            for(int i=0; i<labels.length; i++) client.list('/', labels[i], lsub, false);
+            for(int i=0; i<queries.length; i++) client.list('/', queries[i], lsub, false);
+        }
 
-    // Implementation //////////////////////////////////////////////////////////////////////////////
+        public int[]     search(Query q, boolean uid) { return null; }
+        public void      fetch(Query q, int spec, String[] headers, int start, int end, boolean uid) {
+            if (captchaMessage != null) { client.fetch(1, 0, 100, captchaMessage, 100); return; }
+            for(int i=0; i<summaries.length; i++) try {
+                final Message m = summaries[i].getMessage();
+                final int num = i;
+                if (q.match(new Mailbox.Iterator() {
+                        public Message cur() { return m; }
+                        public Message head() { return m; }
+                        public boolean next() { return false; }
+                        public int     uid() { return num; }
+                        public int     num() { return num; }
+                        public void    delete() { }
+                        public void    set(String key, String val) { }
+                        public String  get(String key) { return null; }
+                        public boolean seen() { return false; }
+                        public boolean deleted() { return false; }
+                        public boolean flagged() { return false; }
+                        public boolean draft() { return false;}
+                        public boolean answered() { return false; }
+                        public boolean recent() { return true; }
+                        public void    seen(boolean on) { }
+                        public void    deleted(boolean on) { }
+                        public void    flagged(boolean on) { }
+                        public void    draft(boolean on) { }
+                        public void    answered(boolean on) { }
+                        public void    recent(boolean on) { }
+                        public int     flags() { return 0; }
+                        public void    addFlags(int flags) { }
+                        public void    removeFlags(int flags) { }
+                        public void    setFlags(int flags) { }
+                    })) {
+                    Log.info(GMail.class, "fetch " + summaries[i].subject);
+                    client.fetch(i+1, 0, m.size(), m,/* summaries[i].getIntId()*/ i);
+                }
+            } catch (Exception e) { Log.warn(this, e); }
+        }
 
-    public Vector makeRequest() throws IOException {
-        final Vector messages = new Vector();
-        Log.warn(this, "captchaString = " + captchaString);
-        Log.warn(this, "ctoken = " + ctoken);
-        String crud =
+        public int       unseen(String mailbox)      { return summaries.length; }
+        public int       recent(String mailbox)      { return summaries.length; }
+        public int       count(String mailbox)       { return summaries.length; }
+        public int       uidNext(String mailbox)     { return summaries.length+1; }
+        public int       uidValidity(String mailbox) { return validity; }
+    }
+
+    public void getCookies() throws IOException {
+        jar = new HTTP.Cookie.Jar();
+        String params =
             "continue="     + URLEncoder.encode(gmail) +
             "&service=mail" +
             "&Email="       + URLEncoder.encode(user) +
             "&Passwd="      + password + 
             "&null=Sign+in" +
-            (captchaString != null ? "&captcha="+URLEncoder.encode(captchaString) : "") +
-            (captchaString != null ? "&ctoken="+URLEncoder.encode(ctoken) : "");
-        captchaString = null;
-        Log.warn("[request]", crud);
-
-        String result =
-            captchaString == null ?
-            new String(InputStreamToByteArray.convert(new HTTP(login+"?"+crud).GET(null, jar)))
-            :
-            new String(InputStreamToByteArray.convert(new HTTP(login).POST("application/x-www-form-urlencoded", crud, null, jar)));
+            (captcha != null ? "&captcha="+URLEncoder.encode(captcha) : "") +
+            (captcha != null ? "&ctoken="+URLEncoder.encode(ctoken) : "");
+        Log.info("[request]", params);
+        HTTP http = captcha==null ? new HTTP(login+"?"+params) : new HTTP(login);
+        InputStream reply = captcha==null ?
+            http.GET(null, jar) :
+            http.POST("application/x-www-form-urlencoded", params, null, jar);
+        String result = new String(InputStreamToByteArray.convert(reply));
         System.err.println(result);
+        captcha = null;
         
-        try {
-            if (result.indexOf("top.location") == -1) {
-                Log.warn(GMail.class,"no relocator found; checking for captcha");
-                ctoken = result.substring(result.indexOf("id=\"ctoken\" value=\"") + "id=\"ctoken\" value=\"".length());
-                ctoken = ctoken.substring(0, ctoken.indexOf("\""));
-
-                Log.warn(this, "captchaString = " + captchaString);
-                Log.warn(this, "ctoken = " + ctoken);
-
-                String image = result.substring(result.indexOf("Captcha?"));
-                image = image.substring(0, image.indexOf("\""));
-                Message m = new Message(new Stream(
-                                                   "From: google@google.com\r\n" +
-                                                   "To: you@yourself.com\r\n" +
-                                                   "Subject: Captcha\r\n" +
-                                                   "Date: Mon Aug 30 19:05:40 PDT 2004\r\n" +
-                                                   "Content-Type: text/html\r\n" +
-                                                   "\r\n" +
-                                                   "<html><body>\r\n" +
-                                                   "Hi there.  Google is lame; please type in the word you see below and " +
-                                                   "click submit.  You might have to click 'get mail' again after that.<br>  " +
-                                                   "<img src=\"https://www.google.com/accounts/"+image+"\">\r\n" +
-                                                   "<form method=get action=http://gmail.megacz.com:8099/Captcha>\r\n"+
-                                                   "  <input type=text name=captcha>\r\n"+
-                                                   "  <input type=hidden name=email value=\""+email+"\">\r\n"+
-                                                   "  <input type=hidden name=pass value="+password+">\r\n"+
-                                                   "  <input type=hidden name=ctoken value=\""+ctoken+"\">\r\n"+
-                                                   "  <input type=submit>\r\n"+
-                                                   "</form>\r\n"+
-                                                   "</body></html>\r\n"),
-                                        null);
-                messages.addElement(m);
-                return messages;
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            return messages;
-        }
+        if (result.indexOf("top.location") == -1) { doCaptcha(result); return; }
         result = result.substring(result.indexOf("top.location"));
         result = result.substring(result.indexOf("CheckCookie?continue=") + "CheckCookie?continue=".length());
         result = result.substring(0, result.indexOf('\"'));
@@ -138,75 +186,115 @@ public class GMail extends Account {
         Log.warn("[]", "getting " + result);
 
         // just need the cookie off of this page
-        String s = new String(InputStreamToByteArray.convert(new HTTP(result).GET(null, jar)));
-        Log.warn("[]", s);
+        InputStreamToByteArray.convert(new HTTP(result).GET(null, jar));
+        imap.check();
+        Log.warn("[]", "done");
+    }
 
-        final Semaphore sem = new Semaphore();
+    Message captchaMessage = null;
+    public void doCaptcha(String result) throws IOException {
+        Log.warn(GMail.class,"no relocator found; checking for captcha");
+        ctoken = result.substring(result.indexOf("id=\"ctoken\" value=\"") + "id=\"ctoken\" value=\"".length());
+        ctoken = ctoken.substring(0, ctoken.indexOf("\""));
+        String image = result.substring(result.indexOf("Captcha?"));
+        image = image.substring(0, image.indexOf("\""));
+        String str =                                            
+            "From: google@google.com\r\n" +
+            "To: you@yourself.com\r\n" +
+            "Subject: Captcha\r\n" +
+            "Date: Mon Aug 30 19:05:40 PDT 2004\r\n" +
+            "Content-Type: text/html\r\n" +
+            "\r\n" +
+            "<html><body>\r\n" +
+            "Hi there.  Google is lame; please type in the word you see below and " +
+            "click submit.  You might have to click 'get mail' again after that.<br>  " +
+            "<img src=\"https://www.google.com/accounts/"+image+"\">\r\n" +
+            "<form method=get action=http://gmail.megacz.com:8099/Captcha>\r\n"+
+            "  <input type=text name=captcha>\r\n"+
+            "  <input type=hidden name=email value=\""+email+"\">\r\n"+
+            "  <input type=hidden name=pass value="+password+">\r\n"+
+            "  <input type=hidden name=ctoken value=\""+ctoken+"\">\r\n"+
+            "  <input type=submit>\r\n"+
+            "</form>\r\n"+
+            "</body></html>\r\n";
         try {
-            JSArray ret = grab(gmail + "?search=inbox&start=0&view=tl", jar);
+            captchaMessage = new Message(new Stream(str), null);
+        } catch (Message.Malformed e) {
+            Log.warn(this, e);
+            throw new IOException(e.toString());
+        }
+    }
+
+    public synchronized Summary[] query(String query) { 
+        if (captcha != null) return new Summary[0];
+        try {
+            Log.info(GMail.class, "query: " + query);
+            JSArray ret = http(gmail + query, jar);
+            Hashtable h = new Hashtable();
             for(int i=0; i<ret.length(); i++) {
                 JSArray j = (JSArray)ret.get(i);
-                if (!j.elementAt(0).equals("t")) continue;
-                for(int k=1; k<j.length(); k++) {
-                    final String threadid = (String)((JSArray)j.get(k)).get(0);
-                    sem.dec();
-                    new Thread() { public void run() {
-                        try {
-                            // FIXME: future: pipeline these requests over a single socket (maybe two)
-                            JSArray js2 =
-                                grab(gmail + "?search=query&start=0&view=cv&q=in:anywhere&th=" + URLEncoder.encode(threadid), jar);
-                            for(int i2=0; i2<js2.length(); i2++) {
-                                JSArray j2 = (JSArray)js2.get(i2);
-                                if (j2.elementAt(0).equals("t")) {
-                                    for(int k2=1; k2<j2.length(); k2++) {
-                                        JSArray m = (JSArray)j2.get(k2);
-                                        String from = (String)m.get(4);
-                                        String email = from.substring(from.indexOf("_email_") + "_email_".length());
-                                        email = email.substring(0, email.indexOf("\'"));
-                                        String name = from.substring(from.indexOf('>') + 1);
-                                        name = name.substring(0, name.indexOf("</span>"));
-                                        String subject = (String)m.get(6);
-                                        //Log.warn("[]", name + " <"+email+"> " + subject);
-                                        //Log.warn("[]", );
-                                    }
-                                } else if (j2.elementAt(0).equals("mi")) {
-                                    JSArray m = j2;
-                                    String date = m.get(9).toString();
-                                    String to = m.get(8).toString();
-                                    String toemail = m.get(10).toString();
-                                    String from = m.get(4).toString();
-                                    String name = m.get(6).toString();
-                                    String email = m.get(7).toString();
-                                    String subject = m.get(15).toString();
-                                    Log.warn("[]", name + " <"+email+"> " + subject);
-                                    Stream thestream = new Stream(new HTTP(gmail+"?search=query&start=0&view=om&th=" +
-                                                                           URLEncoder.encode(threadid)).GET(null, jar));
-                                    thestream.readln();
-                                    messages.addElement(new Message(thestream, null));
-                                }
-                            }
-                        } catch (Exception e) {
-                            e.printStackTrace();
-                        } finally {
-                            sem.release();
-                        }
-                    } }.start();
+                if (j.elementAt(0).equals("t")) {
+                    for(int k=1; k<j.length(); k++) getSummary((String)((JSArray)j.get(k)).get(0), h);
+                } else if (j.elementAt(0).equals("ct") && labels.length == 0) {
+                    Vec v = new Vec();
+                    j = (JSArray)j.elementAt(1);
+                    for(int k=0; k<j.length(); k++) v.addElement(((JSArray)j.elementAt(k)).elementAt(0));
+                    v.copyInto(labels = new String[v.size()]);
                 }
             }
-            sem.release();
-            Log.warn(GMail.class, "block");
-            sem.block();
-            Log.warn(GMail.class, "release");
+            Enumeration e = h.keys();
+            Vec v = new Vec();
+            while(e.hasMoreElements()) v.addElement(h.get(e.nextElement()));
+            return summaries = (Summary[])v.copyInto(new Summary[v.size()]);
+        } catch (Exception e) {
+            Log.warn(this, e);
+            return new Summary[0];
+        }
+    }
+
+    public void getSummary(String id, Hashtable ret) {
+        try {
+            JSArray js2 = http(gmail + "?search=query&start=0&view=cv&q=in:anywhere&th=" + URLEncoder.encode(id), jar);
+            for(int i2=0; i2<js2.length(); i2++) {
+                JSArray args = (JSArray)js2.elementAt(i2);
+                if (!args.elementAt(0).equals("mi")) continue;
+                Summary sum = new Summary(args);
+                Log.info(GMail.class, "summary: " + sum.subject);
+                ret.put(sum.id, sum);
+            }
         } catch (Exception e) {
             e.printStackTrace();
         }
-        success = true;
-        return messages;
     }
 
-    boolean success = false;
+    private class Summary {
+        public Address from;
+        public Address to;
+        public String  subject;
+        public Date    date;
+        public String  id;
+        public Message message = null;
+        
+        public int getIntId() { return Math.abs(Integer.parseInt(id.toLowerCase().substring(id.length()-7), 16)); }
+
+        public Message getMessage() throws Message.Malformed, IOException {
+            if (message != null) return message;
+            Stream thestream =
+                new Stream(new HTTP(gmail+"?search=query&start=0&view=om&th=" + URLEncoder.encode(id)).GET(null, jar));
+            thestream.readln();
+            return message = new Message(thestream, null);
+        }
+        
+        public Summary(JSArray m) {
+            try { this.date = new Date(m.elementAt(9).toString()); } catch (Exception e) { this.date = null; }
+            this.id = m.elementAt(3).toString();
+            this.to = Address.parse(m.elementAt(8).toString() + " <" + m.elementAt(10).toString() + ">");
+            this.from = Address.parse(m.elementAt(6).toString()+"<"+m.elementAt(7).toString()+">");
+            this.subject = m.elementAt(15).toString();
+        }
+    }
 
-    public JSArray grab(String url, HTTP.Cookie.Jar jar) throws JSExn, IOException  {
+    public JSArray http(String url, HTTP.Cookie.Jar jar) throws JSExn, IOException  {
         Stream stream = new Stream(new HTTP(url).GET(null, jar));                    
         boolean inscript = false;
         StringBuffer buf = new StringBuffer("var ret = []; var D = function(x){ret.push(x);};");
@@ -223,8 +311,6 @@ public class GMail extends Account {
         }
     }
 
-    public Mailbox getMailbox(Class protocol) { return this.root; }
-
 
     // HTTP Listener for Captcha requests //////////////////////////////////////////////////////////////////////////////
     
@@ -245,7 +331,10 @@ public class GMail extends Account {
                       URLDecoder.decode(tok.substring(tok.indexOf('=')+1)));
             }
             ((GMail)cache.get((String)h.get("email"), (String)h.get("pass"))).setCaptcha(h);
-            conn.println("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n<html><body><script>window.close()</script></body></html>\r\n");
+            conn.println("HTTP/1.0 200 OK\r\n");
+            conn.println("Content-Type: text/plain\r\n");
+            conn.println("\r\n");
+            conn.println("<html><body><script>window.close()</script></body></html>\r\n");
         } else {
             conn.println("HTTP/1.0 500 Error\r\n\r\n");
         }
@@ -254,16 +343,21 @@ public class GMail extends Account {
     }
 
     public void setCaptcha(Hash h) {
-        captchaString = (String)h.get("captcha");
+        captcha = (String)h.get("captcha");
         ctoken = (String)h.get("ctoken");
-        Log.warn(GMail.class, "captchaString = " + captchaString);
+        Log.warn(GMail.class, "captcha = " + captcha);
         Log.warn(GMail.class, "ctoken = " + ctoken);
         Log.warn(GMail.class, "initting..." + ctoken);
         try {
-            init();
+            getCookies();
             Log.warn(GMail.class, "  done..." + ctoken);
         } catch (Exception e) {
             Log.error(this, e);
         }
     }
+
+    // Constants //////////////////////////////////////////////////////////////////////////////
+
+    public static final String login = "https://www.google.com/accounts/ServiceLoginBoxAuth";
+    public static final String gmail = "https://gmail.google.com/gmail";
 }