blob: 5c4c1ce5944123383451a6c05b7c5c91c912474c [file] [log] [blame]
/****************************************************************
* 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.james.mime4j.codec.Base64OutputStream;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.dom.BinaryBody;
import org.apache.james.mime4j.dom.Body;
import org.apache.james.mime4j.dom.Entity;
import org.apache.james.mime4j.dom.Header;
import org.apache.james.mime4j.dom.Message;
import org.apache.james.mime4j.dom.MessageWriter;
import org.apache.james.mime4j.dom.Multipart;
import org.apache.james.mime4j.dom.SingleBody;
import org.apache.james.mime4j.dom.field.ContentTypeField;
import org.apache.james.mime4j.dom.field.FieldName;
import org.apache.james.mime4j.stream.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;
/**
* Default implementation of {@link MessageWriter}.
*/
public class DefaultMessageWriter implements MessageWriter {
private static final byte[] CRLF = { '\r', '\n' };
private static final byte[] DASHES = { '-', '-' };
public static byte[] asBytes(Message message) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DefaultMessageWriter writer = new DefaultMessageWriter();
writer.writeMessage(message, buffer);
return buffer.toByteArray();
}
/**
* Protected constructor prevents direct instantiation.
*/
public DefaultMessageWriter() {
}
/**
* 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>Message</code> to the specified
* <code>OutputStream</code>.
*
* @param message
* the <code>Message</code> to write.
* @param out
* the OutputStream to write to.
* @throws IOException
* if an I/O error occurs.
*/
public void writeMessage(Message message, OutputStream out) throws IOException {
writeEntity(message, out);
}
/**
* 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);
ByteSequence preamble;
ByteSequence epilogue;
if (multipart instanceof MultipartImpl) {
preamble = ((MultipartImpl) multipart).getPreambleRaw();
epilogue = ((MultipartImpl) multipart).getEpilogueRaw();
} else {
preamble = multipart.getPreamble() != null ? ContentUtil.encode(multipart.getPreamble()) : null;
epilogue = multipart.getEpilogue() != null ? ContentUtil.encode(multipart.getEpilogue()) : null;
}
if (preamble != null) {
writeBytes(preamble, out);
out.write(CRLF);
}
for (Entity 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);
if (epilogue != null) {
writeBytes(epilogue, out);
}
}
/**
* Write the specified <code>Field</code> to the specified
* <code>OutputStream</code>.
*
* @param field
* the <code>Field</code> to write.
* @param out
* the OutputStream to write to.
* @throws IOException
* if an I/O error occurs.
*/
public void writeField(Field field, OutputStream out) throws IOException {
ByteSequence raw = field.getRaw();
if (raw == null) {
StringBuilder buf = new StringBuilder();
buf.append(field.getName());
buf.append(": ");
String body = field.getBody();
if (body != null) {
buf.append(body);
}
raw = ContentUtil.encode(MimeUtil.fold(buf.toString(), 0));
}
writeBytes(raw, out);
out.write(CRLF);
}
/**
* 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) {
writeField(field, out);
}
out.write(CRLF);
}
protected OutputStream encodeStream(OutputStream out, String encoding,
boolean binaryBody) throws IOException {
if (MimeUtil.isBase64Encoding(encoding)) {
return new Base64OutputStream(out);
} else if (MimeUtil.isQuotedPrintableEncoded(encoding)) {
return new QuotedPrintableOutputStream(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_LOWERCASE);
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. Mime-Type: "+contentType.getMimeType()+", Raw: "+contentType.toString());
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());
}
}
}