This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-text.git
commit 36798250bb098a139042d5138ffd02fa07735a6b Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sat Apr 6 18:28:20 2024 -0400 Add StringLookupFactory.builder() for fencing Path resolution of the file, properties and XML lookups --- src/changes/changes.xml | 1 + .../commons/text/lookup/StringLookupFactory.java | 274 +++++++++++++++------ .../commons/text/lookup/FileStringLookupTest.java | 57 +++-- .../text/lookup/PropertiesStringLookupTest.java | 43 ++-- .../external/CustomStringSubstitutorTest.java | 49 ++++ 5 files changed, 317 insertions(+), 107 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 7c4228bb..724f5098 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -50,6 +50,7 @@ The <action> type attribute can be add,update,fix,remove. <action type="add" dev="ggregory" due-to="Gary Gregory">Add StringLookupFactory.fileStringLookup(Path...) and deprecated fileStringLookup().</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add StringLookupFactory.propertiesStringLookup(Path...) and deprecated propertiesStringLookup().</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add StringLookupFactory.xmlStringLookup(Map, Path...) and deprecated xmlStringLookup() and xmlStringLookup(Map).</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add StringLookupFactory.builder() for fencing Path resolution of the file, properties and XML lookups.</action> <!-- FIX --> <action issue="TEXT-232" type="fix" dev="ggregory" due-to="Arnout Engelen, Gary Gregory">WordUtils.containsAllWords​() may throw PatternSyntaxException.</action> <action issue="TEXT-175" type="fix" dev="ggregory" due-to="David Lavati, seanfabs, Gary Gregory, Bruno P. Kinoshita">Fix regression for determining whitespace in WordUtils #519.</action> diff --git a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java index a80e13ee..e60e4917 100644 --- a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java +++ b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Properties; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; import javax.xml.xpath.XPathFactory; @@ -216,6 +217,49 @@ import org.apache.commons.text.StringSubstitutor; */ public final class StringLookupFactory { + /** + * Builds instance of {@link StringLookupFactory}. + * + * @since 1.12.0 + */ + public static final class Builder implements Supplier<StringLookupFactory> { + + /** + * Fences. + */ + private Path[] fences; + + @Override + public StringLookupFactory get() { + return new StringLookupFactory(fences); + } + + /** + * Sets Path resolution fences. + * <p> + * Path Fences apply to the file, property, and XML string lookups. + * </p> + * + * @param fences Path resolution fences. + * @return this. + */ + public Builder setFences(final Path... fences) { + this.fences = fences; + return this; + } + + } + + /** + * Constructs a new {@link Builder}. + * + * @return a new {@link Builder} + * @since 1.12.0 + */ + public static Builder builder() { + return new Builder(); + } + /** * Internal class used to construct the default {@link StringLookup} map used by {@link StringLookupFactory#addDefaultStringLookups(Map)}. */ @@ -570,10 +614,22 @@ public final class StringLookupFactory { } /** - * No need to build instances for now. + * Fences. + */ + private final Path[] fences; + + /** + * Constructs a new instance. */ private StringLookupFactory() { - // empty + this(null); + } + + /** + * Constructs a new instance. + */ + private StringLookupFactory(final Path[] fences) { + this.fences = fences; } /** @@ -816,62 +872,94 @@ public final class StringLookupFactory { } /** - * Returns the FileStringLookup singleton instance. + * Returns a file StringLookup instance. * <p> - * Using a {@link StringLookup} from the {@link StringLookupFactory}: + * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException} + * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions. + * </p> + * <em>Using a fenced StringLookup</em> + * <p> + * To use a fenced {@link StringLookup}, use {@link StringLookupFactory#builder()}: + * </p> + * + * <pre> + * // Make the fence the current directory + * StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get(); + * factory.fileStringLookup().lookup("UTF-8:com/domain/document.txt"); + * + * // throws IllegalArgumentException + * factory.fileStringLookup().lookup("UTF-8:/rootdir/foo/document.txt"); + * + * // throws IllegalArgumentException + * factory.fileStringLookup().lookup("UTF-8:../document.txt"); + * </pre> + * + * <em>Using an unfenced StringLookup</em> + * <p> + * To use an unfenced {@link StringLookup}, use {@link StringLookupFactory#INSTANCE}: * </p> * * <pre> * StringLookupFactory.INSTANCE.fileStringLookup().lookup("UTF-8:com/domain/document.properties"); * </pre> + * + * <em>Using a StringLookup with StringSubstitutor</em> * <p> - * Using a {@link StringSubstitutor}: + * To build a fenced StringSubstitutor, use: + * </p> + * + * <pre> + * // Make the fence the current directory + * final StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get(); + * final StringSubstitutor stringSubstitutor = new StringSubstitutor(factory.interpolatorStringLookup()); + * stringSubstitutor.replace("... ${file:UTF-8:com/domain/document.txt} ...")); + * + * // throws IllegalArgumentException + * stringSubstitutor.replace("... ${file:UTF-8:/rootdir/foo/document.txt} ...")); + * </pre> + * <p> + * Using an unfenced {@link StringSubstitutor}: * </p> * * <pre> - * StringSubstitutor.createInterpolator().replace("... ${file:UTF-8:com/domain/document.properties} ...")); + * StringSubstitutor.createInterpolator().replace("... ${file:UTF-8:com/domain/document.txt} ...")); * </pre> * <p> - * The above examples convert {@code "UTF-8:com/domain/document.properties"} to the contents of the file. + * The above examples convert {@code "UTF-8:com/domain/document.txt"} to the contents of the file. * </p> * - * @return The FileStringLookup singleton instance. + * @return a file StringLookup instance. * @since 1.5 - * @deprecated Use {@link #fileStringLookup(Path...)}. */ - @Deprecated public StringLookup fileStringLookup() { - return FileStringLookup.INSTANCE; + return fences != null ? fileStringLookup(fences) : FileStringLookup.INSTANCE; } /** - * Returns a fenced FileStringLookup instance. + * Returns a fenced file StringLookup instance. * <p> - * Using a {@link StringLookup} from the {@link StringLookupFactory} fenced by the current directory ({@code Paths.get("")}): + * To use a {@link StringLookup} fenced by the current directory, use: * </p> * * <pre> - * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("UTF-8:com/domain/document.properties"); - * </pre> - * <p> - * Using a {@link StringSubstitutor} fenced by the current directory ({@code Paths.get("")}): - * </p> + * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("UTF-8:com/domain/document.txt"); * - * <pre> - * StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); - * final InterpolatorStringLookup stringLookup = (InterpolatorStringLookup) stringSubstitutor.getStringLookup(); - * stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_FILE, StringLookupFactory.INSTANCE.fileStringLookup(Paths.get(""))); - * stringSubstitutor.replace("... ${file:UTF-8:com/domain/document.properties} ...")); + * // throws IllegalArgumentException + * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("UTF-8:/rootdir/foo/document.txt"); + * + * // throws IllegalArgumentException + * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("UTF-8:../com/domain/document.txt"); * </pre> * <p> - * The above examples convert {@code "UTF-8:com/domain/document.properties"} to the contents of the file. + * The above example converts {@code "UTF-8:com/domain/document.txt"} to the contents of the file. * </p> * <p> - * Methods {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't resolves in a fence. + * {@link StringSubstitutor} methods like {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't + * resolves in a fence. * </p> * * @param fences The fences guarding Path resolution. - * @return The FileStringLookup singleton instance. + * @return a file StringLookup instance. * @since 1.12.0 */ public StringLookup fileStringLookup(final Path... fences) { @@ -1048,9 +1136,13 @@ public final class StringLookupFactory { } /** - * Returns the PropertiesStringLookup singleton instance. + * Returns a Properties StringLookup instance. * <p> - * Looks up the value for the key in the format "DocumentPath::MyKey". + * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException} + * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions. + * </p> + * <p> + * We looks up a value for the key in the format "DocumentPath::MyKey". * </p> * <p> * Note the use of "::" instead of ":" to allow for "C:" drive letters in paths. @@ -1058,16 +1150,48 @@ public final class StringLookupFactory { * <p> * For example: "com/domain/document.properties::MyKey". * </p> + * <em>Using a fenced StringLookup</em> + * <p> + * To use a fenced {@link StringLookup}, use {@link StringLookupFactory#builder()}: + * </p> + * + * <pre> + * // Make the fence the current directory + * StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get(); + * factory.propertiesStringLookup().lookup("com/domain/document.properties::MyKey"); + * + * // throws IllegalArgumentException + * factory.propertiesStringLookup().lookup("/com/domain/document.properties::MyKey"); * + * // throws IllegalArgumentException + * factory.propertiesStringLookup().lookup("../com/domain/document.properties::MyKey"); + * </pre> + * + * <em>Using an unfenced StringLookup</em> * <p> - * Using a {@link StringLookup} from the {@link StringLookupFactory}: + * To use an unfenced {@link StringLookup}, use {@link StringLookupFactory#INSTANCE}: * </p> * * <pre> * StringLookupFactory.INSTANCE.propertiesStringLookup().lookup("com/domain/document.properties::MyKey"); * </pre> + * + * <em>Using a StringLookup with StringSubstitutor</em> * <p> - * Using a {@link StringSubstitutor}: + * To build a fenced StringSubstitutor, use: + * </p> + * + * <pre> + * // Make the fence the current directory + * final StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get(); + * final StringSubstitutor stringSubstitutor = new StringSubstitutor(factory.interpolatorStringLookup()); + * stringSubstitutor.replace("... ${properties:com/domain/document.properties::MyKey} ...")); + * + * // throws IllegalArgumentException + * stringSubstitutor.replace("... ${properties:/rootdir/foo/document.properties::MyKey} ...")); + * </pre> + * <p> + * Using an unfenced {@link StringSubstitutor}: * </p> * * <pre> @@ -1078,17 +1202,15 @@ public final class StringLookupFactory { * "com/domain/document.properties". * </p> * - * @return The PropertiesStringLookup singleton instance. + * @return a Properties StringLookup instance. * @since 1.5 - * @deprecated Use {@link #propertiesStringLookup(Path...)}. */ - @Deprecated public StringLookup propertiesStringLookup() { - return PropertiesStringLookup.INSTANCE; + return fences != null ? propertiesStringLookup(fences) : PropertiesStringLookup.INSTANCE; } /** - * Returns a fenced PropertiesStringLookup instance. + * Returns a fenced Properties StringLookup instance. * <p> * Looks up the value for the key in the format "DocumentPath::MyKey":. * </p> @@ -1098,34 +1220,30 @@ public final class StringLookupFactory { * <p> * For example: "com/domain/document.properties::MyKey". * </p> - * * <p> - * Using a {@link StringLookup} from the {@link StringLookupFactory} fenced by the current directory ({@code Paths.get("")}): + * To use a {@link StringLookup} fenced by the current directory, use: * </p> * * <pre> - * StringLookupFactory.INSTANCE.propertiesStringLookup(Paths.get("")).lookup("com/domain/document.properties::MyKey"); - * </pre> - * <p> - * Using a {@link StringSubstitutor} fenced by the current directory ({@code Paths.get("")}): - * </p> + * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("com/domain/document.properties::MyKey"); * - * <pre> - * StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); - * final InterpolatorStringLookup stringLookup = (InterpolatorStringLookup) stringSubstitutor.getStringLookup(); - * stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_PROPERTIES, StringLookupFactory.INSTANCE.fileStringLookup(Paths.get(""))); - * stringSubstitutor.replace("... ${properties:com/domain/document.properties::MyKey} ...")); + * // throws IllegalArgumentException + * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("com/domain/document.properties::MyKey"); + * + * // throws IllegalArgumentException + * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("com/domain/document.properties::MyKey"); * </pre> * <p> - * The above examples convert {@code "com/domain/document.properties::MyKey"} to the key value in the properties file at the path + * The above example converts {@code "com/domain/document.properties::MyKey"} to the key value in the properties file at the path * "com/domain/document.properties". * </p> * <p> - * Methods {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't resolves in a fence. + * {@link StringSubstitutor} methods like {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't + * resolves in a fence. * </p> * * @param fences The fences guarding Path resolution. - * @return The PropertiesStringLookup singleton instance. + * @return a Properties StringLookup instance. * @since 1.12.0 */ public StringLookup propertiesStringLookup(final Path... fences) { @@ -1434,9 +1552,13 @@ public final class StringLookupFactory { } /** - * Returns the XmlStringLookup singleton instance. + * Returns an XML StringLookup instance. + * <p> + * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException} + * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions. + * </p> * <p> - * Looks up the value for the key in the format "DocumentPath:XPath". + * We look up the value for the key in the format "DocumentPath:XPath". * </p> * <p> * For example: "com/domain/document.xml:/path/to/node". @@ -1459,19 +1581,21 @@ public final class StringLookupFactory { * The above examples convert {@code "com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML document. * </p> * - * @return The XmlStringLookup singleton instance. + * @return An XML StringLookup instance. * @since 1.5 - * @deprecated Use {@link #xmlStringLookup(Map, Path...)}. */ - @Deprecated public StringLookup xmlStringLookup() { - return XmlStringLookup.INSTANCE; + return fences != null ? xmlStringLookup(XmlStringLookup.DEFAULT_FEATURES, fences) : XmlStringLookup.INSTANCE; } /** - * Returns a XmlStringLookup instance. + * Returns an XML StringLookup instance. + * <p> + * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException} + * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions. + * </p> * <p> - * Looks up the value for the key in the format "DocumentPath:XPath". + * We look up the value for the key in the format "DocumentPath:XPath". * </p> * <p> * For example: "com/domain/document.xml:/path/to/node". @@ -1495,20 +1619,22 @@ public final class StringLookupFactory { * </p> * * @param xPathFactoryFeatures XPathFactory features to set. - * @return The XmlStringLookup singleton instance. + * @return An XML StringLookup instance. * @see XPathFactory#setFeature(String, boolean) * @since 1.11.0 - * @deprecated Use {@link #xmlStringLookup(Map, Path...)}. */ - @Deprecated public StringLookup xmlStringLookup(final Map<String, Boolean> xPathFactoryFeatures) { - return new XmlStringLookup(xPathFactoryFeatures); + return xmlStringLookup(xPathFactoryFeatures, fences); } /** - * Returns a fenced XmlStringLookup instance. + * Returns a fenced XML StringLookup instance. + * <p> + * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException} + * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions. + * </p> * <p> - * Looks up the value for the key in the format "DocumentPath:XPath". + * We look up the value for the key in the format "DocumentPath:XPath". * </p> * <p> * For example: "com/domain/document.xml:/path/to/node". @@ -1521,22 +1647,30 @@ public final class StringLookupFactory { * StringLookupFactory.INSTANCE.xmlStringLookup(map, Pathe.get("")).lookup("com/domain/document.xml:/path/to/node"); * </pre> * <p> - * Using a {@link StringSubstitutor}: + * <p> + * To use a {@link StringLookup} fenced by the current directory, use: * </p> * * <pre> - * StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); - * final InterpolatorStringLookup stringLookup = (InterpolatorStringLookup) stringSubstitutor.getStringLookup(); - * stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_PROPERTIES, StringLookupFactory.INSTANCE.fileStringLookup(Paths.get(""))); - * stringSubstitutor.replace("... ${xml:com/domain/document.xml:/path/to/node} ...")); + * StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("com/domain/document.xml:/path/to/node"); + * + * // throws IllegalArgumentException + * StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("/rootdir/foo/document.xml:/path/to/node"); + * + * // throws IllegalArgumentException + * StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("../com/domain/document.xml:/path/to/node"); * </pre> * <p> * The above examples convert {@code "com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML document. * </p> + * <p> + * {@link StringSubstitutor} methods like {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't + * resolves in a fence. + * </p> * * @param xPathFactoryFeatures XPathFactory features to set. * @param fences The fences guarding Path resolution. - * @return The XmlStringLookup singleton instance. + * @return An XML StringLookup instance. * @since 1.12.0 */ public StringLookup xmlStringLookup(final Map<String, Boolean> xPathFactoryFeatures, final Path... fences) { diff --git a/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java b/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java index c9dd6742..b22af473 100644 --- a/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java +++ b/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java @@ -17,14 +17,17 @@ package org.apache.commons.text.lookup; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -33,8 +36,25 @@ import org.junit.jupiter.api.Test; */ public class FileStringLookupTest { + private static final Path DOCUEMENT_PATH = Paths.get("src/test/resources/org/apache/commons/text/document.properties"); private static final Path CURRENT_PATH = Paths.get(StringUtils.EMPTY); + public static String readDocumentFixtureString() throws IOException { + return new String(Files.readAllBytes(DOCUEMENT_PATH), StandardCharsets.UTF_8); + } + + public static void testFence(final String expectedString, final FileStringLookup fileStringLookup) { + Assertions.assertEquals(expectedString, fileStringLookup.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties")); + assertThrows(IllegalArgumentException.class, () -> fileStringLookup.lookup("UTF-8:/src/test/resources/org/apache/commons/text/document.properties")); + assertThrows(IllegalArgumentException.class, () -> fileStringLookup.lookup("UTF-8:../src/test/resources/org/apache/commons/text/document.properties")); + } + + public static void testFence(final StringSubstitutor stringSubstitutor) throws IOException { + assertEquals(readDocumentFixtureString(), stringSubstitutor.replace("${file:UTF-8:" + DOCUEMENT_PATH + "}")); + assertThrows(IllegalArgumentException.class, () -> stringSubstitutor.replace("${file:UTF-8:/foo.txt}")); + assertThrows(IllegalArgumentException.class, () -> stringSubstitutor.replace("${file:UTF-8:../foo.txt}")); + } + @Test public void testDefaultInstanceBadCharsetName() { assertThrows(IllegalArgumentException.class, @@ -58,8 +78,7 @@ public class FileStringLookupTest { @Test public void testDefaultInstanceOne() throws Exception { - final byte[] expectedBytes = Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties")); - final String expectedString = new String(expectedBytes, StandardCharsets.UTF_8); + final String expectedString = readDocumentFixtureString(); Assertions.assertEquals(expectedString, FileStringLookup.INSTANCE.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties")); } @@ -74,47 +93,49 @@ public class FileStringLookupTest { final FileStringLookup fileStringLookup = new FileStringLookup(Paths.get("dir does not exist at all")); assertThrows(IllegalArgumentException.class, () -> fileStringLookup.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties")); assertThrows(IllegalArgumentException.class, () -> fileStringLookup.lookup("UTF-8:/src/test/resources/org/apache/commons/text/document.properties")); + assertThrows(IllegalArgumentException.class, () -> fileStringLookup.lookup("UTF-8:../src/test/resources/org/apache/commons/text/document.properties")); } @Test public void testFenceBadDirPlusGoodOne() throws Exception { - final byte[] expectedBytes = Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties")); - final String expectedString = new String(expectedBytes, StandardCharsets.UTF_8); + final String expectedString = readDocumentFixtureString(); final FileStringLookup fileStringLookup = new FileStringLookup(Paths.get("dir does not exist at all"), CURRENT_PATH); - Assertions.assertEquals(expectedString, fileStringLookup.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties")); - assertThrows(IllegalArgumentException.class, () -> fileStringLookup.lookup("UTF-8:/src/test/resources/org/apache/commons/text/document.properties")); + testFence(expectedString, fileStringLookup); } @Test public void testFenceCurrentDirOne() throws Exception { - final byte[] expectedBytes = Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties")); - final String expectedString = new String(expectedBytes, StandardCharsets.UTF_8); + final String expectedString = readDocumentFixtureString(); final FileStringLookup fileStringLookup = new FileStringLookup(CURRENT_PATH); - Assertions.assertEquals(expectedString, fileStringLookup.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties")); - assertThrows(IllegalArgumentException.class, () -> fileStringLookup.lookup("UTF-8:/src/test/resources/org/apache/commons/text/document.properties")); + testFence(expectedString, fileStringLookup); } @Test public void testFenceCurrentDirPlusOne() throws Exception { - final byte[] expectedBytes = Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties")); - final String expectedString = new String(expectedBytes, StandardCharsets.UTF_8); + final String expectedString = readDocumentFixtureString(); final FileStringLookup fileStringLookup = new FileStringLookup(Paths.get("target"), CURRENT_PATH); - Assertions.assertEquals(expectedString, fileStringLookup.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties")); - assertThrows(IllegalArgumentException.class, () -> fileStringLookup.lookup("UTF-8:/src/test/resources/org/apache/commons/text/document.properties")); + testFence(expectedString, fileStringLookup); } @Test public void testFenceEmptyOne() throws Exception { - final byte[] expectedBytes = Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties")); - final String expectedString = new String(expectedBytes, StandardCharsets.UTF_8); + final String expectedString = readDocumentFixtureString(); Assertions.assertEquals(expectedString, new FileStringLookup().lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties")); } @Test public void testFenceNullOne() throws Exception { - final byte[] expectedBytes = Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties")); - final String expectedString = new String(expectedBytes, StandardCharsets.UTF_8); + final String expectedString = readDocumentFixtureString(); Assertions.assertEquals(expectedString, new FileStringLookup((Path[]) null).lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties")); } + + @Test + public void testInterpolatorReplace() throws IOException { + final StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); + assertEquals(readDocumentFixtureString(), stringSubstitutor.replace("${file:UTF-8:" + DOCUEMENT_PATH + "}")); + final InterpolatorStringLookup stringLookup = (InterpolatorStringLookup) stringSubstitutor.getStringLookup(); + stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_FILE, StringLookupFactory.INSTANCE.fileStringLookup(CURRENT_PATH)); + testFence(stringSubstitutor); + } } diff --git a/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java b/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java index a296c6d5..d7068101 100644 --- a/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java +++ b/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java @@ -35,13 +35,25 @@ import org.junit.jupiter.api.Test; */ public class PropertiesStringLookupTest { - private static final Path[] NULL_PATH_ARRAY = null; private static final Path CURRENT_PATH = Paths.get(StringUtils.EMPTY); // NOT "."! private static final String DOC_RELATIVE = "src/test/resources/org/apache/commons/text/document.properties"; private static final String DOC_ROOT = "/foo.txt"; private static final String KEY = "mykey"; private static final String KEY_RELATIVE = PropertiesStringLookup.toPropertyKey(DOC_RELATIVE, KEY); private static final String KEY_ROOT = PropertiesStringLookup.toPropertyKey(DOC_ROOT, KEY); + private static final Path[] NULL_PATH_ARRAY = null; + + public static void testFence(final StringSubstitutor stringSubstitutor) { + assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_RELATIVE + "}")); + assertThrows(IllegalArgumentException.class, () -> stringSubstitutor.replace("${file:UTF-8:/foo.txt}")); + assertThrows(IllegalArgumentException.class, () -> stringSubstitutor.replace("${file:UTF-8:../foo.txt}")); + } + + @Test + public void testFenceOne() { + assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(CURRENT_PATH).lookup(KEY_ROOT)); + assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(Paths.get("not a dir at all"), CURRENT_PATH).lookup(KEY_ROOT)); + } @Test public void testInterpolator() { @@ -50,13 +62,21 @@ public class PropertiesStringLookupTest { } @Test - public void testInterpolatorReplaceFile() { + public void testInterpolatorNestedColon() { + final StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); + // Need to handle "C:" in the sys prop user.dir. + final String replaced = stringSubstitutor.replace("$${properties:${sys:user.dir}/" + KEY_RELATIVE + "}"); + assertEquals("${properties:" + System.getProperty("user.dir") + "/src/test/resources/org/apache/commons/text/document.properties::mykey}", replaced); + assertEquals("Hello World!", stringSubstitutor.replace(replaced)); + } + + @Test + public void testInterpolatorReplace() { final StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_RELATIVE + "}")); final InterpolatorStringLookup stringLookup = (InterpolatorStringLookup) stringSubstitutor.getStringLookup(); stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_FILE, StringLookupFactory.INSTANCE.fileStringLookup(CURRENT_PATH)); - assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_RELATIVE + "}")); - assertThrows(IllegalArgumentException.class, () -> stringSubstitutor.replace("${file:UTF-8:/foo.txt}")); + testFence(stringSubstitutor); } @Test @@ -69,15 +89,6 @@ public class PropertiesStringLookupTest { assertThrows(IllegalArgumentException.class, () -> stringSubstitutor.replace("${properties:UTF-8:/foo.txt}")); } - @Test - public void testInterpolatorNestedColon() { - final StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); - // Need to handle "C:" in the sys prop user.dir. - final String replaced = stringSubstitutor.replace("$${properties:${sys:user.dir}/" + KEY_RELATIVE + "}"); - assertEquals("${properties:" + System.getProperty("user.dir") + "/src/test/resources/org/apache/commons/text/document.properties::mykey}", replaced); - assertEquals("Hello World!", stringSubstitutor.replace(replaced)); - } - @Test public void testInterpolatorWithParameterizedKey() { final Map<String, String> map = new HashMap<>(); @@ -135,12 +146,6 @@ public class PropertiesStringLookupTest { assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(CURRENT_PATH).lookup(KEY_ROOT)); } - @Test - public void testFenceOne() { - assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(CURRENT_PATH).lookup(KEY_ROOT)); - assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(Paths.get("not a dir at all"), CURRENT_PATH).lookup(KEY_ROOT)); - } - @Test public void testToString() { // does not blow up and gives some kind of string. diff --git a/src/test/java/org/apache/commons/text/lookup/external/CustomStringSubstitutorTest.java b/src/test/java/org/apache/commons/text/lookup/external/CustomStringSubstitutorTest.java new file mode 100644 index 00000000..81ac1f1e --- /dev/null +++ b/src/test/java/org/apache/commons/text/lookup/external/CustomStringSubstitutorTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.commons.text.lookup.external; + +import java.io.IOException; +import java.nio.file.Paths; + +import org.apache.commons.text.StringSubstitutor; +import org.apache.commons.text.lookup.FileStringLookupTest; +import org.apache.commons.text.lookup.PropertiesStringLookupTest; +import org.apache.commons.text.lookup.StringLookupFactory; +import org.junit.jupiter.api.Test; + +/** + * Tests building a fenced {@link StringSubstitutor} to exclude the use of package-private elements. + */ +public class CustomStringSubstitutorTest { + + private StringSubstitutor createStringSubstitutor() { + final StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get(); + return new StringSubstitutor(factory.interpolatorStringLookup()); + } + + @Test + public void testFencedFiles() throws IOException { + FileStringLookupTest.testFence(createStringSubstitutor()); + } + + @Test + public void testFencedProperties() { + PropertiesStringLookupTest.testFence(createStringSubstitutor()); + } + +}