From 3bd45c52785b29b50330339b82b9aa42c94f84f0 Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 18 Jun 2004 11:12:43 +0000 Subject: [PATCH] ready for prime-time darcs-hash:20040618111243-5007d-74707eb7df3ff1fc6aada2c2a1496211818f8766.gz --- src/org/ibex/mail/protocol/IMAP.java | 790 ++++++++++---------- src/org/ibex/mail/target/Mailbox.java | 2 +- .../build/java/org/ibex/util/Vec.java | 201 +++++ 3 files changed, 609 insertions(+), 384 deletions(-) diff --git a/src/org/ibex/mail/protocol/IMAP.java b/src/org/ibex/mail/protocol/IMAP.java index 2263bb5..acf8f03 100644 --- a/src/org/ibex/mail/protocol/IMAP.java +++ b/src/org/ibex/mail/protocol/IMAP.java @@ -7,18 +7,38 @@ import java.net.*; import java.text.*; import java.io.*; +// FIXMEFIXME: recent, seen + +// FIXME: substring searches are case-insensitive + +// FIXME: this is valid LSUB "" asdfdas%*%*%*%*SFEFGWEF +// FIXME: recent is ONLY possible in response to a FETCH +// FIXME: p61 red pen +// FIXME: page 11, red pen +// FIXME: page 36 Noselect +// FIXME: be an asshole about which commands are valid when authed/not-authed +// FIXME: must always send BYE before closing connection +// FIXME: be very careful about where/when we quotify stuff +// FIXME: we can never send 8-bit binary data, even when the message contains it +// FIXME: TRYCREATE (page 46) +// FIXME: assume UTF-8 +// FIXME: UTF-7 mailbox names (5.1.3) +// FIXME: asynchronous client notifications?!?! + // Relevant RFC's: // RFC 2060: IMAPv4 // RFC 3501: IMAPv4 with clarifications // RFC 3691: UNSELECT // RFC 2971: ID -// FEATURE: MIME-queries +// FEATURE: MIME-queries and BODYSTRUCTURE // FEATURE: READ-WRITE / READ-ONLY status on SELECT // FEATURE: pipelining // FEATURE: support [charset] // FEATURE: \Noselect // FEATURE: subscriptions +// FEATURE: tune for efficiency +// FEATURE: STARTTLS public class IMAP { @@ -27,47 +47,45 @@ public class IMAP { // API Class ////////////////////////////////////////////////////////////////////////////// public static final int - PEEK=0x1, BODYSTRUCTURE=0x2, ENVELOPE=0x4, FLAGS=0x8, INTERNALDATE=0x10, - RFC822=0x20, RFC822TEXT=0x40, RFC822SIZE=0x80, NEGATEHEADERS=0x100, UID=0x200, RFC822HEADER=0x400; - - public static interface API { - public String[] capability(); - public Hashtable id(Hashtable clientId); - public void copy(Query q, String to); - public void login(String u, String p); - public void logout(); - public void unselect(); - public void delete(String m); - public void create(String m); - public void append(String m, int flags, Date arrival, String body); - public void check(); - public void noop(); - public void close(); - public void subscribe(String mailbox); - public void unsubscribe(String mailbox); - public int seen(String mailbox); - public int recent(String mailbox); - public int count(String mailbox); - public int uidNext(String mailbox); - public int uidValidity(String mailbox); - public int[] search(Query q, boolean uid); - public void rename(String from, String to); - public void select(String mailbox, boolean examineOnly); - - public static interface Client { - public void expunge(int uid); - public void list(char separator, String mailbox); - public void fetch(int uidnum, int flags, int size, Message m); // m may be null or incomplete - } + 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; - public void setFlags(Query q, int flags, boolean uid, Client c); - public void removeFlags(Query q, int flags, boolean uid, Client c); - public void addFlags(Query q, int flags, boolean uid, Client c); - public void expunge(Client client); - public void fetch(Query q, int spec, String[] headers, int start, int end, boolean uid, Client c); - public void lsub(String start, String ref, Client client); - public void list(String start, String ref, Client client); + public static interface Client { + public void expunge(int uid); + public void list(char separator, String mailbox, boolean lsub); + public void fetch(int num, int flags, int size, Message m, int muid); // m may be null or incomplete + } + public static interface Server { + public String[] capability(); + public Hashtable id(Hashtable clientId); + public void copy(Query q, String to); + public void login(String u, String p); + public void logout(); + public void unselect(); + public void delete(String m); + public void create(String m); + public void append(String m, int flags, Date arrival, String body); + public void check(); + public void noop(); + public void close(); + public void subscribe(String mailbox); + public void unsubscribe(String mailbox); + public int seen(String mailbox); + public int recent(String mailbox); + public int count(String mailbox); + public int uidNext(String mailbox); + public int uidValidity(String mailbox); + public int[] search(Query q, boolean uid); + public void rename(String from, String to); + public void select(String mailbox, boolean examineOnly); + public void setFlags(Query q, int flags, boolean uid); + public void removeFlags(Query q, int flags, boolean uid); + public void addFlags(Query q, int flags, boolean uid); + public void expunge(); + 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); } } @@ -77,58 +95,63 @@ public class IMAP { // SocketWrapper ////////////////////////////////////////////////////////////////////////////// - public static class SocketWrapper /* implements API */ { + public static class SocketWrapper /* implements Server */ { // eventually this will implement the client side of an IMAP socket conversation } + // MailboxWrapper ////////////////////////////////////////////////////////////////////////////// - /** wraps an IMAP.API interface around a Mailbox */ - public static class MailboxWrapper implements API { + /** wraps an IMAP.Server interface around a Mailbox */ + public static class MailboxWrapper implements Server { + + public static final char sep = '.'; Mailbox inbox = null; Mailbox selected = null; - Mailbox selected() { if (selected == null) throw new API.Bad("no mailbox selected"); return selected; } - final API.Authenticator auth; - - public static final char sep = '.'; + Mailbox selected() { if (selected == null) throw new Bad("no mailbox selected"); return selected; } + final Server.Authenticator auth; + final Client client; + final Mailbox root; - private final Mailbox root; - public MailboxWrapper(Mailbox root, API.Authenticator auth) { this.root = root; this.auth = auth; } + public MailboxWrapper(Mailbox root, Server.Authenticator auth, Client c) { this.root=root; this.auth=auth; this.client=c;} - private Mailbox getMailbox(String name, boolean create) { + private Mailbox mailbox(String name, boolean create) { + 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 API.No("no such mailbox " + name); + if ((m = m.slash(st.nextToken(), create)) == null) throw new Server.No("no such mailbox " + name); return m; } + // FIXME should return INBOX if applicable // FEATURE: not accurate when a wildcard and subsequent non-wildcards both match a single component - public void lsub(String start, String ref, Client client) { list(start, ref, client); } - public void list(String start, String ref, Client client) { - if (ref.length() == 0 && start.length() == 0) { client.list(sep, ""); return; } + public void lsub(String start, String ref) { list(start, ref, true); } + public void list(String start, String ref) { list(start, ref, false); } + public void list(String start, String ref, boolean lsub) { + if (ref.length() == 0) { client.list(sep, start, lsub); return; } while (start.endsWith(""+sep)) start = start.substring(0, start.length() - 1); if (ref.endsWith("%")) ref = ref + sep; - String[] children = (start.length() == 0 ? root : getMailbox(start, false)).children(); + String[] children = (start.length() == 0 ? root : mailbox(start, false)).children(); for(int i=0; i 0 ? sep+"" : "") + s; while(true) { if (pre.length() == 0) { - if (s.length() == 0) client.list(sep, kid); + if (s.length() == 0) client.list(sep, kid, lsub); } else switch(pre.charAt(0)) { - case sep: if (s.length() == 0) list(kid, pre.substring(1), client); break; - case '%': client.list(sep, kid); pre = pre.substring(1); s = ""; continue; - case '*': client.list(sep, kid); list(kid, pre, client); pre = pre.substring(1); break; - default: if (s.length()==0) break; - if (s.charAt(0) != pre.charAt(0)) break; - s = s.substring(1); pre = pre.substring(1); continue; + case sep: if (s.length() == 0) list(kid, pre.substring(1), lsub); break; + case '%': client.list(sep, kid, lsub); pre = pre.substring(1); s = ""; continue; + case '*': client.list(sep, kid, lsub); list(kid, pre, lsub); pre = pre.substring(1); break; + default: if (s.length()==0) break; + if (s.charAt(0) != pre.charAt(0)) break; + s = s.substring(1); pre = pre.substring(1); continue; } break; } } } - public String[] capability() { return new String[] { "IMAP4rev1" /*, "UNSELECT", "ID"*/ }; } + public String[] capability() { return new String[] { "IMAP4rev1" , "UNSELECT", "ID" }; } public Hashtable id(Hashtable clientId) { Hashtable response = new Hashtable(); response.put("name", IMAP.class.getName()); @@ -140,121 +163,233 @@ public class IMAP { return response; } - public void copy(Query q, String to0) { - Mailbox to = getMailbox(to0, false); for(Mailbox.Iterator it=selected.iterator(q);it.next();) to.add(it.cur()); } - public void login(String u, String p) { if ((inbox = auth.authenticate(u,p)) == null) throw new API.No("Login failed."); } - public void logout() { } + public void copy(Query q, String to) { copy(q, mailbox(to, false)); } + + // FIXME: TRYCREATE + /* */ void copy(Query q, Mailbox to) { for(Mailbox.Iterator it=selected().iterator(q);it.next();) to.add(it.cur()); } + public void login(String u, String p) { if ((inbox = auth.authenticate(u,p)) == null) throw new No("Login failed."); } public void unselect() { selected = null; } - public void delete(String m0) { Mailbox m = getMailbox(m0, false); if (m != inbox) m.destroy(); } - public void create(String m) { if (!m.endsWith(""+sep)) getMailbox(m, true); } - public void append(String m, int flags, Date arrival, String body) { - getMailbox(m, false).add(new Message(null,null,body,arrival), flags); } + public void delete(String m0) { + Mailbox m=mailbox(m0,false); if (m.equals(inbox)) m.destroy(); else throw new Bad("can't delete inbox"); } + public void create(String m) { mailbox(m, true); } + + // FIXME: must always set RECENT flag + public void append(String m, int f, Date a, String b) { mailbox(m, false).add(new Message(null,null,b,a),f); } public void check() { } public void noop() { } + public void logout() { } public void close() { for(Mailbox.Iterator it=selected().iterator(Query.deleted()); it.next();) it.delete(); unselect(); } - public void expunge(Client c) { for(Mailbox.Iterator it = selected().iterator(Query.deleted());it.next();) expunge(it,c); } - public void expunge(Mailbox.Iterator it, Client client) { client.expunge(it.uid()); it.delete(); } + public void expunge() { for(Mailbox.Iterator it = selected().iterator(Query.deleted());it.next();) expunge(it); } + public void expunge(Mailbox.Iterator it) { client.expunge(it.uid()); it.delete(); } public void subscribe(String mailbox) { } public void unsubscribe(String mailbox) { } - public int seen(String mailbox) { return getMailbox(mailbox, false).count(Query.seen()); } - public int recent(String mailbox) { return getMailbox(mailbox, false).count(Query.recent()); } - public int count(String mailbox) { return getMailbox(mailbox, false).count(Query.all()); } - public int uidNext(String mailbox) { return getMailbox(mailbox, false).uidNext(); } - public int uidValidity(String mailbox) { return getMailbox(mailbox, false).uidValidity(); } - public void select(String mailbox, boolean examineOnly) { selected = getMailbox(mailbox, false); } - public void setFlags(Query q, int f, boolean uid, Client c) { doFlags(q, f, c, uid, 0); } - public void addFlags(Query q, int f, boolean uid, Client c) { doFlags(q, f, c, uid, 1); } - public void removeFlags(Query q, int f, boolean uid, Client c) { doFlags(q, f, c, uid, -1); } + public int seen(String mailbox) { return mailbox(mailbox, false).count(Query.seen()); } + public int recent(String mailbox) { return mailbox(mailbox, false).count(Query.recent()); } + public int count(String mailbox) { return mailbox(mailbox, false).count(Query.all()); } + public int uidNext(String mailbox) { return mailbox(mailbox, false).uidNext(); } + public int uidValidity(String mailbox) { return mailbox(mailbox, false).uidValidity(); } + public void select(String mailbox, boolean examineOnly) { selected = mailbox(mailbox, false); } public int[] search(Query q, boolean uid) { - Vec vec = new Vec(); - for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) - vec.addElement(new Integer(uid ? it.uid() : it.num())); - int[] ret = new int[vec.size()]; - for(int i=0; ior emit a fetch reply. + * + * To avoid duplicating tedious parsing logic, this function + * performs both of the following tasks: + * - parse the fetch request in Token[] t and return a fetch spec + * - emit a fetch reply for the parsed spec with respect to message m + */ + private void fetch(Object o, Token[] t, int num, int flags, int size, boolean uid, int muid) { + Query q = o instanceof Query ? (Query)o : null; + Message m = o instanceof Message ? (Message)o : null; + boolean e = m != null; + + lastfetch = t; + int spec = 0; // spec; see constants for flags + String[] headers = null; + int start = -1, end = -1; + StringBuffer r = new StringBuffer(); + if (e) { r.append(num); r.append(" FETCH ("); } + int initlen = r.length(); + if (uid) { + boolean good = false; + for(int i=0; i initlen) r.append(" "); + if (t[i] == null || t[i].s == null) continue; + String s = t[i].s.toUpperCase(); + r.append(s.equals("BODY.PEEK")?"BODY":s); + 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("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(qq(m.body));} + } else if (s.equals("RFC822.HEADER")){ spec|=HEADER; if(e){r.append(" ");r.append(qq(m.allHeaders+"\r\n"));} + } else if (s.equals("RFC822.SIZE")) { spec|=RFC822SIZE; if(e){r.append(" ");r.append(m.rfc822size());} + } else if (s.equals("UID")) { spec|=UID; if(e){r.append(" ");r.append(muid); } + } else if (!(s.equals("BODY.PEEK") || s.equals("BODY"))) { throw new Server.No("unknown fetch argument: " + s); + } else { + if (s.equalsIgnoreCase("BODY.PEEK")) spec |= PEEK; + else if (e) api.addFlags(Query.num(new int[] { num, num }), Mailbox.Flag.SEEN, false); + if (i')); + int dot = s.indexOf('.'); + start = dot == -1 ? Integer.parseInt(s) : Integer.parseInt(s.substring(0, s.indexOf('.'))); + end = dot == -1 ? -1 : Integer.parseInt(s.substring(s.indexOf('.') + 1)); + if (e) { payload = payload.substring(start, Math.min(end+1,payload.length())); r.append("<"+start+">"); } + } + if (e) { r.append(" "); r.append(qq(payload)); } + } + } + if (e) { r.append(")"); star(r.toString()); } else api.fetch(q, spec, headers, start, end, uid); + } + + private String headers(StringBuffer r, String[] headers, boolean negate, Message m, boolean e) { + String payload = ""; + if (e) r.append(" ("); + if (!negate) { + if(e) for(int j=0; jor emit a fetch reply. - * - * To avoid duplicating tedious parsing logic, this function - * performs both of the following tasks: - * - parse the fetch request in Token[] t and return a fetch spec - * - emit a fetch reply for the parsed spec with respect to message m - */ - private void fetch(Query q, Token[] t, int uidnum, int flags, int size, boolean uid, Message m) { - lastfetch = t; - boolean e = m != null; - int spec = 0; // spec; see constants for flags - String[] headers = null; - int start = -1, end = -1; - StringBuffer r = new StringBuffer(); // reply - if (e) { r.append(uidnum); r.append(" FETCH ("); } - int initlen = r.length(); - for(int i=0; i initlen) r.append(" "); - String s = t[i].s.toUpperCase(); - if (!uid || !s.equals("UID")) r.append(s.equals("BODY.PEEK")?"BODY":s); - 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("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|=RFC822HEADER; if(e){r.append(" ");r.append(Printer.qq(m.allHeaders+"\r\n"));} - } else if (s.equals("RFC822.SIZE")) { spec|=RFC822SIZE; if(e){r.append(" "); r.append(m.rfc822size());} - } else if (s.equals("UID")) { - spec|=UID; - if (e && !uid) { r.append(" "); r.append(uidnum); } - /*FIXME: UID in nonuid fetch*/ - } else if (s.equals("BODY.PEEK") || s.equals("BODY")) { - if (s.equalsIgnoreCase("BODY.PEEK")) spec |= PEEK; - String payload = ""; - if (i')); - int dot = s3.indexOf('.'); - start = dot == -1 ? Integer.parseInt(s3) : Integer.parseInt(s3.substring(0, s3.indexOf('.'))); - end = dot == -1 ? -1 : Integer.parseInt(s3.substring(s3.indexOf('.') + 1)); - if (e) { payload = payload.substring(start, end+1); r.append("<"+start+">"); } - } - } else { - if (e) payload = m.body; - } - if (e) { r.append(" "); r.append(Printer.qq(payload)); } - } else { - throw new API.No("unknown fetch argument: " + s); - } - } - if (e) { - // FIXME hack - if (uid) r.append(" UID " + uidnum); - r.append(")"); - star(r.toString()); - } else { - api.fetch(q, spec, headers, start, end, uid, this); - } - } - private static final Hashtable commands = new Hashtable(); private static final int UID = 0; static { commands.put("UID", new Integer(UID)); } private static final int AUTHENTICATE = 1; static { commands.put("AUTHENTICATE", new Integer(AUTHENTICATE)); } @@ -442,27 +485,24 @@ public class IMAP { this.pw = new PrintWriter(new OutputStreamWriter(conn.getOutputStream())); this.r = new PushbackReader(new InputStreamReader(this.is)); } + protected void flush() { pw.flush(); } protected void println(String s) { - pw.println(s); + pw.print(s + "\r\n"); pw.flush(); - if (log.length() > 0) { - System.err.println("\033[31;1m"+log+"\033[33;0m"); - log = ""; - } - System.err.println("\033[33;1m"+s+"\033[33;0m"); + if (log.length() > 0) Log.info(IMAP.Client.class, log); log = ""; + Log.debug(IMAP.Server.class, s); } - protected void flush() { pw.flush(); } - Query query() { + protected Query query() { String s = null; boolean not = false; Query q = null; while(true) { Token t = token(); - if (t.type == t.LIST) throw new API.No("nested queries not yet supported"); + 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()); s = t.atom(); if (s.equals("NOT")) { not = true; continue; } - if (s.equals("OR")) return Query.or(query(), query()); + if (s.equals("OR")) return Query.or(query(), query()); // FIXME parse rest of list if (s.equals("AND")) return Query.and(query(), query()); break; } @@ -497,40 +537,38 @@ public class IMAP { return q; } + private static void bad(String s) { throw new Server.Bad(s); } class Token { - public byte type; - public final String s; - public final Token[] l; - public final int n; - private static final byte NIL = 0; - private static final byte LIST = 1; - private static final byte QUOTED = 2; - private static final byte NUMBER = 3; - private static final byte ATOM = 4; - private static final byte BAREWORD = 5; - private static final byte SET = 6; - public Token() { n = 0; l = null; s = null; type = NIL; } - public Token(String s, boolean quoted) { this.s = s; l = null; type = quoted ? QUOTED : ATOM; n = 0; } - public Token(Token[] list) { l = list; s = null; type = LIST; n = 0; } - public Token(int number) { n = number; l = null; s = null; type = NUMBER; } - - public String flag() { if (type != ATOM) throw new API.Bad("expected a flag"); return s; } - public int n() { if (type != NUMBER) throw new API.Bad("expected number"); return n; } - public int nz() { int n = n(); if (n == 0) throw new API.Bad("expected nonzero number"); return n; } - public String q() { if (type == NIL) return null; if (type != QUOTED) throw new API.Bad("expected qstring"); return s; } - public Token[] l() { if (type == NIL) return null; if (type != LIST) throw new API.Bad("expected list"); return l; } + public final byte type; + private final String s; + private final Token[] l; + private final int n; + private static final byte NIL = 0, LIST = 1, QUOTED = 2, NUMBER = 3, ATOM = 4, BAREWORD = 5, SET = 6; + public Token() { this.s = null; n = 0; l = null; type = NIL; } + public Token(String s) { this(s, false); } + public Token(String s, boolean quoted) { this.s = s; n = 0; l = null; type = quoted ? QUOTED : ATOM; } + public Token(Token[] list) { this.s = null; n = 0; l = list; type = LIST; } + public Token(int number) { this.s = null; n = number; l = null; type = NUMBER; } + + public String flag() { if (type != ATOM) bad("expected a flag"); return s; } + public int n() { if (type != NUMBER) bad("expected number"); return n; } + public int nz() { int n = n(); if (n == 0) bad("expected nonzero number"); return n; } + public String q() { if (type == NIL) return null; if (type != QUOTED) bad("expected qstring"); return s; } + public Token[] l() { if (type == NIL) return null; if (type != LIST) bad("expected list"); return l; } + public String nstring() { if (type==NIL) return null; if (type!=QUOTED) bad("expected nstring"); return s; } + public String astring() { + if (type != ATOM && type != QUOTED) bad("expected atom or string"); + if (s == null) bad("astring cannot be null"); + return s; } public String[] sl() { if (type == NIL) return null; - if (type != LIST) throw new API.Bad("expected list"); + if (type != LIST) bad("expected list"); String[] ret = new String[l.length]; for(int i=0; i 0) { Log.info(IMAP.class, log); log = ""; } } + else if (ret != '\r') log += (char)ret; return (char)ret; } public char peekc() throws IOException { @@ -614,85 +643,80 @@ public class IMAP { } } - public void newline() { - try { - for(char c = peekc(); c == ' ';) { getc(); c = peekc(); }; - for(char c = peekc(); c == '\r' || c == '\n';) { getc(); c = peekc(); }; - } catch (IOException e) { - e.printStackTrace(); - } - } - - public Token token() { - try { - Vec toks = new Vec(); - StringBuffer sb = new StringBuffer(); - char c = getc(); while (c == ' ') c = getc(); - if (c == '\r' || c == '\n') { - throw new API.Bad("unexpected end of line"); - } if (c == '{') { - while(peekc() != '}') sb.append(getc()); - int octets = Integer.parseInt(sb.toString()); - while(peekc() == ' ') getc(); // whitespace - while (getc() != '\n' && getc() != '\r') { } - byte[] bytes = new byte[octets]; - fill(bytes); - return new Token(new String(bytes), true); - } else if (c == '\"') { - while(true) { - c = getc(); - if (c == '\\') sb.append(getc()); - else if (c == '\"') break; - else sb.append(c); - } - return new Token(sb.toString(), true); - - // NOTE: this is technically a violation of the IMAP grammar, since atoms like FOO[BAR should be legal - } else if (c == ']' || c == ')') { return null; - } else if (c == '[' || c == '(') { - Token t; - do { t = token(); if (t != null) toks.addElement(t); } while (t != null); - Token[] ret = new Token[toks.size()]; - toks.copyInto(ret); - return new Token(ret); - - } else while(true) { - sb.append(c); - c = peekc(); - if (c == ' ' || c == '\"' || c == '(' || c == ')' || c == '[' || c == ']' || - c == '{' || c == '\n' || c == '\r') - return new Token(sb.toString(), false); - getc(); + // FIXME: IOException handling + public void newline() { try { + for(char c = peekc(); c == ' ';) { getc(); c = peekc(); }; + for(char c = peekc(); c == '\r' || c == '\n';) { getc(); c = peekc(); }; + } catch (IOException e) { e.printStackTrace(); } } + + public Token token() { try { + Vec toks = new Vec(); + StringBuffer sb = new StringBuffer(); + char c = getc(); while (c == ' ') c = getc(); + if (c == '\r' || c == '\n') bad("unexpected end of line"); + else if (c == '{') { + while(peekc() != '}') sb.append(getc()); + println("+ Ready when you are..."); + int octets = Integer.parseInt(sb.toString()); + while(peekc() == ' ') getc(); // whitespace + while (getc() != '\n' && getc() != '\r') { } + byte[] bytes = new byte[octets]; + fill(bytes); + return new Token(new String(bytes), true); + } else if (c == '\"') { + while(true) { + c = getc(); + if (c == '\\') sb.append(getc()); + else if (c == '\"') break; + else sb.append(c); } - } catch (IOException e) { - e.printStackTrace(); - return null; + return new Token(sb.toString(), true); + + // NOTE: this is technically a violation of the IMAP grammar, since atoms like FOO[BAR should be legal + } else if (c == ']' || c == ')') { return null; + } else if (c == '[' || c == '(') { + Token t; + do { t = token(); if (t != null) toks.addElement(t); } while (t != null); + Token[] ret = new Token[toks.size()]; + toks.copyInto(ret); + return new Token(ret); + + } else while(true) { + sb.append(c); + c = peekc(); + if (c == ' ' || c == '\"' || c == '(' || c == ')' || c == '[' || c == ']' || + c == '{' || c == '\n' || c == '\r') + return new Token(sb.toString(), false); + getc(); } - } + } catch (IOException e) { e.printStackTrace(); } return null; } } - + public static class Printer { - 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)+")"; } - public static String addressList(Object a) { - if (a == null) return "NIL"; - if (a instanceof Address) return "("+address((Address)a)+")"; - Address[] aa = (Address[])a; - StringBuffer ret = new StringBuffer(); - ret.append("("); - for(int i=0; i= store.length - 1) grow(); + store[size++] = o; + } + + public int peek() { + return lastElement(); + } + + public int elementAt(int i) { + return store[i]; + } + + public int lastElement() { + if (size == 0) return 0; + return store[size - 1]; + } + + public void push(int o) { addElement(o); } + public int pop() { + int ret = lastElement(); + if (size > 0) store[size--] = 0; + return ret; + } + + public int size() { return size; } + + public void setSize(int newSize) { + if (newSize < 0) throw new RuntimeException("tried to set size to negative value"); + if (newSize > store.length) grow(newSize * 2); + if (newSize < size) + for(int i=newSize; i= size || i < 0) throw new RuntimeException("tried to remove an element outside the vector's limits"); + for(int j=i; j= size) setSize(i); + store[i] = o; + } + + public void removeElement(int o) { + int idx = indexOf(o); + if (idx != -1) removeElementAt(idx); + } + + public void insertElementAt(int o, int at) { + if (size == store.length) grow(); + for(int i=size; i>at; i--) + store[i] = store[i-1]; + store[at] = o; + size++; + } + + public void sort() { sort(this, null, 0, size-1); } + + public static void sort(Vec.Int a, Vec.Int b) { + if (b != null && a.size != b.size) throw new IllegalArgumentException("Vec.Int a and b must be of equal size"); + sort(a, b, 0, a.size-1); + } + + private static final void sort(Vec.Int a, Vec.Int b, int start, int end) { + int tmpa, tmpb = 0; + if(start >= end) return; + if(end-start <= 6) { + for(int i=start+1;i<=end;i++) { + tmpa = a.store[i]; + if (b != null) tmpb = b.store[i]; + int j; + for(j=i-1;j>=start;j--) { + if((a.store[j]-tmpa) <= 0) break; + a.store[j+1] = a.store[j]; + if (b != null) b.store[j+1] = b.store[j]; + } + a.store[j+1] = tmpa; + if (b != null) b.store[j+1] = tmpb; + } + return; + } + + int pivot = a.store[end]; + int lo = start - 1; + int hi = end; + + do { + while((a.store[++lo]-pivot) < 0) { } + while((hi > lo) && (a.store[--hi]-pivot) > 0) { } + swap(a, lo,hi); + if (b != null) swap(b, lo,hi); + } while(lo < hi); + + swap(a, lo,end); + if (b != null) swap(b, lo,end); + + sort(a, b, start, lo-1); + sort(a, b, lo+1, end); + } + + private static final void swap(Vec.Int vec, int a, int b) { + if(a != b) { + int tmp = vec.store[a]; + vec.store[a] = vec.store[b]; + vec.store[b] = tmp; + } + } + + public static final void sortInts(int[] a, int start, int end) { + int tmpa; + if(start >= end) return; + if(end-start <= 6) { + for(int i=start+1;i<=end;i++) { + tmpa = a[i]; + int j; + for(j=i-1;j>=start;j--) { + if(a[j] <= tmpa) break; + a[j+1] = a[j]; + } + a[j+1] = tmpa; + } + return; + } + + int pivot = a[end]; + int lo = start - 1; + int hi = end; + + do { + while(a[++lo] < pivot) { } + while((hi > lo) && a[--hi] > pivot) { } + swapInts(a, lo, hi); + } while(lo < hi); + swapInts(a, lo, end); + sortInts(a, start, lo-1); + sortInts(a, lo+1, end); + } + + private static final void swapInts(int[] vec, int a, int b) { + if(a != b) { + int tmp = vec[a]; + vec[a] = vec[b]; + vec[b] = tmp; + } + } + } + } -- 1.7.10.4