massive robustness overhaul
authoradam <adam@megacz.com>
Mon, 21 Feb 2005 09:24:49 +0000 (09:24 +0000)
committeradam <adam@megacz.com>
Mon, 21 Feb 2005 09:24:49 +0000 (09:24 +0000)
darcs-hash:20050221092449-5007d-03b880836ded6da2e6d66b5aa63fb834e6cc3fc4.gz

17 files changed:
src/org/ibex/mail/Address.java
src/org/ibex/mail/Confirmation.java
src/org/ibex/mail/MIME.java
src/org/ibex/mail/MailingList.java
src/org/ibex/mail/Message.java
src/org/ibex/mail/Query.java
src/org/ibex/mail/protocol/GMail.java
src/org/ibex/mail/protocol/IMAP.java
src/org/ibex/mail/protocol/Mbox.java
src/org/ibex/mail/protocol/NNTP.java
src/org/ibex/mail/protocol/SMTP.java
src/org/ibex/mail/target/FileBasedMailbox.java
src/org/ibex/mail/target/Mailbox.java
src/org/ibex/mail/target/MailmanArchives.java
src/org/ibex/mail/target/MessageArrayMailbox.java
src/org/ibex/mail/target/Script.java
src/org/ibex/mail/target/Transcript.java

index 694523d..1bf6b04 100644 (file)
@@ -37,9 +37,9 @@ public class Address extends JSReflection implements Serializable {
         host = s.substring(s.indexOf('@')+1);
     }
     public String toString() { return user + "@" + host; }
+    public String coerceToString() { return toString(); }
     public String toString(boolean desc) {
         return desc && description != null && description.length() > 0 ? (description+" <" + toString() + ">") : toString(); }
-    public String coerceToString() { return toString(); }
     public static class Malformed extends Message.Malformed { public Malformed(String s) { super(s); } }
 
     public boolean isLocal() {
index 9bf7804..ba15fb8 100644 (file)
@@ -88,13 +88,12 @@ public abstract class Confirmation implements Externalizable {
     }
 
     public void signAndSend(long secret, Date now) throws IOException, Message.Malformed {
-        SMTP.Outgoing.accept(Message.newMessage(new Stream("From: "    + FROM + "\r\n" +
+        SMTP.Outgoing.accept(Message.newMessage(new Fountain.StringFountain("From: "    + FROM + "\r\n" +
                                                            "To: "      + who.toString(true) + "\r\n" +
                                                            "Subject: confirm " + getDescription() + "\r\n" +
                                                            "\r\n" +
                                                            "Please click the link below to " + getDescription() + "\r\n" +
-                                                           sign(secret)),
-                                                FROM, who)
+                                                           sign(secret)))
                              );
     }
 
index d5018e0..3635fd1 100644 (file)
@@ -3,11 +3,13 @@
 // You may not use this file except in compliance with the License.
 
 package org.ibex.mail;
+import static org.ibex.mail.MailException.*;
 import org.ibex.crypto.*;
 import org.ibex.util.*;
 import org.ibex.mail.protocol.*;
-import org.ibex.io.*;
 import org.ibex.js.*;
+import org.ibex.io.*;
+import org.ibex.io.Fountain;
 import java.util.*;
 import java.net.*;
 import java.io.*;
@@ -17,216 +19,74 @@ import java.io.*;
 /** This class contains logic for encoding and decoding MIME multipart messages */
 public class MIME {
 
-    public static class RFC2047 {
-        public static String decode(String s) {
-            /*
-            try { while (s.indexOf("=?") != -1) {
-                String pre = s.substring(0, s.indexOf("=?"));
-                s = s.substring(s.indexOf("=?") + 2);
-
-                // MIME charset; FIXME use this
-                String charset = s.substring(0, s.indexOf('?')).toLowerCase();
-                s = s.substring(s.indexOf('?') + 1);
-
-                String encoding = s.substring(0, s.indexOf('?')).toLowerCase();
-                s = s.substring(s.indexOf('?') + 1);
-
-                String encodedText = s.substring(0, s.indexOf("?="));
-
-                if (encoding.equals("b"))      encodedText = new String(Base64.decode(encodedText));
-
-                // except that ANY char can be endoed (unlike real qp)
-                else if (encoding.equals("q")) encodedText = MIME.QuotedPrintable.decode(encodedText, true);
-                else Log.warn(MIME.class, "unknown RFC2047 encoding \""+encoding+"\"");
-
-                String post = s.substring(s.indexOf("?=") + 2);
-                s = pre + encodedText + post;
-
-                // FIXME re-encode when transmitting
-
-            } } catch (Exception e) {
-                Log.warn(MIME.class, "error trying to decode RFC2047 encoded-word: \""+s+"\"");
-                Log.warn(MIME.class, e);
-            }
-            */
-            return s;
+    public static class Part extends JSReflection implements Fountain {
+        public  final  Headers      headers;
+        public  final  ContentType  contentType;
+        private final  String       encoding;
+
+        private final Fountain     all;
+        public Stream getStream() { return all.getStream(); }
+        public int getNumLines()  { return all.getNumLines(); }
+        public int getLength()    { return all.getLength(); }
+
+        public Stream getBody()   {
+            return /*
+                     "quoted-printable".equals(encoding) ? Encode.QuotedPrintable.decode(body.toString(),false) :
+                     "base64".equals(encoding)           ? Encode.fromBase64(body.toString()) :
+                   */
+                Headers.skip(getStream());
         }
-    }
-
-    public static class QuotedPrintable {
-        public static String decode(String s, boolean lax) {
-        //
-        //   =XX  -> hex representation, must be uppercase
-        //   9, 32, 33-60, 62-126 can be literal
-        //   9, 32 at end-of-line must get encoded
-        //   trailing whitespace must be deleted when decoding
-        //   =\n = soft line break
-        //   lines cannot be more than 76 chars long
-        //
 
-            // lax is used for RFC2047 headers; removes restrictions on which chars you can encode
-            return s;
-        }
-    }
-
-    // multipart/mixed       -- default
-    // multipart/parallel    -- order of components does not matter
-    // multipart/alternative -- same data, different versions
-    // multipart/digest      -- default content-type of components is message/rfc822
-    // message/rfc822        -- FIXME
-    // message/partial       -- not supported; see RFC 2046, section 5.2.2
-    // message/external-body -- not supported; see RFC 2046, section 5.2.3
-    // FIXME charsets  US-ASCII, ISO-8559-X, 
-    public static class Content extends org.ibex.js.JSReflection {
-        public final String    type;
-        public final String    subtype;
-        public final String    description;
-        public final String    id;
-        public final String    transferEncoding;
-        public final String    charset;
-        public final boolean   composite;
-        public final boolean   alternative;
-        public final Hashtable parameters = new Hashtable();
-        public Content(String header, String description, String id, String transferEncoding) {
-            this.id = id;
-            this.description = description;
-            this.transferEncoding = transferEncoding;
-            if (header == null) { type="text"; subtype="plain"; charset="us-ascii"; alternative=false; composite=false; return; }
-            header = header.trim();
-            if (header.indexOf('/') == -1) {
-                Log.warn(this, "content-type lacks a forward slash: \""+header+"\"");
-                header = "text/plain";
-            }
-            type = header.substring(0, header.indexOf('/')).toLowerCase();
-            header = header.substring(header.indexOf('/') + 1);
-            subtype = (header.indexOf(';') == -1) ? header.toLowerCase() : header.substring(0, header.indexOf(';')).toLowerCase();
-            composite   = type != null && (type.equals("message") || type.equals("multipart"));
-            alternative = composite && subtype.equals("alternative");
-            charset     = parameters.get("charset") == null ? "us-ascii" : parameters.get("charset").toString();
-            if (header.indexOf(';') == -1) return;
-            StringTokenizer st = new StringTokenizer(header.substring(header.indexOf(';') + 1), ";");
-            while(st.hasMoreTokens()) {
-                String key = st.nextToken().trim();
-                if (key.indexOf('=') == -1)
-                    throw new MailException.Malformed("content-type parameter lacks an equals sign: \""+key+"\"");
-                String val = key.substring(key.indexOf('=')+1).trim();
-                if (val.startsWith("\"") && val.endsWith("\"")) val = val.substring(1, val.length() - 2);
-                key = key.substring(0, key.indexOf('=')+1).toLowerCase();
-                parameters.put(key, val);
+        public Part(Fountain all) {
+            this.headers     = new Headers(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") ||
+                  encoding.equals("quoted-printable") || encoding.equals("base64"))) {
+                Log.warn(MIME.class, "unknown TransferEncoding \"" + encoding + "\"");
+                ctype = "application/octet-stream";
             }
+            this.contentType = new ContentType(ctype, headers.get("content-description"), headers.get("content-id"), encoding);
+            this.all = all;
         }
-    }
 
-    public static class Part extends org.ibex.js.JSReflection {
-        public final Content   content;
-        public final boolean   mime;                // true iff Mime-Version is 1.0
-        public final Headers   headers;
-        public final Part[]    subparts;
-        public final String    body;
-        public final int       lines;
-        private final boolean  last;
-
-        private Part[] parseParts(Stream stream) {
+        /*
+        public Part getPart(int i) {
+            Stream stream = body.getStream();
             Vec v = new Vec();
             // first part begins with a boundary delimiter
             for(String s = stream.readln(); s != null; s = stream.readln())
-                if (s.equals("--" + content.parameters.get("boundary"))) break;
+                if (s.equals("--" + contentType.parameters.get("boundary"))) break;
             while(true) {
-                Part p = new Part(stream, (String)content.parameters.get("boundary"), true);
+                Stream substream = new BoundaryStream(stream, (String)contentType.parameters.get("boundary"));
+                Part p = new Part(substream, true, null); // FIXME split off headers
                 v.addElement(p);
-                //lines += p.lines;
-                if (p.last) break;
+                if (substream.isLast()) break;
             }
-            return (Part[])v.copyInto(new Part[v.size()]);
+            return parts = (Part[])v.copyInto(new Part[v.size()]);
         }
-       
-        public Part(Stream stream, String boundary, boolean assumeMime) throws MailException.Malformed {
-            this.headers     = new Headers(stream, assumeMime);
-            this.mime        = assumeMime | (headers.gets("mime-version")!=null&&headers.gets("mime-version").trim().equals("1.0"));
-            String ctype     = headers.gets("content-type");
-            String encoding  = headers.gets("content-transfer-encoding");
-            if (!(encoding == null || encoding.equals("7bit") || encoding.equals("8bit") || encoding.equals("binary") ||
-                  encoding.equals("quoted-printable") || encoding.equals("base64"))) {
-                Log.warn(MIME.class, "unknown TransferEncoding \"" + encoding + "\"");
-                ctype = "application/octet-stream";
-            }
-            content = new Content(ctype, headers.gets("content-description"), headers.gets("content-id"), encoding);
-            //if (content.composite) { subparts = parseParts(stream); body = null; last = false; lines = 0; return; }
-            subparts = null;
-            boolean last = false;
-            int lines = 0;
-            StringBuffer body = new StringBuffer();
+        */
+    }
+    /*
+    public static class Boundary implements Stream.Transformer {
+        private final String boundary;
+        private boolean done = false;
+        private boolean last = false;
+        public Boundary(String bounardy) { this.boundary = boundary; }
+        public boolean isLast() { while(!done) readln(); return last; }
+        public Stream transform(Stream stream) {
             for(String s = stream.readln(); s != null; s = stream.readln()) {
                 if (boundary != null && (s.equals(boundary) || s.equals(boundary + "--"))) {
                     body.setLength(body.length() - 2);  // preceeding CRLF is part of delimiter
                     last = s.equals(boundary + "--");
+                    done = true;
                     break;
                 }
                 body.append(s);
                 body.append("\r\n");
-                lines++;
-            }
-            if ("quoted-printable".equals(encoding)) this.body = MIME.QuotedPrintable.decode(body.toString(),false);
-            else if ("base64".equals(encoding)) this.body = new String(Encode.fromBase64(body.toString()));
-            else this.body = body.toString();
-            this.last = last;
-            this.lines = lines + headers.lines;
-        }
-    }
-
-    public static class Headers extends org.ibex.js.JSReflection {
-       private Hashtable head = new Hashtable();
-        public final int lines;
-        public final String raw;
-        public String toString() { return raw; }
-        public JS get(JS s) throws JSExn { return JSU.S((String)head.get(JSU.toString(s).toLowerCase())); }
-        public String gets(String s) { return (String)head.get(s.toLowerCase()); }
-        public static String uncomment(String val) {
-            boolean inquotes = false;
-            for(int i=0; i<val.length(); i++) {
-                if (val.charAt(i) == '\"') inquotes = !inquotes;
-                if (val.charAt(i) == '(' && !inquotes)
-                    val = val.substring(0, i) + val.substring(val.indexOf(i--, ')') + 1);
-            }
-            return val;
-        }
-        public Headers(Stream stream, boolean assumeMime) throws MailException.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 MailException.Malformed("Message began with a blank line; no headers");
-                    head.put(key, head.get(key) + " " + s.trim());
-                    continue;
-                }
-                if (s.indexOf(':') == -1) throw new MailException.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 MailException.Malformed("Header key \""+key+"\" contains invalid character \"" + key.charAt(i) + "\"");
-                String val = s.substring(s.indexOf(':') + 1).trim();
-                if (gets(key) != null) val = gets(key) + " " + val; // just append it to the previous one;
-                head.put(key, val);
-            }
-            this.raw = all.toString();
-            this.lines = lines;
-
-            java.util.Enumeration e = head.keys();
-            boolean mime = assumeMime | (gets("mime-version") != null && gets("mime-version").trim().equals("1.0"));
-            /*
-            while(e.hasNext()) {
-                String k = (String)e.next();
-                String v = (String)head.get(k);
-                if (mime) k = MIME.RFC2047.decode(k);
-                v = uncomment(v);
-                if (mime) v = MIME.RFC2047.decode(v);
-                head.put(k, v);
+                //lines++;
             }
-            */
         }
     }
+    */
 }
index a27683d..2702de7 100644 (file)
@@ -77,7 +77,7 @@ public class MailingList extends Target implements Serializable {
 
     public void accept(Message m) throws IOException, MailException {
         try {
-            m = Message.newMessage(new Stream("List-Id: " + one_line_description + "<"+address+">\r\n" +
+            m = Message.newMessage(new Fountain.StringFountain("List-Id: " + one_line_description + "<"+address+">\r\n" +
                                        m.toString() +
                                        "--\r\n" +
                                        message_footer + "\r\n" +
@@ -95,7 +95,10 @@ public class MailingList extends Target implements Serializable {
                 String s = subscribers[i];
                 try {
                     Log.warn(MailingList.class, "  trying " + s);
-                    SMTP.Outgoing.accept(Message.newMessage(new Stream(m.toString()), address, Address.parse(s)));
+                    /* FIXME
+                    SMTP.Outgoing.accept(Message.newMessage(new Fountain.StringFountain(m.toString()),
+                                                            address, Address.parse(s)));
+                    */
                     Log.warn("[list]", "successfully sent to " + s);
                 } catch (Exception e2) {
                     Log.error("[list]", e2);
index 2d13408..4d02e43 100644 (file)
@@ -6,24 +6,21 @@ package org.ibex.mail;
 import org.ibex.crypto.*;
 import org.ibex.util.*;
 import org.ibex.mail.protocol.*;
+import org.ibex.js.*;
 import org.ibex.io.*;
+import org.ibex.io.Fountain;
 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)
-
-// soft line limit (suggested): 78 chars /  hard line limit: 998 chars
-// date/time parsing: see spec, 3.3
-
+// 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: threading as in http://www.jwz.org/doc/threading.html
-// FEATURE: lazy body
-// FIXME RFC822 1,000-char limit per line
 
 /** 
  *  [immutable] This class encapsulates a message "floating in the
@@ -46,39 +43,46 @@ public class Message extends MIME.Part {
     public final Address[]   cc;
     public final Address[]   bcc;
 
-    public static Message newMessage(Stream stream) throws Malformed { return newMessage(stream, null, null); }
-    public static Message newMessage(Stream stream, Address from, Address to) throws Malformed {
-        if (from == null && to == null) return new Message(stream);
+    public static Message newMessage(Fountain in) throws Malformed { return new Message(in); }
+
+    // FIXME
+    public static Message newMessage(Fountain in, Address from, Address to) throws Malformed {
         StringBuffer sb = new StringBuffer();
-        boolean inheaders = true;
         if (from != null) sb.append("Return-Path: " + from.toString(true) + "\r\n");
+        Stream stream = in.getStream();
         while(true) {
             String s = stream.readln();
             if (s == null) break;
-            if (inheaders && to != null && s.toLowerCase().startsWith("envelope-to:")) continue;
-            if (inheaders && from != null && s.toLowerCase().startsWith("return-path:")) continue;
-            if (s.length() == 0 && inheaders) {
-                inheaders = false;
+            if (to != null && s.toLowerCase().startsWith("envelope-to:")) continue;
+            if (from != null && s.toLowerCase().startsWith("return-path:")) continue;
+            if (s.length() == 0) {
                 if (to != null) sb.append("Envelope-To: " + to.toString(true) + "\r\n");
+                sb.append("\r\n");
+                break;
             }
             sb.append(s);
             sb.append("\r\n");
         }
-        return newMessage(new Stream(sb.toString()));
+        for(String s = stream.readln(); s != null; s = stream.readln()) {
+            sb.append(s);
+            sb.append("\r\n");
+        }
+        return new Message(new Fountain.StringFountain(sb.toString()));
     }
-    private Message(Stream stream) throws Malformed {
-        super(stream, null, false);
-        this.envelopeTo   = headers.gets("Envelope-To") != null ? Address.parse(headers.gets("Envelope-To")) : null;
-        this.envelopeFrom = headers.gets("Return-Path") != null ? Address.parse(headers.gets("Return-Path")) : null;
-        this.to           = headers.gets("To") != null ? Address.parse(headers.gets("To")) : this.envelopeTo;
-        this.from         = headers.gets("From") != null ? Address.parse(headers.gets("From")) : this.envelopeFrom;
-        this.replyto      = headers.gets("Reply-To") == null ? null : Address.parse(headers.gets("Reply-To"));
-        this.subject      = headers.gets("Subject");
-        this.messageid    = headers.gets("Message-Id");
-        this.cc           = Address.list(headers.gets("Cc"));
-        this.bcc          = Address.list(headers.gets("BCc"));
-        this.date         = parseDate(headers.gets("Date")) == null ? new Date() : parseDate(headers.gets("Date"));
-        this.arrival      = this.date; // FIXME wrong
+
+    private Message(Fountain in) throws Malformed {
+        super(in);
+        this.envelopeTo   = headers.get("Envelope-To") != null ? Address.parse(headers.get("Envelope-To")) : null;
+        this.envelopeFrom = headers.get("Return-Path") != null ? Address.parse(headers.get("Return-Path")) : null;
+        this.to           = headers.get("To") != null ? Address.parse(headers.get("To")) : this.envelopeTo;
+        this.from         = headers.get("From") != null ? Address.parse(headers.get("From")) : this.envelopeFrom;
+        this.replyto      = headers.get("Reply-To") == null ? null : Address.parse(headers.get("Reply-To"));
+        this.subject      = headers.get("Subject");
+        this.messageid    = headers.get("Message-Id");
+        this.cc           = Address.list(headers.get("Cc"));
+        this.bcc          = Address.list(headers.get("Bcc"));
+        this.date         = parseDate(headers.get("Date")) == null ? new Date() : parseDate(headers.get("Date"));
+        this.arrival      = this.date; // FIXME wrong; grab this from traces?
     }
 
 
@@ -98,7 +102,11 @@ public class Message extends MIME.Part {
         return ret.toString();
     }
 
-    public static Date parseDate(String s) { return null; } // FIXME!!!
+    public static Date parseDate(String s) {
+        // FIXME!!! this must be robust
+        // date/time parsing: see spec, 3.3
+        return null;
+    }
    
     //  use null-sender for error messages (don't send errors to the null addr)
     public Message bounce(String reason) {
@@ -106,20 +114,9 @@ public class Message extends MIME.Part {
         return null;
     }  // FIXME!
 
+    public String toString() { throw new RuntimeException("Message.toString() called"); }
     public String summary() { return "[" + envelopeFrom + " -> " + envelopeTo + "] " + subject; }
 
-    public void dump(Stream s) {
-        s.setNewline("\r\n");
-        s.println(headers.raw);
-        s.println("");
-        s.println(body);
-        s.flush();
-    }
-
-    public int size()        { return headers.raw.length() + 2 /* CRLF */ + body.length(); }
-    public String toString() { return headers.raw + "\r\n" + body; }
-    public String coerceToString() { return toString(); }
-
     public static class Malformed extends Exception { public Malformed(String s) { super(s); } }
 }
 
index 8143bbe..d308da8 100644 (file)
@@ -97,18 +97,21 @@ public class Query {
                                     (earliest==null||it.cur().date.after(earliest));
             case ARRIVAL:    return (latest == null || it.cur().arrival.before(latest)) &&
                                     (earliest == null || it.cur().arrival.after(earliest));
-            case SIZE:       return it.cur().size() >= min && it.cur().size() <= max;
-            case HEADER:     return it.cur().headers.gets(key) != null &&
-                                 ((String)it.cur().headers.gets(key)).toLowerCase().indexOf(text.toLowerCase()) != -1;
-            case BODY:       return it.cur().body.toLowerCase().indexOf(text.toLowerCase()) != -1;
-            case FULL:       return it.cur().body.toLowerCase().indexOf(text.toLowerCase()) != -1 ||
-                                 it.cur().headers.raw.indexOf(text) != -1;
+            case HEADER:     return it.cur().headers.get(key) != null &&
+                                 ((String)it.cur().headers.get(key)).toLowerCase().indexOf(text.toLowerCase()) != -1;
             case DELETED:    return it.deleted();
             case SEEN:       return it.seen();
             case FLAGGED:    return it.flagged();
             case DRAFT:      return it.draft();
             case ANSWERED:   return it.answered();
             case RECENT:     return it.recent();
+
+            // FIXME: super inefficient
+            case BODY:       throw new RuntimeException("BODY searches are not supported because they are slow");
+            case FULL:       throw new RuntimeException("FULL searches are not supported because they are slow");
+            case SIZE:       throw new RuntimeException("SIZE searches are not supported because Adam is lame");
+                //return it.cur().size() >= min && it.cur().size() <= max;
+
             default:         throw new Error("this should not happen");
         }
     }
index f9b2dcc..02b85e4 100644 (file)
@@ -3,7 +3,6 @@
 // You may not use this file except in compliance with the License.
 
 package org.ibex.mail.protocol;
-import org.ibex.io.*;
 import org.ibex.crypto.*;
 import org.ibex.mail.protocol.*;
 import org.ibex.jinetd.Listener;
@@ -17,6 +16,8 @@ import java.util.*;
 import java.net.*;
 import java.text.*;
 import java.io.*;
+import org.ibex.io.*;
+import org.ibex.io.Fountain;
 
 public class GMail extends Account {
 
@@ -129,7 +130,7 @@ public class GMail extends Account {
                 final int num = i;
                 if (q.match(new Mailbox.Iterator() {
                         public Message cur() { return m; }
-                        public MIME.Headers head() { return m.headers; }
+                        public Headers head() { return m.headers; }
                         public boolean next() { return false; }
                         public int     uid() { return num; }
                         public int     num() { return num; }
@@ -154,7 +155,8 @@ public class GMail extends Account {
                         public void    setFlags(int flags) { }
                     })) {
                     Log.info(GMail.class, "fetch " + summaries[i].subject);
-                    client.fetch(i+1, 0, m.size(), m,/* summaries[i].getIntId()*/ i);
+                    throw new Error("broken");
+                    //client.fetch(i+1, 0, m.size(), m,/* summaries[i].getIntId()*/ i);
                 }
             } catch (Exception e) { Log.warn(this, e); }
         }
@@ -227,7 +229,7 @@ public class GMail extends Account {
             "</form>\r\n"+
             "</body></html>\r\n";
         try {
-            captchaMessage = Message.newMessage(new Stream(str));
+            captchaMessage = Message.newMessage(new Fountain.StringFountain(str));
         } catch (Message.Malformed e) {
             Log.warn(this, e);
             throw new IOException(e.toString());
@@ -287,11 +289,14 @@ public class GMail extends Account {
         public int getIntId() { return Math.abs(Integer.parseInt(id.toLowerCase().substring(id.length()-7), 16)); }
 
         public Message getMessage() throws Message.Malformed, IOException {
+            throw new Error("broken right now");
+            /*
             if (message != null) return message;
             Stream thestream =
                 new Stream(new HTTP(gmail+"?search=query&start=0&view=om&th=" + URLEncoder.encode(id)).GET(null, jar));
             thestream.readln();
-            return message = Message.newMessage(thestream);
+            return message = Message.newMessage(new Fountain.StringFountainthestream);
+            */
         }
         
         public Summary(JSArray m) {
index a09f4b9..3a02998 100644 (file)
@@ -166,7 +166,7 @@ public class IMAP {
         public void delete(Mailbox m) { if (!m.equals(inbox)) m.destroy(false); else throw new Bad("can't delete inbox"); }
         public void create(String m) { mailbox(m, true, false); }
         public void append(String m,int f,Date a,String b) { try {
-            mailbox(m,false).add(Message.newMessage(new Stream(b)),f|Mailbox.Flag.RECENT);
+            mailbox(m,false).add(Message.newMessage(new Fountain.StringFountain(b)),f|Mailbox.Flag.RECENT);
         } catch (Message.Malformed e) { throw new No(e.getMessage()); } }
         public void check() { }
         public void noop() { }
@@ -437,8 +437,8 @@ public class IMAP {
                 } else if (s.equals("FLAGS")) {        spec|=FLAGS;        if(e){r.append(" ");r.append(Printer.flags(flags));}
                 } else if (s.equals("INTERNALDATE")) { spec|=INTERNALDATE; if(e){r.append(" ");r.append(Printer.date(m.arrival));}
                 } else if (s.equals("RFC822")) {       spec|=RFC822;       if(e){r.append(" ");r.append(Printer.message(m));}
-                } else if (s.equals("RFC822.TEXT")) {  spec|=RFC822TEXT;   if(e){r.append(" ");r.append(Printer.qq(m.body));}
-                } else if (s.equals("RFC822.HEADER")){ spec|=HEADER;if(e){r.append(" ");r.append(Printer.qq(m.headers.raw+"\r\n"));}
+                } else if (s.equals("RFC822.TEXT")) {  spec|=RFC822TEXT;   if(e){r.append(" ");r.append(Printer.qq(m.getBodyString()));}
+                } else if (s.equals("RFC822.HEADER")){ spec|=HEADER;if(e){r.append(" ");r.append(Printer.qq(m.headers.getString()+"\r\n"));}
                 } else if (s.equals("RFC822.SIZE")) {  spec|=RFC822SIZE;   if(e){r.append(" ");r.append(m.size());}
                 } else if (s.equals("UID")) {          spec|=UID;          if(e){r.append(" ");r.append(muid); }
                 } else if (!(s.equals("BODY.PEEK") || s.equals("BODY"))) { throw new Server.No("unknown fetch argument: " + s);
@@ -455,10 +455,10 @@ public class IMAP {
                     Parser.Token[] list = t[++i].l();
                     s = list.length == 0 ? "" : list[0].s.toUpperCase();
                     r.append(s);
-                    if (list.length == 0)                   { spec |= RFC822TEXT;   if(e) payload = m.headers.raw+"\r\n"+m.body; }
-                    else if (s.equals("") || s.equals("1")) { spec |= RFC822TEXT;   if(e) payload = m.headers.raw+"\r\n"+m.body; }
-                    else if (s.equals("TEXT"))              { spec |= RFC822TEXT;   if(e) payload = m.body; }
-                    else if (s.equals("HEADER"))            { spec |= HEADER;       if(e) payload = m.headers.raw+"\r\n"; }
+                    if (list.length == 0)                   { spec |= RFC822TEXT;   if(e) payload = m.headers.getString()+"\r\n"+m.getBodyString(); }
+                    else if (s.equals("") || s.equals("1")) { spec |= RFC822TEXT;   if(e) payload = m.headers.getString()+"\r\n"+m.getBodyString(); }
+                    else if (s.equals("TEXT"))              { spec |= RFC822TEXT;   if(e) payload = m.getBodyString(); }
+                    else if (s.equals("HEADER"))            { spec |= HEADER;       if(e) payload = m.headers.getString()+"\r\n"; }
                     else if (s.equals("HEADER.FIELDS"))     { spec |= FIELDS;     payload=headers(r,t[i].l()[1].sl(),false,m,e); }
                     else if (s.equals("HEADER.FIELDS.NOT")) { spec |= FIELDSNOT;  payload=headers(r,t[i].l()[1].sl(),true,m,e); }
                     else if (s.equals("MIME")) {              throw new Server.Bad("MIME not supported"); }
@@ -488,7 +488,7 @@ public class IMAP {
             if (!negate) {
                 if(e) for(int j=0; j<headers.length; j++) {
                     r.append(headers[j] + (j<headers.length-1?" ":""));
-                    if (m.headers.gets(headers[j]) != null) payload += headers[j]+": "+m.headers.gets(headers[j])+"\r\n";
+                    if (m.headers.get(headers[j]) != null) payload += headers[j]+": "+m.headers.get(headers[j])+"\r\n";
                 }
             } else {
                throw new Server.No("HEADERS.NOT temporarily disaled");
@@ -497,7 +497,7 @@ public class IMAP {
                 if(e) { OUTER: for(Enumeration x=m.headers.keys(); x.hasMoreElements();) {
                     String key = (String)x.nextElement();
                     for(int j=0; j<headers.length; j++) if (key.equalsIgnoreCase(headers[j])) continue OUTER;
-                    payload += key + ": " + m.headers.gets(key)+"\r\n";
+                    payload += key + ": " + m.headers.get(key)+"\r\n";
                 } }
                */
             }
@@ -787,7 +787,7 @@ public class IMAP {
         }
         static String bodystructure(Message m) {
             // FIXME
-            return "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" "+m.size()+" "+m.lines+")";
+            return "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" "+m.size()+" "+m.lines()+")";
         }
         static String message(Message m) { return m.toString(); }
         static String date(Date d) { return "\""+d.toString()+"\""; }
@@ -796,12 +796,12 @@ public class IMAP {
                 "(" + quotify(m.arrival.toString()) +
                 " " + quotify(m.subject) +          
                 " " + addressList(m.from) +      
-                " " + addressList(m.headers.gets("sender")) +
+                " " + addressList(m.headers.get("sender")) +
                 " " + addressList(m.replyto) + 
                 " " + addressList(m.to) + 
                 " " + addressList(m.cc) + 
                 " " + addressList(m.bcc) + 
-                " " + quotify((String)m.headers.gets("in-reply-to")) +
+                " " + quotify((String)m.headers.get("in-reply-to")) +
                 " " + quotify(m.messageid) +
                 ")";
         }
index 277ab40..108fefa 100644 (file)
@@ -22,7 +22,7 @@ public class Mbox {
         for(String s = stream.readln(); ; s = stream.readln()) {
             if (s == null || s.startsWith("From ")) {
                 try {
-                    if (buf != null) vec.addElement(Message.newMessage(new Stream(buf.toString())));
+                    if (buf != null) vec.addElement(Message.newMessage(new Fountain.StringFountain(buf.toString())));
                 } catch (Exception e) { Log.warn(Mbox.class, e); }
                 if (s == null) break;
                 buf = new StringBuffer();
index f822ef5..f1291b6 100644 (file)
@@ -73,7 +73,7 @@ public class NNTP {
             Mailbox.Iterator it = current.iterator(q);
             if (!it.next()) return null;
             try {
-                Message m = body ? it.cur() : Message.newMessage(new Stream(it.head() + "\r\n"));
+                Message m = body ? it.cur() : Message.newMessage(new Fountain.StringFountain(it.head() + "\r\n"));
                 //Message m = it.cur(); // FIXME
                 return new Article(it.num(), m);
             } catch (Exception e) { return null; }
@@ -130,10 +130,10 @@ public class NNTP {
             }
             int code = (head && body) ? 220 : head ? 221 : body ? 222 : 223;
             println(code + " " + a.num + " <" + a.message.messageid + "> get ready for some stuff...");
-            if (head) println(a.message.headers.raw);
+            if (head) println(a.message.headers.getString());
             if (head && body) println();
             if (body) {
-                Stream stream = new Stream(a.message.body);
+                Stream stream = a.message.getBody();
                 while(true) {
                     s = stream.readln();
                     if (s == null) break;
@@ -199,9 +199,9 @@ public class NNTP {
                     Mailbox.Iterator it = api.current.iterator(Query.messagenum(start, end));
                     while(it.next()) {
                         try {
-                            Message m = Message.newMessage(new Stream(it.head() + "\r\n"));
+                            Message m = it.cur();
                             println(it.num()+"\t"+m.subject+"\t"+m.from+"\t"+m.date+"\t"+m.messageid+"\t"+
-                                    m.headers.gets("references") + "\t" + m.size() + "\t" + m.lines);
+                                    m.headers.get("references") + "\t" + m.size() + "\t" + m.lines());
                         } catch (Exception e) { Log.error(this, e); }
                     }
                     println(".");
index b70b5f7..a26b2b9 100644 (file)
@@ -108,7 +108,7 @@ public class SMTP {
                         String body = buf.toString();
                         Message m = null;
                         for(int i=0; i<to.size(); i++) {
-                           m = Message.newMessage(new Stream(body), from, (Address)to.elementAt(i));
+                           m = Message.newMessage(new Fountain.StringFountain(body), from, (Address)to.elementAt(i));
                             if (!m.envelopeTo.isLocal()) Outgoing.accept(m);
                             else                         Target.root.accept(m);
                        }
@@ -195,7 +195,7 @@ public class SMTP {
                 conn.println("MAIL FROM:<" + m.envelopeFrom.user + "@" + m.envelopeFrom.host+">");  check(conn.readln(), conn);
                 conn.println("RCPT TO:<"   + m.envelopeTo.user + "@" + m.envelopeTo.host+">");      check(conn.readln(), conn);
                 conn.println("DATA");                          check(conn.readln(), conn);
-                Stream stream = new Stream(m.toString());
+                Stream stream = m.getStream();
                 boolean inheaders = true;
                 while(true) {
                     String s = stream.readln();
index 8d44198..d11d8ab 100644 (file)
@@ -106,12 +106,11 @@ public class FileBasedMailbox extends Mailbox.Default {
             // Accessors //////////////////////////////////////////////////////////////////////////////
 
             public boolean seen() { return seen; }
-            public MIME.Headers headers() { return new MIME.Headers(new Stream(new ByteArrayInputStream(header)), true); }
+            public Headers headers() { return new Headers(new Stream(new ByteArrayInputStream(header)), true); }
             public Message message(Cache cache) { try {
                 FileInputStream fis = null;
                 try {
-                    fis = new FileInputStream(cache.dir.getParent() + slash + name);
-                    return Message.newMessage(new Stream(fis));
+                    return Message.newMessage(new Fountain.File(new File(cache.dir.getParent() + slash + name)));
                 } finally { if (fis != null) fis.close(); }
             } catch (IOException e) { throw new MailException.IOException(e);
             } catch (Message.Malformed e) { throw new MailException(e.getMessage()); }
@@ -122,7 +121,7 @@ public class FileBasedMailbox extends Mailbox.Default {
             public static Transaction create(File f) throws IOException {
                 final boolean seen = f.lastModified() == MAGIC_DATE;
                 final String name = f.getName();
-                final byte[] header = new MIME.Headers(new Stream(new FileInputStream(f)), true).toString().getBytes();
+                final byte[] header = new Headers(new Stream(new FileInputStream(f)), true).getString().getBytes();
                 return new Transaction() {
                         public void executeOn(Object o, Date now) {
                             Cache cache = (Cache)o;
@@ -218,10 +217,9 @@ public class FileBasedMailbox extends Mailbox.Default {
                 if (!f.exists() && !target.exists()) break;
                 Log.error(this, "aieeee!!!! target of add() already exists: " + target.getAbsolutePath());
             }
-            FileOutputStream fo = new FileOutputStream(f);
-            Stream stream = new Stream(fo);
-            message.dump(stream);
-            fo.close();
+            Stream stream = new Stream(new FileOutputStream(f));
+            message.getStream().transcribe(stream);
+            stream.close();
             f.renameTo(new File(fullname));
             f = new File(fullname);
             if ((flags & Mailbox.Flag.SEEN) == Mailbox.Flag.SEEN) f.setLastModified(MAGIC_DATE);
@@ -234,7 +232,7 @@ public class FileBasedMailbox extends Mailbox.Default {
         int cur = -1;
         private Cache.Entry entry() { return cache.getLinear(cur); }
         private File file() { return new File(cache.dir.getParent() + slash + entry().name); }
-        public MIME.Headers head() { return done() ? null : entry().headers(); }
+        public Headers head() { return done() ? null : entry().headers(); }
         public boolean done() { return cur >= cache.size(); }
         public boolean next() { cur++; return !done(); }
         public boolean seen() { return done() ? false : entry().seen(); }
index b0ea405..ec122de 100644 (file)
@@ -116,7 +116,7 @@ public abstract class Mailbox extends Target {
 
     public static interface Iterator {
         public abstract Message cur();
-        public abstract MIME.Headers head();
+        public abstract Headers head();
         public abstract boolean next();
         public abstract int     uid();
         public abstract int     num();
@@ -148,7 +148,7 @@ public abstract class Mailbox extends Target {
             private Iterator it;
             public Wrapper(Iterator it) { this.it = it; }
             public Message cur() { return it.cur(); }
-            public MIME.Headers head() { return it.head(); }
+            public Headers head() { return it.head(); }
             public boolean next() { return it.next(); }
             public int     uid() { return it.uid(); }
             public int     flags() { return it.flags(); }
@@ -184,7 +184,7 @@ public abstract class Mailbox extends Target {
         public static class NullIterator extends Mailbox.Default.Iterator {
             public NullIterator() { }
             public Message cur() { return null; }
-            public MIME.Headers head() { return null; }
+            public Headers head() { return null; }
             public boolean next() { return false; }
             public int     uid() { return 0; }
             public int     flags() { return 0; }
index 316d1e0..12a8703 100644 (file)
@@ -27,7 +27,7 @@ public class MailmanArchives extends Mailbox.Default {
                 if (s == null || s.startsWith("From ")) {
                     if (acc != null) {
                         Log.warn("[msg]", acc.toString());
-                        all.addElement(Message.newMessage(new Stream(acc.toString())));
+                        all.addElement(Message.newMessage(new Fountain.StringFountain(acc.toString())));
                     }
                     if (s == null) break;
                     acc = new StringBuffer();
@@ -59,7 +59,7 @@ public class MailmanArchives extends Mailbox.Default {
         public int     num() { return num; }
 
         public Message cur() { return messages[num]; }
-        public MIME.Headers head() { return messages[num].headers; }
+        public Headers head() { return messages[num].headers; }
         public boolean next() { return (++num) < messages.length; }
         public void    delete() { }
 
index 4da4375..d2335b8 100644 (file)
@@ -26,7 +26,7 @@ public class MessageArrayMailbox extends Mailbox.Default {
         private int position = -1;
 
         public Message cur()  { return messages[position]; }
-        public MIME.Headers head() { return messages[position].headers; }
+        public Headers head() { return messages[position].headers; }
         public boolean next() { return ++position < messages.length; }
         public int     uid()  { return position+1; }
         public int     num()  { return position+1; }
index 3a87567..912c732 100644 (file)
@@ -153,9 +153,11 @@ public class Script extends Target {
                     }
                     if (envelopeTo == null) envelopeTo = to;
                     if (envelopeFrom == null) envelopeFrom = from;
-                    Message message = Message.newMessage(new org.ibex.io.Stream(headers.toString() + "\r\n" + body),
-                                                         envelopeFrom,
-                                                         envelopeTo);
+                    Message message =
+                        Message.newMessage(new org.ibex.io.Fountain.StringFountain(headers.toString() + "\r\n" + body),
+                                           envelopeFrom,
+                                           envelopeTo
+                                           );
                     //org.ibex.mail.protocol.SMTP.Outgoing.accept(message);
                     boolean ok = org.ibex.mail.protocol.SMTP.Outgoing.attempt(message);
                     if (!ok) throw new JSExn("SMTP server rejected message");
@@ -163,7 +165,7 @@ public class Script extends Target {
                 }
                 if (name.equals("mail.forward2") || name.equals("forward2")) {
                     try {
-                        Message m2 = Message.newMessage(new org.ibex.io.Stream(m.toString()),
+                        Message m2 = Message.newMessage(new org.ibex.io.Fountain.StringFountain(m.toString()),
                                                         m.envelopeFrom,
                                                         new Address(JSU.toString(a)));
                         org.ibex.mail.protocol.SMTP.Outgoing.accept(m2);
@@ -176,9 +178,7 @@ public class Script extends Target {
                 if (name.equals("mail.forward") || name.equals("forward")) { return new Target() {
                         public void accept(Message m) throws MailException {
                             try {
-                                Message m2 = Message.newMessage(new org.ibex.io.Stream(m.toString()),
-                                                                m.envelopeFrom,
-                                                                new Address(JSU.toString(a)));
+                                Message m2 = Message.newMessage(m, m.envelopeFrom, new Address(JSU.toString(a)));
                                 org.ibex.mail.protocol.SMTP.Outgoing.accept(m2);
                             } catch (Exception e) {
                                 throw new MailException(e.toString());
index dbc1c91..3000b0d 100644 (file)
@@ -39,7 +39,7 @@ public class Transcript extends Target {
             File target = new File(today.getPath() + File.separatorChar + time + ".txt");
             OutputStream os = new FileOutputStream(target);
             try {
-                message.dump(new Stream(os));
+                message.getStream().transcribe(new Stream(os));
                 os.flush();
             } finally { os.close(); }
         } catch (IOException e) { throw new MailException.IOException(e); }