diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java index bf15a77b7cb..4e08416d1bc 100644 --- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java +++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java @@ -60,8 +60,9 @@ protected Response handleMessageExists(POP3Session session, MessageMetaData data if (args.getLineCount().isEmpty()) { return handleSyntaxError(); } - InputStream content = getMessageContent(session, data); - InputStream in = new CountingBodyInputStream(new CRLFTerminatedInputStream(new ExtraDotInputStream(content)), args.getLineCount().get()); + int lineCount = args.getLineCount().get(); + InputStream content = lineCount == 0 ? getMessageHeaders(session, data) : getMessageContent(session, data); + InputStream in = new CountingBodyInputStream(new CRLFTerminatedInputStream(new ExtraDotInputStream(content)), lineCount); return new POP3StreamResponse(POP3Response.OK_RESPONSE, "Message follows", in); } @@ -95,6 +96,10 @@ protected InputStream getMessageContent(POP3Session session, MessageMetaData dat return session.getUserMailbox().getMessage(data.getUid()); } + protected InputStream getMessageHeaders(POP3Session session, MessageMetaData data) throws IOException { + return session.getUserMailbox().getMessageHeaders(data.getUid()); + } + @Override public Set getImplementedCapabilities(POP3Session session) { if (session.getHandlerState() == POP3Session.TRANSACTION) { diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/mailbox/Mailbox.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/mailbox/Mailbox.java index 38b5fbb7ccf..6f61cf49636 100644 --- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/mailbox/Mailbox.java +++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/mailbox/Mailbox.java @@ -33,6 +33,15 @@ public interface Mailbox { */ InputStream getMessage(String uid) throws IOException; + /** + * Return only the headers as {@link InputStream} for the given uid. + * Implementations may override this for better performance (e.g. using FetchGroup.HEADERS). + * @exception IOException If message can not be found or is inaccessible + */ + default InputStream getMessageHeaders(String uid) throws IOException { + return getMessage(uid); + } + /** * Return a immutable {@link List} which holds the {@link MessageMetaData} * for all messages in the {@link Mailbox} diff --git a/server/protocols/protocols-pop3-distributed/src/main/java/org/apache/james/pop3server/mailbox/DistributedMailboxAdapter.java b/server/protocols/protocols-pop3-distributed/src/main/java/org/apache/james/pop3server/mailbox/DistributedMailboxAdapter.java index baee0734f5b..3a6a36b2310 100644 --- a/server/protocols/protocols-pop3-distributed/src/main/java/org/apache/james/pop3server/mailbox/DistributedMailboxAdapter.java +++ b/server/protocols/protocols-pop3-distributed/src/main/java/org/apache/james/pop3server/mailbox/DistributedMailboxAdapter.java @@ -113,6 +113,24 @@ public InputStream getMessage(String uid) throws IOException { } } + @Override + public InputStream getMessageHeaders(String uid) throws IOException { + try { + MessageId messageId = messageIdFactory.fromString(uid); + Iterator messages = messageIdManager.getMessage(messageId, FetchGroup.HEADERS, session).iterator(); + if (messages.hasNext()) { + return messages.next().getHeaders().getInputStream(); + } else { + LOGGER.warn("Removing {} from {} POP3 projection for user {} as it is not backed by a MailboxMessage", + uid, mailbox.getId().serialize(), session.getUser().asString()); + Mono.from(metadataStore.remove(mailbox.getId(), messageId)).block(); + throw new IOException("Message does not exist for uid " + uid); + } + } catch (MailboxException e) { + throw new IOException("Unable to retrieve message headers for uid " + uid, e); + } + } + @Override public List getMessages() { return Flux.from(metadataStore.stat(mailbox.getId())) diff --git a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/mailbox/MailboxAdapter.java b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/mailbox/MailboxAdapter.java index e6de9b79f1e..f582ab61b1a 100644 --- a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/mailbox/MailboxAdapter.java +++ b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/mailbox/MailboxAdapter.java @@ -40,9 +40,6 @@ import com.google.common.collect.ImmutableList; public class MailboxAdapter implements Mailbox { - private static final FetchGroup FULL_GROUP = FetchGroup.FULL_CONTENT; - private static final FetchGroup METADATA_GROUP = FetchGroup.MINIMAL; - private final MessageManager manager; private final MailboxSession session; @@ -58,7 +55,7 @@ public MailboxAdapter(MailboxManager mailboxManager, MessageManager manager, Mai public InputStream getMessage(String uid) throws IOException { try { mailboxManager.startProcessingRequest(session); - Iterator results = manager.getMessages(MessageUid.of(Long.parseLong(uid)).toRange(), FULL_GROUP, session); + Iterator results = manager.getMessages(MessageUid.of(Long.parseLong(uid)).toRange(), FetchGroup.FULL_CONTENT, session); if (results.hasNext()) { return results.next().getFullContent().getInputStream(); } else { @@ -71,11 +68,28 @@ public InputStream getMessage(String uid) throws IOException { } } + @Override + public InputStream getMessageHeaders(String uid) throws IOException { + try { + mailboxManager.startProcessingRequest(session); + Iterator results = manager.getMessages(MessageUid.of(Long.parseLong(uid)).toRange(), FetchGroup.HEADERS, session); + if (results.hasNext()) { + return results.next().getHeaders().getInputStream(); + } else { + throw new IOException("Message does not exist for uid " + uid); + } + } catch (MailboxException e) { + throw new IOException("Unable to retrieve message headers for uid " + uid, e); + } finally { + mailboxManager.endProcessingRequest(session); + } + } + @Override public List getMessages() throws IOException { try { mailboxManager.startProcessingRequest(session); - Iterator results = manager.getMessages(MessageRange.all(), METADATA_GROUP, session); + Iterator results = manager.getMessages(MessageRange.all(), FetchGroup.MINIMAL, session); List mList = new ArrayList<>(); while (results.hasNext()) { MessageResult result = results.next(); diff --git a/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java b/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java index d0e091e41fe..00f12a63599 100644 --- a/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java +++ b/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java @@ -544,6 +544,44 @@ void testKnownUserInboxWithMessages() throws Exception { mailboxManager.deleteMailbox(mailboxPath, session); } + @Test + void topWithZeroLinesShouldReturnOnlyHeaders() throws Exception { + finishSetUp(pop3Configuration); + + pop3Client = new POP3Client(); + InetSocketAddress bindedAddress = new ProtocolServerUtils(pop3Server).retrieveBindedAddress(); + pop3Client.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort()); + + Username username = Username.of("topzero"); + usersRepository.addUser(username, "password"); + + MailboxPath mailboxPath = MailboxPath.inbox(username); + MailboxSession session = mailboxManager.authenticate(username, "password").withoutDelegation(); + mailboxManager.createMailbox(mailboxPath, session); + setupTestMails(session, mailboxManager.getMailbox(mailboxPath, session)); + + pop3Client.login("topzero", "password"); + POP3MessageInfo[] entries = pop3Client.listMessages(); + + Reader reader = pop3Client.retrieveMessageTop(entries[0].number, 0); + assertThat(reader).isNotNull(); + + StringBuilder responseBody = new StringBuilder(); + char[] buffer = new char[1024]; + int n; + while ((n = reader.read(buffer)) != -1) { + responseBody.append(buffer, 0, n); + } + reader.close(); + + String response = responseBody.toString(); + assertThat(response).contains("Return-path: return@test.com"); + assertThat(response).contains("Subject: test"); + assertThat(response).doesNotContain("Body Text POP3ServerTest.setupTestMails"); + + mailboxManager.deleteMailbox(mailboxPath, session); + } + @Test void pop3SessionShouldTolerateConcurrentDeletes() throws Exception { finishSetUp(pop3Configuration);