From b02cf2490bda5bdede3c18d66d1cd06ce28f024d Mon Sep 17 00:00:00 2001 From: Akshay Raj Maurya Date: Mon, 16 Jan 2023 22:51:12 +0530 Subject: [PATCH] Added Local Extension Support With Refactoring and Tests --- .../com/hubspot/smtp/client/SmtpSession.java | 63 +++++++++---------- .../com/hubspot/smtp/IntegrationTest.java | 36 +++++++++++ 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/hubspot/smtp/client/SmtpSession.java b/src/main/java/com/hubspot/smtp/client/SmtpSession.java index c84549e..8f6fb5d 100644 --- a/src/main/java/com/hubspot/smtp/client/SmtpSession.java +++ b/src/main/java/com/hubspot/smtp/client/SmtpSession.java @@ -99,6 +99,8 @@ public class SmtpSession { private volatile boolean requiresRset = false; private volatile EhloResponse ehloResponse = EhloResponse.EMPTY; + private List localExtensionsList; + SmtpSession(Channel channel, ResponseHandler responseHandler, SmtpSessionConfig config, Executor executor, Supplier sslEngineSupplier) { this.channel = channel; this.responseHandler = responseHandler; @@ -238,6 +240,18 @@ public CompletableFuture send(String from, Collection send(String from, Collection recipients, MessageContent content, List localExtensionsList) { + this.localExtensionsList = localExtensionsList; + return send(from, recipients, content, Optional.empty()); + } + /** * Sends an email as efficiently as possible, using the extended SMTP features supported by the remote server. * @@ -306,20 +320,11 @@ private CompletableFuture sendInternal(String from, Collecti return sendAsChunked(from, recipients, content, sequenceInterceptor); } - if (content.getEncoding() == MessageContentEncoding.SEVEN_BIT) { - return sendAs7Bit(from, recipients, content, sequenceInterceptor); - } - - if (ehloResponse.isSupported(Extension.EIGHT_BIT_MIME)) { - return sendAs8BitMime(from, recipients, content, sequenceInterceptor); + if (content.getEncoding() != MessageContentEncoding.SEVEN_BIT && !ehloResponse.isSupported(Extension.EIGHT_BIT_MIME)) { + content = encodeContentAs7Bit(content); } - if (content.get8bitCharacterProportion() == 0) { - return sendAs7Bit(from, recipients, content, sequenceInterceptor); - } - - // this message is not 7 bit, but the server only supports 7 bit :( - return sendAs7Bit(from, recipients, encodeContentAs7Bit(content), sequenceInterceptor); + return sendMessage(from, recipients, content, sequenceInterceptor); } private CompletableFuture sendAsChunked(String from, Collection recipients, MessageContent content, Optional sequenceInterceptor) { @@ -379,16 +384,10 @@ public Object next() { }; } - private CompletableFuture sendAs7Bit(String from, Collection recipients, MessageContent content, Optional sequenceInterceptor) { + private CompletableFuture sendMessage(String from, Collection recipients, MessageContent content, Optional sequenceInterceptor) { return sendPipelinedIfPossible(mailCommand(from, recipients), recipients, SmtpRequests.data(), sequenceInterceptor) - .thenSend(content.getDotStuffedContent(), DotCrlfBuffer.get()) - .toResponses(); - } - - private CompletableFuture sendAs8BitMime(String from, Collection recipients, MessageContent content, Optional sequenceInterceptor) { - return sendPipelinedIfPossible(mailCommandWith8BitMime(from, recipients), recipients, SmtpRequests.data(), sequenceInterceptor) - .thenSend(content.getDotStuffedContent(), DotCrlfBuffer.get()) - .toResponses(); + .thenSend(content.getDotStuffedContent(), DotCrlfBuffer.get()) + .toResponses(); } private SendSequence sendPipelinedIfPossible(SmtpRequest mailRequest, Collection recipients, SmtpRequest dataRequest, Optional sequenceInterceptor) { @@ -414,20 +413,18 @@ private Collection rpctCommands(Collection recipients) { return recipients.stream().map(SmtpRequests::rcpt).collect(Collectors.toList()); } - private SmtpRequest mailCommand(String from, Collection recipients) { - if (!ehloResponse.isSupported(Extension.SMTPUTF8) || (isAllAscii(from) && isAllAscii(recipients))) { - return SmtpRequests.mail(from); - } else { - return SmtpRequests.mail(from, "SMTPUTF8"); + private SmtpRequest mailCommand(String from, Collection recipients){ + if(localExtensionsList == null){ + localExtensionsList = new ArrayList<>(); } - } - - private SmtpRequest mailCommandWith8BitMime(String from, Collection recipients) { - if (!ehloResponse.isSupported(Extension.SMTPUTF8) || (isAllAscii(from) && isAllAscii(recipients))) { - return SmtpRequests.mail(from, "BODY=8BITMIME"); - } else { - return SmtpRequests.mail(from, "BODY=8BITMIME", "SMTPUTF8"); + if (ehloResponse.isSupported(Extension.EIGHT_BIT_MIME)) { + localExtensionsList.add("BODY=8BITMIME"); } + if (ehloResponse.isSupported(Extension.SMTPUTF8) && !(isAllAscii(from) && isAllAscii(recipients))) { + localExtensionsList.add("SMTPUTF8"); + } + + return SmtpRequests.mail(from, localExtensionsList.toArray(new String[0])); } private static boolean isAllAscii(String s) { diff --git a/src/test/java/com/hubspot/smtp/IntegrationTest.java b/src/test/java/com/hubspot/smtp/IntegrationTest.java index edc72a6..ab5b538 100644 --- a/src/test/java/com/hubspot/smtp/IntegrationTest.java +++ b/src/test/java/com/hubspot/smtp/IntegrationTest.java @@ -15,6 +15,7 @@ import java.net.ServerSocket; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -364,6 +365,41 @@ public void itCanSendMultipleEmailsAtOnce() throws Exception { assertThat(receivedMails.size()).isEqualTo(200); } + @Test + public void itCanSendAnEmailWithLocalExtensions() throws Exception { + + List localExtensionsList = new ArrayList<>(); + localExtensionsList.add("XCustomExtension1=SomeValue1"); + localExtensionsList.add("XCustomExtension2=SomeValue2"); + + List> futures = Lists.newArrayList(); + + Executor executor = Executors.newFixedThreadPool(50); + SmtpSessionFactory factory = new SmtpSessionFactory(SmtpSessionFactoryConfig.nonProductionConfig().withExecutor(executor)); + + // Avoiding chunking because it hangs with chunking + SmtpSessionConfig pipeliningConfig = getDefaultConfig().withDisabledExtensions(EnumSet.of(Extension.CHUNKING)); + + factory.connect(pipeliningConfig) + .thenCompose(r -> assertSuccess(r).send(req(EHLO, "hubspot.com"))) + .thenCompose(r -> assertSuccess(r).send( + RETURN_PATH, + Lists.newArrayList("a@example.com", "b@example.com"), + createMessageContent(), + localExtensionsList)) + .thenCompose(r -> assertSuccess(r).send(req(QUIT))) + .thenCompose(r -> assertSuccess(r).close()) + .get(); + + assertThat(receivedMails.size()).isEqualTo(1); + MailEnvelope mail = receivedMails.get(0); + + assertThat(mail.getSender().toString()).isEqualTo(RETURN_PATH); + assertThat(mail.getRecipients().get(0).toString()).isEqualTo("a@example.com"); + assertThat(mail.getRecipients().get(1).toString()).isEqualTo("b@example.com"); + assertThat(readContents(mail)).contains(MESSAGE_DATA); + } + @Test public void itSendsKeepAliveCommands() throws Exception { connect(SmtpSessionConfig.forRemoteAddress(serverAddress).withKeepAliveTimeout(Duration.ofSeconds(1)))