1 /**
2 *
3 * Copyright 2003-2004 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package javax.mail.internet;
19
20 import java.io.ByteArrayInputStream;
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.io.UnsupportedEncodingException;
26 import java.util.Enumeration;
27
28 import javax.activation.DataHandler;
29 import javax.mail.BodyPart;
30 import javax.mail.MessagingException;
31 import javax.mail.Multipart;
32 import javax.mail.Part;
33 import javax.mail.internet.HeaderTokenizer.Token;
34 import javax.swing.text.AbstractDocument.Content;
35
36 import org.apache.geronimo.mail.util.ASCIIUtil;
37 import org.apache.geronimo.mail.util.SessionUtil;
38
39 /**
40 * @version $Rev: 412720 $ $Date: 2006-06-08 03:52:48 -0700 (Thu, 08 Jun 2006) $
41 */
42 public class MimeBodyPart extends BodyPart implements MimePart {
43
44 private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
45 private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
46 private static final String MIME_SETDEFAULTTEXTCHARSET = "mail.mime.setdefaulttextcharset";
47 private static final String MIME_SETCONTENTTYPEFILENAME = "mail.mime.setcontenttypefilename";
48
49
50 /**
51 * The {@link DataHandler} for this Message's content.
52 */
53 protected DataHandler dh;
54 /**
55 * This message's content (unless sourced from a SharedInputStream).
56 */
57 protected byte content[];
58 /**
59 * If the data for this message was supplied by a {@link SharedInputStream}
60 * then this is another such stream representing the content of this message;
61 * if this field is non-null, then {@link #content} will be null.
62 */
63 protected InputStream contentStream;
64 /**
65 * This message's headers.
66 */
67 protected InternetHeaders headers;
68
69 public MimeBodyPart() {
70 headers = new InternetHeaders();
71 }
72
73 public MimeBodyPart(InputStream in) throws MessagingException {
74 headers = new InternetHeaders(in);
75 ByteArrayOutputStream baos = new ByteArrayOutputStream();
76 byte[] buffer = new byte[1024];
77 int count;
78 try {
79 while((count = in.read(buffer, 0, 1024)) != -1)
80 baos.write(buffer, 0, count);
81 } catch (IOException e) {
82 throw new MessagingException(e.toString(),e);
83 }
84 content = baos.toByteArray();
85 }
86
87 public MimeBodyPart(InternetHeaders headers, byte[] content) throws MessagingException {
88 this.headers = headers;
89 this.content = content;
90 }
91
92 /**
93 * Return the content size of this message. This is obtained
94 * either from the size of the content field (if available) or
95 * from the contentStream, IFF the contentStream returns a positive
96 * size. Returns -1 if the size is not available.
97 *
98 * @return Size of the content in bytes.
99 * @exception MessagingException
100 */
101 public int getSize() throws MessagingException {
102 if (content != null) {
103 return content.length;
104 }
105 if (contentStream != null) {
106 try {
107 int size = contentStream.available();
108 if (size > 0) {
109 return size;
110 }
111 } catch (IOException e) {
112 }
113 }
114 return -1;
115 }
116
117 public int getLineCount() throws MessagingException {
118 return -1;
119 }
120
121 public String getContentType() throws MessagingException {
122 String value = getSingleHeader("Content-Type");
123 if (value == null) {
124 value = "text/plain";
125 }
126 return value;
127 }
128
129 /**
130 * Tests to see if this message has a mime-type match with the
131 * given type name.
132 *
133 * @param type The tested type name.
134 *
135 * @return If this is a type match on the primary and secondare portion of the types.
136 * @exception MessagingException
137 */
138 public boolean isMimeType(String type) throws MessagingException {
139 return new ContentType(getContentType()).match(type);
140 }
141
142 /**
143 * Retrieve the message "Content-Disposition" header field.
144 * This value represents how the part should be represented to
145 * the user.
146 *
147 * @return The string value of the Content-Disposition field.
148 * @exception MessagingException
149 */
150 public String getDisposition() throws MessagingException {
151 String disp = getSingleHeader("Content-Disposition");
152 if (disp != null) {
153 return new ContentDisposition(disp).getDisposition();
154 }
155 return null;
156 }
157
158 /**
159 * Set a new dispostion value for the "Content-Disposition" field.
160 * If the new value is null, the header is removed.
161 *
162 * @param disposition
163 * The new disposition value.
164 *
165 * @exception MessagingException
166 */
167 public void setDisposition(String disposition) throws MessagingException {
168 if (disposition == null) {
169 removeHeader("Content-Disposition");
170 }
171 else {
172
173 String currentHeader = getSingleHeader("Content-Disposition");
174 if (currentHeader != null) {
175 ContentDisposition content = new ContentDisposition(currentHeader);
176 content.setDisposition(disposition);
177 setHeader("Content-Disposition", content.toString());
178 }
179 else {
180
181 setHeader("Content-Disposition", disposition);
182 }
183 }
184 }
185
186 /**
187 * Retrieves the current value of the "Content-Transfer-Encoding"
188 * header. Returns null if the header does not exist.
189 *
190 * @return The current header value or null.
191 * @exception MessagingException
192 */
193 public String getEncoding() throws MessagingException {
194
195 String encoding = getSingleHeader("Content-Transfer-Encoding");
196 if (encoding != null) {
197
198
199 HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
200
201 Token token = tokenizer.next();
202 while (token.getType() != Token.EOF) {
203
204 if (token.getType() == Token.ATOM) {
205 return token.getValue();
206 }
207 }
208
209
210 return encoding;
211 }
212
213 return null;
214 }
215
216
217 /**
218 * Retrieve the value of the "Content-ID" header. Returns null
219 * if the header does not exist.
220 *
221 * @return The current header value or null.
222 * @exception MessagingException
223 */
224 public String getContentID() throws MessagingException {
225 return getSingleHeader("Content-ID");
226 }
227
228 public void setContentID(String cid) throws MessagingException {
229 setOrRemoveHeader("Content-ID", cid);
230 }
231
232 public String getContentMD5() throws MessagingException {
233 return getSingleHeader("Content-MD5");
234 }
235
236 public void setContentMD5(String md5) throws MessagingException {
237 setHeader("Content-MD5", md5);
238 }
239
240 public String[] getContentLanguage() throws MessagingException {
241 return getHeader("Content-Language");
242 }
243
244 public void setContentLanguage(String[] languages) throws MessagingException {
245 if (languages == null) {
246 removeHeader("Content-Language");
247 } else if (languages.length == 1) {
248 setHeader("Content-Language", languages[0]);
249 } else {
250 StringBuffer buf = new StringBuffer(languages.length * 20);
251 buf.append(languages[0]);
252 for (int i = 1; i < languages.length; i++) {
253 buf.append(',').append(languages[i]);
254 }
255 setHeader("Content-Language", buf.toString());
256 }
257 }
258
259 public String getDescription() throws MessagingException {
260 String description = getSingleHeader("Content-Description");
261 if (description != null) {
262 try {
263
264 return MimeUtility.decodeText(ASCIIUtil.unfold(description));
265 } catch (UnsupportedEncodingException e) {
266
267 }
268 }
269
270 return description;
271 }
272
273 public void setDescription(String description) throws MessagingException {
274 setDescription(description, null);
275 }
276
277 public void setDescription(String description, String charset) throws MessagingException {
278 if (description == null) {
279 removeHeader("Content-Description");
280 }
281 else {
282 try {
283 setHeader("Content-Description", ASCIIUtil.fold(21, MimeUtility.encodeText(description, charset, null)));
284 } catch (UnsupportedEncodingException e) {
285 throw new MessagingException(e.getMessage(), e);
286 }
287 }
288 }
289
290 public String getFileName() throws MessagingException {
291
292 String disposition = getSingleHeader("Content-Disposition");
293 String filename = null;
294
295 if (disposition != null) {
296 filename = new ContentDisposition(disposition).getParameter("filename");
297 }
298
299
300
301 if (filename == null) {
302 String type = getSingleHeader("Content-Type");
303 if (type != null) {
304 try {
305 filename = new ContentType(type).getParameter("name");
306 } catch (ParseException e) {
307 }
308 }
309 }
310
311 if (filename != null && SessionUtil.getBooleanProperty(MIME_DECODEFILENAME, false)) {
312 try {
313 filename = MimeUtility.decodeText(filename);
314 } catch (UnsupportedEncodingException e) {
315 throw new MessagingException("Unable to decode filename", e);
316 }
317 }
318
319 return filename;
320 }
321
322
323 public void setFileName(String name) throws MessagingException {
324
325
326 if (name != null && SessionUtil.getBooleanProperty(MIME_ENCODEFILENAME, false)) {
327 try {
328 name = MimeUtility.encodeText(name);
329 } catch (UnsupportedEncodingException e) {
330 throw new MessagingException("Unable to encode filename", e);
331 }
332 }
333
334
335 String disposition = getDisposition();
336
337 if (disposition == null) {
338 disposition = Part.ATTACHMENT;
339 }
340
341
342 ContentDisposition contentDisposition = new ContentDisposition(disposition);
343 contentDisposition.setParameter("filename", name);
344
345
346 setDisposition(contentDisposition.toString());
347 setHeader("Content-Disposition", contentDisposition.toString());
348
349
350
351 if (SessionUtil.getBooleanProperty(MIME_SETCONTENTTYPEFILENAME, true)) {
352 ContentType type = new ContentType(getContentType());
353 type.setParameter("name", name);
354 setHeader("Content-Type", type.toString());
355 }
356 }
357
358 public InputStream getInputStream() throws MessagingException, IOException {
359 return getDataHandler().getInputStream();
360 }
361
362 protected InputStream getContentStream() throws MessagingException {
363 if (contentStream != null) {
364 return contentStream;
365 }
366
367 if (content != null) {
368 return new ByteArrayInputStream(content);
369 } else {
370 throw new MessagingException("No content");
371 }
372 }
373
374 public InputStream getRawInputStream() throws MessagingException {
375 return getContentStream();
376 }
377
378 public synchronized DataHandler getDataHandler() throws MessagingException {
379 if (dh == null) {
380 dh = new DataHandler(new MimePartDataSource(this));
381 }
382 return dh;
383 }
384
385 public Object getContent() throws MessagingException, IOException {
386 return getDataHandler().getContent();
387 }
388
389 public void setDataHandler(DataHandler handler) throws MessagingException {
390 dh = handler;
391
392
393
394 removeHeader("Content-Type");
395 removeHeader("Content-Transfer-Encoding");
396
397 }
398
399 public void setContent(Object content, String type) throws MessagingException {
400
401 if (content instanceof Multipart) {
402 setContent((Multipart)content);
403 }
404 else {
405 setDataHandler(new DataHandler(content, type));
406 }
407 }
408
409 public void setText(String text) throws MessagingException {
410 setText(text, null);
411 }
412
413 public void setText(String text, String charset) throws MessagingException {
414
415 if (charset == null) {
416
417 if (!ASCIIUtil.isAscii(text)) {
418 charset = MimeUtility.getDefaultMIMECharset();
419 }
420 else {
421 charset = "us-ascii";
422 }
423 }
424 setContent(text, "text/plain; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
425 }
426
427 public void setContent(Multipart part) throws MessagingException {
428 setDataHandler(new DataHandler(part, part.getContentType()));
429 part.setParent(this);
430 }
431
432 public void writeTo(OutputStream out) throws IOException, MessagingException {
433 headers.writeTo(out, null);
434
435 out.write('\r');
436 out.write('\n');
437
438 OutputStream encodingStream = MimeUtility.encode(out, getEncoding());
439 getDataHandler().writeTo(encodingStream);
440 encodingStream.flush();
441 }
442
443 public String[] getHeader(String name) throws MessagingException {
444 return headers.getHeader(name);
445 }
446
447 public String getHeader(String name, String delimiter) throws MessagingException {
448 return headers.getHeader(name, delimiter);
449 }
450
451 public void setHeader(String name, String value) throws MessagingException {
452 headers.setHeader(name, value);
453 }
454
455 /**
456 * Conditionally set or remove a named header. If the new value
457 * is null, the header is removed.
458 *
459 * @param name The header name.
460 * @param value The new header value. A null value causes the header to be
461 * removed.
462 *
463 * @exception MessagingException
464 */
465 private void setOrRemoveHeader(String name, String value) throws MessagingException {
466 if (value == null) {
467 headers.removeHeader(name);
468 }
469 else {
470 headers.setHeader(name, value);
471 }
472 }
473
474 public void addHeader(String name, String value) throws MessagingException {
475 headers.addHeader(name, value);
476 }
477
478 public void removeHeader(String name) throws MessagingException {
479 headers.removeHeader(name);
480 }
481
482 public Enumeration getAllHeaders() throws MessagingException {
483 return headers.getAllHeaders();
484 }
485
486 public Enumeration getMatchingHeaders(String[] name) throws MessagingException {
487 return headers.getMatchingHeaders(name);
488 }
489
490 public Enumeration getNonMatchingHeaders(String[] name) throws MessagingException {
491 return headers.getNonMatchingHeaders(name);
492 }
493
494 public void addHeaderLine(String line) throws MessagingException {
495 headers.addHeaderLine(line);
496 }
497
498 public Enumeration getAllHeaderLines() throws MessagingException {
499 return headers.getAllHeaderLines();
500 }
501
502 public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
503 return headers.getMatchingHeaderLines(names);
504 }
505
506 public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
507 return headers.getNonMatchingHeaderLines(names);
508 }
509
510 protected void updateHeaders() throws MessagingException {
511 DataHandler handler = getDataHandler();
512
513 try {
514
515 String type = dh.getContentType();
516
517 ContentType content = new ContentType(type);
518
519 if (content.match("multipart/*")) {
520
521 try {
522 MimeMultipart part = (MimeMultipart)handler.getContent();
523 part.updateHeaders();
524 } catch (ClassCastException e) {
525 throw new MessagingException("Message content is not MimeMultipart", e);
526 }
527 }
528 else if (!content.match("message/rfc822")) {
529
530
531 if (getSingleHeader("Content-Transfer-Encoding") == null) {
532 setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
533 }
534
535
536 if (getHeader("Content-Type") == null) {
537 if (SessionUtil.getBooleanProperty(MIME_SETDEFAULTTEXTCHARSET, true)) {
538
539 if (content.match("text/*")) {
540
541
542 if (content.getParameter("charset") == null) {
543
544 String encoding = getEncoding();
545
546
547 if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
548 content.setParameter("charset", "us-ascii");
549 }
550 else {
551
552 content.setParameter("charset", MimeUtility.getDefaultMIMECharset());
553 }
554 }
555 }
556 }
557 }
558 }
559
560
561 if (getSingleHeader("Content-Type") == null) {
562
563
564 String disp = getHeader("Content-Disposition", null);
565 if (disp != null) {
566
567 ContentDisposition disposition = new ContentDisposition(disp);
568
569 String filename = disposition.getParameter("filename");
570
571 if (filename != null) {
572 content.setParameter("name", filename);
573 }
574 }
575
576 setHeader("Content-Type", content.toString());
577 }
578
579 } catch (IOException e) {
580 throw new MessagingException("Error updating message headers", e);
581 }
582 }
583
584 private String getSingleHeader(String name) throws MessagingException {
585 String[] values = getHeader(name);
586 if (values == null || values.length == 0) {
587 return null;
588 } else {
589 return values[0];
590 }
591 }
592 }