JavaMail: get size of a MimeMessage - java

I'm trying to get the size of a MimeMessage.
The method getSize() simply always returns -1.
This is my code:
MimeMessage m = new MimeMessage(session);
m.setFrom(new InternetAddress(fromAddress, true));
m.setRecipient(RecipientType.TO, new InternetAddress(toAddress, true));
m.setSubject(subject);
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(body, "text/html");
Multipart mp = new MimeMultipart();
mp.addBodyPart(bodyPart);
m.setContent(mp);
m.getSize(); // -1 is returned
THIS IS THE ANSWER TO MY QUESTION:
ByteArrayOutputStream os = new ByteArrayOutputStream();
m.writeTo(os);
int bytes = os.size();

A more efficient solution, but requiring an external library, is the following one:
public static long getReliableSize(MimeMessage m) throws IOException, MessagingException {
try (CountingOutputStream out = new CountingOutputStream(new NullOutputStream())) {
m.writeTo(out);
return out.getByteCount();
}
}
Both CountingOutputStream and NullOutputStream are available in Apache Common IO. That solution doesn't require to work with a temporary byte buffer (write, allocate, re-allocate, etc.)

Solution provided with Apache Commons is good, but NullOutputStream() constructor is now deprecated. Use the singleton instead:
CountingOutputStream out = new CountingOutputStream(NullOutputStream.NULL_OUTPUT_STREAM);

try calling mp.getSize() to see what it returns, MIMEMessage calls it on mp only.
Also From MIME message API
Return the size of the content of
this part in bytes. Return -1 if the
size cannot be determined.
As of now you have not passed any contents to the message,that could be the reason on -1 return value.

Related

DataSource image becomes corrupted when sending a string in inline javax.mail

I am trying to send inline image in a mail through java.I have byte array so I converted byte array to string using below function.
public static String getImgString(byte[] fileImg) throws IOException {
String imageString = new String(fileImg,"UTF-8");
return imageString;
}
I got an string and this string I verified through converter it displayed and image which I used.
Now I attached my image to body of mail with below code.
byte[] arr = getImageFileBytes(); // I got byte[] from this function
DataSource dataSourceImage = new ByteArrayDataSource(getImgString(arr),"image/png"");
MimeBodyPart imageBodyPart = new MimeBodyPart();
imageBodyPart.setDataHandler(new DataHandler(dataSourceImage));
I am receiving email as below.
I think there is some format I am missing in DataSource conversion or I need to add extra
data:image/png;base64 to image string??
What changes I need to do to get an image that I have in String.
Thanks in advance.
If your image data is actually in a file, you should use the attachFile method:
MimeBodyPart mbp = new MimeBodyPart();
mbp.attachFile("file.png", "image/png", "base64");
If you only have the image data in memory, you need to do something like this:
MimeBodyPart mbp = new MimeBodyPart();
ByteArrayDataSource bds = new ByteArrayDataSource(getImageFileBytes(), "image/png");
mbp.setContent(new DataHandler(bds));
Of course, if you're referencing this image from a separate html part, you'll want to make sure both are wrapped in a multipart/related part.
More information is in the JavaMail FAQ.

Use Java Gmail API to send an email with multiple (large) attachments

I am trying to use the Google Gmail API (Java) to create an email that contains multiple attachments. Using the code below, I am able to send multiple attachments that are embedded within a MimeMessage if the attachments total less than 5MB (Google's threshold for simple file upload).
com.google.api.services.gmailGmail service = (... defined above ...)
javax.mail.internet.MimeMessage message = (... defined above with attachments embedded ...)
// Send the email
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
mimeMessage.writeTo(buffer);
byte[] bytes = buffer.toByteArray();
String encodedEmail = Base64.encodeBase64URLSafeString(bytes);
Message message = new Message();
message.setRaw(encodedEmail);
message = service.users().messages().send("me", message).execute();
However, I am unable to figure out how to correctly attach multiple files to an email using the Gmail Java API. The method below looks promising, but it appears to only accept 1 File/InputStream (mediaContent).
Gmail.Users.Messages.Send send(userId, Message content, AbstractInputStreamContent mediaContent)
Anyone know how to correctly implement a multi-file upload using the API?
As you correctly stated, the maximum attachment size for Simple file upload is 5 MB
Conclusion:
You need to to use Multipart upload or Resumable upload.
A sample sending an email with a multipart upload:
public static MimeMessage createEmailWithAttachment(String to, String from, String subject,
String bodyText,String filePath) throws MessagingException{
File file = new File(filePath);
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
MimeMessage email = new MimeMessage(session);
Multipart multipart = new MimeMultipart();
InternetAddress tAddress = new InternetAddress(to);
InternetAddress fAddress = new InternetAddress(from);
email.setFrom(fAddress);
email.addRecipient(javax.mail.Message.RecipientType.TO, tAddress);
email.setSubject(subject);
if (file.exists()) {
source = new FileDataSource(filePath);
messageFilePart = new MimeBodyPart();
messageBodyPart = new MimeBodyPart();
try {
messageBodyPart.setText(bodyText);
messageFilePart.setDataHandler(new DataHandler(source));
messageFilePart.setFileName(file.getName());
multipart.addBodyPart(messageBodyPart);
multipart.addBodyPart(messageFilePart);
email.setContent(multipart);
} catch (MessagingException e) {
e.printStackTrace();
}
}else
email.setText(bodyText);
return email;
}
Here you can find many other useful samples for sending emails with the Gmail API in Java.
It turns out that my MimeMessage was generated correctly, however, if the attachments included in the MimeMessage are larger than 5MB, you need to use a different Gmail API send() method. The API docs are incredibly confusing because they appear to state that you need to make multiple calls to rest endpoints to upload multiple files. It turns out that the Gmail Java Api does all the for you based off the MimeMessage submitted.
Below is a code snippet that shows how to use the two methods: "simple upload" and "multipart upload".
com.google.api.services.gmailGmail service = (... defined above ...)
javax.mail.internet.MimeMessage message = (... defined above with attachments embedded ...)
/**
* Send email using Gmail API - dynamically uses simple or multipart send depending on attachments size
*
* #param mimeMessage MimeMessage (includes any attachments for the email)
* #param attachments the Set of files that were included in the MimeMessage (if any). Only used to calculate total size to see if we should use "simple" send or need to use multipart upload.
*/
void send(MimeMessage mimeMessage, #Nullable Set<File> attachments) throws Exception {
Message message = new Message();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
mimeMessage.writeTo(buffer);
// See if we need to use multipart upload
if (attachments!=null && computeTotalSizeOfAttachments(attachments) > BYTES_5MB) {
ByteArrayContent content = new ByteArrayContent("message/rfc822", buffer.toByteArray());
message = service.users().messages().send("me", null, content).execute();
// Otherwise, use "simple" send
} else {
String encodedEmail = Base64.encodeBase64URLSafeString(buffer.toByteArray());
message.setRaw(encodedEmail);
message = service.users().messages().send("me", message).execute();
}
System.out.println("Gmail Message: " + message.toPrettyString());
}

How to copy all the content of an Internet mime message to a body item in Notes?

I need to copy the entire body content of an Internet mail to a new Notes document.
Object internetMsgBody = internetMsg.getContent();
MIMEEntity notesBodyItem = notesDocument.createMIMEEntity("Body");
Stream mimeStream = dominoSession.createStream();
...?...
notesBodyItem.setContentFromBytes(mimeStream, msgContentType,
MIMEEntity.ENC_NONE);
The internetMsgBody can be String, MimeMultiPart or InputStream (according to the documentation). I know how I can handle String :) but for the other Object types, I need some help. There is no need for any MIME or Parts content type handling.
Thanks!
In Notes, a multi-part MIME message is represented as a set of items of the same name (usually Body) each of which is TYPE_MIME. Think of a MIMEEntity instance as corresponding to a single one of these items. In other words, a MIMEEntity represents a single MIME part.
So if your input is a multi-part MIME message, you may have to parse the message into individual parts and create a MIMEEntity for each. Unfortunately, the Java back-end classes don't include a MIME parser. Of course, your question states that internetMsg.getContent() may return a MimeMultipart. If that's the case, it sounds like the MIME is already parsed for you.
Either way -- whether your input is a stream of many parts or a MimeMultipart -- I suggest you look at MimeMessageParser.java from the XPages Extension Library. It uses mime4j to parse an input stream. If there are multiple parts in the stream, it uses MIMEEntity to write each part as a separate item. Although the use of mime4j doesn't sound relevant, you may find some useful hints in that code. It implements a very similar use case.
Thanks Dave, you pointed me to the missing part. My solution (so far) is to create a parent Domino MIMEEntity which holds all MIME Parts as children.
Code excerpt:
MimeMultipart mimeMultiparts = (MimeMultipart) message.getContent();
int partCount = mimeMultiparts.getCount();
MIMEEntity dominoParentItem = mailDocument.createMIMEEntity("Body");
Stream dominoStream = dbGetSession().createStream();
for (int counter = 0; counter < partCount; counter++) {
MimeBodyPart mimeBodyPart = (MimeBodyPart) mimeMultiparts.getBodyPart(counter);
MIMEEntity dominoChildItem = dominoParentItem.createChildEntity();
InputStream input = mimeBodyPart.getRawInputStream();
byte[] buffer = new byte[4096];
int lengthTotal = 0;
int length = 0;
while (true) {
length = input.read(buffer);
if (length < 1)
break;
lengthTotal += length;
dominoStream.write(buffer);
}
String encodingType = mimeBodyPart.getEncoding();
int dominoEncoding = MIMEEntity.ENC_NONE;
if (encodingType != null) {
if (encodingType.toLowerCase().contains("base64"))
dominoEncoding = MIMEEntity.ENC_BASE64;
if (encodingType.toLowerCase().contains("7bit"))
dominoEncoding = MIMEEntity.ENC_IDENTITY_7BIT;
if (encodingType.toLowerCase().contains("8bit"))
dominoEncoding = MIMEEntity.ENC_IDENTITY_8BIT;
if (encodingType.toLowerCase().contains("binary"))
dominoEncoding = MIMEEntity.ENC_IDENTITY_BINARY;
if (encodingType.toLowerCase().contains("quoted-printable"))
dominoEncoding = MIMEEntity.ENC_QUOTED_PRINTABLE;
if (dominoEncoding == MIMEEntity.ENC_NONE)
dominoEncoding = MIMEEntity.ENC_EXTENSION;
}
dominoChildItem.setContentFromBytes(dominoStream, mimeBodyPart.getContentType(), dominoEncoding);
Just to follow up on this problem:
I came up with a much easier and more elegant solution. There is a writeTo() method which streams-out all the multipart data. This can then be streamed in again to a Domino stream which fills in the MIMEEntry body item.
case "javax.mail.internet.MimeMultipart": {
// Create input stream with content of MIME data
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
message.writeTo(outputStream);
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
outputStream.close();
// Create Domino stream and fill it with the MIME data
Stream dominoStream = dbGetSession().createStream();
byte[] buffer = new byte[4096];
int lengthTotal = 0;
int length = 0;
while (true) {
length = inputStream.read(buffer);
if (length < 1)
break;
lengthTotal += length;
dominoStream.write(buffer);
}
inputStream.close();
// Create Domino MIME "Body" item with content of MIME data
MIMEEntity dominoMIMEItem = mailDocument.createMIMEEntity("Body");
dominoMIMEItem.setContentFromBytes(dominoStream, "", MIMEEntity.ENC_NONE);
}

Control what DataContentHandler to use for a MimeMessage attachment?

I am creating an attachment to MimeMessage for a Tiff image with a byte array.
ByteArrayOutputStream out = new ByteArrayOutputStream();
MimeBodyPart body = new MimeBodyPart();
body.setContent(tiffByteArray, "image/tiff");
body.setDisposition("attachment");
body.setFileName(filename);
MimeMultipart multipart = new MimeMultipart();
multipart.addBodyPart(body);
MimeMessage message = new MimeMessage(Session.getDefaultInstance(System.getProperties()));
message.setContent(multipart);
message.writeTo(out);
String mimeContent = out.toString();
This normally works. The image is converted to a base64 string in the message. However, at some point something on the system occurs and this piece of code starts using com.sun.xml.internal.messaging.saaj.soap.ImageDataContentHandler. This particular converted expects an java.awt.Image object as opposed to a byte array (relevant source). I get the following error:
Unable to encode the image to a stream ImageDataContentHandler requires Image object, was given object of type class [B
I can see that you can set the javax.activation.DataHandler on the javax.mail.internet.MimeMessage and in the DataHandler you can set the javax.activation.DataContentHandlerFactory, but I'm not sure what to set it to.
Is there a way to force a byte array to be converted to a base64 encoded String regardless of the mime type?
javax.mail provides a DataSource implementation for bytes that you can explicitly use.
ByteArrayDataSource dataSource = new ByteArrayDataSource(tiffByteArray, "image/tiff");
DataHandler byteDataHandler = new DataHandler(dataSource);
body.setDataHandler(byteDataHandler);
body.setDisposition("attachment");
body.setFileName(filename);

Send e-mail in Pdf attachment as stream

I want to send a Pdf as an e-mail attachment (I am using the JavaMail API ). I have the Pdf (generated by jasper) as an byte[].
public InputStream exportPdfToInputStream(User user) throws ParseErrorException, MethodInvocationException, ResourceNotFoundException, JRException, IOException{
JasperPrint jasperPrint = createJasperPrintObject(user);
byte[] pdfByteArray = JasperExportManager.exportReportToPdf(jasperPrint);
return new ByteArrayInputStream(pdfByteArray);
}
Here is the code that I am using to construct the MimeBodyPart that will be the attachment:
if (arrayInputStream != null && arrayInputStream instanceof ByteArrayInputStream) {
MimeBodyPart attachment = new MimeBodyPart(arrayInputStream);
attachment.setHeader("Content-Type", "application/pdf");
mimeMultipart.addBodyPart(attachment);
}
This code gives me this error:
javax.mail.MessagingException: IOException while sending message;
nested exception is:
java.io.IOException: Error in encoded stream: needed at least 2 valid base64 characters, but only got 1 before padding character (=), the 10 most recent characters were: "\24\163\193\n\185\194\216#\208="
I have found a solution as suggested in this thread. It seems that there is a DataSource class created just for this purpose. Hope this example will help others also.
if (arrayInputStream != null && arrayInputStream instanceof ByteArrayInputStream) {
// create the second message part with the attachment from a OutputStrean
MimeBodyPart attachment= new MimeBodyPart();
ByteArrayDataSource ds = new ByteArrayDataSource(arrayInputStream, "application/pdf");
attachment.setDataHandler(new DataHandler(ds));
attachment.setFileName("Report.pdf");
mimeMultipart.addBodyPart(attachment);
}
The constructor you used is for parsing a mime part from the transport.
Your second example should work out right. You may consider
not to convert to InputStream and back, this will make unnecessary copies
add a disposition ( e.g. bp.setDisposition(Part.ATTACHMENT); )

Categories