From: adam Date: Mon, 28 Jun 2004 23:06:35 +0000 (+0000) Subject: mail overhaul X-Git-Url: http://git.megacz.com/?a=commitdiff_plain;h=071000f577ab3a75dc06560dfa1983331df2bd98;p=org.ibex.mail.git mail overhaul darcs-hash:20040628230635-5007d-b1527d1020731690a9d1eaf12a4b945e640f65a7.gz --- diff --git a/Makefile b/Makefile index d37d5ca..508dffa 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ dist-clean: rm -rf .configure* .install* build .compile .build* sources := $(shell find src -name \*.java) -sources += upstream/org.ibex.crypto/src/org/ibex/crypto/Base36.java +#sources += upstream/org.ibex.crypto/src/org/ibex/crypto/Base36.java upstream/org.ibex.crypto/src/org/ibex/crypto/Base36.java: .download_org.ibex.crypto @@ -20,10 +20,14 @@ upstream/org.ibex.crypto/src/org/ibex/crypto/Base36.java: .download_org.ibex.cry @cd upstream/org.ibex.$*; make compile @touch $@ -mail.jar: $(sources) +compile: .compile +.compile: $(sources) @make .download_org.ibex.crypto .build_org.ibex.core @mkdir -p build/class - @javac -d build/class -classpath upstream/org.ibex.core/build/class $^ + @javac -d build/class -classpath /jinetd/LIB/ibex.core.jar:build/class $^ + @touch $@ + +mail.jar: .compile @cd build/class; jar cvf ../../mail.jar . #run: mail.jar diff --git a/src/org/ibex/mail/Address.java b/src/org/ibex/mail/Address.java index 92bdbc8..ebf349b 100644 --- a/src/org/ibex/mail/Address.java +++ b/src/org/ibex/mail/Address.java @@ -7,25 +7,26 @@ import java.util.*; import java.net.*; import java.io.*; +// FIXME this should be more forgiving public class Address extends JSReflection { public final String user; public final String host; public final String description; + public static Address parse(String s) { try { return new Address(s); } catch (Malformed _) { return null; } } public Address(String user, String host, String description) {this.user=user;this.host=host;this.description=description;} - public Address(String s) throws Address.Malformed { - s = s.trim(); - if (s.indexOf('<') != -1) { + public Address(String s0) throws Address.Malformed { + String s = s0.trim(); + if (s.indexOf('<') == -1) description = ""; + else { if (s.indexOf('>') == -1) { throw new Malformed("found open-angle-bracket (<) but not close-angle-bracket (>)"); } description = s.substring(0, s.indexOf('<')) + s.substring(s.indexOf('>') + 1); s = s.substring(s.indexOf('<') + 1, s.indexOf('>')); - } else { - description = ""; } - if (s.indexOf('@') == -1) { throw new Malformed("no @-sign in email address"); } + if (s.indexOf('@') == -1) { throw new Malformed("no @-sign in email address \""+s0+"\""); } user = s.substring(0, s.indexOf('@')); host = s.substring(s.indexOf('@')+1); } public String coerceToString() { return toString(); } public String toString() { return description.equals("") ? (user +"@"+ host) : description+" <" + user +"@"+ host + ">"; } - public static class Malformed extends MailException.Malformed { public Malformed(String s) { super(s); } } + public static class Malformed extends RuntimeException { public Malformed(String s) { super(s); } } } diff --git a/src/org/ibex/mail/Main.java b/src/org/ibex/mail/Main.java index 8bd9da6..f607f5f 100644 --- a/src/org/ibex/mail/Main.java +++ b/src/org/ibex/mail/Main.java @@ -8,6 +8,33 @@ import java.util.*; public class Main { + /* + public static class Resin { + new extends com.caucho.server.port.Protocol() { + public String getProtocolName() { return "imap"; } + public com.caucho.server.port.ServerRequest createRequest(com.caucho.server.connection.Connection conn) { + try { + return new Listener(conn); + } catch (Exception e) { + Log.error(this, e); + return null; + } + } + } + + public SMTP() { } + public String getProtocolName() { return "smtp"; } + public com.caucho.server.port.ServerRequest createRequest(com.caucho.server.connection.Connection conn) { + try { + return new Server(conn); + } catch (Exception e) { + Log.error(this, e); + return null; + } + } + + } + public static void main(String[] s) throws Exception { // set up logging @@ -21,8 +48,8 @@ public class Main { new SMTPThread().start(); } - - private static class BogusAuthenticator implements IMAP.Server.Authenticator { + */ + public static class BogusAuthenticator implements IMAP.Server.Authenticator { final Mailbox root = FileBasedMailbox.getFileBasedMailbox(Mailbox.STORAGE_ROOT, true).slash("user", true).slash("megacz", true); public Mailbox authenticate(String u, String p) { @@ -30,7 +57,7 @@ public class Main { return null; } } - + /* private static class IMAPThread extends Thread { final int port = Integer.parseInt(System.getProperty("ibex.mail.imap.port", "143")); public void run() { @@ -81,4 +108,5 @@ public class Main { } } } + */ } diff --git a/src/org/ibex/mail/Message.java b/src/org/ibex/mail/Message.java index 793a3b4..d27ae1c 100644 --- a/src/org/ibex/mail/Message.java +++ b/src/org/ibex/mail/Message.java @@ -1,8 +1,9 @@ package org.ibex.mail; import org.ibex.crypto.*; -import org.ibex.js.*; import org.ibex.util.*; import org.ibex.mail.protocol.*; +import org.ibex.io.*; +import org.ibex.net.*; import java.util.*; import java.net.*; import java.io.*; @@ -26,7 +27,7 @@ import java.io.*; * ether": RFC2822 data but no storage-specific flags or other * metadata. */ -public class Message extends JSReflection { +public class Message extends org.ibex.js.JSReflection { public final String allHeaders; // pristine headers public final CaseInsensitiveHash headers; // hash of headers (not including resent's and traces) @@ -49,28 +50,30 @@ public class Message extends JSReflection { public final Date arrival; // when the message first arrived at this machine; IMAP "internal date message attr" - public void dump(OutputStream os) throws IOException { - Writer w = new OutputStreamWriter(os); - w.write("X-org.ibex.mail.headers.envelope.From: " + envelopeFrom + "\r\n"); - w.write("X-org.ibex.mail.headers.envelope.To: " + envelopeTo + "\r\n"); - w.write(allHeaders); - w.write("\r\n"); - w.write(body); - w.flush(); + public int size() { return allHeaders.length() + 2 /* CRLF */ + body.length(); } + public String toString() { return allHeaders + "\r\n" + body; } + public void dump(Stream s) { + s.out.setNewline("\r\n"); + s.out.println("X-org.ibex.mail.headers.envelope.From: " + envelopeFrom); + s.out.println("X-org.ibex.mail.headers.envelope.To: " + envelopeTo); + s.out.println(allHeaders); + s.out.println(); + s.out.println(body); + s.out.flush(); } public class Trace { final String returnPath; final Element[] elements; - public Trace(LineReader lr) throws Trace.Malformed, IOException { - String retPath = lr.readLine(); + public Trace(Stream stream) throws Trace.Malformed { + String retPath = stream.in.readln(); if (!retPath.startsWith("Return-Path:")) throw new Trace.Malformed("trace did not start with Return-Path header"); returnPath = retPath.substring(12).trim(); Vec el = new Vec(); while(true) { - String s = lr.readLine(); + String s = stream.in.readln(); if (s == null) break; - if (!s.startsWith("Received:")) { lr.pushback(s); break; } + if (!s.startsWith("Received:")) { stream.in.unread(s); break; } s = s.substring(9).trim(); el.addElement(new Element(s)); } @@ -97,95 +100,92 @@ public class Message extends JSReflection { public class Malformed extends Message.Malformed { public Malformed(String s) { super(s); } } } - public static class Malformed extends MailException.Malformed { public Malformed(String s) { super(s); } } - - public Message(Address envelopeFrom, Address envelopeTo, String s, Date arrival) - { this(envelopeFrom, envelopeTo, new LineReader(new StringReader(s)), arrival); } - public Message(Address envelopeFrom, Address envelopeTo, LineReader rs) { this(envelopeFrom, envelopeTo, rs, null); } - public Message(Address envelopeFrom, Address envelopeTo, LineReader rs, Date arrival) { - try { - this.arrival = arrival == null ? new Date() : arrival; - this.headers = new CaseInsensitiveHash(); - Vec envelopeToHeader = new Vec(); - String key = null; - StringBuffer all = new StringBuffer(); - Date date = null; - Address to = null, from = null, replyto = null; - String subject = null, messageid = null; - Vec cc = new Vec(), bcc = new Vec(), resent = new Vec(), traces = new Vec(); - int lines = 0; - for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) { - all.append(s); - lines++; - 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"); - ((CaseInsensitiveHash)headers).add(key, headers.get(key) + s); - continue; - } - if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s); - key = s.substring(0, s.indexOf(':')); - for(int i=0; i 126) - throw new Malformed("Header key \""+key+"\" contains invalid character \"" + key.charAt(i) + "\""); - String val = s.substring(s.indexOf(':') + 1).trim(); - while(val.length() > 0 && Character.isSpace(val.charAt(0))) val = val.substring(1); - if (key.startsWith("Resent-")) { - if (key.startsWith("Resent-From")) resent.addElement(new Hashtable()); - ((Hashtable)resent.lastElement()).put(key.substring(7), val); - } else if (key.startsWith("Return-Path")) { - rs.pushback(s); traces.addElement(new Trace(rs)); - } else if (key.equals("X-org.ibex.mail.headers.envelope.From")) { - if (envelopeFrom == null) envelopeFrom = new Address(val); - } else if (key.equals("X-org.ibex.mail.headers.envelope.To")) { - if (envelopeTo == null) envelopeTo = new Address(val); - } else { - // just append it to the previous one; valid for Comments/Keywords - if (headers.get(key) != null) val = headers.get(key) + " " + val; - ((CaseInsensitiveHash)headers).add(key, val); - } + public static class Malformed extends RuntimeException { public Malformed(String s) { super(s); } } + + public Message(Address from, Address to, String s, Date arrival) { this(from, to, new Stream(s), arrival); } + public Message(Address from, Address to, Stream in) { this(from, to, in, new Date()); } + public Message(Address envelopeFrom, Address envelopeTo, Stream stream, Date arrival) { + this.arrival = arrival; + this.headers = new CaseInsensitiveHash(); + Vec envelopeToHeader = new Vec(); + String key = null; + StringBuffer all = new StringBuffer(); + Date date = null; + Address to = null, from = null, replyto = null; + String subject = null, messageid = null; + Vec cc = new Vec(), bcc = new Vec(), resent = new Vec(), traces = new Vec(); + int lines = 0; + for(String s = stream.in.readln(); s != null && !s.equals(""); s = stream.in.readln()) { + all.append(s); + lines++; + 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"); + ((CaseInsensitiveHash)headers).add(key, headers.get(key) + s); + continue; } - - // FIXME what if all are null? - this.to = headers.get("To") == null ? envelopeTo : new Address((String)headers.get("To")); - this.from = headers.get("From") == null ? envelopeFrom : new Address((String)headers.get("From")); - this.envelopeFrom = envelopeFrom == null ? this.from : envelopeFrom; - this.envelopeTo = envelopeTo == null ? this.to : envelopeTo; - - this.date = new Date(); // FIXME (Date)headers.get("Date"); - this.replyto = headers.get("Reply-To") == null ? null : new Address((String)headers.get("Reply-To")); - this.subject = (String)headers.get("Subject"); - this.messageid = (String)headers.get("Message-Id"); - if (headers.get("Cc") != null) { - // FIXME: tokenize better - StringTokenizer st = new StringTokenizer((String)headers.get("Cc")); - this.cc = new Address[st.countTokens()]; - for(int i=0; i 126) + throw new Malformed("Header key \""+key+"\" contains invalid character \"" + key.charAt(i) + "\""); + String val = s.substring(s.indexOf(':') + 1).trim(); + while(val.length() > 0 && Character.isSpace(val.charAt(0))) val = val.substring(1); + if (key.startsWith("Resent-")) { + if (resent.size() == 0 || key.startsWith("Resent-From")) resent.addElement(new Hashtable()); + ((Hashtable)resent.lastElement()).put(key.substring(7), val); + } else if (key.startsWith("Return-Path")) { + stream.in.unread(s); traces.addElement(new Trace(stream)); + } else if (key.equals("X-org.ibex.mail.headers.envelope.From")) { + if (envelopeFrom == null) envelopeFrom = new Address(val); + } else if (key.equals("X-org.ibex.mail.headers.envelope.To")) { + if (envelopeTo == null) envelopeTo = new Address(val); } else { - this.cc = new Address[0]; - } - if (headers.get("Bcc") != null) { - StringTokenizer st = new StringTokenizer((String)headers.get("Bcc")); - this.bcc = new Address[st.countTokens()]; - for(int i=0; i= it.uid()) return true; + for(int i=0; i= it.uid()) return true; return false; } else return it.uid() >= min && it.uid() <= max; case NUM: if (set != null) { @@ -92,7 +93,7 @@ public class Query { (earliest==null||it.cur().date.after(earliest)); case ARRIVAL: return (latest == null || it.cur().arrival.before(latest)) && (earliest == null || it.cur().arrival.after(earliest)); - case SIZE: return it.cur().rfc822size() >= min && it.cur().rfc822size() <= max; + case SIZE: return it.cur().size() >= min && it.cur().size() <= max; case HEADER: return it.cur().headers.get(key) != null && ((String)it.cur().headers.get(key)).toLowerCase().indexOf(text.toLowerCase()) != -1; case BODY: return it.cur().body.toLowerCase().indexOf(text.toLowerCase()) != -1; diff --git a/src/org/ibex/mail/Target.java b/src/org/ibex/mail/Target.java index f845f9f..b9c4363 100644 --- a/src/org/ibex/mail/Target.java +++ b/src/org/ibex/mail/Target.java @@ -3,7 +3,7 @@ import java.io.*; import org.ibex.js.*; /** base class for mail message "destinations" */ -public class Target { +public class Target extends JS { public static final Target root = Script.root(); public void accept(Message m) throws IOException, MailException { throw new MailException("Target.accept() unimplemented"); diff --git a/src/org/ibex/mail/protocol/Connection.java b/src/org/ibex/mail/protocol/Connection.java deleted file mode 100644 index d1cca88..0000000 --- a/src/org/ibex/mail/protocol/Connection.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.ibex.mail.protocol; -import org.ibex.mail.*; -import org.ibex.util.*; -import org.ibex.mail.target.*; -import java.util.*; -import java.net.*; -import java.text.*; -import java.io.*; - -/** central place for logging conversations */ -public abstract class Connection { - - protected Socket conn; - protected String vhost; - protected PrintWriter pw; - protected InputStream is; - protected PushbackReader r; - protected LineReader lr; - - private static String convdir; - static { - try { - convdir = System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail"); - convdir += File.separatorChar + "conversation"; - new File(convdir).mkdirs(); - } catch (Exception e) { - Log.warn(Connection.class, e); - } - } - - public Connection(Socket conn, String vhost) throws IOException { - this.vhost = vhost; - this.conn = conn; - this.pw = new PrintWriter(new OutputStreamWriter(conn.getOutputStream())); - this.is = conn.getInputStream(); - Reader isr = new InputStreamReader(is); - this.r = new PushbackReader(isr); - this.lr = new LineReader(isr); - } - - protected abstract boolean handleRequest() throws IOException; - - private String cid; - private StringBuffer inbound = new StringBuffer(); - private PrintWriter conversation; - - public final boolean handle() throws IOException { - cid = getConversation(); - Log.setThreadAnnotation("[conversation " + cid + "] "); - InetSocketAddress remote = (InetSocketAddress)conn.getRemoteSocketAddress(); - Log.info(this, "connection from "+remote.getHostName()+":"+remote.getPort()+" ("+remote.getAddress()+")"); - conversation = new PrintWriter(new OutputStreamWriter(new FileOutputStream(convdir + File.separatorChar + cid + ".txt"))); - boolean ret = false; - try { - ret = handleRequest(); - Log.setThreadAnnotation(""); - } finally { - conversation.close(); - } - return ret; - } - - protected void println(String s) throws IOException { - if (inbound.length() > 0) { conversation.println("C: " + inbound.toString()); inbound.setLength(0); } - conversation.println("S: " + s); - conversation.flush(); - pw.print(s); - pw.print("\r\n"); - pw.flush(); - } - protected void flush() throws IOException { pw.flush(); } - protected String readln() throws IOException { - String line = lr.readLine(); - conversation.println("C: " + line); - conversation.flush(); - return line; - } - public char getc() throws IOException { - int ret = r.read(); - if (ret == -1) throw new EOFException(); - if (ret == '\n') { - if (inbound.length() > 0) { conversation.println("C: " + inbound.toString()); inbound.setLength(0); } } - else if (ret != '\r') inbound.append((char)ret); - conversation.flush(); - return (char)ret; - } - public char peekc() throws IOException { - int ret = r.read(); - if (ret == -1) throw new EOFException(); - r.unread(ret); - return (char)ret; - } - public void fill(byte[] b) throws IOException { - int num = 0; - while (num < b.length) { - int numread = is.read(b, num, b.length - num); - if (numread == -1) throw new EOFException(); - num += numread; - } - } - - private static String lastTime = null; - private static int lastCounter = 0; - static String getConversation() { - String time = new SimpleDateFormat("yy.MMM.dd-hh:mm:ss").format(new Date()); - synchronized (SMTP.class) { - if (lastTime != null && lastTime.equals(time)) time += "." + (++lastCounter); - else lastTime = time; - } - return time; - } - -} diff --git a/src/org/ibex/mail/protocol/IMAP.java b/src/org/ibex/mail/protocol/IMAP.java index f6670f6..2291826 100644 --- a/src/org/ibex/mail/protocol/IMAP.java +++ b/src/org/ibex/mail/protocol/IMAP.java @@ -1,4 +1,7 @@ package org.ibex.mail.protocol; +import org.ibex.io.*; +import org.ibex.jinetd.Listener; +import org.ibex.net.*; import org.ibex.mail.*; import org.ibex.util.*; import org.ibex.mail.target.*; @@ -6,7 +9,7 @@ import java.util.*; import java.net.*; import java.text.*; import java.io.*; - + // FIXME: this is valid LSUB "" asdfdas%*%*%*%*SFEFGWEF // FIXME: be very careful about where/when we quotify stuff // FIXME: 'UID FOO 100:*' must match at least one message even if all UIDs less than 100 @@ -29,16 +32,19 @@ import java.io.*; // FEATURE: STARTTLS // FEATURE: asynchronous client notifications (need to re-read RFC) -public class IMAP { +public class IMAP implements Listener { + + public void accept(Connection c) { + Log.error(this, "IMAP got a connection!"); + new Listener().handleRequest(c); + c.close(); + } + public IMAP() { } public static final float version = (float)0.1; // API Class ////////////////////////////////////////////////////////////////////////////// - public static final int - 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 static interface Client { public void expunge(int uid); public void list(char separator, String mailbox, boolean lsub, boolean phantom); @@ -161,7 +167,7 @@ public class IMAP { 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 close() { for(Mailbox.Iterator it=selected().iterator(Query.deleted()); it.next();) 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) { } @@ -171,7 +177,9 @@ public class IMAP { 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 void select(String mailbox, boolean examineOnly) { + selected = mailbox(mailbox, false); + } public int[] search(Query q, boolean uid) { Vec.Int vec = new Vec.Int(); @@ -193,7 +201,8 @@ public class IMAP { 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()); + // FIXME + //if (!silent) client.fetch(it.num(), it.flags(), -1, null, it.uid()); } } public void rename(String from0, String to) { @@ -204,7 +213,7 @@ public class IMAP { } public void fetch(Query q, int spec, String[] headers, int start, int end, boolean uid) { for(Mailbox.Iterator it = selected().iterator(q); it.next(); ) { - client.fetch(it.num(), it.flags(), it.cur().rfc822size(), it.cur(), it.uid()); + client.fetch(it.num(), it.flags(), it.cur().size(), it.cur(), it.uid()); it.recent(false); } } @@ -214,32 +223,119 @@ public class IMAP { // Single Session Handler ////////////////////////////////////////////////////////////////////////////// /** takes an IMAP.Server and exposes it to the world as an IMAP server on a TCP socket */ - public static class Listener extends Parser implements Client { + public static class Listener implements Worker, Client { String selectedName = null; Mailbox inbox = null, root = null; - final Server api; - public Listener(Socket conn, Server.Authenticator auth) throws IOException { - this(conn, java.net.InetAddress.getLocalHost().getHostName(), auth); } - public Listener(Socket conn, String vhost, Server.Authenticator auth) throws IOException { - super(conn, vhost); - this.api = new IMAP.MailboxWrapper(auth, this); + Server api; + Parser parser = null; + Connection conn = null; + public Listener() { } + Parser.Token token() { return parser.token(); } + void println(String s) { conn.println(s); } + void newline() { parser.newline(); } + Query query() { return parser.query(); } + public void handleRequest(Connection conn) { + parser = new Parser(conn); + this.conn = conn; + api = new IMAP.MailboxWrapper(new Main.BogusAuthenticator(), this); + conn.setTimeout(30 * 60 * 1000); + println("* OK " + conn.vhost + " " + IMAP.class.getName() + " IMAP4rev1 [RFC3501] v" + version + " server ready"); + for(String tag = null;; newline()) try { + conn.out.flush(); + boolean uid = false; + tag = null; Parser.Token tok = token(); if (tok == null) return; tag = tok.astring(); + String command = token().atom(); + if (command.equalsIgnoreCase("UID")) { uid = true; command = token().atom(); } + int commandKey = ((Integer)commands.get(command.toUpperCase())).intValue(); + switch(commandKey) { + case LOGIN: api.login(token().astring(), token().astring()); break; + case CAPABILITY: println("* CAPABILITY " + Printer.join(" ", api.capability())); break; + case AUTHENTICATE: throw new Server.No("AUTHENTICATE not supported"); + case LOGOUT: api.logout(); println("* BYE"); conn.close(); return; + case LIST: api.list(token().q(), token().q()); break; + case LSUB: api.lsub(token().q(), token().q()); break; + 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()), token().astring()); break; + case DELETE: api.delete(token().atom()); break; + case CHECK: selected(); api.check(); break; + case NOOP: api.noop(); break; + case CLOSE: selected(); api.close(); 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()), + lastfetch=token().lx(), 0, 0, 0, uid, 0); break; + case SEARCH: selected(); println("* SEARCH " + Printer.join(api.search(query(), uid))); break; + case EXAMINE: + case SELECT: { + String mailbox = token().astring(); + api.select(mailbox, commandKey==EXAMINE); + println("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)"); + println("* " + api.count(mailbox) + " EXISTS"); + println("* " + api.recent(mailbox) + " RECENT"); + println("* OK [UIDVALIDITY " + api.uidValidity(mailbox) + "] UIDs valid"); + println("* OK [UIDNEXT " + api.uidNext(mailbox) + "]"); + println("* OK [PERMANENTFLAGS (\\Seen \\Draft \\Answered \\Deleted)]"); + selected = true; + break; } + case STATUS: { + String mailbox = token().astring(); // hack for GNUS buggy client + Parser.Token[] list = token().l(); + String response = ""; + for(int i=0; ior emit a fetch reply. @@ -249,7 +345,7 @@ public class IMAP { * - 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) { + private void fetch(Object o, Parser.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; @@ -264,25 +360,25 @@ public class IMAP { if (uid) { boolean good = false; for(int i=0; i initlen) r.append(" "); @@ -294,23 +390,26 @@ public class IMAP { } 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("RFC822.TEXT")) { spec|=RFC822TEXT; if(e){r.append(" ");r.append(Printer.qq(m.body));} + } else if (s.equals("RFC822.HEADER")){ spec|=HEADER;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.size());} } 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, 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("["); - Token[] list = t[++i].l(); + 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.body; } - else if (s.equals("")) { spec |= RFC822TEXT; if(e) payload = m.body; } + if (list.length == 0) { spec |= RFC822TEXT; if(e) payload = m.allHeaders+"\r\n"+m.body; } + else if (s.equals("")) { spec |= RFC822TEXT; if(e) payload = m.allHeaders+"\r\n"+m.body; } else if (s.equals("TEXT")) { spec |= RFC822TEXT; if(e) payload = m.body; } else if (s.equals("HEADER")) { spec |= HEADER; if(e) payload = m.allHeaders+"\r\n"; } else if (s.equals("HEADER.FIELDS")) { spec |= FIELDS; payload=headers(r,t[i].l()[1].sl(),false,m,e); } @@ -325,12 +424,12 @@ public class IMAP { 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("] "); r.append(Printer.qq(payload)); } } } if (e) { r.append(")"); - star(r.toString()); + println("* " + r.toString()); } else { api.fetch(q, spec, headers, start, end, uid); } @@ -345,104 +444,20 @@ public class IMAP { 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= names.length) return null; try { File file = new File(path + File.separatorChar + names[cur]); - return new Message(null, null, new LineReader(new InputStreamReader(new FileInputStream(file)))); + return new Message(null, null, new Stream(new FileInputStream(file))); } catch (IOException e) { throw new MailException.IOException(e); } } public boolean next() { cur++; if (cur >= names.length) return false; String name = names[cur].substring(names[cur].indexOf('.') + 1); + if (!(new File(path + File.separatorChar + names[cur])).exists()) return next(); seen = name.indexOf('s') != -1; deleted = name.indexOf('x') != -1; flagged = name.indexOf('f') != -1; @@ -145,11 +147,14 @@ public class FileBasedMailbox extends Mailbox.Default { (draft ? "d" : "") + (recent ? "r" : "") + (answered ? "a" : ""); - new File(names[cur]).renameTo(new File(path + File.separatorChar + newName)); + new File(path + File.separatorChar + names[cur]).renameTo(new File(path + File.separatorChar + newName)); names[cur] = newName; } - public void delete() { new File(names[cur]).delete(); } + public void delete() { + new File(path + File.separatorChar + names[cur]).delete(); + // FIXME remove from list? + } public void set(String key, String val) { throw new MailException("not supported"); } public String get(String key) { throw new MailException("not supported"); } diff --git a/src/org/ibex/mail/target/Transcript.java b/src/org/ibex/mail/target/Transcript.java index fbb485c..5d105bf 100644 --- a/src/org/ibex/mail/target/Transcript.java +++ b/src/org/ibex/mail/target/Transcript.java @@ -1,7 +1,7 @@ package org.ibex.mail.target; import org.ibex.mail.*; import org.ibex.util.*; -import org.ibex.mail.*; +import org.ibex.io.*; import java.io.*; import java.net.*; import java.util.*; @@ -34,7 +34,7 @@ public class Transcript extends Target { File target = new File(today.getPath() + File.separatorChar + time + ".txt"); OutputStream os = new FileOutputStream(target); - message.dump(os); + message.dump(new Stream(os)); os.flush(); os.close(); } catch (IOException e) { throw new MailException.IOException(e); } diff --git a/upstream/org.ibex.core/AUTHORS b/upstream/org.ibex.core/AUTHORS deleted file mode 100644 index ca0f31f..0000000 --- a/upstream/org.ibex.core/AUTHORS +++ /dev/null @@ -1,17 +0,0 @@ - -[megacz] Adam Megacz - - Original Architect - - Ibex Engine - - Most of the widget set - -[david] David Crawshaw - - Widget Czar - - Fixes to slider.ibex - -[corey] Corey Jewett - - Patch to make build.xml not depend on task - -[ejones] Evan Jones - - Google demo - - Dynamic Trees - diff --git a/upstream/org.ibex.core/CHANGES b/upstream/org.ibex.core/CHANGES deleted file mode 100644 index c40c796..0000000 --- a/upstream/org.ibex.core/CHANGES +++ /dev/null @@ -1,1066 +0,0 @@ - -== 2002 ================================================================= - -10-Apr megacz Box.java, Surface.java: fixed rendering glitch caused by - lastDirtiedTimeStamp hack. - -10-Apr megacz org/xwt/html/p.xwt: Improved flow performance. - -10-Apr megacz org/xwt/Box.java: fixed a bug that prevented regions from being - dirtied properly when boxes are removed from the tree and then - re-added. - -10-Apr megacz org/xwt/Box.java, org/xwt/SpecialBoxProperties.java: - hshrink/vshrink are no longer implemented by - manipulating dmax; the prerender pass understands them - natively. - -11-Apr megacz org/xwt/server/HTTP.java: fixed some CDATA misbehaviors. - -11-Apr megacz org/xwt/html/p.xwt, org/xwt/html/test.xwt: improved HTML - rendering; we can now render pretty much any

tag - -13-Apr megacz README: updated to note that build process requires - libgcj.jar even for jvm builds. - -21-Apr megacz src/org/xwt/plat/GCJ.xml: removed -fno-rtti - -21-Apr megacz src/org/xwt/Surface.java: workaround for GCJ PR java/6393 - -22-Apr megacz src/org/xwt/plat/Java2.java: fixed bug that caused - RasterFormatException on jdk1.4 - -22-Apr megacz README: included instructions on how to build without gcc. - -26-Apr megacz src/org/xwt/Main.java: included text description on splash screen - -26-Apr megacz src/org/xwt/plat/Win32.xml, src/org/xwt/plat/Java2.xml: - adjusted dist / signature process. - -26-Apr megacz README: included printStackTrace() patch - -26-Apr megacz src/org/xwt/XWT.java: fixed bug 53 - -26-Apr megacz src/org/xwt/TinySSL.java: fixed PKCS1 bug - -26-Apr megacz build.xml: staging/production push process - -26-Apr megacz src/org/xwt/tasks/BashTask.java: now checks exit codes - -26-Apr megacz src/org/xwt/tasks/BashTask.java: added ssh support - -27-Apr megacz README: added file locking patch - -27-Apr megacz [lots of files]: introduced notion of buildid's, to make - XWT upgrades work more smoothly, and to prevent problems - with browser/plugin caches. - -27-Apr megacz JSObject.java: added extra debugging info - -27-Apr megacz XWT.java, Platform.java, Main.java, Java2.java, - Win32.java, Win32.cc, faq.html: added support for - xwt.newBrowserWindow() - -27-Apr megacz Surface.java, Box.java: nuked dirtiedTimeStamp - altogether; it caused more problems than it solved. - -27-Apr megacz reference.html: Changed capitalization of - faultstring/faultcode for xwt.soap() to match SOAP spec. - -27-Apr megacz XWT.java, Box.java: fixed ConversionError bug - -27-Apr megacz Platform.java: added a 3-pixel minimum for a font's - descent -- ensures that we have space for underlining. - -28-Apr megacz Template.java: fixed a bug where would - misbehave if used on a scriptless