001 /**************************************************************** 002 * Licensed to the Apache Software Foundation (ASF) under one * 003 * or more contributor license agreements. See the NOTICE file * 004 * distributed with this work for additional information * 005 * regarding copyright ownership. The ASF licenses this file * 006 * to you under the Apache License, Version 2.0 (the * 007 * "License"); you may not use this file except in compliance * 008 * with the License. You may obtain a copy of the License at * 009 * * 010 * http://www.apache.org/licenses/LICENSE-2.0 * 011 * * 012 * Unless required by applicable law or agreed to in writing, * 013 * software distributed under the License is distributed on an * 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * 015 * KIND, either express or implied. See the License for the * 016 * specific language governing permissions and limitations * 017 * under the License. * 018 ****************************************************************/ 019 020 package org.apache.james.mime4j.message; 021 022 import java.io.IOException; 023 import java.io.OutputStream; 024 025 import org.apache.james.mime4j.codec.CodecUtil; 026 import org.apache.james.mime4j.field.ContentTypeField; 027 import org.apache.james.mime4j.field.FieldName; 028 import org.apache.james.mime4j.parser.Field; 029 import org.apache.james.mime4j.util.ByteArrayBuffer; 030 import org.apache.james.mime4j.util.ByteSequence; 031 import org.apache.james.mime4j.util.ContentUtil; 032 import org.apache.james.mime4j.util.MimeUtil; 033 034 /** 035 * Writes a message (or a part of a message) to an output stream. 036 * <p> 037 * This class cannot be instantiated; instead the static instance 038 * {@link #DEFAULT} implements the default strategy for writing a message. 039 * <p> 040 * This class may be subclassed to implement custom strategies for writing 041 * messages. 042 */ 043 public class MessageWriter { 044 045 private static final byte[] CRLF = { '\r', '\n' }; 046 private static final byte[] DASHES = { '-', '-' }; 047 048 /** 049 * The default message writer. 050 */ 051 public static final MessageWriter DEFAULT = new MessageWriter(); 052 053 /** 054 * Protected constructor prevents direct instantiation. 055 */ 056 protected MessageWriter() { 057 } 058 059 /** 060 * Write the specified <code>Body</code> to the specified 061 * <code>OutputStream</code>. 062 * 063 * @param body 064 * the <code>Body</code> to write. 065 * @param out 066 * the OutputStream to write to. 067 * @throws IOException 068 * if an I/O error occurs. 069 */ 070 public void writeBody(Body body, OutputStream out) throws IOException { 071 if (body instanceof Message) { 072 writeEntity((Message) body, out); 073 } else if (body instanceof Multipart) { 074 writeMultipart((Multipart) body, out); 075 } else if (body instanceof SingleBody) { 076 ((SingleBody) body).writeTo(out); 077 } else 078 throw new IllegalArgumentException("Unsupported body class"); 079 } 080 081 /** 082 * Write the specified <code>Entity</code> to the specified 083 * <code>OutputStream</code>. 084 * 085 * @param entity 086 * the <code>Entity</code> to write. 087 * @param out 088 * the OutputStream to write to. 089 * @throws IOException 090 * if an I/O error occurs. 091 */ 092 public void writeEntity(Entity entity, OutputStream out) throws IOException { 093 final Header header = entity.getHeader(); 094 if (header == null) 095 throw new IllegalArgumentException("Missing header"); 096 097 writeHeader(header, out); 098 099 final Body body = entity.getBody(); 100 if (body == null) 101 throw new IllegalArgumentException("Missing body"); 102 103 boolean binaryBody = body instanceof BinaryBody; 104 OutputStream encOut = encodeStream(out, entity 105 .getContentTransferEncoding(), binaryBody); 106 107 writeBody(body, encOut); 108 109 // close if wrapped (base64 or quoted-printable) 110 if (encOut != out) 111 encOut.close(); 112 } 113 114 /** 115 * Write the specified <code>Multipart</code> to the specified 116 * <code>OutputStream</code>. 117 * 118 * @param multipart 119 * the <code>Multipart</code> to write. 120 * @param out 121 * the OutputStream to write to. 122 * @throws IOException 123 * if an I/O error occurs. 124 */ 125 public void writeMultipart(Multipart multipart, OutputStream out) 126 throws IOException { 127 ContentTypeField contentType = getContentType(multipart); 128 129 ByteSequence boundary = getBoundary(contentType); 130 131 writeBytes(multipart.getPreambleRaw(), out); 132 out.write(CRLF); 133 134 for (BodyPart bodyPart : multipart.getBodyParts()) { 135 out.write(DASHES); 136 writeBytes(boundary, out); 137 out.write(CRLF); 138 139 writeEntity(bodyPart, out); 140 out.write(CRLF); 141 } 142 143 out.write(DASHES); 144 writeBytes(boundary, out); 145 out.write(DASHES); 146 out.write(CRLF); 147 148 writeBytes(multipart.getEpilogueRaw(), out); 149 } 150 151 /** 152 * Write the specified <code>Header</code> to the specified 153 * <code>OutputStream</code>. 154 * 155 * @param header 156 * the <code>Header</code> to write. 157 * @param out 158 * the OutputStream to write to. 159 * @throws IOException 160 * if an I/O error occurs. 161 */ 162 public void writeHeader(Header header, OutputStream out) throws IOException { 163 for (Field field : header) { 164 writeBytes(field.getRaw(), out); 165 out.write(CRLF); 166 } 167 168 out.write(CRLF); 169 } 170 171 protected OutputStream encodeStream(OutputStream out, String encoding, 172 boolean binaryBody) throws IOException { 173 if (MimeUtil.isBase64Encoding(encoding)) { 174 return CodecUtil.wrapBase64(out); 175 } else if (MimeUtil.isQuotedPrintableEncoded(encoding)) { 176 return CodecUtil.wrapQuotedPrintable(out, binaryBody); 177 } else { 178 return out; 179 } 180 } 181 182 private ContentTypeField getContentType(Multipart multipart) { 183 Entity parent = multipart.getParent(); 184 if (parent == null) 185 throw new IllegalArgumentException( 186 "Missing parent entity in multipart"); 187 188 Header header = parent.getHeader(); 189 if (header == null) 190 throw new IllegalArgumentException( 191 "Missing header in parent entity"); 192 193 ContentTypeField contentType = (ContentTypeField) header 194 .getField(FieldName.CONTENT_TYPE); 195 if (contentType == null) 196 throw new IllegalArgumentException( 197 "Content-Type field not specified"); 198 199 return contentType; 200 } 201 202 private ByteSequence getBoundary(ContentTypeField contentType) { 203 String boundary = contentType.getBoundary(); 204 if (boundary == null) 205 throw new IllegalArgumentException( 206 "Multipart boundary not specified"); 207 208 return ContentUtil.encode(boundary); 209 } 210 211 private void writeBytes(ByteSequence byteSequence, OutputStream out) 212 throws IOException { 213 if (byteSequence instanceof ByteArrayBuffer) { 214 ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence; 215 out.write(bab.buffer(), 0, bab.length()); 216 } else { 217 out.write(byteSequence.toByteArray()); 218 } 219 } 220 221 }