X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2Fmail%2FHeaders.java;h=c7813eba12ac52aaa329a171363d6fa0b23df193;hb=4b76a31e8f86bc8de673899bbb45252c01e9a0f6;hp=26c12adb752bdd55508d59de2ebd64eb6d994378;hpb=7bc79e9bf3e8dfc7ebc1e9eab162da4768a9cb7d;p=org.ibex.mail.git diff --git a/src/org/ibex/mail/Headers.java b/src/org/ibex/mail/Headers.java index 26c12ad..c7813eb 100644 --- a/src/org/ibex/mail/Headers.java +++ b/src/org/ibex/mail/Headers.java @@ -14,113 +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) +// FEATURE: construct hash lazily? 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; + + /** + * 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 ////////////////////////////////////////////////////////////////////////////// - private String raw; - private StringFountain fountain; + 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 String get(String s) { - String ret = (String)headModified.get(s.toLowerCase()); - if (ret==null) ret = (String)head.get(s.toLowerCase()); - return ret; + public Stream getStream() { return fountain().getStream(); } + public long getLength() { return fountain().getLength(); } + public int getNumLines() { return fountain().getNumLines(); } + + + // 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; } - public void remove(String k) { put(k, null); } - public void put(String k, String v) { - Stream stream = getStream(); - StringBuffer all = new StringBuffer(); - int lines = 0; - String key = null; - 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; } - continue; - } - all.append(s); - all.append("\r\n"); - lines++; + + 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) +")"); } - if (v != null) { - lines++; - all.append(k + ": " + v + "\r\n"); + } + + 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); } - all.append("\r\n"); - this.raw = all.toString(); - this.lines = lines; - this.fountain = new Fountain.StringFountain(this.raw); } - 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 Headers(Stream stream) throws Malformed { this(stream, false); } - public Headers(Stream stream, boolean assumeMime) throws Malformed { - StringBuffer all = new StringBuffer(); + + private static Entry[] extractEntries(Fountain fountain) throws Malformed { String key = null; - int lines = 0; + Stream stream = fountain.getStream(); + ArrayList entries = new ArrayList(); for(String s = stream.readln(); s != null && !s.equals(""); s = stream.readln()) { - all.append(s); - all.append("\r\n"); - lines++; + 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"); - head.put(key, head.get(key) + " " + s.trim()); + Entry e = entries.remove(entries.size()-1); + entries.add(new Entry(e.key, e.val+s)); continue; } 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); + key = s.substring(0, s.indexOf(':')); + entries.add(new Entry(key, s.substring(s.indexOf(':') + 1))); } - 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); - } - */ + return (Entry[])entries.toArray(new Entry[entries.size()]); + } + + 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