X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2Fmail%2FHeaders.java;h=c7813eba12ac52aaa329a171363d6fa0b23df193;hb=0af90c1a1f14e24e1eb6790228be021b3e8e65b4;hp=a68d9d73e7a240dd752e8bb49d5f1a153daf93b0;hpb=440e9bee20802bfbf97061b01f950448d3baf8f5;p=org.ibex.mail.git diff --git a/src/org/ibex/mail/Headers.java b/src/org/ibex/mail/Headers.java index a68d9d7..c7813eb 100644 --- a/src/org/ibex/mail/Headers.java +++ b/src/org/ibex/mail/Headers.java @@ -14,127 +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 set(String k, String v) { - Stream stream = getStream(); - StringBuffer all = new StringBuffer(); - int lines = 0; + 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(); } + + + // 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())); + return (Entry[])entries.toArray(new Entry[entries.size()]); } - - // FIXME - public abstract String getString(); - - 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); + 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