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.dom.BinaryBody;
027 import org.apache.james.mime4j.dom.Body;
028 import org.apache.james.mime4j.dom.Entity;
029 import org.apache.james.mime4j.dom.Header;
030 import org.apache.james.mime4j.dom.Message;
031 import org.apache.james.mime4j.dom.MessageWriter;
032 import org.apache.james.mime4j.dom.Multipart;
033 import org.apache.james.mime4j.dom.SingleBody;
034 import org.apache.james.mime4j.dom.field.ContentTypeField;
035 import org.apache.james.mime4j.dom.field.FieldName;
036 import org.apache.james.mime4j.stream.Field;
037 import org.apache.james.mime4j.util.ByteArrayBuffer;
038 import org.apache.james.mime4j.util.ByteSequence;
039 import org.apache.james.mime4j.util.ContentUtil;
040 import org.apache.james.mime4j.util.MimeUtil;
041
042 /**
043 * Default implementation of {@link MessageWriter}.
044 */
045 public class DefaultMessageWriter implements MessageWriter {
046
047 private static final byte[] CRLF = { '\r', '\n' };
048 private static final byte[] DASHES = { '-', '-' };
049
050 /**
051 * Protected constructor prevents direct instantiation.
052 */
053 public DefaultMessageWriter() {
054 }
055
056 /**
057 * Write the specified <code>Body</code> to the specified
058 * <code>OutputStream</code>.
059 *
060 * @param body
061 * the <code>Body</code> to write.
062 * @param out
063 * the OutputStream to write to.
064 * @throws IOException
065 * if an I/O error occurs.
066 */
067 public void writeBody(Body body, OutputStream out) throws IOException {
068 if (body instanceof Message) {
069 writeEntity((Message) body, out);
070 } else if (body instanceof Multipart) {
071 writeMultipart((Multipart) body, out);
072 } else if (body instanceof SingleBody) {
073 ((SingleBody) body).writeTo(out);
074 } else
075 throw new IllegalArgumentException("Unsupported body class");
076 }
077
078 /**
079 * Write the specified <code>Entity</code> to the specified
080 * <code>OutputStream</code>.
081 *
082 * @param entity
083 * the <code>Entity</code> to write.
084 * @param out
085 * the OutputStream to write to.
086 * @throws IOException
087 * if an I/O error occurs.
088 */
089 public void writeEntity(Entity entity, OutputStream out) throws IOException {
090 final Header header = entity.getHeader();
091 if (header == null)
092 throw new IllegalArgumentException("Missing header");
093
094 writeHeader(header, out);
095
096 final Body body = entity.getBody();
097 if (body == null)
098 throw new IllegalArgumentException("Missing body");
099
100 boolean binaryBody = body instanceof BinaryBody;
101 OutputStream encOut = encodeStream(out, entity
102 .getContentTransferEncoding(), binaryBody);
103
104 writeBody(body, encOut);
105
106 // close if wrapped (base64 or quoted-printable)
107 if (encOut != out)
108 encOut.close();
109 }
110
111 /**
112 * Write the specified <code>Message</code> to the specified
113 * <code>OutputStream</code>.
114 *
115 * @param message
116 * the <code>Message</code> to write.
117 * @param out
118 * the OutputStream to write to.
119 * @throws IOException
120 * if an I/O error occurs.
121 */
122 public void writeMessage(Message message, OutputStream out) throws IOException {
123 writeEntity(message, out);
124 }
125
126 /**
127 * Write the specified <code>Multipart</code> to the specified
128 * <code>OutputStream</code>.
129 *
130 * @param multipart
131 * the <code>Multipart</code> to write.
132 * @param out
133 * the OutputStream to write to.
134 * @throws IOException
135 * if an I/O error occurs.
136 */
137 public void writeMultipart(Multipart multipart, OutputStream out)
138 throws IOException {
139 ContentTypeField contentType = getContentType(multipart);
140
141 ByteSequence boundary = getBoundary(contentType);
142
143 ByteSequence preamble;
144 ByteSequence epilogue;
145 if (multipart instanceof MultipartImpl) {
146 preamble = ((MultipartImpl) multipart).getPreambleRaw();
147 epilogue = ((MultipartImpl) multipart).getEpilogueRaw();
148 } else {
149 preamble = multipart.getPreamble() != null ? ContentUtil.encode(multipart.getPreamble()) : null;
150 epilogue = multipart.getEpilogue() != null ? ContentUtil.encode(multipart.getEpilogue()) : null;
151 }
152 if (preamble != null) {
153 writeBytes(preamble, out);
154 out.write(CRLF);
155 }
156
157 for (Entity bodyPart : multipart.getBodyParts()) {
158 out.write(DASHES);
159 writeBytes(boundary, out);
160 out.write(CRLF);
161
162 writeEntity(bodyPart, out);
163 out.write(CRLF);
164 }
165
166 out.write(DASHES);
167 writeBytes(boundary, out);
168 out.write(DASHES);
169 out.write(CRLF);
170 if (epilogue != null) {
171 writeBytes(epilogue, out);
172 }
173 }
174
175 /**
176 * Write the specified <code>Field</code> to the specified
177 * <code>OutputStream</code>.
178 *
179 * @param field
180 * the <code>Field</code> to write.
181 * @param out
182 * the OutputStream to write to.
183 * @throws IOException
184 * if an I/O error occurs.
185 */
186 public void writeField(Field field, OutputStream out) throws IOException {
187 ByteSequence raw = field.getRaw();
188 if (raw == null) {
189 StringBuilder buf = new StringBuilder();
190 buf.append(field.getName());
191 buf.append(": ");
192 String body = field.getBody();
193 if (body != null) {
194 buf.append(body);
195 }
196 raw = ContentUtil.encode(MimeUtil.fold(buf.toString(), 0));
197 }
198 writeBytes(raw, out);
199 out.write(CRLF);
200 }
201
202 /**
203 * Write the specified <code>Header</code> to the specified
204 * <code>OutputStream</code>.
205 *
206 * @param header
207 * the <code>Header</code> to write.
208 * @param out
209 * the OutputStream to write to.
210 * @throws IOException
211 * if an I/O error occurs.
212 */
213 public void writeHeader(Header header, OutputStream out) throws IOException {
214 for (Field field : header) {
215 writeField(field, out);
216 }
217
218 out.write(CRLF);
219 }
220
221 protected OutputStream encodeStream(OutputStream out, String encoding,
222 boolean binaryBody) throws IOException {
223 if (MimeUtil.isBase64Encoding(encoding)) {
224 return CodecUtil.wrapBase64(out);
225 } else if (MimeUtil.isQuotedPrintableEncoded(encoding)) {
226 return CodecUtil.wrapQuotedPrintable(out, binaryBody);
227 } else {
228 return out;
229 }
230 }
231
232 private ContentTypeField getContentType(Multipart multipart) {
233 Entity parent = multipart.getParent();
234 if (parent == null)
235 throw new IllegalArgumentException(
236 "Missing parent entity in multipart");
237
238 Header header = parent.getHeader();
239 if (header == null)
240 throw new IllegalArgumentException(
241 "Missing header in parent entity");
242
243 ContentTypeField contentType = (ContentTypeField) header
244 .getField(FieldName.CONTENT_TYPE);
245 if (contentType == null)
246 throw new IllegalArgumentException(
247 "Content-Type field not specified");
248
249 return contentType;
250 }
251
252 private ByteSequence getBoundary(ContentTypeField contentType) {
253 String boundary = contentType.getBoundary();
254 if (boundary == null)
255 throw new IllegalArgumentException(
256 "Multipart boundary not specified. Mime-Type: "+contentType.getMimeType()+", Raw: "+contentType.toString());
257
258 return ContentUtil.encode(boundary);
259 }
260
261 private void writeBytes(ByteSequence byteSequence, OutputStream out)
262 throws IOException {
263 if (byteSequence instanceof ByteArrayBuffer) {
264 ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence;
265 out.write(bab.buffer(), 0, bab.length());
266 } else {
267 out.write(byteSequence.toByteArray());
268 }
269 }
270
271 }