if no inbox, use root
[org.ibex.mail.git] / src / org / ibex / mail / protocol / IMAP.java
index c82fd30..45ff1dc 100644 (file)
@@ -1,8 +1,8 @@
 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.net.*;
 import org.ibex.mail.*;
 import org.ibex.util.*;
 import org.ibex.mail.target.*;
@@ -36,7 +36,7 @@ import java.io.*;
 public class IMAP {
 
     public IMAP() { }
-    public static final float version = (float)0.1;
+    public static final float version = (float)0.2;
 
     // API Class //////////////////////////////////////////////////////////////////////////////
 
@@ -76,7 +76,6 @@ public class IMAP {
         public void      fetch(Query q, int spec, String[] headers, int start, int end, boolean uid);
         public void      lsub(String start, String ref);
         public void      list(String start, String ref);
-        public static interface Authenticator { public abstract Mailbox authenticate(String user, String pass); } 
         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); } }
@@ -94,16 +93,20 @@ public class IMAP {
         Mailbox selected = null;
         Mailbox root = null;
         Mailbox selected() { if (selected == null) throw new Bad("no mailbox selected"); return selected; }
-        final Server.Authenticator auth;
+        final Login auth;
         final Client client;
 
-        public MailboxWrapper(Server.Authenticator auth, Client c) { this.auth=auth; this.client=c;}
+        public MailboxWrapper(Login auth, Client c) { this.auth=auth; this.client=c;}
 
-        private Mailbox mailbox(String name, boolean create) {
+        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;
             for(StringTokenizer st = new StringTokenizer(name, sep + ""); st.hasMoreTokens();)
-                if ((m = m.slash(st.nextToken(), create)) == null) throw new Server.No("no such mailbox " + name);
+                if ((m = m.slash(st.nextToken(), create)) == null) {
+                    if (throwexn) throw new Server.No("no such mailbox " + name);
+                    return null;
+                }
             return m;
         }
 
@@ -151,15 +154,18 @@ public class IMAP {
             for(Mailbox.Iterator it=selected().iterator(q);it.next();) to.add(it.cur(), it.flags() | Mailbox.Flag.RECENT); }
 
         public void login(String u, String p) {
-            if ((root = auth.authenticate(u,p)) == null) throw new No("Login failed.");
+            Account account = null;
+            if ((account = auth.login(u,p)) == null) throw new No("Login failed.");
+            root = account.getMailbox(IMAP.class);
             inbox = mailbox("INBOX", false);  // FEATURE: ??
+            if (inbox == null) inbox = root;
         }
         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 create(String m) { mailbox(m, true); }
+        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(new Message(null,null,b,a),f|Mailbox.Flag.RECENT);
+            mailbox(m,false).add(new Message(new Stream(b), new Message.Envelope(null,null,a)),f|Mailbox.Flag.RECENT);
         } catch (Message.Malformed e) { throw new No(e.getMessage()); } }
         public void check() { }
         public void noop() { }
@@ -227,9 +233,10 @@ public class IMAP {
         String selectedName = null;
         Mailbox inbox = null, root = null;
         Server api;
+        Login auth = null;
         Parser parser = null;
         Connection conn = null;
-        public Listener() { }
+        public Listener(Login auth) { this.auth = auth; }
         Parser.Token token() { return parser.token(); }
         void println(String s) { conn.println(s); }
         void newline() { parser.newline(); }
@@ -237,7 +244,7 @@ public class IMAP {
         public void handleRequest(Connection conn) {
             this.conn = conn;
             parser = new Parser(conn);
-            api = new IMAP.MailboxWrapper(new Main.BogusAuthenticator(), this);
+            api = new IMAP.MailboxWrapper(auth, this);
             conn.setTimeout(30 * 60 * 1000);
             println("* OK " + conn.vhost + " " + IMAP.class.getName() + " IMAP4rev1 [RFC3501] v" + version + " server ready");
             for(String tag = null;; newline()) try {
@@ -286,23 +293,24 @@ public class IMAP {
                         String response = "";
                         for(int i=0; i<list.length; i++) {
                             String s = list[i].atom().toUpperCase();
+                            if (i>0) response += " ";
                             if (s.equals("MESSAGES"))    response += "MESSAGES "    + api.count(mailbox);
                             if (s.equals("RECENT"))      response += "RECENT "      + api.recent(mailbox);
                             if (s.equals("UIDNEXT"))     response += "UIDNEXT "     + api.uidNext(mailbox);
                             if (s.equals("UIDVALIDITY")) response += "UIDVALIDITY " + api.uidValidity(mailbox);
                             if (s.equals("UNSEEN"))      response += "UNSEEN "      + api.unseen(mailbox);
                         }
-                        println("* STATUS " + selectedName + " (" + response + ")");
+                        println("* STATUS " + mailbox + " (" + response + ")");
                         break;
                     }
                     case APPEND: { 
-                        String m = token().atom();
+                        String m = token().astring();
                         int flags = 0;
-                        Date arrival = null;
+                        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(); }
-                        api.append(m, flags, arrival, token().q());
+                        if (t.type != t.QUOTED) { arrival = t.datetime(); t = token(); }
+                        api.append(m, flags, arrival, t.q());
                         break; }
                     case STORE: {
                         selected();
@@ -388,10 +396,10 @@ public class IMAP {
                 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));}
-                } else if (s.equals("INTERNALDATE")) { spec|=INTERNALDATE; if(e){r.append(" ");r.append(Printer.date(m.arrival));}
+                } else if (s.equals("INTERNALDATE")) { spec|=INTERNALDATE; if(e){r.append(" ");r.append(Printer.date(m.envelope.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.allHeaders+"\r\n"));}
+                } 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("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);
@@ -408,10 +416,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.allHeaders+"\r\n"+m.body; }
-                    else if (s.equals(""))                  { spec |= RFC822TEXT;   if(e) payload = m.allHeaders+"\r\n"+m.body; }
+                    if (list.length == 0)                   { spec |= RFC822TEXT;   if(e) payload = m.headers.raw+"\r\n"+m.body; }
+                    else if (s.equals(""))                  { 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.allHeaders+"\r\n"; }
+                    else if (s.equals("HEADER"))            { spec |= HEADER;       if(e) payload = m.headers.raw+"\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"); }
@@ -441,7 +449,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.get(headers[j]) != null) payload += headers[j]+": "+m.headers.get(headers[j])+"\r\n";
+                    if (m.headers.gets(headers[j]) != null) payload += headers[j]+": "+m.headers.gets(headers[j])+"\r\n";
                 }
             } else {
                throw new Server.No("HEADERS.NOT temporarily disaled");
@@ -450,7 +458,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.get(key)+"\r\n";
+                    payload += key + ": " + m.headers.gets(key)+"\r\n";
                 } }
                */
             }
@@ -657,10 +665,11 @@ public class IMAP {
             if (c == '\r' || c == '\n') { if (freak) bad("unexpected end of line"); return null; }
             else if (c == '{') {
                 while(stream.peekc() != '}') sb.append(stream.getc());
+                stream.getc();
                 stream.println("+ Ready when you are...");
                 int octets = Integer.parseInt(sb.toString());
                 while(stream.peekc() == ' ') stream.getc();   // whitespace
-                while (stream.getc() != '\n' && stream.getc() != '\r') { }
+                while(stream.peekc() == '\n' || stream.peekc() == '\r') stream.getc();
                 byte[] bytes = new byte[octets];
                 int numread = 0;
                 while(numread < bytes.length) {
@@ -708,6 +717,7 @@ public class IMAP {
             public static String addressList(Object a) {
                 if (a == null) return "NIL";
                 if (a instanceof Address) return "("+address((Address)a)+")";
+                if (a instanceof String) return "("+address(Address.parse((String)a))+")";
                 Address[] aa = (Address[])a;
                 StringBuffer ret = new StringBuffer();
                 ret.append("(");
@@ -743,15 +753,15 @@ public class IMAP {
         static String date(Date d) { return d.toString(); }
         static String envelope(Message m) {
             return
-                "(" + quotify(m.arrival.toString()) +
+                "(" + quotify(m.envelope.arrival.toString()) +
                 " " + quotify(m.subject) +          
                 " " + addressList(m.from) +      
-                " " + addressList(m.headers.get("sender")) +
+                " " + addressList(m.headers.gets("sender")) +
                 " " + addressList(m.replyto) + 
                 " " + addressList(m.to) + 
                 " " + addressList(m.cc) + 
                 " " + addressList(m.bcc) + 
-                " " + quotify((String)m.headers.get("in-reply-to")) +
+                " " + quotify((String)m.headers.gets("in-reply-to")) +
                 " " + quotify(m.messageid) +
                 ")";
         }
@@ -788,19 +798,8 @@ public class IMAP {
 
     // Main //////////////////////////////////////////////////////////////////////////////
 
-    /** simple listener for testing purposes */
-    public static void main(String[] args) throws Exception {
-        ServerSocket ss = new ServerSocket(143);
-        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 Listener();
-            } catch (Exception e) { e.printStackTrace(); } } }.start();
-        }
-    }
-
     public static final int
         PEEK=0x1, BODYSTRUCTURE=0x2, ENVELOPE=0x4, FLAGS=0x8, INTERNALDATE=0x10, FIELDS=0x800, FIELDSNOT=0x1000,
         RFC822=0x20, RFC822TEXT=0x40, RFC822SIZE=0x80, HEADERNOT=0x100, UID=0x200, HEADER=0x400;
+
 }