From 440e9bee20802bfbf97061b01f950448d3baf8f5 Mon Sep 17 00:00:00 2001 From: adam Date: Sat, 15 Apr 2006 05:49:23 +0000 Subject: [PATCH] made Headers class truly immutable darcs-hash:20060415054923-5007d-e52a4ebded2fd3386a5c06d7f15ffbb1d556500e.gz --- src/org/ibex/mail/Headers.java | 138 ++++++++++++++----------- src/org/ibex/mail/MIME.java | 4 +- src/org/ibex/mail/MailingList.java | 6 +- src/org/ibex/mail/Main.java | 4 +- src/org/ibex/mail/Message.java | 40 +++++-- src/org/ibex/mail/protocol/SMTP.java | 4 +- src/org/ibex/mail/target/MailmanArchives.java | 1 + 7 files changed, 118 insertions(+), 79 deletions(-) diff --git a/src/org/ibex/mail/Headers.java b/src/org/ibex/mail/Headers.java index 26c12ad..a68d9d7 100644 --- a/src/org/ibex/mail/Headers.java +++ b/src/org/ibex/mail/Headers.java @@ -15,22 +15,9 @@ import java.net.*; import java.io.*; // FIXME: this is important: folded headers: can insert CRLF anywhere that whitespace appears (before the whitespace) -public class Headers extends JS.Immutable implements Fountain { - private final Hash head = new Hash(); - private final Hash headModified = new Hash(); - public int lines; - public final boolean mime; +public abstract class Headers extends JS.Immutable implements Fountain { - private String raw; - private StringFountain fountain; - - public String get(String s) { - String ret = (String)headModified.get(s.toLowerCase()); - if (ret==null) ret = (String)head.get(s.toLowerCase()); - return ret; - } - public void remove(String k) { put(k, null); } - public void put(String k, String v) { + public Headers set(String k, String v) { Stream stream = getStream(); StringBuffer all = new StringBuffer(); int lines = 0; @@ -53,59 +40,85 @@ public class Headers extends JS.Immutable implements Fountain { all.append(k + ": " + v + "\r\n"); } all.append("\r\n"); - this.raw = all.toString(); - this.lines = lines; - this.fountain = new Fountain.StringFountain(this.raw); + return new Original(new Stream(all.toString())); } - public JS get(JS s) throws JSExn { return JSU.S(get(JSU.toString(s).toLowerCase())); } - - public Stream getStream() { return fountain.getStream(); } - public int getLength() { return fountain.getLength(); } - public int getNumLines() { return fountain.getNumLines(); } - public Stream getStreamWithCRLF() { return new Stream(raw+"\r\n"); } // FIXME - public String getString() { return raw; } + public abstract String getString(); - public Headers(Stream stream) throws Malformed { this(stream, false); } - public Headers(Stream stream, boolean assumeMime) throws Malformed { - StringBuffer all = new StringBuffer(); - String key = null; - int lines = 0; - for(String s = stream.readln(); s != null && !s.equals(""); s = stream.readln()) { - all.append(s); - all.append("\r\n"); - lines++; - if (Character.isSpace(s.charAt(0))) { - if (key == null) throw new Malformed("Message began with a blank line; no headers"); - head.put(key, head.get(key) + " " + s.trim()); - continue; + public abstract String get(String s); + public abstract java.util.Enumeration names(); + + public Headers set(String[] keyval) { + Headers ret = this; + 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(); + if (get(key) != null) val = get(key) + " " + val; // just append it to the previous one; + head.put(key, val); } - if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s); - key = s.substring(0, s.indexOf(':')).toLowerCase(); - 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(); - if (get(key) != null) val = get(key) + " " + val; // just append it to the previous one; - head.put(key, val); + this.raw = all.toString(); + this.fountain = new Fountain.StringFountain(this.raw); + this.lines = lines; + this.mime = assumeMime | (get("mime-version") != null && get("mime-version").trim().equals("1.0")); + /* + java.util.Enumeration e = head.keys(); + while(e.hasNext()) { + String k = (String)e.next(); + String v = (String)head.get(k); + if (mime) k = Encode.RFC2047.decode(k); + v = uncomment(v); + if (mime) v = Encode.RFC2047.decode(v); + head.put(k, v); + } + */ } - this.raw = all.toString(); - this.fountain = new Fountain.StringFountain(this.raw); - this.lines = lines; - this.mime = assumeMime | (get("mime-version") != null && get("mime-version").trim().equals("1.0")); - /* - java.util.Enumeration e = head.keys(); - while(e.hasNext()) { - String k = (String)e.next(); - String v = (String)head.get(k); - if (mime) k = Encode.RFC2047.decode(k); - v = uncomment(v); - if (mime) v = Encode.RFC2047.decode(v); - head.put(k, v); - } - */ - } + // Helpers ////////////////////////////////////////////////////////////////////////////// @@ -123,4 +136,5 @@ public class Headers extends JS.Immutable implements Fountain { } return val; } + } } diff --git a/src/org/ibex/mail/MIME.java b/src/org/ibex/mail/MIME.java index d395f23..db6ee07 100644 --- a/src/org/ibex/mail/MIME.java +++ b/src/org/ibex/mail/MIME.java @@ -40,12 +40,12 @@ public class MIME { "quoted-printable".equals(encoding) ? Encode.QuotedPrintable.decode(body.toString(),false) : "base64".equals(encoding) ? Encode.fromBase64(body.toString()) : */ - Headers.skip(all.getStream()); + Headers.Original.skip(all.getStream()); } } public Part(Fountain all) { - this.headers = new Headers(all.getStream()); + this.headers = new Headers.Original(all.getStream()); String ctype = headers.get("content-type"); this.encoding = headers.get("content-transfer-encoding"); if (!(encoding == null || encoding.equals("7bit") || encoding.equals("8bit") || encoding.equals("binary") || diff --git a/src/org/ibex/mail/MailingList.java b/src/org/ibex/mail/MailingList.java index ea0e248..82c5ec3 100644 --- a/src/org/ibex/mail/MailingList.java +++ b/src/org/ibex/mail/MailingList.java @@ -158,9 +158,9 @@ public class MailingList implements Target, Iterable { public void accept(Message m) throws IOException, MailException { StringBuffer buf = new StringBuffer(); m.getBody().getStream().transcribe(buf); - Headers head = new Headers(m.headers.getStream()); - head.put("List-Id", one_line_description + "<"+address+">"); - head.put("Subject", properties.get("prefix") + " " + head.get("Subject")); + Headers head = new Headers.Original(m.headers.getStream()); + head = head.set("List-Id", one_line_description + "<"+address+">"); + head = head.set("Subject", properties.get("prefix") + " " + head.get("Subject")); m = Message.newMessage(new Fountain.StringFountain(head.getString()+"\r\n"+buf.toString())); Log.warn(MailingList.class, "archiving list message " + m.subject); diff --git a/src/org/ibex/mail/Main.java b/src/org/ibex/mail/Main.java index ed1791b..9108be3 100644 --- a/src/org/ibex/mail/Main.java +++ b/src/org/ibex/mail/Main.java @@ -21,10 +21,12 @@ public class Main implements Listener { try { if (conn.getLocalPort() == 143) new IMAP.Listener(auth).handleRequest(conn); else if (conn.getLocalPort() == 25) new SMTP.Server().handleRequest(conn); + else if (conn.getLocalPort() == 8080) new SMTP.Server().handleRequest(conn); else if (conn.getLocalPort() == 119) new NNTP.Listener(auth).handleRequest(conn); //else if (conn.getLocalPort() == 110) new POP3.Listener(auth).handleRequest(conn); else if (conn.getLocalPort() == 8099) GMail.handleRequest(conn); - else if (conn.getLocalPort() == 8080) Jetty.instance().accept(conn); + // else if (conn.getLocalPort() == 8080) Jetty.instance().accept(conn); + else if (conn.getLocalPort() == 443) Jetty.instance().accept(conn); else if (conn.getLocalPort() == 80) Jetty.instance().accept(conn); else return false; return true; diff --git a/src/org/ibex/mail/Message.java b/src/org/ibex/mail/Message.java index 6d160d5..882ca9f 100644 --- a/src/org/ibex/mail/Message.java +++ b/src/org/ibex/mail/Message.java @@ -13,13 +13,11 @@ import java.util.*; import java.net.*; import java.io.*; -// FIXME: this is important: folded headers: can insert CRLF anywhere that whitespace appears (before the whitespace) // FIXME: messages must NEVER contain 8-bit binary data; this is a violation of IMAP // FIXME: RFC822 1,000-char limit per line [soft line limit (suggested): 78 chars / hard line limit: 998 chars] // FEATURE: PGP-signature-parsing -// FEATURE: mailing list header parsing -// FEATURE: delivery status notification (and the sneaky variety) +// FEATURE: mailing list header parsing (?) // FEATURE: threading as in http://www.jwz.org/doc/threading.html /** @@ -45,7 +43,28 @@ public class Message extends MIME.Part { public static Message newMessage(Fountain in) throws Malformed { return new Message(in); } + public Message reply(Fountain in, Address from, boolean includeReInSubject) throws Malformed { + /* + Address to = null; + if (to==null) to = Address.parse(headers.get("reply-to")); + if (to==null) to = Address.parse(headers.get("from")); + if (to==null) to = envelopeFrom; + Message ret = newMessage(in, from, to); + ret.headers.put("In-Reply-To", messageid); + String references = headers.get("references"); + ret.headers.put("References", messageid + (references==null?"":(" "+references))); + if (includeReInSubject && subject!=null && !subject.toLowerCase().trim().startsWith("re:")) + headers.put("subject", "Re: "+subject); + return ret; + */ + // FIXME + return null; + } + // FIXME + //public static Message newMessage(Headers headers, Fountain body, Address from, Address to) throws Malformed { + //} + public static Message newMessage(Fountain in, Address from, Address to) throws Malformed { StringBuffer sb = new StringBuffer(); if (from != null) sb.append("Return-Path: " + from.toString(true) + "\r\n"); @@ -111,12 +130,15 @@ public class Message extends MIME.Part { public Message bounce(String reason) { if (envelopeFrom==null || envelopeFrom.toString().equals("")) return null; - Headers h = new Headers(headers.getStream()); - h.put("Envelope-To", envelopeFrom.toString()); - h.put("Return-Path", "<>"); - h.put("From", "MAILER-DAEMON <>"); - h.put("To", envelopeFrom.toString()); - h.put("Subject", "failure notice"); + Log.warn(Message.class, "bouncing message due to: " + reason); + Headers h = new Headers.Original(headers.getStream()); + h = h.set(new String[] { + "Envelope-To", envelopeFrom.toString(), + "Return-Path", "<>", + "From", "MAILER-DAEMON <>", + "To", envelopeFrom.toString(), + "Subject", "failure notice" + }); String error = "Hi. This is the Ibex Mail Server. I'm afraid I wasn't able to deliver\r\n"+ diff --git a/src/org/ibex/mail/protocol/SMTP.java b/src/org/ibex/mail/protocol/SMTP.java index 9b12907..209de02 100644 --- a/src/org/ibex/mail/protocol/SMTP.java +++ b/src/org/ibex/mail/protocol/SMTP.java @@ -245,8 +245,8 @@ public class SMTP { conn.println("RCPT TO:<" + m.envelopeTo.toString()+">"); check(conn.readln(), conn); conn.println("DATA"); check(conn.readln(), conn); Headers head = m.headers; - head.remove("return-path"); - head.remove("bcc"); + head = head.remove("return-path"); + head = head.remove("bcc"); Stream stream = head.getStream(); for(String s = stream.readln(); s!=null; s=stream.readln()) { if (s.startsWith(".")) conn.print("."); diff --git a/src/org/ibex/mail/target/MailmanArchives.java b/src/org/ibex/mail/target/MailmanArchives.java index 12a8703..4e043e9 100644 --- a/src/org/ibex/mail/target/MailmanArchives.java +++ b/src/org/ibex/mail/target/MailmanArchives.java @@ -12,6 +12,7 @@ import java.util.*; import java.util.zip.*; import java.text.*; +/** designed to make a set of HTTP-accessible Mailman archives appear as a mailbox */ public class MailmanArchives extends Mailbox.Default { public static final String archiveUrl = -- 1.7.10.4