3434import java .util .TreeMap ;
3535
3636import static java .lang .String .format ;
37+ import static org .simplejavamail .internal .util .MiscUtil .extractCID ;
3738import static org .simplejavamail .internal .util .MiscUtil .valueNullOrEmpty ;
3839
3940/**
4243 * @version current: MimeMessageParser.java 2016-02-25 Benny Bottema
4344 */
4445public final class MimeMessageParser {
45-
46+
4647 /**
4748 * Contains the headers we will ignore, because either we set the information differently (such as Subject) or we recognize the header as
4849 * interfering or obsolete for new emails).
@@ -107,16 +108,17 @@ public static ParsedMimeMessageComponents parseMimeMessage(@Nonnull final MimeMe
107108 parsedComponents .fromAddress = parseFromAddress (mimeMessage );
108109 parsedComponents .replyToAddresses = parseReplyToAddresses (mimeMessage );
109110 parseMimePartTree (mimeMessage , parsedComponents );
111+ moveInvalidEmbeddedResourcesToAttachments (parsedComponents );
110112 return parsedComponents ;
111113 }
112-
114+
113115 private static void parseMimePartTree (@ Nonnull final MimePart currentPart , @ Nonnull final ParsedMimeMessageComponents parsedComponents ) {
114116 for (final Header header : retrieveAllHeaders (currentPart )) {
115117 parseHeader (header , parsedComponents );
116118 }
117-
119+
118120 final String disposition = parseDisposition (currentPart );
119-
121+
120122 if (isMimeType (currentPart , "text/plain" ) && parsedComponents .plainContent == null && !Part .ATTACHMENT .equalsIgnoreCase (disposition )) {
121123 parsedComponents .plainContent = parseContent (currentPart );
122124 } else if (isMimeType (currentPart , "text/html" ) && parsedComponents .htmlContent == null && !Part .ATTACHMENT .equalsIgnoreCase (disposition )) {
@@ -128,10 +130,10 @@ private static void parseMimePartTree(@Nonnull final MimePart currentPart, @Nonn
128130 }
129131 } else {
130132 final DataSource ds = createDataSource (currentPart );
131- // If the diposition is not provided, the part should be treated as attachment
132- if (disposition == null || Part .ATTACHMENT .equalsIgnoreCase (disposition )) {
133+ // if the diposition is not provided, for now the part should be treated as inline (later non-embedded inline attachments are moved)
134+ if (Part .ATTACHMENT .equalsIgnoreCase (disposition )) {
133135 parsedComponents .attachmentList .put (parseResourceName (parseContentID (currentPart ), parseFileName (currentPart )), ds );
134- } else if (Part .INLINE .equalsIgnoreCase (disposition )) {
136+ } else if (disposition == null || Part .INLINE .equalsIgnoreCase (disposition )) {
135137 if (parseContentID (currentPart ) != null ) {
136138 parsedComponents .cidMap .put (parseContentID (currentPart ), ds );
137139 } else {
@@ -143,7 +145,7 @@ private static void parseMimePartTree(@Nonnull final MimePart currentPart, @Nonn
143145 }
144146 }
145147 }
146-
148+
147149 @ SuppressWarnings ("StatementWithEmptyBody" )
148150 private static void parseHeader (final Header header , @ Nonnull final ParsedMimeMessageComponents parsedComponents ) {
149151 if (header .getName ().equals ("Disposition-Notification-To" )) {
@@ -158,7 +160,7 @@ private static void parseHeader(final Header header, @Nonnull final ParsedMimeMe
158160 // header recognized, but not relevant (see #HEADERS_TO_IGNORE)
159161 }
160162 }
161-
163+
162164 @ SuppressWarnings ("WeakerAccess" )
163165 public static String parseFileName (@ Nonnull final Part currentPart ) {
164166 try {
@@ -167,7 +169,7 @@ public static String parseFileName(@Nonnull final Part currentPart) {
167169 throw new MimeMessageParseException (MimeMessageParseException .ERROR_GETTING_FILENAME , e );
168170 }
169171 }
170-
172+
171173 @ SuppressWarnings ("WeakerAccess" )
172174 @ Nullable
173175 public static String parseContentID (@ Nonnull final MimePart currentPart ) {
@@ -177,7 +179,7 @@ public static String parseContentID(@Nonnull final MimePart currentPart) {
177179 throw new MimeMessageParseException (MimeMessageParseException .ERROR_GETTING_CONTENT_ID , e );
178180 }
179181 }
180-
182+
181183 @ SuppressWarnings ("WeakerAccess" )
182184 public static MimeBodyPart getBodyPartAtIndex (final Multipart parentMultiPart , final int index ) {
183185 try {
@@ -186,7 +188,7 @@ public static MimeBodyPart getBodyPartAtIndex(final Multipart parentMultiPart, f
186188 throw new MimeMessageParseException (format (MimeMessageParseException .ERROR_GETTING_BODYPART_AT_INDEX , index ), e );
187189 }
188190 }
189-
191+
190192 @ SuppressWarnings ("WeakerAccess" )
191193 public static int countBodyParts (final Multipart mp ) {
192194 try {
@@ -195,7 +197,7 @@ public static int countBodyParts(final Multipart mp) {
195197 throw new MimeMessageParseException (MimeMessageParseException .ERROR_PARSING_MULTIPART_COUNT , e );
196198 }
197199 }
198-
200+
199201 @ SuppressWarnings ("WeakerAccess" )
200202 public static <T > T parseContent (@ Nonnull final MimePart currentPart ) {
201203 try {
@@ -205,7 +207,7 @@ public static <T> T parseContent(@Nonnull final MimePart currentPart) {
205207 throw new MimeMessageParseException (MimeMessageParseException .ERROR_PARSING_CONTENT , e );
206208 }
207209 }
208-
210+
209211 @ SuppressWarnings ("WeakerAccess" )
210212 public static String parseDisposition (@ Nonnull final MimePart currentPart ) {
211213 try {
@@ -214,7 +216,7 @@ public static String parseDisposition(@Nonnull final MimePart currentPart) {
214216 throw new MimeMessageParseException (MimeMessageParseException .ERROR_PARSING_DISPOSITION , e );
215217 }
216218 }
217-
219+
218220 @ Nonnull
219221 private static String parseResourceName (@ Nullable final String possibleWrappedContentID , @ Nonnull final String fileName ) {
220222 if (!valueNullOrEmpty (possibleWrappedContentID )) {
@@ -228,7 +230,7 @@ private static String parseResourceName(@Nullable final String possibleWrappedCo
228230 return fileName ;
229231 }
230232 }
231-
233+
232234 @ SuppressWarnings ("WeakerAccess" )
233235 @ Nonnull
234236 public static List <Header > retrieveAllHeaders (@ Nonnull final MimePart part ) {
@@ -238,7 +240,7 @@ public static List<Header> retrieveAllHeaders(@Nonnull final MimePart part) {
238240 throw new MimeMessageParseException (MimeMessageParseException .ERROR_GETTING_ALL_HEADERS , e );
239241 }
240242 }
241-
243+
242244 @ Nonnull
243245 private static InternetAddress createAddress (final Header header , final String typeOfAddress ) {
244246 try {
@@ -247,7 +249,7 @@ private static InternetAddress createAddress(final Header header, final String t
247249 throw new MimeMessageParseException (format (MimeMessageParseException .ERROR_PARSING_ADDRESS , typeOfAddress ), e );
248250 }
249251 }
250-
252+
251253 /**
252254 * Checks whether the MimePart contains an object of the given mime type.
253255 *
@@ -267,7 +269,7 @@ public static boolean isMimeType(@Nonnull final MimePart part, @Nonnull final St
267269 return retrieveContentType (part ).equalsIgnoreCase (mimeType );
268270 }
269271 }
270-
272+
271273 @ SuppressWarnings ("WeakerAccess" )
272274 public static String retrieveContentType (@ Nonnull final MimePart part ) {
273275 try {
@@ -276,7 +278,7 @@ public static String retrieveContentType(@Nonnull final MimePart part) {
276278 throw new MimeMessageParseException (MimeMessageParseException .ERROR_GETTING_CONTENT_TYPE , e );
277279 }
278280 }
279-
281+
280282 @ SuppressWarnings ("WeakerAccess" )
281283 public static DataHandler retrieveDataHandler (@ Nonnull final MimePart part ) {
282284 try {
@@ -285,7 +287,7 @@ public static DataHandler retrieveDataHandler(@Nonnull final MimePart part) {
285287 throw new MimeMessageParseException (MimeMessageParseException .ERROR_GETTING_DATAHANDLER , e );
286288 }
287289 }
288-
290+
289291 /**
290292 * Parses the MimePart to create a DataSource.
291293 *
@@ -304,7 +306,7 @@ private static DataSource createDataSource(@Nonnull final MimePart part) {
304306 result .setName (dataSourceName );
305307 return result ;
306308 }
307-
309+
308310 @ SuppressWarnings ("WeakerAccess" )
309311 public static InputStream retrieveInputStream (final DataSource dataSource ) {
310312 try {
@@ -313,13 +315,13 @@ public static InputStream retrieveInputStream(final DataSource dataSource) {
313315 throw new MimeMessageParseException (MimeMessageParseException .ERROR_GETTING_INPUTSTREAM , e );
314316 }
315317 }
316-
318+
317319 @ Nullable
318320 private static String parseDataSourceName (@ Nonnull final Part part , @ Nonnull final DataSource dataSource ) {
319321 final String result = !valueNullOrEmpty (dataSource .getName ()) ? dataSource .getName () : parseFileName (part );
320322 return !valueNullOrEmpty (result ) ? decodeText (result ) : null ;
321323 }
322-
324+
323325 @ Nonnull
324326 private static String decodeText (@ Nonnull final String result ) {
325327 try {
@@ -328,13 +330,13 @@ private static String decodeText(@Nonnull final String result) {
328330 throw new MimeMessageParseException (MimeMessageParseException .ERROR_DECODING_TEXT , e );
329331 }
330332 }
331-
333+
332334 @ Nonnull
333335 private static byte [] readContent (@ Nonnull final InputStream is ) {
334336 final BufferedInputStream isReader = new BufferedInputStream (is );
335337 final ByteArrayOutputStream os = new ByteArrayOutputStream ();
336338 final BufferedOutputStream osWriter = new BufferedOutputStream (os );
337-
339+
338340 int ch ;
339341 try {
340342 while ((ch = isReader .read ()) != -1 ) {
@@ -361,26 +363,26 @@ private static String parseBaseMimeType(@Nonnull final String fullMimeType) {
361363 }
362364 return fullMimeType ;
363365 }
364-
365-
366+
367+
366368 @ SuppressWarnings ("WeakerAccess" )
367369 @ Nonnull
368370 public static List <InternetAddress > parseToAddresses (@ Nonnull final MimeMessage mimeMessage ) {
369371 return parseInternetAddresses (retrieveRecipients (mimeMessage , RecipientType .TO ));
370372 }
371-
373+
372374 @ SuppressWarnings ("WeakerAccess" )
373375 @ Nonnull
374376 public static List <InternetAddress > parseCcAddresses (@ Nonnull final MimeMessage mimeMessage ) {
375377 return parseInternetAddresses (retrieveRecipients (mimeMessage , RecipientType .CC ));
376378 }
377-
379+
378380 @ SuppressWarnings ("WeakerAccess" )
379381 @ Nonnull
380382 public static List <InternetAddress > parseBccAddresses (@ Nonnull final MimeMessage mimeMessage ) {
381383 return parseInternetAddresses (retrieveRecipients (mimeMessage , RecipientType .BCC ));
382384 }
383-
385+
384386 @ SuppressWarnings ("WeakerAccess" )
385387 @ Nullable
386388 public static Address [] retrieveRecipients (@ Nonnull final MimeMessage mimeMessage , final RecipientType recipientType ) {
@@ -390,7 +392,7 @@ public static Address[] retrieveRecipients(@Nonnull final MimeMessage mimeMessag
390392 throw new MimeMessageParseException (format (MimeMessageParseException .ERROR_GETTING_RECIPIENTS , recipientType ), e );
391393 }
392394 }
393-
395+
394396 @ Nonnull
395397 private static List <InternetAddress > parseInternetAddresses (@ Nullable final Address [] recipients ) {
396398 final List <Address > addresses = (recipients != null ) ? Arrays .asList (recipients ) : new ArrayList <Address >();
@@ -402,7 +404,7 @@ private static List<InternetAddress> parseInternetAddresses(@Nullable final Addr
402404 }
403405 return mailAddresses ;
404406 }
405-
407+
406408 @ SuppressWarnings ("WeakerAccess" )
407409 @ Nullable
408410 public static InternetAddress parseFromAddress (@ Nonnull final MimeMessage mimeMessage ) {
@@ -413,7 +415,7 @@ public static InternetAddress parseFromAddress(@Nonnull final MimeMessage mimeMe
413415 throw new MimeMessageParseException (MimeMessageParseException .ERROR_PARSING_FROMADDRESS , e );
414416 }
415417 }
416-
418+
417419 @ SuppressWarnings ("WeakerAccess" )
418420 @ Nullable
419421 public static InternetAddress parseReplyToAddresses (@ Nonnull final MimeMessage mimeMessage ) {
@@ -424,7 +426,7 @@ public static InternetAddress parseReplyToAddresses(@Nonnull final MimeMessage m
424426 throw new MimeMessageParseException (MimeMessageParseException .ERROR_PARSING_REPLY_TO_ADDRESSES , e );
425427 }
426428 }
427-
429+
428430 @ Nullable
429431 public static String parseSubject (@ Nonnull final MimeMessage mimeMessage ) {
430432 try {
@@ -433,8 +435,8 @@ public static String parseSubject(@Nonnull final MimeMessage mimeMessage) {
433435 throw new MimeMessageParseException (MimeMessageParseException .ERROR_GETTING_SUBJECT , e );
434436 }
435437 }
436-
437-
438+
439+
438440 @ SuppressWarnings ("WeakerAccess" )
439441 @ Nullable
440442 public static String parseMessageId (@ Nonnull final MimeMessage mimeMessage ) {
@@ -444,7 +446,17 @@ public static String parseMessageId(@Nonnull final MimeMessage mimeMessage) {
444446 throw new MimeMessageParseException (MimeMessageParseException .ERROR_GETTING_MESSAGE_ID , e );
445447 }
446448 }
447-
449+
450+ private static void moveInvalidEmbeddedResourcesToAttachments (ParsedMimeMessageComponents parsedComponents ) {
451+ final String htmlContent = parsedComponents .htmlContent .toString ();
452+ for (Map .Entry <String , DataSource > cidEntry : parsedComponents .cidMap .entrySet ()) {
453+ if (!htmlContent .contains ("cid:" + extractCID (cidEntry .getKey ()))) {
454+ parsedComponents .attachmentList .put (cidEntry .getKey (), cidEntry .getValue ());
455+ parsedComponents .cidMap .remove (cidEntry .getKey ());
456+ }
457+ }
458+ }
459+
448460 public static class ParsedMimeMessageComponents {
449461 private final Map <String , DataSource > attachmentList = new TreeMap <>();
450462 private final Map <String , DataSource > cidMap = new TreeMap <>();
@@ -461,63 +473,63 @@ public static class ParsedMimeMessageComponents {
461473 private InternetAddress bounceToAddress ;
462474 private String plainContent ;
463475 private String htmlContent ;
464-
476+
465477 public String getMessageId () {
466478 return messageId ;
467479 }
468-
480+
469481 public Map <String , DataSource > getAttachmentList () {
470482 return attachmentList ;
471483 }
472-
484+
473485 public Map <String , DataSource > getCidMap () {
474486 return cidMap ;
475487 }
476-
488+
477489 public Map <String , Object > getHeaders () {
478490 return headers ;
479491 }
480-
492+
481493 public List <InternetAddress > getToAddresses () {
482494 return toAddresses ;
483495 }
484-
496+
485497 public List <InternetAddress > getCcAddresses () {
486498 return ccAddresses ;
487499 }
488-
500+
489501 public List <InternetAddress > getBccAddresses () {
490502 return bccAddresses ;
491503 }
492-
504+
493505 public String getSubject () {
494506 return subject ;
495507 }
496-
508+
497509 public InternetAddress getFromAddress () {
498510 return fromAddress ;
499511 }
500-
512+
501513 public InternetAddress getReplyToAddresses () {
502514 return replyToAddresses ;
503515 }
504-
516+
505517 public InternetAddress getDispositionNotificationTo () {
506518 return dispositionNotificationTo ;
507519 }
508-
520+
509521 public InternetAddress getReturnReceiptTo () {
510522 return returnReceiptTo ;
511523 }
512-
524+
513525 public InternetAddress getBounceToAddress () {
514526 return bounceToAddress ;
515527 }
516-
528+
517529 public String getPlainContent () {
518530 return plainContent ;
519531 }
520-
532+
521533 public String getHtmlContent () {
522534 return htmlContent ;
523535 }
0 commit comments