You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by rm...@apache.org on 2014/08/26 20:17:09 UTC

svn commit: r1620683 [7/17] - in /geronimo/specs/trunk: ./ geronimo-javamail_1.5_spec/ geronimo-javamail_1.5_spec/src/ geronimo-javamail_1.5_spec/src/main/ geronimo-javamail_1.5_spec/src/main/java/ geronimo-javamail_1.5_spec/src/main/java/javax/ geroni...

Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimeBodyPart.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimeBodyPart.java?rev=1620683&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimeBodyPart.java (added)
+++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MimeBodyPart.java Tue Aug 26 18:17:06 2014
@@ -0,0 +1,830 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.BodyPart;
+import javax.mail.EncodingAware;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.internet.HeaderTokenizer.Token;
+
+import org.apache.geronimo.mail.util.ASCIIUtil;
+import org.apache.geronimo.mail.util.SessionUtil;
+
+
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MimeBodyPart extends BodyPart implements MimePart {
+	 // constants for accessed properties
+    private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
+    private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
+    private static final String MIME_SETDEFAULTTEXTCHARSET = "mail.mime.setdefaulttextcharset";
+    private static final String MIME_SETCONTENTTYPEFILENAME = "mail.mime.setcontenttypefilename";
+
+    static final boolean cacheMultipart = SessionUtil.getBooleanProperty("mail.mime.cachemultipart", true);
+
+    /**
+     * The {@link DataHandler} for this Message's content.
+     */
+    protected DataHandler dh;
+    /**
+     * This message's content (unless sourced from a SharedInputStream).
+     */
+    
+    
+    /**
+     * If our content is a Multipart or Message object, we save it
+     * the first time it's created by parsing a stream so that changes
+     * to the contained objects will not be lost. 
+     *
+     * If this field is not null, it's return by the {@link #getContent}
+     * method.  The {@link #getContent} method sets this field if it
+     * would return a Multipart or MimeMessage object.  This field is
+     * is cleared by the {@link #setDataHandler} method.
+     *
+     * @since   JavaMail 1.5
+     */
+    protected Object cachedContent;
+    
+    
+    protected byte content[];
+    /**
+     * If the data for this message was supplied by a {@link SharedInputStream}
+     * then this is another such stream representing the content of this message;
+     * if this field is non-null, then {@link #content} will be null.
+     */
+    protected InputStream contentStream;
+    /**
+     * This message's headers.
+     */
+    protected InternetHeaders headers;
+
+    public MimeBodyPart() {
+        headers = new InternetHeaders();
+    }
+
+    public MimeBodyPart(final InputStream in) throws MessagingException {
+        headers = new InternetHeaders(in);
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final byte[] buffer = new byte[1024];
+        int count;
+        try {
+            while((count = in.read(buffer, 0, 1024)) > 0) {
+                baos.write(buffer, 0, count);
+            }
+        } catch (final IOException e) {
+            throw new MessagingException(e.toString(),e);
+        }
+        content = baos.toByteArray();
+    }
+
+    public MimeBodyPart(final InternetHeaders headers, final byte[] content) throws MessagingException {
+        this.headers = headers;
+        this.content = content;
+    }
+
+    /**
+     * Return the content size of this message.  This is obtained
+     * either from the size of the content field (if available) or
+     * from the contentStream, IFF the contentStream returns a positive
+     * size.  Returns -1 if the size is not available.
+     *
+     * @return Size of the content in bytes.
+     * @exception MessagingException
+     */
+    public int getSize() throws MessagingException {
+        if (content != null) {
+            return content.length;
+        }
+        if (contentStream != null) {
+            try {
+                final int size = contentStream.available();
+                if (size > 0) {
+                    return size;
+                }
+            } catch (final IOException e) {
+            }
+        }
+        return -1;
+    }
+
+    public int getLineCount() throws MessagingException {
+        return -1;
+    }
+
+    public String getContentType() throws MessagingException {
+        String value = getSingleHeader("Content-Type");
+        if (value == null) {
+            value = "text/plain";
+        }
+        return value;
+    }
+
+    /**
+     * Tests to see if this message has a mime-type match with the
+     * given type name.
+     *
+     * @param type   The tested type name.
+     *
+     * @return If this is a type match on the primary and secondare portion of the types.
+     * @exception MessagingException
+     */
+    public boolean isMimeType(final String type) throws MessagingException {
+        return new ContentType(getContentType()).match(type);
+    }
+
+    /**
+     * Retrieve the message "Content-Disposition" header field.
+     * This value represents how the part should be represented to
+     * the user.
+     *
+     * @return The string value of the Content-Disposition field.
+     * @exception MessagingException
+     */
+    public String getDisposition() throws MessagingException {
+        final String disp = getSingleHeader("Content-Disposition");
+        if (disp != null) {
+            return new ContentDisposition(disp).getDisposition();
+        }
+        return null;
+    }
+
+    /**
+     * Set a new dispostion value for the "Content-Disposition" field.
+     * If the new value is null, the header is removed.
+     *
+     * @param disposition
+     *               The new disposition value.
+     *
+     * @exception MessagingException
+     */
+    public void setDisposition(final String disposition) throws MessagingException {
+        if (disposition == null) {
+            removeHeader("Content-Disposition");
+        }
+        else {
+            // the disposition has parameters, which we'll attempt to preserve in any existing header.
+            final String currentHeader = getSingleHeader("Content-Disposition");
+            if (currentHeader != null) {
+                final ContentDisposition content = new ContentDisposition(currentHeader);
+                content.setDisposition(disposition);
+                setHeader("Content-Disposition", content.toString());
+            }
+            else {
+                // set using the raw string.
+                setHeader("Content-Disposition", disposition);
+            }
+        }
+    }
+
+    /**
+     * Retrieves the current value of the "Content-Transfer-Encoding"
+     * header.  Returns null if the header does not exist.
+     *
+     * @return The current header value or null.
+     * @exception MessagingException
+     */
+    public String getEncoding() throws MessagingException {
+        // this might require some parsing to sort out.
+        final String encoding = getSingleHeader("Content-Transfer-Encoding");
+        if (encoding != null) {
+            // we need to parse this into ATOMs and other constituent parts.  We want the first
+            // ATOM token on the string.
+            final HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
+
+            final Token token = tokenizer.next();
+            while (token.getType() != Token.EOF) {
+                // if this is an ATOM type, return it.
+                if (token.getType() == Token.ATOM) {
+                    return token.getValue();
+                }
+            }
+            // not ATOMs found, just return the entire header value....somebody might be able to make sense of
+            // this.
+            return encoding;
+        }
+        // no header, nothing to return.
+        return null;
+    }
+
+
+    /**
+     * Retrieve the value of the "Content-ID" header.  Returns null
+     * if the header does not exist.
+     *
+     * @return The current header value or null.
+     * @exception MessagingException
+     */
+    public String getContentID() throws MessagingException {
+        return getSingleHeader("Content-ID");
+    }
+
+    public void setContentID(final String cid) throws MessagingException {
+        setOrRemoveHeader("Content-ID", cid);
+    }
+
+    public String getContentMD5() throws MessagingException {
+        return getSingleHeader("Content-MD5");
+    }
+
+    public void setContentMD5(final String md5) throws MessagingException {
+        setHeader("Content-MD5", md5);
+    }
+
+    public String[] getContentLanguage() throws MessagingException {
+        return getHeader("Content-Language");
+    }
+
+    public void setContentLanguage(final String[] languages) throws MessagingException {
+        if (languages == null) {
+            removeHeader("Content-Language");
+        } else if (languages.length == 1) {
+            setHeader("Content-Language", languages[0]);
+        } else {
+            final StringBuffer buf = new StringBuffer(languages.length * 20);
+            buf.append(languages[0]);
+            for (int i = 1; i < languages.length; i++) {
+                buf.append(',').append(languages[i]);
+            }
+            setHeader("Content-Language", buf.toString());
+        }
+    }
+
+    public String getDescription() throws MessagingException {
+        final String description = getSingleHeader("Content-Description");
+        if (description != null) {
+            try {
+                // this could be both folded and encoded.  Return this to usable form.
+                return MimeUtility.decodeText(MimeUtility.unfold(description));
+            } catch (final UnsupportedEncodingException e) {
+                // ignore
+            }
+        }
+        // return the raw version for any errors.
+        return description;
+    }
+
+    public void setDescription(final String description) throws MessagingException {
+        setDescription(description, null);
+    }
+
+    public void setDescription(final String description, final String charset) throws MessagingException {
+        if (description == null) {
+            removeHeader("Content-Description");
+        }
+        else {
+            try {
+                setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null)));
+            } catch (final UnsupportedEncodingException e) {
+                throw new MessagingException(e.getMessage(), e);
+            }
+        }
+    }
+
+    public String getFileName() throws MessagingException {
+        // see if there is a disposition.  If there is, parse off the filename parameter.
+        final String disposition = getSingleHeader("Content-Disposition");
+        String filename = null;
+
+        if (disposition != null) {
+            filename = new ContentDisposition(disposition).getParameter("filename");
+        }
+
+        // if there's no filename on the disposition, there might be a name parameter on a
+        // Content-Type header.
+        if (filename == null) {
+            final String type = getSingleHeader("Content-Type");
+            if (type != null) {
+                try {
+                    filename = new ContentType(type).getParameter("name");
+                } catch (final ParseException e) {
+                }
+            }
+        }
+        // if we have a name, we might need to decode this if an additional property is set.
+        if (filename != null && SessionUtil.getBooleanProperty(MIME_DECODEFILENAME, false)) {
+            try {
+                filename = MimeUtility.decodeText(filename);
+            } catch (final UnsupportedEncodingException e) {
+                throw new MessagingException("Unable to decode filename", e);
+            }
+        }
+
+        return filename;
+    }
+
+
+    public void setFileName(String name) throws MessagingException {
+        // there's an optional session property that requests file name encoding...we need to process this before
+        // setting the value.
+        if (name != null && SessionUtil.getBooleanProperty(MIME_ENCODEFILENAME, false)) {
+            try {
+                name = MimeUtility.encodeText(name);
+            } catch (final UnsupportedEncodingException e) {
+                throw new MessagingException("Unable to encode filename", e);
+            }
+        }
+
+        // get the disposition string.
+        String disposition = getDisposition();
+        // if not there, then this is an attachment.
+        if (disposition == null) {
+            disposition = Part.ATTACHMENT;
+        }
+
+        // now create a disposition object and set the parameter.
+        final ContentDisposition contentDisposition = new ContentDisposition(disposition);
+        contentDisposition.setParameter("filename", name);
+
+        // serialize this back out and reset.
+        setHeader("Content-Disposition", contentDisposition.toString());
+
+        // The Sun implementation appears to update the Content-type name parameter too, based on
+        // another system property
+        if (SessionUtil.getBooleanProperty(MIME_SETCONTENTTYPEFILENAME, true)) {
+            final ContentType type = new ContentType(getContentType());
+            type.setParameter("name", name);
+            setHeader("Content-Type", type.toString());
+        }
+    }
+
+    public InputStream getInputStream() throws MessagingException, IOException {
+        return getDataHandler().getInputStream();
+    }
+
+    protected InputStream getContentStream() throws MessagingException {
+        if (contentStream != null) {
+            return contentStream;
+        }
+
+        if (content != null) {
+            return new ByteArrayInputStream(content);
+        } else {
+            throw new MessagingException("No content");
+        }
+    }
+
+    public InputStream getRawInputStream() throws MessagingException {
+        return getContentStream();
+    }
+
+    public synchronized DataHandler getDataHandler() throws MessagingException {
+        if (dh == null) {
+            dh = new DataHandler(new MimePartDataSource(this));
+        }
+        return dh;
+    }
+    
+    public Object getContent() throws MessagingException, IOException {
+        
+        if (cachedContent != null) {
+            return cachedContent;
+        }
+        
+        final Object c = getDataHandler().getContent();
+        
+        if (MimeBodyPart.cacheMultipart && (c instanceof Multipart || c instanceof Message) && (content != null || contentStream != null)) {
+            cachedContent = c;
+ 
+            if (c instanceof MimeMultipart) {
+                ((MimeMultipart) c).parse();
+            }
+        }
+        
+        return c;
+    }
+
+    public void setDataHandler(final DataHandler handler) throws MessagingException {
+        dh = handler;
+        // if we have a handler override, then we need to invalidate any content
+        // headers that define the types.  This information will be derived from the
+        // data heander unless subsequently overridden.
+        removeHeader("Content-Type");
+        removeHeader("Content-Transfer-Encoding");
+        cachedContent = null;
+
+    }
+
+    public void setContent(final Object content, final String type) throws MessagingException {
+        // Multipart content needs to be handled separately.
+        if (content instanceof Multipart) {
+            setContent((Multipart)content);
+        }
+        else {
+            setDataHandler(new DataHandler(content, type));
+        }
+
+    }
+
+    public void setText(final String text) throws MessagingException {
+        setText(text, null);
+    }
+
+    public void setText(final String text, final String charset) throws MessagingException {
+        // the default subtype is plain text.
+        setText(text, charset, "plain");
+    }
+
+
+    public void setText(final String text, String charset, final String subtype) throws MessagingException {
+        // we need to sort out the character set if one is not provided.
+        if (charset == null) {
+            // if we have non us-ascii characters here, we need to adjust this.
+            if (!ASCIIUtil.isAscii(text)) {
+                charset = MimeUtility.getDefaultMIMECharset();
+            }
+            else {
+                charset = "us-ascii";
+            }
+        }
+        setContent(text, "text/plain; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
+    }
+
+    public void setContent(final Multipart part) throws MessagingException {
+        setDataHandler(new DataHandler(part, part.getContentType()));
+        part.setParent(this);
+    }
+
+    public void writeTo(final OutputStream out) throws IOException, MessagingException {
+        headers.writeTo(out, null);
+        // add the separater between the headers and the data portion.
+        out.write('\r');
+        out.write('\n');
+        // we need to process this using the transfer encoding type
+        final OutputStream encodingStream = MimeUtility.encode(out, getEncoding());
+        getDataHandler().writeTo(encodingStream);
+        encodingStream.flush();
+    }
+
+    public String[] getHeader(final String name) throws MessagingException {
+        return headers.getHeader(name);
+    }
+
+    public String getHeader(final String name, final String delimiter) throws MessagingException {
+        return headers.getHeader(name, delimiter);
+    }
+
+    public void setHeader(final String name, final String value) throws MessagingException {
+        headers.setHeader(name, value);
+    }
+
+    /**
+     * Conditionally set or remove a named header.  If the new value
+     * is null, the header is removed.
+     *
+     * @param name   The header name.
+     * @param value  The new header value.  A null value causes the header to be
+     *               removed.
+     *
+     * @exception MessagingException
+     */
+    private void setOrRemoveHeader(final String name, final String value) throws MessagingException {
+        if (value == null) {
+            headers.removeHeader(name);
+        }
+        else {
+            headers.setHeader(name, value);
+        }
+    }
+
+    public void addHeader(final String name, final String value) throws MessagingException {
+        headers.addHeader(name, value);
+    }
+
+    public void removeHeader(final String name) throws MessagingException {
+        headers.removeHeader(name);
+    }
+
+    public Enumeration getAllHeaders() throws MessagingException {
+        return headers.getAllHeaders();
+    }
+
+    public Enumeration getMatchingHeaders(final String[] name) throws MessagingException {
+        return headers.getMatchingHeaders(name);
+    }
+
+    public Enumeration getNonMatchingHeaders(final String[] name) throws MessagingException {
+        return headers.getNonMatchingHeaders(name);
+    }
+
+    public void addHeaderLine(final String line) throws MessagingException {
+        headers.addHeaderLine(line);
+    }
+
+    public Enumeration getAllHeaderLines() throws MessagingException {
+        return headers.getAllHeaderLines();
+    }
+
+    public Enumeration getMatchingHeaderLines(final String[] names) throws MessagingException {
+        return headers.getMatchingHeaderLines(names);
+    }
+
+    public Enumeration getNonMatchingHeaderLines(final String[] names) throws MessagingException {
+        return headers.getNonMatchingHeaderLines(names);
+    }
+
+    protected void updateHeaders() throws MessagingException {
+        final DataHandler handler = getDataHandler();
+
+        try {
+            // figure out the content type.  If not set, we'll need to figure this out.
+            String type = dh.getContentType();
+            // parse this content type out so we can do matches/compares.
+            final ContentType contentType = new ContentType(type);
+            
+            // we might need to reconcile the content type and our explicitly set type
+            final String explicitType = getSingleHeader("Content-Type"); 
+            // is this a multipart content?
+            if (contentType.match("multipart/*")) {
+                // the content is suppose to be a MimeMultipart.  Ping it to update it's headers as well.
+                try {
+                    final MimeMultipart part = (MimeMultipart)handler.getContent();
+                    part.updateHeaders();
+                } catch (final ClassCastException e) {
+                    throw new MessagingException("Message content is not MimeMultipart", e);
+                }
+            }
+            else if (!contentType.match("message/rfc822")) {
+                // simple part, we need to update the header type information
+                // if no encoding is set yet, figure this out from the data handler.
+                if (getSingleHeader("Content-Transfer-Encoding") == null) {
+                    setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
+                }
+
+                // is a content type header set?  Check the property to see if we need to set this.
+                if (explicitType == null) {
+                    if (SessionUtil.getBooleanProperty(MIME_SETDEFAULTTEXTCHARSET, true)) {
+                        // is this a text type?  Figure out the encoding and make sure it is set.
+                        if (contentType.match("text/*")) {
+                            // the charset should be specified as a parameter on the MIME type.  If not there,
+                            // try to figure one out.
+                            if (contentType.getParameter("charset") == null) {
+
+                                final String encoding = getEncoding();
+                                // if we're sending this as 7-bit ASCII, our character set need to be
+                                // compatible.
+                                if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
+                                    contentType.setParameter("charset", "us-ascii");
+                                }
+                                else {
+                                    // get the global default.
+                                    contentType.setParameter("charset", MimeUtility.getDefaultMIMECharset());
+                                }
+                                // replace the datasource provided type 
+                                type = contentType.toString(); 
+                            }
+                        }
+                    }
+                }
+            }
+
+            // if we don't have a content type header, then create one.
+            if (explicitType == null) {
+                // get the disposition header, and if it is there, copy the filename parameter into the
+                // name parameter of the type.
+                final String disp = getHeader("Content-Disposition", null);
+                if (disp != null) {
+                    // parse up the string value of the disposition
+                    final ContentDisposition disposition = new ContentDisposition(disp);
+                    // now check for a filename value
+                    final String filename = disposition.getParameter("filename");
+                    // copy and rename the parameter, if it exists.
+                    if (filename != null) {
+                        contentType.setParameter("name", filename);
+                        // and update the string version 
+                        type = contentType.toString(); 
+                    }
+                }
+                // set the header with the updated content type information.
+                setHeader("Content-Type", type);
+            }
+            
+            
+            if (cachedContent != null) {
+                dh = new DataHandler(cachedContent, getContentType());
+                cachedContent = null;
+                content = null;
+                if (contentStream != null) {
+                    try {
+                        contentStream.close();
+                    } catch (final IOException ioex) {
+                        //np-op
+                    }
+                }
+                contentStream = null;
+            }
+
+        } catch (final IOException e) {
+            throw new MessagingException("Error updating message headers", e);
+        }
+    }
+
+    private String getSingleHeader(final String name) throws MessagingException {
+        final String[] values = getHeader(name);
+        if (values == null || values.length == 0) {
+            return null;
+        } else {
+            return values[0];
+        }
+    }
+
+
+    /**
+     * Use the specified file to provide the data for this part.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The encoding will be chosen appropriately for the
+     * file data.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param       file        the File object to attach
+     * @exception   IOException errors related to accessing the file
+     * @exception   MessagingException  message related errors
+     * @since       JavaMail 1.4
+     */
+    public void attachFile(final File file) throws IOException, MessagingException {
+                
+    	final FileDataSource dataSource = new FileDataSource(file);
+        setDataHandler(new DataHandler(dataSource));
+        setFileName(dataSource.getName());
+        
+        /* Since JavaMail 1.5:
+         An oversight when these methods were originally added.
+         Clearly attachments should set the disposition to ATTACHMENT.
+         */
+        setDisposition(ATTACHMENT);
+    }
+
+
+    /**
+     * Use the specified file to provide the data for this part.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The encoding will be chosen appropriately for the
+     * file data.
+     *
+     * @param       file        the name of the file to attach
+     * @exception   IOException errors related to accessing the file
+     * @exception   MessagingException  message related errors
+     * @since       JavaMail 1.4
+     */
+    public void attachFile(final String file) throws IOException, MessagingException {
+
+        attachFile(new File(file));
+    }
+    
+    
+    
+    /**
+     * Use the specified file with the specified Content-Type and
+     * Content-Transfer-Encoding to provide the data for this part.
+     * If contentType or encoding are null, appropriate values will
+     * be chosen.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param       file        the File object to attach
+     * @param       contentType the Content-Type, or null
+     * @param       encoding    the Content-Transfer-Encoding, or null
+     * @exception   IOException errors related to accessing the file
+     * @exception   MessagingException  message related errors
+     * @since       JavaMail 1.5
+     */
+    public void attachFile(final File file, final String contentType, final String encoding)
+                throws IOException, MessagingException {
+     
+        final FileDataSource dataSource = new EncodingAwareFileDataSource(file, contentType, encoding);
+        setDataHandler(new DataHandler(dataSource));
+        setFileName(dataSource.getName());
+               
+        /* Since JavaMail 1.5:
+         An oversight when these methods were originally added.
+         Clearly attachments should set the disposition to ATTACHMENT.
+         */
+        setDisposition(ATTACHMENT);
+    }
+
+    /**
+     * Use the specified file with the specified Content-Type and
+     * Content-Transfer-Encoding to provide the data for this part.
+     * If contentType or encoding are null, appropriate values will
+     * be chosen.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param       file        the name of the file
+     * @param       contentType the Content-Type, or null
+     * @param       encoding    the Content-Transfer-Encoding, or null
+     * @exception   IOException errors related to accessing the file
+     * @exception   MessagingException  message related errors
+     * @since       JavaMail 1.5
+     */
+    public void attachFile(final String file, final String contentType, final String encoding)
+                throws IOException, MessagingException {
+        
+        attachFile(new File(file), contentType, encoding);
+    }
+
+
+    /**
+     * Save the body part content to a given target file.
+     *
+     * @param file   The File object used to store the information.
+     *
+     * @exception IOException
+     * @exception MessagingException
+     */
+    public void saveFile(final File file) throws IOException, MessagingException {
+    	final OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
+        // we need to read the data in to write it out (sigh).
+        final InputStream in = getInputStream();
+        try {
+            final byte[] buffer = new byte[8192];
+	        int length;
+	        while ((length = in.read(buffer)) > 0) {
+         		out.write(buffer, 0, length);
+            }
+        }
+        finally {
+            // make sure all of the streams are closed before we return
+            if (in != null) {
+                in.close();
+            }
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+
+    /**
+     * Save the body part content to a given target file.
+     *
+     * @param file   The file name used to store the information.
+     *
+     * @exception IOException
+     * @exception MessagingException
+     */
+    public void saveFile(final String file) throws IOException, MessagingException {
+        saveFile(new File(file));
+    }
+    
+    private static class EncodingAwareFileDataSource extends FileDataSource implements EncodingAware {
+        private final String contentType;
+        private final String encoding;
+
+        public EncodingAwareFileDataSource(final File file, final String contentType, final String encoding) {
+            super(file);
+            this.contentType = contentType;
+            this.encoding = encoding;
+        }
+
+        @Override
+        public String getContentType() {
+            return contentType == null ? super.getContentType() : contentType;
+        }
+
+        //this will be evaluated in MimeUtility.getEncoding(DataSource)
+        public String getEncoding() {
+            return encoding;
+        }
+    }
+}