From 9e8b6feb546982dac33922b34e1f2432c8681ab6 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 12 Jan 2022 17:36:48 +0100 Subject: [PATCH 1/2] Polishing --- .../util/xml/XmlValidationModeDetector.java | 27 ++++++++++++------- .../xml/XmlValidationModeDetectorTests.java | 15 ++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java index 19814c2b83..32e81c6f1f 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java +++ b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,7 @@ public class XmlValidationModeDetector { /** - * The token in a XML document that declares the DTD to use for validation + * The token in an XML document that declares the DTD to use for validation * and thus that DTD validation is being used. */ private static final String DOCTYPE = "DOCTYPE"; @@ -82,13 +82,15 @@ public class XmlValidationModeDetector { /** * Detect the validation mode for the XML document in the supplied {@link InputStream}. - * Note that the supplied {@link InputStream} is closed by this method before returning. + *

Note that the supplied {@link InputStream} is closed by this method before returning. * @param inputStream the InputStream to parse * @throws IOException in case of I/O failure * @see #VALIDATION_DTD * @see #VALIDATION_XSD */ public int detectValidationMode(InputStream inputStream) throws IOException { + this.inComment = false; + // Peek into the file to look for DOCTYPE. try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { boolean isDtdValidated = false; @@ -125,9 +127,11 @@ public class XmlValidationModeDetector { } /** - * Does the supplied content contain an XML opening tag. If the parse state is currently - * in an XML comment then this method always returns false. It is expected that all comment - * tokens will have consumed for the supplied content before passing the remainder to this method. + * Determine if the supplied content contains an XML opening tag. + *

It is expected that all comment tokens will have been consumed for the + * supplied content before passing the remainder to this method. However, as + * a sanity check, if the parse state is currently in an XML comment this + * method always returns {@code false}. */ private boolean hasOpeningTag(String content) { if (this.inComment) { @@ -166,7 +170,7 @@ public class XmlValidationModeDetector { } /** - * Consume the next comment token, update the "inComment" flag + * Consume the next comment token, update the "inComment" flag, * and return the remaining content. */ @Nullable @@ -183,14 +187,19 @@ public class XmlValidationModeDetector { return commentToken(line, START_COMMENT, true); } + /** + * Try to consume the {@link #END_COMMENT} token. + * @see #commentToken(String, String, boolean) + */ private int endComment(String line) { return commentToken(line, END_COMMENT, false); } /** * Try to consume the supplied token against the supplied content and update the - * in comment parse state to the supplied value. Returns the index into the content - * which is after the token or -1 if the token is not found. + * "in comment" parse state to the supplied value. + *

Returns the index into the content which is after the token or -1 if the + * token is not found. */ private int commentToken(String line, String token, boolean inCommentIfPresent) { int index = line.indexOf(token); diff --git a/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java b/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java index 631a61d4f7..5951d29830 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java +++ b/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.util.xml; +import java.io.IOException; import java.io.InputStream; import org.junit.jupiter.params.ParameterizedTest; @@ -39,8 +40,16 @@ class XmlValidationModeDetectorTests { @ValueSource(strings = { "dtdWithTrailingComment.xml", "dtdWithLeadingComment.xml", "dtdWithCommentOnNextLine.xml", "dtdWithMultipleComments.xml" }) void dtdDetection(String fileName) throws Exception { - InputStream inputStream = getClass().getResourceAsStream(fileName); - assertThat(xmlValidationModeDetector.detectValidationMode(inputStream)).isEqualTo(VALIDATION_DTD); + assertValidationMode(fileName, VALIDATION_DTD); + } + + + private void assertValidationMode(String fileName, int expectedValidationMode) throws IOException { + try (InputStream inputStream = getClass().getResourceAsStream(fileName)) { + assertThat(xmlValidationModeDetector.detectValidationMode(inputStream)) + .as("Validation Mode") + .isEqualTo(expectedValidationMode); + } } } From 4b1b25496bfd72c288c3af07c741183d0d90567a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 13 Jan 2022 14:44:05 +0100 Subject: [PATCH 2/2] Improve comment parsing in DTD/XSD detection algorithm Prior to this commit, XmlValidationModeDetector did not properly parse all categories of comments (described below). When such categories of comments were encountered XmlValidationModeDetector may have incorrectly detected that an XML file used a DTD when it used an XSD, or vice versa. This commit revises the parsing algorithm in XmlValidationModeDetector so that multi-line comments and multiple comments on a single line are properly recognized. Specifically, with this commit the following categories of comments are now handled properly. - Multiple comments on a single line - Multi-line comment: beginning on one line and then ending on another line with an additional comment following on that same line. - Multi-line comment: beginning at the end of XML content on one line and then spanning multiple lines. Closes gh-27915 --- .../util/xml/XmlValidationModeDetector.java | 19 +++++++--------- .../xml/XmlValidationModeDetectorTests.java | 22 +++++++++++++++++-- .../util/xml/dtdWithNoComments.xml | 6 +++++ ...WithTrailingCommentAcrossMultipleLines.xml | 6 +++++ .../util/xml/xsdWithDoctypeInComment.xml | 8 +++++++ ...CommentWithAdditionalCommentOnSameLine.xml | 9 ++++++++ .../util/xml/xsdWithMultipleComments.xml | 10 +++++++++ .../util/xml/xsdWithNoComments.xml | 8 +++++++ 8 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 spring-core/src/test/resources/org/springframework/util/xml/dtdWithNoComments.xml create mode 100644 spring-core/src/test/resources/org/springframework/util/xml/dtdWithTrailingCommentAcrossMultipleLines.xml create mode 100644 spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInComment.xml create mode 100644 spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml create mode 100644 spring-core/src/test/resources/org/springframework/util/xml/xsdWithMultipleComments.xml create mode 100644 spring-core/src/test/resources/org/springframework/util/xml/xsdWithNoComments.xml diff --git a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java index 32e81c6f1f..869f9ca051 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java +++ b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java @@ -97,7 +97,7 @@ public class XmlValidationModeDetector { String content; while ((content = reader.readLine()) != null) { content = consumeCommentTokens(content); - if (this.inComment || !StringUtils.hasText(content)) { + if (!StringUtils.hasText(content)) { continue; } if (hasDoctype(content)) { @@ -143,11 +143,10 @@ public class XmlValidationModeDetector { } /** - * Consume all leading and trailing comments in the given String and return - * the remaining content, which may be empty since the supplied content might - * be all comment data. + * Consume all comments in the given String and return the remaining content, + * which may be empty since the supplied content might be all comment data. + *

This method takes the current "in comment" parsing state into account. */ - @Nullable private String consumeCommentTokens(String line) { int indexOfStartComment = line.indexOf(START_COMMENT); if (indexOfStartComment == -1 && !line.contains(END_COMMENT)) { @@ -156,17 +155,15 @@ public class XmlValidationModeDetector { String result = ""; String currLine = line; - if (indexOfStartComment >= 0) { + if (!this.inComment && (indexOfStartComment >= 0)) { result = line.substring(0, indexOfStartComment); currLine = line.substring(indexOfStartComment); } - while ((currLine = consume(currLine)) != null) { - if (!this.inComment && !currLine.trim().startsWith(START_COMMENT)) { - return result + currLine; - } + if ((currLine = consume(currLine)) != null) { + result += consumeCommentTokens(currLine); } - return null; + return result; } /** diff --git a/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java b/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java index 5951d29830..35a5e13222 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java +++ b/spring-core/src/test/java/org/springframework/util/xml/XmlValidationModeDetectorTests.java @@ -24,6 +24,7 @@ import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.util.xml.XmlValidationModeDetector.VALIDATION_DTD; +import static org.springframework.util.xml.XmlValidationModeDetector.VALIDATION_XSD; /** * Unit tests for {@link XmlValidationModeDetector}. @@ -37,12 +38,29 @@ class XmlValidationModeDetectorTests { @ParameterizedTest - @ValueSource(strings = { "dtdWithTrailingComment.xml", "dtdWithLeadingComment.xml", "dtdWithCommentOnNextLine.xml", - "dtdWithMultipleComments.xml" }) + @ValueSource(strings = { + "dtdWithNoComments.xml", + "dtdWithLeadingComment.xml", + "dtdWithTrailingComment.xml", + "dtdWithTrailingCommentAcrossMultipleLines.xml", + "dtdWithCommentOnNextLine.xml", + "dtdWithMultipleComments.xml" + }) void dtdDetection(String fileName) throws Exception { assertValidationMode(fileName, VALIDATION_DTD); } + @ParameterizedTest + @ValueSource(strings = { + "xsdWithNoComments.xml", + "xsdWithMultipleComments.xml", + "xsdWithDoctypeInComment.xml", + "xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml" + }) + void xsdDetection(String fileName) throws Exception { + assertValidationMode(fileName, VALIDATION_XSD); + } + private void assertValidationMode(String fileName, int expectedValidationMode) throws IOException { try (InputStream inputStream = getClass().getResourceAsStream(fileName)) { diff --git a/spring-core/src/test/resources/org/springframework/util/xml/dtdWithNoComments.xml b/spring-core/src/test/resources/org/springframework/util/xml/dtdWithNoComments.xml new file mode 100644 index 0000000000..299c52abef --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/dtdWithNoComments.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/dtdWithTrailingCommentAcrossMultipleLines.xml b/spring-core/src/test/resources/org/springframework/util/xml/dtdWithTrailingCommentAcrossMultipleLines.xml new file mode 100644 index 0000000000..68776b4f61 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/dtdWithTrailingCommentAcrossMultipleLines.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInComment.xml b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInComment.xml new file mode 100644 index 0000000000..3f0fd05ed9 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInComment.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml new file mode 100644 index 0000000000..3d831932a9 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithDoctypeInOpenCommentWithAdditionalCommentOnSameLine.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/xsdWithMultipleComments.xml b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithMultipleComments.xml new file mode 100644 index 0000000000..8843539e4a --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithMultipleComments.xml @@ -0,0 +1,10 @@ + + + + diff --git a/spring-core/src/test/resources/org/springframework/util/xml/xsdWithNoComments.xml b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithNoComments.xml new file mode 100644 index 0000000000..4f178c702f --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/util/xml/xsdWithNoComments.xml @@ -0,0 +1,8 @@ + + + + +