/**************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * * or more contributor license agreements. See the NOTICE file * * distributed with this work for additional information * * regarding copyright ownership. The ASF licenses this file * * to you under the Apache License, Version 2.0 (the * * "License"); you may not use this file except in compliance * * with the License. You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, * * software distributed under the License is distributed on an * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * * KIND, either express or implied. See the License for the * * specific language governing permissions and limitations * * under the License. * ****************************************************************/ package org.apache.james.mime4j.message; import java.io.IOException; import java.io.OutputStream; import org.apache.james.mime4j.codec.CodecUtil; import org.apache.james.mime4j.field.ContentTypeField; import org.apache.james.mime4j.field.FieldName; import org.apache.james.mime4j.parser.Field; import org.apache.james.mime4j.util.ByteArrayBuffer; import org.apache.james.mime4j.util.ByteSequence; import org.apache.james.mime4j.util.ContentUtil; import org.apache.james.mime4j.util.MimeUtil; /** * Writes a message (or a part of a message) to an output stream. * <p> * This class cannot be instantiated; instead the static instance * {@link #DEFAULT} implements the default strategy for writing a message. * <p> * This class may be subclassed to implement custom strategies for writing * messages. */ public class MessageWriter { private static final byte[] CRLF = { '\r', '\n' }; private static final byte[] DASHES = { '-', '-' }; /** * The default message writer. */ public static final MessageWriter DEFAULT = new MessageWriter(); /** * Protected constructor prevents direct instantiation. */ protected MessageWriter() { } /** * Write the specified <code>Body</code> to the specified * <code>OutputStream</code>. * * @param body * the <code>Body</code> to write. * @param out * the OutputStream to write to. * @throws IOException * if an I/O error occurs. */ public void writeBody(Body body, OutputStream out) throws IOException { if (body instanceof Message) { writeEntity((Message) body, out); } else if (body instanceof Multipart) { writeMultipart((Multipart) body, out); } else if (body instanceof SingleBody) { ((SingleBody) body).writeTo(out); } else throw new IllegalArgumentException("Unsupported body class"); } /** * Write the specified <code>Entity</code> to the specified * <code>OutputStream</code>. * * @param entity * the <code>Entity</code> to write. * @param out * the OutputStream to write to. * @throws IOException * if an I/O error occurs. */ public void writeEntity(Entity entity, OutputStream out) throws IOException { final Header header = entity.getHeader(); if (header == null) throw new IllegalArgumentException("Missing header"); writeHeader(header, out); final Body body = entity.getBody(); if (body == null) throw new IllegalArgumentException("Missing body"); boolean binaryBody = body instanceof BinaryBody; OutputStream encOut = encodeStream(out, entity .getContentTransferEncoding(), binaryBody); writeBody(body, encOut); // close if wrapped (base64 or quoted-printable) if (encOut != out) encOut.close(); } /** * Write the specified <code>Multipart</code> to the specified * <code>OutputStream</code>. * * @param multipart * the <code>Multipart</code> to write. * @param out * the OutputStream to write to. * @throws IOException * if an I/O error occurs. */ public void writeMultipart(Multipart multipart, OutputStream out) throws IOException { ContentTypeField contentType = getContentType(multipart); ByteSequence boundary = getBoundary(contentType); writeBytes(multipart.getPreambleRaw(), out); out.write(CRLF); for (BodyPart bodyPart : multipart.getBodyParts()) { out.write(DASHES); writeBytes(boundary, out); out.write(CRLF); writeEntity(bodyPart, out); out.write(CRLF); } out.write(DASHES); writeBytes(boundary, out); out.write(DASHES); out.write(CRLF); writeBytes(multipart.getEpilogueRaw(), out); } /** * Write the specified <code>Header</code> to the specified * <code>OutputStream</code>. * * @param header * the <code>Header</code> to write. * @param out * the OutputStream to write to. * @throws IOException * if an I/O error occurs. */ public void writeHeader(Header header, OutputStream out) throws IOException { for (Field field : header) { writeBytes(field.getRaw(), out); out.write(CRLF); } out.write(CRLF); } protected OutputStream encodeStream(OutputStream out, String encoding, boolean binaryBody) throws IOException { if (MimeUtil.isBase64Encoding(encoding)) { return CodecUtil.wrapBase64(out); } else if (MimeUtil.isQuotedPrintableEncoded(encoding)) { return CodecUtil.wrapQuotedPrintable(out, binaryBody); } else { return out; } } private ContentTypeField getContentType(Multipart multipart) { Entity parent = multipart.getParent(); if (parent == null) throw new IllegalArgumentException( "Missing parent entity in multipart"); Header header = parent.getHeader(); if (header == null) throw new IllegalArgumentException( "Missing header in parent entity"); ContentTypeField contentType = (ContentTypeField) header .getField(FieldName.CONTENT_TYPE); if (contentType == null) throw new IllegalArgumentException( "Content-Type field not specified"); return contentType; } private ByteSequence getBoundary(ContentTypeField contentType) { String boundary = contentType.getBoundary(); if (boundary == null) throw new IllegalArgumentException( "Multipart boundary not specified"); return ContentUtil.encode(boundary); } private void writeBytes(ByteSequence byteSequence, OutputStream out) throws IOException { if (byteSequence instanceof ByteArrayBuffer) { ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence; out.write(bab.buffer(), 0, bab.length()); } else { out.write(byteSequence.toByteArray()); } } }