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 c9be15366 Disable include http[s] by default (#633)
c9be15366 is described below

commit c9be15366278781477c7ba643938214e0c7326c7
Author: Gary Gregory <[email protected]>
AuthorDate: Thu Apr 30 16:27:16 2026 -0400

    Disable include http[s] by default (#633)
    
    * Allow include error handling to be customized through a listener.
    
    * [CONFIGURATION-756] Allow for custom behavior to handle errors loading 
included properties files.
    
    * [CONFIGURATION-756] Allow for custom behavior to handle errors loading
    included properties files.
    
    * Javadoc.
    
    * Disable includes of files via http[s] by default.
    
    ---------
    
    Co-authored-by: Gary Gregory <[email protected]>
    Co-authored-by: Gary Gregory <[email protected]>
---
 pom.xml                                            |   5 +
 .../configuration2/PropertiesConfiguration.java    |  61 ++++-
 .../ConfigurationDeniedException.java}             |  21 +-
 .../io/AbsoluteNameLocationStrategy.java           |  34 ++-
 .../io/AbstractFileLocationStrategy.java           | 273 ++++++++++++++++++++-
 .../io/BasePathLocationStrategy.java               |  27 +-
 .../io/ClasspathLocationStrategy.java              |  25 +-
 .../io/CombinedLocationStrategy.java               |  85 ++++++-
 .../configuration2/io/FileLocationStrategy.java    |   4 +
 .../configuration2/io/FileLocatorUtils.java        |  24 +-
 .../io/FileSystemLocationStrategy.java             |  25 +-
 .../io/HomeDirectoryLocationStrategy.java          |  87 ++++++-
 .../io/ProvidedURLLocationStrategy.java            |  34 ++-
 .../TestPropertiesConfiguration.java               | 160 +++++++++++-
 .../io/TestAbstractFileLocationStrategy.java}      |  13 +-
 .../io/TestProvidedURLLocationStrategy.java        |  42 ++++
 ...clude-load-url-bad-scheme-exception.properties} |   2 +-
 ... => include-load-url-file-exception.properties} |   2 +-
 ...ude-load-url-host-unknown-exception.properties} |   0
 ... => include-load-url-http-exception.properties} |   0
 20 files changed, 846 insertions(+), 78 deletions(-)

diff --git a/pom.xml b/pom.xml
index 9e6990746..b7b8db408 100644
--- a/pom.xml
+++ b/pom.xml
@@ -272,6 +272,11 @@
       <artifactId>junit-jupiter</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.junit-pioneer</groupId>
+      <artifactId>junit-pioneer</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-core</artifactId>
diff --git 
a/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java 
b/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
index c1ac7c2df..c8e7a762a 100644
--- 
a/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
+++ 
b/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
@@ -38,8 +38,11 @@ import java.util.regex.Pattern;
 import org.apache.commons.configuration2.convert.ListDelimiterHandler;
 import org.apache.commons.configuration2.convert.ValueTransformer;
 import org.apache.commons.configuration2.event.ConfigurationEvent;
+import org.apache.commons.configuration2.ex.ConfigurationDeniedException;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
+import org.apache.commons.configuration2.io.AbstractFileLocationStrategy;
+import 
org.apache.commons.configuration2.io.AbstractFileLocationStrategy.AbstractBuilder;
 import org.apache.commons.configuration2.io.FileHandler;
 import org.apache.commons.configuration2.io.FileLocator;
 import org.apache.commons.configuration2.io.FileLocatorAware;
@@ -159,6 +162,7 @@ import org.apache.commons.text.translate.UnicodeEscaper;
  * class, which is responsible for storing the layout of the parsed properties 
file (i.e. empty lines, comments, and
  * such things). The {@code getLayout()} method can be used to obtain this 
layout object. With {@code setLayout()} a new
  * layout object can be set. This should be done before a properties file was 
loaded.
+ * </p>
  * <p>
  * Like other {@code Configuration} implementations, this class uses a {@code 
Synchronizer} object to control concurrent
  * access. By choosing a suitable implementation of the {@code Synchronizer} 
interface, an instance can be made
@@ -166,6 +170,7 @@ import org.apache.commons.text.translate.UnicodeEscaper;
  * the {@code Synchronizer}. The intended usage is that these properties are 
set once at construction time through the
  * builder and after that remain constant. If you wish to change such 
properties during life time of an instance, you
  * have to use the {@code lock()} and {@code unlock()} methods manually to 
ensure that other threads see your changes.
+ * </p>
  * <p>
  * As this class extends {@link AbstractConfiguration}, all basic features 
like variable interpolation, list handling,
  * or data type conversions are available as well. This is described in the 
chapter
@@ -173,8 +178,28 @@ import org.apache.commons.text.translate.UnicodeEscaper;
  * and AbstractConfiguration</a> of the user's guide. There is also a separate 
chapter dealing with
  * <a 
href="commons.apache.org/proper/commons-configuration/userguide/howto_properties.html">
 Properties files</a> in
  * special.
+ * </p>
+ * <p>
+ * As of version 2.15.0, by default, when including files, the only URL 
schemes allowed are {@code file} and {@code jar}. To override this default,
+ * you can either use the system property {@code 
org.apache.commons.configuration2.io.FileLocationStrategy.schemes} or build a 
subclass of
+ * {@link AbstractFileLocationStrategy}.
+ * </p>
+ * <strong>Using System Properties</strong>
+ * <p>
+ * The system property {@code 
org.apache.commons.configuration2.io.FileLocationStrategy.schemes} String value 
must be a comma-separated list of schemes,
+ * where the default is {@code "file,jar"}, and the complete list is {@code 
"file,http,https,jar"}.
+ * </p>
+ * <strong>Using a Builder</strong>
+ * <p>
+ * The root builder for {@link AbstractFileLocationStrategy} is {@link 
AbstractBuilder} where you define allowed schemes and hosts through its setter
+ * methods.
+ * </p>
+ * <p>
+ * For example, to programatically enable the shemes "file", "http", "https", 
and "jar" for all strategies, see {@link AbstractFileLocationStrategy}.
+ * </p>
  *
  * @see java.util.Properties#load
+ * @see AbstractFileLocationStrategy
  */
 public class PropertiesConfiguration extends BaseConfiguration implements 
FileBasedConfiguration, FileLocatorAware {
 
@@ -1336,7 +1361,17 @@ public class PropertiesConfiguration extends 
BaseConfiguration implements FileBa
     }
 
     /**
-     * Stores the current {@code FileLocator} for a following IO operation. 
The {@code FileLocator} is needed to resolve
+     * Gets the file locator.
+     *
+     * @return the file locator.
+     * @since 2.15.0
+     */
+    public FileLocator getLocator() {
+        return locator;
+    }
+
+    /**
+     * Sets the current {@code FileLocator} for a following IO operation. The 
{@code FileLocator} is needed to resolve
      * include files with relative file names.
      *
      * @param locator the current {@code FileLocator}
@@ -1377,7 +1412,7 @@ public class PropertiesConfiguration extends 
BaseConfiguration implements FileBa
     }
 
     /**
-     * Helper method for loading an included properties file. This method is 
called by {@code load()} when an
+     * Loads an included properties file. This method is called by {@code 
load()} when an
      * {@code include} property is encountered. It tries to resolve relative 
file names based on the current base path. If
      * this fails, a resolution based on the location of this properties file 
is tried.
      *
@@ -1389,21 +1424,23 @@ public class PropertiesConfiguration extends 
BaseConfiguration implements FileBa
     private void loadIncludeFile(final String fileName, final boolean 
optional, final Deque<URL> seenStack) throws ConfigurationException {
         if (locator == null) {
             throw new ConfigurationException(
-                "Load operation not properly initialized! Do not call 
read(InputStream) directly, but use a FileHandler to load a configuration.");
+                    "Load operation not properly initialized. Do not call 
read(InputStream) directly, use a FileHandler to load a configuration.");
         }
-
-        URL url = locateIncludeFile(locator.getBasePath(), fileName);
-        if (url == null) {
-            final URL baseURL = locator.getSourceURL();
-            if (baseURL != null) {
-                url = locateIncludeFile(baseURL.toString(), fileName);
+        URL url = null;
+        try {
+            url = locateIncludeFile(locator.getBasePath(), fileName);
+            if (url == null) {
+                final URL baseURL = locator.getSourceURL();
+                if (baseURL != null) {
+                    url = locateIncludeFile(baseURL.toString(), fileName);
+                }
             }
+        } catch (ConfigurationDeniedException e) {
+            getIncludeListener().accept(new ConfigurationException(e));
         }
-
         if (optional && url == null) {
             return;
         }
-
         if (url == null) {
             getIncludeListener().accept(new ConfigurationException(new 
FileNotFoundException(fileName), "Cannot resolve include file %s", fileName));
         } else {
@@ -1432,7 +1469,7 @@ public class PropertiesConfiguration extends 
BaseConfiguration implements FileBa
     }
 
     /**
-     * Tries to obtain the URL of an include file using the specified 
(optional) base path and file name.
+     * Locates the URL of an include file using the specified (optional) base 
path and file name.
      *
      * @param basePath the base path
      * @param fileName the file name
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/ex/ConfigurationDeniedException.java
similarity index 52%
copy from 
src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
copy to 
src/main/java/org/apache/commons/configuration2/ex/ConfigurationDeniedException.java
index 53cbe45ba..69ef3ca25 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/ex/ConfigurationDeniedException.java
@@ -14,11 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.configuration2.io;
+
+package org.apache.commons.configuration2.ex;
 
 /**
- * Abstracts services for {@link FileLocationStrategy} implementations.
+ * Thrown when an application only grants specific configurations for elements 
like URL schemes and hosts.
+ *
+ * @since 2.15.0
  */
-abstract class AbstractFileLocationStrategy implements FileLocationStrategy {
+public class ConfigurationDeniedException extends 
ConfigurationRuntimeException {
+
+    private static final long serialVersionUID = 1L;
 
+    /**
+     * Constructs a new {@code ConfigurationDeniedException} with specified 
detail message using {@link String#format(String,Object...)}.
+     *
+     * @param message the error message.
+     * @param args    arguments to the error message.
+     * @see String#format(String,Object...)
+     */
+    public ConfigurationDeniedException(final String message, final Object... 
args) {
+        super(message, args);
+    }
 }
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/AbsoluteNameLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/io/AbsoluteNameLocationStrategy.java
index 7a1d145b2..ce673c860 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/AbsoluteNameLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/AbsoluteNameLocationStrategy.java
@@ -28,33 +28,57 @@ import org.apache.commons.lang3.StringUtils;
  * This strategy ignores the URL and the base path stored in the passed in 
{@link FileLocator}. It is only triggered by
  * absolute names in the locator's {@code fileName} component.
  * </p>
+ * <p>
+ * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+ * </p>
  *
+ * @see AbstractFileLocationStrategy
  * @since 2.0
  */
 public class AbsoluteNameLocationStrategy extends AbstractFileLocationStrategy 
{
 
     /**
-     * A singleton instance of this strategy.
+     * Builds new instances of {@link ProvidedURLLocationStrategy}.
+     *
+     * @return a new builder.
+     * @since 2.15.0
      */
-    static final AbsoluteNameLocationStrategy INSTANCE = new 
AbsoluteNameLocationStrategy();
+    public static StrategyBuilder<AbsoluteNameLocationStrategy> builder() {
+        return new StrategyBuilder<>(AbsoluteNameLocationStrategy::new);
+    }
 
     /**
-     * Constructs a new instance.
+     * Constructs a new instance where URL resources are bound by {@link 
AbstractFileLocationStrategy.AbstractBuilder}.
+     * <p>
+     * </p>
      */
     public AbsoluteNameLocationStrategy() {
         // empty
     }
 
     /**
-     * {@inheritDoc} This implementation constructs a {@code File} object from 
the locator's file name (if defined). If this
+     * Constructs a new instance where URL resources are bound by {@link 
AbstractFileLocationStrategy.AbstractBuilder}.
+     *
+     * @param builder How to build the instance.
+     * @since 2.15.0
+     */
+    public AbsoluteNameLocationStrategy(final AbstractBuilder<?, ?> builder) {
+        super(builder);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation constructs a {@code File} object from the locator's 
file name (if defined). If this
      * results in an absolute file name pointing to an existing file, the 
corresponding URL is returned.
+     * </p>
      */
     @Override
     public URL locate(final FileSystem fileSystem, final FileLocator locator) {
         if (StringUtils.isNotEmpty(locator.getFileName())) {
             final File file = new File(locator.getFileName());
             if (file.isAbsolute() && file.exists()) {
-                return FileLocatorUtils.convertFileToURL(file);
+                return check(FileLocatorUtils.convertFileToURL(file));
             }
         }
         return null;
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
index 53cbe45ba..1eb5716a5 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
@@ -14,11 +14,280 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.commons.configuration2.io;
 
+import java.net.URL;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.configuration2.ex.ConfigurationDeniedException;
+import org.apache.commons.io.build.AbstractSupplier;
+import org.apache.commons.lang3.StringUtils;
+
 /**
- * Abstracts services for {@link FileLocationStrategy} implementations.
+ * Abstracts services for FileLocationStrategy implementations.
+ * <p>
+ * Note that some FileLocationStrategy implementation use URLs internally to 
encode file locations.
+ * </p>
+ * <p>
+ * As of version 2.15.0, by default, the only URL schemes allowed are {@code 
file} and {@code jar}. To override this default, you can either use the system
+ * property {@code 
org.apache.commons.configuration2.io.FileLocationStrategy.schemes} or build a 
subclass of {@link AbstractFileLocationStrategy}.
+ * </p>
+ * <strong>Using System Properties</strong>
+ * <p>
+ * The system property {@code 
org.apache.commons.configuration2.io.FileLocationStrategy.schemes} String value 
must be a comma-separated list of schemes,
+ * where the default is {@code "file,jar"}, and the complete list is {@code 
"file,http,https,jar"}.
+ * </p>
+ * <strong>Using a Builder</strong>
+ * <p>
+ * The root builder for {@link AbstractFileLocationStrategy} is {@link 
AbstractBuilder} where you define allowed schemes and hosts through its setter
+ * methods.
+ * </p>
+ * <p>
+ * For example, to programatically enable the shemes "file", "http", "https", 
and "jar" for all strategies, you write:
+ * </p>
+ * <pre>{@code
+ * final PropertiesConfiguration pc = new PropertiesConfiguration();
+ *      pc.setIncludeListener(PropertiesConfiguration.NOOP_INCLUDE_LISTENER);
+ *      final FileHandler handler = new FileHandler(pc);
+ *      final CombinedLocationStrategy.Builder builder = new 
CombinedLocationStrategy.Builder()
+ *              .setSchemes(new TreeSet<>(Arrays.asList("file", "http", 
"https", "jar")));
+ *      // @formatter:off
+ *      handler.setLocationStrategy(builder.setSubStrategies(Arrays.asList(
+ *              new ProvidedURLLocationStrategy(builder),
+ *              new FileSystemLocationStrategy(builder),
+ *              new AbsoluteNameLocationStrategy(builder),
+ *              new BasePathLocationStrategy(builder),
+ *              new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(true).getUnchecked(),
+ *              new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(false).getUnchecked(),
+ *              new ClasspathLocationStrategy(builder)))
+ *              .get());
+ *      // @formatter:on
+ *      handler.setBasePath(TEST_BASE_PATH);
+ *      
handler.setFileName("include-load-url-host-unknown-exception.properties");
+ *      handler.load();
+ * }</pre>
+ *
+ *
+ * @since 2.15.0
+ * @see FileLocationStrategy
  */
-abstract class AbstractFileLocationStrategy implements FileLocationStrategy {
+public abstract class AbstractFileLocationStrategy implements 
FileLocationStrategy {
+
+    /**
+     * Builds new instances for subclasses.
+     * <p>
+     * As of version 2.15.0, by default, the only URL schemes allowed are 
{@code file} and {@code jar}. To override this default, you can either use the 
system
+     * property {@code 
org.apache.commons.configuration2.io.FileLocationStrategy.schemes} or build a 
subclass of {@link AbstractFileLocationStrategy}.
+     * </p>
+     * <strong>Using System Properties</strong>
+     * <p>
+     * The system property {@code 
org.apache.commons.configuration2.io.FileLocationStrategy.schemes} String value 
must be a comma-separated list of schemes,
+     * where the default is {@code "file,jar"}, and the complete list is 
{@code "file,http,https,jar"}.
+     * </p>
+     * <strong>Using a Builder</strong>
+     * <p>
+     * The root builder for {@link AbstractFileLocationStrategy} is {@link 
AbstractBuilder} where you define allowed schemes and hosts through its setter
+     * methods.
+     * </p>
+     * <p>
+     * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+     * </p>
+     *
+     * @param <T> The type of {@link FileLocationStrategy} to build.
+     * @param <B> The builder type.
+     */
+    public abstract static class AbstractBuilder<T extends 
FileLocationStrategy, B extends AbstractBuilder<T, B>> extends 
AbstractSupplier<T, B> {
+
+        /**
+         * Enabled URL-based hosts, empty means all are enabled. Host are 
case-insensitive.
+         */
+        private Set<Pattern> hosts = Collections.emptySet();
+        /**
+         * Enabled URL-based schemes, empty means all are enabled. Schemes are 
case-insensitive.
+         */
+        private Set<String> schemes = Collections.emptySet();
+
+        /**
+         * Constructs a new instance for subclasses.
+         */
+        public AbstractBuilder() {
+            // empty
+        }
+
+        Set<Pattern> getHosts() {
+            return hosts;
+        }
+
+        Set<String> getSchemes() {
+            return schemes;
+        }
+
+        /**
+         * Sets enabled URL-based hosts, empty means all are enabled. URL 
hosts are case-insensitive.
+         *
+         * @param hosts enabled URL-based hosts.
+         * @return {@code this} instance.
+         */
+        public B setHosts(final Set<Pattern> hosts) {
+            this.hosts = hosts != null ? hosts : Collections.emptySet();
+            return asThis();
+        }
+
+        /**
+         * Sets enabled URL-based hosts, empty means all are enabled. URL 
hosts are case-insensitive.
+         *
+         * @param hosts Regular expressions enabled URL-based hosts.
+         * @return {@code this} instance.
+         */
+        public B setHostsRegEx(final Set<String> hosts) {
+            return setHosts(hosts.stream().map(e -> Pattern.compile(e, 
Pattern.CASE_INSENSITIVE)).collect(Collectors.toSet()));
+        }
+
+        /**
+         * Sets enabled URL-based schemes, empty means all are enabled. URL 
schemes are case-insensitive.
+         *
+         * @param schemes enabled URL-based schemes, the default null means 
all schemes are allowed.
+         * @return {@code this} instance.
+         */
+        public B setSchemes(final Set<String> schemes) {
+            this.schemes = schemes != null ? schemes : Collections.emptySet();
+            return asThis();
+        }
+    }
+
+    /**
+     * Builds new instances of T.
+     *
+     * @param <T> The type of {@link FileLocationStrategy} to build.
+     */
+    public static class StrategyBuilder<T extends FileLocationStrategy> 
extends AbstractBuilder<T, StrategyBuilder<T>> {
+
+        /**
+         * Either set this or implement get().
+         */
+        private final Function<StrategyBuilder<T>, T> function;
+
+        /**
+         * Constructs a new instance for subclasses.
+         *
+         * @param function Builds an instance of T.
+         */
+        public StrategyBuilder(final Function<StrategyBuilder<T>, T> function) 
{
+            this.function = Objects.requireNonNull(function, "function");
+        }
+
+        @Override
+        public T get() {
+            return function.apply(asThis());
+        }
+    }
+
+    /**
+     * Default schemes.
+     */
+    private static final String DEFAULT_SCHEMES = "file,jar";
+    /**
+     * The system property key {@code 
org.apache.commons.configuration2.io.FileLocationStrategy.schemes}.
+     * <p>
+     * If absent, defaults to {@code "file,jar"}.
+     * </p>
+     * <p>
+     * For complete functionality, use {@code "file,http,https,jar"}.
+     * </p>
+     */
+    private static final String KEY_SCHEMES = 
"org.apache.commons.configuration2.io.FileLocationStrategy.schemes";
+
+    private static Set<String> getSchemesProperty() {
+        final Set<String> set = new LinkedHashSet<>();
+        final String[] split = System.getProperty(KEY_SCHEMES, 
DEFAULT_SCHEMES).split(",");
+        Collections.addAll(set, split);
+        return set;
+    }
+
+    /**
+     * Enabled URL-based hosts, empty means all are enabled. Host are 
case-insensitive.
+     */
+    private final Set<Pattern> hosts;
+    /**
+     * Enabled URL-based schemes, empty means all are enabled. Schemes are 
case-insensitive.
+     */
+    private final Set<String> schemes;
+
+    /**
+     * Constructs a new instance where the enabled URL schemes are read the 
system property
+     * {@code 
"org.apache.commons.configuration2.io.FileLocationStrategy.schemes"}.
+     * <p>
+     * If absent, defaults to {@code "file,jar"}.
+     * </p>
+     * <p>
+     * For complete functionality, use {@code "file,http,https,jar"}.
+     * </p>
+     */
+    AbstractFileLocationStrategy() {
+        this(getSchemesProperty());
+    }
+
+    AbstractFileLocationStrategy(final AbstractBuilder<?, ?> builder) {
+        Objects.requireNonNull(builder, "builder");
+        this.schemes = builder.schemes;
+        this.hosts = builder.hosts != null ? builder.hosts : 
Collections.emptySet();
+    }
+
+    AbstractFileLocationStrategy(final Set<String> schemes) {
+        this.schemes = schemes;
+        this.hosts = Collections.emptySet();
+    }
+
+    URL check(final URL url) {
+        if (url != null) {
+            checkScheme("scheme", url, url.getProtocol(), schemes);
+            checkHost("host", url, url.getHost(), hosts);
+        }
+        return url;
+    }
+
+    void checkHost(final String type, final URL url, final String value, final 
Set<Pattern> validSet) {
+        if (!validSet.isEmpty() && StringUtils.isNotEmpty(value)) {
+            hosts.stream().filter(p -> 
p.matcher(StringUtils.toRootLowerCase(value)).matches()).findFirst()
+                    .orElseThrow(() -> new 
ConfigurationDeniedException(String.format("URL %s is not enabled: %s; must be 
one of %s", type, value, validSet)));
+        }
+    }
+
+    void checkScheme(final String type, final URL url, final String value, 
final Set<String> validSet) {
+        if (!validSet.isEmpty() && value != null && 
!validSet.contains(StringUtils.toRootLowerCase(value))) {
+            throw new ConfigurationDeniedException(String.format(
+                    "URL %s \"%s\" is not enabled, must be one of %s, override 
defaults with the system property \"%s\", complete set: 
\"file,http,https,jar\"",
+                    type, value, validSet, KEY_SCHEMES));
+        }
+    }
+
+    /**
+     * Gets the enabled hosts.
+     *
+     * @return the enabled hosts.
+     */
+    Set<Pattern> getHosts() {
+        return hosts;
+    }
+
+    /**
+     * Gets the enabled schemes.
+     *
+     * @return the enabled schemes.
+     */
+    Set<String> getSchemes() {
+        return schemes;
+    }
 
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [schemes=" + schemes + ", 
hosts=" + hosts + "]";
+    }
 }
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/BasePathLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/io/BasePathLocationStrategy.java
index 22edc02d3..5d8411e96 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/BasePathLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/BasePathLocationStrategy.java
@@ -29,23 +29,42 @@ import org.apache.commons.lang3.StringUtils;
  * base path (if present) and the file name. If the resulting path points to a 
valid file, the corresponding URL is
  * returned.
  * </p>
+ * <p>
+ * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+ * </p>
  *
+ * @see AbstractFileLocationStrategy
  * @since 2.0
  */
 public class BasePathLocationStrategy extends AbstractFileLocationStrategy {
 
     /**
-     * A singleton instance of this strategy.
+     * Builds new instances of {@link ProvidedURLLocationStrategy}.
+     *
+     * @return a new builder.
+     * @since 2.15.0
      */
-    static final BasePathLocationStrategy INSTANCE = new 
BasePathLocationStrategy();
+    public static StrategyBuilder<BasePathLocationStrategy> builder() {
+        return new StrategyBuilder<>(BasePathLocationStrategy::new);
+    }
 
     /**
-     * Constructs a new instance.
+     * Constructs a new instance where URL resources are bound by {@link 
AbstractFileLocationStrategy.AbstractBuilder}.
      */
     public BasePathLocationStrategy() {
         // empty
     }
 
+    /**
+     * Constructs a new instance where URL resources are bound by {@link 
AbstractFileLocationStrategy.AbstractBuilder}.
+     *
+     * @param builder How to build the instance.
+     * @since 2.15.0
+     */
+    public BasePathLocationStrategy(final AbstractBuilder<?, ?> builder) {
+        super(builder);
+    }
+
     /**
      * {@inheritDoc} This implementation uses utility methods from {@code 
FileLocatorUtils} to generate a {@code File} from
      * the locator's base path and file name. If this {@code File} exists, its 
URL is returned.
@@ -55,7 +74,7 @@ public class BasePathLocationStrategy extends 
AbstractFileLocationStrategy {
         if (StringUtils.isNotEmpty(locator.getFileName())) {
             final File file = 
FileLocatorUtils.constructFile(locator.getBasePath(), locator.getFileName());
             if (file.isFile()) {
-                return FileLocatorUtils.convertFileToURL(file);
+                return check(FileLocatorUtils.convertFileToURL(file));
             }
         }
         return null;
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/ClasspathLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/io/ClasspathLocationStrategy.java
index f0f588f18..303a47fca 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/ClasspathLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/ClasspathLocationStrategy.java
@@ -26,15 +26,24 @@ import org.apache.commons.lang3.StringUtils;
  * This strategy implementation ignores the URL and the base path components 
of the passed in {@link FileLocator}. It
  * tries to look up the file name on both the class path and the system class 
path.
  * </p>
+ * <p>
+ * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+ * </p>
  *
+ * @see AbstractFileLocationStrategy
  * @since 2.0
  */
 public class ClasspathLocationStrategy extends AbstractFileLocationStrategy {
 
     /**
-     * A singleton instance of this strategy.
+     * Builds new instances of {@link ProvidedURLLocationStrategy}.
+     *
+     * @return a new builder.
+     * @since 2.15.0
      */
-    static final ClasspathLocationStrategy INSTANCE = new 
ClasspathLocationStrategy();
+    public static StrategyBuilder<ClasspathLocationStrategy> builder() {
+        return new StrategyBuilder<>(ClasspathLocationStrategy::new);
+    }
 
     /**
      * Constructs a new instance.
@@ -43,11 +52,21 @@ public class ClasspathLocationStrategy extends 
AbstractFileLocationStrategy {
         // empty
     }
 
+    /**
+     * Constructs a new instance.
+     *
+     * @param builder How to build the instance.
+     * @since 2.15.0
+     */
+    public ClasspathLocationStrategy(final AbstractBuilder<?, ?> builder) {
+        super(builder);
+    }
+
     /**
      * {@inheritDoc} This implementation looks up the locator's file name as a 
resource on the class path.
      */
     @Override
     public URL locate(final FileSystem fileSystem, final FileLocator locator) {
-        return StringUtils.isEmpty(locator.getFileName()) ? null : 
FileLocatorUtils.getClasspathResource(locator.getFileName());
+        return check(StringUtils.isEmpty(locator.getFileName()) ? null : 
FileLocatorUtils.getClasspathResource(locator.getFileName()));
     }
 }
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/CombinedLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/io/CombinedLocationStrategy.java
index 8838bc835..c1eccf0c0 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/CombinedLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/CombinedLocationStrategy.java
@@ -16,10 +16,13 @@
  */
 package org.apache.commons.configuration2.io;
 
+import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Set;
+import java.util.regex.Pattern;
 
 /**
  * A specialized implementation of a {@code FileLocationStrategy} which 
encapsulates an arbitrary number of
@@ -37,29 +40,93 @@ import java.util.Collections;
  * requirements. Note that the order in which strategies are added to a {@code 
CombinedLocationStrategy} matters: sub
  * strategies are queried in the same order as they appear in the collection 
passed to the constructor.
  * </p>
+ * <p>
+ * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+ * </p>
  *
+ * @see AbstractFileLocationStrategy
  * @since 2.0
  */
 public class CombinedLocationStrategy extends AbstractFileLocationStrategy {
 
+    /**
+     * Builds new instances of {@link CombinedLocationStrategy}.
+     *
+     * @since 2.15.0
+     */
+    public static class Builder extends 
AbstractBuilder<CombinedLocationStrategy, Builder> {
+
+        /** A collection with all sub strategies managed by this object. */
+        private Collection<? extends FileLocationStrategy> subStrategies;
+
+        @Override
+        public CombinedLocationStrategy get() throws IOException {
+            return new CombinedLocationStrategy(this);
+        }
+
+        /**
+         * Sets the collection with sub strategies.
+         *
+         * @param subStrategies the collection with sub strategies.
+         * @return {@code this} instance.
+         */
+        public Builder setSubStrategies(final Collection<FileLocationStrategy> 
subStrategies) {
+            this.subStrategies = subStrategies;
+            return asThis();
+        }
+
+        /**
+         * Propagates properties of the parent builder scheme and host to 
subStrategies.
+         *
+         * @return {@code this} instance.
+         */
+        public Builder propagate() {
+            if (subStrategies != null) {
+                subStrategies.forEach(e -> {
+                    if (e instanceof AbstractFileLocationStrategy) {
+                        AbstractFileLocationStrategy afls = 
(AbstractFileLocationStrategy) e;
+                        final Set<String> schemes = afls.getSchemes();
+                        schemes.clear();
+                        schemes.addAll(getSchemes());
+                        final Set<Pattern> hosts = afls.getHosts();
+                        hosts.clear();
+                        hosts.addAll(getHosts());
+                    }
+                });
+            }
+            return asThis();
+        }
+
+    }
+
     /** A collection with all sub strategies managed by this object. */
     private final Collection<FileLocationStrategy> subStrategies;
 
     /**
-     * Creates a new instance of {@code CombinedLocationStrategy} and 
initializes it with the provided sub strategies. The
-     * passed in collection must not be <strong>null</strong> or contain 
<strong>null</strong> elements.
+     * Constructs a new instance.
      *
-     * @param subs the collection with sub strategies
-     * @throws IllegalArgumentException if the collection is 
<strong>null</strong> or has <strong>null</strong> elements
+     * @param builder How to build the instance.
      */
-    public CombinedLocationStrategy(final Collection<? extends 
FileLocationStrategy> subs) {
-        if (subs == null) {
+    private CombinedLocationStrategy(final Builder builder) {
+        super(builder);
+        if (builder.subStrategies == null) {
             throw new IllegalArgumentException("Collection with sub strategies 
must not be null.");
         }
-        subStrategies = Collections.unmodifiableCollection(new 
ArrayList<>(subs));
-        if (subStrategies.contains(null)) {
+        if (builder.subStrategies.contains(null)) {
             throw new IllegalArgumentException("Collection with sub strategies 
contains null entry.");
         }
+        subStrategies = Collections.unmodifiableCollection(new 
ArrayList<>(builder.subStrategies));
+    }
+
+    /**
+     * Creates a new instance of {@code CombinedLocationStrategy} and 
initializes it with the provided sub strategies. The
+     * passed in collection must not be <strong>null</strong> or contain 
<strong>null</strong> elements.
+     *
+     * @param subs the collection with sub strategies.
+     * @throws IllegalArgumentException if the collection is 
<strong>null</strong> or has <strong>null</strong> elements.
+     */
+    public CombinedLocationStrategy(final Collection<FileLocationStrategy> 
subs) {
+        this(new Builder().setSubStrategies(subs));
     }
 
     /**
@@ -79,7 +146,7 @@ public class CombinedLocationStrategy extends 
AbstractFileLocationStrategy {
         for (final FileLocationStrategy sub : getSubStrategies()) {
             final URL url = sub.locate(fileSystem, locator);
             if (url != null) {
-                return url;
+                return check(url);
             }
         }
         return null;
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/FileLocationStrategy.java 
b/src/main/java/org/apache/commons/configuration2/io/FileLocationStrategy.java
index f8b48d5c5..24b6f056b 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/FileLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/FileLocationStrategy.java
@@ -36,7 +36,11 @@ import java.net.URL;
  * {@code FileLocationStrategy} implementations; so a file can be searched 
using multiple strategies until one of them
  * is successful.
  * </p>
+ * <p>
+ * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+ * </p>
  *
+ * @see AbstractFileLocationStrategy
  * @since 2.0
  */
 public interface FileLocationStrategy {
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/FileLocatorUtils.java 
b/src/main/java/org/apache/commons/configuration2/io/FileLocatorUtils.java
index 46bd4a2ee..e15ccf725 100644
--- a/src/main/java/org/apache/commons/configuration2/io/FileLocatorUtils.java
+++ b/src/main/java/org/apache/commons/configuration2/io/FileLocatorUtils.java
@@ -73,7 +73,9 @@ public final class FileLocatorUtils {
      * <li>Otherwise, the strategy gives up and returns <strong>null</strong> 
indicating that the file cannot be resolved.</li>
      * </ul>
      */
-    public static final FileLocationStrategy DEFAULT_LOCATION_STRATEGY = 
initDefaultLocationStrategy();
+    // @formatter:off
+    public static final FileLocationStrategy DEFAULT_LOCATION_STRATEGY = 
newDefaultLocationStrategy();
+    // @formatter:on
 
     /** Constant for the file URL protocol */
     private static final String FILE_SCHEME = "file:";
@@ -420,19 +422,19 @@ public final class FileLocatorUtils {
      * of the {@link #DEFAULT_LOCATION_STRATEGY} member field.
      *
      * @return the default {@code FileLocationStrategy}
+     * @since 2.15.0
      */
-    private static FileLocationStrategy initDefaultLocationStrategy() {
+    public static FileLocationStrategy newDefaultLocationStrategy() {
         // @formatter:off
-        final FileLocationStrategy[] subStrategies = {
-                ProvidedURLLocationStrategy.INSTANCE,
-                FileSystemLocationStrategy.INSTANCE,
-                AbsoluteNameLocationStrategy.INSTANCE,
-                BasePathLocationStrategy.INSTANCE,
-                new HomeDirectoryLocationStrategy(true),
-                new HomeDirectoryLocationStrategy(false),
-                ClasspathLocationStrategy.INSTANCE};
+        return new CombinedLocationStrategy(Arrays.asList(
+               new ProvidedURLLocationStrategy(),
+               new FileSystemLocationStrategy(),
+               new AbsoluteNameLocationStrategy(),
+               new BasePathLocationStrategy(),
+               new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(true).getUnchecked(),
+               new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(false).getUnchecked(),
+               new ClasspathLocationStrategy()));
         // @formatter:on
-        return new CombinedLocationStrategy(Arrays.asList(subStrategies));
     }
 
     /**
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/FileSystemLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/io/FileSystemLocationStrategy.java
index 09a3cb503..66c562bb0 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/FileSystemLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/FileSystemLocationStrategy.java
@@ -26,15 +26,24 @@ import java.net.URL;
  * file name. These properties are passed to the {@code locateFromURL()} 
method of {@code FileSystem}. So the burden of
  * resolving the file is delegated to the {@code FileSystem}.
  * </p>
+ * <p>
+ * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+ * </p>
  *
+ * @see AbstractFileLocationStrategy
  * @since 2.0
  */
 public class FileSystemLocationStrategy extends AbstractFileLocationStrategy {
 
     /**
-     * A singleton instance of this strategy.
+     * Builds new instances of {@link ProvidedURLLocationStrategy}.
+     *
+     * @return a new builder.
+     * @since 2.15.0
      */
-    static final FileSystemLocationStrategy INSTANCE = new 
FileSystemLocationStrategy();
+    public static StrategyBuilder<FileSystemLocationStrategy> builder() {
+        return new StrategyBuilder<>(FileSystemLocationStrategy::new);
+    }
 
     /**
      * Constructs a new instance.
@@ -43,11 +52,21 @@ public class FileSystemLocationStrategy extends 
AbstractFileLocationStrategy {
         // empty
     }
 
+    /**
+     * Constructs a new instance.
+     *
+     * @param builder How to build the instance.
+     * @since 2.15.0
+     */
+    public FileSystemLocationStrategy(final AbstractBuilder<?, ?> builder) {
+        super(builder);
+    }
+
     /**
      * {@inheritDoc} This implementation delegates to the {@code FileSystem}.
      */
     @Override
     public URL locate(final FileSystem fileSystem, final FileLocator locator) {
-        return fileSystem.locateFromURL(locator.getBasePath(), 
locator.getFileName());
+        return check(fileSystem.locateFromURL(locator.getBasePath(), 
locator.getFileName()));
     }
 }
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/HomeDirectoryLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/io/HomeDirectoryLocationStrategy.java
index 868357693..85efc9a15 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/HomeDirectoryLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/HomeDirectoryLocationStrategy.java
@@ -17,6 +17,7 @@
 package org.apache.commons.configuration2.io;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.URL;
 
 import org.apache.commons.lang3.StringUtils;
@@ -33,15 +34,61 @@ import org.apache.commons.lang3.SystemProperties;
  * </p>
  * <p>
  * When constructing an instance it can be configured whether the base path 
should be taken into account. If this option
- * is set, the base path is appended to the home directory if it is not 
<strong>null</strong>. This is useful for instance to
+ * is set, the base path is appended to the home directory if it is not {@code 
null}. This is useful for instance to
  * select a specific sub directory of the user's home directory. If this 
option is set to <strong>false</strong>, the base path is
  * always ignored, and only the file name is evaluated.
  * </p>
+ * <p>
+ * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+ * </p>
+ *
+ * @see AbstractFileLocationStrategy
  */
 public class HomeDirectoryLocationStrategy extends 
AbstractFileLocationStrategy {
 
     /**
-     * Obtains the home directory to be used by a new instance. If a directory 
name is provided, it is used. Otherwise, the
+     * Builds new instances of {@link HomeDirectoryLocationStrategy}.
+     *
+     * @since 2.15.0
+     */
+    public static class Builder extends 
AbstractBuilder<HomeDirectoryLocationStrategy, Builder> {
+
+        /** The flag whether the base path is to be taken into account. */
+        private boolean evaluateBasePath;
+
+        /** The home directory to be searched for the requested file. */
+        private String homeDirectory;
+
+        @Override
+        public HomeDirectoryLocationStrategy get() throws IOException {
+            return new HomeDirectoryLocationStrategy(this);
+        }
+
+        /**
+         * Sets whether the base path should be evaluated.
+         *
+         * @param evaluateBasePath whether the base path should be evaluated.
+         * @return {@code this} instance..
+         */
+        public Builder setEvaluateBasePath(final boolean evaluateBasePath) {
+            this.evaluateBasePath = evaluateBasePath;
+            return asThis();
+        }
+
+        /**
+         * Sets the path to the home directory (may be {@code null}).
+         *
+         * @param homeDirectory the path to the home directory (may be {@code 
null})
+         * @return {@code this} instance..
+         */
+        public Builder setHomeDirectory(final String homeDirectory) {
+            this.homeDirectory = homeDirectory;
+            return asThis();
+        }
+    }
+
+    /**
+     * Gets the home directory to be used by a new instance. If a directory 
name is provided, it is used. Otherwise, the
      * user's home directory is looked up.
      *
      * @param homeDir the passed in home directory
@@ -51,12 +98,12 @@ public class HomeDirectoryLocationStrategy extends 
AbstractFileLocationStrategy
         return homeDir != null ? homeDir : SystemProperties.getUserHome();
     }
 
-    /** The home directory to be searched for the requested file. */
-    private final String homeDirectory;
-
     /** The flag whether the base path is to be taken into account. */
     private final boolean evaluateBasePath;
 
+    /** The home directory to be searched for the requested file. */
+    private final String homeDirectory;
+
     /**
      * Creates a new instance of {@code HomeDirectoryLocationStrategy} with 
default settings. The home directory is set to
      * the user's home directory. The base path flag is set to 
<strong>false</strong> (which means that the base path is ignored).
@@ -69,21 +116,35 @@ public class HomeDirectoryLocationStrategy extends 
AbstractFileLocationStrategy
      * Creates a new instance of {@code HomeDirectoryLocationStrategy} and 
initializes the base path flag. The home
      * directory is set to the user's home directory.
      *
-     * @param withBasePath a flag whether the base path should be evaluated
+     * @param withBasePath a flag whether the base path should be evaluated.
+     * @deprecated Use {@link Builder#setEvaluateBasePath(boolean)}.
      */
+    @Deprecated
     public HomeDirectoryLocationStrategy(final boolean withBasePath) {
-        this(null, withBasePath);
+        this(new 
Builder().setHomeDirectory(null).setEvaluateBasePath(withBasePath));
+    }
+
+    /**
+     * Constructs a new instance.
+     *
+     * @param builder How to build the instance.
+     */
+    private HomeDirectoryLocationStrategy(final Builder builder) {
+        super(builder);
+        homeDirectory = getHomeDirectory(builder.homeDirectory);
+        evaluateBasePath = builder.evaluateBasePath;
     }
 
     /**
      * Creates a new instance of {@code HomeDirectoryLocationStrategy} and 
initializes it with the specified settings.
      *
-     * @param homeDir the path to the home directory (can be 
<strong>null</strong>)
-     * @param withBasePath a flag whether the base path should be evaluated
+     * @param homeDir the path to the home directory (may be {@code null}).
+     * @param withBasePath a flag whether the base path should be evaluated.
+     * @deprecated Use {@link Builder#setHomeDirectory(String)}.
      */
+    @Deprecated
     public HomeDirectoryLocationStrategy(final String homeDir, final boolean 
withBasePath) {
-        homeDirectory = getHomeDirectory(homeDir);
-        evaluateBasePath = withBasePath;
+        this(new 
Builder().setHomeDirectory(homeDir).setEvaluateBasePath(withBasePath));
     }
 
     /**
@@ -102,7 +163,7 @@ public class HomeDirectoryLocationStrategy extends 
AbstractFileLocationStrategy
     /**
      * Gets the home directory. In this directory the strategy searches for 
files.
      *
-     * @return the home directory used by this object
+     * @return the home directory used by this object.
      */
     public String getHomeDirectory() {
         return homeDirectory;
@@ -128,7 +189,7 @@ public class HomeDirectoryLocationStrategy extends 
AbstractFileLocationStrategy
             final String basePath = getBasePath(locator);
             final File file = FileLocatorUtils.constructFile(basePath, 
locator.getFileName());
             if (file.isFile()) {
-                return FileLocatorUtils.convertFileToURL(file);
+                return check(FileLocatorUtils.convertFileToURL(file));
             }
         }
         return null;
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/ProvidedURLLocationStrategy.java
 
b/src/main/java/org/apache/commons/configuration2/io/ProvidedURLLocationStrategy.java
index 694215c43..a7cfe86cc 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/ProvidedURLLocationStrategy.java
+++ 
b/src/main/java/org/apache/commons/configuration2/io/ProvidedURLLocationStrategy.java
@@ -22,25 +22,51 @@ import java.net.URL;
  * A specialized implementation of {@code FileLocationStrategy} which checks 
whether a passed in {@link FileLocator}
  * already has a defined URL.
  * <p>
- * {@code FileLocator} objects that have a URL already reference a file in an 
unambiguous way. Therefore, this strategy
+ * {@link FileLocator} objects that have a URL already reference a file in an 
unambiguous way. Therefore, this strategy
  * just returns the URL of the passed in {@code FileLocator}. It can be used 
as a first step of the file resolving
  * process. If it fails, more sophisticated attempts for resolving the file 
can be made.
  * </p>
+ * <p>
+ * See {@link AbstractFileLocationStrategy} learn how to grant an deny URL 
schemes and hosts.
+ * </p>
  *
+ * @see AbstractFileLocationStrategy
  * @since 2.0
  */
 public class ProvidedURLLocationStrategy extends AbstractFileLocationStrategy {
 
     /**
-     * A singleton instance of this strategy.
+     * Builds new instances of {@link ProvidedURLLocationStrategy}.
+     *
+     * @return a new builder.
+     * @since 2.15.0
+     */
+    public static StrategyBuilder<ProvidedURLLocationStrategy> builder() {
+        return new StrategyBuilder<>(ProvidedURLLocationStrategy::new);
+    }
+
+    /**
+     * Constructs a new instance where URL resources are bound by {@link 
AbstractFileLocationStrategy.AbstractBuilder}.
      */
-    static final ProvidedURLLocationStrategy INSTANCE = new 
ProvidedURLLocationStrategy();
+    public ProvidedURLLocationStrategy() {
+    }
+
+    /**
+     * Constructs a new instance where URL resources are bound by {@link 
AbstractFileLocationStrategy.AbstractBuilder}.
+     *
+     * @param builder How to build the instance.
+     * @since 2.15.0
+     */
+    public ProvidedURLLocationStrategy(final AbstractBuilder<?, ?> builder) {
+        super(builder);
+    }
 
     /**
      * {@inheritDoc} This implementation just returns the URL stored in the 
given {@code FileLocator}.
      */
     @Override
     public URL locate(final FileSystem fileSystem, final FileLocator locator) {
-        return locator.getSourceURL();
+        return check(locator.getSourceURL());
     }
+
 }
diff --git 
a/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
 
b/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
index 3f90bda54..22f60b65c 100644
--- 
a/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
+++ 
b/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
@@ -63,6 +63,7 @@ import java.util.List;
 import java.util.PriorityQueue;
 import java.util.Properties;
 import java.util.Set;
+import java.util.TreeSet;
 
 import org.apache.commons.collections.IteratorUtils;
 import org.apache.commons.configuration2.SynchronizerTestImpl.Methods;
@@ -76,16 +77,26 @@ import 
org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
 import org.apache.commons.configuration2.convert.LegacyListDelimiterHandler;
 import org.apache.commons.configuration2.convert.ListDelimiterHandler;
 import org.apache.commons.configuration2.event.ConfigurationEvent;
+import org.apache.commons.configuration2.ex.ConfigurationDeniedException;
 import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.io.AbsoluteNameLocationStrategy;
+import org.apache.commons.configuration2.io.BasePathLocationStrategy;
+import org.apache.commons.configuration2.io.ClasspathLocationStrategy;
+import org.apache.commons.configuration2.io.CombinedLocationStrategy;
 import org.apache.commons.configuration2.io.DefaultFileSystem;
 import org.apache.commons.configuration2.io.FileHandler;
+import org.apache.commons.configuration2.io.FileLocatorUtils;
 import org.apache.commons.configuration2.io.FileSystem;
+import org.apache.commons.configuration2.io.FileSystemLocationStrategy;
+import org.apache.commons.configuration2.io.HomeDirectoryLocationStrategy;
+import org.apache.commons.configuration2.io.ProvidedURLLocationStrategy;
 import org.apache.commons.lang3.mutable.MutableObject;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
+import org.junitpioneer.jupiter.SetSystemProperty;
 
 /**
  * Test for loading and saving properties files.
@@ -106,6 +117,21 @@ public class TestPropertiesConfiguration {
         }
     }
 
+    static class ExceptionList implements 
ConfigurationConsumer<ConfigurationException> {
+
+        private final List<ConfigurationException> exceptions = new 
ArrayList<>();
+
+        @Override
+        public void accept(final ConfigurationException t) throws 
ConfigurationException {
+            exceptions.add(t);
+        }
+
+        List<ConfigurationException> getExceptions() {
+            return exceptions;
+        }
+
+    }
+
     /**
      * A mock implementation of a HttpURLConnection used for testing saving to 
a HTTP server.
      */
@@ -117,8 +143,8 @@ public class TestPropertiesConfiguration {
         /** The output file. The output stream will point to this file. */
         private final File outputFile;
 
-        protected MockHttpURLConnection(final URL u, final int respCode, final 
File outFile) {
-            super(u);
+        protected MockHttpURLConnection(final URL url, final int respCode, 
final File outFile) {
+            super(url);
             returnCode = respCode;
             outputFile = outFile;
         }
@@ -221,6 +247,13 @@ public class TestPropertiesConfiguration {
         }
     }
 
+    /**
+     * All URL schemes.
+     */
+    private static final String ALL_SCHEMES = "file,http,https,jar";
+
+    private static final String KEY_SCHEMES = 
"org.apache.commons.configuration2.io.FileLocationStrategy.schemes";
+
     /** Constant for a test property name. */
     private static final String PROP_NAME = "testProperty";
 
@@ -315,6 +348,10 @@ public class TestPropertiesConfiguration {
         return checkConfig;
     }
 
+    private void reinitLocationStrategy() {
+        
conf.initFileLocator(FileLocatorUtils.fileLocator(conf.getLocator()).locationStrategy(FileLocatorUtils.newDefaultLocationStrategy()).create());
+    }
+
     /**
      * Saves the test configuration to a default output file.
      *
@@ -330,7 +367,6 @@ public class TestPropertiesConfiguration {
         conf = new PropertiesConfiguration();
         conf.setListDelimiterHandler(new LegacyListDelimiterHandler(','));
         load(conf, TEST_PROPERTIES);
-
         // remove the test save file if it exists
         if (TEST_SAVE_PROPERTIES_FILE.exists()) {
             assertTrue(TEST_SAVE_PROPERTIES_FILE.delete());
@@ -772,12 +808,115 @@ public class TestPropertiesConfiguration {
     }
 
     @Test
-    void testIncludeLoadAllOnLoadException() throws Exception {
+    void testIncludeLoadAllOnLoadBadHostException() throws Exception {
+        final PropertiesConfiguration pc = new PropertiesConfiguration();
+        final ExceptionList list = new ExceptionList();
+        pc.setIncludeListener(list);
+        final FileHandler handler = new FileHandler(pc);
+        // @formatter:off
+        final CombinedLocationStrategy.Builder builder = new 
CombinedLocationStrategy.Builder()
+                .setSchemes(new 
TreeSet<>(Arrays.asList(ALL_SCHEMES.split(","))))
+                .setHostsRegEx(new TreeSet<>(Arrays.asList("GrantThisHost")));
+        handler.setLocationStrategy(builder.setSubStrategies(Arrays.asList(
+                new ProvidedURLLocationStrategy(builder),
+                new FileSystemLocationStrategy(builder),
+                new AbsoluteNameLocationStrategy(builder),
+                new BasePathLocationStrategy(builder),
+                new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(true).getUnchecked(),
+                new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(false).getUnchecked(),
+                new ClasspathLocationStrategy(builder)))
+                .get());
+        // @formatter:on
+        handler.setBasePath(TEST_BASE_PATH);
+        
handler.setFileName("include-load-url-host-unknown-exception.properties");
+        handler.load();
+        assertFalse(list.exceptions.isEmpty());
+        assertInstanceOf(ConfigurationDeniedException.class, 
list.exceptions.get(0).getCause());
+        assertEquals("valueA", pc.getString("keyA"));
+    }
+
+    @Test
+    @SetSystemProperty(key = KEY_SCHEMES, value = ALL_SCHEMES)
+    void testIncludeLoadAllOnLoadBadUrlException() throws Exception {
         final PropertiesConfiguration pc = new PropertiesConfiguration();
         pc.setIncludeListener(PropertiesConfiguration.NOOP_INCLUDE_LISTENER);
         final FileHandler handler = new FileHandler(pc);
+        // pick up @SetSystemProperty
+        
handler.setLocationStrategy(FileLocatorUtils.newDefaultLocationStrategy());
         handler.setBasePath(TEST_BASE_PATH);
-        handler.setFileName("include-load-exception.properties");
+        
handler.setFileName("include-load-url-host-unknown-exception.properties");
+        handler.load();
+        assertEquals("valueA", pc.getString("keyA"));
+    }
+
+    @Test
+    @SetSystemProperty(key = KEY_SCHEMES, value = ALL_SCHEMES)
+    void testIncludeLoadAllOnLoadBadUrlExceptionManualDefault() throws 
Exception {
+        final PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.setIncludeListener(PropertiesConfiguration.NOOP_INCLUDE_LISTENER);
+        final FileHandler handler = new FileHandler(pc);
+        // pick up @SetSystemProperty
+        // @formatter:off
+        handler.setLocationStrategy(new CombinedLocationStrategy(Arrays.asList(
+                new ProvidedURLLocationStrategy(),
+                new FileSystemLocationStrategy(),
+                new AbsoluteNameLocationStrategy(),
+                new BasePathLocationStrategy(),
+                new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(true).getUnchecked(),
+                new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(false).getUnchecked(),
+                new ClasspathLocationStrategy())));
+        // @formatter:on
+        handler.setBasePath(TEST_BASE_PATH);
+        
handler.setFileName("include-load-url-host-unknown-exception.properties");
+        handler.load();
+        assertEquals("valueA", pc.getString("keyA"));
+    }
+
+    @Test
+    void testIncludeLoadAllOnLoadBadUrlExceptionManualPropagate() throws 
Exception {
+        final PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.setIncludeListener(PropertiesConfiguration.NOOP_INCLUDE_LISTENER);
+        final FileHandler handler = new FileHandler(pc);
+        final CombinedLocationStrategy.Builder builder = new 
CombinedLocationStrategy.Builder()
+                .setSchemes(new 
TreeSet<>(Arrays.asList(ALL_SCHEMES.split(","))));
+        // @formatter:off
+        handler.setLocationStrategy(builder.setSubStrategies(Arrays.asList(
+                new ProvidedURLLocationStrategy(builder),
+                new FileSystemLocationStrategy(builder),
+                new AbsoluteNameLocationStrategy(builder),
+                new BasePathLocationStrategy(builder),
+                new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(true).getUnchecked(),
+                new 
HomeDirectoryLocationStrategy.Builder().setEvaluateBasePath(false).getUnchecked(),
+                new ClasspathLocationStrategy(builder)))
+                .get());
+        // @formatter:on
+        handler.setBasePath(TEST_BASE_PATH);
+        
handler.setFileName("include-load-url-host-unknown-exception.properties");
+        handler.load();
+        assertEquals("valueA", pc.getString("keyA"));
+    }
+
+    @Test
+    void testIncludeLoadAllOnLoadFileException() throws Exception {
+        final PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.setIncludeListener(PropertiesConfiguration.NOOP_INCLUDE_LISTENER);
+        final FileHandler handler = new FileHandler(pc);
+        handler.setBasePath(TEST_BASE_PATH);
+        handler.setFileName("include-load-url-file-exception.properties");
+        handler.load();
+        assertEquals("valueA", pc.getString("keyA"));
+    }
+
+    @Test
+    @SetSystemProperty(key = KEY_SCHEMES, value = ALL_SCHEMES)
+    void testIncludeLoadAllOnLoadUrlException() throws Exception {
+        final PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.setIncludeListener(PropertiesConfiguration.NOOP_INCLUDE_LISTENER);
+        final FileHandler handler = new FileHandler(pc);
+        // pick up @SetSystemProperty
+        
handler.setLocationStrategy(FileLocatorUtils.newDefaultLocationStrategy());
+        handler.setBasePath(TEST_BASE_PATH);
+        handler.setFileName("include-load-url-http-exception.properties");
         handler.load();
         assertEquals("valueA", pc.getString("keyA"));
     }
@@ -835,6 +974,17 @@ public class TestPropertiesConfiguration {
         assertEquals("valueA", pc.getString("keyA"));
     }
 
+    @Test
+    void testIncludeLoadUrlBadScheme() throws Exception {
+        final PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.setIncludeListener(PropertiesConfiguration.NOOP_INCLUDE_LISTENER);
+        final FileHandler handler = new FileHandler(pc);
+        handler.setBasePath(TEST_BASE_PATH);
+        
handler.setFileName("include-load-url-bad-scheme-exception.properties");
+        handler.load();
+        assertEquals("valueA", pc.getString("keyA"));
+    }
+
     /**
      * Tests initializing a properties configuration from a non existing file. 
There was a bug, which caused properties
      * getting lost when later save() is called.
diff --git 
a/src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
 
b/src/test/java/org/apache/commons/configuration2/io/TestAbstractFileLocationStrategy.java
similarity index 71%
copy from 
src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
copy to 
src/test/java/org/apache/commons/configuration2/io/TestAbstractFileLocationStrategy.java
index 53cbe45ba..5ceb2e799 100644
--- 
a/src/main/java/org/apache/commons/configuration2/io/AbstractFileLocationStrategy.java
+++ 
b/src/test/java/org/apache/commons/configuration2/io/TestAbstractFileLocationStrategy.java
@@ -14,11 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.commons.configuration2.io;
 
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
 /**
- * Abstracts services for {@link FileLocationStrategy} implementations.
+ * Tests {@link AbstractFileLocationStrategy}.
  */
-abstract class AbstractFileLocationStrategy implements FileLocationStrategy {
+public class TestAbstractFileLocationStrategy {
 
+    @Test
+    void testBuilder() {
+        assertThrows(NullPointerException.class, () -> new 
AbstractFileLocationStrategy.StrategyBuilder<>(null));
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/configuration2/io/TestProvidedURLLocationStrategy.java
 
b/src/test/java/org/apache/commons/configuration2/io/TestProvidedURLLocationStrategy.java
index b3534764e..2002246da 100644
--- 
a/src/test/java/org/apache/commons/configuration2/io/TestProvidedURLLocationStrategy.java
+++ 
b/src/test/java/org/apache/commons/configuration2/io/TestProvidedURLLocationStrategy.java
@@ -14,15 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.commons.configuration2.io;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.mock;
 
 import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.apache.commons.configuration2.ConfigurationAssert;
+import org.apache.commons.configuration2.ex.ConfigurationDeniedException;
+import 
org.apache.commons.configuration2.io.AbstractFileLocationStrategy.StrategyBuilder;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -49,6 +56,16 @@ public class TestProvidedURLLocationStrategy {
         assertNull(strategy.locate(fs, locator));
     }
 
+    /**
+     * Tests a failed locate() operation.
+     */
+    @Test
+    void testLocateFailDefaultBuilder() {
+        final FileSystem fs = mock(FileSystem.class);
+        final FileLocator locator = 
FileLocatorUtils.fileLocator().basePath("somePath").fileName("someFile.xml").create();
+        assertNull(ProvidedURLLocationStrategy.builder().get().locate(fs, 
locator));
+    }
+
     /**
      * Tests a successful locate() operation.
      */
@@ -59,4 +76,29 @@ public class TestProvidedURLLocationStrategy {
         final FileLocator locator = 
FileLocatorUtils.fileLocator().sourceURL(url).create();
         assertSame(url, strategy.locate(fs, locator));
     }
+
+    /**
+     * Tests a successful locate() operation.
+     */
+    @Test
+    void testLocateSuccessDefaultBuilder() {
+        final FileSystem fs = mock(FileSystem.class);
+        final URL url = ConfigurationAssert.getTestURL("test.xml");
+        final FileLocator locator = 
FileLocatorUtils.fileLocator().sourceURL(url).create();
+        assertSame(url, ProvidedURLLocationStrategy.builder().get().locate(fs, 
locator));
+    }
+
+    @Test
+    void testLocateSchemes() {
+        final FileSystem fs = mock(FileSystem.class);
+        final URL url = ConfigurationAssert.getTestURL("test.xml");
+        final FileLocator locator = 
FileLocatorUtils.fileLocator().sourceURL(url).create();
+        final Set<String> schemes = new HashSet<>();
+        final StrategyBuilder<ProvidedURLLocationStrategy> builder = 
ProvidedURLLocationStrategy.builder();
+        assertEquals("file", builder.setSchemes(schemes).get().locate(fs, 
locator).getProtocol());
+        schemes.add("foo");
+        assertThrows(ConfigurationDeniedException.class, () -> 
builder.setSchemes(schemes).get().locate(fs, locator));
+        schemes.add("file");
+        assertSame(url, builder.setSchemes(schemes).get().locate(fs, locator));
+    }
 }
diff --git a/src/test/resources/include-load-exception.properties 
b/src/test/resources/include-load-url-bad-scheme-exception.properties
similarity index 92%
copy from src/test/resources/include-load-exception.properties
copy to src/test/resources/include-load-url-bad-scheme-exception.properties
index 9faf9e7d9..1beca3e64 100644
--- a/src/test/resources/include-load-exception.properties
+++ b/src/test/resources/include-load-url-bad-scheme-exception.properties
@@ -12,5 +12,5 @@
 #   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.
-include = http://this-file-does-not-and-should-not-exist.txt
+include = BadScheme://this-file-does-not-and-should-not-exist.txt
 keyA = valueA
diff --git a/src/test/resources/include-load-exception.properties 
b/src/test/resources/include-load-url-file-exception.properties
similarity index 93%
copy from src/test/resources/include-load-exception.properties
copy to src/test/resources/include-load-url-file-exception.properties
index 9faf9e7d9..44136fadd 100644
--- a/src/test/resources/include-load-exception.properties
+++ b/src/test/resources/include-load-url-file-exception.properties
@@ -12,5 +12,5 @@
 #   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.
-include = http://this-file-does-not-and-should-not-exist.txt
+include = file://this-file-does-not-and-should-not-exist.txt
 keyA = valueA
diff --git a/src/test/resources/include-load-exception.properties 
b/src/test/resources/include-load-url-host-unknown-exception.properties
similarity index 100%
copy from src/test/resources/include-load-exception.properties
copy to src/test/resources/include-load-url-host-unknown-exception.properties
diff --git a/src/test/resources/include-load-exception.properties 
b/src/test/resources/include-load-url-http-exception.properties
similarity index 100%
rename from src/test/resources/include-load-exception.properties
rename to src/test/resources/include-load-url-http-exception.properties

Reply via email to