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