c6f1d5f374
Co-authored-by: Francisco Ferrari Bihurriet <fferrari@openjdk.org> Co-authored-by: Martin Balao <mbalao@openjdk.org> Reviewed-by: weijun, mullan, kdriver
928 lines
35 KiB
Java
928 lines
35 KiB
Java
/*
|
|
* 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<Include> 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<String, String> 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<String, String> 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<PropsFile> createdFiles;
|
|
private final Set<String> 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<String> 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<Path> 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 <fileName>=<APPLIED_PROP_VALUE> 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<String> lines = Files.lines(propsFile.path)) {
|
|
long lineNumber = 1L;
|
|
Iterator<String> 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<String, String> 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<String> 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<String> 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<String> command) {
|
|
Map<String, String> allSystemProps = new LinkedHashMap<>(systemProps);
|
|
if (extraPropsFile != null) {
|
|
allSystemProps.putAll(extraPropsFile.getSystemProperties());
|
|
}
|
|
for (Map.Entry<String, String> 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 <fileName>=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=<fileName>.
|
|
//
|
|
// 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;
|
|
}
|
|
}
|