From 3492f56637223c93d748aef93888bc107e8e1252 Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 7 May 2004 22:13:52 +0000 Subject: [PATCH] almost working darcs-hash:20040507221352-5007d-e88f7b3c86d0822f852cb2c536e3d2be6cacb0d1.gz --- Makefile | 4 +- src/org/ibex/mail/Address.java | 31 +++++ src/org/ibex/mail/MailException.java | 3 + src/org/ibex/mail/Message.java | 127 ++++++++---------- src/org/ibex/mail/protocol/Incoming.java | 6 +- src/org/ibex/mail/protocol/SMTP.java | 205 ++++++++++++++++++----------- src/org/ibex/mail/store/MessageStore.java | 33 +++-- src/org/ibex/mail/target/Script.java | 21 ++- src/org/ibex/mail/target/Target.java | 2 +- 9 files changed, 260 insertions(+), 172 deletions(-) create mode 100644 src/org/ibex/mail/Address.java diff --git a/Makefile b/Makefile index bf021a7..c432e83 100644 --- a/Makefile +++ b/Makefile @@ -7,13 +7,15 @@ dist-clean: sources := $(shell find src -name \*.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 + .download_org.ibex.%: @echo -e "\033[1mfetching repository org.ibex.$*\033[0m" @mkdir -p upstream; cd upstream; rm -rf org.ibex.$*; rm -rf org.ibex.$*_* @cd upstream; darcs get --verbose --partial --repo-name=org.ibex.$* http://$*.ibex.org @touch $@ -.build_org.ibex.%: +.build_org.ibex.%: .download_org.ibex.% @echo -e "\033[1mbuilding repository org.ibex.$*\033[0m" @cd upstream/org.ibex.$*; make compile @touch $@ diff --git a/src/org/ibex/mail/Address.java b/src/org/ibex/mail/Address.java new file mode 100644 index 0000000..92bdbc8 --- /dev/null +++ b/src/org/ibex/mail/Address.java @@ -0,0 +1,31 @@ +package org.ibex.mail; +import org.ibex.crypto.*; +import org.ibex.js.*; +import org.ibex.util.*; +import org.ibex.mail.protocol.*; +import java.util.*; +import java.net.*; +import java.io.*; + +public class Address extends JSReflection { + public final String user; + public final String host; + public final String description; + 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) { + 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"); } + 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); } } +} diff --git a/src/org/ibex/mail/MailException.java b/src/org/ibex/mail/MailException.java index 6bb7ef9..1b789b7 100644 --- a/src/org/ibex/mail/MailException.java +++ b/src/org/ibex/mail/MailException.java @@ -4,7 +4,10 @@ import java.io.*; public class MailException extends Exception { + public MailException() { } + public MailException(String s) { super(s); } public static class MailboxFull extends MailException { } + public static class Malformed extends MailException { public Malformed(String s) { super(s); } } public static class RelayingDenied extends MailException { } public static class IOException extends MailException { // FIXME: fill in stack trace diff --git a/src/org/ibex/mail/Message.java b/src/org/ibex/mail/Message.java index 96f0873..8303ea8 100644 --- a/src/org/ibex/mail/Message.java +++ b/src/org/ibex/mail/Message.java @@ -7,7 +7,6 @@ import java.util.*; import java.net.*; import java.io.*; -// FIXME MIME: RFC2045, 2046, 2049 // NOTE: always use Win32 line endings // hard line limit: 998 chars // soft line limit (suggested): 78 chars @@ -17,6 +16,7 @@ import java.io.*; // body needs CRLF; one or the other alone is not acceptable // date/time parsing: see 3.3 +// FEATURE: MIME RFC2045, 2046, 2049 // FEATURE: PGP-signature-parsing // FEATURE: mailing list header parsing // FEATURE: delivery status notification (and the sneaky variety) @@ -28,7 +28,6 @@ public class Message extends JSReflection { public final Hashtable headers; // hash of headers (not including resent's and traces) public final String body; // entire body - // parsed header fields public final Date date; public final Address to; public final Address from; // if multiple From entries, this is sender @@ -38,75 +37,69 @@ public class Message extends JSReflection { public final Address[] cc; public final Address[] bcc; public final Hashtable[] resent; - public final Trace[] traces; + public Trace[] traces; - // envelope fields public final Address envelopeFrom; public final Address[] envelopeTo; - public void dump(OutputStream os) { - Log.error(this, "not implemented"); + public void dump(OutputStream os) throws IOException { + Writer w = new OutputStreamWriter(os); + w.write(allHeaders); + w.write("\r\n"); + w.write(body); + w.flush(); } public static class StoredMessage extends Message { - public StoredMessage(/*ReadStream rs*/SMTP.LineReader rs, boolean dotTerminatedLikeSMTP) throws IOException { - super(rs, dotTerminatedLikeSMTP); uid = -1; } - public final int uid; + public int uid; public boolean deleted = false; public boolean read = false; public boolean answered = false; - public String dumpStoredForm() { throw new Error("StoredMessage.dumpStoredForm() not implemented"); }; - public static StoredMessage undump(InputStream os) { - Log.error(StoredMessage.class, "not implemented"); - return null; - } + public StoredMessage(LineReader rs) throws IOException, MailException.Malformed { super(rs); } } - public static class Address extends JSReflection { - public String coerceToString() { return toString(); } - public String toString() { - if (description == null || description.equals("")) return user +"@"+ host; - return description + " " + "<" + user +"@"+ host + ">"; - } - public final String user; - public final String host; - public final String description; - public Address(String user, String host, String description) { - this.user = user; this.host = host; this.description = description; - } - public Address(String s) { - s = s.trim(); - String descrip = null; - if (s.indexOf('<') != -1) { - if (s.indexOf('>') == -1) { /* FIXME */ } - descrip = s.substring(0, s.indexOf('<')) + s.substring(s.indexOf('>') + 1); - s = s.substring(s.indexOf('<') + 1, s.indexOf('>')); + public class Trace { + final String returnPath; + final Element[] elements; + public Trace(LineReader lr) throws Trace.Malformed, IOException { + String retPath = lr.readLine(); + 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(); + if (s == null) break; + if (!s.startsWith("Received:")) { lr.pushback(s); break; } + s = s.substring(9).trim(); + el.addElement(new Element(s)); } - if (s.indexOf('@') == -1) { /* FIXME */ } - description = descrip; - user = s.substring(0, s.indexOf('@')); - host = s.substring(s.indexOf('@')+1); + elements = new Element[el.size()]; + el.copyInto(elements); } - } - - public class Trace { - String returnPath = null; - Element[] elements; public class Element { - // FIXME final - String fromDomain; - String fromIP; - String toDomain; - String forWhom; - Date date; + String fromDomain; + String fromIP; + String toDomain; + String forWhom; + Date date; + public Element(String fromDomain, String fromIP, String toDomain, String forWhom, Date date) { + this.fromDomain=fromDomain; this.fromIP=fromIP; this.toDomain=toDomain; this.forWhom=forWhom; this.date=date; } + public Element(String s) throws Trace.Malformed { + StringTokenizer st = new StringTokenizer(s); + if (!st.nextToken().equals("FROM")) throw new Trace.Malformed("trace did note have a FROM element: " + s); + fromDomain = st.nextToken(); + if (!st.nextToken().equals("BY")) throw new Trace.Malformed("trace did note have a BY element: " + s); + toDomain = st.nextToken(); + // FIXME not done yet + } } + public class Malformed extends Message.Malformed { public Malformed(String s) { super(s); } } } - // FIXME: support dotTerminatedLikeSMTP - public Message(/*ReadStream rs*/SMTP.LineReader rs, boolean dotTerminatedLikeSMTP) throws IOException { + public static class Malformed extends MailException.Malformed { public Malformed(String s) { super(s); } } + public Message(LineReader rs) throws IOException, Malformed { String key = null; StringBuffer all = new StringBuffer(); - String lastKey = null; replyto = null; subject = null; messageid = null; @@ -124,27 +117,28 @@ public class Message extends JSReflection { for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) { all.append(s); all.append("\r\n"); - if (Character.isSpace(s.charAt(0))) { - if (lastKey == null) { /* FIXME */ } - headers.put(lastKey, headers.get(lastKey) + s); + if (s.length() == 0 || Character.isSpace(s.charAt(0))) { + if (key == null) throw new Malformed("Message began with a blank line; no headers"); + headers.put(key, headers.get(key) + s); continue; } - if (s.indexOf(':') == -1) { - /* FIXME */ - break; - } - - lastKey = key = s.substring(0, s.indexOf(':')); + if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s); + key = s.substring(0, s.indexOf(':')); String val = s.substring(0, s.indexOf(':') + 1); while(Character.isSpace(val.charAt(0))) val = val.substring(1); if (headers.get(key) != null) if (key.startsWith("Resent-")) { // FIXME: multi-resent headers + } else if (key.startsWith("Return-Path:")) { - // FIXME: parse traces, see RFC2821, section 4.4 - } else if (key.startsWith("Recieved:")) { - // FIXME: parse traces, see RFC2821, section 4.4 + rs.pushback(s); + Trace t = new Trace(rs); + Trace[] newTraces = new Trace[traces == null ? 1 : traces.length + 1]; + if (traces != null) System.arraycopy(traces, 0, newTraces, 0, traces.length); + traces = newTraces; + traces[traces.length-1] = t; + } else { // just append it to the previous one; valid for Comments/Keywords val = headers.get(key) + " " + val; @@ -154,13 +148,8 @@ public class Message extends JSReflection { } allHeaders = all.toString(); - StringBuffer body = new StringBuffer(); - for(String s = rs.readLine();; s = rs.readLine()) { - if (s == null || (dotTerminatedLikeSMTP && s.equals("."))) break; - body.append(s); - } - + for(String s = rs.readLine();; s = rs.readLine()) { if (s == null) break; else body.append(s + "\r\n"); } this.body = body.toString(); } diff --git a/src/org/ibex/mail/protocol/Incoming.java b/src/org/ibex/mail/protocol/Incoming.java index f02b657..d46a75e 100644 --- a/src/org/ibex/mail/protocol/Incoming.java +++ b/src/org/ibex/mail/protocol/Incoming.java @@ -5,10 +5,8 @@ import org.ibex.mail.target.*; import java.io.*; public class Incoming { - - protected void accept(Message m) throws IOException { - MessageStore.transcript.add((Message.StoredMessage)m); // currently, we write all inbound messages to the transcript + protected void accept(Message m) throws IOException, MailException { + MessageStore.transcript.add(m); // currently, we write all inbound messages to the transcript Target.root.accept(m); } - } diff --git a/src/org/ibex/mail/protocol/SMTP.java b/src/org/ibex/mail/protocol/SMTP.java index 2b01ed7..ff1a5d7 100644 --- a/src/org/ibex/mail/protocol/SMTP.java +++ b/src/org/ibex/mail/protocol/SMTP.java @@ -6,20 +6,34 @@ import org.ibex.util.*; import java.net.*; import java.io.*; import java.util.*; +import java.text.*; public class SMTP extends MessageProtocol { - public static void main(String[] s) throws IOException { + public static String convdir = null; + public static void main(String[] s) throws Exception { + String logto = System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail"); + logto += File.separatorChar + "log"; + Log.file(logto); + convdir = System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail"); + convdir += File.separatorChar + "conversation"; + new File(convdir).mkdirs(); SMTP smtp = new SMTP(); - ServerSocket ss = new ServerSocket(1025); - Socket sock = ss.accept(); - smtp.handle(sock); + int port = Integer.parseInt(System.getProperty("ibex.mail.port", "25")); + Log.info(SMTP.class, "binding to port " + port + "..."); + ServerSocket ss = new ServerSocket(port); + Log.info(SMTP.class, "listening for connections..."); + while(true) { + final Socket sock = ss.accept(); + new Thread() { public void run() { smtp.handle(sock); } }.start(); + } } - public SMTP() {/* setProtocolName("SMTP"); */} - public void handle(Socket s) throws IOException { new Listener(s).handleRequest(); } + //public SMTP() { setProtocolName("SMTP"); } //public ServerRequest createRequest(Connection conn) { return new Listener((TcpConnection)conn); } + public void handle(Socket s) { new Listener(s, "megacz.com").handleRequest(); } + public static class Outgoing { // recommended retry interval is 30 minutes // give up after 4-5 days @@ -46,125 +60,156 @@ public class SMTP extends MessageProtocol { } } - public static class LineReader extends InputStreamReader { - public LineReader(InputStream r) { super(r); } - public String readLine() throws IOException { - StringBuffer ret = new StringBuffer(); - while(true) { - int c = read(); - if (c == -1) throw new EOFException(); - if (c == '\n') return ret.toString(); - if (c == '\r') continue; //return ret.toString(); - ret.append((char)c); - } - } - } + private static String lastTime = null; + private static int lastCounter = 0; private class Listener extends Incoming /*implements ServerRequest*/ { - //TcpConnection conn; Socket conn; - //public Listener(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); } - public Listener(Socket conn) throws IOException { this.conn = conn; conn.setSoTimeout(5 * 60 * 1000); } + String vhost; public void init() { } + public Listener(Socket conn, String vhost) { this.vhost = vhost; this.conn = conn; } - public boolean handleRequest() throws IOException { - //ReadStream rs = conn.getReadStream(); - //WriteStream ws = conn.getWriteStream(); - LineReader rs = new LineReader(conn.getInputStream()); - PrintWriter ws = new PrintWriter(new OutputStreamWriter(conn.getOutputStream())); + //TcpConnection conn; + //public Listener(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); } + public boolean handleRequest() { + try { + conn.setSoTimeout(5 * 60 * 1000); + StringBuffer logMessage = new StringBuffer(); + 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; + } + } + String conversationId = time; + Log.setThreadAnnotation("[conversation/" + conversationId + "] "); + InetSocketAddress remote = (InetSocketAddress)conn.getRemoteSocketAddress(); + Log.info(this, "connection from " + remote.getHostName() + ":" + remote.getPort() + + " (" + remote.getAddress() + ")"); + PrintWriter logf = + new PrintWriter(new OutputStreamWriter(new FileOutputStream(convdir + File.separatorChar + conversationId))); + try { + return handleRequest(new LoggedLineReader(new InputStreamReader(conn.getInputStream()), logf), + new LoggedPrintWriter(new OutputStreamWriter(conn.getOutputStream()), logf)); + } catch(Throwable t) { + Log.warn(this, t); + } finally { + logf.close(); + Log.setThreadAnnotation(""); + } + } catch (Exception e) { + Log.error(this, e); + } + return false; + } - // FIXME - //ws.setNewLineString("\r\n"); + private class LoggedLineReader extends LineReader { + PrintWriter log; + public LoggedLineReader(Reader r, PrintWriter log) { super(r); this.log = log; } + public String readLine() throws IOException { + String s = super.readLine(); + if (s != null) { log.println("C: " + s); log.flush(); } + return s; + } + } - ws.println("220 " + /*conn.getVirtualHost()*/ "megacz.com" + " ESMTP " + this.getClass().getName()); - ws.flush(); + private class LoggedPrintWriter extends PrintWriter { + PrintWriter log; + public LoggedPrintWriter(Writer w, PrintWriter log) { super(w); this.log = log; } + public void println(String s) { + log.println("S: " + s); + super.println(s); + flush(); + } + } - Message.Address from = null; + public boolean handleRequest(LineReader rs, PrintWriter ws) throws IOException, MailException { + //ReadStream rs = conn.getReadStream(); + //WriteStream ws = conn.getWriteStream(); + //ws.setNewLineString("\r\n"); + ws.println("220 " + vhost + " ESMTP " + this.getClass().getName()); + Address from = null; Vector to = new Vector(); - // 551 = no, i won't forward that - // 452 = mailbox full - // see 4.4 for trace info while(true) { String command = rs.readLine(); - // FIXME: validate the HELO domain argument - // (double check against other end of connection? must not reject though) - if (command.toUpperCase().startsWith("HELO")) { - ws.println("250 HELO " + /*conn.getVirtualHost()*/("megacz.com")); - ws.flush(); + String c = command.toUpperCase(); + if (c.startsWith("HELO")) { + ws.println("250 HELO " + vhost); from = null; to = new Vector(); - } else if (command.toUpperCase().startsWith("EHLO")) { - ws.println("250-" + /*conn.getVirtualHost()*/("megacz.com")); + } else if (c.startsWith("EHLO")) { + ws.println("250-" + vhost); ws.println("250-SIZE"); ws.println("250 PIPELINING"); - ws.flush(); from = null; to = new Vector(); - } else if (command.toUpperCase().startsWith("RSET")) { + } else if (c.startsWith("RSET")) { from = null; to = new Vector(); ws.println("250 reset ok"); - ws.flush(); - } else if (command.toUpperCase().startsWith("MAIL FROM:")) { + } else if (c.startsWith("MAIL FROM:")) { command = command.substring(10).trim(); - if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' ')); - from = new Message.Address(command); + from = new Address(command); ws.println("250 " + from + " is syntactically correct"); - ws.flush(); - } else if (command.toUpperCase().startsWith("RCPT TO:")) { + } else if (c.startsWith("RCPT TO:")) { if (from == null) { ws.println("503 MAIL FROM must precede RCPT TO"); - ws.flush(); continue; } + // FIXME: 551 = no, i won't forward that command = command.substring(9).trim(); if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' ')); - Message.Address addr = new Message.Address(command); + Address addr = new Address(command); to.addElement(addr); ws.println("250 " + addr + " is syntactically correct"); - ws.flush(); - } else if (command.toUpperCase().startsWith("DATA")) { - if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); ws.flush(); continue; } - if (to == null) { ws.println("503 RCPT TO command must precede DATA"); ws.flush(); continue; } + } else if (c.startsWith("DATA")) { + if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); continue; } + if (to == null) { ws.println("503 RCPT TO command must precede DATA"); continue; } ws.println("354 Enter message, ending with \".\" on a line by itself"); - ws.flush(); StringBuffer data = new StringBuffer(); // move this into the Message class boolean good = false; try { - good = true; - Message m = new Message(rs, true); - accept(m); - } finally { - //ws.println("251 user not local; will forward"); - if (good) { ws.println("250 OK message accepted for delivery"); ws.flush(); } - else { /* FIXME */ } + accept(new Message(new DotTerminatedLineReader(rs))); + } catch (MailException.Malformed mfe) { + ws.println("501 " + mfe.toString()); break; + } catch (MailException.MailboxFull mbf) { + ws.println("452 " + mbf); + } catch (IOException ioe) { + ws.println("554 " + ioe.toString()); break; } + ws.println("250 message accepted"); break; - } else if (command.toUpperCase().startsWith("HELP")) { - ws.println("214 sorry, you are beyond help. please see a trained professional."); - ws.flush(); - - } else if (command.toUpperCase().startsWith("VRFY")) { // FIXME, see code 252 - } else if (command.toUpperCase().startsWith("EXPN")) { ws.println("550 EXPN not available"); ws.flush(); - } else if (command.toUpperCase().startsWith("NOOP")) { ws.println("250 OK"); ws.flush(); - } else if (command.toUpperCase().startsWith("QUIT")) { - ws.println("221 " + /*conn.getVirtualHost()*/("megacz.com") + " closing connection"); - ws.flush(); - break; - - } else { - ws.println("500 unrecognized command"); - ws.flush(); + } else if (c.startsWith("HELP")) { ws.println("214 you are beyond help. see a trained professional."); + } else if (c.startsWith("VRFY")) { ws.println("252 We don't VRFY; proceed anyway"); + } else if (c.startsWith("EXPN")) { ws.println("550 EXPN not available"); + } else if (c.startsWith("NOOP")) { ws.println("250 OK"); + } else if (c.startsWith("QUIT")) { ws.println("221 " + vhost + " closing connection"); break; + } else { ws.println("500 unrecognized command"); } } - return false; // FIXME: what does this mean? + return false; // always tell resin to close the connection + } + } + + private static class DotTerminatedLineReader extends LineReader { + private final LineReader r; + private boolean done = false; + public DotTerminatedLineReader(LineReader r) { super(null); this.r = r; } + public String readLine() throws IOException { + if (done) return null; + String s = r.readLine(); + if (s.equals(".")) { done = true; return null; } + if (s.startsWith(".")) return s.substring(1); + return s; } } } diff --git a/src/org/ibex/mail/store/MessageStore.java b/src/org/ibex/mail/store/MessageStore.java index 9c452b5..5b15243 100644 --- a/src/org/ibex/mail/store/MessageStore.java +++ b/src/org/ibex/mail/store/MessageStore.java @@ -9,14 +9,14 @@ import java.text.*; // FIXME: appallingly inefficient public class MessageStore { - private static final String STORAGE_ROOT = System.getProperty("ibex.mail.root", - File.separatorChar + "var" + File.separatorChar + "org.ibex.mail"); + private static final String STORAGE_ROOT = + System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail"); //public final FileBased root = new FileBased(STORAGE_ROOT + File.separatorChar); - public static FileBased transcript = null; + public static Transcript transcript = null; static { try { - transcript = new FileBased(STORAGE_ROOT + File.separatorChar + "transcript"); + transcript = new Transcript(STORAGE_ROOT + File.separatorChar + "transcript"); } catch (Exception e) { e.printStackTrace(); } @@ -25,7 +25,7 @@ public class MessageStore { public MessageStore slash(String name) throws IOException { throw new Error(this.getClass().getName() + " does not support the slash() method"); } public int[] list() { throw new Error(this.getClass().getName() + " does not support the list() method"); } - public int add(Message.StoredMessage message) throws IOException { + public int add(Message message) throws IOException { throw new Error(this.getClass().getName() + " does not support the add() method"); } public Message.StoredMessage get(int messagenum) throws IOException { throw new Error(this.getClass().getName() + " does not support the get() method"); } @@ -33,18 +33,18 @@ public class MessageStore { throw new Error(this.getClass().getName() + " does not support the query() method"); } /** a fast-write, slow-read place to stash all messages we touch -- in case of a major f*ckup */ - public static class Transcript { + public static class Transcript extends MessageStore { private String path; public Transcript(String path) throws IOException { new File(this.path = path).mkdirs(); } private static String lastTime = null; private static int lastCounter = 0; /** returns a message identifier */ - public synchronized int add(Message.StoredMessage message) throws IOException { - File today = new File(path + File.separatorChar + (new SimpleDateFormat("yyyyy.MMMMM.dd").format(new Date()))); + public synchronized int add(Message message) throws IOException { + File today = new File(path + File.separatorChar + (new SimpleDateFormat("yy-MMM-dd").format(new Date()))); today.mkdirs(); - String time = new SimpleDateFormat("").format(new Date("hh.mm.ss")); + String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); synchronized (Transcript.class) { if (lastTime != null && lastTime.equals(time)) { time += "." + (++lastCounter); @@ -54,9 +54,8 @@ public class MessageStore { } File target = new File(today.getPath() + File.separatorChar + time + ".txt"); - String msg = message.dumpStoredForm(); OutputStream os = new FileOutputStream(target); - os.write(msg.getBytes("UTF-8")); // FIXME: right? + message.dump(os); os.close(); return -1; // FIXME } @@ -85,7 +84,7 @@ public class MessageStore { } /** returns a message identifier */ - public synchronized int add(Message.StoredMessage message) throws IOException { + public synchronized int add(Message message) throws IOException { int[] all = list(); int max = 0; for(int i=0; i