basically works completely with mozilla imap
authoradam <adam@megacz.com>
Mon, 14 Jun 2004 02:02:08 +0000 (02:02 +0000)
committeradam <adam@megacz.com>
Mon, 14 Jun 2004 02:02:08 +0000 (02:02 +0000)
darcs-hash:20040614020208-5007d-7ecc50b94fd9decb4b466b907c74b1ba373733e3.gz

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

index 24a56c4..8a2319c 100644 (file)
@@ -50,13 +50,13 @@ public class IMAP {
         public int count(String mailbox);
         public int uidNext(String mailbox);
         public int uidValidity(String mailbox);
+        public int[] search(Query q, boolean uid);
         public void rename(String from, String to);
         public void select(String mailbox, boolean examineOnly);
 
         public static interface Client {
             public void expunge(int uid);
             public void list(char separator, String mailbox);
-            public void lsub(char separator, String mailbox);
             public void fetch(int uidnum, int flags, int size, Message m);  // m may be null or incomplete
         }
 
@@ -108,6 +108,7 @@ public class IMAP {
         public void list(String start, String ref, Client client) {
             if (ref.length() == 0 && start.length() == 0) { client.list(sep, ""); return; }
             while (start.endsWith(""+sep)) start = start.substring(0, start.length() - 1);
+            if (ref.endsWith("%")) ref = ref + sep;
             String[] children = (start.length() == 0 ? root : getMailbox(start, false)).children();
             for(int i=0; i<children.length; i++) {
                 String s = children[i], pre = ref, kid = start + (start.length() > 0 ? sep+"" : "") + s;                
@@ -116,7 +117,7 @@ public class IMAP {
                         if (s.length() == 0)       client.list(sep, kid);
                     } else switch(pre.charAt(0)) {
                         case sep:        if (s.length() == 0) list(kid, pre.substring(1), client);          break;
-                        case '%':        client.list(sep, kid); pre=pre.substring(1); list(kid, pre, client);       break;
+                        case '%':        client.list(sep, kid); pre = pre.substring(1); s = "";    continue;
                         case '*':        client.list(sep, kid); list(kid, pre, client); pre = pre.substring(1);     break;
                         default:         if (s.length()==0)                                                 break;
                                          if (s.charAt(0) != pre.charAt(0))                                  break;
@@ -164,6 +165,14 @@ public class IMAP {
         public void setFlags(Query q, int f, boolean uid, Client c) { doFlags(q, f, c, uid, 0); }
         public void addFlags(Query q, int f, boolean uid, Client c) { doFlags(q, f, c, uid, 1); }
         public void removeFlags(Query q, int f, boolean uid, Client c) { doFlags(q, f, c, uid, -1); }
+        public int[] search(Query q, boolean uid) {
+            Vec vec = new Vec();
+            for(Mailbox.Iterator it = selected.iterator(q); it.next(); )
+                vec.addElement(new Integer(uid ? it.uid() : it.num()));
+            int[] ret = new int[vec.size()];
+            for(int i=0; i<ret.length; i++) ret[i] = ((Integer)vec.elementAt(i)).intValue();
+            return ret;
+        }
         private void doFlags(Query q, int flags, Client c, boolean uid, int style) {
             for(Mailbox.Iterator it = selected.iterator(q);it.next();) {
                 switch(style) {
@@ -209,13 +218,13 @@ public class IMAP {
         // client callbacks
         private Token[] lastfetch = null; // hack
         private boolean lastuid = false;  // hack
-        public void fetch(int uidnum, int flags, int size, Message m) {
-            fetch(null, lastfetch, uidnum, flags, size, lastuid, m); }
+        public void fetch(int uidnum, int flags, int size, Message m) { fetch(null, lastfetch, uidnum, flags, size, lastuid, m); }
         public void expunge(int uid) { star(uid + " EXPUNGE"); }
-        public void list(char separator, String mailbox) { star("LIST \"" + separator + "\" \""+mailbox+"\""); }
-        public void lsub(char separator, String mailbox) { star("LSUB \"" + separator + "\" \""+mailbox+"\""); }
+        public void list(char separator, String mailbox) { star((lsub ? "LSUB":"LIST")+" () \"" + separator + "\" \""+mailbox+"\""); }
         public void star(String s)    { println("* " + s); }
 
+        boolean lsub = false;
+
         public boolean handleRequest() throws IOException {
             star("OK " + vhost + " " + IMAP.class.getName() + " IMAP4rev1 [RFC3501] v" + version + " server ready");
             for(String tag = null;; newline()) try {
@@ -228,9 +237,9 @@ public class IMAP {
                     case AUTHENTICATE: api.login(token().astring(), token().astring()); break;
                     case LOGIN:        api.login(token().astring(), token().astring()); break;
                     case LOGOUT:       api.logout(); star("BYE"); conn.close(); return false;
-                    case LIST:         api.list(token().q(), token().q(), this); break;
-                    case LSUB:         api.lsub(token().q(), token().q(), this); break;
-                    case SUBSCRIBE:    api.subscribe(token().atom()); break;
+                    case LIST:         lsub = false; api.list(token().q(), token().q(), this); break;
+                    case LSUB:         lsub = true; api.lsub(token().q(), token().q(), this); break;
+                    case SUBSCRIBE:    api.subscribe(token().q()); break;
                     case UNSUBSCRIBE:  api.unsubscribe(token().atom()); break;
                     case CAPABILITY:   star("CAPABILITY " + Printer.join(" ", api.capability())); break;
                         //case ID:           id(); break;
@@ -244,7 +253,8 @@ public class IMAP {
                     case EXPUNGE:      api.expunge(this); break;
                     case UNSELECT:     api.unselect(); break;
                     case CREATE:       api.create(token().astring()); break;
-                    case FETCH:        fetch(Query.set(lastuid = uid, token().set()), token().l(), 0, 0, 0, uid, null); break;
+                    case FETCH:        lastuid=uid; fetch(Query.set(uid, token().set()), token().l(), 0, 0, 0, uid, null); break;
+                    case SEARCH:       star("SEARCH " + Printer.join(api.search(query(), uid))); break;
                     case SELECT: {
                         String mailbox = token().astring();
                         api.select(mailbox, false);
@@ -266,6 +276,7 @@ public class IMAP {
                             if (s.equals("UNSEEN"))      response += "UIDNEXT "     + api.uidNext(mailbox);
                         }
                         star("STATUS " + selectedName + " (" + response + ")");
+                        break;
                     }
                     case APPEND: { 
                         String m = token().atom();
@@ -310,11 +321,12 @@ public class IMAP {
             String[] headers = null;
             int start = -1, end = -1;
             StringBuffer r = new StringBuffer();       // reply
-            if(e){ r.append(uidnum); r.append(" FETCH ("); }
+            if (e) { r.append(uidnum); r.append(" FETCH ("); }
+            int initlen = r.length();
             for(int i=0; i<t.length; i++) {
-                if (i>0) r.append(" ");
+                if (r.length() > initlen) r.append(" ");
                 String s = t[i].s.toUpperCase();
-                r.append(s);
+                if (!uid || !s.equals("UID")) r.append(s.equals("BODY.PEEK")?"BODY":s);
                 if (s.equals("BODYSTRUCTURE")) {        spec|=BODYSTRUCTURE;if(e){r.append(" ");r.append(Printer.bodystructure(m));}
                 } else if (s.equals("ENVELOPE")) {      spec|=ENVELOPE; if(e){r.append(" "); r.append(Printer.envelope(m));}
                 } else if (s.equals("FLAGS")) {         spec|=FLAGS; if(e){r.append(" "); r.append(Printer.flags(flags));}
@@ -323,12 +335,18 @@ public class IMAP {
                 } else if (s.equals("RFC822.TEXT")) {   spec|=RFC822TEXT; if(e){r.append(" ");r.append(Printer.qq(m.body));}
                 } else if (s.equals("RFC822.HEADER")) { spec|=RFC822HEADER; if(e){r.append(" ");r.append(Printer.qq(m.allHeaders));}
                 } else if (s.equals("RFC822.SIZE")) {   spec|=RFC822SIZE; if(e){r.append(" "); r.append(m.rfc822size());}
+                } else if (s.equals("UID")) {
+                    spec|=UID;
+                    if (e && !uid) { r.append(" "); r.append(uidnum); }
+                    /*FIXME: UID in nonuid fetch*/
                 } else if (s.equals("BODY.PEEK") || s.equals("BODY")) {
                     if (s.equalsIgnoreCase("BODY.PEEK")) spec |= PEEK;
                     String payload = "";
                     if (i<t.length - 1 && (t[i+1].type == t[i].LIST)) {
                         i++;
+                        r.append("[");
                         String s2 = t[i].l()[0].s.toUpperCase();
+                        r.append(s2);
                         if (t[i].l().length == 0)                { spec |= RFC822TEXT;   if(e) payload = m.body; }
                         else if (s2.equals(""))                  { spec |= RFC822TEXT;   if(e) payload = m.body; }
                         else if (s2.equals("TEXT"))              { spec |= RFC822TEXT;   if(e) payload = m.body; }
@@ -336,20 +354,29 @@ public class IMAP {
                         else if (s2.equals("HEADER.FIELDS"))     {
                             spec |= RFC822HEADER;
                             headers = t[i].l()[1].sl();
-                            if(e) for(int j=0; j<headers.length; j++) payload += headers[j] + ": " + m.headers.get(headers[j]);
+                            if (e) r.append(" (");
+                            if(e) for(int j=0; j<headers.length; j++) {
+                                r.append(headers[j] + (j<headers.length-1?" ":""));
+                                payload += headers[j]+": "+m.headers.get(headers[j])+"\r\n";
+                            }
+                            if (e) r.append(")]");
                         } else if (s2.equals("HEADER.FIELDS.NOT")) {
                             spec |= RFC822HEADER|NEGATEHEADERS;
                             headers = t[i].l()[1].sl();
+                            if (e) r.append(" (");
+                            if (e) for(int j=0; j<headers.length; j++)
+                                r.append(headers[j] + (j<headers.length-1?" ":""));
+                            if (e) r.append(")]");
                             if(e) { OUTER: for(Enumeration x=m.headers.keys(); x.hasMoreElements();) {
                                 String key = (String)x.nextElement();
                                 for(int j=0; j<headers.length; j++) if (key.equalsIgnoreCase(headers[j])) continue OUTER;
-                                payload += key + ": " + m.headers.get(key);
+                                payload += key + ": " + m.headers.get(key)+"\r\n";
                             } }
                         } else if (s2.equals("MIME")) {            throw new API.Bad("MIME not supported");
                         } else throw new API.Bad("unknown section type " + s2);
                         if (i<t.length - 1 && (t[i+1].s != null && t[i+1].s.startsWith("<"))) {
                             i++;
-                            String s3 = t[i].s.substring(1, s.indexOf('>'));
+                            String s3 = t[i].s.substring(1, t[i].s.indexOf('>'));
                             int dot = s3.indexOf('.');
                             start = dot == -1 ? Integer.parseInt(s3) : Integer.parseInt(s3.substring(0, s3.indexOf('.')));
                             end = dot == -1 ? -1 : Integer.parseInt(s3.substring(s3.indexOf('.') + 1));
@@ -364,6 +391,8 @@ public class IMAP {
                 }
             }
             if (e) {
+                // FIXME hack
+                if (uid) r.append(" UID " + uidnum);
                 r.append(")");
                 star(r.toString());
             } else {
@@ -397,6 +426,7 @@ public class IMAP {
         private static final int FETCH = 22;       static { commands.put("FETCH", new Integer(FETCH)); }
         private static final int APPEND = 23;      static { commands.put("APPEND", new Integer(APPEND)); }
         private static final int STORE = 24;       static { commands.put("STORE", new Integer(STORE)); }
+        private static final int SEARCH = 25;      static { commands.put("SEARCH", new Integer(SEARCH)); }
     }
 
     public static class Parser {
@@ -410,7 +440,15 @@ public class IMAP {
             this.pw = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
             this.r = new PushbackReader(new InputStreamReader(this.is));
         }
-        protected void println(String s) { pw.println(s); pw.flush(); }
+        protected void println(String s) {
+            pw.println(s);
+            pw.flush();
+            if (log.length() > 0) {
+                System.err.println("\033[31;1m"+log+"\033[33;0m");
+                log = "";
+            }
+            System.err.println("\033[33;1m"+s+"\033[33;0m");
+        }
         protected void flush() { pw.flush(); }
         Query query() {
             String s = null;
@@ -429,6 +467,7 @@ public class IMAP {
             if (s.startsWith("UN"))        { not = true; s = s.substring(2); }
             if (s.equals("ANSWERED"))        q = Query.answered();
             else if (s.equals("DELETED"))    q = Query.deleted();
+            else if (s.equals("ALL"))        q = Query.all();
             else if (s.equals("DRAFT"))      q = Query.draft();
             else if (s.equals("FLAGGED"))    q = Query.flagged();
             else if (s.equals("RECENT"))     q = Query.recent();
@@ -548,9 +587,14 @@ public class IMAP {
             }
         }
 
+        String log = "";
         public char getc() throws IOException {
             int ret = r.read();
             if (ret == -1) throw new EOFException();
+            if (ret == '\n') {
+                System.err.println("\033[31;1m"+log+"\033[33;0m");
+                log = "";
+            } else if (ret != '\r') log += (char)ret;
             return (char)ret;
         }
         public  char peekc() throws IOException {
@@ -640,7 +684,7 @@ public class IMAP {
             return ret.toString();
         }
         static String flags(Mailbox.Iterator it) {
-            return 
+            return
                 (it.deleted()  ? "\\Deleted "  : "") +
                 (it.seen()     ? "\\Seen "     : "") +
                 (it.flagged()  ? "\\Flagged "  : "") +
@@ -649,13 +693,15 @@ public class IMAP {
                 (it.recent()   ? "\\Recent "   : "");
         }
         static String flags(int flags) {
-            return 
+            String ret = "(" +
                 (((flags & Mailbox.Flag.DELETED) == Mailbox.Flag.DELETED) ? "\\Deleted "  : "") +
                 (((flags & Mailbox.Flag.SEEN) == Mailbox.Flag.SEEN)    ? "\\Seen "     : "") +
                 (((flags & Mailbox.Flag.FLAGGED) == Mailbox.Flag.FLAGGED) ? "\\Flagged "  : "") +
                 (((flags & Mailbox.Flag.DRAFT) == Mailbox.Flag.DRAFT)   ? "\\Draft "    : "") +
                 (((flags & Mailbox.Flag.ANSWERED) == Mailbox.Flag.ANSWERED)? "\\Answered " : "") +
                 (((flags & Mailbox.Flag.RECENT) == Mailbox.Flag.RECENT)  ? "\\Recent "   : "");
+            if (ret.endsWith(" ")) ret = ret.substring(0, ret.length() - 1);
+            return ret + ")";
         }
         static String bodystructure(Message m) {
             // FIXME
@@ -691,6 +737,14 @@ public class IMAP {
             return ret.toString();
         }
         
+        private static String join(int[] nums) {
+            StringBuffer ret = new StringBuffer();
+            for(int i=0; i<nums.length; i++) {
+                ret.append(nums[i]);
+                if (i<nums.length-1) ret.append(' ');
+            }
+            return ret.toString();
+        }
         private static String join(String delimit, String[] stuff) {
             StringBuffer ret = new StringBuffer();
             for(int i=0; i<stuff.length; i++) {
@@ -707,7 +761,8 @@ public class IMAP {
     /** simple listener for testing purposes */
     public static void main(String[] args) throws Exception {
         ServerSocket ss = new ServerSocket(143);
-        for(final Socket s = ss.accept();;)
+        for(;;) {
+            final Socket s = ss.accept();
             new Thread() { public void run() { try {
                 final Mailbox root = FileBasedMailbox.getFileBasedMailbox(Mailbox.STORAGE_ROOT+File.separatorChar+"imap", true);
                 new Server(s, root,
@@ -717,5 +772,6 @@ public class IMAP {
                                     return null;
                                 } } ).handleRequest();
             } catch (Exception e) { e.printStackTrace(); } } }.start();
+        }
     }
 }