This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git
The following commit(s) were added to refs/heads/master by this push:
new b51f6bf26 Detect and avoid processing cycles in YAML input
(YAMLConfiguration). (#634)
b51f6bf26 is described below
commit b51f6bf26e774f3416fdf782a5e1edf33f32ba82
Author: Gary Gregory <[email protected]>
AuthorDate: Mon May 11 12:24:30 2026 -0400
Detect and avoid processing cycles in YAML input (YAMLConfiguration). (#634)
---
.../AbstractYAMLBasedConfiguration.java | 48 ++++++++++++++--------
.../commons/configuration2/YAMLConfiguration.java | 20 +++------
.../commons/configuration2/io/FileHandler.java | 3 --
.../configuration2/TestYAMLConfiguration.java | 16 +++++++-
.../apache/commons/configuration2/yaml/cycle.yaml | 17 ++++++++
src/test/resources/test.yaml | 3 +-
6 files changed, 69 insertions(+), 38 deletions(-)
diff --git
a/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java
b/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java
index 1e730c3f8..a1573ec1f 100644
---
a/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java
+++
b/src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java
@@ -21,13 +21,16 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.ConfigurationLogger;
import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.commons.lang3.StringUtils;
/**
* <p>
@@ -69,44 +72,51 @@ public class AbstractYAMLBasedConfiguration extends
BaseHierarchicalConfiguratio
}
/**
- * Creates a part of the hierarchical nodes structure of the resulting
configuration. The passed in element is converted
- * into one or multiple configuration nodes. (If list structures are
involved, multiple nodes are returned.)
+ * Creates a part of the hierarchical nodes structure of the resulting
configuration. The passed in element is converted into one or multiple
configuration
+ * nodes. (If list structures are involved, multiple nodes are returned.)
*
- * @param key the key of the new node(s)
- * @param elem the element to be processed
+ * @param key the key of the new node(s).
+ * @param elem the element to be processed.
+ * @param visited the set of visited objects.
* @return a list with configuration nodes representing the element
*/
- private static List<ImmutableNode> constructHierarchy(final String key,
final Object elem) {
+ private static List<ImmutableNode> constructHierarchy(final String key,
final Object elem, final Set<Object> visited) {
if (elem instanceof Map) {
- return parseMap((Map<String, Object>) elem, key);
+ return isVisisted(elem, visited) ? Collections.emptyList() :
parseMap((Map<String, Object>) elem, key, visited);
}
if (elem instanceof Collection) {
- return parseCollection((Collection<Object>) elem, key);
+ return isVisisted(elem, visited) ? Collections.emptyList() :
parseCollection((Collection<Object>) elem, key, visited);
}
return Collections.singletonList(new
ImmutableNode.Builder().name(key).value(elem).create());
}
+ private static boolean isVisisted(final Object elem, final Set<Object>
visited) {
+ return !visited.add(System.identityHashCode(elem));
+ }
+
/**
* Parses a collection structure. The elements of the collection are
processed recursively.
*
- * @param col the collection to be processed
- * @param key the key under which this collection is to be stored
- * @return a node representing this collection
+ * @param col the collection to be processed.
+ * @param key the key under which this collection is to be stored.
+ * @param visited the set of visited objects.
+ * @return a node representing this collection.
*/
- private static List<ImmutableNode> parseCollection(final
Collection<Object> col, final String key) {
- return col.stream().flatMap(elem -> constructHierarchy(key,
elem).stream()).collect(Collectors.toList());
+ private static List<ImmutableNode> parseCollection(final
Collection<Object> col, final String key, final Set<Object> visited) {
+ return col.stream().flatMap(elem -> constructHierarchy(key, elem,
visited).stream()).collect(Collectors.toList());
}
/**
* Parses a map structure. The single keys of the map are processed
recursively.
*
- * @param map the map to be processed
- * @param key the key under which this map is to be stored
+ * @param map the map to be processed.
+ * @param key the key under which this map is to be stored.
+ * @param visited the set of visited objects.
* @return a node representing this map
*/
- private static List<ImmutableNode> parseMap(final Map<String, Object> map,
final String key) {
+ private static List<ImmutableNode> parseMap(final Map<String, Object> map,
final String key, final Set<Object> visited) {
final ImmutableNode.Builder subtree = new
ImmutableNode.Builder().name(key);
- map.forEach((k, v) -> constructHierarchy(k,
v).forEach(subtree::addChild));
+ map.forEach((k, v) -> constructHierarchy(k, v,
visited).forEach(subtree::addChild));
return Collections.singletonList(subtree.create());
}
@@ -159,7 +169,9 @@ public class AbstractYAMLBasedConfiguration extends
BaseHierarchicalConfiguratio
* @param map the map to be processed
*/
protected void load(final Map<String, Object> map) {
- final List<ImmutableNode> roots = constructHierarchy("", map);
- getNodeModel().setRootNode(roots.get(0));
+ final List<ImmutableNode> roots =
constructHierarchy(StringUtils.EMPTY, map, new HashSet<>());
+ if (!roots.isEmpty()) {
+ getNodeModel().setRootNode(roots.get(0));
+ }
}
}
diff --git
a/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java
b/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java
index 72125edbc..6ca91be29 100644
--- a/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java
@@ -21,7 +21,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
-import java.util.Map;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.InputStreamSupport;
@@ -68,8 +67,7 @@ public class YAMLConfiguration extends
AbstractYAMLBasedConfiguration implements
public void dump(final Writer out, final DumperOptions options)
throws ConfigurationException, IOException {
- final Yaml yaml = new Yaml(options);
- yaml.dump(constructMap(getNodeModel().getNodeHandler().getRootNode()),
out);
+ new
Yaml(options).dump(constructMap(getNodeModel().getNodeHandler().getRootNode()),
out);
}
/**
@@ -81,9 +79,7 @@ public class YAMLConfiguration extends
AbstractYAMLBasedConfiguration implements
@Override
public void read(final InputStream in) throws ConfigurationException {
try {
- final Yaml yaml = createYamlForReading(new LoaderOptions());
- final Map<String, Object> map = yaml.load(in);
- load(map);
+ load(createYamlForReading(new LoaderOptions()).load(in));
} catch (final Exception e) {
rethrowException(e);
}
@@ -91,9 +87,7 @@ public class YAMLConfiguration extends
AbstractYAMLBasedConfiguration implements
public void read(final InputStream in, final LoaderOptions options) throws
ConfigurationException {
try {
- final Yaml yaml = createYamlForReading(options);
- final Map<String, Object> map = yaml.load(in);
- load(map);
+ load(createYamlForReading(options).load(in));
} catch (final Exception e) {
rethrowException(e);
}
@@ -102,9 +96,7 @@ public class YAMLConfiguration extends
AbstractYAMLBasedConfiguration implements
@Override
public void read(final Reader in) throws ConfigurationException {
try {
- final Yaml yaml = createYamlForReading(new LoaderOptions());
- final Map<String, Object> map = yaml.load(in);
- load(map);
+ load(createYamlForReading(new LoaderOptions()).load(in));
} catch (final Exception e) {
rethrowException(e);
}
@@ -112,9 +104,7 @@ public class YAMLConfiguration extends
AbstractYAMLBasedConfiguration implements
public void read(final Reader in, final LoaderOptions options) throws
ConfigurationException {
try {
- final Yaml yaml = createYamlForReading(options);
- final Map<String, Object> map = yaml.load(in);
- load(map);
+ load(createYamlForReading(options).load(in));
} catch (final Exception e) {
rethrowException(e);
}
diff --git
a/src/main/java/org/apache/commons/configuration2/io/FileHandler.java
b/src/main/java/org/apache/commons/configuration2/io/FileHandler.java
index 0f28f36b6..6042f546b 100644
--- a/src/main/java/org/apache/commons/configuration2/io/FileHandler.java
+++ b/src/main/java/org/apache/commons/configuration2/io/FileHandler.java
@@ -586,7 +586,6 @@ public class FileHandler {
} catch (final MalformedURLException e1) {
throw new ConfigurationException("Cannot create URL from file %s",
file);
}
-
load(url);
}
@@ -687,7 +686,6 @@ public class FileHandler {
*/
private void load(final URL url, final FileLocator locator) throws
ConfigurationException {
InputStream in = null;
-
try {
final FileSystem fileSystem =
FileLocatorUtils.getFileSystem(locator);
final URLConnectionOptions urlConnectionOptions =
locator.getURLConnectionOptions();
@@ -732,7 +730,6 @@ public class FileHandler {
syncSupport.lock(LockMode.WRITE);
try {
injectFileLocator(url);
-
if (getContent() instanceof InputStreamSupport) {
loadFromStreamDirectly(in);
} else {
diff --git
a/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java
b/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java
index 9ba7c319d..c4f2521c5 100644
--- a/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java
@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Map;
import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.io.FileHandler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -68,9 +69,22 @@ public class TestYAMLConfiguration {
assertEquals("bar", yamlConfiguration.getString("foo"));
}
+ @Test
+ void testCycle() throws ConfigurationException {
+ final YAMLConfiguration configuration = new YAMLConfiguration();
+ final FileHandler handler = new FileHandler(configuration);
+ handler.load(new
File("src/test/resources/org/apache/commons/configuration2/yaml/cycle.yaml"));
+ }
+
@Test
void testDoubleStringValues() {
- final Object property = yamlConfiguration.getProperty("key5.example");
+ final Object property = yamlConfiguration.getProperty("key5.example2");
+ assertEquals(Arrays.asList("a", "a", "value"), property);
+ }
+
+ @Test
+ void testDoubleStringEmptyValues() {
+ final Object property = yamlConfiguration.getProperty("key5.example1");
assertEquals(Arrays.asList("", "", "value"), property);
}
diff --git
a/src/test/resources/org/apache/commons/configuration2/yaml/cycle.yaml
b/src/test/resources/org/apache/commons/configuration2/yaml/cycle.yaml
new file mode 100644
index 000000000..a00023265
--- /dev/null
+++ b/src/test/resources/org/apache/commons/configuration2/yaml/cycle.yaml
@@ -0,0 +1,17 @@
+# 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
+#
+# https://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.
+
+root: &cycle
+ - *cycle
diff --git a/src/test/resources/test.yaml b/src/test/resources/test.yaml
index 6052e50aa..ee972d3dd 100644
--- a/src/test/resources/test.yaml
+++ b/src/test/resources/test.yaml
@@ -26,4 +26,5 @@ martin:
skill: Elite
key5:
- example: [ '', '', value]
\ No newline at end of file
+ example1: [ '', '', value]
+ example2: [ 'a', 'a', value]
\ No newline at end of file