X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2Fmail%2FMIME.java;h=477ce639049228da8e82b67d0993918946a755c8;hb=43b53fd9e204f99ff18101c029e8c3a3a7cf5763;hp=8f4b99c70a7c58ad689efacc8d157010625396d0;hpb=53487d9d2a16e5b6274f0fcb2d4886737079ff08;p=org.ibex.mail.git diff --git a/src/org/ibex/mail/MIME.java b/src/org/ibex/mail/MIME.java index 8f4b99c..477ce63 100644 --- a/src/org/ibex/mail/MIME.java +++ b/src/org/ibex/mail/MIME.java @@ -1,8 +1,15 @@ +// 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.*; @@ -12,199 +19,99 @@ 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); + /** 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 s; + return super.get(key); } - } - - 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) throw new MailException.Malformed("content-type lacks a forward slash: \""+header+"\""); - 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(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); } - } - 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; - private final boolean last; + private Stream transformBodyStream(Stream body) { + //"quoted-printable".equals(encoding) ? Encode.QuotedPrintable.decode(body.toString(),false) : + //"base64".equals(encoding) ? Encode.fromBase64(body.toString()) : + return body; + } - 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); - 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.get("mime-version")!=null&&headers.get("mime-version").trim().equals("1.0")); - String ctype = headers.get("content-type"); - String 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"; - } - content = new Content(ctype, headers.get("content-description"), headers.get("content-id"), encoding); - if (content.composite) { subparts = parseParts(stream); body = null; last = false; return; } - subparts = null; - boolean last = false; - 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); - } - if ("quoted-printable".equals(encoding)) this.body = MIME.QuotedPrintable.decode(body.toString(),false); - else if ("base64".equals(encoding)) this.body = new String(Base64.decode(body.toString())); - else this.body = body.toString(); - this.last = last; - } - } - - public static class Headers extends org.ibex.js.JSReflection { - private Hashtable head = new Hashtable(); - public final String raw; - public String get(String s) { return (String)head.get(s.toLowerCase()); } - public static String uncomment(String val) { - boolean inquotes = false; - for(int i=0; i 126) - throw new MailException.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(); - - Enumeration e = head.keys(); - boolean mime = assumeMime | (get("mime-version") != null && get("mime-version").trim().equals("1.0")); - while(e.hasMoreElements()) { - String k = (String)e.nextElement(); - 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); + body.append("\r\n"); + //lines++; } } } + */ }