import java.text.*;
import java.io.*;
+// FEATURE: IDLE extension for blackberries
+
// FIXME: this is valid LSUB "" asdfdas%*%*%*%*SFEFGWEF
// FIXME: be very careful about where/when we quotify stuff
// FIXME: 'UID FOO 100:*' must match at least one message even if all UIDs less than 100
public IMAP() { }
public static final float version = (float)0.2;
- // FIXME this is evil
- public static String getBodyString(Message m) {
- StringBuffer sb = new StringBuffer();
- m.getBody().getStream().transcribe(sb);
- return sb.toString();
- }
-
// API Class //////////////////////////////////////////////////////////////////////////////
public static interface Client {
Mailbox inbox = null;
Mailbox selected = null;
- Mailbox root = null;
+ MailTree root = null;
Mailbox selected() { if (selected == null) throw new Bad("no mailbox selected"); return selected; }
final Login auth;
final Client client;
public MailboxWrapper(Login auth, Client c) { this.auth=auth; this.client=c;}
public void setClient(IMAP.Client client) { }
+ private String dirname(String name) { return name.substring(0, name.lastIndexOf(sep)); }
+ private String basename(String name) { return name.substring(name.lastIndexOf(sep)+1); }
private Mailbox mailbox(String name, boolean create) { return mailbox(name, create, true); }
private Mailbox mailbox(String name, boolean create, boolean throwexn) {
if (name.equalsIgnoreCase("inbox")) return inbox;
- Mailbox m = root;
+ if (name.equalsIgnoreCase("trash")) name = "trash";
+ MailTree mt = mailboxTree(name, create, throwexn);
+ /* FIXME: throw throwexn here
+ if (mt==null || mt.getMailbox()==null)
+ Log.error("mt==null", name);
+ */
+ return mt==null ? null : mt.getMailbox();
+ }
+ private MailTree mailboxTree(String name, boolean create) { return mailboxTree(name, create, true); }
+ private MailTree mailboxTree(String name, boolean create, boolean throwexn) {
+ MailTree m = root;
for(StringTokenizer st = new StringTokenizer(name, sep + ""); st.hasMoreTokens();)
if ((m = m.slash(st.nextToken(), create)) == null) {
if (throwexn) throw new Server.No("no such mailbox " + name);
if (ref.length() == 0) { client.list(sep, start, lsub, false); return; }
while (start.endsWith(""+sep)) start = start.substring(0, start.length() - 1);
if (ref.endsWith("%")) ref = ref + sep;
- String[] children = (start.length() == 0 ? root : mailbox(start, false)).children();
+ String[] children = (start.length() == 0 ? root : mailboxTree(start, false)).children();
for(int i=0; i<children.length; i++) {
String s = children[i], pre = ref, kid = start + (start.length() > 0 ? sep+"" : "") + s;
if (mailbox(kid, false) == null) continue;
- Mailbox phant = mailbox(kid, false, false);
+ MailTree phant = mailboxTree(kid, false, false);
if (phant != null) {
- boolean phantom = phant.phantom();
+ boolean phantom = phant.getMailbox()==null;
while(true) {
if (pre.length() == 0) {
if (s.length() == 0) client.list(sep, kid, lsub, phantom);
}
}
- public String[] capability() { return new String[] { "IMAP4rev1" , "UNSELECT", "ID" }; }
+ public String[] capability() { return new String[] { "IMAP4rev1" , "UNSELECT" /*, "ID"*/ }; }
public Hashtable id(Hashtable clientId) {
Hashtable response = new Hashtable();
response.put("name", IMAP.class.getName());
for(Mailbox.Iterator it=selected().iterator(q);it.next();) to.insert(it.cur(), it.getFlags() | Mailbox.Flag.RECENT); }
public void unselect() { selected = null; }
- public void delete(String m0) { delete(mailbox(m0,false)); }
- public void delete(Mailbox m) { if (!m.equals(inbox)) m.destroy(false); else throw new Bad("can't delete inbox"); }
+
+ public void delete(String m0) { mailboxTree(dirname(m0),false).rmdir(basename(m0)); }
+
+ public void rename(String from0, String to) {
+ Mailbox from = mailbox(from0, false);
+ if (from.equals(inbox)) { from.copy(Query.all(), mailbox(to, true)); }
+ else if (to.equalsIgnoreCase("inbox")) { from.copy(Query.all(), mailbox(to, true)); delete(from0); }
+ else mailboxTree(dirname(from0), false)
+ .rename(dirname(from0),
+ mailboxTree(dirname(to),
+ true /* required by IMAP */),
+ basename(to));
+ }
+
public void create(String m) { mailbox(m, true, false); }
public void append(String m,int f,Date a,String b) { try {
+ // FIXME: more efficient streaming here?
mailbox(m,false).insert(Message.newMessage(new Fountain.StringFountain(b)), f | Mailbox.Flag.RECENT);
} catch (Message.Malformed e) { throw new No(e.getMessage()); } }
public void check() { }
for(Mailbox.Iterator it = selected().iterator(q);it.next();) {
// FIXME: what's going on here with the recent flag?
//boolean recent = it.getFlag(Mailbox.Flag.RECENT);
- try { throw new Exception("flags " + flags); } catch (Exception e) { Log.error(this, e); }
+ //try { throw new Exception("flags " + flags); } catch (Exception e) { Log.error(this, e); }
if (style == -1) it.setFlags(it.getFlags() & ~flags);
else if (style == 0) it.setFlags(flags);
else if (style == 1) it.setFlags(it.getFlags() | flags);
}
}
- public void rename(String from0, String to) {
- Mailbox from = mailbox(from0, false);
- if (from.equals(inbox)) { from.copy(Query.all(), mailbox(to, true)); }
- else if (to.equalsIgnoreCase("inbox")) { from.copy(Query.all(), mailbox(to, true)); from.destroy(false); }
- else from.rename(to);
- }
public void fetch(Query q, int spec, String[] headers, int start, int end, boolean uid) {
for(Mailbox.Iterator it = selected().iterator(q); it.next(); ) {
- Message message = ((spec & (BODYSTRUCTURE | ENVELOPE | INTERNALDATE | FIELDS | FIELDSNOT | RFC822 |
+ Message message =
+ ((spec & (BODYSTRUCTURE | ENVELOPE | INTERNALDATE | FIELDS | FIELDSNOT | RFC822 |
RFC822TEXT | RFC822SIZE | HEADERNOT | HEADER)) != 0) ? it.cur() : null;
- int size = message == null ? 0 : message.getLength();
- client.fetch(it.imapNumber(), it.getFlags(), size, message, it.uid());
+ long size = message == null ? 0 : message.getLength();
+ client.fetch(it.imapNumber(), it.getFlags(), (int)size, message, it.uid());
it.setFlags(it.getFlags() & ~Mailbox.Flag.RECENT);
}
}
/** takes an IMAP.Server and exposes it to the world as an IMAP server on a TCP socket */
public static class Listener implements Client {
String selectedName = null;
- Mailbox inbox = null, root = null;
+ Mailbox inbox = null;
+ MailTree root = null;
Server api;
Parser parser = null;
Connection conn = null;
Login auth;
public Listener(Login auth) { api = new IMAP.MailboxWrapper(this.auth = auth, this); }
Parser.Token token() { return parser.token(); }
- void println(String s) { conn.println(s); }
+ Parser.Token token(boolean freak) { return parser.token(freak); }
+ void println(String s) {
+ conn.println(s);
+ //Log.info("", s);
+ }
void newline() { parser.newline(); }
Query query(int max) { return parser.query(max, maxn(true)); }
} else {
Account account = (Account)ret;
((MailboxWrapper)api).root = root = account.getMailbox(IMAP.class);
- Log.warn(this, "logged in, root="+root);
- ((MailboxWrapper)api).inbox = inbox = root.slash("INBOX", false);
- if (inbox == null) ((MailboxWrapper)api).inbox = inbox = root;
+ MailTree ibt = root.slash("INBOX", false);
+ Mailbox ib = ibt==null ? null : ibt.getMailbox();
+ ((MailboxWrapper)api).inbox = inbox = ib;
}
}
case EXPUNGE: selected(); api.expunge(); break;
case UNSELECT: selected(); api.unselect(); selected = false; break;
case CREATE: api.create(token().astring()); break;
- case FETCH: selected(); fetch(((lastuid=uid)
- ? Query.uid(token().set(maxn(uid)))
- : Query.imapNumber(token().set(maxn(uid)))),
- lastfetch=token().lx(), 0, 0, 0, uid, 0); break;
+ case FETCH: selected(); lastuid = uid; fetch((uid
+ ? Query.uid(token().set(maxn(uid)))
+ : Query.imapNumber(token().set(maxn(uid)))),
+ lastfetch=token().lx(), 0, 0, 0, uid, 0); break;
case COPY: selected(); api.copy(uid
? Query.uid(token().set(maxn(uid)))
: Query.imapNumber(token().set(maxn(uid))), token().astring()); break;
Date arrival = new Date();
Parser.Token t = token();
if (t.type == t.LIST) { flags = t.flags(); t = token(); }
- if (t.type != t.QUOTED) { arrival = t.datetime(); t = token(); }
+ Parser.Token t2 = token(false);
+ if (t2 != null) { arrival = t.datetime(); t = t2; }
api.append(m, flags, arrival, t.q());
break; }
case STORE: {
return;
}
} catch (Server.Bad b) { println(tag==null ? "* BAD Invalid tag":(tag + " Bad " + b.toString())); Log.warn(this,b);
- } catch (Server.No n) { println(tag==null?"* BAD Invalid tag":(tag+" No " + n.toString())); Log.warn(this,n); }
+ } catch (Server.No n) { println(tag==null?"* BAD Invalid tag":(tag+" No " + n.toString())); Log.warn(this,n);
+ }
}
private Parser.Token[] lastfetch = null; // hack
t = new Parser.Token[] { parser.token("FLAGS"), parser.token("INTERNALDATE"),
parser.token("RFC822.SIZE") };
}
+ boolean looked_at_body = false;
for(int i=0; i<t.length; i++) {
if (r.length() > initlen) r.append(" ");
if (t[i] == null || t[i].s == null) continue;
} else if (s.equals("FLAGS")) { spec|=FLAGS; if(e){r.append(" ");r.append(Printer.flags(flags));}
} else if (s.equals("INTERNALDATE")) { spec|=INTERNALDATE; if(e){r.append(" ");r.append(Printer.date(m.arrival));}
} else if (s.equals("RFC822")) { spec|=RFC822; if(e){r.append(" ");r.append(Printer.message(m));}
- } else if (s.equals("RFC822.TEXT")) { spec|=RFC822TEXT; if(e){r.append(" ");r.append(Printer.qq(getBodyString(m)));}
- } else if (s.equals("RFC822.HEADER")){ spec|=HEADER;if(e){r.append(" ");r.append(Printer.qq(m.headers.getString()+"\r\n"));}
+ } else if (s.equals("RFC822.TEXT")) { spec|=RFC822TEXT; if(e){r.append(" ");r.append(Printer.qq(m.getStream()));}
+ } else if (s.equals("RFC822.HEADER")){ spec|=HEADER; if(e){r.append(" ");r.append(Printer.qq(m.headers.getStream().append("\r\n")));}
} else if (s.equals("RFC822.SIZE")) { spec|=RFC822SIZE; if(e){r.append(" ");r.append(m.getLength());}
} else if (s.equals("UID")) { spec|=UID; if(e){r.append(" ");r.append(muid); }
} else if (!(s.equals("BODY.PEEK") || s.equals("BODY"))) { throw new Server.No("unknown fetch argument: " + s);
} else {
if (s.equalsIgnoreCase("BODY.PEEK")) spec |= PEEK;
- //else if (e) api.addFlags(Query.imapNumber(new int[] { num, num }), Mailbox.Flag.SEEN, false, false);
+ looked_at_body = true;
if (i >= t.length - 1 || t[i+1].type != Parser.Token.LIST) {
spec |= BODYSTRUCTURE;
if (e) { r.append(" "); r.append(Printer.bodystructure(m)); } continue;
- //{ if (e) { r.append(" "); r.append(Printer.qq(m.body)); } continue; }
+ //if (e) { r.append(" "); r.append(Printer.qq(m.getBody().getStream())); } continue;
}
- String payload = "";
+ Fountain payload = Fountain.Util.create("");
r.append("[");
Parser.Token[] list = t[++i].l();
s = list.length == 0 ? "" : list[0].s.toUpperCase();
r.append(s);
- if (list.length == 0) { spec |= RFC822TEXT; if(e) payload = m.headers.getString()+"\r\n"+getBodyString(m); }
- else if (s.equals("") || s.equals("1")) { spec |= RFC822TEXT; if(e) payload = m.headers.getString()+"\r\n"+getBodyString(m); }
- else if (s.equals("TEXT")) { spec |= RFC822TEXT; if(e) payload = getBodyString(m); }
- else if (s.equals("HEADER")) { spec |= HEADER; if(e) payload = m.headers.getString()+"\r\n"; }
- else if (s.equals("HEADER.FIELDS")) { spec |= FIELDS; payload=headers(r,t[i].l()[1].sl(),false,m,e); }
- else if (s.equals("HEADER.FIELDS.NOT")) { spec |= FIELDSNOT; payload=headers(r,t[i].l()[1].sl(),true,m,e); }
+ if (list.length == 0) { spec |= RFC822TEXT; if(e) payload = m; }
+ else if (s.equals("") || s.equals("1")) { spec |= RFC822TEXT; if(e) payload = m; }
+ else if (s.equals("TEXT")) { spec |= RFC822TEXT; if(e) payload = m.getBody(); }
+ else if (s.equals("HEADER")) { spec |= HEADER; if(e) payload = Fountain.Util.concat(m.headers, Fountain.Util.create("\r\n")); }
+ else if (s.equals("HEADER.FIELDS")) { spec |= FIELDS; payload=Fountain.Util.create(headers(r,t[i].l()[1].sl(),false,m,e)); }
+ else if (s.equals("HEADER.FIELDS.NOT")) { spec |= FIELDSNOT; payload=Fountain.Util.create(headers(r,t[i].l()[1].sl(),true,m,e)); }
else if (s.equals("MIME")) { throw new Server.Bad("MIME not supported"); }
else throw new Server.Bad("unknown section type " + s);
if (i<t.length - 1 && (t[i+1].s != null && t[i+1].s.startsWith("<"))) {
int dot = s.indexOf('.');
start = dot == -1 ? Integer.parseInt(s) : Integer.parseInt(s.substring(0, s.indexOf('.')));
end = dot == -1 ? -1 : Integer.parseInt(s.substring(s.indexOf('.') + 1));
- if (e) { payload = payload.substring(start, Math.min(end+1,payload.length())); r.append("<"+start+">"); }
+ if (e) {
+ //payload = payload.substring(start, Math.min(end+1,payload.getLength())); r.append("<"+start+">");
+ // FIXME
+ throw new RuntimeException("this had to be disabled");
+ }
}
- if (e) { r.append("] "); r.append(Printer.qq(payload)); }
+ if (e) { r.append("] "); r.append(Printer.qq(payload.getStream())); }
}
}
+ if ((spec & PEEK) == 0 && looked_at_body && e)
+ api.addFlags(Query.imapNumber(new int[] { num, num }), Mailbox.Flag.SEEN, false, false);
if (e) {
r.append(")");
println("* " + r.toString());
}
public Date datetime() {
if (type != QUOTED) bad("Expected quoted datetime");
- try { return new SimpleDateFormat("dd-MM-yyyy hh:mm:ss zzzz").parse(s.trim());
+ try { return new SimpleDateFormat("dd-MMM-yyyy hh:mm:ss zzzz").parse(s.trim());
} catch (ParseException p) { throw new Server.Bad("invalid datetime format " + s + " : " + p); }
}
public String atom() {
")";
}
+ // FIXME: ugly
+ public static String qq(Stream stream) {
+ StringBuffer sb = new StringBuffer();
+ stream.transcribe(sb);
+ return qq(sb.toString());
+ }
public static String qq(String s) {
StringBuffer ret = new StringBuffer();
ret.append('{');