more progress
authoradam <adam@megacz.com>
Mon, 31 May 2004 03:52:30 +0000 (03:52 +0000)
committeradam <adam@megacz.com>
Mon, 31 May 2004 03:52:30 +0000 (03:52 +0000)
darcs-hash:20040531035230-5007d-bd274b234511ff8d20884b78260a2d77f65bc6d0.gz

src/org/ibex/mail/protocol/IMAP.java

index 1d895fd..02c2cbf 100644 (file)
@@ -7,8 +7,7 @@ import java.net.*;
 import java.text.*;
 import java.io.*;
 
-// FIXME: tagged responses
-
+// FEATURE: hoist all the ok()'s?
 // FEATURE: pipelining
 // FEATURE: support [charset]
 public class IMAP extends MessageProtocol {
@@ -22,7 +21,7 @@ public class IMAP extends MessageProtocol {
             new Thread() {
                 public void run() {
                     try {
-                        service(s);
+                        new Listener(s, "megacz.com").handleRequest();
                     } catch (Exception e) {
                         e.printStackTrace();
                     }
@@ -31,31 +30,37 @@ public class IMAP extends MessageProtocol {
         }
     }
 
-    public static class IMAPException extends MailException { public IMAPException(String s) { super(s); } }
-    public static class No extends IMAPException { public No(String s) { super(s); } }
-    public static class Bad extends IMAPException { public Bad(String s) { super(s); } }
-
-    public static void service(Socket s) { /* FIXME */ }
+    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); } }
+    }
 
     private static class Listener extends Incoming {
-        private InputStream is;
-        private PushbackReader r;
-
-        Socket conn;
-        String vhost;
-
         Mailbox selected = null;
         Mailbox root = null;
 
+        Socket conn;
+        String vhost;
         PrintWriter pw;
-        LineReader lr;
-
+        InputStream is;
+        PushbackReader r;
         public void init() { }
-        public Listener(Socket conn, String vhost) { this.vhost = vhost; this.conn = conn; this.selected = null; }
+        public Listener(Socket conn, String vhost) throws IOException {
+            this.vhost = vhost;
+            this.conn = conn;
+            this.selected = null;
+            this.pw = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
+            this.is = conn.getInputStream();
+            this.r = new PushbackReader(new InputStreamReader(is));
+        }
 
-        private void ok(String s) { star("OK " + s); }
+        // ugly: not concurrent
+        String tag;
+        private void ok(String s) { pw.println(tag + " OK " + s); }
         private void star(String s) { pw.println("* " + s); }
-        private boolean auth(String user, String pass) { /* FEATURE */ return user.equals("megacz") && pass.equals(""); }
+
+        // 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) {
@@ -67,74 +72,68 @@ public class IMAP extends MessageProtocol {
             return m;
         }
 
-        // sharing problem?  immutability helps
-        public void copy(int[] set, Mailbox target) {
-            for(int i=0; i<set.length; i++) target.add(selected.get(set[i]));
-        }
-
+        public void lsub(Mailbox m, String s) { star("LIST () \".\" INBOX"); ok("LSUB completed"); } // FIXME
+        public void list(Mailbox m, String s) { star("LIST () \".\" INBOX"); ok("LIST completed"); } // FIXME
 
-        public void login(String user, String password) { if (!auth(user, password)) throw new No("Liar, liar, pants on fire."); }
+        private boolean auth(String user, String pass) { /* FEATURE */ return user.equals("megacz") && pass.equals(""); }
+        public void copy(int[] set, Mailbox target) { for(int i=0; i<set.length; i++) target.add(selected.get(set[i])); }
+        public void login(String user, String password) {if (!auth(user,password))throw new Exn.No("Liar, liar, pants on fire."); }
         public void capability() { star("CAPABILITY IMAP4rev1"); ok("Completed"); }
         public void noop() { ok("Completed"); }
         public void logout() { star("BYE LOGOUT received"); ok("Completed"); }
-        public void delete(Mailbox m) { if (!m.getName().toLowerCase().equals("inbox")) m.destroy(); }
+        public void delete(Mailbox m) { if (!m.getName().toLowerCase().equals("inbox")) m.destroy(); ok("Completed"); }
         public void subscribe(String[] args) { ok("SUBSCRIBE ignored"); }
         public void unsubscribe(String[] args) { ok("UNSUBSCRIBE ignored"); }
-        public void check(String[] args, boolean examineOnly) { ok("check complete"); }
-        public void lsub(Mailbox m, String s) { list(m, s); }
-        public void list(Mailbox m, String s) { star("LIST () \".\" INBOX"); ok("LIST completed"); } // FIXME
+        public void check() { ok("CHECK ignored"); }
         public void create(String mailbox){if(!mailbox.endsWith(".")&&!mailbox.equalsIgnoreCase("inbox"))getMailbox(mailbox,true);}
         public void rename(Mailbox from, String to) {
-            if (from.getName().toLowerCase().equals("inbox")) from.moveAllMessagesTo(getMailbox(to, true));
-            else if (to.toLowerCase().equals("inbox"))        { from.moveAllMessagesTo(getMailbox(to, true)); from.destroy(); }
+            if (from.getName().equalsIgnoreCase("inbox")) from.moveAllMessagesTo(getMailbox(to, true));
+            else if (to.equalsIgnoreCase("inbox"))      { from.moveAllMessagesTo(getMailbox(to, true)); from.destroy(); }
             else from.rename(to);
         }
 
         public void status(Mailbox m, Token[] attrs) {
-            boolean messages = false, recent = false, uidnext = false, uidvalidity = false, unseen = false;
-            for(int i=0; i<attrs.length; i++) {
-                String s = attrs[i].atom();
-                if (s.equals("MESSAGES"))    messages = true;
-                if (s.equals("RECENT"))      recent = true;
-                if (s.equals("UIDNEXT"))     uidnext = true;
-                if (s.equals("UIDVALIDITY")) uidvalidity = true;
-                if (s.equals("UNSEEN"))      unseen = true;
-            }
-            int[] list = m.list(); int numRecent = 0, numUnseen = 0;
+            int[] list = m.list();
+            int recent = 0, unseen = 0;
             for(int i=0; i<list.length; i++) {
                 Message message = m.get(list[i]);
-                if (!message.seen) numUnseen++;
-                if (message.recent) numRecent++;
+                if (!message.seen) unseen++;
+                if (message.recent) recent++;
             }
-            star("STATUS FIXME ( " +
-                 (messages    ? ("MESSAGES "    + list.length      + " ") : "") +
-                 (recent      ? ("RECENT "      + numRecent        + " ") : "") +
-                 (unseen      ? ("UNSEEN "      + numUnseen        + " ") : "") +
-                 (uidvalidity ? ("UIDVALIDITY " + m.uidvalidity + " ") : "") +
-                 (uidnext     ? ("UIDNEXT "     + m.uidnext     + " ") : "") + ")");
+            String response = "";
+            for(int i=0; i<attrs.length; i++) {
+                String s = attrs[i].atom();
+                if (s.equals("MESSAGES"))    response += "MESSAGES "    + list.length;
+                if (s.equals("RECENT"))      response += "RECENT "      + recent;
+                if (s.equals("UIDNEXT"))     response += "UNSEEN "      + unseen;
+                if (s.equals("UIDVALIDITY")) response += "UIDVALIDITY " + m.uidvalidity;
+                if (s.equals("UNSEEN"))      response += "UIDNEXT "     + m.uidnext;
+            }
+            star("STATUS " + m.getName() + " (" + response + ")");
         }
 
-        public void select(String[] args, boolean examineOnly) {
-            if (args.length < 1) throw new Bad("Not enough arguments");
-            selected = getMailbox(args[0], false);
-            if (selected == null) throw new No("No such mailbox");
+        public void select(String mailbox, boolean examineOnly) {
+            selected = getMailbox(mailbox, false);
             star(selected.list().length + " EXISTS");
-            star("1 RECENT");
+            int recent = 0;
+            int[] list = selected.list(); for(int i=0; i<list.length; i++) if (selected.get(list[i]).recent) recent++;
+            star(recent + " RECENT");
             //star("OK [UNSEEN 12] Message 12 is first unseen");    FEATURE
             star("OK [UIDVALIDITY " + selected.uidvalidity + "] UIDs valid");
             star("FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)");
-            ok(/* FEATURE: READ-WRITE / READ-ONLY */ (examineOnly ? "EXAMINE" : "SELECT") + " completed");
+            // FEATURE: READ-WRITE / READ-ONLY
+            ok((examineOnly ? "EXAMINE" : "SELECT") + " completed");
         }
 
-        public void close(String[] args, boolean examineOnly) {
-            if (selected == null) throw new Bad("no mailbox selected");
-            expunge(new String[] { }, false, true);
+        public void close(boolean examineOnly) {
+            if (selected == null) throw new Exn.Bad("no mailbox selected");
+            expunge(false, true);
             selected = null;
             ok("CLOSE completed");
         }
 
-        public void expunge(String[] args, boolean examineOnly, boolean silent) {
-            if (selected == null) throw new Bad("no mailbox selected");
+        public void expunge(boolean examineOnly, boolean silent) {
+            if (selected == null) throw new Exn.Bad("no mailbox selected");
             int[] messages = selected.list();
             for(int i=0; i<messages.length; i++) {
                 Message m = selected.get(messages[i]);
@@ -154,62 +153,58 @@ public class IMAP extends MessageProtocol {
             String literal = q();
             try {
                 Message message = new Message(null, null, new LineReader(new StringReader(literal)));
-                if (flags != null)   setFlags(flags, message);
-                //if (arrival != null) message.arrival = arrival;  FIXME
+                if (flags != null) { /* FEATURE */ }
                 selected.add(message);
             } catch (IOException e) {
-                e.printStackTrace();
+                throw new MailException.IOException(e);
             }
         }
 
-        public void setFlags(Token[] t, Message m) { /* FIXME */ }
-
-        // FIXME set
         public void fetch(int[] set, Token t) {
             Token[] tl = null;
             int start = -1, end = -1;
-            boolean peek = false;
+            boolean peek = false, fast = false, all = false, full = false;
             if (t.type == Token.LIST) tl = t.l();
-            else if (t.atom().equals("FULL")) tl = (Token[]) parse("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"); 
-            else if (t.atom().equals("ALL")) tl = (Token[]) parse("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"); 
-            else if (t.atom().equals("FAST")) tl = (Token[]) parse("FLAGS INTERNALDATE RFC822.SIZE"); 
+            else if (t.atom().equals("FULL")) full = true;
+            else if (t.atom().equals("ALL")) all = true;
+            else if (t.atom().equals("FAST")) fast = true;
             // FEATURE: range requests
             StringBuffer reply = new StringBuffer();
-            Message m = null; // FIXME
-            for (int i=0; i<tl.length; i++) {
-                String s = tl[i].atom();
-                if (s.startsWith("BODY.PEEK"))                 { peek = true; s = "BODY" + s.substring(9); }
-                if (s.startsWith("BODY.1"))                      s = "BODY" + s.substring(6);
-                if (s.indexOf('<') != -1) {
-                    String range = s.substring(s.indexOf('<') + 1, s.indexOf('>'));
-                    s = s.substring(0, s.indexOf('<'));
-                    if (range.indexOf('.') == -1) end = Integer.MAX_VALUE;
-                    else {
-                        end = Integer.parseInt(range.substring(range.indexOf('.') + 1));
-                        range = range.substring(0, range.indexOf('.'));
+            for(int j=0; j<set.length; j++) {
+                Message m = selected.get(set[j]);
+                for (int i=0; i<tl.length; i++) {
+                    String s = tl[i].atom();
+                    if (s.startsWith("BODY.PEEK"))                 { peek = true; s = "BODY" + s.substring(9); }
+                    if (s.startsWith("BODY.1"))                      s = "BODY" + s.substring(6);
+                    if (s.indexOf('<') != -1) {
+                        String range = s.substring(s.indexOf('<') + 1, s.indexOf('>'));
+                        s = s.substring(0, s.indexOf('<'));
+                        if (range.indexOf('.') == -1) end = Integer.MAX_VALUE;
+                        else {
+                            end = Integer.parseInt(range.substring(range.indexOf('.') + 1));
+                            range = range.substring(0, range.indexOf('.'));
+                        }
+                        start = Integer.parseInt(range);
                     }
-                    start = Integer.parseInt(range);
+                    if (s.equals("ENVELOPE") || all || full)          reply.append("ENVELOPE " + envelope(m) + " ");
+                    if (s.equals("FLAGS") || full || all || fast)  reply.append("FLAGS (" + flags(m) + ") ");
+                    if (s.equals("INTERNALDATE") || full || all || fast) reply.append("INTERNALDATE "+quotify(m.arrival)+" ");
+                    if (s.equals("RFC822.SIZE") || full || all || fast) reply.append("RFC822.SIZE " + m.rfc822size() + " ");
+                    if (s.equals("RFC822.HEADER") || s.equals("BODY[HEADER]"))
+                        { reply.append("BODY[HEADER] {" + m.allHeaders.length() + "}\r\n"); reply.append(m.allHeaders); }
+                    if (s.equals("RFC822")||s.equals("RFC822.TEXT")||s.equals("BODY")||s.equals("BODY[]")||s.equals("TEXT")||full)
+                        { reply.append("BODY[TEXT] {" + m.body.length() + "}\r\n"); reply.append(m.body); }
+                    if (s.equals("UID"))                        reply.append("UID " + m.uid);
+                    if (s.equals("MIME"))                       throw new Exn.No("FETCH BODY.MIME not supported");
+                    if (s.startsWith("BODY[HEADER.FIELDS"))     throw new Exn.No("partial headers not supported");
+                    if (s.startsWith("BODY[HEADER.FIELDS.NOT")) throw new Exn.No("partial headers not supported");
+                    if (s.equals("BODYSTRUCTURE"))
+                        reply.append("(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" " +
+                                     m.rfc822size()+" "+ m.numLines() +")");
                 }
-                if (s.equals("ENVELOPE"))                        reply.append("ENVELOPE " + envelope(m) + " ");
-                else if (s.equals("FLAGS"))                      reply.append("FLAGS (" + flags(m) + ") ");
-                else if (s.equals("INTERNALDATE"))               reply.append("INTERNALDATE " + quotify(m.arrival) + " ");
-                else if (s.equals("RFC822.SIZE"))                reply.append("RFC822.SIZE " + m.rfc822size() + " ");
-                else if (s.equals("RFC822.HEADER") || s.equals("BODY[HEADER]"))
-                    { reply.append("BODY[HEADER] {" + m.allHeaders.length() + "}\r\n"); reply.append(m.allHeaders); }
-                else if (s.equals("RFC822")||s.equals("RFC822.TEXT")||s.equals("BODY") || s.equals("BODY[]") || s.equals("TEXT"))
-                    { reply.append("BODY[TEXT] {" + m.body.length() + "}\r\n"); reply.append(m.body); }
-                else if (s.equals("UID"))                        reply.append("UID " + m.uid);
-                else if (s.equals("MIME"))                       throw new No("FETCH BODY.MIME not supported");
-                else if (s.startsWith("BODY[HEADER.FIELDS"))     throw new No("partial headers not supported");
-                else if (s.startsWith("BODY[HEADER.FIELDS.NOT")) throw new No("partial headers not supported");
-                else if (s.equals("BODYSTRUCTURE"))
-                    reply.append("(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" " +
-                                 m.rfc822size()+" "+ m.numLines() +")");
-                else if (s.startsWith("BODY"))                   throw new No("FETCH BODY[*] not supported");
-                else                                             throw new Bad("unrecognized FETCH argument \"" + s + "\"");
+                star(m.messageNum + " FETCH (" + reply.toString() + ")");
+                // FEATURE set seen flag if not BODY.PEEK
             }
-            star(m.messageNum + " FETCH (" + reply.toString() + ")");
-            // FEATURE set seen flag if not BODY.PEEK
         }
 
         public void store(int[] messages, String what, Token[] flags) {
@@ -229,13 +224,14 @@ public class IMAP extends MessageProtocol {
             }
         }
 
-        public boolean handleRequest(LineReader r, PrintWriter pw) throws IOException {
+        public boolean handleRequest() throws IOException {
+            LineReader lr = new LineReader(r);
             pw.println("* OK " + vhost + " " + IMAP.class.getName() + " IMAP4 v0.1 server ready");
             while(true) {
                 boolean uid = false;
-                String s = r.readLine();
+                String s = lr.readLine();
                 if (s.indexOf(' ') == -1) { pw.println("* BAD Invalid tag"); continue; }
-                String tag = atom();
+                tag = atom();
                 String command = atom();
                 if (command.equals("UID"))             { uid = true; command = atom(); }
                 if (command.equals("AUTHENTICATE"))    { login(astring(), astring()); }
@@ -246,63 +242,63 @@ public class IMAP extends MessageProtocol {
                 else if (command.equals("LOGOUT"))     { logout(); conn.close(); return false; }
                 else if (command.equals("RENAME"))       rename(mailbox(), atom());
                 else if (command.equals("APPEND"))       append(mailbox(), token()); 
-                //else if (command.equals("EXAMINE"))      examine(mailbox());   FIXME
+                else if (command.equals("EXAMINE"))      select(astring(), true);
+                else if (command.equals("SELECT"))       select(astring(), false);
                 else if (command.equals("COPY"))         copy(set(), mailbox());
                 else if (command.equals("DELETE"))       delete(mailbox());
-                //else if (command.equals("CREATE"))       create(mailbox());  FIXME
+                else if (command.equals("CHECK"))        check();
+                else if (command.equals("CREATE"))       create(astring());
                 else if (command.equals("STORE"))        store(set(), atom(), l());
                 else if (command.equals("FETCH"))        fetch(set(), token());
                 else if (command.equals("STATUS"))       status(mailbox(), l());
-                else                                     throw new Bad("unrecognized command \"" + command + "\"");
+                else                                     throw new Exn.Bad("unrecognized command \"" + command + "\"");
             }
         }
 
-    static String quotify(String s){return s==null?"NIL":"\""+s.replaceAll("\\\\","\\\\").replaceAll("\"","\\\\\"")+"\"";}
-    static String quotify(Date d) { throw new Error("not implemented"); }
-    static String address(Address a) {return"("+quotify(a.description)+" NIL "+quotify(a.user)+" "+quotify(a.host)+")"; }
-    public static String addressList(Object a) {
-        if (a == null) return "NIL";
-        if (a instanceof Address) return "("+address((Address)a)+")";
-        Address[] aa = (Address[])a;
-        StringBuffer ret = new StringBuffer();
-        ret.append("(");
-        for(int i=0; i<aa.length; i++) { ret.append(aa[i]); if (i < aa.length - 1) ret.append(" "); }
-        ret.append(")");
-        return ret.toString();
-    }
-    static String flags(Message m) {
-        return 
-            (m.deleted  ? "\\Deleted "  : "") +
-            (m.seen     ? "\\Seen "     : "") +
-            (m.flagged  ? "\\Flagged "  : "") +
-            (m.draft    ? "\\Draft "    : "") +
-            (m.answered ? "\\Answered " : "") +
-            (m.recent   ? "\\Recent "   : "");
-    }
-    static String envelope(Message m) {
-        return
-            "(" + quotify(m.arrival.toString()) +
-            " " + quotify(m.subject) +          
-            " " + addressList(m.from) +      
-            " " + addressList(m.headers.get("sender")) +
-            " " + addressList(m.replyto) + 
-            " " + addressList(m.to) + 
-            " " + addressList(m.cc) + 
-            " " + addressList(m.bcc) + 
-            " " + quotify((String)m.headers.get("in-reply-to")) +
-            " " + quotify(m.messageid) +
-            ")";
-    }
-
+        static String quotify(String s){return s==null?"NIL":"\""+s.replaceAll("\\\\","\\\\").replaceAll("\"","\\\\\"")+"\"";}
+        static String quotify(Date d) { return new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss +zzzz").format(d); }
+        static String address(Address a) {return"("+quotify(a.description)+" NIL "+quotify(a.user)+" "+quotify(a.host)+")"; }
+        public static String addressList(Object a) {
+            if (a == null) return "NIL";
+            if (a instanceof Address) return "("+address((Address)a)+")";
+            Address[] aa = (Address[])a;
+            StringBuffer ret = new StringBuffer();
+            ret.append("(");
+            for(int i=0; i<aa.length; i++) { ret.append(aa[i]); if (i < aa.length - 1) ret.append(" "); }
+            ret.append(")");
+            return ret.toString();
+        }
+        static String flags(Message m) {
+            return 
+                (m.deleted  ? "\\Deleted "  : "") +
+                (m.seen     ? "\\Seen "     : "") +
+                (m.flagged  ? "\\Flagged "  : "") +
+                (m.draft    ? "\\Draft "    : "") +
+                (m.answered ? "\\Answered " : "") +
+                (m.recent   ? "\\Recent "   : "");
+        }
+        static String envelope(Message m) {
+            return
+                "(" + quotify(m.arrival.toString()) +
+                " " + quotify(m.subject) +          
+                " " + addressList(m.from) +      
+                " " + addressList(m.headers.get("sender")) +
+                " " + addressList(m.replyto) + 
+                " " + addressList(m.to) + 
+                " " + addressList(m.cc) + 
+                " " + addressList(m.bcc) + 
+                " " + quotify((String)m.headers.get("in-reply-to")) +
+                " " + quotify(m.messageid) +
+                ")";
+        }
 
-        Token[] parse(String s) { /* FIXME */ return null; }
         Query query() {
             String s = null;
             boolean not = false;
             Query q = null;
             while(true) {
                 Token t = token();
-                if (t.type == t.LIST) throw new No("nested queries not yet supported");
+                if (t.type == t.LIST) throw new Exn.No("nested queries not yet supported");
                 else if (t.type == t.SET) return Query.set(t.set());
                 s = t.atom();
                 if (s.equals("NOT")) { not = true; continue; }
@@ -340,7 +336,6 @@ public class IMAP extends MessageProtocol {
             return q;
         }
 
-
         class Token {
             private byte type;
             private final String s;
@@ -358,41 +353,41 @@ public class IMAP extends MessageProtocol {
             public Token(Token[] list) { l = list; s = null; type = LIST; n = 0; }
             public Token(int number) { n = number; l = null; s = null; type = NUMBER; }
 
-            // assumes token is a flag list or a fag
+            // assumes token is a flag list or a flag
             public void setFlags(Message m) { }
             public void addFlags(Message m) { }
             public void deleteFlags(Message m) { }
 
-            public String mailboxPattern() throws Bad {
+            public String mailboxPattern() throws Exn.Bad {
                 if (type == ATOM) return s;
                 if (type == QUOTED) return s;
-                throw new Bad("exepected a mailbox pattern");            
+                throw new Exn.Bad("exepected a mailbox pattern");            
             }
-            public String flag() throws Bad {
-                if (type != ATOM) throw new Bad("expected a flag");
+            public String flag() throws Exn.Bad {
+                if (type != ATOM) throw new Exn.Bad("expected a flag");
                 return s;  // if first char != backslash, it is a keyword-flag
             }
-            public int n() throws Bad {
-                if (type != NUMBER) throw new Bad("expected number");
+            public int n() throws Exn.Bad {
+                if (type != NUMBER) throw new Exn.Bad("expected number");
                 return n;
             }
-            public int nz() throws Bad {
-                if (type != NUMBER) throw new Bad("expected number");
-                if (n == 0) throw new Bad("expected nonzero number");
+            public int nz() throws Exn.Bad {
+                if (type != NUMBER) throw new Exn.Bad("expected number");
+                if (n == 0) throw new Exn.Bad("expected nonzero number");
                 return n;
             }
-            public String  q() throws Bad {
+            public String  q() throws Exn.Bad {
                 if (type == NIL) return null;
-                if (type != QUOTED) throw new Bad("expected qstring");
+                if (type != QUOTED) throw new Exn.Bad("expected qstring");
                 return s;
             }
-            public Token[] l() throws Bad {
+            public Token[] l() throws Exn.Bad {
                 if (type == NIL) return null;
-                if (type != LIST) throw new Bad("expected parenthesized list");
+                if (type != LIST) throw new Exn.Bad("expected parenthesized list");
                 return l;
             }
-            public int[] set() throws Bad {
-                if (type != ATOM) throw new Bad("expected a messageid set");
+            public int[] set() throws Exn.Bad {
+                if (type != ATOM) throw new Exn.Bad("expected a messageid set");
                 Vec ids = new Vec();
                 StringTokenizer st = new StringTokenizer(s, ",");
                 while(st.hasMoreTokens()) {
@@ -412,39 +407,39 @@ public class IMAP extends MessageProtocol {
                     }
                 }
                 int[] ret = new int[ids.size()];
-                //ids.copyInto(ret);  FIXME
+                for(int i=0; i<ret.length; i++) ret[i] = ((Integer)ids.elementAt(i)).intValue();
                 return ret;
             }
-            public Date date() throws Bad {
-                if (type != QUOTED && type != ATOM) throw new Bad("Expected quoted or unquoted date");
+            public Date date() throws Exn.Bad {
+                if (type != QUOTED && type != ATOM) throw new Exn.Bad("Expected quoted or unquoted date");
                 try { return new SimpleDateFormat("dd-MMM-yyyy").parse(s);
-                } catch (ParseException p) { throw new Bad("invalid date format; " + p); }
+                } catch (ParseException p) { throw new Exn.Bad("invalid date format; " + p); }
             }
-            public Date datetime() throws Bad {
-                if (type != QUOTED && type != ATOM) throw new Bad("Expected quoted or unquoted datetime");
+            public Date datetime() throws Exn.Bad {
+                if (type != QUOTED && type != ATOM) throw new Exn.Bad("Expected quoted or unquoted datetime");
                 try { return new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss +zzzz").parse(s);
-                } catch (ParseException p) { throw new Bad("invalid datetime format; " + p); }
+                } catch (ParseException p) { throw new Exn.Bad("invalid datetime format; " + p); }
             }
-            public String nstring() throws Bad {
+            public String nstring() throws Exn.Bad {
                 if (type == NIL) return null;
                 if (type == QUOTED) return s;
-                throw new Bad("expected NIL or string");
+                throw new Exn.Bad("expected NIL or string");
             }
-            public String astring() throws Bad {
+            public String astring() throws Exn.Bad {
                 if (type == ATOM) return s;
                 if (type == QUOTED) return s;
-                throw new Bad("expected atom or string");
+                throw new Exn.Bad("expected atom or string");
             }
-            public String atom() throws Bad {
-                if (type != ATOM) throw new Bad("expected atom");
+            public String atom() throws Exn.Bad {
+                if (type != ATOM) throw new Exn.Bad("expected atom");
                 for(int i=0; i<s.length(); i++) {
                     char c = s.charAt(i);
                     if (c == '(' || c == ')' || c == '{' || c == ' ' || c == '%' || c == '*')
-                        throw new Bad("invalid char in atom: " + c);
+                        throw new Exn.Bad("invalid char in atom: " + c);
                 }
                 return s;
             }
-            public Mailbox mailbox() throws Bad, IOException {
+            public Mailbox mailbox() throws Exn.Bad {
                 if (type == BAREWORD && s.toLowerCase().equals("inbox")) return getMailbox("INBOX", false);
                 return getMailbox(astring(), false);
             }
@@ -491,6 +486,7 @@ public class IMAP extends MessageProtocol {
                         else if (c == '\"') break;
                         else sb.append(c);
                     }
+                    return new Token(sb.toString());
                 } else if (c == ')') {
                     return null;
                 } else if (c == '(') {
@@ -504,26 +500,25 @@ public class IMAP extends MessageProtocol {
                     if (c == ' ' || c == '\"' || c == '(' || c == ')' || c == '{') break;
                     sb.append(getc());
                 }
-                //return toks.toArray();
-                return null; // FIXME
+                return new Token(sb.toString());
             } catch (IOException e) {
                 e.printStackTrace();
                 return null;
             }
         }
 
-        public String mailboxPattern() throws Bad { return token().mailboxPattern(); }
-        public String flag() throws Bad { return token().flag(); }
-        public int n() throws Bad { return token().n(); }
-        public int nz() throws Bad { return token().nz(); }
-        public String  q() throws Bad { return token().q(); }
-        public Token[] l() throws Bad { return token().l(); }
-        public int[] set() throws Bad { return token().set(); }
-        public Date date() throws Bad { return token().date(); }
-        public Date datetime() throws Bad { return token().datetime(); }
-        public String nstring() throws Bad { return token().nstring(); }
-        public String astring() throws Bad { return token().astring(); }
-        public String atom() throws Bad { return token().atom(); }
-        public Mailbox mailbox() throws Bad, IOException { return token().mailbox(); }
+        public String mailboxPattern() throws Exn.Bad { return token().mailboxPattern(); }
+        public String flag() throws Exn.Bad { return token().flag(); }
+        public int n() throws Exn.Bad { return token().n(); }
+        public int nz() throws Exn.Bad { return token().nz(); }
+        public String  q() throws Exn.Bad { return token().q(); }
+        public Token[] l() throws Exn.Bad { return token().l(); }
+        public int[] set() throws Exn.Bad { return token().set(); }
+        public Date date() throws Exn.Bad { return token().date(); }
+        public Date datetime() throws Exn.Bad { return token().datetime(); }
+        public String nstring() throws Exn.Bad { return token().nstring(); }
+        public String astring() throws Exn.Bad { return token().astring(); }
+        public String atom() throws Exn.Bad { return token().atom(); }
+        public Mailbox mailbox() throws Exn.Bad, IOException { return token().mailbox(); }
     }
 }