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-jdkim.git
commit 0bd9828783e2a16e2320299925c97bc4d9978e4c Author: Benoit TELLIER <[email protected]> AuthorDate: Wed Nov 4 14:59:55 2026 +0100 [FIX] TagValue: allow a single FWS around tag names and values The FWS check only covered the text after the final ";". Around a tag name or a value, trimFWS steps back over any number of CRLF pairs once the segment ends with WSP, so "v=1\r\n\r\n " parsed as v=1, and so did "a\r\n\r\n =b" or "v=\r\n\r\n ;f=1". FWS is [*WSP CRLF] 1*WSP: a single CRLF, a line holding only WSP is obsolete syntax. When strict validation is on, parse now checks that the whitespace before and after each tag name and value is at most one FWS, before trimming it. The testValidFWSTags case relying on a blank line before ";" now uses a single fold. --- .../org/apache/james/jdkim/tagvalue/TagValue.java | 32 +++++++++++++++++++++- .../apache/james/jdkim/tagvalue/TagValueTest.java | 18 +++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java b/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java index 7593a02..a16d4de 100644 --- a/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java +++ b/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java @@ -63,7 +63,7 @@ public class TagValue { private static final Pattern valuePattern = Pattern.compile("^(?:" + tval + "(?:(?:(?:\r\n)?[\t ])++" + tval + ")*+)?$"); private static final Pattern fwsPattern = Pattern.compile("[ \t\r\n]"); - // FWS = [*WSP CRLF] 1*WSP + // FWS = [*WSP CRLF] 1*WSP, or nothing private static final Pattern trailingFWSPattern = Pattern.compile("^(?:(?:[ \t]*\r\n)?[ \t]+)?$"); // we may use a TreeMap because we may need to know original order. @@ -184,6 +184,11 @@ public class TagValue { + data); } + if (isStrictValidation()) { + // SPEC: tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS]: trimFWS removes any number of folds + checkSingleFWSAround(data, i, equal); + checkSingleFWSAround(data, equal + 1, next); + } CharSequence tag = trimFWS(data, i, equal - 1, true).toString(); if (VALIDATION && !tagPattern.matcher(tag).matches()) { throw new IllegalStateException("Syntax error in tag: " + tag); @@ -206,6 +211,31 @@ public class TagValue { this.stringRepresentation = data; } + /** + * Checks that the whitespace that starts and ends data[start, end) is at most one FWS on each side: + * FWS = [*WSP CRLF] 1*WSP holds a single CRLF, a line holding only WSP is obsolete syntax. + */ + private static void checkSingleFWSAround(String data, int start, int end) { + int contentStart = start; + while (contentStart < end && isFWSChar(data.charAt(contentStart))) { + contentStart++; + } + int contentEnd = end; + while (contentEnd > contentStart && isFWSChar(data.charAt(contentEnd - 1))) { + contentEnd--; + } + String leading = data.substring(start, contentStart); + String trailing = data.substring(contentEnd, end); + if (!trailingFWSPattern.matcher(leading).matches() || !trailingFWSPattern.matcher(trailing).matches()) { + throw new IllegalStateException("Syntax error: whitespace that is not FWS in [" + + data.substring(start, end) + "]"); + } + } + + private static boolean isFWSChar(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; + } + public int hashCode() { final int prime = 31; int result = 1; diff --git a/main/src/test/java/org/apache/james/jdkim/tagvalue/TagValueTest.java b/main/src/test/java/org/apache/james/jdkim/tagvalue/TagValueTest.java index 8b2862e..50eac49 100644 --- a/main/src/test/java/org/apache/james/jdkim/tagvalue/TagValueTest.java +++ b/main/src/test/java/org/apache/james/jdkim/tagvalue/TagValueTest.java @@ -124,7 +124,7 @@ public class TagValueTest { "p=ciao;s=cips;v=DKIM1;")); Assert.assertTrue(tagValuesEquals("p\r\n = ciao; s=cips\r\n\t; v=DKIM1;", "p=ciao;s=cips;v=DKIM1;")); - Assert.assertTrue(tagValuesEquals("p=ciao\r\n\r\n ; \r\n s=cips\r\n\t; v=DKIM1;", + Assert.assertTrue(tagValuesEquals("p=ciao\r\n ; \r\n s=cips\r\n\t; v=DKIM1;", "p=ciao;s=cips;v=DKIM1;")); } @@ -220,4 +220,20 @@ public class TagValueTest { new TagValue("a=b; \r\n\t"); new TagValue("a=b;\r\n "); } + + @Test + public void blankLineInsideATagSpecShouldBeRejected() { + assertThatThrownBy(() -> new TagValue("v=1\r\n\r\n ")).isInstanceOf(IllegalStateException.class); + assertThatThrownBy(() -> new TagValue("a\r\n\r\n =b")).isInstanceOf(IllegalStateException.class); + assertThatThrownBy(() -> new TagValue("v=\r\n\r\n ;f=1")).isInstanceOf(IllegalStateException.class); + assertThatThrownBy(() -> new TagValue("p=ciao\r\n\r\n ; s=cips")).isInstanceOf(IllegalStateException.class); + assertThatThrownBy(() -> new TagValue(" \r\n \r\n a=b")).isInstanceOf(IllegalStateException.class); + } + + @Test + public void singleFoldAroundATagSpecShouldBeAccepted() { + assertEquals("1", new TagValue("v=1\r\n ").getValue("v").toString()); + assertEquals("b", new TagValue("a\r\n =b;\r\n\tc=d").getValue("a").toString()); + assertEquals("", new TagValue("v= \r\n\t;f=1").getValue("v").toString()); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
