made Headers class truly immutable
authoradam <adam@megacz.com>
Sat, 15 Apr 2006 05:49:23 +0000 (05:49 +0000)
committeradam <adam@megacz.com>
Sat, 15 Apr 2006 05:49:23 +0000 (05:49 +0000)
darcs-hash:20060415054923-5007d-e52a4ebded2fd3386a5c06d7f15ffbb1d556500e.gz

src/org/ibex/mail/Headers.java
src/org/ibex/mail/MIME.java
src/org/ibex/mail/MailingList.java
src/org/ibex/mail/Main.java
src/org/ibex/mail/Message.java
src/org/ibex/mail/protocol/SMTP.java
src/org/ibex/mail/target/MailmanArchives.java

index 26c12ad..a68d9d7 100644 (file)
@@ -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<keyval.length; i+=2)
+            ret = ret.set(keyval[i], keyval[i+1]);
+        return ret;
+    }
+    public Headers remove(String key) { return set(key, null); /* FIXME */ }
+
+    public String getLowerCaseTrimmed(String s) {
+        String ret = get(s);
+        return ret==null ? null : ret.toLowerCase();
+    }
+    
+    public static class Original extends Headers {
+        private final Hash head = new Hash();
+        private final Hash headModified = new Hash();
+        public        int lines;
+        public  final boolean mime;
+        private String raw;
+        private StringFountain fountain;
+
+        public String getString() { return raw; }
+        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"); }
+        public JS get(JS s) throws JSExn { return JSU.S(get(JSU.toString(s).toLowerCase())); }
+        public java.util.Enumeration names() { return head.keys(); }
+        
+        public String get(String s) { return (String)head.get(s.toLowerCase()); }
+        public Original(Stream stream) throws Malformed { this(stream, false); }
+        public Original(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;
+                }
+                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<key.length(); i++)
+                    if (key.charAt(i) < 33 || key.charAt(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<key.length(); i++)
-                if (key.charAt(i) < 33 || key.charAt(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;
     }
+    }
 }
index d395f23..db6ee07 100644 (file)
@@ -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") ||
index ea0e248..82c5ec3 100644 (file)
@@ -158,9 +158,9 @@ public class MailingList implements Target, Iterable<MailingList.Subscriber> {
     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);
index ed1791b..9108be3 100644 (file)
@@ -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;
index 6d160d5..882ca9f 100644 (file)
@@ -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"+
index 9b12907..209de02 100644 (file)
@@ -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(".");
index 12a8703..4e043e9 100644 (file)
@@ -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 =