improve logic for header-stripping in MIME.java
[org.ibex.mail.git] / src / org / ibex / mail / MIME.java
index 58d0621..477ce63 100644 (file)
+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// 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.js.*;
+import org.ibex.io.*;
+import org.ibex.io.Fountain;
+import java.util.*;
+import java.net.*;
+import java.io.*;
 
 // FEATURE: MIME RFC2045, 2046, 2049
 
 /** This class contains logic for encoding and decoding MIME multipart messages */
 public class MIME {
-    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;
+
+    /** Part = Headers+Body */
+    public static class Part extends JSReflection implements Fountain {
+        public  final Headers      headers;
+        public  final ContentType  contentType;
+        private final String       encoding;
+
+        private final Fountain     all;
+        private final Fountain     body;
+
+        public Stream getStream()   { return all.getStream(); }
+        public int getNumLines()    { return all.getNumLines(); }
+        public long getLength()     { return all.getLength(); }
+        public Fountain getBody()   { return body; }
+
+        public JS get(JS key) throws JSExn {
+            String k = JSU.toString(key);
+            if ("body".equals(k)) {
+                StringBuffer sb = new StringBuffer();
+                getBody().getStream().transcribe(sb);
+                return JSU.S(sb.toString());
+            }
+            return super.get(key);
+        }
+
+        public Part(final Fountain fount, String[] keyval) {
+            Headers h        = new Headers(fount);
+            this.headers     = keyval==null ? h : new Headers(h, keyval);
+            String ctype     = headers.get("content-type");
+            this.encoding    = headers.get("content-transfer-encoding");
+            String enc = this.encoding;
+            if (enc!=null) enc = enc.toLowerCase();
+            if (!(enc == null || enc.equals("7bit") || enc.equals("8bit") || enc.equals("binary") ||
+                  enc.equals("quoted-printable") || enc.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);
+            // FIXME: this is a horrible, tangled mess.
+            this.body = new Fountain() {
+                    public int getNumLines()  { return Stream.countLines(this.getStream()); }
+                    public long getLength()   { return Stream.countBytes(this.getStream()); }
+                    public Stream getStream() { return transformBodyStream(Headers.skip(fount.getStream())); }
+                };
+            this.all =
+                keyval==null
+                ? fount
+                : Fountain.Util.concat(this.headers, Fountain.Util.create("\r\n"), this.body);
+        }
+
+        private Stream transformBodyStream(Stream body) {
+            //"quoted-printable".equals(encoding) ? Encode.QuotedPrintable.decode(body.toString(),false) :
+            //"base64".equals(encoding)           ? Encode.fromBase64(body.toString()) :
+            return body;
+        }
+
+        /*
+        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("--" + contentType.parameters.get("boundary"))) break;
+            while(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);
+                if (substream.isLast()) break;
+            }
+            return parts = (Part[])v.copyInto(new Part[v.size()]);
+        }
+        */
+    }
+
+    /*
+    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++;
+            }
         }
     }
+    */
 }