1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
6 import org.ibex.crypto.*;
7 import org.ibex.mail.protocol.*;
8 import org.ibex.jinetd.Listener;
9 import org.ibex.util.*;
10 import org.ibex.net.*;
12 import org.ibex.mail.target.*;
18 import org.ibex.io.Fountain;
20 public class GMail extends Account {
22 private GMailIMAP imap = new GMailIMAP();
23 private HTTP.Cookie.Jar jar = new HTTP.Cookie.Jar();
24 private String captcha = null;
25 private String ctoken = null;
26 private String email = null;
27 private String password = null;
28 private boolean invalid = false;
29 private String[] labels = new String[0];
30 private String[] queries = new String[0];
31 private Summary[] summaries = new Summary[0];
33 // Constructor, Pooling ///////////////////////////////////////////////////////////////////////////
35 public GMail(String email, String pass) throws IOException {
36 super(email.substring(0, email.indexOf('@')), Address.parse(email));
37 this.email = email; this.password = pass;
38 Log.warn(GMail.class, "logging in " + email);
42 public void close() { cache.remove(email, password); invalid = true; }
44 private static Hash cache = new Hash();
45 static { HTTP.userAgent = "Mozilla/5.0 (compatible;)"; }
46 public static GMail getGMail(String email, String pass) {
48 GMail g = (GMail)cache.get(email, pass);
49 if (g == null) cache.put(email, pass, g = new GMail(email, pass));
51 } catch (Exception e) {
52 Log.error(GMail.class, e);
57 // IMAP Interface //////////////////////////////////////////////////////////////////////////////
59 public IMAP.Server getIMAP() { return imap; }
60 private class GMailIMAP implements IMAP.Server {
62 private String query = "?search=inbox&start=0&view=tl";
63 private int validity = new Random().nextInt();
65 private IMAP.Client client;
66 public void setClient(IMAP.Client client) { this.client = client; }
68 public String[] capability() { return new String[] { }; }
69 public Hashtable id(Hashtable clientId) { return null; }
70 public void logout() { GMail.this.close(); }
71 public void subscribe(String mailbox) { }
72 public void unsubscribe(String mailbox) { }
74 public void unselect() { summaries = new Summary[0]; }
75 public void close() { summaries = new Summary[0]; }
77 public void noop() { check(); }
78 public void expunge() { }
80 public void setFlags(Query q, int flags, boolean uid, boolean silent) { }
81 public void removeFlags(Query q, int flags, boolean uid, boolean silent) { }
82 public void addFlags(Query q, int flags, boolean uid, boolean silent) { }
84 public void rename(String from, String to) { }
85 public void delete(String m) { }
86 public void create(String m) {
87 String[] newqueries = new String[queries.length+1];
88 System.arraycopy(queries, 0, newqueries, 0, queries.length);
89 newqueries[queries.length] = m;
93 public void append(String m, int flags, Date arrival, String body) { }
94 public void copy(Query q, String to) { }
96 public void check() { query(query); }
97 public void select(String mailbox, boolean examineOnly) {
98 String oldquery = query;
99 if (mailbox.equalsIgnoreCase("inbox")) {
100 query = "?search=inbox&start=0&view=tl";
101 if (!query.equals(oldquery)) check();
104 for(int i=0; i<labels.length; i++) {
105 if (labels[i].equals(mailbox)) {
106 query = "?search=cat&cat="+mailbox+"&inbox&start=0&view=tl";
107 if (!query.equals(oldquery)) check();
111 query = "?search=query&q="+URLEncoder.encode(mailbox)+"&start=0&view=tl";
112 if (!query.equals(oldquery)) check();
115 public void lsub(String start, String ref) { list(true); }
116 public void list(String start, String ref) { list(false); }
117 private void list(boolean lsub) {
118 client.list('/', "inbox", lsub, false);
119 for(int i=0; i<labels.length; i++) client.list('/', labels[i], lsub, false);
120 for(int i=0; i<queries.length; i++) client.list('/', queries[i], lsub, false);
123 public int[] search(Query q, boolean uid) { return null; }
124 public void fetch(Query q, int spec, String[] headers, int start, int end, boolean uid) {
125 if (captchaMessage != null) { client.fetch(1, 0, 100, captchaMessage, 100); return; }
126 for(int i=0; i<summaries.length; i++) try {
127 final Message m = summaries[i].getMessage();
129 if (q.match(new Mailbox.Default.Iterator() {
130 public Message cur() { return m; }
131 public Headers head() { return m.headers; }
132 public boolean next() { return false; }
133 public int uid() { return num; }
134 public int imapNumber() { return num; }
135 public int nntpNumber() { throw new RuntimeException("not supported"); }
136 public void delete() { }
137 public void set(String key, String val) { }
138 public String get(String key) { return null; }
139 public boolean seen() { return false; }
140 public boolean deleted() { return false; }
141 public boolean flagged() { return false; }
142 public boolean draft() { return false;}
143 public boolean answered() { return false; }
144 public boolean recent() { return true; }
145 public void seen(boolean on) { }
146 public void deleted(boolean on) { }
147 public void flagged(boolean on) { }
148 public void draft(boolean on) { }
149 public void answered(boolean on) { }
150 public void recent(boolean on) { }
151 public int flags() { return 0; }
152 public void addFlags(int flags) { }
153 public void removeFlags(int flags) { }
154 public void setFlags(int flags) { }
156 Log.info(GMail.class, "fetch " + summaries[i].subject);
157 throw new Error("broken");
158 //client.fetch(i+1, 0, m.size(), m,/* summaries[i].getIntId()*/ i);
160 } catch (Exception e) { Log.warn(this, e); }
163 public int unseen(String mailbox) { return summaries.length; }
164 public int recent(String mailbox) { return summaries.length; }
165 public int count(String mailbox) { return summaries.length; }
166 public int count() { return summaries.length; }
167 public int maxuid() { return summaries.length; }
168 public int uidNext(String mailbox) { return summaries.length+1; }
169 public int uidValidity(String mailbox) { return validity; }
172 public void getCookies() throws IOException {
173 jar = new HTTP.Cookie.Jar();
175 "continue=" + URLEncoder.encode(gmail) +
177 "&Email=" + URLEncoder.encode(user) +
178 "&Passwd=" + password +
180 (captcha != null ? "&captcha="+URLEncoder.encode(captcha) : "") +
181 (captcha != null ? "&ctoken="+URLEncoder.encode(ctoken) : "");
182 Log.info("[request]", params);
183 HTTP http = captcha==null ? new HTTP(login+"?"+params) : new HTTP(login);
184 InputStream reply = captcha==null ?
185 http.GET(null, jar) :
186 http.POST("application/x-www-form-urlencoded", params, null, jar);
187 String result = new String(InputStreamToByteArray.convert(reply));
188 System.err.println(result);
191 if (result.indexOf("top.location") == -1) { doCaptcha(result); return; }
192 result = result.substring(result.indexOf("top.location"));
193 result = result.substring(result.indexOf("CheckCookie?continue=") + "CheckCookie?continue=".length());
194 result = result.substring(0, result.indexOf('\"'));
195 result = URLDecoder.decode(result);
196 Log.warn("[]", "getting " + result);
198 // just need the cookie off of this page
199 InputStreamToByteArray.convert(new HTTP(result).GET(null, jar));
201 Log.warn("[]", "done");
204 Message captchaMessage = null;
205 public void doCaptcha(String result) throws IOException {
206 Log.warn(GMail.class,"no relocator found; checking for captcha");
207 ctoken = result.substring(result.indexOf("id=\"ctoken\" value=\"") + "id=\"ctoken\" value=\"".length());
208 ctoken = ctoken.substring(0, ctoken.indexOf("\""));
209 String image = result.substring(result.indexOf("Captcha?"));
210 image = image.substring(0, image.indexOf("\""));
212 "From: google@google.com\r\n" +
213 "To: you@yourself.com\r\n" +
214 "Subject: Captcha\r\n" +
215 "Date: Mon Aug 30 19:05:40 PDT 2004\r\n" +
216 "Content-Type: text/html\r\n" +
219 "Hi there. Google is lame; please type in the word you see below and " +
220 "click submit. You might have to click 'get mail' again after that.<br> " +
221 "<img src=\"https://www.google.com/accounts/"+image+"\">\r\n" +
222 "<form method=get action=http://gmail.megacz.com:8099/Captcha>\r\n"+
223 " <input type=text name=captcha>\r\n"+
224 " <input type=hidden name=email value=\""+email+"\">\r\n"+
225 " <input type=hidden name=pass value="+password+">\r\n"+
226 " <input type=hidden name=ctoken value=\""+ctoken+"\">\r\n"+
227 " <input type=submit>\r\n"+
229 "</body></html>\r\n";
231 captchaMessage = Message.newMessage(new Fountain.StringFountain(str));
232 } catch (Message.Malformed e) {
234 throw new IOException(e.toString());
238 public synchronized Summary[] query(String query) {
239 if (captcha != null) return new Summary[0];
241 Log.info(GMail.class, "query: " + query);
242 JSArray ret = http(gmail + query, jar);
243 Hashtable h = new Hashtable();
244 for(int i=0; i<ret.size(); i++) {
245 JSArray j = (JSArray)ret.get(i);
246 if (j.get(0).equals("t")) {
247 for(int k=1; k<j.size(); k++) getSummary((String)((JSArray)j.get(k)).get(0), h);
248 } else if (j.get(0).equals("ct") && labels.length == 0) {
250 j = (JSArray)j.get(1);
251 for(int k=0; k<j.size(); k++) v.addElement(((JSArray)j.get(k)).get(0));
252 v.copyInto(labels = new String[v.size()]);
255 Enumeration e = h.keys();
257 while(e.hasMoreElements()) v.addElement(h.get(e.nextElement()));
258 return summaries = (Summary[])v.copyInto(new Summary[v.size()]);
259 } catch (Exception e) {
261 return new Summary[0];
265 public void getSummary(String id, Hashtable ret) {
267 JSArray js2 = http(gmail + "?search=query&start=0&view=cv&q=in:anywhere&th=" + URLEncoder.encode(id), jar);
268 for(int i2=0; i2<js2.size(); i2++) {
269 JSArray args = (JSArray)js2.get(i2);
270 if (!args.get(0).equals("mi")) continue;
271 Summary sum = new Summary(args);
272 Log.info(GMail.class, "summary: " + sum.subject);
273 ret.put(sum.id, sum);
275 } catch (Exception e) {
280 private class Summary {
283 public String subject;
286 public Message message = null;
288 public int getIntId() { return Math.abs(Integer.parseInt(id.toLowerCase().substring(id.length()-7), 16)); }
290 public Message getMessage() throws Message.Malformed, IOException {
291 throw new Error("broken right now");
293 if (message != null) return message;
295 new Stream(new HTTP(gmail+"?search=query&start=0&view=om&th=" + URLEncoder.encode(id)).GET(null, jar));
297 return message = Message.newMessage(new Fountain.StringFountainthestream);
301 public Summary(JSArray m) {
302 try { this.date = new Date(m.get(9).toString()); } catch (Exception e) { this.date = null; }
303 this.id = m.get(3).toString();
304 this.to = Address.parse(m.get(8).toString() + " <" + m.get(10).toString() + ">");
305 this.from = Address.parse(m.get(6).toString()+"<"+m.get(7).toString()+">");
306 this.subject = m.get(15).toString();
310 public JSArray http(String url, HTTP.Cookie.Jar jar) throws JSExn, IOException {
311 Stream stream = new Stream(new HTTP(url).GET(null, jar));
312 boolean inscript = false;
313 StringBuffer buf = new StringBuffer("var ret = []; var D = function(x){ret.push(x);};");
315 while((s = stream.readln()) != null) {
316 if (s.indexOf("<script>") != -1) { inscript = true; continue; }
317 if (s.indexOf("</script>") != -1) { inscript = false; continue; }
318 if (inscript) buf.append(s);
320 buf.append("return ret;");
321 synchronized(GMail.class) {
322 JS js = JSU.fromReader("google", 0, new StringReader(buf.toString()));
323 return (JSArray)js.call(null, JSU.emptyArgs);
328 // HTTP Listener for Captcha requests //////////////////////////////////////////////////////////////////////////////
330 public static void handleRequest(Connection conn) {
332 for(String s = conn.readln(); s != null && s.length() > 0; s = conn.readln()) {
333 if (top == null) top = s;
334 Log.warn(GMail.class, s);
336 if (top.startsWith("GET /Captcha")) {
337 top = top.substring(top.indexOf('?')+1);
338 top = top.substring(0, top.indexOf(' '));
339 StringTokenizer st = new StringTokenizer(top, "&");
341 while(st.hasMoreTokens()) {
342 String tok = st.nextToken();
343 h.put(URLDecoder.decode(tok.substring(0, tok.indexOf('='))),
344 URLDecoder.decode(tok.substring(tok.indexOf('=')+1)));
346 ((GMail)cache.get((String)h.get("email"), (String)h.get("pass"))).setCaptcha(h);
347 conn.println("HTTP/1.0 200 OK\r\n");
348 conn.println("Content-Type: text/plain\r\n");
349 conn.println("\r\n");
350 conn.println("<html><body><script>window.close()</script></body></html>\r\n");
352 conn.println("HTTP/1.0 500 Error\r\n\r\n");
358 public void setCaptcha(Hash h) {
359 captcha = (String)h.get("captcha");
360 ctoken = (String)h.get("ctoken");
361 Log.warn(GMail.class, "captcha = " + captcha);
362 Log.warn(GMail.class, "ctoken = " + ctoken);
363 Log.warn(GMail.class, "initting..." + ctoken);
366 Log.warn(GMail.class, " done..." + ctoken);
367 } catch (Exception e) {
372 // Constants //////////////////////////////////////////////////////////////////////////////
374 public static final String login = "https://www.google.com/accounts/ServiceLoginBoxAuth";
375 public static final String gmail = "https://gmail.google.com/gmail";