+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+
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.*;
import org.ibex.util.*;
+import org.ibex.net.*;
+import org.ibex.js.*;
import org.ibex.mail.target.*;
import java.util.*;
import java.net.*;
import java.text.*;
import java.io.*;
+import org.ibex.io.*;
+import org.ibex.io.Fountain;
public class GMail extends Account {
- public Mailbox getMailbox(Class protocol) { return this.root; }
- public GMail(String user, String pass) {
- super(user, Address.parse(user + "@gmail.com"));
- Log.warn(GMail.class, "logging in " + user + "@gmail.com...");
+
+ private GMailIMAP imap = new GMailIMAP();
+ private HTTP.Cookie.Jar jar = new HTTP.Cookie.Jar();
+ 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 ///////////////////////////////////////////////////////////////////////////
+
+ 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;)"; }
+ public static GMail getGMail(String email, String pass) {
try {
- Process p =
- Runtime.getRuntime().exec(new String[] {
- "/usr/bin/python",
- "/usr/local/libgmail/demos/archive.py",
- user,
- pass
- });
- final Mailbox inbox = new MessageArrayMailbox(Mbox.parse(new Stream(p.getInputStream())));
- 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 int uidValidity() { return 1; }
- public Mailbox.Iterator iterator() { return new Mailbox.Iterator.NullIterator(); }
- public int uidNext() { return 500; }
- public String[] children() { return new String[] { "gmail" }; }
- public Mailbox slash(String name, boolean create) { return inbox; }
- };
- p.waitFor();
- Log.warn(GMail.class, " succeeded for " + user + "@gmail.com!");
+ GMail g = (GMail)cache.get(email, pass);
+ if (g == null) cache.put(email, pass, g = new GMail(email, pass));
+ return g;
} catch (Exception e) {
Log.error(GMail.class, e);
+ return null;
+ }
+ }
+
+ // IMAP Interface //////////////////////////////////////////////////////////////////////////////
+
+ public IMAP.Server getIMAP() { return imap; }
+ private class GMailIMAP implements IMAP.Server {
+
+ 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) {
+ String oldquery = query;
+ if (mailbox.equalsIgnoreCase("inbox")) {
+ query = "?search=inbox&start=0&view=tl";
+ if (!query.equals(oldquery)) check();
+ return;
+ }
+ for(int i=0; i<labels.length; i++) {
+ if (labels[i].equals(mailbox)) {
+ query = "?search=cat&cat="+mailbox+"&inbox&start=0&view=tl";
+ if (!query.equals(oldquery)) check();
+ return;
+ }
+ }
+ 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);
+ }
+
+ 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 Headers head() { return m.headers; }
+ public boolean next() { return false; }
+ public int uid() { return num; }
+ public int imapNumber() { return num; }
+ public int nntpNumber() { throw new RuntimeException("not supported"); }
+ 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);
+ throw new Error("broken");
+ //client.fetch(i+1, 0, m.size(), m,/* summaries[i].getIntId()*/ i);
+ }
+ } catch (Exception e) { Log.warn(this, e); }
+ }
+
+ 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 count() { return summaries.length; }
+ public int maxuid() { 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" +
+ (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;
+
+ 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('\"'));
+ result = URLDecoder.decode(result);
+ Log.warn("[]", "getting " + result);
+
+ // just need the cookie off of this page
+ InputStreamToByteArray.convert(new HTTP(result).GET(null, jar));
+ imap.check();
+ Log.warn("[]", "done");
+ }
+
+ 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 {
+ captchaMessage = Message.newMessage(new Fountain.StringFountain(str));
+ } 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.size(); i++) {
+ JSArray j = (JSArray)ret.get(i);
+ if (j.get(0).equals("t")) {
+ for(int k=1; k<j.size(); k++) getSummary((String)((JSArray)j.get(k)).get(0), h);
+ } else if (j.get(0).equals("ct") && labels.length == 0) {
+ Vec v = new Vec();
+ j = (JSArray)j.get(1);
+ for(int k=0; k<j.size(); k++) v.addElement(((JSArray)j.get(k)).get(0));
+ v.copyInto(labels = new String[v.size()]);
+ }
+ }
+ 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.size(); i2++) {
+ JSArray args = (JSArray)js2.get(i2);
+ if (!args.get(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();
+ }
+ }
+
+ 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 {
+ throw new Error("broken right now");
+ /*
+ 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 = Message.newMessage(new Fountain.StringFountainthestream);
+ */
+ }
+
+ public Summary(JSArray m) {
+ try { this.date = new Date(m.get(9).toString()); } catch (Exception e) { this.date = null; }
+ this.id = m.get(3).toString();
+ this.to = Address.parse(m.get(8).toString() + " <" + m.get(10).toString() + ">");
+ this.from = Address.parse(m.get(6).toString()+"<"+m.get(7).toString()+">");
+ this.subject = m.get(15).toString();
}
}
+
+ 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);};");
+ String s = null;
+ while((s = stream.readln()) != null) {
+ if (s.indexOf("<script>") != -1) { inscript = true; continue; }
+ if (s.indexOf("</script>") != -1) { inscript = false; continue; }
+ if (inscript) buf.append(s);
+ }
+ buf.append("return ret;");
+ synchronized(GMail.class) {
+ JS js = JSU.fromReader("google", 0, new StringReader(buf.toString()));
+ return (JSArray)js.call(null, JSU.emptyArgs);
+ }
+ }
+
+
+ // HTTP Listener for Captcha requests //////////////////////////////////////////////////////////////////////////////
+
+ public static void handleRequest(Connection conn) {
+ String top = null;
+ for(String s = conn.readln(); s != null && s.length() > 0; s = conn.readln()) {
+ if (top == null) top = s;
+ Log.warn(GMail.class, s);
+ }
+ if (top.startsWith("GET /Captcha")) {
+ top = top.substring(top.indexOf('?')+1);
+ top = top.substring(0, top.indexOf(' '));
+ StringTokenizer st = new StringTokenizer(top, "&");
+ Hash h = new Hash();
+ while(st.hasMoreTokens()) {
+ String tok = st.nextToken();
+ h.put(URLDecoder.decode(tok.substring(0, tok.indexOf('='))),
+ 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\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");
+ }
+ conn.flush();
+ conn.close();
+ }
+
+ public void setCaptcha(Hash h) {
+ captcha = (String)h.get("captcha");
+ ctoken = (String)h.get("ctoken");
+ Log.warn(GMail.class, "captcha = " + captcha);
+ Log.warn(GMail.class, "ctoken = " + ctoken);
+ Log.warn(GMail.class, "initting..." + ctoken);
+ try {
+ 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";
}