change num() to imapNumber() and nntpNumber(), add comments about semantics
[org.ibex.mail.git] / src / org / ibex / mail / protocol / IMAP.java
index a09f4b9..5b98105 100644 (file)
@@ -6,7 +6,6 @@ package org.ibex.mail.protocol;
 import org.ibex.io.*;
 import org.ibex.crypto.*;
 import org.ibex.jinetd.Listener;
-import org.ibex.jinetd.Worker;
 import org.ibex.mail.*;
 import org.ibex.util.*;
 import org.ibex.mail.target.*;
@@ -42,6 +41,13 @@ public class IMAP {
     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 {
@@ -121,6 +127,10 @@ public class IMAP {
         public void lsub(String start, String ref) { list(start, ref, true); }
         public void list(String start, String ref) { list(start, ref, false); }
         public void list(String start, String ref, boolean lsub) {
+
+            // FIXME this might be wrong
+            if (ref.equalsIgnoreCase("inbox")) { client.list(sep,"inbox",lsub,false); return; }
+
             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;
@@ -128,19 +138,22 @@ public class IMAP {
             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;
-                boolean phantom = mailbox(kid, false).phantom();
-                while(true) {
-                    if (pre.length() == 0) {
-                        if (s.length() == 0)       client.list(sep, kid, lsub, phantom);
-                    } else switch(pre.charAt(0)) {
-                        case sep:        if (s.length() == 0) list(kid, pre.substring(1), lsub);                    break;
-                        case '%':        client.list(sep,kid,lsub,phantom);pre=pre.substring(1); s = "";            continue;
-                        case '*':        client.list(sep,kid,lsub,phantom);list(kid,pre,lsub);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;
+                Mailbox phant = mailbox(kid, false, false);
+                if (phant != null) {
+                    boolean phantom = phant.phantom();
+                    while(true) {
+                        if (pre.length() == 0) {
+                            if (s.length() == 0)       client.list(sep, kid, lsub, phantom);
+                        } else switch(pre.charAt(0)) {
+                            case sep:        if (s.length() == 0) list(kid, pre.substring(1), lsub);                    break;
+                            case '%':        client.list(sep,kid,lsub,phantom);pre=pre.substring(1); s = "";            continue;
+                            case '*':        client.list(sep,kid,lsub,phantom);list(kid,pre,lsub);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;
                     }
-                    break;
                 }
             }
         }
@@ -166,7 +179,7 @@ public class IMAP {
         public void delete(Mailbox m) { if (!m.equals(inbox)) m.destroy(false); else throw new Bad("can't delete inbox"); }
         public void create(String m) { mailbox(m, true, false); }
         public void append(String m,int f,Date a,String b) { try {
-            mailbox(m,false).add(Message.newMessage(new Stream(b)),f|Mailbox.Flag.RECENT);
+            mailbox(m,false).add(Message.newMessage(new Fountain.StringFountain(b)),f|Mailbox.Flag.RECENT);
         } catch (Message.Malformed e) { throw new No(e.getMessage()); } }
         public void check() { }
         public void noop() { }
@@ -188,15 +201,13 @@ public class IMAP {
         public int count(String mailbox)       { return mailbox(mailbox, false).count(Query.all()); }
         public int count()                     { return selected().count(Query.all()); }
         public int uidNext(String mailbox)     { return mailbox(mailbox, false).uidNext(); }
-        public int uidValidity(String mailbox) { return mailbox(mailbox, false).uidValidity(); }
-        public void select(String mailbox, boolean examineOnly) {
-           selected = mailbox(mailbox, false);
-       }
+        public int uidValidity(String mailbox) { return Math.abs(mailbox(mailbox, false).uidValidity()); }
+        public void select(String mailbox, boolean examineOnly) { selected = mailbox(mailbox, false); }
 
         public int[] search(Query q, boolean uid) {
             Vec.Int vec = new Vec.Int();
             for(Mailbox.Iterator it = selected().iterator(q); it.next();) {
-                vec.addElement(uid ? it.uid() : it.num());
+                vec.addElement(uid ? it.uid() : it.imapNumber());
                 it.recent(false);
             }
             return vec.dump();
@@ -214,7 +225,7 @@ public class IMAP {
                 else if (style == 0) it.setFlags(flags);
                 else if (style == 1) it.addFlags(flags);
                 it.recent(recent);
-                if (!silent) client.fetch(it.num(), it.flags(), -1, null, it.uid());
+                if (!silent) client.fetch(it.imapNumber(), it.flags(), -1, null, it.uid());
             }
         }            
         public void rename(String from0, String to) {
@@ -227,8 +238,8 @@ public class IMAP {
             for(Mailbox.Iterator it = selected().iterator(q); it.next(); ) {
                 Message message = ((spec & (BODYSTRUCTURE | ENVELOPE | INTERNALDATE | FIELDS | FIELDSNOT | RFC822 |
                                             RFC822TEXT | RFC822SIZE | HEADERNOT | HEADER)) != 0) ? it.cur() : null;
-                int size = message == null ? 0 : message.size();
-                client.fetch(it.num(), it.flags(), size, message, it.uid());
+                int size = message == null ? 0 : message.getLength();
+                client.fetch(it.imapNumber(), it.flags(), size, message, it.uid());
                 it.recent(false);
             }
         }
@@ -238,7 +249,7 @@ public class IMAP {
     // Single Session Handler //////////////////////////////////////////////////////////////////////////////
 
     /** takes an IMAP.Server and exposes it to the world as an IMAP server on a TCP socket */
-    public static class Listener implements Worker, Client {
+    public static class Listener implements Client {
         String selectedName = null;
         Mailbox inbox = null, root = null;
         Server api;
@@ -273,7 +284,7 @@ public class IMAP {
             parser = new Parser(conn);
             conn.setTimeout(30 * 60 * 1000);
             println("* OK " + conn.vhost + " " + IMAP.class.getName() + " IMAP4rev1 [RFC3501] v" + version + " server ready");
-            for(String tag = null;; newline()) try {
+            for(String tag = null;;) try {
                 conn.flush();
                 boolean uid = false;
                 tag = null; Parser.Token tok = token(); if (tok == null) return; tag = tok.astring();
@@ -290,7 +301,6 @@ public class IMAP {
                     case SUBSCRIBE:    api.subscribe(token().astring()); break;
                     case UNSUBSCRIBE:  api.unsubscribe(token().astring()); break;
                     case RENAME:       api.rename(token().astring(), token().astring()); break;
-                    case COPY:         selected(); api.copy(Query.set(uid, token().set(maxn(uid))), token().astring()); break;
                     case DELETE:       api.delete(token().atom()); break;
                     case CHECK:        selected(); api.check(); break;
                     case NOOP:         api.noop(); break;
@@ -298,8 +308,13 @@ public class IMAP {
                     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(Query.set(lastuid=uid, token().set(maxn(uid))),
+                    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 COPY:         selected(); api.copy(uid
+                                                            ? Query.uid(token().set(maxn(uid)))
+                                                            : Query.imapNumber(token().set(maxn(uid))), token().astring()); break;
                     case SEARCH: {
                         selected();
                         int[] result = api.search(query(maxn(uid)), uid);
@@ -346,7 +361,7 @@ public class IMAP {
                         break; }
                     case STORE: {
                         selected();
-                        Query q = uid ? Query.uid(token().set(maxn(uid))) : Query.num(token().set(maxn(uid)));
+                        Query q = uid ? Query.uid(token().set(maxn(uid))) : Query.imapNumber(token().set(maxn(uid)));
                         String s = token().atom().toUpperCase();
                         int flags = token().flags();
                         if (s.equals("FLAGS"))              api.setFlags(q,    flags, uid, false);
@@ -361,9 +376,14 @@ public class IMAP {
                 }
                 println(tag+" OK "+command+" Completed. " +
                         (commandKey == LOGIN ? ("[CAPABILITY "+Printer.join(" ", api.capability())+"]") : ""));
-                Log.error("[imap]", conn.dumpLog());
-            } catch (Server.Bad b) { println(tag==null ? "* BAD Invalid tag":(tag + " Bad " + b.toString()));
-            } catch (Server.No n)  { println(tag==null?"* BAD Invalid tag":(tag+" No "  + n.toString())); }
+                try {
+                    newline();
+                } catch (Stream.EOF e) {
+                    Log.info(this, "connection closed");
+                    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); }
         }
 
         private Parser.Token[] lastfetch = null; // hack
@@ -437,14 +457,14 @@ public class IMAP {
                 } 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(m.body));}
-                } else if (s.equals("RFC822.HEADER")){ spec|=HEADER;if(e){r.append(" ");r.append(Printer.qq(m.headers.raw+"\r\n"));}
-                } else if (s.equals("RFC822.SIZE")) {  spec|=RFC822SIZE;   if(e){r.append(" ");r.append(m.size());}
+                } 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.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.num(new int[] { num, num }), Mailbox.Flag.SEEN, false, false);
+                    //else if (e) api.addFlags(Query.imapNumber(new int[] { num, num }), Mailbox.Flag.SEEN, false, false);
                     if (i >= t.length - 1 || t[i+1].type != Parser.Token.LIST) {
                        spec |= BODYSTRUCTURE;
                        if (e) { r.append(" "); r.append(Printer.bodystructure(m)); } continue;
@@ -455,10 +475,10 @@ public class IMAP {
                     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.raw+"\r\n"+m.body; }
-                    else if (s.equals("") || s.equals("1")) { spec |= RFC822TEXT;   if(e) payload = m.headers.raw+"\r\n"+m.body; }
-                    else if (s.equals("TEXT"))              { spec |= RFC822TEXT;   if(e) payload = m.body; }
-                    else if (s.equals("HEADER"))            { spec |= HEADER;       if(e) payload = m.headers.raw+"\r\n"; }
+                    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); }
                     else if (s.equals("MIME")) {              throw new Server.Bad("MIME not supported"); }
@@ -488,7 +508,7 @@ public class IMAP {
             if (!negate) {
                 if(e) for(int j=0; j<headers.length; j++) {
                     r.append(headers[j] + (j<headers.length-1?" ":""));
-                    if (m.headers.gets(headers[j]) != null) payload += headers[j]+": "+m.headers.gets(headers[j])+"\r\n";
+                    if (m.headers.get(headers[j]) != null) payload += headers[j]+": "+m.headers.get(headers[j])+"\r\n";
                 }
             } else {
                throw new Server.No("HEADERS.NOT temporarily disaled");
@@ -497,7 +517,7 @@ public class IMAP {
                 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.gets(key)+"\r\n";
+                    payload += key + ": " + m.headers.get(key)+"\r\n";
                 } }
                */
             }
@@ -547,7 +567,7 @@ public class IMAP {
                 Parser.Token t = token(false);
                 if (t == null) break;
                 if (t.type == t.LIST) throw new Server.No("nested queries not yet supported FIXME");
-                else if (t.type == t.SET) return Query.num(t.set(max));
+                else if (t.type == t.SET) return Query.imapNumber(t.set(max));
                 s = t.atom().toUpperCase();
                 if (s.equals("NOT"))   return Query.not(query(max, maxuid));
                 if (s.equals("OR"))    return Query.or(query(max, maxuid), query(max, maxuid));    // FIXME parse rest of list
@@ -690,12 +710,7 @@ public class IMAP {
             }
         }
 
-        public void newline() {
-           while (stream.peekc() == '\r' || stream.peekc() == '\n' || stream.peekc() == ' ') {
-               for(char c = stream.peekc(); c == ' ';) { stream.getc(); c = stream.peekc(); };
-               for(char c = stream.peekc(); c == '\r' || c == '\n';) { stream.getc(); c = stream.peekc(); };
-           }
-        }
+        public void newline() { stream.readln(); }
 
         public Token token() { return token(true); }
         public Token token(boolean freak) {
@@ -787,7 +802,7 @@ public class IMAP {
         }
         static String bodystructure(Message m) {
             // FIXME
-            return "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" "+m.size()+" "+m.lines+")";
+            return "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" "+m.getLength()+" "+m.getNumLines()+")";
         }
         static String message(Message m) { return m.toString(); }
         static String date(Date d) { return "\""+d.toString()+"\""; }
@@ -796,12 +811,12 @@ public class IMAP {
                 "(" + quotify(m.arrival.toString()) +
                 " " + quotify(m.subject) +          
                 " " + addressList(m.from) +      
-                " " + addressList(m.headers.gets("sender")) +
+                " " + addressList(m.headers.get("sender")) +
                 " " + addressList(m.replyto) + 
                 " " + addressList(m.to) + 
                 " " + addressList(m.cc) + 
                 " " + addressList(m.bcc) + 
-                " " + quotify((String)m.headers.gets("in-reply-to")) +
+                " " + quotify((String)m.headers.get("in-reply-to")) +
                 " " + quotify(m.messageid) +
                 ")";
         }