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 imapNumber(int min,int max) { return new Query(IMAPNUM,null,min,max,0,null,null,null,null, null); }
+ public static Query nntpNumber(int min,int max) { return new Query(NNTPNUM,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 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 imapNumber(int[] set) { return new Query(IMAPNUM, 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 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); }
- public static Query set(boolean uid, int[] set) { return uid ? uid(set) : num(set); }
+ //public static Query set(boolean uid, int[] set) { return uid ? uid(set) : imapNumber(set); }
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 SIZE = 8;
public static final int BODY = 9;
public static final int FULL = 10;
- public static final int NUM = 11;
+ public static final int IMAPNUM = 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 static final int NNTPNUM = 18;
public final int type;
public final Query[] q;
if (set[i] <= it.uid() && set[i+1] >= 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+=2) if (set[i] <= it.num() && set[i+1] >= it.num()) return true;
+ case IMAPNUM: if (set != null) {
+ for(int i=0; i<set.length; i+=2) if (set[i] <= it.imapNumber() && set[i+1] >= it.imapNumber()) return true;
return false; }
- else return it.num() >= min && it.num() <= max;
+ else return it.imapNumber() >= min && it.imapNumber() <= max;
+ case NNTPNUM: if (set != null) {
+ for(int i=0; i<set.length; i+=2) if (set[i] <= it.nntpNumber() && set[i+1] >= it.nntpNumber()) return true;
+ return false; }
+ else return it.imapNumber() >= min && it.imapNumber() <= 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)) &&
public Headers head() { return m.headers; }
public boolean next() { return false; }
public int uid() { return num; }
- public int num() { return num; }
+ public int imapNumber() { return num; }
+ public int nntpNumber() { throw new RuntimeException("not supported"); }
public void delete() { }
public void set(String key, String val) { }
public String get(String key) { return null; }
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();
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) {
Message message = ((spec & (BODYSTRUCTURE | ENVELOPE | INTERNALDATE | FIELDS | FIELDSNOT | RFC822 |
RFC822TEXT | RFC822SIZE | HEADERNOT | HEADER)) != 0) ? it.cur() : null;
int size = message == null ? 0 : message.getLength();
- client.fetch(it.num(), it.flags(), size, message, it.uid());
+ client.fetch(it.imapNumber(), it.flags(), size, message, it.uid());
it.recent(false);
}
}
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;
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);
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);
} 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;
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
public Article next() { return article(ptr++, false, false); }
public Article last() { return article(ptr--, false, false); }
public Article article(String i, boolean h, boolean b) { return article(Query.header("message-id",i),h,b); }
- public Article article(int n, boolean h, boolean b) { ptr = n; return article(Query.messagenum(n,n),h,b); }
+ public Article article(int n, boolean h, boolean b) { ptr = n; return article(Query.nntpNumber(n,n),h,b); }
private Article article(Query q, boolean head, boolean body) {
Mailbox.Iterator it = current.iterator(q);
if (!it.next()) return null;
try {
Message m = body ? it.cur() : Message.newMessage(new Fountain.StringFountain(it.head() + "\r\n"));
- return new Article(it.num(), m);
+ return new Article(it.nntpNumber(), m);
} catch (Exception e) { return null; }
}
public Group[] list() { return list(root, ""); }
} else if (command.equals("BODY")) { article(st.hasMoreTokens() ? st.nextToken() : null, false, true);
} else if (command.equals("STAT")) { article(st.hasMoreTokens() ? st.nextToken() : null, false, false);
} else if (command.equals("HELP")) { println("100 you are beyond help."); println(".");
- } else if (command.equals("SLAVE")) { println("220 I don't care");
+ } else if (command.equals("SLAVE")) { println("220 SLAVE was removed in RFC3977, you should not use it");
} else if (command.equals("XOVER")) {
println("224 Overview information follows");
MailboxWrapper api = (MailboxWrapper)this.api;
String range = st.hasMoreTokens() ? st.nextToken() : (api.ptr+"-"+api.ptr);
int start = Integer.parseInt(range.substring(0, range.indexOf('-')));
int end = Integer.parseInt(range.substring(range.indexOf('-') + 1));
- Mailbox.Iterator it = api.current.iterator(Query.messagenum(start, end));
+ Mailbox.Iterator it = api.current.iterator(Query.nntpNumber(start, end));
while(it.next()) {
try {
Message m = it.cur();
- println(it.num()+"\t"+m.subject+"\t"+m.from+"\t"+m.date+"\t"+m.messageid+"\t"+
+ println(it.nntpNumber()+"\t"+m.subject+"\t"+m.from+"\t"+m.date+"\t"+m.messageid+"\t"+
m.headers.get("references") + "\t" + m.getLength() + "\t" + m.getNumLines());
} catch (Exception e) { Log.error(this, e); }
}
// Server //////////////////////////////////////////////////////////////////////////////
public static class Server {
- public void handleRequest(Connection conn) {
+ public void handleRequest(Connection conn) throws IOException {
conn.setTimeout(5 * 60 * 1000);
conn.setNewline("\r\n");
conn.println("220 " + conn.vhost + " SMTP " + this.getClass().getName());
String remotehost = null;
for(String command = conn.readln(); ; command = conn.readln()) try {
if (command == null) return;
- Log.warn("**"+conn.getRemoteAddress()+"**", command);
+ //Log.warn("**"+conn.getRemoteAddress()+"**", command);
String c = command.toUpperCase();
if (c.startsWith("HELO")) {
remotehost = c.substring(5).trim();
} catch (MailException.Malformed mfe) { conn.println("501 " + mfe.toString());
} catch (MailException.MailboxFull mbf) { conn.println("452 " + mbf);
} catch (Later.LaterException le) { conn.println("453 try again later");
- } catch (IOException ioe) {
- //conn.println("554 " + ioe.toString());
- Log.error(this, ioe);
- conn.close();
- return;
}
} else { conn.println("500 unrecognized command"); }
} catch (Message.Malformed e) { conn.println("501 " + e.toString()); }
public boolean next() { cur++; return !done(); }
public boolean seen() { return false; }
public boolean recent() { return false; }
- public int num() { return cur+1; } // EUDORA insists that message numbers start at 1, not 0
+ public int nntpNumber() { return cur+1; } // FIXME: lame
+ public int imapNumber() { return cur+1; } // EUDORA insists that message numbers start at 1, not 0
public int uid() { return done() ? -1 : Integer.parseInt(files[cur].substring(0, files[cur].length()-1)); }
public void delete() { File f = file(); if (f != null && f.exists()) f.delete(); }
public void seen(boolean seen) { }
s[0] = (m.from==null?"":m.from.toString(true));
s[1] = m.subject;
s[2] = (m.date + "").trim().replaceAll(" "," ");
- s[3] = it.num() + "";
+ s[3] = it.imapNumber() + "";
msgs.addElement(s);
}
String[][] messages;
int target = Integer.parseInt(request.getParameter("msgnum"));
Mailbox.Iterator it = mbox.iterator();
while(it.next())
- if (it.num() == target)
+ if (it.imapNumber() == target)
break;
if (it.cur() != null) {
pw.println(" <table width=100% border=0 cellspacing=0 style='border: 1px black solid; background-color:#F0F0E0;'>");
public void recent(boolean on) { }
public void set(String key, String val) { throw new MailException("not supported"); }
public String get(String key) { throw new MailException("not supported"); }
+ public int nntpNumber() { throw new MailException("not supported"); }
public int flags() {
return
(deleted() ? Flag.DELETED : 0) |
public abstract Message cur();
public abstract Headers head();
public abstract boolean next();
- public abstract int uid();
- public abstract int num();
public abstract void delete();
+ /** a unique identifier for this message */
+ public abstract int uid();
+
+ /**
+ * Message number according to IMAP semantics.
+ * - no two messages in the same mailbox may have the same imapNumber
+ * - sorting by uid must yield the same order as sorting them by imapNumber
+ * - imapNumber may only change if uidValidity changes
+ * - if uidValidity changes, imapNumbers may change or be reused
+ */
+ public abstract int imapNumber();
+
+ /**
+ * Message number according to NNTP semantics.
+ * - no two messages in the same mailbox may have the same nntpNumber
+ * - article number may NEVER change or EVER be reused
+ * - uidValidity is irrelevant
+ */
+ public abstract int nntpNumber();
+
public abstract void set(String key, String val);
public abstract String get(String key);
public boolean next() { return it.next(); }
public int uid() { return it.uid(); }
public int flags() { return it.flags(); }
- public int num() { return it.num(); }
+ public int nntpNumber() { return it.nntpNumber(); }
+ public int imapNumber() { return it.imapNumber(); }
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 next() { return false; }
public int uid() { return 0; }
public int flags() { return 0; }
- public int num() { return 0; }
+ public int imapNumber() { return 0; }
+ public int nntpNumber() { throw new RuntimeException("this mailbox does not keep article numbers"); }
public void set(String key, String val) { }
public String get(String key) { return null; }
public void delete() { }
int num = 0;
public int uid() { return num; }
- public int num() { return num; }
-
+ public int nntpNumber() { return num; }
+ public int imapNumber() { return num; }
public Message cur() { return messages[num]; }
public Headers head() { return messages[num].headers; }
public boolean next() { return (++num) < messages.length; }
public Headers head() { return messages[position].headers; }
public boolean next() { return ++position < messages.length; }
public int uid() { return position+1; }
- public int num() { return position+1; }
+ public int imapNumber() { return position+1; }
public void delete() { return; }
public void set(String key, String val) { return; }
public Headers head() { return cur().headers; }
public boolean next() { try { m = null; count++; return rs.next(); } catch (Exception e) { throw new RuntimeException(e); } }
public int uid() { throw new RuntimeException("not supported"); }
- public int num() { return count; } // FIXME FIXME
- public void delete() { throw new RuntimeException("not supported"); }
+ public int nntpNumber() { throw new RuntimeException("not supported"); }
+ public int imapNumber() { return count; }
+ public void delete() { throw new RuntimeException("not supported"); }
}
private static String streamToString(Stream stream) throws Exception {