From 6be20eb1109868daae492d6b4d7440c213382254 Mon Sep 17 00:00:00 2001 From: adam Date: Sun, 13 Jun 2004 04:00:51 +0000 Subject: [PATCH] massive cleanup, almost there! darcs-hash:20040613040051-5007d-f39d07badb46ca98fa090265d1735bbca1005a0a.gz --- src/org/ibex/mail/Message.java | 14 +- src/org/ibex/mail/protocol/IMAP.java | 816 ++++++++++++------------ src/org/ibex/mail/target/FileBasedMailbox.java | 5 +- src/org/ibex/mail/target/Mailbox.java | 12 +- 4 files changed, 436 insertions(+), 411 deletions(-) diff --git a/src/org/ibex/mail/Message.java b/src/org/ibex/mail/Message.java index 690285c..46f2e48 100644 --- a/src/org/ibex/mail/Message.java +++ b/src/org/ibex/mail/Message.java @@ -49,8 +49,8 @@ public class Message extends JSReflection { public void dump(OutputStream os) throws IOException { Writer w = new OutputStreamWriter(os); w.write(allHeaders); - w.write("X-IbexMail-EnvelopeFrom: " + envelopeFrom + "\r\n"); - w.write("X-IbexMail-EnvelopeTo: "); for(int i=0; i 126) throw new Malformed("Header key \""+key+"\" contains invalid character \"" + key.charAt(i) + "\""); String val = s.substring(s.indexOf(':') + 1).trim(); - while(Character.isSpace(val.charAt(0))) val = val.substring(1); + while(val.length() > 0 && Character.isSpace(val.charAt(0))) val = val.substring(1); if (key.startsWith("Resent-")) { if (key.startsWith("Resent-From")) resent.addElement(new Hashtable()); ((Hashtable)resent.lastElement()).put(key.substring(7), val); diff --git a/src/org/ibex/mail/protocol/IMAP.java b/src/org/ibex/mail/protocol/IMAP.java index 50b2803..1963ce2 100644 --- a/src/org/ibex/mail/protocol/IMAP.java +++ b/src/org/ibex/mail/protocol/IMAP.java @@ -7,204 +7,164 @@ import java.net.*; import java.text.*; import java.io.*; -// FEATURE: hoist all the ok()'s? +// Relevant RFC's: +// RFC 2060: IMAPv4 +// RFC 3501: IMAPv4 with clarifications +// RFC 3691: UNSELECT +// RFC 2971: ID + +// FEATURE: READ-WRITE / READ-ONLY status on SELECT // FEATURE: pipelining // FEATURE: support [charset] +// FEATURE: \Noselect +// FEATURE: subscriptions public class IMAP extends MessageProtocol { + // Constants ////////////////////////////////////////////////////////////////////////////// + public static final char imapSeparator = '.'; + public static final float version = (float)0.1; - public static void main(String[] args) throws Exception { - ServerSocket ss = new ServerSocket(143); - while(true) { - System.out.println("listening"); - final Socket s = ss.accept(); - System.out.println("connected"); - new Thread() { - public void run() { - try { - Mailbox root = - FileBasedMailbox.getFileBasedMailbox(Mailbox.STORAGE_ROOT + File.separatorChar + "imap", true); - Mailbox inbox = root.slash("users", true).slash("megacz", true); - new Listener(s, "megacz.com", root, inbox).handleRequest(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }.start(); - } - } - public static class Exn extends MailException { - public Exn(String s) { super(s); } - public static class No extends Exn { public No(String s) { super(s); } } - public static class Bad extends Exn { public Bad(String s) { super(s); } } - } + // Callbacks ////////////////////////////////////////////////////////////////////////////// + + public static interface Authenticator { public abstract Mailbox authenticate(String user, String pass); } + + + // Exceptions ////////////////////////////////////////////////////////////////////////////// - private static class Listener extends Incoming { + public static class Exn extends MailException { public Exn(String s) { super(s); } } + public static class Bad extends Exn { public Bad(String s) { super(s); } } + public static class No extends Exn { public No(String s) { super(s); } } + + + // Single Session Handler ////////////////////////////////////////////////////////////////////////////// + + private static class Session extends Incoming { Mailbox selected = null; - Mailbox root; - Mailbox inbox; - Socket conn; - String vhost; - PrintWriter pw; - InputStream is; - PushbackReader r; + Mailbox selected() { if (selected == null) throw new Bad("no mailbox selected"); return selected; } + String selectedName = null; + Mailbox inbox = null; + final Mailbox root; + final Socket conn; + final String vhost; + final Authenticator auth; + final PrintWriter pw; + final PushbackReader r; + final InputStream is; public void init() { } - public Listener(Socket conn, String vhost, Mailbox root, Mailbox inbox) throws IOException { - this.vhost = vhost; - this.conn = conn; - this.selected = null; - this.root = root; - this.inbox = inbox; + public Session(Socket conn, Mailbox root, Authenticator auth) throws IOException { + this(conn, java.net.InetAddress.getLocalHost().getHostName(), root, auth); } + public Session(Socket conn, String vhost, Mailbox root, Authenticator auth) throws IOException { + this.vhost = vhost; this.conn = conn; this.root = root; this.auth = auth; this.pw = new PrintWriter(new OutputStreamWriter(conn.getOutputStream())); - this.is = conn.getInputStream(); - this.r = new PushbackReader(new InputStreamReader(is)); + this.r = new PushbackReader(new InputStreamReader(this.is = conn.getInputStream())); } - private void star(String s) { - pw.println("* " + s); - System.err.println("* " + s); - pw.flush(); - } + private void star(String s) { println("* " + s); } + private void println(String s) { pw.println(s); pw.flush(); } + private void print(String s) { pw.print(s); } - // FIXME: user-inbox-relative stuff - // FIXME should throw a No if mailbox not found and create==false private Mailbox getMailbox(String name, boolean create) { Mailbox m = root; - while(name.length() > 0) { - int end = name.length(); - if (name.indexOf(imapSeparator) != -1) end = name.indexOf(imapSeparator); - m = m.slash(name.substring(0, end), create); - if (end == name.length()) break; - name = name.substring(end+1); - } + for(StringTokenizer st = new StringTokenizer(name, imapSeparator + ""); st.hasMoreTokens();) + if ((m = m.slash(st.nextToken(), create)) == null) throw new No("no such mailbox " + name); return m; } - private boolean auth(String user, String pass) { /* FEATURE */ return user.equals("megacz") && pass.equals("pass"); } - - public void lsub(String start, String ref) { list(start, ref); } // FEATURE: manage subscriptions - public void list(String start, String ref) { - if (ref.length() == 0) { star("LIST () \".\" \"\""); return; } - if (start.startsWith(".")) return; + // FIXME: not accurate when a wildcard and subsequent non-wildcards both match a single component + public HashSet lsub(String start, String ref, HashSet reply) { return list(start, ref, reply); } + public HashSet list(String start, String ref, HashSet reply) { + if (reply == null) reply = new HashSet(); + if (ref.length() == 0 && start.length() == 0) { reply.add(""); return reply; } + while (start.endsWith(""+imapSeparator)) start = start.substring(0, start.length() - 1); String[] children = (start.length() == 0 ? root : getMailbox(start, false)).children(); for(int i=0; i 0 ? imapSeparator+"" : "") + s; while(true) { - int percent = pre.indexOf('%'), star = pre.indexOf('*'), dot = pre.indexOf('.'); - if (s.length() == 0 && pre.length() == 0) { - star("LIST () \".\" \""+(start + (start.length() > 0 ? "." : "") + children[i])+"\""); - break; - } else if (pre.length() == 0) { - break; - } else if (percent != 0 && star != 0 && dot != 0) { - if (s.length() == 0 || s.charAt(0) != pre.charAt(0)) break; - s = s.substring(1); pre = pre.substring(1); - } else if (pre.charAt(0) == '.') { - if (s.length() == 0) list(start + (start.length() > 0 ? "." : "") + children[i], pre.substring(1)); - break; - } else if (pre.charAt(0) == '%') { - star("LIST () \".\" \""+(start + (start.length() > 0 ? "." : "") + children[i])+"\""); - list(start + (start.length() > 0 ? "." : "") + children[i], pre.substring(1)); - // FIXME this isn't right - pre = pre.substring(1); - } else if (pre.charAt(0) == '*') { - // FIXME need to iterate - star("LIST () \".\" \""+(start + (start.length() > 0 ? "." : "") + children[i])+"\""); - list(start + (start.length() > 0 ? "." : "") + children[i], pre); - pre = pre.substring(1); - } + if (pre.length() == 0) { + if (s.length() == 0) reply.add(kid); + } else switch(pre.charAt(0)) { + case imapSeparator: if (s.length() == 0) list(kid, pre.substring(1), reply); break; + case '%': reply.add(kid); pre=pre.substring(1); list(kid, pre, reply); break; + case '*': reply.add(kid); list(kid, pre, reply); pre = pre.substring(1); break; + default: if (s.length()==0) break; + if (s.charAt(0) != pre.charAt(0)) break; + s = s.substring(1); pre = pre.substring(1); continue; + } + break; } } - // FIXME: \Noselect + return reply; } - public void copy(final Query q, Mailbox to) { for(Mailbox.Iterator it=selected.iterator(q);it.next();) to.add(it.cur()); } - public void login(String user, String pass) { if (!auth(user, pass)) throw new Exn.No("Liar, liar, pants on fire."); } - public void capability() { star("CAPABILITY IMAP4rev1"); } - public void logout() { star("BYE LOGOUT received"); } + public String[] capability() { return new String[] { "IMAP4rev1", "UNSELECT", "ID" }; } + public Hashtable id(Hashtable clientId) { + Hashtable response = new Hashtable(); + response.put("name", IMAP.class.getName()); + response.put("version", version + ""); + response.put("os", System.getProperty("os.name", null)); + response.put("os-version", System.getProperty("os.version", null)); + response.put("vendor", "none"); + response.put("support-url", "http://mail.ibex.org/"); + return response; + } + + public void copy(Query q, Mailbox to) { for(Mailbox.Iterator it=selected.iterator(q);it.next();) to.add(it.cur()); } + public void login(String u, String p) { if ((inbox = auth.authenticate(u,p)) == null) throw new No("Login failed."); } + public void logout() { } + public void unselect() { selected = null; selectedName = null; } public void delete(Mailbox m) { if (!m.equals(inbox)) m.destroy(); } - public void create(String mailbox){if(!mailbox.endsWith(".")&&!mailbox.equalsIgnoreCase("inbox"))getMailbox(mailbox,true);} - public void close(boolean examineOnly) { expunge(false, true); selected = null; } + public void create(String m) { if (!m.endsWith(""+imapSeparator) && !m.equalsIgnoreCase("inbox")) getMailbox(m, true); } + public void append(Mailbox m, int flags, Date arrival, String body) { m.add(new Message(null,null,body,arrival), flags); } public void check() { } public void noop() { } - + public void close() { for(Mailbox.Iterator it=selected().iterator(Query.deleted()); it.next();) it.delete(); unselect(); } + public void expunge() { for(Mailbox.Iterator it = selected().iterator(Query.deleted()); it.next();) expunge(it); } + public void expunge(Mailbox.Iterator it) { star(it.uid() + " EXPUNGE"); it.delete(); } + public void subscribe(String mailbox) { } + public void unsubscribe(String mailbox) { } public void rename(Mailbox from, String to) { - if (from.equals(inbox)) from.copy(Query.all(), getMailbox(to, true)); + if (from.equals(inbox)) from.copy(Query.all(), getMailbox(to, true)); else if (to.equalsIgnoreCase("inbox")) { from.copy(Query.all(), getMailbox(to, true)); from.destroy(); } else from.rename(to); } - public void status(final Mailbox m, Token[] attrs) { - int count0 = 0, count1 = 0, count2 = 0; - for(Mailbox.Iterator it = m.iterator(); it.next(); ) { if (!it.seen()) count0++; if (it.recent()) count1++; count2++; } + // FIXME lift out unparsing + public void status(Mailbox m, Token[] attrs) { + int seen = 0, recent = 0, messages = 0; + for(Mailbox.Iterator it = m.iterator(); it.next(); ) { if (!it.seen()) seen++; if (it.recent()) recent++; messages++; } String response = ""; for(int i=0; i" + qq(payload.substring(0, frb.end + 1))); @@ -242,122 +204,84 @@ public class IMAP extends MessageProtocol { } } - - // FEATURE: hoist the parsing here - public void store(Query q, String what, Token[] flags) { + public void store(Query q, int addFlags, int removeFlags, boolean silent, boolean uid) { for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) { - Message m = it.cur(); - if (what.charAt(0) == 'F') { - it.deleted(false); it.seen(false); it.flagged(false); it.draft(false); it.answered(false); it.recent(false); } - for(int j=0; j")) { - startend = s.substring(s.indexOf('<'), s.indexOf('>')); - s = s.substring(0, s.indexOf('<')); - } + public FetchRequest[] parseFetch(Token[] t) { + FetchRequest[] ret = new FetchRequest[t.length]; + for(int i=0; i")) { + startend = s.substring(s.indexOf('<'), s.indexOf('>')); + s = s.substring(0, s.indexOf('<')); + } - if (s.equalsIgnoreCase("BODY.PEEK")) b.peek = true; - else if (s.equalsIgnoreCase("BODY")) b.peek = false; - else throw new Exn.No("unknown fetch argument: " + s); + if (s.equalsIgnoreCase("BODY.PEEK")) b.peek = true; + else if (s.equalsIgnoreCase("BODY")) b.peek = false; + else throw new No("unknown fetch argument: " + s); - if (i= '0' && s2.charAt(0) <= '9') { - mimeVec.addElement(new Integer(Integer.parseInt(s2.substring(0, s2.indexOf('.'))))); - s2 = s2.substring(s2.indexOf('.') + 1); - } - if (mimeVec.size() > 0) { - b.path = new int[mimeVec.size()]; - for(int j=0; j= '0' && s2.charAt(0) <= '9') { + mimeVec.addElement(new Integer(Integer.parseInt(s2.substring(0, s2.indexOf('.'))))); + s2 = s2.substring(s2.indexOf('.') + 1); } + if (mimeVec.size() > 0) { + b.path = new int[mimeVec.size()]; + for(int j=0; j