5b62faaaf148b2755f20a2b58ad0738c9e66d557
[org.ibex.mail.git] / src / org / ibex / mail / MIME.java
1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package org.ibex.mail;
6 import static org.ibex.mail.MailException.*;
7 import org.ibex.crypto.*;
8 import org.ibex.util.*;
9 import org.ibex.mail.protocol.*;
10 import org.ibex.js.*;
11 import org.ibex.io.*;
12 import org.ibex.io.Fountain;
13 import java.util.*;
14 import java.net.*;
15 import java.io.*;
16
17 // FEATURE: MIME RFC2045, 2046, 2049
18
19 /** This class contains logic for encoding and decoding MIME multipart messages */
20 public class MIME {
21
22     /** Part = Headers+Body */
23     public static class Part extends JSReflection implements Fountain {
24         public  final Headers      headers;
25         public  final ContentType  contentType;
26         private final String       encoding;
27
28         private final Fountain     all;
29         private final Fountain     body;
30
31         public Stream getStream()   { return all.getStream(); }
32         public int getNumLines()    { return all.getNumLines(); }
33         public long getLength()     { return all.getLength(); }
34         public Fountain getBody()   { return body; }
35
36         public JS get(JS key) throws JSExn {
37             String k = JSU.toString(key);
38             if ("body".equals(k)) {
39                 StringBuffer sb = new StringBuffer();
40                 getBody().getStream().transcribe(sb);
41                 return JSU.S(sb.toString());
42             }
43             return super.get(key);
44         }
45
46         public Part(final Fountain fount, String[] keyval) {
47             Headers h        = new Headers(fount);
48             this.headers     = keyval==null ? h : new Headers(h, keyval);
49             String ctype     = headers.get("content-type");
50             this.encoding    = headers.get("content-transfer-encoding");
51             String enc = this.encoding;
52             if (enc!=null) enc = enc.toLowerCase();
53             if (!(enc == null || enc.equals("7bit") || enc.equals("8bit") || enc.equals("binary") ||
54                   enc.equals("quoted-printable") || enc.equals("base64"))) {
55                 Log.warn(MIME.class, "unknown TransferEncoding \"" + encoding + "\"");
56                 ctype = "application/octet-stream";
57             }
58             this.contentType = new ContentType(ctype, headers.get("content-description"), headers.get("content-id"), encoding);
59             final long bodylength = fount.getLength() - h.getLength() - 2; /*CRLF*/
60             // FIXME: this is a horrible, tangled mess.
61             this.body = new Fountain() {
62                     public int getNumLines()  { return Stream.countLines(getStream()); }
63                     public long getLength()   { return bodylength; }
64                     public Stream getStream() { return transformBodyStream(Headers.skip(fount.getStream())); }
65                 };
66             this.all =
67                 keyval==null
68                 ? fount
69                 : Fountain.Util.concat(this.headers, Fountain.Util.create("\r\n"), this.body);
70         }
71
72         private Stream transformBodyStream(Stream body) {
73             //"quoted-printable".equals(encoding) ? Encode.QuotedPrintable.decode(body.toString(),false) :
74             //"base64".equals(encoding)           ? Encode.fromBase64(body.toString()) :
75             return body;
76         }
77
78         /*
79         public Part getPart(int i) {
80             Stream stream = body.getStream();
81             Vec v = new Vec();
82             // first part begins with a boundary delimiter
83             for(String s = stream.readln(); s != null; s = stream.readln())
84                 if (s.equals("--" + contentType.parameters.get("boundary"))) break;
85             while(true) {
86                 Stream substream = new BoundaryStream(stream, (String)contentType.parameters.get("boundary"));
87                 Part p = new Part(substream, true, null); // FIXME split off headers
88                 v.addElement(p);
89                 if (substream.isLast()) break;
90             }
91             return parts = (Part[])v.copyInto(new Part[v.size()]);
92         }
93         */
94     }
95
96     /*
97     public static class Boundary implements Stream.Transformer {
98         private final String boundary;
99         private boolean done = false;
100         private boolean last = false;
101         public Boundary(String bounardy) { this.boundary = boundary; }
102         public boolean isLast() { while(!done) readln(); return last; }
103         public Stream transform(Stream stream) {
104             for(String s = stream.readln(); s != null; s = stream.readln()) {
105                 if (boundary != null && (s.equals(boundary) || s.equals(boundary + "--"))) {
106                     body.setLength(body.length() - 2);  // preceeding CRLF is part of delimiter
107                     last = s.equals(boundary + "--");
108                     done = true;
109                     break;
110                 }
111                 body.append(s);
112                 body.append("\r\n");
113                 //lines++;
114             }
115         }
116     }
117     */
118 }