+ private Parser.Token[] lastfetch = null; // hack
+ private boolean lastuid = false; // hack
+ private boolean selected = false;
+ private void selected() { if (!selected) throw new Server.Bad("no mailbox selected"); }
+
+ // Callbacks //////////////////////////////////////////////////////////////////////////////
+
+ public void expunge(int uid) { println("* " + uid + " EXPUNGE"); }
+ public void fetch(int n, int f, int size, Message m, int muid) { fetch(m, lastfetch, n, f, size, lastuid, muid); }
+ public void list(char sep, String mb, boolean sub, boolean p) {
+ println("* " + (sub?"LSUB":"LIST")+" ("+(p?"\\Noselect":"")+") \""+sep+"\" \""+mb+"\"");}
+
+ /**
+ * Parse a fetch request <i>or</i> 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, Parser.Token[] t, int num, int flags, int size, boolean uid, int muid) {
+ Query q = o == null ? null : o instanceof Query ? (Query)o : null;
+ Message m = o == null ? null : o instanceof Message ? (Message)o : null;
+ boolean e = q == null;
+
+ // asynchronous flags update
+ if (size == -1) {
+ println("* " + num + " FETCH (FLAGS " + Printer.flags(flags) + (uid?(" UID "+muid):"") + ")");
+ return;
+ }
+
+ 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<t.length; i++)
+ if ((t[i].type == Parser.Token.QUOTED || t[i].type == Parser.Token.ATOM) &&
+ t[i].astring().equalsIgnoreCase("UID")) good = true;
+ if (!good) {
+ Parser.Token[] t2 = new Parser.Token[t.length + 1];
+ System.arraycopy(t, 0, t2, 0, t.length);
+ t2[t2.length - 1] = parser.token("UID");
+ lastfetch = (t = t2);
+ }
+ }
+ if (t.length == 0 && (t[0].type == Parser.Token.QUOTED || t[0].type == Parser.Token.ATOM)) {
+ if (t[0].astring().equalsIgnoreCase("ALL"))
+ t = new Parser.Token[] { parser.token("FLAGS"), parser.token("INTERNALDATE"),
+ parser.token("ENVELOPE"), parser.token("RFC822.SIZE") };
+ else if (t[0].astring().equalsIgnoreCase("FULL"))
+ t = new Parser.Token[] { parser.token("FLAGS"), parser.token("INTERNALDATE"), parser.token("BODY"),
+ parser.token("ENVELOPE"), parser.token("RFC822.SIZE") };
+ else if (t[0].astring().equalsIgnoreCase("FAST"))
+ t = new Parser.Token[] { parser.token("FLAGS"), parser.token("INTERNALDATE"),
+ parser.token("RFC822.SIZE") };
+ }
+ for(int i=0; i<t.length; i++) {
+ if (r.length() > initlen) r.append(" ");
+ if (t[i] == null || t[i].s == null) continue;
+ String s = t[i].s.toUpperCase();
+ r.append(s.equalsIgnoreCase("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(getBodyString(m)));}
+ } else if (s.equals("RFC822.HEADER")){ spec|=HEADER;if(e){r.append(" ");r.append(Printer.qq(m.headers.getString()+"\r\n"));}
+ } else if (s.equals("RFC822.SIZE")) { spec|=RFC822SIZE; if(e){r.append(" ");r.append(m.getLength());}
+ } 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.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;
+ //{ if (e) { r.append(" "); r.append(Printer.qq(m.body)); } continue; }
+ }
+ String payload = "";
+ r.append("[");
+ Parser.Token[] list = t[++i].l();
+ s = list.length == 0 ? "" : list[0].s.toUpperCase();
+ r.append(s);
+ if (list.length == 0) { spec |= RFC822TEXT; if(e) payload = m.headers.getString()+"\r\n"+getBodyString(m); }
+ else if (s.equals("") || s.equals("1")) { spec |= RFC822TEXT; if(e) payload = m.headers.getString()+"\r\n"+getBodyString(m); }
+ else if (s.equals("TEXT")) { spec |= RFC822TEXT; if(e) payload = getBodyString(m); }
+ else if (s.equals("HEADER")) { spec |= HEADER; if(e) payload = m.headers.getString()+"\r\n"; }
+ else if (s.equals("HEADER.FIELDS")) { spec |= FIELDS; payload=headers(r,t[i].l()[1].sl(),false,m,e); }
+ else if (s.equals("HEADER.FIELDS.NOT")) { spec |= FIELDSNOT; payload=headers(r,t[i].l()[1].sl(),true,m,e); }
+ else if (s.equals("MIME")) { throw new Server.Bad("MIME not supported"); }
+ else throw new Server.Bad("unknown section type " + s);
+ if (i<t.length - 1 && (t[i+1].s != null && t[i+1].s.startsWith("<"))) {
+ i++;
+ s = t[i].s.substring(1, t[i].s.indexOf('>'));
+ 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(Printer.qq(payload)); }
+ }
+ }
+ if (e) {
+ r.append(")");
+ println("* " + 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; j<headers.length; j++) {
+ r.append(headers[j] + (j<headers.length-1?" ":""));
+ if (m.headers.get(headers[j]) != null) payload += headers[j]+": "+m.headers.get(headers[j])+"\r\n";
+ }
+ } else {
+ throw new Server.No("HEADERS.NOT temporarily disaled");
+ /*
+ if (e) for(int j=0; j<headers.length; j++) r.append(headers[j] + (j<headers.length-1?" ":""));
+ if(e) { OUTER: for(Enumeration x=m.headers.keys(); x.hasMoreElements();) {
+ String key = (String)x.nextElement();
+ for(int j=0; j<headers.length; j++) if (key.equalsIgnoreCase(headers[j])) continue OUTER;
+ payload += key + ": " + m.headers.get(key)+"\r\n";
+ } }
+ */
+ }
+ if (e) r.append(")");
+ return payload + "\r\n";
+ }
+
+ 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)); }
+ private static final int LIST = 2; static { commands.put("LIST", new Integer(LIST)); }
+ private static final int LSUB = 3; static { commands.put("LSUB", new Integer(LSUB)); }
+ private static final int SUBSCRIBE = 4; static { commands.put("SUBSCRIBE", new Integer(SUBSCRIBE)); }
+ private static final int UNSUBSCRIBE = 5; static { commands.put("UNSUBSCRIBE", new Integer(UNSUBSCRIBE)); }
+ private static final int CAPABILITY = 6; static { commands.put("CAPABILITY", new Integer(CAPABILITY)); }
+ private static final int ID = 7; static { commands.put("ID", new Integer(ID)); }
+ private static final int LOGIN = 8; static { commands.put("LOGIN", new Integer(LOGIN)); }
+ private static final int LOGOUT = 9; static { commands.put("LOGOUT", new Integer(LOGOUT)); }
+ private static final int RENAME = 10; static { commands.put("RENAME", new Integer(RENAME)); }
+ private static final int EXAMINE = 11; static { commands.put("EXAMINE", new Integer(EXAMINE)); }
+ private static final int SELECT = 12; static { commands.put("SELECT", new Integer(SELECT)); }
+ private static final int COPY = 13; static { commands.put("COPY", new Integer(COPY)); }
+ private static final int DELETE = 14; static { commands.put("DELETE", new Integer(DELETE)); }
+ private static final int CHECK = 15; static { commands.put("CHECK", new Integer(CHECK)); }
+ private static final int NOOP = 16; static { commands.put("NOOP", new Integer(NOOP)); }
+ private static final int CLOSE = 17; static { commands.put("CLOSE", new Integer(CLOSE)); }
+ private static final int EXPUNGE = 18; static { commands.put("EXPUNGE", new Integer(EXPUNGE)); }
+ private static final int UNSELECT = 19; static { commands.put("UNSELECT", new Integer(UNSELECT)); }
+ private static final int CREATE = 20; static { commands.put("CREATE", new Integer(CREATE)); }
+ private static final int STATUS = 21; static { commands.put("STATUS", new Integer(STATUS)); }
+ private static final int FETCH = 22; static { commands.put("FETCH", new Integer(FETCH)); }
+ private static final int APPEND = 23; static { commands.put("APPEND", new Integer(APPEND)); }
+ private static final int STORE = 24; static { commands.put("STORE", new Integer(STORE)); }
+ private static final int SEARCH = 25; static { commands.put("SEARCH", new Integer(SEARCH)); }
+ }
+
+ public static class Parser {
+ private Stream stream;
+ public Parser(Stream from) { this.stream = from; }
+ public Token token(String s) { return new Token(s); }
+ protected Query query(int max, int maxuid) {