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.*;
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;)"; }
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);
}
}
- 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('\"'));
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);};");
}
}
- public Mailbox getMailbox(Class protocol) { return this.root; }
-
// HTTP Listener for Captcha requests //////////////////////////////////////////////////////////////////////////////
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");
}
}
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";
}