100

Now that I`ve downloaded all the messages, and store them to

Message[] temp;

How do I get the list of attachments for each of those messages to

List<File> attachments;

Note: no thirdparty libs, please, just JavaMail.

George
  • 8,368
  • 12
  • 65
  • 106

5 Answers5

115

Without exception handling, but here goes:

List<File> attachments = new ArrayList<File>();
for (Message message : temp) {
    Multipart multipart = (Multipart) message.getContent();

    for (int i = 0; i < multipart.getCount(); i++) {
        BodyPart bodyPart = multipart.getBodyPart(i);
        if(!Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) &&
               StringUtils.isBlank(bodyPart.getFileName())) {
            continue; // dealing with attachments only
        } 
        InputStream is = bodyPart.getInputStream();
        // -- EDIT -- SECURITY ISSUE --
        // do not do this in production code -- a malicious email can easily contain this filename: "../etc/passwd", or any other path: They can overwrite _ANY_ file on the system that this code has write access to!
//      File f = new File("/tmp/" + bodyPart.getFileName());
        FileOutputStream fos = new FileOutputStream(f);
        byte[] buf = new byte[4096];
        int bytesRead;
        while((bytesRead = is.read(buf))!=-1) {
            fos.write(buf, 0, bytesRead);
        }
        fos.close();
        attachments.add(f);
    }
}
rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
David Rabinowitz
  • 29,904
  • 14
  • 93
  • 125
  • 2
    But wait a minute, aren`t we supposed to check if (bodyPart.getDisposition() == Part.ATTACHMENT){} before saving the file, so that it does not save the body of the email? – George Nov 18 '09 at 10:41
  • 8
    Wouldn't StringUtils.isBlank() be more natural to read than using !StringUtils.isNotBlank ? – Kuchi Feb 18 '14 at 17:35
  • This answer doesn't consider nested multipart attachments (commonly used by Thunderbird for example). To be able to find nested multipart attachments, see @mefi answer. – Ruslan Stelmachenko Sep 05 '18 at 16:42
  • 3
    The snippet is a security breach waiting to happen. I have edited the snippet to highlight this. – rzwitserloot Feb 05 '19 at 13:24
  • 1
    Of course, and thanks for that. This snippet was only to demonstrate how it can be done, naturally it is not a production code – David Rabinowitz Feb 11 '19 at 05:23
34

Question is very old, but maybe it will help someone. I would like to expand David Rabinowitz`s answer.

if(!Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()))

should not return all atachments as you expect, because you can have mail where mixed part is without defined disposition.

   ----boundary_328630_1e15ac03-e817-4763-af99-d4b23cfdb600
Content-Type: application/octet-stream;
    name="00000000009661222736_236225959_20130731-7.txt"
Content-Transfer-Encoding: base64

so in this case, you can also check for filename. Like this:

if (!Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) && StringUtils.isBlank(part.getFileName())) {...}

EDIT

there is whole working code using condition descibed above.. Because each part can encapsulate another parts and attachment should be nested in, recursion is used to traverse through all parts

public List<InputStream> getAttachments(Message message) throws Exception {
    Object content = message.getContent();
    if (content instanceof String)
        return null;        

    if (content instanceof Multipart) {
        Multipart multipart = (Multipart) content;
        List<InputStream> result = new ArrayList<InputStream>();

        for (int i = 0; i < multipart.getCount(); i++) {
            result.addAll(getAttachments(multipart.getBodyPart(i)));
        }
        return result;

    }
    return null;
}

private List<InputStream> getAttachments(BodyPart part) throws Exception {
    List<InputStream> result = new ArrayList<InputStream>();
    Object content = part.getContent();
    if (content instanceof InputStream || content instanceof String) {
        if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) || StringUtils.isNotBlank(part.getFileName())) {
            result.add(part.getInputStream());
            return result;
        } else {
            return new ArrayList<InputStream>();
        }
    }

    if (content instanceof Multipart) {
            Multipart multipart = (Multipart) content;
            for (int i = 0; i < multipart.getCount(); i++) {
                BodyPart bodyPart = multipart.getBodyPart(i);
                result.addAll(getAttachments(bodyPart));
            }
    }
    return result;
}
mefi
  • 1,203
  • 1
  • 12
  • 16
  • the expression checks for filename empty or `null`. Is that correct? – Keerthivasan May 16 '14 at 05:00
  • Octopus: Yes. Checks if a CharSequence is not empty (""), not null and not whitespace only. – mefi May 20 '14 at 13:46
  • Hey can you tell how to convert the List to List ? – kumuda Mar 19 '15 at 12:37
  • kumunda: hi, you should iterate that list and use org.apache.commons.io.FileUtils.copyInputStreamToFile(InputStream source, File destination) and each add file to another collection. If you use Guava, check Lists.transform(...), which you can use instad of iteration (depends on how you need to initialize each File instance) – mefi Mar 27 '15 at 12:11
10

Some time saver for the code where you save the attachment file :

with javax mail version 1.4 and after , you can say

// SECURITY LEAK - do not do this! Do not trust the 'getFileName' input. Imagine it is: "../etc/passwd", for example.
// bodyPart.saveFile("/tmp/" + bodyPart.getFileName());

instead of

    InputStream is = bodyPart.getInputStream();
    File f = new File("/tmp/" + bodyPart.getFileName());
    FileOutputStream fos = new FileOutputStream(f);
    byte[] buf = new byte[4096];
    int bytesRead;
    while((bytesRead = is.read(buf))!=-1) {
        fos.write(buf, 0, bytesRead);
    }
    fos.close();
rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
kommradHomer
  • 4,127
  • 5
  • 51
  • 68
  • 4
    [Apparently](http://docs.oracle.com/javaee/6/api/javax/mail/internet/MimeBodyPart.html#saveFile(java.lang.String)) `bodyPart` should be first converted to `MimeBodyPart`, such like: `((MimeBodyPart) bodyPart).saveFile("/tmp/" + bodyPart.getFileName());` – yair Mar 09 '15 at 10:05
6

You can simply use Apache Commons Mail API MimeMessageParser - getAttachmentList() along Commons IO and Commons Lang.

MimeMessageParser parser = ....
parser.parse();
for(DataSource dataSource : parser.getAttachmentList()) {

    if (StringUtils.isNotBlank(dataSource.getName())) {}

        //use apache commons IOUtils to save attachments
        IOUtils.copy(dataSource.getInputStream(), ..dataSource.getName()...)
    } else {
        //handle how you would want attachments without file names
        //ex. mails within emails have no file name
    }
}
moe
  • 1,716
  • 1
  • 14
  • 30
Stackee007
  • 3,196
  • 1
  • 26
  • 39
0

Returns list of body parts with attachments.

@Throws(Exception::class)
    fun getAttachments(message: Message): List<BodyPart>{
        val content = message.content
        if (content is String) return ArrayList<BodyPart>()
        if (content is Multipart) {
            val result: MutableList<BodyPart> = ArrayList<BodyPart>()
            for (i in 0 until content.count) {
                result.addAll(getAttachments(content.getBodyPart(i)))
            }
            return result
        }
        return ArrayList<BodyPart>()
    }

    @Throws(Exception::class)
    private fun getAttachments(part: BodyPart): List<BodyPart> {
        val result: MutableList<BodyPart> = ArrayList<BodyPart>()

        if (Part.ATTACHMENT == part.disposition && !part.fileName.isNullOrBlank()){
            result.add(part)
        }

        val content = part.content
        if (content is Multipart) {
            for (i in 0 until (content ).count) {
                val bodyPart = content.getBodyPart(i)
                result.addAll(getAttachments(bodyPart)!!)
            }
        }
        return result
    }
andermirik
  • 40
  • 4