+++ /dev/null
-package org.ibex.mail;
-
-public class Flag {
- public static final int DELETED = 0x0001;
- public static final int SEEN = 0x0002;
- public static final int FLAGGED = 0x0004;
- public static final int DRAFT = 0x0010;
- public static final int ANSWERED = 0x0020;
- public static final int RECENT = 0x0040;
-}
import java.io.*;
public class MailException extends RuntimeException {
-
public MailException() { }
public MailException(String s) { super(s); }
public static class MailboxFull extends MailException { }
public static class MetadataNotSupported extends MailException { public MetadataNotSupported(String s) { super(s); } }
public static class Malformed extends MailException { public Malformed(String s) { super(s); } }
public static class RelayingDenied extends MailException { }
- public static class IOException extends MailException {
- // FIXME: fill in stack trace
- final java.io.IOException ioe;
- public IOException(java.io.IOException ioe) { this.ioe = ioe; }
- }
-
+ public static class IOException extends MailException { public IOException(java.io.IOException ioe) { initCause(ioe); } }
}
*/
public class Message extends JSReflection {
- // FIXME
- public Date sent() { return null; }
- public Date arrived() { return null; }
-
- private static class CaseInsensitiveHash extends Hashtable {
- public Object get(Object o) {
- if (o instanceof String) return super.get(((String)o).toLowerCase());
- return super.get(o);
- }
- public Object put(Object k, Object v) { throw new Error("you cannot write to a CaseInsensitiveHash"); }
- void add(Object k, Object v) {
- if (k instanceof String) super.put(((String)k).toLowerCase(), v);
- else super.put(k, v);
- }
- }
-
- public int rfc822size() { return allHeaders.length() + 2 /* CRLF */ + body.length(); } // double check this
- public int size() { return allHeaders.length() + 2 /* CRLF */ + body.length(); } // double check this
-
- public final String allHeaders; // pristine headers
- public final Hashtable headers; // hash of headers (not including resent's and traces)
- public final String body; // entire body
- public final int lines; // lines in the body
+ public final String allHeaders; // pristine headers
+ public final Hashtable headers; // hash of headers (not including resent's and traces)
+ public final String body; // entire body
+ public final int lines; // lines in the body
public final Date date;
public final Address to;
public final Date arrival; // when the message first arrived at this machine; IMAP "internal date message attr"
+ // FIXME: need to be able to read back in the EnvelopeFrom / EnvelopeTo fields
public void dump(OutputStream os) throws IOException {
Writer w = new OutputStreamWriter(os);
w.write(allHeaders);
}
public static class Malformed extends MailException.Malformed { public Malformed(String s) { super(s); } }
- public Message(Address envelopeFrom, Address[] envelopeTo, LineReader rs) throws IOException, MailException.Malformed {
- this.envelopeFrom = envelopeFrom;
- this.envelopeTo = envelopeTo;
- this.arrival = new Date();
- this.headers = new CaseInsensitiveHash();
- String key = null;
- StringBuffer all = new StringBuffer();
- Date date = null;
- Address to = null, from = null, replyto = null;
- String subject = null, messageid = null;
- Vec cc = new Vec(), bcc = new Vec(), resent = new Vec(), traces = new Vec();
- for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) {
- all.append(s);
- all.append("\r\n");
- if (s.length() == 0 || Character.isSpace(s.charAt(0))) {
- if (key == null) throw new Malformed("Message began with a blank line; no headers");
- headers.put(key, headers.get(key) + s);
- continue;
+ public Message(Address envelopeFrom, Address[] envelopeTo, LineReader rs) {
+ try {
+ this.envelopeFrom = envelopeFrom;
+ this.envelopeTo = envelopeTo;
+ this.arrival = new Date();
+ this.headers = new CaseInsensitiveHash();
+ String key = null;
+ StringBuffer all = new StringBuffer();
+ Date date = null;
+ Address to = null, from = null, replyto = null;
+ String subject = null, messageid = null;
+ Vec cc = new Vec(), bcc = new Vec(), resent = new Vec(), traces = new Vec();
+ for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) {
+ all.append(s);
+ all.append("\r\n");
+ if (s.length() == 0 || Character.isSpace(s.charAt(0))) {
+ if (key == null) throw new Malformed("Message began with a blank line; no headers");
+ ((CaseInsensitiveHash)headers).add(key, headers.get(key) + s);
+ continue;
+ }
+ if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s);
+ key = s.substring(0, s.indexOf(':'));
+ for(int i=0; i<key.length(); i++)
+ if (key.charAt(i) < 33 || key.charAt(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);
+ if (key.startsWith("Resent-")) {
+ if (key.startsWith("Resent-From")) resent.addElement(new Hashtable());
+ ((Hashtable)resent.lastElement()).put(key.substring(7), val);
+ } else if (key.startsWith("Return-Path:")) {
+ rs.pushback(s); traces.addElement(new Trace(rs));
+ } else {
+ // just append it to the previous one; valid for Comments/Keywords
+ if (headers.get(key) != null) val = headers.get(key) + " " + val;
+ ((CaseInsensitiveHash)headers).add(key, val);
+ }
}
- if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s);
- key = s.substring(0, s.indexOf(':'));
- for(int i=0; i<key.length(); i++)
- if (key.charAt(i) < 33 || key.charAt(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);
- if (key.startsWith("Resent-")) {
- if (key.startsWith("Resent-From")) resent.addElement(new Hashtable());
- ((Hashtable)resent.lastElement()).put(key.substring(7), val);
- } else if (key.startsWith("Return-Path:")) {
- rs.pushback(s); traces.addElement(new Trace(rs));
- } else {
- // just append it to the previous one; valid for Comments/Keywords
- if (headers.get(key) != null) val = headers.get(key) + " " + val;
- headers.put(key, val);
- }
- }
- this.date = (Date)headers.get("Date");
- this.to = new Address((String)headers.get("To")); // FIXME what if null?
- this.from = headers.get("From") == null ? envelopeFrom : new Address((String)headers.get("From"));
- this.replyto = headers.get("Reply-To") == null ? null : new Address((String)headers.get("Reply-To"));
- this.subject = (String)headers.get("Subject");
- this.messageid = (String)headers.get("Message-Id");
- if (headers.get("Cc") != null) {
- StringTokenizer st = new StringTokenizer((String)headers.get("Cc"));
- this.cc = new Address[st.countTokens()];
- for(int i=0; i<this.cc.length; i++) this.cc[i] = new Address(st.nextToken());
- } else {
- this.cc = new Address[0];
- }
- if (headers.get("Bcc") != null) {
- StringTokenizer st = new StringTokenizer((String)headers.get("Bcc"));
- this.bcc = new Address[st.countTokens()];
- for(int i=0; i<this.bcc.length; i++) this.bcc[i] = new Address(st.nextToken());
- } else {
- this.bcc = new Address[0];
- }
- resent.copyInto(this.resent = new Hashtable[resent.size()]);
- traces.copyInto(this.traces = new Trace[traces.size()]);
- allHeaders = all.toString();
- StringBuffer body = new StringBuffer();
- int lines = 0;
- for(String s = rs.readLine();; s = rs.readLine()) { if (s == null) break; lines++; body.append(s + "\r\n"); }
- this.lines = lines;
- this.body = body.toString();
+ this.date = (Date)headers.get("Date");
+ this.to = new Address((String)headers.get("To")); // FIXME what if null?
+ this.from = headers.get("From") == null ? envelopeFrom : new Address((String)headers.get("From"));
+ this.replyto = headers.get("Reply-To") == null ? null : new Address((String)headers.get("Reply-To"));
+ this.subject = (String)headers.get("Subject");
+ this.messageid = (String)headers.get("Message-Id");
+ if (headers.get("Cc") != null) {
+ StringTokenizer st = new StringTokenizer((String)headers.get("Cc"));
+ this.cc = new Address[st.countTokens()];
+ for(int i=0; i<this.cc.length; i++) this.cc[i] = new Address(st.nextToken());
+ } else {
+ this.cc = new Address[0];
+ }
+ if (headers.get("Bcc") != null) {
+ StringTokenizer st = new StringTokenizer((String)headers.get("Bcc"));
+ this.bcc = new Address[st.countTokens()];
+ for(int i=0; i<this.bcc.length; i++) this.bcc[i] = new Address(st.nextToken());
+ } else {
+ this.bcc = new Address[0];
+ }
+ resent.copyInto(this.resent = new Hashtable[resent.size()]);
+ traces.copyInto(this.traces = new Trace[traces.size()]);
+ allHeaders = all.toString();
+ StringBuffer body = new StringBuffer();
+ int lines = 0;
+ for(String s = rs.readLine();; s = rs.readLine()) { if (s == null) break; lines++; body.append(s + "\r\n"); }
+ this.lines = lines;
+ this.body = body.toString();
+ } catch (IOException e) { throw new MailException.IOException(e); }
}
// http://www.jwz.org/doc/mid.html
return ret.toString();
}
+ public int rfc822size() { return allHeaders.length() + 2 /* CRLF */ + body.length(); } // FIXME: double check this
+
public String summary() {
return
" Subject: " + subject + "\n" +
// use null-sender for error messages (don't send errors to the null addr)
public Message bounce(String reason) { throw new RuntimeException("bounce not implemented"); } // FIXME!
+
+ private static class CaseInsensitiveHash extends Hashtable {
+ public Object get(Object o) { return (o instanceof String) ? super.get(((String)o).toLowerCase()) : super.get(o); }
+ public Object put(Object k, Object v) { throw new Error("you cannot write to a CaseInsensitiveHash"); }
+ void add(Object k, Object v) { if (k instanceof String) super.put(((String)k).toLowerCase(), v); else super.put(k, v); }
+ }
+
}
*/
public class Query {
- public static Query not(Query q) { return new Query(NOT, new Query[] { q }, 0, 0, 0, null, null, null, null, null); }
- public static Query and(Query q1, Query q2) { return new Query(AND, new Query[] {q1,q2},0,0,0,null, null, null, null, null); }
- public static Query and(Query[] q) { return new Query(AND, q, 0, 0, 0, null, null, null, null, null ); }
- public static Query or(Query q1, Query q2) { return new Query(OR,new Query[] {q1,q2},0, 0, 0, null, null, null, null, null); }
- public static Query or(Query[] q) { return new Query(OR, q, 0, 0, 0, null, null, null, null, null); }
- public static Query uid(int min, int max) { return new Query(UID, null, min, max, 0, null, null, null, null, null); }
- public static Query messagenum(int min, int max) { return new Query(MESSAGENUM,null,min,max,0,null,null,null,null, null); }
- public static Query sent(Date earliest, Date latest) { return new Query(SENT,null,0,0,0,null,null,earliest,latest,null); }
- public static Query arrival(Date earliest, Date latest){return new Query(ARRIVAL,null,0,0,0,null,null, earliest,latest,null);}
- public static Query header(String name, String val) { return new Query(HEADER, null, 0, 0, 0, name, val, null, null, null);}
- public static Query size(int min, int max) { return new Query(SIZE, null, min, max, 0, null, null, null, null, null);}
- public static Query flags(int flags) { return new Query(FLAGS, null, 0, 0, flags, null, null, null, null, null);}
- public static Query body(String text) { return new Query(BODY, null, 0, 0, 0, null, text, null, null, null);}
- public static Query full(String text) { return new Query(FULL, null, 0, 0, 0, null, text, null, null, null);}
- public static Query set(int[] set) { return new Query(SET, null, 0 ,0 ,0, null, null, null, null, set);}
- public static Query all() { return new Query(ALL, null, 0, 0, 0, null, null, null, null, null); }
+ public static Query not(Query q) { return new Query(NOT, new Query[] { q },0,0,0,null,null,null,null,null); }
+ public static Query and(Query q1, Query q2) { return new Query(AND,new Query[]{q1,q2},0,0,0,null,null,null,null,null); }
+ public static Query and(Query[] q) { return new Query(AND, q,0,0,0,null, null, null, null, null ); }
+ public static Query or(Query q1, Query q2) { return new Query(OR,new Query[] {q1,q2},0,0,0,null,null, null, null, null); }
+ public static Query or(Query[] q) { return new Query(OR, q, 0, 0, 0, null, null, null, null, null); }
+ public static Query uid(int min, int max) { return new Query(UID, null, min, max, 0, null, null, null, null, null); }
+ public static Query messagenum(int min,int max) { return new Query(NUM,null,min,max,0,null,null,null,null, null); }
+ public static Query sent(Date e, Date l) { return new Query(SENT,null,0,0,0,null,null,e,l,null); }
+ public static Query arrival(Date e, Date l) { return new Query(ARRIVAL,null,0,0,0,null,null, e,l,null);}
+ public static Query header(String k, String v) { return new Query(HEADER, null, 0, 0, 0, k, v, null, null, null);}
+ public static Query size(int min, int max) { return new Query(SIZE, null, min, max, 0, null, null, null, null, null);}
+ public static Query body(String text) { return new Query(BODY, null, 0, 0, 0, null, text, null, null, null);}
+ public static Query full(String text) { return new Query(FULL, null, 0, 0, 0, null, text, null, null, null);}
+ public static Query uid(int[] set) { return new Query(UID, null, 0 ,0 ,0, null, null, null, null, set);}
+ public static Query num(int[] set) { return new Query(NUM, null, 0 ,0 ,0, null, null, null, null, set);}
+ public static Query all() { return new Query(ALL, null, 0, 0, 0, null, null, null, null, null); }
+ public static Query deleted() { return new Query(DELETED, null, 0, 0, 0, null, null, null, null, null); }
+ public static Query seen() { return new Query(SEEN, null, 0, 0, 0, null, null, null, null, null); }
+ public static Query flagged() { return new Query(FLAGGED, null, 0, 0, 0, null, null, null, null, null); }
+ public static Query draft() { return new Query(DRAFT, null, 0, 0, 0, null, null, null, null, null); }
+ public static Query answered() { return new Query(ANSWERED, null, 0, 0, 0, null, null, null, null, null); }
+ public static Query recent() { return new Query(RECENT, null, 0, 0, 0, null, null, null, null, null); }
private Query(int type, Query[] q,int min,int max, int flags, String key, String text, Date earliest, Date latest, int[] set) {
this.type = type; this.q = q; this.min = min; this.max = max; this.flags = flags; this.key = key; this.text = text;
public static final int AND = 2;
public static final int OR = 3;
public static final int UID = 4;
- public static final int MESSAGENUM = 5;
- public static final int SENT = 6;
- public static final int ARRIVAL = 7;
- public static final int HEADER = 8;
- public static final int SIZE = 9;
- public static final int FLAGS = 10;
- public static final int BODY = 11;
- public static final int FULL = 12;
- public static final int SET = 13;
+ public static final int SENT = 5;
+ public static final int ARRIVAL = 6;
+ public static final int HEADER = 7;
+ public static final int SIZE = 8;
+ public static final int BODY = 9;
+ public static final int FULL = 10;
+ public static final int NUM = 11;
+ public static final int DELETED = 12;
+ public static final int SEEN = 13;
+ public static final int FLAGGED = 14;
+ public static final int DRAFT = 15;
+ public static final int ANSWERED = 16;
+ public static final int RECENT = 17;
public final int type;
public final Query[] q;
case NOT: return !q[0].match(it);
case OR: for(int i=0; i<q.length; i++) if (q[i].match(it)) return true; return false;
case AND: for(int i=0; i<q.length; i++) if (!q[i].match(it)) return false; return true;
- case UID: return it.uid() >= min && it.uid() <= max;
- case MESSAGENUM: return it.num() >= min && it.num() <= max;
- case SENT: return (latest == null || it.cur().sent().before(latest))
- &&(earliest == null || it.cur().sent().after(earliest));
- case ARRIVAL: return (latest == null || it.cur().arrived().before(latest))
- &&(earliest == null || it.cur().arrived().after(earliest));
- case SIZE: return it.cur().size() >= min && it.cur().size() <= max;
- case FLAGS: return it.getFlag(flags);
+ case UID: if (set != null){for(int i=0; i<set.length; i++) if (set[i] == it.uid()) return true; return false; }
+ else return it.uid() >= min && it.uid() <= max;
+ case NUM: if (set != null){for(int i=0; i<set.length; i++) if (set[i] == it.uid()) return true; return false; }
+ else return it.uid() >= min && it.uid() <= max;
+ case SENT: return (latest==null||it.cur().date.before(latest)) &&
+ (earliest==null||it.cur().date.after(earliest));
+ case ARRIVAL: return (latest == null || it.cur().arrival.before(latest)) &&
+ (earliest == null || it.cur().arrival.after(earliest));
+ case SIZE: return it.cur().rfc822size() >= min && it.cur().rfc822size() <= max;
case HEADER: return it.cur().headers.get(key) != null && ((String)it.cur().headers.get(key)).indexOf(text) != -1;
case BODY: return it.cur().body.indexOf(text) != -1;
case FULL: return it.cur().body.indexOf(text) != -1 || it.cur().allHeaders.indexOf(text) != -1;
- case SET: for(int i=0; i<set.length; i++) if (set[i] == it.num()) return true; return false;
+ case DELETED: return it.deleted();
+ case SEEN: return it.seen();
+ case FLAGGED: return it.flagged();
+ case DRAFT: return it.draft();
+ case ANSWERED: return it.answered();
+ case RECENT: return it.recent();
default: throw new Error("this should not happen");
}
}
-
}
// FEATURE: support [charset]
public class IMAP extends MessageProtocol {
+ public static final char imapSeparator = '.';
+
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(143);
while(true) {
new Thread() {
public void run() {
try {
- new Listener(s, "megacz.com").handleRequest();
+ 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();
}
private static class Listener extends Incoming {
Mailbox selected = null;
- Mailbox root = null;
-
+ Mailbox root;
+ Mailbox inbox;
Socket conn;
String vhost;
PrintWriter pw;
InputStream is;
PushbackReader r;
public void init() { }
- public Listener(Socket conn, String vhost) throws IOException {
+ 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;
this.pw = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
this.is = conn.getInputStream();
this.r = new PushbackReader(new InputStreamReader(is));
}
- // ugly: not concurrent
- String tag;
- private void ok(String s) { pw.println(tag + " OK " + s); }
private void star(String s) { pw.println("* " + s); }
// FIXME should throw a No if mailbox not found and create==false
Mailbox m = root;
while(name.length() > 0) {
int end = name.length();
- if (name.indexOf('.') != -1) end = name.indexOf('.');
+ if (name.indexOf(imapSeparator) != -1) end = name.indexOf(imapSeparator);
m = m.slash(name.substring(0, end), create);
name = name.substring(end);
}
return m;
}
- 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
-
private boolean auth(String user, String pass) { /* FEATURE */ return user.equals("megacz") && pass.equals(""); }
- public void copy(final Query q, final Mailbox target) {
- for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) { target.add(it.cur()); } }
- 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"); }
-
- // FIXME
- 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() { ok("CHECK ignored"); }
- // FIXME
- public void create(String mailbox){/*if(!mailbox.endsWith(".")&&!mailbox.equalsIgnoreCase("inbox"))getMailbox(mailbox,true);*/}
+ public void lsub(Mailbox m, String s) { star("LIST () \".\" INBOX"); } // FEATURE
+ public void list(Mailbox m, String s) { star("LIST () \".\" INBOX"); } // FEATURE
+ 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 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 check() { }
- // FIXME
public void rename(Mailbox from, String to) {
- /*
- 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);
+ 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++;
- }
+ for(Mailbox.Iterator it = m.iterator(); it.next(); ) { if (!it.seen()) count0++; if (it.recent()) count1++; count2++; }
String response = "";
for(int i=0; i<attrs.length; i++) {
String s = attrs[i].atom();
if (s.equals("UIDVALIDITY")) response += "UIDVALIDITY " + m.uidValidity();
if (s.equals("UNSEEN")) response += "UIDNEXT " + m.uidNext();
}
- star("STATUS " + m.getName() + " (" + response + ")");
+ star("STATUS " + /* FIXME m.getName() +*/ " (" + response + ")");
}
public void select(String mailbox, boolean examineOnly) {
selected = getMailbox(mailbox, false);
star(selected.count(Query.all()) + " EXISTS");
- int recent = 0;
- for(Mailbox.Iterator it = selected.iterator(); it.next(); ) { if (it.recent()) recent++; }
- star(recent + " RECENT");
+ star(selected.count(Query.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)");
// FEATURE: READ-WRITE / READ-ONLY
- ok((examineOnly ? "EXAMINE" : "SELECT") + " completed");
- }
-
- 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(final boolean examineOnly, final boolean silent) {
if (!silent) star(it.uid() + " EXPUNGE");
if (!examineOnly) it.delete();
}
- if (!silent) ok("EXPUNGE completed");
}
public void append(Mailbox m, Token t) {
if (t.type == t.LIST) { flags = t.l(); t = token(); }
if (t.type == t.QUOTED) { arrival = t.datetime(); t = token(); }
String literal = q();
- try {
- Message message = new Message(null, null, new LineReader(new StringReader(literal)));
- if (flags != null) { /* FEATURE */ }
- selected.add(message);
- } catch (IOException e) {
- throw new MailException.IOException(e);
- }
+ selected.add(new Message(null, null, new LineReader(new StringReader(literal))));
+ if (flags != null) { /* FEATURE */ }
}
public void fetch(Query q, Token t) {
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();
for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) {
Message m = it.cur();
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;
+ if (range.indexOf(imapSeparator) == -1) end = Integer.MAX_VALUE;
else {
- end = Integer.parseInt(range.substring(range.indexOf('.') + 1));
- range = range.substring(0, range.indexOf('.'));
+ end = Integer.parseInt(range.substring(range.indexOf(imapSeparator) + 1));
+ range = range.substring(0, range.indexOf(imapSeparator));
}
start = Integer.parseInt(range);
}
m.rfc822size()+" "+ m.lines +")");
}
star(it.num() + " FETCH (" + reply.toString() + ")");
+ // FEATURE: range requests
// FEATURE set seen flag if not BODY.PEEK
}
}
+ // FEATURE: hoist the parsing here
public void store(Query q, String what, Token[] flags) {
for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) {
Message m = it.cur();
LineReader lr = new LineReader(r);
pw.println("* OK " + vhost + " " + IMAP.class.getName() + " IMAP4 v0.1 server ready");
while(true) {
- boolean uid = false;
- String s = lr.readLine();
- if (s.indexOf(' ') == -1) { pw.println("* BAD Invalid tag"); continue; }
- tag = atom();
- String command = atom();
- if (command.equals("UID")) { uid = true; command = atom(); }
- if (command.equals("AUTHENTICATE")) { login(astring(), astring()); }
- else if (command.equals("LIST")) list(mailbox(), mailboxPattern());
- else if (command.equals("LSUB")) lsub(mailbox(), mailboxPattern());
- else if (command.equals("CAPABILITY")) { capability(); }
- else if (command.equals("LOGIN")) login(astring(), astring());
- 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")) select(astring(), true);
- else if (command.equals("SELECT")) select(astring(), false);
- else if (command.equals("COPY")) copy(Query.set(set()), mailbox());
- else if (command.equals("DELETE")) delete(mailbox());
- else if (command.equals("CHECK")) check();
- else if (command.equals("CREATE")) create(astring());
- else if (command.equals("STORE")) store(Query.set(set()), atom(), l());
- else if (command.equals("FETCH")) fetch(Query.set(set()), token());
- else if (command.equals("STATUS")) status(mailbox(), l());
- else throw new Exn.Bad("unrecognized command \"" + command + "\"");
+ String tag = null;
+ try {
+ boolean uid = false;
+ String s = lr.readLine();
+ if (s.indexOf(' ') == -1) throw new Exn.Bad("BAD Invalid tag");
+ tag = atom();
+ String command = atom();
+ if (command.equals("UID")) { uid = true; command = atom(); }
+ if (command.equals("AUTHENTICATE")) { login(astring(), astring()); }
+ else if (command.equals("LIST")) list(mailbox(), mailboxPattern());
+ else if (command.equals("LSUB")) lsub(mailbox(), mailboxPattern());
+ else if (command.equals("CAPABILITY")) { capability(); }
+ else if (command.equals("LOGIN")) login(astring(), astring());
+ 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")) select(astring(), true);
+ else if (command.equals("SELECT")) select(astring(), false);
+ else if (command.equals("COPY")) copy(Query.num(set()), mailbox());
+ else if (command.equals("DELETE")) delete(mailbox());
+ else if (command.equals("CHECK")) check();
+ else if (command.equals("CREATE")) create(astring());
+ else if (command.equals("STORE")) store(Query.num(set()), atom(), l());
+ else if (command.equals("FETCH")) fetch(Query.num(set()), token());
+ else if (command.equals("STATUS")) status(mailbox(), l());
+ else throw new Exn.Bad("unrecognized command \"" + command + "\"");
+ pw.println(tag + " OK " + command + " Completed.");
+ } catch (Exn.Bad b) { pw.println(tag + " Bad " + b.toString());
+ } catch (Exn.No n) { pw.println(tag + " OK " + n.toString());
+ }
+ pw.flush();
}
}
+
+ // Unparsing (printing) logic //////////////////////////////////////////////////////////////////////////////
+
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)+")"; }
")";
}
+
+
+ // Parsing Logic //////////////////////////////////////////////////////////////////////////////
+
Query query() {
String s = null;
boolean not = false;
while(true) {
Token t = token();
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());
+ else if (t.type == t.SET) return Query.num(t.set());
s = t.atom();
if (s.equals("NOT")) { not = true; continue; }
if (s.equals("OR")) return Query.or(query(), query());
break;
}
if (s.startsWith("UN")) { not = true; s = s.substring(2); }
- if (s.equals("ANSWERED")) q = Query.flags(Flag.ANSWERED);
- else if (s.equals("DELETED")) q = Query.flags(Flag.DELETED);
- else if (s.equals("DRAFT")) q = Query.flags(Flag.DRAFT);
- else if (s.equals("FLAGGED")) q = Query.flags(Flag.FLAGGED);
- else if (s.equals("RECENT")) q = Query.flags(Flag.RECENT);
- else if (s.equals("SEEN")) q = Query.flags(Flag.SEEN);
- else if (s.equals("OLD")) { not = true; q = Query.flags(Flag.RECENT); }
- else if (s.equals("NEW")) q = Query.and(Query.flags(Flag.RECENT), Query.not(Query.flags(Flag.SEEN)));
+ if (s.equals("ANSWERED")) q = Query.answered();
+ else if (s.equals("DELETED")) q = Query.deleted();
+ else if (s.equals("DRAFT")) q = Query.draft();
+ else if (s.equals("FLAGGED")) q = Query.flagged();
+ else if (s.equals("RECENT")) q = Query.recent();
+ else if (s.equals("SEEN")) q = Query.seen();
+ else if (s.equals("OLD")) { not = true; q = Query.recent(); }
+ else if (s.equals("NEW")) q = Query.and(Query.recent(), Query.not(Query.seen()));
else if (s.equals("KEYWORD")) q = Query.header("keyword", flag());
else if (s.equals("HEADER")) q = Query.header(astring(), astring());
else if (s.equals("BCC")) q = Query.header("bcc", astring());
else if (s.equals("TEXT")) q = Query.full(astring());
else if (s.equals("BEFORE")) q = Query.arrival(new Date(0), date());
else if (s.equals("SINCE")) q = Query.arrival(date(), new Date(Long.MAX_VALUE));
- else if (s.equals("ON")) q = null; // FIXME
+ else if (s.equals("ON")) { Date d = date(); q = Query.arrival(d, new Date(d.getTime() + 24 * 60 * 60)); }
else if (s.equals("SENTBEFORE")) q = Query.sent(new Date(0), date());
else if (s.equals("SENTSINCE")) q = Query.sent(date(), new Date(Long.MAX_VALUE));
- else if (s.equals("SENTON")) q = null; // FIXME
- //else if (s.equals("UID")) q = Query.uid(set());
- // FIXME
+ else if (s.equals("SENTON")) { Date d = date(); q = Query.sent(d, new Date(d.getTime() + 24 * 60 * 60)); }
+ else if (s.equals("UID")) q = Query.uid(set());
return q;
}
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 flag
- public void setFlags(Message m) { }
- public void addFlags(Message m) { }
- public void deleteFlags(Message m) { }
-
public String mailboxPattern() throws Exn.Bad {
if (type == ATOM) return s;
if (type == QUOTED) return s;
}
}
+ // FEATURE: inline these?
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 class Incoming {
protected void accept(Message m) throws IOException, MailException {
- Mailbox.transcript.add(m); // currently, we write all inbound messages to the transcript
+ Transcript.transcript.accept(m); // currently, we write all inbound messages to the transcript
Target.root.accept(m);
}
}
public class SMTP extends MessageProtocol {
+ // FIXME
+ private static final Mailbox outgoing = null;
+
static { new Thread() { public void run() { Outgoing.runq(); } }.start(); }
public static String convdir = null;
public static void main(String[] s) throws Exception {
return;
}
synchronized(Outgoing.class) {
- Mailbox.root.slash("outgoing").add(m);
+ outgoing.add(m);
queue.append(m);
Outgoing.class.notify();
}
w.print(".\r\n");
check(r.readLine());
Log.info(SMTP.Outgoing.class, "message accepted by " + mx);
- Mailbox.root.slash("outgoing").delete(m);
+ // FIXME!
+ //outgoing.delete(m);
s.close();
return true;
} catch (Exception e) {
static void runq() {
try {
Log.setThreadAnnotation("[outgoing smtp] ");
- Mailbox outgoing = Mailbox.root.slash("outgoing");
Log.info(SMTP.Outgoing.class, "outgoing thread started; " + outgoing.count(Query.all()) + " messages to send");
for(Mailbox.Iterator it = outgoing.iterator(); it.cur() != null; it.next()) queue.append(it.cur());
while(true) {
import java.util.*;
import java.text.*;
-// FIXME: uids and messagenums are all messed up
+/** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */
+public class FileBasedMailbox extends Mailbox.Default {
-public class FileBasedMailbox extends Mailbox {
+ private static final char slash = File.separatorChar;
+ private static final Hashtable instances = new Hashtable();
+ public static FileBasedMailbox getFileBasedMailbox(String path, boolean create) {
+ FileBasedMailbox ret = (FileBasedMailbox)instances.get(path);
+ if (ret != null) return ret;
+ File f = new File(path);
+ if (!create && !f.exists()) return null;
+ instances.put(path, ret = new FileBasedMailbox(path));
+ return ret;
+ }
- private String path;
- public FileBasedMailbox(String path) throws MailException { new File(this.path = path).mkdirs(); }
- public Mailbox slash(String name, boolean create) throws MailException { return new FileBasedMailbox(path + "/" + name); }
- // FIXME
- public String getName() { return "FOO"; }
-
- protected synchronized void flags(Message m, int newFlags) throws MailException {
- try {
- File f = new File(messageToFile(m).getCanonicalPath() + ".flags");
- PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f)));
- if ((newFlags & Flag.DELETED) != 0) pw.print("\\Deleted ");
- if ((newFlags & Flag.SEEN) != 0) pw.print("\\Seen ");
- if ((newFlags & Flag.FLAGGED) != 0) pw.print("\\Flagged ");
- if ((newFlags & Flag.DRAFT) != 0) pw.print("\\Draft ");
- if ((newFlags & Flag.ANSWERED) != 0) pw.print("\\Answered ");
- if ((newFlags & Flag.RECENT) != 0) pw.print("\\Recent ");
- pw.close();
- } catch (IOException e) { throw new MailException.IOException(e); }
+ public static final FilenameFilter filter = new FilenameFilter() {
+ public boolean accept(File f, String s) {
+ return s.indexOf('.') != -1;
+ } };
+
+ // Instance //////////////////////////////////////////////////////////////////////////////
+
+ private String path;
+ private File uidNext;
+ private FileBasedMailbox(String path) throws MailException {
+ new File(this.path = path).mkdirs();
+ uidNext(false);
}
- protected synchronized int flags(Message m) {
+ public Mailbox slash(String name, boolean create) {
+ return FileBasedMailbox.getFileBasedMailbox(path + slash + name, create); }
+ public Mailbox.Iterator iterator() { return new FileBasedMailbox.Iterator(); }
+ public int uidValidity() { return (int)(new File(path).lastModified() & 0xffffffL); }
+
+ public int uidNext() { return uidNext(false); }
+ public int uidNext(boolean inc) {
try {
- File f = new File(messageToFile(m).getCanonicalPath() + ".flags");
- if (!f.exists()) return 0;
- String s = new BufferedReader(new InputStreamReader(new FileInputStream(f))).readLine();
- StringTokenizer st = new StringTokenizer(s);
- int ret = 0;
- while(st.hasMoreTokens()) {
- String s2 = st.nextToken();
- if (s2.equals("\\Deleted")) ret |= Flag.DELETED;
- if (s2.equals("\\Seen")) ret |= Flag.SEEN;
- if (s2.equals("\\Flagged")) ret |= Flag.FLAGGED;
- if (s2.equals("\\Draft")) ret |= Flag.DRAFT;
- if (s2.equals("\\Answered")) ret |= Flag.ANSWERED;
- if (s2.equals("\\Recent")) ret |= Flag.RECENT;
+ uidNext = new File(path + slash + "UIDNEXT");
+ if (!uidNext.exists()) {
+ File tmp = new File(uidNext + "-");
+ PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tmp)));
+ pw.println("1");
+ pw.flush();
+ pw.close();
+ tmp.renameTo(uidNext);
+ return 1;
+ }
+ BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(uidNext)));
+ int ret = Integer.parseInt(br.readLine());
+ if (inc) {
+ File tmp = new File(uidNext + "-");
+ PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tmp)));
+ pw.println(ret+1);
+ pw.flush();
+ pw.close();
+ tmp.renameTo(uidNext);
}
return ret;
} catch (IOException e) { throw new MailException.IOException(e); }
- }
-
- private int uidNext = 0;
- public int uidNext() { return uidNext; }
- public int uidValidity() { return 1; }
- public int uid(Message m) { Integer i = (Integer)uidMap.get(m); if (i == null) return -1; return i.intValue(); }
- public int num(Message m) { Integer i = (Integer)numMap.get(m); if (i == null) return -1; return i.intValue(); }
- private File messageToFile(Message message) { return new File(path + File.separatorChar + num(message) + "."); }
+ }
- public int delete(Message message) throws MailException {
- messageToFile(message).delete();
- uidMap.remove(message);
- numMap.remove(message);
- return -1;
+ public synchronized int add(Message message) {
+ try {
+ int num = new File(path).list(filter).length;
+ File target = new File(path + slash + uidNext(true) + ".");
+ File f = new File(target.getCanonicalPath() + "-");
+ FileOutputStream fo = new FileOutputStream(f);
+ message.dump(fo);
+ fo.close();
+ f.renameTo(target);
+ return num;
+ } catch (IOException e) { throw new MailException.IOException(e); }
}
- public Mailbox.Iterator iterator() { return new FileBasedMailbox.Iterator(); }
- private class Iterator extends Mailbox.Iterator {
+ private class Iterator implements Mailbox.Default.Iterator {
+ int cur = -1;
private String[] names;
- public Iterator() { names = new File(path).list(); }
- public Message cur() { return null; }
- public boolean next() { return false; }
- protected int flags() { return 0; }
- protected void flags(int newFlags) { }
- public int uid() { return -1; }
- public int num() { return -1; }
- public void delete() { }
+ private boolean seen = false, deleted = false, draft = false, flagged = false, answered = false, recent = false;
+ public Iterator() { names = new File(path).list(filter); }
- /*
+ public Message cur() {
try {
- //= Integer.parseInt(names[i].substring(0, names[i].length() - 1));
- // FIXME
+ File file = new File(path + File.separatorChar + names[cur]);
+ return new Message(/* FIXME */ null, /* FIXME */ new Address[] { },
+ new LineReader(new InputStreamReader(new FileInputStream(file))));
+ } catch (IOException e) { throw new MailException.IOException(e); }
+ }
+ public boolean next() {
+ cur++;
+ if (cur >= names.length) return false;
+ String name = names[cur].substring(names[cur].indexOf('.') + 1);
+ seen = name.indexOf('s') != -1;
+ deleted = name.indexOf('x') != -1;
+ flagged = name.indexOf('f') != -1;
+ draft = name.indexOf('d') != -1;
+ answered = name.indexOf('a') != -1;
+ recent = name.indexOf('r') != -1;
+ return true;
+ }
+ public int num() { return cur; }
+ public int uid() {
+ try { return Integer.parseInt(names[cur].substring(0, names[cur].indexOf('.')));
} catch (NumberFormatException nfe) {
- Log.warn(FileBasedMailbox.class, "NumberFormatException: " + names[i].substring(0, names[i].length() - 1));
- j--;
- int[] newret = new int[ret.length - 1];
- System.arraycopy(ret, 0, newret, 0, newret.length);
- ret = newret;
- }
+ Log.warn(FileBasedMailbox.class, "NumberFormatException: " + names[cur].substring(0, names[cur].length() - 1));
+ return -1; } }
+
+ private void fixflags() {
+ String newName =
+ names[cur].substring(0, names[cur].indexOf('.') + 1) +
+ (seen ? "s" : "") +
+ (deleted ? "x" : "") +
+ (flagged ? "f" : "") +
+ (draft ? "d" : "") +
+ (recent ? "r" : "") +
+ (answered ? "a" : "");
+ new File(names[cur]).renameTo(new File(path + File.separatorChar + newName));
+ names[cur] = newName;
}
- */
- }
- private Hashtable uidMap = new Hashtable();
- private Hashtable numMap = new Hashtable();
- private int numNext = 0;
+ public void delete() { new File(names[cur]).delete(); }
+ public void set(String key, String val) { throw new MailException("not supported"); }
+ public String get(String key) { throw new MailException("not supported"); }
- public synchronized int add(Message message) throws MailException {
- try {
- File target = messageToFile(message);
- File f = new File(target.getCanonicalPath() + ".-");
- FileOutputStream fo = new FileOutputStream(f);
- message.dump(fo);
- fo.close();
- f.renameTo(target);
- uidMap.put(new Integer(uidNext++), message);
- numMap.put(new Integer(numNext++), message);
- return numNext - 1;
- } catch (IOException e) { throw new MailException.IOException(e); }
- }
+ public boolean seen() { return seen; }
+ public boolean deleted() { return deleted; }
+ public boolean flagged() { return flagged; }
+ public boolean draft() { return draft; }
+ public boolean answered() { return answered; }
+ public boolean recent() { return recent; }
+ public void seen(boolean on) { seen = on; fixflags(); }
+ public void deleted(boolean on) { deleted = on; fixflags(); }
+ public void flagged(boolean on) { flagged = on; fixflags(); }
+ public void draft(boolean on) { draft = on; fixflags(); }
+ public void answered(boolean on) { answered = on; fixflags(); }
+ public void recent(boolean on) { recent = on; fixflags(); }
+ }
}
public abstract class Mailbox extends Target {
- private static final String STORAGE_ROOT =
+ public static final String STORAGE_ROOT =
System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
- public static FileBasedMailbox root = null;
- public static Transcript transcript = null;
- static {
- try {
- root = new FileBasedMailbox(STORAGE_ROOT + File.separatorChar);
- transcript = new Transcript(STORAGE_ROOT + File.separatorChar + "transcript");
- } catch (Exception e) {
- e.printStackTrace();
+
+
+ // Required Methods //////////////////////////////////////////////////////////////////////////////
+
+ public abstract Mailbox.Iterator iterator(Query q);
+ public abstract int add(Message message);
+ public abstract void move(Query q, Mailbox dest);
+ public abstract void copy(Query q, Mailbox dest);
+ public abstract int count(Query q);
+ public abstract int uidNext();
+ public abstract int uidValidity();
+ public abstract void rename(String newName);
+ public abstract void destroy();
+ public abstract Mailbox slash(String name, boolean create);
+
+
+ // Thunks ////////////////////////////////////////////////////////////////////////////
+
+ public final void accept(Message m) { add(m); }
+ public Mailbox.Iterator iterator() { return iterator(Query.all()); }
+
+
+ // Default Implementation //////////////////////////////////////////////////////////////////////////////
+
+ /** default, inefficient implementation of Mailbox; only requires a few methods to be implemented */
+ public static abstract class Default extends Mailbox {
+ public Mailbox.Iterator iterator(Query q) { return new Mailbox.Iterator.QueryIterator(q, this); }
+ public void copy(Query q, Mailbox dest) { for(Mailbox.Iterator it = iterator(q); it.next();) dest.add(it.cur()); }
+ public int count(Query q) { int count = 0; for(Mailbox.Iterator it = iterator(q); it.next();) count++; return count; }
+ public void rename(String newName) { throw new MailException("not supported"); }
+ public void destroy() { throw new MailException("not supported"); }
+ public Mailbox slash(String name, boolean create) { throw new MailException("not supported"); }
+ public void move(Query q, Mailbox dest) {
+ for(Mailbox.Iterator it = iterator(q);it.next();) { dest.add(it.cur()); it.delete(); }
}
}
- // iterator
- public abstract Mailbox.Iterator iterator(); // only abstract method
- public Mailbox.Iterator iterator(Query q) throws MailException { return new QueryIterator(q); }
- public int add(Message message) throws MailException { throw new MailException("not implemented"); }
- public int delete(Message message) throws MailException { throw new MailException("not implemented"); }
- public void move(Query q, Mailbox dest, boolean copy) throws MailException { throw new MailException("not implemented"); }
- public int count(Query q) throws MailException {
- int i=0;
- for(Iterator it = iterator(); it.next(); ) if (q.match(it)) i++;
- return i;
- }
- // submailboxes
- public void rename(String newName) throws MailException { throw new MailException("you cannot rename this mailbox"); }
- public void destroy() throws MailException { throw new MailException("you cannot destroy this mailbox"); }
- public Mailbox slash(String name, boolean create) throws MailException { throw new MailException("no submailboxes"); }
- public Mailbox slash(String name) throws MailException { return slash(name, false); }
-
- public abstract int uidNext();
- public abstract int uidValidity();
-
- // FIXME
- public abstract String getName();
-
- private class QueryIterator extends Iterator {
- Query q;
- Mailbox.Iterator i;
- public QueryIterator(Query q) { this.q = q; this.i = Mailbox.this.iterator(); }
- public Message cur() { return i.cur(); }
- public boolean next() { do { if (!i.next()) return false; } while(!q.match(i)); return true; }
- protected int flags() { return i.flags(); }
- protected void flags(int newFlags) { i.flags(newFlags); }
- public int uid() { return i.uid(); }
- public int num() { return i.num(); }
- public void set(String key, String val) { i.set(key, val); }
- public String get(String key) { return i.get(key); }
- public void delete() { i.delete(); }
- }
+ // Iterator Definition //////////////////////////////////////////////////////////////////////////////
- public static abstract class Iterator {
+ public static interface Iterator {
public abstract Message cur();
public abstract boolean next();
+ public abstract int uid();
+ public abstract int num();
+ public abstract void delete();
+ public abstract void set(String key, String val);
+ public abstract String get(String key);
+ public abstract boolean seen();
+ public abstract boolean deleted();
+ public abstract boolean flagged();
+ public abstract boolean draft();
+ public abstract boolean answered();
+ public abstract boolean recent();
+ public abstract void seen(boolean on);
+ public abstract void deleted(boolean on);
+ public abstract void flagged(boolean on);
+ public abstract void draft(boolean on);
+ public abstract void answered(boolean on);
+ public abstract void recent(boolean on);
- // minimal implementation
- protected abstract int flags();
- protected abstract void flags(int newFlags);
- public abstract int uid();
- public abstract int num();
- public abstract void delete();
-
- public void set(String key, String val) { throw new MailException.MetadataNotSupported(""); }
- public String get(String key) { throw new MailException.MetadataNotSupported(""); }
-
- public final boolean getFlag(int flag) { return ((flags() & flag) == flag); }
- public final void setFlag(int flag) { flags(flags() | flag); }
- public final void clearFlag(int flag) { flags(flags() & ~flag); }
-
- public final boolean seen() { return getFlag(Flag.SEEN); }
- public final boolean deleted() { return getFlag(Flag.DELETED); }
- public final boolean flagged() { return getFlag(Flag.FLAGGED); }
- public final boolean draft() { return getFlag(Flag.DRAFT); }
- public final boolean answered() { return getFlag(Flag.ANSWERED); }
- public final boolean recent() { return getFlag(Flag.RECENT); }
-
- public final void seen(boolean on) { setFlag(Flag.SEEN); }
- public final void deleted(boolean on) { setFlag(Flag.DELETED); }
- public final void flagged(boolean on) { setFlag(Flag.FLAGGED); }
- public final void draft(boolean on) { setFlag(Flag.DRAFT); }
- public final void answered(boolean on) { setFlag(Flag.ANSWERED); }
- public final void recent(boolean on) { setFlag(Flag.RECENT); }
- }
+ public static class Wrapper implements Iterator {
+ private Iterator it;
+ public Wrapper(Iterator it) { this.it = it; }
+ public Message cur() { return it.cur(); }
+ public boolean next() { return it.next(); }
+ public int uid() { return it.uid(); }
+ public int num() { return it.num(); }
+ public void set(String key, String val) { it.set(key, val); }
+ public String get(String key) { return it.get(key); }
+ public void delete() { it.delete(); }
+ public boolean seen() { return it.seen(); }
+ public boolean deleted() { return it.deleted(); }
+ public boolean flagged() { return it.flagged(); }
+ public boolean draft() { return it.draft(); }
+ public boolean answered() { return it.answered(); }
+ public boolean recent() { return it.recent(); }
+ public void seen(boolean on) { it.seen(on); }
+ public void deleted(boolean on) { it.deleted(on); }
+ public void flagged(boolean on) { it.flagged(on); }
+ public void draft(boolean on) { it.draft(on); }
+ public void answered(boolean on) { it.answered(on); }
+ public void recent(boolean on) { it.recent(on); }
+ }
- public final void accept(Message m) throws MailException { add(m); }
+ class QueryIterator extends Mailbox.Iterator.Wrapper {
+ Query q;
+ public QueryIterator(Query q, Mailbox m) { super(m.iterator()); this.q = q; }
+ public boolean next() { do { if (!super.next()) return false; } while(!q.match(this)); return true; }
+ }
+ }
+
}
import java.text.*;
/** a fast-write, slow-read place to stash all messages we touch -- in case of a major f*ckup */
-public class Transcript extends Mailbox {
+public class Transcript extends Target {
+
+ public static final Transcript transcript = new Transcript(Mailbox.STORAGE_ROOT + File.separatorChar + "transcript");
+
private String path;
- public Transcript(String path) throws MailException { new File(this.path = path).mkdirs(); }
+ public Transcript(String path) { new File(this.path = path).mkdirs(); }
private static String lastTime = null;
private static int lastCounter = 0;
- // FIXME
- public String getName() { return "Transcript"; }
-
- public Mailbox.Iterator iterator() { return null; }
- public int uidValidity() { return 0; }
- public int uidNext() { return 0; }
-
- /** returns a message identifier */
- public synchronized int add(Message message) throws MailException {
+ public synchronized void accept(Message message) {
try {
File today = new File(path + File.separatorChar + (new SimpleDateFormat("yy-MMM-dd").format(new Date())));
today.mkdirs();
time += "." + (++lastCounter);
} else {
lastTime = time;
+ lastCounter = 0;
}
}
File target = new File(today.getPath() + File.separatorChar + time + ".txt");
OutputStream os = new FileOutputStream(target);
message.dump(os);
+ os.flush();
os.close();
- return -1;
} catch (IOException e) { throw new MailException.IOException(e); }
}
}