Skip to content

Commit 8f39f04

Browse files
committed
#427: Added support for maximum email size validation with a possible EmailTooBig exception as result (as cause)
1 parent 93a2125 commit 8f39f04

File tree

11 files changed

+180
-13
lines changed

11 files changed

+180
-13
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.simplejavamail.api.mailer;
2+
3+
import static java.lang.String.format;
4+
5+
/**
6+
* Thrown when an email (as MimeMessage) is bigger than the maximum allowed size.
7+
*
8+
* @see MailerGenericBuilder#withMaximumEmailSize(int)
9+
*/
10+
public class EmailTooBigException extends RuntimeException {
11+
public EmailTooBigException(final long emailSize, final long maximumEmailSize) {
12+
super(format("Email size of %s bytes exceeds maximum allowed size of %s bytes", emailSize, maximumEmailSize));
13+
}
14+
}

modules/core-module/src/main/java/org/simplejavamail/api/mailer/MailerGenericBuilder.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,14 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
260260
*/
261261
T withEmailOverrides(@NotNull Email emailoverrides);
262262

263+
/**
264+
* Sets a maximum size for emails (as MimeMessage) in bytes. If an email exceeds this size, exception @{@link EmailTooBigException} will be thrown (as the cause).
265+
*
266+
* @param maximumEmailSize Maximum size of an email (as MimeMessage) in bytes.
267+
* @see #clearMaximumEmailSize()
268+
*/
269+
T withMaximumEmailSize(int maximumEmailSize);
270+
263271
/**
264272
* Signs this <em>all emails by default</em> with an <a href="https://tools.ietf.org/html/rfc5751">S/MIME</a> signature, so the receiving client
265273
* can verify whether the email content was tampered with.
@@ -685,6 +693,13 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
685693
*/
686694
T clearEmailOverrides();
687695

696+
/**
697+
* Makes the maximum email size <code>null</code>, meaning no size check will be performed.
698+
*
699+
* @see #withMaximumEmailSize(int)
700+
*/
701+
T clearMaximumEmailSize();
702+
688703
/**
689704
* Removes S/MIME signing, so emails won't be signed by default.
690705
*
@@ -780,6 +795,12 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
780795
@Nullable
781796
Email getEmailOverrides();
782797

798+
/**
799+
* @see #withMaximumEmailSize(int)
800+
*/
801+
@Nullable
802+
Integer getMaximumEmailSize();
803+
783804
/**
784805
* @see #signByDefaultWithSmime(Pkcs12Config)
785806
* @see #signByDefaultWithSmime(InputStream, String, String, String)

modules/core-module/src/main/java/org/simplejavamail/api/mailer/config/EmailGovernance.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
@Getter()
2727
public class EmailGovernance {
2828

29-
public static final EmailGovernance NO_GOVERNANCE = new EmailGovernance(null, null, null, null);
29+
public static final EmailGovernance NO_GOVERNANCE = new EmailGovernance(null, null, null, null, null);
3030

3131
/**
3232
* The effective email validator used for email validation. Can be <code>null</code> if no validation should be done.
@@ -56,4 +56,10 @@ public class EmailGovernance {
5656
* @see MailerGenericBuilder#withEmailOverrides(Email)
5757
*/
5858
@Nullable private final Email emailOverrides;
59+
60+
/**
61+
* Determines at what size Simple Java Mail should reject a MimeMessage. Useful if you know your SMTP server has a limit.
62+
* @see MailerGenericBuilder#withMaximumEmailSize(int)
63+
*/
64+
@Nullable private final Integer maximumEmailSize;
5965
}

modules/simple-java-mail/src/main/java/org/simplejavamail/converter/EmailConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ public static MimeMessage emailToMimeMessage(@NotNull final Email email, @NotNul
447447
try {
448448
return MimeMessageProducerHelper.produceMimeMessage(
449449
checkNonEmptyArgument(email, "email"),
450-
new EmailGovernance(null, checkNonEmptyArgument(defaultSmimeSigningStore, "defaultSmimeSigningStore"), null, null),
450+
new EmailGovernance(null, checkNonEmptyArgument(defaultSmimeSigningStore, "defaultSmimeSigningStore"), null, null, null),
451451
checkNonEmptyArgument(session, "session"));
452452
} catch (UnsupportedEncodingException | MessagingException e) {
453453
// this should never happen, so we don't acknowledge this exception (and simply bubble up)

modules/simple-java-mail/src/main/java/org/simplejavamail/internal/moduleloader/ModuleLoader.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import org.simplejavamail.internal.modules.SMIMEModule;
88
import org.simplejavamail.internal.util.MiscUtil;
99

10-
import java.util.ArrayList;
11-
import java.util.Collection;
10+
import java.util.HashSet;
11+
import java.util.Set;
1212
import java.util.HashMap;
1313
import java.util.Map;
1414

@@ -22,8 +22,8 @@ public class ModuleLoader {
2222
private static final Map<Class, Object> LOADED_MODULES = new HashMap<>();
2323

2424
// used from junit tests
25-
private static final Collection<Class> FORCED_DISABLED_MODULES = new ArrayList<>();
26-
private static final Collection<Class> FORCED_RECHECK_MODULES = new ArrayList<>();
25+
private static final Set<Class> FORCED_DISABLED_MODULES = new HashSet<>();
26+
private static final Set<Class> FORCED_RECHECK_MODULES = new HashSet<>();
2727

2828
public static AuthenticatedSocksModule loadAuthenticatedSocksModule() {
2929
if (!LOADED_MODULES.containsKey(AuthenticatedSocksModule.class)) {

modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
/**
66
* This exception is used to communicate errors during the sending of email.
77
*/
8-
@SuppressWarnings("serial")
98
class MailerException extends MailException {
109

1110
static final String ERROR_READING_SMIME_FROM_INPUTSTREAM = "Was unable to read S/MIME data from input stream";
1211
static final String ERROR_READING_FROM_FILE = "Error reading from file: %s";
1312
static final String MISSING_OAUTH2_TOKEN = "TransportStrategy is OAUTH2 but no OAUTH2 token provided as password";
1413
static final String INVALID_PROXY_SLL_COMBINATION = "Proxy is not supported for SSL connections (this is a limitation by the underlying JavaMail framework)";
1514
static final String ERROR_CONNECTING_SMTP_SERVER = "Was unable to connect to SMTP server";
15+
static final String MAILER_ERROR = "Failed to send email [%s]";
1616
static final String GENERIC_ERROR = "Failed to send email [%s], reason: Third party error";
1717
static final String INVALID_ENCODING = "Failed to send email [%s], reason: Encoding not accepted";
1818
static final String UNKNOWN_ERROR = "Failed to send email [%s], reason: Unknown error";

modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerGenericBuilderImpl.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ abstract class MailerGenericBuilderImpl<T extends MailerGenericBuilderImpl<?>> i
123123
@Nullable
124124
private Email emailOverrides;
125125

126+
/**
127+
* @see MailerGenericBuilder#withMaximumEmailSize(int)
128+
*/
129+
@Nullable
130+
private Integer maximumEmailSize;
131+
126132
/**
127133
* @see MailerGenericBuilder#signByDefaultWithSmime(Pkcs12Config)
128134
*/
@@ -296,7 +302,12 @@ private void validateProxy() {
296302
* For internal use.
297303
*/
298304
EmailGovernance buildEmailGovernance() {
299-
return new EmailGovernance(getEmailValidator(), getPkcs12ConfigForSmimeSigning(), getEmailDefaults(), getEmailOverrides());
305+
return new EmailGovernance(
306+
getEmailValidator(),
307+
getPkcs12ConfigForSmimeSigning(),
308+
getEmailDefaults(),
309+
getEmailOverrides(),
310+
getMaximumEmailSize());
300311
}
301312

302313
/**
@@ -454,6 +465,15 @@ public T withEmailOverrides(@NotNull Email emailOverrides) {
454465
return (T) this;
455466
}
456467

468+
/**
469+
* @see MailerGenericBuilder#withMaximumEmailSize(int)
470+
*/
471+
@Override
472+
public T withMaximumEmailSize(int maximumEmailSize) {
473+
this.maximumEmailSize = maximumEmailSize;
474+
return (T) this;
475+
}
476+
457477
/**
458478
* @param pkcs12StoreFile The file containing the keystore
459479
* @param storePassword The password to get keys from the store
@@ -843,6 +863,15 @@ public T clearEmailOverrides() {
843863
return (T) this;
844864
}
845865

866+
/**
867+
* @see MailerGenericBuilder#clearMaximumEmailSize()
868+
*/
869+
@Override
870+
public T clearMaximumEmailSize() {
871+
this.maximumEmailSize = null;
872+
return (T) this;
873+
}
874+
846875
/**
847876
* @see MailerGenericBuilder#clearSignByDefaultWithSmime()
848877
*/
@@ -974,6 +1003,15 @@ public Email getEmailOverrides() {
9741003
return emailOverrides;
9751004
}
9761005

1006+
/**
1007+
* @see MailerGenericBuilder#getMaximumEmailSize()
1008+
*/
1009+
@Override
1010+
@Nullable
1011+
public Integer getMaximumEmailSize() {
1012+
return maximumEmailSize;
1013+
}
1014+
9771015
/**
9781016
* @see MailerGenericBuilder#getPkcs12ConfigForSmimeSigning()
9791017
*/

modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/SendMailClosure.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
import org.jetbrains.annotations.Nullable;
88
import org.simplejavamail.api.email.Email;
99
import org.simplejavamail.api.internal.authenticatedsockssupport.socks5server.AnonymousSocks5Server;
10+
import org.simplejavamail.api.mailer.EmailTooBigException;
1011
import org.simplejavamail.api.mailer.config.OperationalConfig;
1112
import org.simplejavamail.mailer.internal.util.TransportRunner;
1213

1314
import java.util.concurrent.atomic.AtomicInteger;
1415

1516
import static java.lang.String.format;
17+
import static java.util.Optional.ofNullable;
1618
import static org.simplejavamail.mailer.internal.MailerException.GENERIC_ERROR;
19+
import static org.simplejavamail.mailer.internal.MailerException.MAILER_ERROR;
1720
import static org.simplejavamail.mailer.internal.MailerException.UNKNOWN_ERROR;
1821

1922
/**
@@ -53,13 +56,18 @@ public void executeClosure() {
5356
}
5457
} catch (final MessagingException e) {
5558
handleException(e, GENERIC_ERROR);
59+
} catch (final MailerException | EmailTooBigException e) {
60+
handleException(e, MAILER_ERROR);
5661
} catch (final Exception e) {
5762
handleException(e, UNKNOWN_ERROR);
5863
}
5964
}
6065

6166
private void handleException(final Exception e, String errorMsg) {
62-
LOGGER.trace("Failed to send email {}\n{}", email.getId(), email);
63-
throw new MailerException(format(errorMsg, email.getId()), e);
67+
LOGGER.trace("Failed to send email {}\n{}\n\t{}", email.getId(), email, errorMsg);
68+
val emailId = ofNullable(email.getId())
69+
.map(id -> format("ID: '%s'", id))
70+
.orElse(format("Subject: '%s'", email.getSubject()));
71+
throw new MailerException(format(errorMsg, emailId), e);
6472
}
6573
}

modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/SessionBasedEmailToMimeMessageConverter.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
import lombok.val;
1010
import org.jetbrains.annotations.NotNull;
1111
import org.simplejavamail.api.email.Email;
12+
import org.simplejavamail.api.mailer.EmailTooBigException;
1213
import org.simplejavamail.api.mailer.config.EmailGovernance;
1314
import org.simplejavamail.api.mailer.config.OperationalConfig;
1415
import org.simplejavamail.converter.internal.mimemessage.MimeMessageProducerHelper;
1516
import org.simplejavamail.mailer.internal.util.SessionLogger;
1617
import org.slf4j.Logger;
1718
import org.slf4j.LoggerFactory;
1819

20+
import java.io.ByteArrayOutputStream;
21+
import java.io.IOException;
1922
import java.io.UnsupportedEncodingException;
2023

2124
import static java.lang.String.format;
@@ -54,12 +57,29 @@ public static void unprimeSession(@NotNull Session session) {
5457
@NotNull
5558
public static MimeMessage convertAndLogMimeMessage(Session session, final Email email) throws MessagingException {
5659
val mimeMessageConverter = (SessionBasedEmailToMimeMessageConverter) session.getProperties().get(MIMEMESSAGE_CONVERTER_KEY);
57-
return mimeMessageConverter.convertAndLogMimeMessage(email);
60+
val mimeMessage = mimeMessageConverter.convertAndLogMimeMessage(email);
61+
val governance = mimeMessageConverter.emailGovernance;
62+
63+
if (governance.getMaximumEmailSize() != null) {
64+
val emailSize = calculateEmailSize(mimeMessage);
65+
if (emailSize > governance.getMaximumEmailSize()) {
66+
throw new EmailTooBigException(emailSize, governance.getMaximumEmailSize());
67+
}
68+
}
69+
return mimeMessage;
70+
}
71+
72+
private static int calculateEmailSize(MimeMessage mimeMessage) throws MessagingException {
73+
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
74+
mimeMessage.writeTo(os);
75+
return os.size();
76+
} catch (IOException e) {
77+
throw new RuntimeException("error trying to calculate email size", e);
78+
}
5879
}
5980

6081
@NotNull
6182
private MimeMessage convertAndLogMimeMessage(final Email email) throws MessagingException {
62-
// fill and send wrapped mime message parts
6383
val message = convertMimeMessage(email, session, emailGovernance);
6484

6585
SessionLogger.logSession(session, operationalConfig.isAsync(), "mail");

modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/MailerLiveTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.simplejavamail.mailer;
22

33
import jakarta.mail.MessagingException;
4+
import jakarta.mail.Session;
45
import jakarta.mail.internet.MimeMessage;
56
import lombok.val;
67
import org.jetbrains.annotations.NotNull;
@@ -14,7 +15,10 @@
1415
import org.simplejavamail.api.email.OriginalSmimeDetails.SmimeMode;
1516
import org.simplejavamail.api.email.Recipient;
1617
import org.simplejavamail.api.internal.smimesupport.model.PlainSmimeDetails;
18+
import org.simplejavamail.api.mailer.CustomMailer;
19+
import org.simplejavamail.api.mailer.EmailTooBigException;
1720
import org.simplejavamail.api.mailer.Mailer;
21+
import org.simplejavamail.api.mailer.config.OperationalConfig;
1822
import org.simplejavamail.converter.EmailConverter;
1923
import org.simplejavamail.email.EmailBuilder;
2024
import org.simplejavamail.email.internal.InternalEmailPopulatingBuilder;
@@ -41,6 +45,7 @@
4145
import static java.util.Optional.ofNullable;
4246
import static java.util.stream.Collectors.toList;
4347
import static org.assertj.core.api.Assertions.assertThat;
48+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
4449
import static org.assertj.core.data.MapEntry.entry;
4550
import static org.simplejavamail.api.email.ContentTransferEncoding.BIT7;
4651
import static org.simplejavamail.converter.EmailConverter.mimeMessageToEmail;
@@ -541,4 +546,59 @@ private void assertAttachmentMetadata(AttachmentResource embeddedImg, String mim
541546
assertThat(embeddedImg.getDataSource().getContentType()).isEqualTo(mimeType);
542547
assertThat(embeddedImg.getName()).isEqualTo(filename);
543548
}
549+
550+
@Test
551+
public void testMaximumEmailSize() {
552+
val mailer = MailerBuilder
553+
.withSMTPServer("localhost", SERVER_PORT, USERNAME, PASSWORD)
554+
.withMaximumEmailSize(4)
555+
.buildMailer();
556+
557+
sendAndVerifyEmailTooBigException(mailer);
558+
}
559+
560+
@Test
561+
public void testMaximumEmailSize_CustomMailer() {
562+
val mailer = MailerBuilder
563+
.withCustomMailer(new CustomMailer() {
564+
@Override
565+
public void testConnection(@NotNull OperationalConfig operationalConfig, @NotNull Session session) {
566+
throw new RuntimeException("should reach here");
567+
}
568+
569+
@Override
570+
public void sendMessage(@NotNull OperationalConfig operationalConfig, @NotNull Session session, @NotNull Email email, @NotNull MimeMessage message) {
571+
throw new RuntimeException("should reach here");
572+
}
573+
})
574+
.withMaximumEmailSize(4)
575+
.buildMailer();
576+
577+
sendAndVerifyEmailTooBigException(mailer);
578+
}
579+
580+
@Test
581+
public void testMaximumEmailSize_DontSendOnlyLog() {
582+
val mailer = MailerBuilder
583+
.withTransportModeLoggingOnly()
584+
.withMaximumEmailSize(4)
585+
.buildMailer();
586+
587+
sendAndVerifyEmailTooBigException(mailer);
588+
}
589+
590+
private static void sendAndVerifyEmailTooBigException(Mailer mailer) {
591+
val email = EmailBuilder.startingBlank()
592+
.withPlainText("non empty text")
593+
.withSubject("email size test")
594+
595+
596+
.buildEmail();
597+
598+
assertThatThrownBy(() -> mailer.sendMail(email))
599+
.hasMessageStartingWith("Failed to send email [ID:")
600+
.getCause()
601+
.isInstanceOf(EmailTooBigException.class)
602+
.hasMessageContaining("bytes exceeds maximum allowed size of 4 bytes");
603+
}
544604
}

0 commit comments

Comments
 (0)