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.
6 import static org.ibex.mail.MailException.*;
7 import org.ibex.crypto.*;
8 import org.ibex.util.*;
9 import org.ibex.mail.protocol.*;
12 import org.ibex.io.Fountain;
17 // FEATURE: MIME RFC2045, 2046, 2049
19 /** This class contains logic for encoding and decoding MIME multipart messages */
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;
28 private final Fountain all;
29 private final Fountain body;
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; }
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());
43 return super.get(key);
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";
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())); }
69 : Fountain.Util.concat(this.headers, Fountain.Util.create("\r\n"), this.body);
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()) :
79 public Part getPart(int i) {
80 Stream stream = body.getStream();
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;
86 Stream substream = new BoundaryStream(stream, (String)contentType.parameters.get("boundary"));
87 Part p = new Part(substream, true, null); // FIXME split off headers
89 if (substream.isLast()) break;
91 return parts = (Part[])v.copyInto(new Part[v.size()]);
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 + "--");