if (flags != null) { /* FEATURE */ }
}
- public void fetch(Query q, Token t) {
- Token[] tl = null;
- int start = -1, end = -1;
- boolean peek = false, fast = false, all = false, full = false;
- if (t == null) { tl = new Token[] { new Token("BODY", false) }; }
- else if (t.type == Token.LIST) tl = t.l();
- else if (t.atom().equals("FULL")) full = true;
- else if (t.atom().equals("ALL")) all = true;
- else if (t.atom().equals("FAST")) fast = true;
- StringBuffer reply = new StringBuffer();
+ public static String qq(String s) {
+ StringBuffer ret = new StringBuffer(s.length() + 20);
+ ret.append('{');
+ ret.append(s.length());
+ ret.append('}');
+ ret.append('\r');
+ ret.append('\n');
+ ret.append(s);
+ ret.append('\r');
+ ret.append('\n');
+ return ret.toString();
+ }
+
+ public void fetch(Query q, FetchRequest[] fr, boolean uid) {
+ System.err.println("query == " + q);
for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) {
Message m = it.cur();
- for (int i=0; i<tl.length; i++) {
- String s = tl[i].atom();
- if (s.startsWith("BODY.PEEK")) { peek = true; s = "BODY" + s.substring(9); }
- if (s.startsWith("BODY.1")) s = "BODY" + s.substring(6);
- if (s.indexOf('<') != -1) {
- String range = s.substring(s.indexOf('<') + 1, s.indexOf('>'));
- s = s.substring(0, s.indexOf('<'));
- if (range.indexOf(imapSeparator) == -1) end = Integer.MAX_VALUE;
- else {
- end = Integer.parseInt(range.substring(range.indexOf(imapSeparator) + 1));
- range = range.substring(0, range.indexOf(imapSeparator));
+ System.err.println("message == " + m);
+ StringBuffer reply = new StringBuffer();
+ boolean peek = false;
+ for(int i=0; i<fr.length; i++)
+ if (fr[i] == null) continue;
+ else if (fr[i] == BODYSTRUCTURE) throw new Exn.Bad("BODYSTRUCTURE not supported");
+ else if (fr[i] == ENVELOPE ) reply.append("ENVELOPE " + envelope(m) + " ");
+ else if (fr[i] == FLAGS ) reply.append("FLAGS (" + flags(it) + ") ");
+ else if (fr[i] == INTERNALDATE ) reply.append("INTERNALDATE " + quotify(m.arrival) + " ");
+ else if (fr[i] == UID ) reply.append("UID " + it.uid() + " ");
+ else if (fr[i] == RFC822 ) { reply.append("RFC822 "); reply.append(qq(m.body)); }
+ else if (fr[i] == RFC822HEADER ) { reply.append("RFC822.HEADER ");reply.append(qq(m.allHeaders));}
+ else if (fr[i] == RFC822SIZE ) reply.append("RFC822.SIZE " + m.rfc822size() + " ");
+ else if (fr[i] == RFC822TEXT ) { }
+ else if (fr[i] instanceof FetchRequestBody) {
+ FetchRequestBody frb = (FetchRequestBody)fr[i];
+ StringBuffer payload = new StringBuffer();
+ peek = frb.peek;
+ // FIXME obey path[] here
+ switch(frb.type) {
+ case FetchRequestBody.PATH: case FetchRequestBody.TEXT:
+ reply.append("BODY[TEXT] "); payload.append(m.body); break;
+ case FetchRequestBody.MIME: throw new Exn.Bad("FETCH MIME not supported");
+ case FetchRequestBody.HEADER: reply.append("BODY[HEADER] "); payload.append(m.allHeaders); break;
+ case FetchRequestBody.FIELDS:
+ reply.append("BODY[HEADER.FIELDS (");
+ for(int j=0; j<frb.headers.length; j++) {
+ reply.append(frb.headers[j].s + " ");
+ payload.append(frb.headers[j].s + ": " + m.headers.get(frb.headers[j].s) + "\r\n");
+ }
+ reply.append(")] ");
+ break;
+ case FetchRequestBody.FIELDS_NOT: throw new Exn.Bad("FETCH HEADERS.NOT not supported");
}
- start = Integer.parseInt(range);
- }
- if (s.equals("ENVELOPE") || all || full) reply.append("ENVELOPE " + envelope(m) + " ");
- if (s.equals("FLAGS") || full || all || fast) reply.append("FLAGS (" + flags(it) + ") ");
- if (s.equals("INTERNALDATE") || full || all || fast) reply.append("INTERNALDATE "+quotify(m.arrival)+" ");
- if (s.equals("RFC822.SIZE") || full || all || fast) reply.append("RFC822.SIZE " + m.rfc822size() + " ");
- if (s.equals("RFC822.HEADER") || s.equals("BODY[HEADER]"))
- { reply.append("BODY[HEADER] {" + m.allHeaders.length() + "}\r\n"); reply.append(m.allHeaders); }
- if (s.equals("RFC822")||s.equals("RFC822.TEXT")||s.equals("BODY")||s.equals("BODY[]")||s.equals("TEXT")||full)
- { reply.append("BODY[TEXT] {" + m.body.length() + "}\r\n"); reply.append(m.body); }
- if (s.equals("UID")) reply.append("UID " + it.uid());
- if (s.equals("MIME")) throw new Exn.No("FETCH BODY.MIME not supported");
- if (s.startsWith("BODY[HEADER.FIELDS")) throw new Exn.No("partial headers not supported");
- if (s.startsWith("BODY[HEADER.FIELDS.NOT")) throw new Exn.No("partial headers not supported");
- if (s.equals("BODYSTRUCTURE"))
- reply.append("(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" " +
- m.rfc822size()+" "+ m.lines +")");
- }
- star(it.num() + " FETCH (" + reply.toString() + ")");
- // FEATURE: range requests
- // FEATURE set seen flag if not BODY.PEEK
+ if (frb.start == -1) reply.append(qq(payload.toString()));
+ else if (frb.start == -1) reply.append("<" + frb.end + ">" + qq(payload.substring(0, frb.end + 1)));
+ else reply.append("<" + frb.start + "." + frb.end + ">" + qq(payload.substring(frb.start, frb.end + 1)));
+ } else throw new Error("this should never happen");
+ star((uid ? it.uid() : it.num()) + " FETCH (" + reply.toString() + (uid ? " UID " + it.uid() : "") + ")");
+ if (!peek) it.seen(true);
}
}
+
// FEATURE: hoist the parsing here
public void store(Query q, String what, Token[] flags) {
for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) {
if (command.equalsIgnoreCase("AUTHENTICATE")) { login(astring(), astring()); }
else if (command.equalsIgnoreCase("LIST")) list(mailbox(), mailboxPattern());
else if (command.equalsIgnoreCase("LSUB")) lsub(mailbox(), mailboxPattern());
+ else if (command.equalsIgnoreCase("SUBSCRIBE")) { /* FIXME */ }
+ else if (command.equalsIgnoreCase("UNSUBSCRIBE")) { /* FIXME */ }
else if (command.equalsIgnoreCase("CAPABILITY")) { capability(); }
else if (command.equalsIgnoreCase("LOGIN")) login(astring(), astring());
else if (command.equalsIgnoreCase("LOGOUT")) { logout(); conn.close(); return false; }
else if (command.equalsIgnoreCase("NOOP")) noop();
else if (command.equalsIgnoreCase("CREATE")) create(astring());
else if (command.equalsIgnoreCase("STORE")) store(Query.num(set()), atom(), l());
- else if (command.equalsIgnoreCase("FETCH")) fetch(Query.num(set()), token());
+ else if (command.equalsIgnoreCase("FETCH")) fetch(uid ? Query.uid(set()) : Query.num(set()),
+ parseFetchRequest(l()), uid);
else if (command.equalsIgnoreCase("STATUS")) status(mailbox(), l());
else throw new Exn.Bad("unrecognized command \"" + command + "\"");
- pw.println(tag + " OK " + command + " Completed.");
- System.err.println(tag + " OK " + command + " Completed.");
+ pw.println(tag + " OK uid " + command + " Completed.");
+ System.err.println(tag + " OK uid " + command + " Completed.");
} catch (Exn.Bad b) { pw.println(tag + " Bad " + b.toString()); System.err.println(tag + " Bad " + b.toString()); b.printStackTrace();
} catch (Exn.No n) { pw.println(tag + " OK " + n.toString()); System.err.println(tag + " OK " + n.toString());
}
}
class Token {
- private byte type;
- private final String s;
- private final Token[] l;
- private final int n;
+ 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;
String end_s = s.substring(s.indexOf(':')+1);
if (end_s.equals("*")) {
ids.addElement(new Integer(start));
- ids.addElement(new Integer(-1));
+ ids.addElement(new Integer(Integer.MAX_VALUE));
} else {
int end = Integer.parseInt(end_s);
for(int j=start; j<=end; j++) ids.addElement(new Integer(j));
}
} else {
ids.addElement(new Integer(Integer.parseInt(s)));
+ ids.addElement(new Integer(Integer.parseInt(s)));
}
}
int[] ret = new int[ids.size()];
else sb.append(c);
}
return new Token(sb.toString(), true);
- } else if (c == ')') {
- return null;
- } else if (c == '(') {
+
+ // 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 null; // FIXME
+ return new Token(ret); // FIXME
+
} else while(true) {
sb.append(c);
c = peekc();
- if (c == ' ' || c == '\"' || c == '(' || c == ')' || c == '{' || c == '\n' || c == '\r')
+ if (c == ' ' || c == '\"' || c == '(' || c == ')' || c == '[' || c == ']' ||
+ c == '{' || c == '\n' || c == '\r')
return new Token(sb.toString(), false);
getc();
}
public String astring() throws Exn.Bad { return token().astring(); }
public String atom() throws Exn.Bad { return token().atom(); }
public Mailbox mailbox() throws Exn.Bad, IOException { return token().mailbox(); }
+
+ public final FetchRequest BODYSTRUCTURE = new FetchRequest();
+ public final FetchRequest ENVELOPE = new FetchRequest();
+ public final FetchRequest FLAGS = new FetchRequest();
+ public final FetchRequest INTERNALDATE = new FetchRequest();
+ public final FetchRequest RFC822 = new FetchRequest();
+ public final FetchRequest RFC822HEADER = new FetchRequest();
+ public final FetchRequest RFC822SIZE = new FetchRequest();
+ public final FetchRequest RFC822TEXT = new FetchRequest();
+ public final FetchRequest UID = new FetchRequest();
+ public final FetchRequest BODY = new FetchRequest();
+ public final FetchRequest BODYPEEK = new FetchRequest();
+ public class FetchRequest {
+ }
+ public class FetchRequestBody extends FetchRequest {
+ int type = 0;
+ boolean peek = false;
+ int[] path = null;
+ int start = -1;
+ int end = -1;
+ Token[] headers = null;
+ public static final int PATH = 0;
+ public static final int TEXT = 1;
+ public static final int MIME = 2;
+ public static final int HEADER = 3;
+ public static final int FIELDS = 4;
+ public static final int FIELDS_NOT= 5;
+ }
+
+ public FetchRequest[] parseFetchRequest(Token[] t) {
+ FetchRequest[] ret = new FetchRequest[t.length];
+ for(int i=0; i<t.length; i++) {
+ if (t[i] == null) continue;
+ if (t[i].s.equalsIgnoreCase("BODYSTRUCTURE")) ret[i] = BODYSTRUCTURE;
+ else if (t[i].s.equalsIgnoreCase("ENVELOPE")) ret[i] = ENVELOPE;
+ else if (t[i].s.equalsIgnoreCase("FLAGS")) ret[i] = FLAGS;
+ else if (t[i].s.equalsIgnoreCase("INTERNALDATE")) ret[i] = INTERNALDATE;
+ else if (t[i].s.equalsIgnoreCase("RFC822")) ret[i] = RFC822;
+ else if (t[i].s.equalsIgnoreCase("RFC822.HEADER")) ret[i] = RFC822HEADER;
+ else if (t[i].s.equalsIgnoreCase("RFC822.SIZE")) ret[i] = RFC822SIZE;
+ else if (t[i].s.equalsIgnoreCase("RFC822.TEXT")) ret[i] = RFC822TEXT;
+ else if (t[i].s.equalsIgnoreCase("UID")) ret[i] = UID;
+ else {
+ FetchRequestBody b = new FetchRequestBody();
+ String s = t[i].s.toUpperCase();
+
+ String startend = null;
+ if (s.indexOf('<') != -1 && s.endsWith(">")) {
+ startend = s.substring(s.indexOf('<'), s.indexOf('>'));
+ s = s.substring(0, s.indexOf('<'));
+ }
+
+ if (s.equalsIgnoreCase("BODY.PEEK")) b.peek = true;
+ else if (s.equalsIgnoreCase("BODY")) b.peek = false;
+ else throw new Exn.No("unknown fetch argument: " + s);
+
+ if (i<t.length - 1 && (t[i+1].type == t[i].LIST)) {
+ i++;
+ Token[] t2 = t[i].l();
+ if (t2.length == 0) {
+ b.type = b.TEXT;
+ } else {
+ String s2 = t2[0].s.toUpperCase();
+ Vec mimeVec = new Vec();
+ while(s2.charAt(0) >= '0' && s2.charAt(0) <= '9') {
+ mimeVec.addElement(new Integer(Integer.parseInt(s2.substring(0, s2.indexOf('.')))));
+ s2 = s2.substring(s2.indexOf('.') + 1);
+ }
+ if (mimeVec.size() > 0) {
+ b.path = new int[mimeVec.size()];
+ for(int j=0; j<b.path.length; j++) b.path[j] = ((Integer)mimeVec.elementAt(j)).intValue();
+ }
+ if (s2.equals("")) { b.type = b.PATH; }
+ else if (s2.equals("HEADER")) { b.type = b.HEADER; }
+ else if (s2.equals("HEADER.FIELDS")) { b.type = b.FIELDS; b.headers = t2[1].l(); }
+ else if (s2.equals("HEADER.FIELDS.NOT")) { b.type = b.FIELDS_NOT; b.headers = t2[1].l(); }
+ else if (s2.equals("MIME")) { b.type = b.MIME; }
+ else if (s2.equals("TEXT")) { b.type = b.TEXT; }
+ }
+ }
+
+ if (i<t.length - 1 && (t[i+1].s.startsWith("<"))) {
+ i++;
+ startend = t[i].s.substring(1, t[i].s.length() - 1);
+ }
+
+ if (startend != null) {
+ if (startend.indexOf('.') == -1) {
+ b.start = Integer.parseInt(startend);
+ b.end = -1;
+ } else {
+ b.start = Integer.parseInt(startend.substring(0, startend.indexOf('.')));
+ b.end = Integer.parseInt(startend.substring(startend.indexOf('.') + 1));
+ }
+ }
+ ret[i] = b;
+ }
+ }
+ return ret;
+ }
+
}
}