public static interface Visitor { public abstract void visit(Message m); }
- // FIXME make this immutable
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) {
+ 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) return super.put(((String)k).toLowerCase(), v);
else return super.put(k, v);
}
w.write(body);
w.flush();
}
- /*
- public static class Persistent {
- public boolean deleted = false;
- public boolean seen = false;
- public boolean flagged = false;
- public boolean draft = false;
- public boolean answered = false;
- public boolean recent = false;
- public int uid = 0;
- }
- */
- /*
- public static class StoredMessage extends Message {
- public int uid;
- public final int uid;
- public final int uidValidity; // must be reset if mailbox is deleted and then recreated
- public final int sequenceNumber; // starts at 1; numbers reshuffled upon EXPUNGE
- public Hash keywords = new Hash();
- public boolean deleted = false;
- public boolean seen = false;
- public boolean flagged = false;
- public boolean draft = false;
- public boolean answered = false;
- public boolean recent = false; // "Message is "recently" arrived in this mailbox. This
- // session is the first session to have been notified
- // about this message;
- public StoredMessage(LineReader rs) throws IOException, MailException.Malformed { super(rs); }
- }
- */
public class Trace {
final String returnPath;
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);
+ headers.add(key, headers.get(key) + s);
continue;
}
if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s);
} 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);
+ headers.add(key, val);
}
}
" MessageId: " + messageid;
}
- public Message bounce(String reason) {
- // use null-sender for error messages (don't send errors to the null addr)
- // FIXME
- throw new RuntimeException("bounce not implemented");
- }
+ // 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!
}
public static Query arrival(Date earliest, Date latest) { return new Query(ARRIVAL, null,0,0,0,null,null, earliest, latest); }
public static Query header(String name, String val) { return new Query(HEADER, null, 0, 0, 0, name, val, null, null); }
public static Query size(int min, int max) { return new Query(SIZE, null, min, max, 0, null, null, null, null); }
- public static Query body(String text) { return new Query(BODY, null, 0, 0, 0, null, text, null, null); }
public static Query flags(int flags) { return new Query(FLAGS, null, 0, 0, flags, null, null, null, null); }
+ public static Query body(String text) { return new Query(BODY, null, 0, 0, 0, null, text, null, null); }
+ public static Query body(String text) { return new Query(FULL, null, 0, 0, 0, null, text, null, null); }
private Query(int type, Query[] q, int min, int max, int flags, String key, String text, Date earliest, Date latest) {
this.type = type; this.q = q; this.min = min; this.max = max; this.flags = flags; this.key = key; this.text = text;
public static int SIZE = 9;
public static int FLAGS = 10;
public static int BODY = 11;
+ public static int FULL = 12;
public final int type;
public final Query[] q;
case SIZE: return m.size >= min && m.size <= max;
case FLAGS: return mbox.getFlags(flags);
case HEADER: return m.headers.get(key) != null && ((String)m.headers.get(key)).indexOf(text) != -1;
- case HEADER: return m.body.indexOf(text) != -1;
+ case BODY: return m.body.indexOf(text) != -1;
+ case FULL: return m.body.indexOf(text) != -1 || m.allHeaders.indexOf(text) != -1;
default: throw new Error("this should not happen");
}
}
break;
}
if (s.startsWith("UN")) { not = true; s = s.substring(2); }
- if (s.equals("ANSWERED")) q = Query.Flag.ANSWERED;
- else if (s.equals("DELETED")) q = Query.Flag.DELETED;
- else if (s.equals("DRAFT")) q = Query.Flag.DRAFT;
- else if (s.equals("FLAGGED")) q = Query.Flag.FLAGGED;
- else if (s.equals("RECENT")) q = Query.Flag.RECENT;
- else if (s.equals("SEEN")) q = Query.Flag.SEEN;
- else if (s.equals("OLD")) { not = true; q = Query.Flag.RECENT; }
+ 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.Flag.RECENT, Query.not(Query.Flag.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("SUBJECT")) q = Query.header("subject", astring());
else if (s.equals("LARGER")) q = Query.size(n(), true);
else if (s.equals("SMALLER")) q = Query.size(n(), false);
- else if (s.equals("BODY")) q = Query.fullText(astring(), true, false);
+ else if (s.equals("BODY")) q = Query.body(astring(), true, false);
else if (s.equals("TEXT")) q = Query.fullText(astring(), true, true);
- else if (s.equals("BEFORE")) q = Query.arrival(date(), true, false);
- else if (s.equals("SINCE")) q = Query.arrival(date(), false, true);
- else if (s.equals("ON")) q = Query.arrival(date(), true, true);
- else if (s.equals("SENTBEFORE")) q = Query.sent(date(), true, false);
- else if (s.equals("SENTSINCE")) q = Query.sent(date(), false, true);
- else if (s.equals("SENTON")) q = Query.sent(date(), true, true);
+ 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("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());
return q;
}
import java.util.*;
import java.text.*;
-public class Mailbox extends Target {
+public abstract class Mailbox extends Target {
+
+ private static final String STORAGE_ROOT =
+ System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
+ public static FileBased root = null;
+ public static Transcript transcript = null;
+ static {
+ try {
+ root = new FileBased(STORAGE_ROOT + File.separatorChar);
+ transcript = new Transcript(STORAGE_ROOT + File.separatorChar + "transcript");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
// metadata
public void set(Message m, String key, String val) { throw new MailException.MetadataNotSupported(); }
public abstract int uidValidity(); // get the uid validity identifier (see IMAP RFC)
// messages
- public int add(Message message) throws MailException { throw new Error("not implemented"); }
- public int delete(Message message) throws MailException { throw new Error("not implemented"); }
- public void query(Query q, Message.Visitor v) throws MailException { throw new Error("not implemented"); }
- public int count(Query q) throws MailException { throw new Error("not implemented"); }
- public void move(Query q, Mailbox dest, boolean copy) throws MailException { throw new Error("not implemented"); }
+ public abstract void visit(Message.Visitor v) throws MailException; // only abstract method
+ 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 { CounterVisitor cv = new CounterVisitor(this,q); visit(cv); return cv.count; }
+ public void query(Query q, final Message.Visitor v) throws MailException {
+ visit(new Message.Visitor() { public void visit(Message m) { if (q.match(Mailbox.this,m)) v.visit(Mailbox.this,m); } }); }
// submailboxes
- public void rename(String newName) throws MailException { throw new Error("not implemented"); }
- public void destroy() throws MailException { throw new Error("not implemented"); }
- public Mailbox slash(String name, boolean create) throws MailException { throw new Error("not implemented"); }
-
- /** a fast-write, slow-read place to stash all messages we touch -- in case of a major f*ckup */
- public static class Transcript extends Mailbox {
- private String path;
- public Transcript(String path) throws MailException { new File(this.path = path).mkdirs(); }
- private static String lastTime = null;
- private static int lastCounter = 0;
-
- /** returns a message identifier */
- public synchronized int add(Message message) throws MailException {
- try {
- File today = new File(path + File.separatorChar + (new SimpleDateFormat("yy-MMM-dd").format(new Date())));
- today.mkdirs();
-
- String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
- synchronized (Transcript.class) {
- if (lastTime != null && lastTime.equals(time)) {
- time += "." + (++lastCounter);
- } else {
- lastTime = time;
- }
- }
-
- File target = new File(today.getPath() + File.separatorChar + time + ".txt");
- OutputStream os = new FileOutputStream(target);
- message.dump(os);
- os.close();
- return -1;
- } catch (IOException e) { throw new MailException.IOException(e); }
- }
- }
-
- public static class FileBased extends Mailbox {
- private String path;
- private FileBased(String path) throws MailException { new File(this.path = path).mkdirs(); }
- public Mailbox slash(String name, boolean create) throws MailException {
- // FIXME: create
- return new FileBased(path + "/" + name);
- }
-
- public int[] list() {
- String[] names = new File(path).list();
- int[] ret = new int[names.length];
- for(int i=0, j=0; j<ret.length; i++, j++) {
- try {
- ret[j] = Integer.parseInt(names[i].substring(0, names[i].length() - 1));
- } catch (NumberFormatException nfe) {
- Log.warn(FileBased.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;
- }
- }
- return ret;
- }
-
- /** returns a message identifier */
- public synchronized int add(Message message) throws MailException {
- try {
- int[] all = list();
- int max = 0;
- for(int i=0; i<all.length; i++) max = Math.max(max, all[i]);
- int target = max++;
- File f = new File(path + File.separatorChar + max + ".-");
- FileOutputStream fo = new FileOutputStream(f);
- message.dump(fo);
- fo.close();
- f.renameTo(new File(path + File.separatorChar + max + "."));
- return target;
- } catch (IOException e) { throw new MailException.IOException(e); }
- }
+ 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 Message get(int messagenum) throws MailException {
- File f = new File(path + File.separatorChar + messagenum + ".");
- if (!f.exists()) throw new MailException.IOException(new FileNotFoundException(f.toString()));
- //try {
- // FIXME: need to store envelope from/to
- //Message ret = new Message(new LineReader(new InputStreamReader(new FileInputStream(f))));
- // FIXME: set answered/read/etc here
- //return ret;
- //return null;
- /*
- } catch (MailException.Malformed malf) {
- Log.error(this, "This should never happen");
- Log.error(this, malf);
- return null;
- }
- */
- return null;
- }
-
- // query types: stringmatch (headers, body), header element, deletion status, date range, message size
- public Message[] query(int maxResults) {
- throw new RuntimeException("FileBased.query() not implemented yet");
- }
-
- }
-
-
- String user;
- private static Hashtable cache = new Hashtable();
- public static Mailbox getForUser(String user) {
- Mailbox ret = (Mailbox)cache.get(user);
- if (ret == null) ret = new Mailbox(user);
- return ret;
- }
- Mailbox(String user) { this.user = user; }
- public Mailbox slash(String name) throws MailException {
- throw new Error(this.getClass().getName() + " does not support the slash() method"); }
- public synchronized int add(Message message) throws MailException {
- FileOutputStream fos = new FileOutputStream("/var/mail/" + user, true);
- PrintWriter pw = new PrintWriter(new OutputStreamWriter(fos));
- pw.println("From " + message.envelopeFrom);
- pw.flush();
- message.dump(fos);
- fos.close();
- return -1;
- }
-
-
- private static final String STORAGE_ROOT =
- System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
- public static FileBased root = null;
- public static Transcript transcript = null;
- static {
- try {
- root = new FileBased(STORAGE_ROOT + File.separatorChar);
- transcript = new Transcript(STORAGE_ROOT + File.separatorChar + "transcript");
- } catch (Exception e) {
- e.printStackTrace();
- }
+ private static class CounterVisitor implements Message.Visitor {
+ public int count = 0;
+ public void visit(Message m) { if (q.match(Mailbox.this, m)) count++; }
}
public final void accept(Message m) throws MailException { add(m); }