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

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

commit 6a83667b6c818e1cc186181f663964e0e05d5fcd
Author: Quan Tran <[email protected]>
AuthorDate: Wed Nov 4 21:41:49 2026 +0700

    [FIX] SPFSession: trim only spaces and tabs, then parse MAIL FROM
    
    SPFSession trimmed MAIL FROM and HELO with String.trim, which also
    removes control characters: a HELO "mail.example.com\u0001" was
    checked as mail.example.com, a domain the client did not give, instead
    of a malformed domain (RFC 7208 4.3). And it parsed MAIL FROM before
    trimming it: "[email protected]\t" checked the domain "example.com\t",
    and a MAIL FROM of spaces was not read as the null sender, whose
    identity is postmaster at the HELO domain (RFC 7208 2.4).
    
    Trim only spaces and tabs from MAIL FROM and HELO, and parse the
    trimmed MAIL FROM. The client address is still trimmed with
    String.trim: it holds no control character, so that gives the same
    address.
---
 .../org/apache/james/jspf/core/SPFSession.java     | 30 ++++++++++++++----
 .../org/apache/james/jspf/core/SPFSessionTest.java | 36 ++++++++++++++++++++++
 2 files changed, 60 insertions(+), 6 deletions(-)

diff --git a/resolver/src/main/java/org/apache/james/jspf/core/SPFSession.java 
b/resolver/src/main/java/org/apache/james/jspf/core/SPFSession.java
index 97d8381..d00b8ee 100644
--- a/resolver/src/main/java/org/apache/james/jspf/core/SPFSession.java
+++ b/resolver/src/main/java/org/apache/james/jspf/core/SPFSession.java
@@ -36,6 +36,23 @@ import java.util.function.Predicate;
 
 public class SPFSession implements MacroData {
 
+    /**
+     * @param s an identity of the SMTP session
+     * @return the identity without its leading and trailing spaces and tabs: 
unlike String.trim, a
+     *         control character stays, so that it makes a malformed domain 
instead of another one
+     */
+    private static String trimSpaces(String s) {
+        int start = 0;
+        int end = s.length();
+        while (start < end && (s.charAt(start) == ' ' || s.charAt(start) == 
'\t')) {
+            start++;
+        }
+        while (end > start && (s.charAt(end - 1) == ' ' || s.charAt(end - 1) 
== '\t')) {
+            end--;
+        }
+        return s.substring(start, end);
+    }
+
     private String ipAddress = ""; // also used for (i)<sending-host>
 
     private String mailFrom = ""; // (s)<responsible-sender>
@@ -94,10 +111,11 @@ public class SPFSession implements MacroData {
         if (containsCRLF(heloDomain)) {
             throw new IllegalArgumentException("heloDomain must not contain CR 
or LF characters");
         }
-        this.mailFrom = mailFrom.trim();
-        this.hostName = heloDomain.trim();
+        this.mailFrom = trimSpaces(mailFrom);
+        this.hostName = trimSpaces(heloDomain);
        
         try {
+            // an address holds no control character: String.trim gives the 
same address
             this.ipAddress = IPAddr.getProperIpAddress(clientIP.trim());
             // get the in Address, of the address checked: trimmed, and IPv4 
when IPv4-mapped
             this.inAddress = IPAddr.getInAddress(this.ipAddress);
@@ -107,22 +125,22 @@ public class SPFSession implements MacroData {
         }
 
         // if nullsender is used postmaster@helo will be used as email
-        if (mailFrom.equals("")) {
+        if (this.mailFrom.equals("")) {
             this.currentSenderPart = "postmaster";
             this.senderDomain = hostName;
             this.mailFrom = currentSenderPart + "@" + hostName;
         } else {
-            String[] fromParts = mailFrom.split("@");
+            String[] fromParts = this.mailFrom.split("@");
             // What to do when mailFrom is "@example.com" ?
             if (fromParts.length > 1) {
                 this.senderDomain = fromParts[fromParts.length -1];
-                this.currentSenderPart = mailFrom.substring(0, 
mailFrom.length() - senderDomain.length() - 1);
+                this.currentSenderPart = this.mailFrom.substring(0, 
this.mailFrom.length() - senderDomain.length() - 1);
                 if (this.currentSenderPart.length() == 0) {
                     this.currentSenderPart = "postmaster";
                 }
             } else {
                 this.currentSenderPart = "postmaster";
-                this.senderDomain = mailFrom;
+                this.senderDomain = this.mailFrom;
             }
         }
         this.currentDomain = this.senderDomain;
diff --git 
a/resolver/src/test/java/org/apache/james/jspf/core/SPFSessionTest.java 
b/resolver/src/test/java/org/apache/james/jspf/core/SPFSessionTest.java
index 7f91325..aa30d6d 100644
--- a/resolver/src/test/java/org/apache/james/jspf/core/SPFSessionTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/core/SPFSessionTest.java
@@ -52,6 +52,42 @@ public class SPFSessionTest {
         assertEquals("ip6", spfSession.getInAddress());
     }
 
+    @Test
+    public void theSenderDomainShouldBeThatOfTheTrimmedMailFrom() {
+        SPFSession spfSession = new SPFSession(" [email protected]\t", 
"mail.example.org", "192.0.2.1");
+
+        assertEquals("example.com", spfSession.getSenderDomain());
+        assertEquals("foo", spfSession.getCurrentSenderPart());
+        assertEquals("[email protected]", spfSession.getMailFrom());
+    }
+
+    @Test
+    public void aMailFromOfSpacesShouldBeTheNullSender() {
+        SPFSession spfSession = new SPFSession("  ", "mail.example.org", 
"192.0.2.1");
+
+        // RFC 7208 2.4: "postmaster@" and the HELO identity
+        assertEquals("mail.example.org", spfSession.getSenderDomain());
+        assertEquals("[email protected]", spfSession.getMailFrom());
+    }
+
+    @Test
+    public void theClientAddressShouldBeTrimmedOfControlCharacters() {
+        // as read from a line of a protocol
+        SPFSession spfSession = new SPFSession("[email protected]", 
"mail.example.org", "192.0.2.10\n");
+
+        assertEquals("192.0.2.10", spfSession.getIpAddress());
+        assertNull(spfSession.getCurrentResultExpanded());
+    }
+
+    @Test
+    public void controlCharactersShouldStayInTheIdentities() {
+        SPFSession spfSession = new SPFSession("[email protected]\u0002", 
"mail.example.org\u0001", "192.0.2.1");
+
+        // only spaces and tabs are trimmed: a control character makes a 
malformed domain, not another one
+        assertEquals("example.com\u0002", spfSession.getSenderDomain());
+        assertEquals("mail.example.org\u0001", spfSession.getHostName());
+    }
+
     @Test
     public void shouldReturnIPv4Mapped() {
         SPFSession spfSession = new SPFSession("", "", "::ffff:192.168.1.1");


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

Reply via email to