From 31f6bfd3b57924e8c3d506592132e91e73145462 Mon Sep 17 00:00:00 2001 From: adam Date: Sat, 17 Mar 2007 10:32:59 +0000 Subject: [PATCH] massive refactoring of Headers class darcs-hash:20070317103259-5007d-b5bd837abc764f72dd1daeb59254241673830e8e.gz --- src/org/ibex/mail/Headers.java | 198 ++++++++++++++++++------------------ src/org/ibex/mail/MIME.java | 3 +- src/org/ibex/mail/MailingList.java | 8 +- src/org/ibex/mail/Message.java | 3 +- src/org/ibex/mail/SMTP.java | 8 +- 5 files changed, 114 insertions(+), 106 deletions(-) diff --git a/src/org/ibex/mail/Headers.java b/src/org/ibex/mail/Headers.java index 2225919..2d2f2ba 100644 --- a/src/org/ibex/mail/Headers.java +++ b/src/org/ibex/mail/Headers.java @@ -14,110 +14,112 @@ 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) -public abstract class Headers extends JS.Immutable implements Fountain { +// FEATURE: construct hash lazily? +public class Headers extends JS.Immutable implements Fountain { + + /** + * constructs a new set of Headers based on a preexisting set -- + * keyval's even-numbered elements are keys, odd elements are + * values -- a null value deletes, non-null value replaces + */ + public Headers(Headers old, String[] keyval) { this(old.updateHeaders(keyval), false); } + + public Headers(Fountain fountain) throws Malformed { this(fountain, false); } + public Headers(Fountain fountain, boolean assumeMime) throws Malformed { this(extractEntries(fountain), assumeMime); } + + + // public ////////////////////////////////////////////////////////////////////////////// + + public String[] getHeaderNames() { return (String[])head.dumpkeys(new String[head.size()]); } + public String get(String s) { return (String)head.get(s.toLowerCase()); } + public JS get(JS s) throws JSExn { return JSU.S(get(JSU.toString(s).toLowerCase())); } + + public Stream getStream() { return fountain().getStream(); } + public long getLength() { return fountain().getLength(); } + public int getNumLines() { return fountain().getNumLines(); } + - public Headers set(String k, String v) { - Stream stream = getStream(); - StringBuffer all = new StringBuffer(); - int lines = 0; + // private ////////////////////////////////////////////////////////////////////////////// + + private final Hash head = new Hash(); + private final boolean mime; + private final Entry[] entries; + private Fountain fountain = null; + + private synchronized Fountain fountain() { // lazily constructed + if (fountain == null) { + StringBuffer sb = new StringBuffer(); + for(Entry e : entries) + sb.append(e.toString()); + this.fountain = Fountain.Util.create(sb.toString()); + } + return fountain; + } + + private static class Entry { + public final String key; + public final String val; + public String toString() { return key+":"+val; } + public Entry(String key, String val) throws Malformed { + this.key = key; + this.val = val; + for(int i=0; i 126) + throw new Malformed("Header key \""+key+"\" contains invalid character \"" + + key.charAt(i) + "\" (0x"+Integer.toString(key.charAt(i), 16) +")"); + } + } + + private Headers(Entry[] entries, boolean assumeMime) { + this.entries = entries; + this.mime = assumeMime | (get("mime-version") != null && get("mime-version").trim().equals("1.0")); + for(Entry e : entries) { + String val = (String)head.get(e.key.toLowerCase()); + val = val==null ? e.val.trim() : val+" "+e.val.trim(); // introduce folding whitespace =( + //if (mime) k = Encode.RFC2047.decode(k); + //if (mime) v = Encode.RFC2047.decode(v); + head.put(e.key.toLowerCase(), val); + } + } + + private static Entry[] extractEntries(Fountain fountain) throws Malformed { String key = null; + Stream stream = fountain.getStream(); + ArrayList entries = new ArrayList(); for(String s = stream.readln(); s != null && !s.equals(""); s = stream.readln()) { - if (!Character.isSpace(s.charAt(0))) { - if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s); - key = s.substring(0, s.indexOf(':')).toLowerCase(); - } - if (key.toLowerCase().equals(k.toLowerCase())) { - if (v != null) { all.append(k + ": " + v + "\r\n"); lines++; v = null; } + s += "\r\n"; // this is the only place where we introduce manglage -- we normalize EOLs + if (Character.isSpace(s.charAt(0))) { + if (key == null) throw new Malformed("Message began with a blank line; no headers"); + Entry e = entries.remove(entries.size()-1); + entries.add(new Entry(e.key, e.val+s)); continue; } - all.append(s); - all.append("\r\n"); - lines++; - } - if (v != null) { - lines++; - all.append(k + ": " + v + "\r\n"); + if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s); + key = s.substring(0, s.indexOf(':')); + entries.add(new Entry(key, s.substring(s.indexOf(':') + 1))); } - all.append("\r\n"); - return new Original(new Stream(all.toString())); - } - - // FIXME - //public abstract String getString(); - public abstract String[] getHeaderNames(); - public abstract String get(String s); - - public Headers set(String[] keyval) { - Headers ret = this; - for(int i=0; i 126) { - Log.error(null,all); - throw new Malformed("Header key \""+key+"\" contains invalid character \"" + - key.charAt(i) + "\" (0x"+Integer.toString(key.charAt(i), 16) +")"); - } - 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); + + private Entry[] updateHeaders(String[] keyval) { + ArrayList entries = new ArrayList(); + for(int i=0; i"); - head = head.set("Subject", properties.get("prefix") + " " + head.get("Subject")); + Headers head = new Headers(m.headers, + new String[] { + "List-Id", one_line_description + "<"+address+">", + "Subject", properties.get("prefix") + " " + m.headers.get("Subject") + }); m = Message.newMessage(Fountain.Util.concat(new Fountain[] { head, Fountain.Util.create("\r\n"), diff --git a/src/org/ibex/mail/Message.java b/src/org/ibex/mail/Message.java index 7b8ba4f..9d91a18 100644 --- a/src/org/ibex/mail/Message.java +++ b/src/org/ibex/mail/Message.java @@ -163,8 +163,7 @@ public class Message extends MIME.Part { if (envelopeFrom==null || envelopeFrom.toString().equals("")) return null; Log.warn(Message.class, "bouncing message due to: " + reason); - Headers h = new Headers.Original(headers.getStream()); - h = h.set(new String[] { + Headers h = new Headers(headers, new String[] { "Envelope-To", envelopeFrom.toString(), "Return-Path", "<>", "From", "MAILER-DAEMON <>", diff --git a/src/org/ibex/mail/SMTP.java b/src/org/ibex/mail/SMTP.java index a23c7de..f8bcf0b 100644 --- a/src/org/ibex/mail/SMTP.java +++ b/src/org/ibex/mail/SMTP.java @@ -349,9 +349,11 @@ 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 = head.remove("return-path"); - head = head.remove("bcc"); + Headers head = new Headers(m.headers, + new String[] { + "return-path", null, + "bcc", null + }); Stream stream = head.getStream(); for(String s = stream.readln(); s!=null; s=stream.readln()) { if (s.startsWith(".")) conn.print("."); -- 1.7.10.4