001    /**
002     *
003     * Copyright 2003-2006 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package javax.mail.internet;
019    
020    import java.io.BufferedInputStream;
021    import java.io.ByteArrayInputStream;
022    import java.io.ByteArrayOutputStream;
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.ObjectStreamException;
026    import java.io.OutputStream;
027    import java.io.UnsupportedEncodingException;
028    import java.util.ArrayList;
029    import java.util.Arrays;
030    import java.util.Date;
031    import java.util.Enumeration;
032    import java.util.HashMap;
033    import java.util.List;
034    import java.util.Map;
035    
036    import javax.activation.DataHandler;
037    import javax.mail.Address;
038    import javax.mail.Flags;
039    import javax.mail.Folder;
040    import javax.mail.Message;
041    import javax.mail.MessagingException;
042    import javax.mail.Multipart;
043    import javax.mail.Part;
044    import javax.mail.Session;
045    import javax.mail.internet.HeaderTokenizer.Token;
046    
047    import org.apache.geronimo.mail.util.ASCIIUtil;
048    import org.apache.geronimo.mail.util.SessionUtil;
049    
050    /**
051     * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
052     */
053    public class MimeMessage extends Message implements MimePart {
054            private static final String MIME_ADDRESS_STRICT = "mail.mime.address.strict";
055            private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
056            private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
057    
058            private static final String MAIL_ALTERNATES = "mail.alternates";
059            private static final String MAIL_REPLYALLCC = "mail.replyallcc";
060    
061        // static used to ensure message ID uniqueness
062        private static int messageID = 0;
063    
064    
065        /**
066         * Extends {@link javax.mail.Message.RecipientType} to support addition recipient types.
067         */
068        public static class RecipientType extends Message.RecipientType {
069            /**
070             * Recipient type for Usenet news.
071             */
072            public static final RecipientType NEWSGROUPS = new RecipientType("Newsgroups");
073    
074            protected RecipientType(String type) {
075                super(type);
076            }
077    
078            /**
079             * Ensure the singleton is returned.
080             *
081             * @return resolved object
082             */
083            protected Object readResolve() throws ObjectStreamException {
084                if (this.type.equals("Newsgroups")) {
085                    return NEWSGROUPS;
086                } else {
087                    return super.readResolve();
088                }
089            }
090        }
091    
092        /**
093         * The {@link DataHandler} for this Message's content.
094         */
095        protected DataHandler dh;
096        /**
097         * This message's content (unless sourced from a SharedInputStream).
098         */
099        protected byte[] content;
100        /**
101         * If the data for this message was supplied by a {@link SharedInputStream}
102         * then this is another such stream representing the content of this message;
103         * if this field is non-null, then {@link #content} will be null.
104         */
105        protected InputStream contentStream;
106        /**
107         * This message's headers.
108         */
109        protected InternetHeaders headers;
110        /**
111         * This message's flags.
112         */
113        protected Flags flags;
114        /**
115         * Flag indicating that the message has been modified; set to true when
116         * an empty message is created or when {@link #saveChanges()} is called.
117         */
118        protected boolean modified;
119        /**
120         * Flag indicating that the message has been saved.
121         */
122        protected boolean saved;
123    
124        private final MailDateFormat dateFormat = new MailDateFormat();
125    
126        /**
127         * Create a new MimeMessage.
128         * An empty message is created, with empty {@link #headers} and empty {@link #flags}.
129         * The {@link #modified} flag is set.
130         *
131         * @param session the session for this message
132         */
133        public MimeMessage(Session session) {
134            super(session);
135            headers = new InternetHeaders();
136            flags = new Flags();
137            // empty messages are modified, because the content is not there, and require saving before use.
138            modified = true;
139            saved = false;
140        }
141    
142        /**
143         * Create a MimeMessage by reading an parsing the data from the supplied stream.
144         *
145         * @param session the session for this message
146         * @param in      the stream to load from
147         * @throws MessagingException if there is a problem reading or parsing the stream
148         */
149        public MimeMessage(Session session, InputStream in) throws MessagingException {
150            this(session);
151            parse(in);
152            // this message is complete, so marked as unmodified.
153            modified = false;
154            // and no saving required
155            saved = true;
156        }
157    
158        /**
159         * Copy a MimeMessage.
160         *
161         * @param message the message to copy
162         * @throws MessagingException is there was a problem copying the message
163         */
164        public MimeMessage(MimeMessage message) throws MessagingException {
165            super(message.session);
166            // this is somewhat difficult to do.  There's a lot of data in both the superclass and this
167            // class that needs to undergo a "deep cloning" operation.  These operations don't really exist
168            // on the objects in question, so the only solution I can come up with is to serialize the
169            // message data of the source object using the write() method, then reparse the data in this
170            // object.  I've not found a lot of uses for this particular constructor, so perhaps that's not
171            // really all that bad of a solution.
172    
173            // serialized this out to an in-memory stream.
174            ByteArrayOutputStream copy = new ByteArrayOutputStream();
175    
176            try {
177                // write this out the stream.
178                message.writeTo(copy);
179                copy.close();
180                // I think this ends up creating a new array for the data, but I'm not aware of any more
181                // efficient options.
182                ByteArrayInputStream inData = new ByteArrayInputStream(copy.toByteArray());
183                // now reparse this message into this object.
184                inData.close();
185                parse (inData);
186                // writing out the source data requires saving it, so we should consider this one saved also.
187                saved = true;
188                // this message is complete, so marked as unmodified.
189                modified = false;
190            } catch (IOException e) {
191                // I'm not sure ByteArrayInput/OutputStream actually throws IOExceptions or not, but the method
192                // signatures declare it, so we need to deal with it.  Turning it into a messaging exception
193                // should fit the bill.
194                throw new MessagingException("Error copying MimeMessage data", e);
195            }
196        }
197    
198        /**
199         * Create an new MimeMessage in the supplied {@link Folder} and message number.
200         *
201         * @param folder the Folder that contains the new message
202         * @param number the message number of the new message
203         */
204        protected MimeMessage(Folder folder, int number) {
205            super(folder, number);
206            headers = new InternetHeaders();
207            flags = new Flags();
208            // saving primarly involves updates to the message header.  Since we're taking the header info
209            // from a message store in this context, we mark the message as saved.
210            saved = true;
211            // we've not filled in the content yet, so this needs to be marked as modified
212            modified = true;
213        }
214    
215        /**
216         * Create a MimeMessage by reading an parsing the data from the supplied stream.
217         *
218         * @param folder the folder for this message
219         * @param in     the stream to load from
220         * @param number the message number of the new message
221         * @throws MessagingException if there is a problem reading or parsing the stream
222         */
223        protected MimeMessage(Folder folder, InputStream in, int number) throws MessagingException {
224            this(folder, number);
225            parse(in);
226            // this message is complete, so marked as unmodified.
227            modified = false;
228            // and no saving required
229            saved = true;
230        }
231    
232    
233        /**
234         * Create a MimeMessage with the supplied headers and content.
235         *
236         * @param folder  the folder for this message
237         * @param headers the headers for the new message
238         * @param content the content of the new message
239         * @param number  the message number of the new message
240         * @throws MessagingException if there is a problem reading or parsing the stream
241         */
242        protected MimeMessage(Folder folder, InternetHeaders headers, byte[] content, int number) throws MessagingException {
243            this(folder, number);
244            this.headers = headers;
245            this.content = content;
246            // this message is complete, so marked as unmodified.
247            modified = false;
248        }
249    
250        /**
251         * Parse the supplied stream and initialize {@link #headers} and {@link #content} appropriately.
252         *
253         * @param in the stream to read
254         * @throws MessagingException if there was a problem parsing the stream
255         */
256        protected void parse(InputStream in) throws MessagingException {
257            in = new BufferedInputStream(in);
258            // create the headers first from the stream
259            headers = new InternetHeaders(in);
260    
261            // now we need to get the rest of the content as a byte array...this means reading from the current
262            // position in the stream until the end and writing it to an accumulator ByteArrayOutputStream.
263            ByteArrayOutputStream baos = new ByteArrayOutputStream();
264            try {
265                byte buffer[] = new byte[1024];
266                int count;
267                while ((count = in.read(buffer, 0, 1024)) != -1) {
268                    baos.write(buffer, 0, count);
269                }
270            } catch (Exception e) {
271                throw new MessagingException(e.toString(), e);
272            }
273            // and finally extract the content as a byte array.
274            content = baos.toByteArray();
275        }
276    
277        /**
278         * Get the message "From" addresses.  This looks first at the
279         * "From" headers, and no "From" header is found, the "Sender"
280         * header is checked.  Returns null if not found.
281         *
282         * @return An array of addresses identifying the message from target.  Returns
283         *         null if this is not resolveable from the headers.
284         * @exception MessagingException
285         */
286        public Address[] getFrom() throws MessagingException {
287            // strict addressing controls this.
288            boolean strict = isStrictAddressing();
289            Address[] result = getHeaderAsInternetAddresses("From", strict);
290            if (result == null) {
291                result = getHeaderAsInternetAddresses("Sender", strict);
292            }
293            return result;
294        }
295    
296        /**
297         * Set the current message "From" recipient.  This replaces any
298         * existing "From" header.  If the address is null, the header is
299         * removed.
300         *
301         * @param address The new "From" target.
302         *
303         * @exception MessagingException
304         */
305        public void setFrom(Address address) throws MessagingException {
306            setHeader("From", address);
307        }
308    
309        /**
310         * Set the "From" header using the value returned by {@link InternetAddress#getLocalAddress(javax.mail.Session)}.
311         *
312         * @throws MessagingException if there was a problem setting the header
313         */
314        public void setFrom() throws MessagingException {
315            InternetAddress address = InternetAddress.getLocalAddress(session);
316            // no local address resolvable?  This is an error.
317            if (address == null) {
318                throw new MessagingException("No local address defined");
319            }
320            setFrom(address);
321        }
322    
323        /**
324         * Add a set of addresses to the existing From header.
325         *
326         * @param addresses The list to add.
327         *
328         * @exception MessagingException
329         */
330        public void addFrom(Address[] addresses) throws MessagingException {
331            addHeader("From", addresses);
332        }
333    
334        /**
335         * Return the "Sender" header as an address.
336         *
337         * @return the "Sender" header as an address, or null if not present
338         * @throws MessagingException if there was a problem parsing the header
339         */
340        public Address getSender() throws MessagingException {
341            Address[] addrs = getHeaderAsInternetAddresses("Sender", isStrictAddressing());
342            return addrs != null && addrs.length > 0 ? addrs[0] : null;
343        }
344    
345        /**
346         * Set the "Sender" header.  If the address is null, this
347         * will remove the current sender header.
348         *
349         * @param address the new Sender address
350         *
351         * @throws MessagingException
352         *                if there was a problem setting the header
353         */
354        public void setSender(Address address) throws MessagingException {
355            setHeader("Sender", address);
356        }
357    
358        /**
359         * Gets the recipients by type.  Returns null if there are no
360         * headers of the specified type.  Acceptable RecipientTypes are:
361         *
362         *   javax.mail.Message.RecipientType.TO
363         *   javax.mail.Message.RecipientType.CC
364         *   javax.mail.Message.RecipientType.BCC
365         *   javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
366         *
367         * @param type   The message RecipientType identifier.
368         *
369         * @return The array of addresses for the specified recipient types.
370         * @exception MessagingException
371         */
372        public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
373            // is this a NEWSGROUP request?  We need to handle this as a special case here, because
374            // this needs to return NewsAddress instances instead of InternetAddress items.
375            if (type == RecipientType.NEWSGROUPS) {
376                return getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
377            }
378            // the other types are all internet addresses.
379            return getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
380        }
381    
382        /**
383         * Retrieve all of the recipients defined for this message.  This
384         * returns a merged array of all possible message recipients
385         * extracted from the headers.  The relevant header types are:
386         *
387         *
388         *    javax.mail.Message.RecipientType.TO
389         *    javax.mail.Message.RecipientType.CC
390         *    javax.mail.Message.RecipientType.BCC
391         *    javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
392         *
393         * @return An array of all target message recipients.
394         * @exception MessagingException
395         */
396        public Address[] getAllRecipients() throws MessagingException {
397            List recipients = new ArrayList();
398            addRecipientsToList(recipients, RecipientType.TO);
399            addRecipientsToList(recipients, RecipientType.CC);
400            addRecipientsToList(recipients, RecipientType.BCC);
401            addRecipientsToList(recipients, RecipientType.NEWSGROUPS);
402    
403            // this is supposed to return null if nothing is there.
404            if (recipients.isEmpty()) {
405                return null;
406            }
407            return (Address[]) recipients.toArray(new Address[recipients.size()]);
408        }
409    
410        /**
411         * Utility routine to merge different recipient types into a
412         * single list.
413         *
414         * @param list   The accumulator list.
415         * @param type   The recipient type to extract.
416         *
417         * @exception MessagingException
418         */
419        private void addRecipientsToList(List list, Message.RecipientType type) throws MessagingException {
420    
421            Address[] recipients;
422            if (type == RecipientType.NEWSGROUPS) {
423                recipients = getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
424            }
425            else {
426                recipients = getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
427            }
428            if (recipients != null) {
429                list.addAll(Arrays.asList(recipients));
430            }
431        }
432    
433        /**
434         * Set a recipients list for a particular recipient type.  If the
435         * list is null, the corresponding header is removed.
436         *
437         * @param type      The type of recipient to set.
438         * @param addresses The list of addresses.
439         *
440         * @exception MessagingException
441         */
442        public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
443            setHeader(getHeaderForRecipientType(type), addresses);
444        }
445    
446        /**
447         * Set a recipient field to a string address (which may be a
448         * list or group type).
449         *
450         * If the address is null, the field is removed.
451         *
452         * @param type    The type of recipient to set.
453         * @param address The address string.
454         *
455         * @exception MessagingException
456         */
457        public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
458            setOrRemoveHeader(getHeaderForRecipientType(type), address);
459        }
460    
461    
462        /**
463         * Add a list of addresses to a target recipient list.
464         *
465         * @param type    The target recipient type.
466         * @param address An array of addresses to add.
467         *
468         * @exception MessagingException
469         */
470        public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
471            addHeader(getHeaderForRecipientType(type), address);
472        }
473    
474        /**
475         * Add an address to a target recipient list by string name.
476         *
477         * @param type    The target header type.
478         * @param address The address to add.
479         *
480         * @exception MessagingException
481         */
482        public void addRecipients(Message.RecipientType type, String address) throws MessagingException {
483            addHeader(getHeaderForRecipientType(type), address);
484        }
485    
486        /**
487         * Get the ReplyTo address information.  The headers are parsed
488         * using the "mail.mime.address.strict" setting.  If the "Reply-To" header does
489         * not have any addresses, then the value of the "From" field is used.
490         *
491         * @return An array of addresses obtained from parsing the header.
492         * @exception MessagingException
493         */
494        public Address[] getReplyTo() throws MessagingException {
495             Address[] addresses = getHeaderAsInternetAddresses("Reply-To", isStrictAddressing());
496             if (addresses == null) {
497                 addresses = getFrom();
498             }
499             return addresses;
500        }
501    
502        /**
503         * Set the Reply-To field to the provided list of addresses.  If
504         * the address list is null, the header is removed.
505         *
506         * @param address The new field value.
507         *
508         * @exception MessagingException
509         */
510        public void setReplyTo(Address[] address) throws MessagingException {
511            setHeader("Reply-To", address);
512        }
513    
514        /**
515         * Returns the value of the "Subject" header.  If the subject
516         * is encoded as an RFC 2047 value, the value is decoded before
517         * return.  If decoding fails, the raw string value is
518         * returned.
519         *
520         * @return The String value of the subject field.
521         * @exception MessagingException
522         */
523        public String getSubject() throws MessagingException {
524            String subject = getSingleHeader("Subject");
525            if (subject == null) {
526                return null;
527            } else {
528                try {
529                    // this needs to be unfolded before decodeing.
530                    return MimeUtility.decodeText(MimeUtility.unfold(subject));
531                } catch (UnsupportedEncodingException e) {
532                    // ignored.
533                }
534            }
535    
536            return subject;
537        }
538    
539        /**
540         * Set the value for the "Subject" header.  If the subject
541         * contains non US-ASCII characters, it is encoded in RFC 2047
542         * fashion.
543         *
544         * If the subject value is null, the Subject field is removed.
545         *
546         * @param subject The new subject value.
547         *
548         * @exception MessagingException
549         */
550        public void setSubject(String subject) throws MessagingException {
551            // just set this using the default character set.
552            setSubject(subject, null);
553        }
554    
555        public void setSubject(String subject, String charset) throws MessagingException {
556            // standard null removal (yada, yada, yada....)
557            if (subject == null) {
558                removeHeader("Subject");
559            }
560            else {
561                try {
562                    String s = MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null));
563                    // encode this, and then fold to fit the line lengths.
564                    setHeader("Subject", MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null)));
565                } catch (UnsupportedEncodingException e) {
566                    throw new MessagingException("Encoding error", e);
567                }
568            }
569        }
570    
571        /**
572         * Get the value of the "Date" header field.  Returns null if
573         * if the field is absent or the date is not in a parseable format.
574         *
575         * @return A Date object parsed according to RFC 822.
576         * @exception MessagingException
577         */
578        public Date getSentDate() throws MessagingException {
579            String value = getSingleHeader("Date");
580            if (value == null) {
581                return null;
582            }
583            try {
584                return dateFormat.parse(value);
585            } catch (java.text.ParseException e) {
586                return null;
587            }
588        }
589    
590        /**
591         * Set the message sent date.  This updates the "Date" header.
592         * If the provided date is null, the header is removed.
593         *
594         * @param sent   The new sent date value.
595         *
596         * @exception MessagingException
597         */
598        public void setSentDate(Date sent) throws MessagingException {
599            setOrRemoveHeader("Date", dateFormat.format(sent));
600        }
601    
602        /**
603         * Get the message received date.  The Sun implementation is
604         * documented as always returning null, so this one does too.
605         *
606         * @return Always returns null.
607         * @exception MessagingException
608         */
609        public Date getReceivedDate() throws MessagingException {
610            return null;
611        }
612    
613        /**
614         * Return the content size of this message.  This is obtained
615         * either from the size of the content field (if available) or
616         * from the contentStream, IFF the contentStream returns a positive
617         * size.  Returns -1 if the size is not available.
618         *
619         * @return Size of the content in bytes.
620         * @exception MessagingException
621         */
622        public int getSize() throws MessagingException {
623            if (content != null) {
624                return content.length;
625            }
626            if (contentStream != null) {
627                try {
628                    int size = contentStream.available();
629                    if (size > 0) {
630                        return size;
631                    }
632                } catch (IOException e) {
633                    // ignore
634                }
635            }
636            return -1;
637        }
638    
639        /**
640         * Retrieve the line count for the current message.  Returns
641         * -1 if the count cannot be determined.
642         *
643         * The Sun implementation always returns -1, so this version
644         * does too.
645         *
646         * @return The content line count (always -1 in this implementation).
647         * @exception MessagingException
648         */
649        public int getLineCount() throws MessagingException {
650            return -1;
651        }
652    
653        /**
654         * Returns the current content type (defined in the "Content-Type"
655         * header.  If not available, "text/plain" is the default.
656         *
657         * @return The String name of the message content type.
658         * @exception MessagingException
659         */
660        public String getContentType() throws MessagingException {
661            String value = getSingleHeader("Content-Type");
662            if (value == null) {
663                value = "text/plain";
664            }
665            return value;
666        }
667    
668    
669        /**
670         * Tests to see if this message has a mime-type match with the
671         * given type name.
672         *
673         * @param type   The tested type name.
674         *
675         * @return If this is a type match on the primary and secondare portion of the types.
676         * @exception MessagingException
677         */
678        public boolean isMimeType(String type) throws MessagingException {
679            return new ContentType(getContentType()).match(type);
680        }
681    
682        /**
683         * Retrieve the message "Content-Disposition" header field.
684         * This value represents how the part should be represented to
685         * the user.
686         *
687         * @return The string value of the Content-Disposition field.
688         * @exception MessagingException
689         */
690        public String getDisposition() throws MessagingException {
691            String disp = getSingleHeader("Content-Disposition");
692            if (disp != null) {
693                return new ContentDisposition(disp).getDisposition();
694            }
695            return null;
696        }
697    
698    
699        /**
700         * Set a new dispostion value for the "Content-Disposition" field.
701         * If the new value is null, the header is removed.
702         *
703         * @param disposition
704         *               The new disposition value.
705         *
706         * @exception MessagingException
707         */
708        public void setDisposition(String disposition) throws MessagingException {
709            if (disposition == null) {
710                removeHeader("Content-Disposition");
711            }
712            else {
713                // the disposition has parameters, which we'll attempt to preserve in any existing header.
714                String currentHeader = getSingleHeader("Content-Disposition");
715                if (currentHeader != null) {
716                    ContentDisposition content = new ContentDisposition(currentHeader);
717                    content.setDisposition(disposition);
718                    setHeader("Content-Disposition", content.toString());
719                }
720                else {
721                    // set using the raw string.
722                    setHeader("Content-Disposition", disposition);
723                }
724            }
725        }
726    
727        /**
728         * Decode the Content-Transfer-Encoding header to determine
729         * the transfer encoding type.
730         *
731         * @return The string name of the required encoding.
732         * @exception MessagingException
733         */
734        public String getEncoding() throws MessagingException {
735            // this might require some parsing to sort out.
736            String encoding = getSingleHeader("Content-Transfer-Encoding");
737            if (encoding != null) {
738                // we need to parse this into ATOMs and other constituent parts.  We want the first
739                // ATOM token on the string.
740                HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
741    
742                Token token = tokenizer.next();
743                while (token.getType() != Token.EOF) {
744                    // if this is an ATOM type, return it.
745                    if (token.getType() == Token.ATOM) {
746                        return token.getValue();
747                    }
748                }
749                // not ATOMs found, just return the entire header value....somebody might be able to make sense of
750                // this.
751                return encoding;
752            }
753            // no header, nothing to return.
754            return null;
755        }
756    
757        /**
758         * Retrieve the value of the "Content-ID" header.  Returns null
759         * if the header does not exist.
760         *
761         * @return The current header value or null.
762         * @exception MessagingException
763         */
764        public String getContentID() throws MessagingException {
765            return getSingleHeader("Content-ID");
766        }
767    
768        public void setContentID(String cid) throws MessagingException {
769            setOrRemoveHeader("Content-ID", cid);
770        }
771    
772        public String getContentMD5() throws MessagingException {
773            return getSingleHeader("Content-MD5");
774        }
775    
776        public void setContentMD5(String md5) throws MessagingException {
777            setOrRemoveHeader("Content-MD5", md5);
778        }
779    
780        public String getDescription() throws MessagingException {
781            String description = getSingleHeader("Content-Description");
782            if (description != null) {
783                try {
784                    // this could be both folded and encoded.  Return this to usable form.
785                    return MimeUtility.decodeText(MimeUtility.unfold(description));
786                } catch (UnsupportedEncodingException e) {
787                    // ignore
788                }
789            }
790            // return the raw version for any errors.
791            return description;
792        }
793    
794        public void setDescription(String description) throws MessagingException {
795            setDescription(description, null);
796        }
797    
798        public void setDescription(String description, String charset) throws MessagingException {
799            if (description == null) {
800                removeHeader("Content-Description");
801            }
802            else {
803                try {
804                    setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null)));
805                } catch (UnsupportedEncodingException e) {
806                    throw new MessagingException(e.getMessage(), e);
807                }
808            }
809    
810        }
811    
812        public String[] getContentLanguage() throws MessagingException {
813            return getHeader("Content-Language");
814        }
815    
816        public void setContentLanguage(String[] languages) throws MessagingException {
817            if (languages == null) {
818                removeHeader("Content-Language");
819            } else if (languages.length == 1) {
820                setHeader("Content-Language", languages[0]);
821            } else {
822                StringBuffer buf = new StringBuffer(languages.length * 20);
823                buf.append(languages[0]);
824                for (int i = 1; i < languages.length; i++) {
825                    buf.append(',').append(languages[i]);
826                }
827                setHeader("Content-Language", buf.toString());
828            }
829        }
830    
831        public String getMessageID() throws MessagingException {
832            return getSingleHeader("Message-ID");
833        }
834    
835        public String getFileName() throws MessagingException {
836            // see if there is a disposition.  If there is, parse off the filename parameter.
837            String disposition = getDisposition();
838            String filename = null;
839    
840            if (disposition != null) {
841                filename = new ContentDisposition(disposition).getParameter("filename");
842            }
843    
844            // if there's no filename on the disposition, there might be a name parameter on a
845            // Content-Type header.
846            if (filename == null) {
847                String type = getContentType();
848                if (type != null) {
849                    try {
850                        filename = new ContentType(type).getParameter("name");
851                    } catch (ParseException e) {
852                    }
853                }
854            }
855            // if we have a name, we might need to decode this if an additional property is set.
856            if (filename != null && SessionUtil.getBooleanProperty(session, MIME_DECODEFILENAME, false)) {
857                try {
858                    filename = MimeUtility.decodeText(filename);
859                } catch (UnsupportedEncodingException e) {
860                    throw new MessagingException("Unable to decode filename", e);
861                }
862            }
863    
864            return filename;
865        }
866    
867    
868        public void setFileName(String name) throws MessagingException {
869            // there's an optional session property that requests file name encoding...we need to process this before
870            // setting the value.
871            if (name != null && SessionUtil.getBooleanProperty(session, MIME_ENCODEFILENAME, false)) {
872                try {
873                    name = MimeUtility.encodeText(name);
874                } catch (UnsupportedEncodingException e) {
875                    throw new MessagingException("Unable to encode filename", e);
876                }
877            }
878    
879            // get the disposition string.
880            String disposition = getDisposition();
881            // if not there, then this is an attachment.
882            if (disposition == null) {
883                disposition = Part.ATTACHMENT;
884            }
885            // now create a disposition object and set the parameter.
886            ContentDisposition contentDisposition = new ContentDisposition(disposition);
887            contentDisposition.setParameter("filename", name);
888    
889            // serialize this back out and reset.
890            setDisposition(contentDisposition.toString());
891        }
892    
893        public InputStream getInputStream() throws MessagingException, IOException {
894            return getDataHandler().getInputStream();
895        }
896    
897        protected InputStream getContentStream() throws MessagingException {
898            if (contentStream != null) {
899                return contentStream;
900            }
901    
902            if (content != null) {
903                return new ByteArrayInputStream(content);
904            } else {
905                throw new MessagingException("No content");
906            }
907        }
908    
909        public InputStream getRawInputStream() throws MessagingException {
910            return getContentStream();
911        }
912    
913        public synchronized DataHandler getDataHandler() throws MessagingException {
914            if (dh == null) {
915                dh = new DataHandler(new MimePartDataSource(this));
916            }
917            return dh;
918        }
919    
920        public Object getContent() throws MessagingException, IOException {
921            return getDataHandler().getContent();
922        }
923    
924        public void setDataHandler(DataHandler handler) throws MessagingException {
925            dh = handler;
926            // if we have a handler override, then we need to invalidate any content
927            // headers that define the types.  This information will be derived from the
928            // data heander unless subsequently overridden.
929            removeHeader("Content-Type");
930            removeHeader("Content-Transfer-Encoding");
931        }
932    
933        public void setContent(Object content, String type) throws MessagingException {
934            setDataHandler(new DataHandler(content, type));
935        }
936    
937        public void setText(String text) throws MessagingException {
938            setText(text, null, "plain");
939        }
940    
941        public void setText(String text, String charset) throws MessagingException {
942            setText(text, charset, "plain");
943        }
944    
945    
946        public void setText(String text, String charset, String subtype) throws MessagingException {
947            // we need to sort out the character set if one is not provided.
948            if (charset == null) {
949                // if we have non us-ascii characters here, we need to adjust this.
950                if (!ASCIIUtil.isAscii(text)) {
951                    charset = MimeUtility.getDefaultMIMECharset();
952                }
953                else {
954                    charset = "us-ascii";
955                }
956            }
957            setContent(text, "text/" + subtype + "; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
958        }
959    
960        public void setContent(Multipart part) throws MessagingException {
961            setDataHandler(new DataHandler(part, part.getContentType()));
962            part.setParent(this);
963        }
964    
965        public Message reply(boolean replyToAll) throws MessagingException {
966            // create a new message in this session.
967            MimeMessage reply = createMimeMessage(session);
968    
969            // get the header and add the "Re:" bit, if necessary.
970            String newSubject = getSubject();
971            if (newSubject != null) {
972                // check to see if it already begins with "Re: " (in any case).
973                // Add one on if we don't have it yet.
974                if (!newSubject.regionMatches(true, 0, "Re: ", 0, 4)) {
975                    newSubject = "Re: " + newSubject;
976                }
977                reply.setSubject(newSubject);
978            }
979    
980            Address[] toRecipients = getReplyTo();
981    
982            // set the target recipients the replyTo value
983            reply.setRecipients(Message.RecipientType.TO, getReplyTo());
984    
985            // need to reply to everybody?  More things to add.
986            if (replyToAll) {
987                // when replying, we want to remove "duplicates" in the final list.
988    
989                HashMap masterList = new HashMap();
990    
991                // reply to all implies add the local sender.  Add this to the list if resolveable.
992                InternetAddress localMail = InternetAddress.getLocalAddress(session);
993                if (localMail != null) {
994                    masterList.put(localMail.getAddress(), localMail);
995                }
996                // see if we have some local aliases to deal with.
997                String alternates = session.getProperty(MAIL_ALTERNATES);
998                if (alternates != null) {
999                    // parse this string list and merge with our set.
1000                    Address[] alternateList = InternetAddress.parse(alternates, false);
1001                    mergeAddressList(masterList, alternateList);
1002                }
1003    
1004                // the master list now contains an a list of addresses we will exclude from
1005                // the addresses.  From this point on, we're going to prune any additional addresses
1006                // against this list, AND add any new addresses to the list
1007    
1008                // now merge in the main recipients, and merge in the other recipents as well
1009                Address[] toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.TO));
1010                if (toList.length != 0) {
1011                    // now check to see what sort of reply we've been asked to send.
1012                    // if replying to all as a CC, then we need to add to the CC list, otherwise they are
1013                    // TO recipients.
1014                    if (SessionUtil.getBooleanProperty(session, MAIL_REPLYALLCC, false)) {
1015                        reply.addRecipients(Message.RecipientType.CC, toList);
1016                    }
1017                    else {
1018                        reply.addRecipients(Message.RecipientType.TO, toList);
1019                    }
1020                }
1021                // and repeat for the CC list.
1022                toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.CC));
1023                if (toList.length != 0) {
1024                    reply.addRecipients(Message.RecipientType.CC, toList);
1025                }
1026    
1027                // a news group list is separate from the normal addresses.  We just take these recepients
1028                // asis without trying to prune duplicates.
1029                toList = getRecipients(RecipientType.NEWSGROUPS);
1030                if (toList != null && toList.length != 0) {
1031                    reply.addRecipients(RecipientType.NEWSGROUPS, toList);
1032                }
1033            }
1034    
1035            // this is a bit of a pain.  We can't set the flags here by specifying the system flag, we need to
1036            // construct a flag item instance inorder to set it.
1037    
1038            // this is an answered email.
1039            setFlags(new Flags(Flags.Flag.ANSWERED), true);
1040            // all done, return the constructed Message object.
1041            return reply;
1042        }
1043    
1044    
1045        /**
1046         * Merge a set of addresses into a master accumulator list, eliminating
1047         * duplicates.
1048         *
1049         * @param master The set of addresses we've accumulated so far.
1050         * @param list   The list of addresses to merge in.
1051         */
1052        private void mergeAddressList(Map master, Address[] list) {
1053            // make sure we have a list.
1054            if (list == null) {
1055                return;
1056            }
1057            for (int i = 0; i < list.length; i++) {
1058                InternetAddress address = (InternetAddress)list[i];
1059    
1060                // if not in the master list already, add it now.
1061                if (!master.containsKey(address.getAddress())) {
1062                    master.put(address.getAddress(), address);
1063                }
1064            }
1065        }
1066    
1067    
1068        /**
1069         * Prune a list of addresses against our master address list,
1070         * returning the "new" addresses.  The master list will be
1071         * updated with this new set of addresses.
1072         *
1073         * @param master The master address list of addresses we've seen before.
1074         * @param list   The new list of addresses to prune.
1075         *
1076         * @return An array of addresses pruned of any duplicate addresses.
1077         */
1078        private Address[] pruneAddresses(Map master, Address[] list) {
1079            // return an empy array if we don't get an input list.
1080            if (list == null) {
1081                return new Address[0];
1082            }
1083    
1084            // optimistically assume there are no addresses to eliminate (common).
1085            ArrayList prunedList = new ArrayList(list.length);
1086            for (int i = 0; i < list.length; i++) {
1087                InternetAddress address = (InternetAddress)list[i];
1088    
1089                // if not in the master list, this is a new one.  Add to both the master list and
1090                // the pruned list.
1091                if (!master.containsKey(address.getAddress())) {
1092                    master.put(address.getAddress(), address);
1093                    prunedList.add(address);
1094                }
1095            }
1096            // convert back to list form.
1097            return (Address[])prunedList.toArray(new Address[0]);
1098        }
1099    
1100    
1101        /**
1102         * Write the message out to a stream in RFC 822 format.
1103         *
1104         * @param out    The target output stream.
1105         *
1106         * @exception MessagingException
1107         * @exception IOException
1108         */
1109        public void writeTo(OutputStream out) throws MessagingException, IOException {
1110            writeTo(out, null);
1111        }
1112    
1113        /**
1114         * Write the message out to a target output stream, excluding the
1115         * specified message headers.
1116         *
1117         * @param out    The target output stream.
1118         * @param ignoreHeaders
1119         *               An array of header types to ignore.  This can be null, which means
1120         *               write out all headers.
1121         *
1122         * @exception MessagingException
1123         * @exception IOException
1124         */
1125        public void writeTo(OutputStream out, String[] ignoreHeaders) throws MessagingException, IOException {
1126            // make sure everything is saved before we write
1127            if (!saved) {
1128                saveChanges();
1129            }
1130    
1131            // write out the headers first
1132            headers.writeTo(out, ignoreHeaders);
1133            // add the separater between the headers and the data portion.
1134            out.write('\r');
1135            out.write('\n');
1136    
1137            // if the modfied flag, we don't have current content, so the data handler needs to
1138            // take care of writing this data out.
1139            if (modified) {
1140                dh.writeTo(MimeUtility.encode(out, getEncoding()));
1141            } else {
1142                // if we have content directly, we can write this out now.
1143                if (content != null) {
1144                    out.write(content);
1145                }
1146                else {
1147                    // see if we can get a content stream for this message.  We might have had one
1148                    // explicitly set, or a subclass might override the get method to provide one.
1149                    InputStream in = getContentStream();
1150    
1151                    byte[] buffer = new byte[8192];
1152                    int length = in.read(buffer);
1153                    // copy the data stream-to-stream.
1154                    while (length > 0) {
1155                        out.write(buffer, 0, length);
1156                        length = in.read(buffer);
1157                    }
1158                    in.close();
1159                }
1160            }
1161    
1162            // flush any data we wrote out, but do not close the stream.  That's the caller's duty.
1163            out.flush();
1164        }
1165    
1166    
1167        /**
1168         * Retrieve all headers that match a given name.
1169         *
1170         * @param name   The target name.
1171         *
1172         * @return The set of headers that match the given name.  These headers
1173         *         will be the decoded() header values if these are RFC 2047
1174         *         encoded.
1175         * @exception MessagingException
1176         */
1177        public String[] getHeader(String name) throws MessagingException {
1178            return headers.getHeader(name);
1179        }
1180    
1181        /**
1182         * Get all headers that match a particular name, as a single string.
1183         * Individual headers are separated by the provided delimiter.   If
1184         * the delimiter is null, only the first header is returned.
1185         *
1186         * @param name      The source header name.
1187         * @param delimiter The delimiter string to be used between headers.  If null, only
1188         *                  the first is returned.
1189         *
1190         * @return The headers concatenated as a single string.
1191         * @exception MessagingException
1192         */
1193        public String getHeader(String name, String delimiter) throws MessagingException {
1194            return headers.getHeader(name, delimiter);
1195        }
1196    
1197        /**
1198         * Set a new value for a named header.
1199         *
1200         * @param name   The name of the target header.
1201         * @param value  The new value for the header.
1202         *
1203         * @exception MessagingException
1204         */
1205        public void setHeader(String name, String value) throws MessagingException {
1206            headers.setHeader(name, value);
1207        }
1208    
1209        /**
1210         * Conditionally set or remove a named header.  If the new value
1211         * is null, the header is removed.
1212         *
1213         * @param name   The header name.
1214         * @param value  The new header value.  A null value causes the header to be
1215         *               removed.
1216         *
1217         * @exception MessagingException
1218         */
1219        private void setOrRemoveHeader(String name, String value) throws MessagingException {
1220            if (value == null) {
1221                headers.removeHeader(name);
1222            }
1223            else {
1224                headers.setHeader(name, value);
1225            }
1226        }
1227    
1228        /**
1229         * Add a new value to an existing header.  The added value is
1230         * created as an additional header of the same type and value.
1231         *
1232         * @param name   The name of the target header.
1233         * @param value  The removed header.
1234         *
1235         * @exception MessagingException
1236         */
1237        public void addHeader(String name, String value) throws MessagingException {
1238            headers.addHeader(name, value);
1239        }
1240    
1241        /**
1242         * Remove a header with the given name.
1243         *
1244         * @param name   The name of the removed header.
1245         *
1246         * @exception MessagingException
1247         */
1248        public void removeHeader(String name) throws MessagingException {
1249            headers.removeHeader(name);
1250        }
1251    
1252        /**
1253         * Retrieve the complete list of message headers, as an enumeration.
1254         *
1255         * @return An Enumeration of the message headers.
1256         * @exception MessagingException
1257         */
1258        public Enumeration getAllHeaders() throws MessagingException {
1259            return headers.getAllHeaders();
1260        }
1261    
1262        public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
1263            return headers.getMatchingHeaders(names);
1264        }
1265    
1266        public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
1267            return headers.getNonMatchingHeaders(names);
1268        }
1269    
1270        public void addHeaderLine(String line) throws MessagingException {
1271            headers.addHeaderLine(line);
1272        }
1273    
1274        public Enumeration getAllHeaderLines() throws MessagingException {
1275            return headers.getAllHeaderLines();
1276        }
1277    
1278        public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
1279            return headers.getMatchingHeaderLines(names);
1280        }
1281    
1282        public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
1283            return headers.getNonMatchingHeaderLines(names);
1284        }
1285    
1286        public synchronized Flags getFlags() throws MessagingException {
1287            return (Flags) flags.clone();
1288        }
1289    
1290        public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
1291            return flags.contains(flag);
1292        }
1293    
1294        /**
1295         * Set or clear a flag value.
1296         *
1297         * @param flags  The set of flags to effect.
1298         * @param set    The value to set the flag to (true or false).
1299         *
1300         * @exception MessagingException
1301         */
1302        public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
1303            if (set) {
1304                flags.add(flag);
1305            }
1306            else {
1307                flags.remove(flag);
1308            }
1309        }
1310    
1311        /**
1312         * Saves any changes on this message.  When called, the modified
1313         * and saved flags are set to true and updateHeaders() is called
1314         * to force updates.
1315         *
1316         * @exception MessagingException
1317         */
1318        public void saveChanges() throws MessagingException {
1319            // setting modified invalidates the current content.
1320            modified = true;
1321            saved = true;
1322            // update message headers from the content.
1323            updateHeaders();
1324        }
1325    
1326        /**
1327         * Update the internet headers so that they make sense.  This
1328         * will attempt to make sense of the message content type
1329         * given the state of the content.
1330         *
1331         * @exception MessagingException
1332         */
1333        protected void updateHeaders() throws MessagingException {
1334    
1335            // make sure we set the MIME version
1336            setHeader("MIME-Version", "1.0");
1337    
1338            DataHandler handler = getDataHandler();
1339    
1340            try {
1341                // figure out the content type.  If not set, we'll need to figure this out.
1342                String type = dh.getContentType();
1343                // parse this content type out so we can do matches/compares.
1344                ContentType content = new ContentType(type);
1345    
1346                // is this a multipart content?
1347                if (content.match("multipart/*")) {
1348                    // the content is suppose to be a MimeMultipart.  Ping it to update it's headers as well.
1349                    try {
1350                        MimeMultipart part = (MimeMultipart)handler.getContent();
1351                        part.updateHeaders();
1352                    } catch (ClassCastException e) {
1353                        throw new MessagingException("Message content is not MimeMultipart", e);
1354                    }
1355                }
1356                else if (!content.match("message/rfc822")) {
1357                    // simple part, we need to update the header type information
1358                    // if no encoding is set yet, figure this out from the data handler content.
1359                    if (getSingleHeader("Content-Transfer-Encoding") == null) {
1360                        setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
1361                    }
1362    
1363                    // is a content type header set?  Check the property to see if we need to set this.
1364                    if (getSingleHeader("Content-Type") == null) {
1365                        if (SessionUtil.getBooleanProperty(session, "MIME_MAIL_SETDEFAULTTEXTCHARSET", true)) {
1366                            // is this a text type?  Figure out the encoding and make sure it is set.
1367                            if (content.match("text/*")) {
1368                                // the charset should be specified as a parameter on the MIME type.  If not there,
1369                                // try to figure one out.
1370                                if (content.getParameter("charset") == null) {
1371    
1372                                    String encoding = getEncoding();
1373                                    // if we're sending this as 7-bit ASCII, our character set need to be
1374                                    // compatible.
1375                                    if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
1376                                        content.setParameter("charset", "us-ascii");
1377                                    }
1378                                    else {
1379                                        // get the global default.
1380                                        content.setParameter("charset", MimeUtility.getDefaultMIMECharset());
1381                                    }
1382                                }
1383                            }
1384                        }
1385                    }
1386                }
1387    
1388                // if we don't have a content type header, then create one.
1389                if (getSingleHeader("Content-Type") == null) {
1390                    // get the disposition header, and if it is there, copy the filename parameter into the
1391                    // name parameter of the type.
1392                    String disp = getSingleHeader("Content-Disposition");
1393                    if (disp != null) {
1394                        // parse up the string value of the disposition
1395                        ContentDisposition disposition = new ContentDisposition(disp);
1396                        // now check for a filename value
1397                        String filename = disposition.getParameter("filename");
1398                        // copy and rename the parameter, if it exists.
1399                        if (filename != null) {
1400                            content.setParameter("name", filename);
1401                        }
1402                    }
1403                    // set the header with the updated content type information.
1404                    setHeader("Content-Type", content.toString());
1405                }
1406    
1407                // new javamail 1.4 requirement.
1408                updateMessageID();
1409    
1410            } catch (IOException e) {
1411                throw new MessagingException("Error updating message headers", e);
1412            }
1413        }
1414    
1415    
1416        protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
1417            // internet headers has a constructor for just this purpose
1418            return new InternetHeaders(in);
1419        }
1420    
1421        /**
1422         * Convert a header into an array of NewsAddress items.
1423         *
1424         * @param header The name of the source header.
1425         *
1426         * @return The parsed array of addresses.
1427         * @exception MessagingException
1428         */
1429        private Address[] getHeaderAsNewsAddresses(String header) throws MessagingException {
1430            // NB:  We're using getHeader() here to allow subclasses an opportunity to perform lazy loading
1431            // of the headers.
1432            String mergedHeader = getHeader(header, ",");
1433            if (mergedHeader != null) {
1434                return NewsAddress.parse(mergedHeader);
1435            }
1436            return null;
1437        }
1438    
1439        private Address[] getHeaderAsInternetAddresses(String header, boolean strict) throws MessagingException {
1440            // NB:  We're using getHeader() here to allow subclasses an opportunity to perform lazy loading
1441            // of the headers.
1442            String mergedHeader = getHeader(header, ",");
1443    
1444            if (mergedHeader != null) {
1445                return InternetAddress.parseHeader(mergedHeader, strict);
1446            }
1447            return null;
1448        }
1449    
1450        /**
1451         * Check to see if we require strict addressing on parsing
1452         * internet headers.
1453         *
1454         * @return The current value of the "mail.mime.address.strict" session
1455         *         property, or true, if the property is not set.
1456         */
1457        private boolean isStrictAddressing() {
1458            return SessionUtil.getBooleanProperty(session, MIME_ADDRESS_STRICT, true);
1459        }
1460    
1461        /**
1462         * Set a named header to the value of an address field.
1463         *
1464         * @param header  The header name.
1465         * @param address The address value.  If the address is null, the header is removed.
1466         *
1467         * @exception MessagingException
1468         */
1469        private void setHeader(String header, Address address) throws MessagingException {
1470            if (address == null) {
1471                removeHeader(header);
1472            }
1473            else {
1474                setHeader(header, address.toString());
1475            }
1476        }
1477    
1478        /**
1479         * Set a header to a list of addresses.
1480         *
1481         * @param header    The header name.
1482         * @param addresses An array of addresses to set the header to.  If null, the
1483         *                  header is removed.
1484         */
1485        private void setHeader(String header, Address[] addresses) {
1486            if (addresses == null) {
1487                headers.removeHeader(header);
1488            }
1489            else {
1490                headers.setHeader(header, addresses);
1491            }
1492        }
1493    
1494        private void addHeader(String header, Address[] addresses) throws MessagingException {
1495            headers.addHeader(header, InternetAddress.toString(addresses));
1496        }
1497    
1498        private String getHeaderForRecipientType(Message.RecipientType type) throws MessagingException {
1499            if (RecipientType.TO == type) {
1500                return "To";
1501            } else if (RecipientType.CC == type) {
1502                return "Cc";
1503            } else if (RecipientType.BCC == type) {
1504                return "Bcc";
1505            } else if (RecipientType.NEWSGROUPS == type) {
1506                return "Newsgroups";
1507            } else {
1508                throw new MessagingException("Unsupported recipient type: " + type.toString());
1509            }
1510        }
1511    
1512        /**
1513         * Utility routine to get a header as a single string value
1514         * rather than an array of headers.
1515         *
1516         * @param name   The name of the header.
1517         *
1518         * @return The single string header value.  If multiple headers exist,
1519         *         the additional ones are ignored.
1520         * @exception MessagingException
1521         */
1522        private String getSingleHeader(String name) throws MessagingException {
1523            String[] values = getHeader(name);
1524            if (values == null || values.length == 0) {
1525                return null;
1526            } else {
1527                return values[0];
1528            }
1529        }
1530    
1531        /**
1532         * Update the message identifier after headers have been updated.
1533         *
1534         * The default message id is composed of the following items:
1535         *
1536         * 1)  A newly created object's hash code.
1537         * 2)  A uniqueness counter
1538         * 3)  The current time in milliseconds
1539         * 4)  The string JavaMail
1540         * 5)  The user's local address as returned by InternetAddress.getLocalAddress().
1541         *
1542         * @exception MessagingException
1543         */
1544        public void updateMessageID() throws MessagingException {
1545            StringBuffer id = new StringBuffer();
1546    
1547            id.append('<');
1548            id.append(new Object().hashCode());
1549            id.append('.');
1550            id.append(messageID++);
1551            id.append(System.currentTimeMillis());
1552            id.append('.');
1553            id.append("JavaMail.");
1554    
1555            // get the local address and apply a suitable default.
1556    
1557            InternetAddress localAddress = InternetAddress.getLocalAddress(session);
1558            if (localAddress != null) {
1559                id.append(localAddress.getAddress());
1560            }
1561            else {
1562                id.append("javamailuser@localhost");
1563            }
1564            id.append('>');
1565    
1566            setHeader("Message-ID", id.toString());
1567        }
1568    
1569        /**
1570         * Method used to create a new MimeMessage instance.  This method
1571         * is used whenever the MimeMessage class needs to create a new
1572         * Message instance (e.g, reply()).  This method allows subclasses
1573         * to override the class of message that gets created or set
1574         * default values, if needed.
1575         *
1576         * @param session The session associated with this message.
1577         *
1578         * @return A newly create MimeMessage instance.
1579         */
1580        protected MimeMessage createMimeMessage(Session session) {
1581            return new MimeMessage(session);
1582        }
1583    
1584    }