diff options
author | Stephan Merkli | 2018-08-17 12:33:57 +0000 |
---|---|---|
committer | Paolo Bazzi | 2018-08-24 07:04:05 +0000 |
commit | 4345f51cba3eb04cccc87b4d672c9956de1d93ae (patch) | |
tree | 7fadfebdb2dc5efc54e72fe686fddca6fa1ab565 /org.eclipse.scout.rt.mail | |
parent | cc46b6079a9712102210933b11ec835393c39536 (diff) | |
download | org.eclipse.scout.rt-4345f51cba3eb04cccc87b4d672c9956de1d93ae.tar.gz org.eclipse.scout.rt-4345f51cba3eb04cccc87b4d672c9956de1d93ae.tar.xz org.eclipse.scout.rt-4345f51cba3eb04cccc87b4d672c9956de1d93ae.zip |
MailHelper: add methods getHtmlBody, readContentAsString and
getAttachmentFilename
Improve code that extracts charset from part.
Change-Id: I06f9309787f290ccb2dfb16f4557ad990524ed39
Reviewed-on: https://git.eclipse.org/r/127926
Tested-by: CI Bot
Reviewed-by: Paolo Bazzi <paolo.bazzi@bsi-software.com>
Diffstat (limited to 'org.eclipse.scout.rt.mail')
-rw-r--r-- | org.eclipse.scout.rt.mail/src/main/java/org/eclipse/scout/rt/mail/MailHelper.java | 223 |
1 files changed, 168 insertions, 55 deletions
diff --git a/org.eclipse.scout.rt.mail/src/main/java/org/eclipse/scout/rt/mail/MailHelper.java b/org.eclipse.scout.rt.mail/src/main/java/org/eclipse/scout/rt/mail/MailHelper.java index b44e6e8b61..76cb2943f9 100644 --- a/org.eclipse.scout.rt.mail/src/main/java/org/eclipse/scout/rt/mail/MailHelper.java +++ b/org.eclipse.scout.rt.mail/src/main/java/org/eclipse/scout/rt/mail/MailHelper.java @@ -22,12 +22,12 @@ import java.net.IDN; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; +import java.text.Normalizer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.function.Function; import javax.activation.DataHandler; import javax.activation.DataSource; @@ -41,20 +41,24 @@ import javax.mail.Multipart; import javax.mail.Part; import javax.mail.Session; import javax.mail.internet.AddressException; +import javax.mail.internet.ContentType; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; -import javax.mail.internet.MimePart; import javax.mail.internet.MimeUtility; +import javax.mail.internet.ParseException; import javax.mail.util.ByteArrayDataSource; import org.eclipse.scout.rt.platform.ApplicationScoped; import org.eclipse.scout.rt.platform.exception.ProcessingException; import org.eclipse.scout.rt.platform.resource.BinaryResource; +import org.eclipse.scout.rt.platform.resource.MimeType; +import org.eclipse.scout.rt.platform.util.Assertions; import org.eclipse.scout.rt.platform.util.CollectionUtility; import org.eclipse.scout.rt.platform.util.FileUtility; import org.eclipse.scout.rt.platform.util.IOUtility; +import org.eclipse.scout.rt.platform.util.ObjectUtility; import org.eclipse.scout.rt.platform.util.StringUtility; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -89,8 +93,6 @@ public class MailHelper { public static final String CONTENT_TYPE_IMAGE_PREFIX = "image/"; public static final String CONTENT_TYPE_MULTIPART_PREFIX = "multipart/"; - private static final Pattern PATTERN_MIME_CONTENT_TYPE_CHARSET = Pattern.compile(".*charset=(\")([^\1\\s;]+)\\1", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); - public static final String HEADER_IN_REPLY_TO = "In-Reply-To"; /** @@ -224,17 +226,11 @@ public class MailHelper { * </p> */ protected void checkValidCharset(Part part) throws MessagingException { - String contentType = part.getContentType(); - if (contentType == null) { + String charset = getPartCharsetInternal(part); + if (charset == null) { return; } - Matcher matcher = PATTERN_MIME_CONTENT_TYPE_CHARSET.matcher(contentType); - if (!matcher.find()) { - return; - } - - String charset = matcher.group(2); try { Charset.forName(charset); return; @@ -249,6 +245,7 @@ public class MailHelper { } catch (NullPointerException e) { // NOSONAR LOG.info("Mail part seems to use an unsupported character set {}, use UTF-8 as fallback.", charset, e); + String contentType = part.getContentType(); // cannot be null because otherwise charset would have been null already part.setHeader(CONTENT_TYPE_ID, contentType.replace(charset, StandardCharsets.UTF_8.name())); } catch (IOException e) { @@ -258,36 +255,46 @@ public class MailHelper { } /** - * @param part - * @return the plainText part encoded with the encoding given in the MIME header or UTF-8 encoded or null if the - * plainText Part is not given + * @param message + * Message to look for html body part and read content from. + * @return Content from html body part encoded with the encoding given in the MIME header or UTF-8 encoded or null if + * the html part is not given. + */ + public String getHtmlBody(Part message) { + List<Part> bodyParts = getBodyParts(message); + Part htmlPart = getHtmlPart(bodyParts); + return readContentAsString(htmlPart); + } + + /** + * @param message + * Message to look for plain text body part and read content from. + * @return Content from plain text part encoded with the encoding given in the MIME header or UTF-8 encoded or null if + * the plainText Part is not given */ - public String getPlainText(Part part) { - String text = null; + public String getPlainText(Part message) { + List<Part> bodyParts = getBodyParts(message); + Part plainTextPart = getPlainTextPart(bodyParts); + return readContentAsString(plainTextPart); + } + + /** + * Reads the content of the part as string, with the encoding given in the MIME header or UTF-8 encoded. + */ + public String readContentAsString(Part part) { + if (part == null) { + return null; + } + try { - List<Part> bodyParts = getBodyParts(part); - Part plainTextPart = getPlainTextPart(bodyParts); - - if (plainTextPart instanceof MimePart) { - MimePart mimePart = (MimePart) plainTextPart; - try (InputStream in = mimePart.getInputStream()) { - if (in != null) { - byte[] content = IOUtility.readBytes(in); - try { // NOSONAR - text = new String(content, getCharacterEncodingOfPart(mimePart)); - } - catch (UnsupportedEncodingException e) { - LOG.warn("unsupported encoding", e); - text = new String(content); - } - } - } + Charset charset = ObjectUtility.nvl(getPartCharset(part), StandardCharsets.UTF_8); // default, a good guess + try (InputStream in = part.getInputStream()) { + return in == null ? null : IOUtility.readString(in, charset.name()); } } - catch (MessagingException | IOException e) { - throw new ProcessingException("Unexpected: ", e); + catch (IOException | MessagingException e) { + throw new ProcessingException("Failed to read content as string", e); } - return text; } public Part getHtmlPart(List<? extends Part> bodyParts) { @@ -697,25 +704,51 @@ public class MailHelper { * @return * @throws MessagingException */ + // TODO sme [9.0] mark deprecated in 9.0, use getPartCharset instead. public String getCharacterEncodingOfPart(Part part) throws MessagingException { - Pattern pattern = Pattern.compile("charset=\".*\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); - Matcher matcher = pattern.matcher(part.getContentType()); - String characterEncoding = StandardCharsets.UTF_8.name(); // default, a good guess in Europe - if (matcher.find()) { - if (matcher.group(0).split("\"").length >= 2) { - characterEncoding = matcher.group(0).split("\"")[1]; - } + return ObjectUtility.nvl(getPartCharset(part), StandardCharsets.UTF_8).name(); // default, a good guess in Europe + } + + /** + * Detects the charset of the given part. If none or and invalid charset is found, <code>null</code> is returned. + */ + @SuppressWarnings("squid:S1166") // catch of UnsupportedCharsetException without a rethrow + protected Charset getPartCharset(Part part) throws MessagingException { + String charset = getPartCharsetInternal(part); + + try { + return charset == null ? null : Charset.forName(charset); } - else { - final String charsetEquals = "charset="; - if (part.getContentType().contains(charsetEquals)) { - String[] contentTypeParts = part.getContentType().split(charsetEquals); - if (contentTypeParts.length == 2) { - characterEncoding = contentTypeParts[1]; - } - } + catch (UnsupportedCharsetException e) { + // ignore exception itself (use trace log) + LOG.trace("Part has an invalid charset '{}'", charset); + return null; + } + } + + /** + * @return Charset as string + */ + protected String getPartCharsetInternal(Part part) throws MessagingException { + if (part == null) { + return null; + } + + String contentType = part.getContentType(); + if (contentType == null) { + return null; + } + + String charset; + try { + charset = new ContentType(contentType).getParameter("charset"); + } + catch (ParseException e) { + LOG.trace("Failed to parse content type '{}'", contentType); + return null; } - return characterEncoding; + + return charset; } /** @@ -845,7 +878,7 @@ public class MailHelper { * message * @return Message-Id or <code>null</code> */ - protected String getMessageIdSafely(MimeMessage mimeMessage) { + public String getMessageIdSafely(MimeMessage mimeMessage) { if (mimeMessage == null) { return null; } @@ -858,4 +891,84 @@ public class MailHelper { return null; } } + + /** + * Returns the decoded and normalized filename from {@link Part#getFileName()} if available. Otherwise guesses the + * file extension via content type ({@link Part#getContentType()}) and calls the default filename function with the + * file extension (or <code>null</code> if none could be guessed). + * + * @param part + * Attachment part + * @param defaultFilenameFunction + * Mandatory function called with guessed file extension (might be null) if no filename was found. + * @return Filename of the given attachment part. + */ + public String getAttachmentFilename(Part part, Function<String, String> defaultFilenameFunction) { + Assertions.assertNotNull(part, "Part must not be null"); + Assertions.assertNotNull(defaultFilenameFunction, "Default filename function must not be null"); + try { + String filename = decodeAttachmentFilename(part.getFileName()); + if (filename != null) { + return filename; + } + + String fileExtension = guessAttachmentFileExtension(part.getContentType()); + return defaultFilenameFunction.apply(fileExtension); + } + catch (MessagingException e) { + LOG.warn("Failed to get attachment filename", e); + return null; + } + } + + /** + * Decodes an attachment filename. + * <p> + * Used internal by {@link #getAttachmentFilename(Part)}. + * + * @param filename + * Filename as provided by {@link Part#getFileName()}. + */ + protected String decodeAttachmentFilename(String filename) { + if (filename == null) { + return null; + } + + try { + String decoded = MimeUtility.decodeText(filename); + return Normalizer.normalize(decoded, Normalizer.Form.NFC); + } + catch (Exception e) { + LOG.warn("Failed to clean attachment filename", e); + return filename; + } + } + + /** + * Guesses attachment file extension based on a content type. + * <p> + * Used internal by {@link #getAttachmentFilename(Part)} if not filename is available. + * + * @param contentType + * Content type as provided by {@link Part#getContentType()}. + * @return File extension for content type (e.g. txt, eml, ...) if one is found, <code>null</code> otherwise. + */ + protected String guessAttachmentFileExtension(String contentType) { + if (contentType == null) { + return null; + } + + ContentType ct; + try { + ct = new ContentType(contentType); + } + catch (ParseException e) { + LOG.warn("Failed to parse content type '{}'", contentType); + return null; + } + + String baseType = ct.getBaseType(); + MimeType mimeType = MimeType.convertToMimeType(baseType); + return mimeType == null ? null : mimeType.getFileExtension(); + } } |