This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new 84ac44e020 JAMES-4184 POP3 TOP msg 0 - only read headers (#2965)
84ac44e020 is described below

commit 84ac44e020787d34e3d2df6c79d0428e4d25676b
Author: Benoit TELLIER <[email protected]>
AuthorDate: Mon Mar 9 04:25:15 2026 +0100

    JAMES-4184 POP3 TOP msg 0 - only read headers (#2965)
---
 .../james/protocols/pop3/core/TopCmdHandler.java   |  9 +++--
 .../james/protocols/pop3/mailbox/Mailbox.java      |  9 +++++
 .../mailbox/DistributedMailboxAdapter.java         | 18 ++++++++++
 .../james/pop3server/mailbox/MailboxAdapter.java   | 24 +++++++++++---
 .../apache/james/pop3server/POP3ServerTest.java    | 38 ++++++++++++++++++++++
 5 files changed, 91 insertions(+), 7 deletions(-)

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 bf15a77b7c..4e08416d1b 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 @@ public class TopCmdHandler extends AbstractPOP3CommandHandler 
implements CapaCap
                 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 @@ public class TopCmdHandler extends 
AbstractPOP3CommandHandler implements CapaCap
         return session.getUserMailbox().getMessage(data.getUid());
     }
 
+    protected InputStream getMessageHeaders(POP3Session session, 
MessageMetaData data) throws IOException {
+        return session.getUserMailbox().getMessageHeaders(data.getUid());
+    }
+
     @Override
     public Set<String> 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 38b5fbb7cc..6f61cf4963 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 
<code>uid</code>.
+     * 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 baee0734f5..3a6a36b231 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 class DistributedMailboxAdapter implements Mailbox {
         }
     }
 
+    @Override
+    public InputStream getMessageHeaders(String uid) throws IOException {
+        try {
+            MessageId messageId = messageIdFactory.fromString(uid);
+            Iterator<MessageResult> 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<MessageMetaData> 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 e6de9b79f1..f582ab61b1 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 
org.apache.james.protocols.pop3.mailbox.MessageMetaData;
 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 class MailboxAdapter implements Mailbox {
     public InputStream getMessage(String uid) throws IOException {
         try {
             mailboxManager.startProcessingRequest(session);
-            Iterator<MessageResult> results = 
manager.getMessages(MessageUid.of(Long.parseLong(uid)).toRange(), FULL_GROUP, 
session);
+            Iterator<MessageResult> 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 class MailboxAdapter implements Mailbox {
         }
     }
 
+    @Override
+    public InputStream getMessageHeaders(String uid) throws IOException {
+        try {
+            mailboxManager.startProcessingRequest(session);
+            Iterator<MessageResult> 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<MessageMetaData> getMessages() throws IOException {
         try {
             mailboxManager.startProcessingRequest(session);
-            Iterator<MessageResult> results = 
manager.getMessages(MessageRange.all(), METADATA_GROUP, session);
+            Iterator<MessageResult> results = 
manager.getMessages(MessageRange.all(), FetchGroup.MINIMAL, session);
             List<MessageMetaData> 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 d0e091e41f..00f12a6359 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 @@ public class POP3ServerTest {
         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: [email protected]");
+        assertThat(response).contains("Subject: test");
+        assertThat(response).doesNotContain("Body Text 
POP3ServerTest.setupTestMails");
+
+        mailboxManager.deleteMailbox(mailboxPath, session);
+    }
+
     @Test
     void pop3SessionShouldTolerateConcurrentDeletes() throws Exception {
         finishSetUp(pop3Configuration);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to