/* * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; import jdk.test.lib.Utils; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; import sun.net.www.ParseUtil; import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UncheckedIOException; import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.nio.CharBuffer; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.security.Security; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; /* * @test * @summary Tests security properties passed through java.security, * java.security.properties or included from other properties files. * @bug 8155246 8292297 8292177 8281658 8319332 * @modules java.base/sun.net.www * @library /test/lib * @run main ConfigFileTest */ public class ConfigFileTest { static final String SEPARATOR_THIN = "----------------------------"; private static void printTestHeader(String testName) { System.out.println(); System.out.println(SEPARATOR_THIN); System.out.println(testName); System.out.println(SEPARATOR_THIN); System.out.println(); } public static void main(String[] args) throws Exception { if (args.length == 1 && Executor.RUNNER_ARG.equals(args[0])) { // Executed by a test-launched JVM. // Force the initialization of java.security.Security. Security.getProviders(); Security.setProperty("postInitTest", "shouldNotRecord"); System.out.println(FilesManager.LAST_FILE_PROP_NAME + ": " + Security.getProperty(FilesManager.LAST_FILE_PROP_NAME)); assertTestSecuritySetPropertyShouldNotInclude(); } else { // Executed by the test JVM. try (FilesManager filesMgr = new FilesManager()) { for (Method m : ConfigFileTest.class.getDeclaredMethods()) { if (m.getName().startsWith("test")) { printTestHeader(m.getName()); Executor.run(m, filesMgr); } } } } } /* * Success cases */ static void testShowSettings(Executor ex, FilesManager filesMgr) throws Exception { // Sanity test passing the -XshowSettings:security option. ex.addJvmArg("-XshowSettings:security"); ex.setMasterFile(filesMgr.newMasterFile()); ex.assertSuccess(); ex.getOutputAnalyzer() .shouldContain("Security properties:") .shouldContain("Security provider static configuration:") .shouldContain("Security TLS configuration"); } static void testIncludeBasic(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); ExtraPropsFile extraFile = filesMgr.newExtraFile(); PropsFile file0 = filesMgr.newFile("file0.properties"); PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); PropsFile file2 = filesMgr.newFile("dir1/dir2/file2.properties"); masterFile.addAbsoluteInclude(file0); extraFile.addRelativeInclude(file2); file2.addAbsoluteInclude(file1); ex.setMasterFile(masterFile); ex.setExtraFile(extraFile, Executor.ExtraMode.FILE_URI, false); ex.assertSuccess(); } static void testRepeatedInclude(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); PropsFile file0 = filesMgr.newFile("file0.properties"); PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); masterFile.addAbsoluteInclude(file0); masterFile.addAbsoluteInclude(file1); masterFile.addAbsoluteInclude(file0); file1.addRelativeInclude(file0); ex.setMasterFile(masterFile); ex.assertSuccess(); } static void testIncludeWithOverrideAll(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); ExtraPropsFile extraFile = filesMgr.newExtraFile(); PropsFile file0 = filesMgr.newFile("file0.properties"); PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); masterFile.addRelativeInclude(file0); extraFile.addAbsoluteInclude(file1); ex.setMasterFile(masterFile); ex.setExtraFile(extraFile, Executor.ExtraMode.HTTP_SERVED, true); ex.assertSuccess(); } static void extraPropertiesByHelper(Executor ex, FilesManager filesMgr, Executor.ExtraMode mode) throws Exception { ExtraPropsFile extraFile = filesMgr.newExtraFile(); PropsFile file0 = filesMgr.newFile("file0.properties"); extraFile.addRelativeInclude(file0); ex.setMasterFile(filesMgr.newMasterFile()); ex.setExtraFile(extraFile, mode, true); ex.assertSuccess(); } static void testExtraPropertiesByPathAbsolute(Executor ex, FilesManager filesMgr) throws Exception { extraPropertiesByHelper(ex, filesMgr, Executor.ExtraMode.PATH_ABS); } static void testExtraPropertiesByPathRelative(Executor ex, FilesManager filesMgr) throws Exception { extraPropertiesByHelper(ex, filesMgr, Executor.ExtraMode.PATH_REL); } static void specialCharsIncludes(Executor ex, FilesManager filesMgr, char specialChar, Executor.ExtraMode extraMode, boolean useRelativeIncludes) throws Exception { String suffix = specialChar + ".properties"; ExtraPropsFile extraFile; PropsFile file0, file1; try { extraFile = filesMgr.newExtraFile("extra" + suffix); file0 = filesMgr.newFile("file0" + suffix); file1 = filesMgr.newFile("file1" + suffix); } catch (InvalidPathException ipe) { // The platform encoding may not allow to create files with some // special characters. Skip the test in these cases. return; } if (useRelativeIncludes) { extraFile.addRelativeInclude(file0); } else { extraFile.addAbsoluteInclude(file0); } extraFile.addAbsoluteInclude(file1); ex.setMasterFile(filesMgr.newMasterFile()); ex.setExtraFile(extraFile, extraMode, false); ex.assertSuccess(); } static void testUnicodeIncludes1(Executor ex, FilesManager filesMgr) throws Exception { specialCharsIncludes(ex, filesMgr, '\u2022', Executor.ExtraMode.PATH_ABS, true); } static void testUnicodeIncludes2(Executor ex, FilesManager filesMgr) throws Exception { specialCharsIncludes(ex, filesMgr, '\u2022', Executor.ExtraMode.FILE_URI, true); } static void testUnicodeIncludes3(Executor ex, FilesManager filesMgr) throws Exception { // Backward compatibility check. Malformed URLs such as // file:/tmp/extra•.properties are supported for the extra file. // However, relative includes are not allowed in these cases. specialCharsIncludes(ex, filesMgr, '\u2022', Executor.ExtraMode.RAW_FILE_URI1, false); } static void testUnicodeIncludes4(Executor ex, FilesManager filesMgr) throws Exception { // Backward compatibility check. Malformed URLs such as // file:///tmp/extra•.properties are supported for the extra file. // However, relative includes are not allowed in these cases. specialCharsIncludes(ex, filesMgr, '\u2022', Executor.ExtraMode.RAW_FILE_URI2, false); } static void testSpaceIncludes1(Executor ex, FilesManager filesMgr) throws Exception { specialCharsIncludes(ex, filesMgr, ' ', Executor.ExtraMode.PATH_ABS, true); } static void testSpaceIncludes2(Executor ex, FilesManager filesMgr) throws Exception { specialCharsIncludes(ex, filesMgr, ' ', Executor.ExtraMode.FILE_URI, true); } static void testSpaceIncludes3(Executor ex, FilesManager filesMgr) throws Exception { // Backward compatibility check. Malformed URLs such as // file:/tmp/extra .properties are supported for the extra file. // However, relative includes are not allowed in these cases. specialCharsIncludes(ex, filesMgr, ' ', Executor.ExtraMode.RAW_FILE_URI1, false); } static void testSpaceIncludes4(Executor ex, FilesManager filesMgr) throws Exception { // Backward compatibility check. Malformed URLs such as // file:///tmp/extra .properties are supported for the extra file. // However, relative includes are not allowed in these cases. specialCharsIncludes(ex, filesMgr, ' ', Executor.ExtraMode.RAW_FILE_URI2, false); } static void notOverrideOnFailureHelper(Executor ex, FilesManager filesMgr, String nonExistentExtraFile) throws Exception { // An overriding extra properties file that does not exist // should not erase properties from the master file. ex.setIgnoredExtraFile(nonExistentExtraFile, true); ex.setMasterFile(filesMgr.newMasterFile()); ex.assertSuccess(); ex.getOutputAnalyzer().shouldContain("unable to load security " + "properties from " + nonExistentExtraFile); } static void testNotOverrideOnEmptyFailure(Executor ex, FilesManager filesMgr) throws Exception { notOverrideOnFailureHelper(ex, filesMgr, ""); ex.getOutputAnalyzer() .shouldContain("Empty extra properties file path"); } static void testNotOverrideOnURLFailure(Executor ex, FilesManager filesMgr) throws Exception { notOverrideOnFailureHelper(ex, filesMgr, "file:///nonExistentFile.properties"); } static void testNotOverrideOnPathFailure(Executor ex, FilesManager filesMgr) throws Exception { notOverrideOnFailureHelper(ex, filesMgr, "nonExistentFile.properties"); } static void testNotOverrideOnDirFailure(Executor ex, FilesManager filesMgr) throws Exception { notOverrideOnFailureHelper(ex, filesMgr, "file:///"); ex.getOutputAnalyzer().shouldContain("Is a directory"); } static void testNotOverrideOnBadFileURLFailure(Executor ex, FilesManager filesMgr) throws Exception { notOverrideOnFailureHelper(ex, filesMgr, "file:///%00"); } static void testDisabledExtraPropertiesFile(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); PropsFile file0 = filesMgr.newFile("file0.properties"); masterFile.addRawProperty("security.overridePropertiesFile", "false"); ex.setMasterFile(masterFile); ex.setIgnoredExtraFile(file0.path.toString(), true); ex.assertSuccess(); } static final String SECURITY_SET_PROP_FILE_PATH = "testSecuritySetPropertyShouldNotInclude.propsFilePath"; static void testSecuritySetPropertyShouldNotInclude(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); PropsFile file0 = filesMgr.newFile("file0.properties"); ex.addSystemProp(SECURITY_SET_PROP_FILE_PATH, file0.path.toString()); ex.setMasterFile(masterFile); ex.assertSuccess(); } static void assertTestSecuritySetPropertyShouldNotInclude() { // This check is executed by the launched JVM. String propsFilePath = System.getProperty(SECURITY_SET_PROP_FILE_PATH); if (propsFilePath != null) { String name = Path.of(propsFilePath).getFileName().toString(); String setPropInvokeRepr = "Security.setProperty(\"include\", " + "\"" + propsFilePath + "\")"; try { Security.setProperty("include", propsFilePath); throw new RuntimeException(setPropInvokeRepr + " was " + "expected to throw IllegalArgumentException."); } catch (IllegalArgumentException expected) {} if (FilesManager.APPLIED_PROP_VALUE.equals( Security.getProperty(name))) { throw new RuntimeException(setPropInvokeRepr + " caused " + "a file inclusion."); } try { Security.getProperty("include"); throw new RuntimeException("Security.getProperty(\"include\")" + " was expected to throw IllegalArgumentException."); } catch (IllegalArgumentException expected) {} } } /* * Error cases */ static void testCannotResolveRelativeFromHTTPServed(Executor ex, FilesManager filesMgr) throws Exception { ExtraPropsFile extraFile = filesMgr.newExtraFile(); PropsFile file0 = filesMgr.newFile("file0.properties"); extraFile.addRelativeInclude(file0); ex.setMasterFile(filesMgr.newMasterFile()); ex.setExtraFile(extraFile, Executor.ExtraMode.HTTP_SERVED, true); ex.assertError("InternalError: Cannot resolve '" + file0.fileName + "' relative path when included from a non-regular " + "properties file (e.g. HTTP served file)"); } static void testCannotIncludeCycles(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); PropsFile file0 = filesMgr.newFile("file0.properties"); PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); // Includes chain: master -> file0 -> file1 -> master. file1.addRelativeInclude(masterFile); file0.addRelativeInclude(file1); masterFile.addRelativeInclude(file0); ex.setMasterFile(masterFile); ex.assertError( "InternalError: Cyclic include of '" + masterFile.path + "'"); } static void testCannotIncludeURL(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); ExtraPropsFile extraFile = filesMgr.newExtraFile(); masterFile.addRawProperty("include", extraFile.url.toString()); ex.setMasterFile(masterFile); ex.assertError("InternalError: Unable to include 'http://127.0.0.1:"); } static void testCannotIncludeNonexistentFile(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); String nonexistentPath = "/nonExistentFile.properties"; masterFile.addRawProperty("include", nonexistentPath); ex.setMasterFile(masterFile); ex.assertError( "InternalError: Unable to include '" + nonexistentPath + "'"); } static void testMustHaveMasterFile(Executor ex, FilesManager filesMgr) throws Exception { // Launch a JDK without a master java.security file present. ex.assertError("InternalError: Error loading java.security file"); } static void testMustHaveMasterFileEvenWithExtraFile(Executor ex, FilesManager filesMgr) throws Exception { // Launch a JDK without a master java.security file present, but with an // extra file passed. Since the "security.overridePropertiesFile=true" // security property is missing, it should fail anyway. ex.setExtraFile( filesMgr.newExtraFile(), Executor.ExtraMode.FILE_URI, true); ex.assertError("InternalError: Error loading java.security file"); } } sealed class PropsFile permits ExtraPropsFile { protected static final class Include { final PropsFile propsFile; final String value; private Include(PropsFile propsFile, String value) { this.propsFile = propsFile; this.value = value; } static Include of(PropsFile propsFile) { return new Include(propsFile, propsFile.path.toString()); } static Include of(PropsFile propsFile, String value) { return new Include(propsFile, value); } } protected final List includes = new ArrayList<>(); protected final PrintWriter writer; protected boolean includedFromExtra = false; final String fileName; final Path path; PropsFile(String fileName, Path path) throws IOException { this.fileName = fileName; this.path = path; this.writer = new PrintWriter(Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND), true); } private static String escape(String text, boolean escapeSpace) { StringBuilder sb = new StringBuilder(text.length()); CharBuffer cb = CharBuffer.wrap(text); while (cb.hasRemaining()) { char c = cb.get(); if (c == '\\' || escapeSpace && c == ' ') { sb.append('\\'); } if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.BASIC_LATIN) { sb.append(c); } else { sb.append("\\u%04x".formatted((int) c)); } } return sb.toString(); } private void addRawProperty(String key, String value, String sep) { writer.println(escape(key, true) + sep + escape(value, false)); } protected void addIncludeDefinition(Include include) { if (include.propsFile instanceof ExtraPropsFile) { throw new RuntimeException("ExtraPropsFile should not be included"); } includes.add(include); addRawProperty("include", include.value, " "); } void addComment(String comment) { writer.println("# " + comment); } void addRawProperty(String key, String value) { addRawProperty(key, value, "="); } void addAbsoluteInclude(PropsFile propsFile) { addIncludeDefinition(Include.of(propsFile)); } void addRelativeInclude(PropsFile propsFile) { addIncludeDefinition(Include.of(propsFile, path.getParent().relativize(propsFile.path).toString())); } void assertApplied(OutputAnalyzer oa) { oa.shouldContain(Executor.INITIAL_PROP_LOG_MSG + fileName + "=" + FilesManager.APPLIED_PROP_VALUE); for (Include include : includes) { include.propsFile.assertApplied(oa); oa.shouldContain("processing include: '" + include.value + "'"); oa.shouldContain("finished processing " + include.propsFile.path); } } void assertWasOverwritten(OutputAnalyzer oa) { oa.shouldNotContain(Executor.INITIAL_PROP_LOG_MSG + fileName + "=" + FilesManager.APPLIED_PROP_VALUE); for (Include include : includes) { if (!include.propsFile.includedFromExtra) { include.propsFile.assertWasOverwritten(oa); } oa.shouldContain("processing include: '" + include.value + "'"); oa.shouldContain("finished processing " + include.propsFile.path); } } void markAsIncludedFromExtra() { includedFromExtra = true; for (Include include : includes) { include.propsFile.markAsIncludedFromExtra(); } } PropsFile getLastFile() { return includes.isEmpty() ? this : includes.getLast().propsFile.getLastFile(); } void close() { writer.close(); } } final class ExtraPropsFile extends PropsFile { private final Map systemProps = new LinkedHashMap<>(); final URI url; ExtraPropsFile(String fileName, URI url, Path path) throws IOException { super(fileName, path); this.url = url; } @Override protected void addIncludeDefinition(Include include) { if (includes.isEmpty()) { String propName = "props.fileName"; systemProps.put(propName, include.propsFile.fileName); include = Include.of(include.propsFile, include.value.replace(include.propsFile.fileName, "${props.none}${" + propName + "}")); } include.propsFile.markAsIncludedFromExtra(); super.addIncludeDefinition(include); } Map getSystemProperties() { return Collections.unmodifiableMap(systemProps); } } final class FilesManager implements Closeable { private static final Path ROOT_DIR = Path.of(ConfigFileTest.class.getSimpleName()).toAbsolutePath(); private static final Path PROPS_DIR = ROOT_DIR.resolve("properties"); private static final Path JDK_DIR = ROOT_DIR.resolve("jdk"); private static final Path MASTER_FILE = JDK_DIR.resolve("conf/security/java.security"); private static final Path MASTER_FILE_TEMPLATE = MASTER_FILE.resolveSibling("java.security.template"); static final String JAVA_EXECUTABLE = JDK_DIR.resolve("bin/java").toString(); static final String LAST_FILE_PROP_NAME = "last-file"; static final String APPLIED_PROP_VALUE = "applied"; private final List createdFiles; private final Set fileNamesInUse; private final HttpServer httpServer; private final URI serverUri; private final long masterFileLines; FilesManager() throws Exception { createdFiles = new ArrayList<>(); fileNamesInUse = new HashSet<>(); httpServer = HttpServer.create( new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); httpServer.createContext("/", this::handleRequest); InetSocketAddress address = httpServer.getAddress(); httpServer.start(); serverUri = new URI("http", null, address.getHostString(), address.getPort(), null, null, null); copyJDK(); try (Stream s = Files.lines(MASTER_FILE_TEMPLATE)) { masterFileLines = s.count(); } } private static void copyJDK() throws Exception { Path testJDK = Path.of(Objects.requireNonNull( System.getProperty("test.jdk"), "unspecified test.jdk")); if (!Files.exists(testJDK)) { throw new RuntimeException("test.jdk -> nonexistent JDK"); } Files.createDirectories(JDK_DIR); try (Stream pathStream = Files.walk(testJDK)) { pathStream.skip(1).forEach((Path file) -> { try { Files.copy(file, JDK_DIR.resolve(testJDK.relativize(file)), StandardCopyOption.COPY_ATTRIBUTES); } catch (IOException ioe) { throw new UncheckedIOException(ioe); } }); } Files.move(MASTER_FILE, MASTER_FILE_TEMPLATE); } private void handleRequest(HttpExchange x) throws IOException { String rawPath = x.getRequestURI().getRawPath(); Path f = ROOT_DIR.resolve(x.getRequestURI().getPath().substring(1)); int statusCode; byte[] responseBody; // Check for unescaped space, unresolved parent or backward slash. if (rawPath.matches("^.*( |(\\.|%2[Ee]){2}|\\\\|%5[Cc]).*$")) { statusCode = HttpURLConnection.HTTP_BAD_REQUEST; responseBody = new byte[0]; } else if (Files.isRegularFile(f)) { x.getResponseHeaders().add("Content-type", "text/plain"); statusCode = HttpURLConnection.HTTP_OK; responseBody = Files.readAllBytes(f); } else { statusCode = HttpURLConnection.HTTP_NOT_FOUND; responseBody = new byte[0]; } System.out.println("[" + Instant.now() + "] " + getClass().getSimpleName() + ": " + x.getRequestMethod() + " " + rawPath + " -> " + statusCode + " (" + responseBody.length + " bytes)"); try (OutputStream responseStream = x.getResponseBody()) { x.sendResponseHeaders(statusCode, responseBody.length); responseStream.write(responseBody); } } @FunctionalInterface private interface PropsFileBuilder { PropsFile build(String fileName, Path path) throws IOException; } private PropsFile newFile(Path path, PropsFileBuilder builder) throws IOException { String fileName = path.getFileName().toString(); if (!fileNamesInUse.add(fileName)) { // Names must be unique in order for the special // property = to work. throw new RuntimeException(fileName + " is repeated"); } Files.createDirectories(path.getParent()); PropsFile propsFile = builder.build(fileName, path); propsFile.addComment("Property to determine if this properties file " + "was parsed and not overwritten:"); propsFile.addRawProperty(fileName, APPLIED_PROP_VALUE); propsFile.addComment(ConfigFileTest.SEPARATOR_THIN); propsFile.addComment("Property to be overwritten by every properties " + "file (master, extra or included):"); propsFile.addRawProperty(LAST_FILE_PROP_NAME, fileName); propsFile.addComment(ConfigFileTest.SEPARATOR_THIN); createdFiles.add(propsFile); return propsFile; } PropsFile newFile(String relPathStr) throws IOException { return newFile(PROPS_DIR.resolve(relPathStr), PropsFile::new); } PropsFile newMasterFile() throws IOException { Files.copy(MASTER_FILE_TEMPLATE, MASTER_FILE); return newFile(MASTER_FILE, PropsFile::new); } ExtraPropsFile newExtraFile() throws IOException { return newExtraFile("extra.properties"); } ExtraPropsFile newExtraFile(String extraFileName) throws IOException { return (ExtraPropsFile) newFile(PROPS_DIR.resolve(extraFileName), (fileName, path) -> { URI uri = serverUri.resolve(ParseUtil.encodePath( ROOT_DIR.relativize(path).toString())); return new ExtraPropsFile(fileName, uri, path); }); } void reportCreatedFiles() throws IOException { for (PropsFile propsFile : createdFiles) { System.err.println(); System.err.println(propsFile.path.toString()); System.err.println(ConfigFileTest.SEPARATOR_THIN.repeat(3)); try (Stream lines = Files.lines(propsFile.path)) { long lineNumber = 1L; Iterator it = lines.iterator(); while (it.hasNext()) { String line = it.next(); if (!propsFile.path.equals(MASTER_FILE) || lineNumber > masterFileLines) { System.err.println(line); } lineNumber++; } } System.err.println(); } } void clear() throws IOException { if (!createdFiles.isEmpty()) { for (PropsFile propsFile : createdFiles) { propsFile.close(); Files.delete(propsFile.path); } FileUtils.deleteFileTreeUnchecked(PROPS_DIR); createdFiles.clear(); fileNamesInUse.clear(); } } @Override public void close() throws IOException { clear(); httpServer.stop(0); FileUtils.deleteFileTreeUnchecked(ROOT_DIR); } } final class Executor { enum ExtraMode { HTTP_SERVED, FILE_URI, RAW_FILE_URI1, RAW_FILE_URI2, PATH_ABS, PATH_REL } static final String RUNNER_ARG = "runner"; static final String INITIAL_PROP_LOG_MSG = "Initial security property: "; private static final String OVERRIDING_LOG_MSG = "overriding other security properties files!"; private static final String[] ALWAYS_UNEXPECTED_LOG_MSGS = { "java.lang.AssertionError", INITIAL_PROP_LOG_MSG + "postInitTest=shouldNotRecord", INITIAL_PROP_LOG_MSG + "include=", }; private static final Path CWD = Path.of(".").toAbsolutePath(); private static final String JAVA_SEC_PROPS = "java.security.properties"; private static final String CLASS_PATH = Objects.requireNonNull( System.getProperty("test.classes"), "unspecified test.classes"); private static final String DEBUG_ARG = "-Xrunjdwp:transport=dt_socket,address=localhost:8000,suspend=y"; private final Map systemProps = new LinkedHashMap<>( Map.of("java.security.debug", "all", "javax.net.debug", "all", // Ensure we get UTF-8 debug outputs in Windows: "stderr.encoding", "UTF-8", "stdout.encoding", "UTF-8")); private final List jvmArgs = new ArrayList<>( List.of(FilesManager.JAVA_EXECUTABLE, "-enablesystemassertions", // Uncomment DEBUG_ARG to debug test-launched JVMs: "-classpath", CLASS_PATH//, DEBUG_ARG )); private PropsFile masterPropsFile; private ExtraPropsFile extraPropsFile; private boolean expectedOverrideAll = false; private OutputAnalyzer oa; static void run(Method m, FilesManager filesMgr) throws Exception { try { m.invoke(null, new Executor(), filesMgr); } catch (Throwable e) { filesMgr.reportCreatedFiles(); throw e; } finally { filesMgr.clear(); } } void addSystemProp(String key, String value) { systemProps.put(key, value); } private void setRawExtraFile(String extraFile, boolean overrideAll) { addSystemProp(JAVA_SEC_PROPS, (overrideAll ? "=" : "") + extraFile); } void setMasterFile(PropsFile masterPropsFile) { this.masterPropsFile = masterPropsFile; } void setExtraFile(ExtraPropsFile extraPropsFile, ExtraMode mode, boolean overrideAll) { this.extraPropsFile = extraPropsFile; expectedOverrideAll = overrideAll; setRawExtraFile(switch (mode) { case HTTP_SERVED -> extraPropsFile.url.toString(); case FILE_URI -> extraPropsFile.path.toUri().toString(); case RAW_FILE_URI1 -> "file:" + extraPropsFile.path; case RAW_FILE_URI2 -> "file://" + (extraPropsFile.path.startsWith("/") ? "" : "/") + extraPropsFile.path; case PATH_ABS -> extraPropsFile.path.toString(); case PATH_REL -> CWD.relativize(extraPropsFile.path).toString(); }, overrideAll); } void setIgnoredExtraFile(String extraPropsFile, boolean overrideAll) { setRawExtraFile(extraPropsFile, overrideAll); expectedOverrideAll = false; } void addJvmArg(String arg) { jvmArgs.add(arg); } private void execute(boolean successExpected) throws Exception { List command = new ArrayList<>(jvmArgs); Collections.addAll(command, Utils.getTestJavaOpts()); addSystemPropertiesAsJvmArgs(command); command.add(ConfigFileTest.class.getSimpleName()); command.add(RUNNER_ARG); oa = ProcessTools.executeProcess(new ProcessBuilder(command)); oa.shouldHaveExitValue(successExpected ? 0 : 1); for (String output : ALWAYS_UNEXPECTED_LOG_MSGS) { oa.shouldNotContain(output); } } private void addSystemPropertiesAsJvmArgs(List command) { Map allSystemProps = new LinkedHashMap<>(systemProps); if (extraPropsFile != null) { allSystemProps.putAll(extraPropsFile.getSystemProperties()); } for (Map.Entry e : allSystemProps.entrySet()) { command.add("-D" + e.getKey() + "=" + e.getValue()); } } void assertSuccess() throws Exception { execute(true); // Ensure every file was processed by checking a unique property used as // a flag. Each file defines =applied. // // For example: // // file0 // --------------- // file0=applied // include file1 // // file1 // --------------- // file1=applied // // The assertion would be file0 == applied AND file1 == applied. // if (extraPropsFile != null) { extraPropsFile.assertApplied(oa); } if (expectedOverrideAll) { // When overriding with an extra file, check that neither // the master file nor its includes are visible. oa.shouldContain(OVERRIDING_LOG_MSG); masterPropsFile.assertWasOverwritten(oa); } else { oa.shouldNotContain(OVERRIDING_LOG_MSG); masterPropsFile.assertApplied(oa); } // Ensure the last included file overwrote a fixed property. Each file // defines last-file=. // // For example: // // file0 // --------------- // last-file=file0 // include file1 // // file1 // --------------- // last-file=file1 // // The assertion would be last-file == file1. // PropsFile lastFile = (extraPropsFile == null ? masterPropsFile : extraPropsFile).getLastFile(); oa.shouldContain(FilesManager.LAST_FILE_PROP_NAME + "=" + lastFile.fileName); oa.stdoutShouldContain(FilesManager.LAST_FILE_PROP_NAME + ": " + lastFile.fileName); } void assertError(String message) throws Exception { execute(false); oa.shouldContain(message); } OutputAnalyzer getOutputAnalyzer() { return oa; } }