X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2Fmail%2FHeaders.java;h=c7813eba12ac52aaa329a171363d6fa0b23df193;hb=c0cdaca74524315b01e9973c027e689627d3d58e;hp=2225919625d5ce1508bebf415c4af77922e686f5;hpb=dff9ab3384fb252c9ce43cf713c3202031b928ce;p=org.ibex.mail.git diff --git a/src/org/ibex/mail/Headers.java b/src/org/ibex/mail/Headers.java index 2225919..c7813eb 100644 --- a/src/org/ibex/mail/Headers.java +++ b/src/org/ibex/mail/Headers.java @@ -14,126 +14,134 @@ 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(String[] keyval) { this(new Headers(), keyval); } + + public Headers() { this(new String[0]); } + 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 =( + // FEATURE + //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 0;) s = stream.readln(); + for(String s = stream.readln(); s!=null && s.trim().length() > 0;) + s = stream.readln(); return stream; } - public static String uncomment(String val) { + // designed to remove CFWS, but doesn't work right + public static String removeCFWS(String val) { boolean inquotes = false; for(int i=0; i