Rewrite nested jar support code and remove Java 8 support
Rewrite nested jar code to better align with the implementations
provided in Java 17. This update makes two fundamental changes to
the previous implementation:
- Resource cleanup is now handled using the `java.lang.ref.Cleaner`
- Jar URLs now use the form `jar:nested:/my.jar/!nested.jar!/entry`
Unlike the previous `jar🫙/my,jar!/nested.jar!/entry` URL format,
the new format is compatible with Java's default Jar URL handler.
Specifically, it now only uses a single `jar:` prefix and it no longer
includes multiple `!/` separators.
In addition to the changes above, many of the ancillary classes have
also been refactored and updated to create cleaner APIs.
Closes gh-37668
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.jar.JarFile;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link LaunchedURLClassLoader}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
class LaunchedURLClassLoaderTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
@Test
|
||||
void resolveResourceFromArchive() throws Exception {
|
||||
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(
|
||||
new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader());
|
||||
assertThat(loader.getResource("demo/Application.java")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveResourcesFromArchive() throws Exception {
|
||||
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(
|
||||
new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader());
|
||||
assertThat(loader.getResources("demo/Application.java").hasMoreElements()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRootPathFromArchive() throws Exception {
|
||||
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(
|
||||
new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader());
|
||||
assertThat(loader.getResource("")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRootResourcesFromArchive() throws Exception {
|
||||
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(
|
||||
new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader());
|
||||
assertThat(loader.getResources("").hasMoreElements()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveFromNested() throws Exception {
|
||||
File file = new File(this.tempDir, "test.jar");
|
||||
TestJarCreator.createTestJar(file);
|
||||
try (JarFile jarFile = new JarFile(file)) {
|
||||
URL url = jarFile.getUrl();
|
||||
try (LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null)) {
|
||||
URL resource = loader.getResource("nested.jar!/3.dat");
|
||||
assertThat(resource).hasToString(url + "nested.jar!/3.dat");
|
||||
try (InputStream input = resource.openConnection().getInputStream()) {
|
||||
assertThat(input.read()).isEqualTo(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveFromNestedWhileThreadIsInterrupted() throws Exception {
|
||||
File file = new File(this.tempDir, "test.jar");
|
||||
TestJarCreator.createTestJar(file);
|
||||
try (JarFile jarFile = new JarFile(file)) {
|
||||
URL url = jarFile.getUrl();
|
||||
try (LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null)) {
|
||||
Thread.currentThread().interrupt();
|
||||
URL resource = loader.getResource("nested.jar!/3.dat");
|
||||
assertThat(resource).hasToString(url + "nested.jar!/3.dat");
|
||||
URLConnection connection = resource.openConnection();
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
assertThat(input.read()).isEqualTo(3);
|
||||
}
|
||||
((JarURLConnection) connection).getJarFile().close();
|
||||
}
|
||||
finally {
|
||||
Thread.interrupted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.archive;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.TestJarCreator;
|
||||
import org.springframework.boot.loader.archive.Archive.Entry;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ExplodedArchive}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class ExplodedArchiveTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
private File rootDirectory;
|
||||
|
||||
private ExplodedArchive archive;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
createArchive();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
if (this.archive != null) {
|
||||
this.archive.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void createArchive() throws Exception {
|
||||
createArchive(null);
|
||||
}
|
||||
|
||||
private void createArchive(String directoryName) throws Exception {
|
||||
File file = new File(this.tempDir, "test.jar");
|
||||
TestJarCreator.createTestJar(file);
|
||||
this.rootDirectory = (StringUtils.hasText(directoryName) ? new File(this.tempDir, directoryName)
|
||||
: new File(this.tempDir, UUID.randomUUID().toString()));
|
||||
JarFile jarFile = new JarFile(file);
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
File destination = new File(this.rootDirectory.getAbsolutePath() + File.separator + entry.getName());
|
||||
destination.getParentFile().mkdirs();
|
||||
if (entry.isDirectory()) {
|
||||
destination.mkdir();
|
||||
}
|
||||
else {
|
||||
FileCopyUtils.copy(jarFile.getInputStream(entry), new FileOutputStream(destination));
|
||||
}
|
||||
}
|
||||
this.archive = new ExplodedArchive(this.rootDirectory);
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifest() throws Exception {
|
||||
assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntries() {
|
||||
Map<String, Archive.Entry> entries = getEntriesMap(this.archive);
|
||||
assertThat(entries).hasSize(12);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUrl() throws Exception {
|
||||
assertThat(this.archive.getUrl()).isEqualTo(this.rootDirectory.toURI().toURL());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUrlWithSpaceInPath() throws Exception {
|
||||
createArchive("spaces in the name");
|
||||
assertThat(this.archive.getUrl()).isEqualTo(this.rootDirectory.toURI().toURL());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNestedArchive() throws Exception {
|
||||
Entry entry = getEntriesMap(this.archive).get("nested.jar");
|
||||
Archive nested = this.archive.getNestedArchive(entry);
|
||||
assertThat(nested.getUrl()).hasToString(this.rootDirectory.toURI() + "nested.jar");
|
||||
nested.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedDirArchive() throws Exception {
|
||||
Entry entry = getEntriesMap(this.archive).get("d/");
|
||||
Archive nested = this.archive.getNestedArchive(entry);
|
||||
Map<String, Entry> nestedEntries = getEntriesMap(nested);
|
||||
assertThat(nestedEntries).hasSize(1);
|
||||
assertThat(nested.getUrl()).hasToString("file:" + this.rootDirectory.toURI().getPath() + "d/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNonRecursiveEntriesForRoot() throws Exception {
|
||||
try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("/"), false)) {
|
||||
Map<String, Archive.Entry> entries = getEntriesMap(explodedArchive);
|
||||
assertThat(entries).hasSizeGreaterThan(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNonRecursiveManifest() throws Exception {
|
||||
try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("src/test/resources/root"))) {
|
||||
assertThat(explodedArchive.getManifest()).isNotNull();
|
||||
Map<String, Archive.Entry> entries = getEntriesMap(explodedArchive);
|
||||
assertThat(entries).hasSize(4);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNonRecursiveManifestEvenIfNonRecursive() throws Exception {
|
||||
try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("src/test/resources/root"), false)) {
|
||||
assertThat(explodedArchive.getManifest()).isNotNull();
|
||||
Map<String, Archive.Entry> entries = getEntriesMap(explodedArchive);
|
||||
assertThat(entries).hasSize(3);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceAsStream() throws Exception {
|
||||
try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("src/test/resources/root"))) {
|
||||
assertThat(explodedArchive.getManifest()).isNotNull();
|
||||
URLClassLoader loader = new URLClassLoader(new URL[] { explodedArchive.getUrl() });
|
||||
assertThat(loader.getResourceAsStream("META-INF/spring/application.xml")).isNotNull();
|
||||
loader.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceAsStreamNonRecursive() throws Exception {
|
||||
try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("src/test/resources/root"), false)) {
|
||||
assertThat(explodedArchive.getManifest()).isNotNull();
|
||||
URLClassLoader loader = new URLClassLoader(new URL[] { explodedArchive.getUrl() });
|
||||
assertThat(loader.getResourceAsStream("META-INF/spring/application.xml")).isNotNull();
|
||||
loader.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Archive.Entry> getEntriesMap(Archive archive) {
|
||||
Map<String, Archive.Entry> entries = new HashMap<>();
|
||||
for (Archive.Entry entry : archive) {
|
||||
entries.put(entry.getName(), entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.archive;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.TestJarCreator;
|
||||
import org.springframework.boot.loader.archive.Archive.Entry;
|
||||
import org.springframework.boot.loader.jar.JarFile;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarFileArchive}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Camille Vienot
|
||||
*/
|
||||
class JarFileArchiveTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
private File rootJarFile;
|
||||
|
||||
private JarFileArchive archive;
|
||||
|
||||
private String rootJarFileUrl;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
setup(false);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
this.archive.close();
|
||||
}
|
||||
|
||||
private void setup(boolean unpackNested) throws Exception {
|
||||
this.rootJarFile = new File(this.tempDir, "root.jar");
|
||||
this.rootJarFileUrl = this.rootJarFile.toURI().toString();
|
||||
TestJarCreator.createTestJar(this.rootJarFile, unpackNested);
|
||||
if (this.archive != null) {
|
||||
this.archive.close();
|
||||
}
|
||||
this.archive = new JarFileArchive(this.rootJarFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifest() throws Exception {
|
||||
assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntries() {
|
||||
Map<String, Archive.Entry> entries = getEntriesMap(this.archive);
|
||||
assertThat(entries).hasSize(12);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUrl() throws Exception {
|
||||
URL url = this.archive.getUrl();
|
||||
assertThat(url).hasToString(this.rootJarFileUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNestedArchive() throws Exception {
|
||||
Entry entry = getEntriesMap(this.archive).get("nested.jar");
|
||||
try (Archive nested = this.archive.getNestedArchive(entry)) {
|
||||
assertThat(nested.getUrl()).hasToString("jar:" + this.rootJarFileUrl + "!/nested.jar!/");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNestedUnpackedArchive() throws Exception {
|
||||
setup(true);
|
||||
Entry entry = getEntriesMap(this.archive).get("nested.jar");
|
||||
try (Archive nested = this.archive.getNestedArchive(entry)) {
|
||||
assertThat(nested.getUrl().toString()).startsWith("file:");
|
||||
assertThat(nested.getUrl().toString()).endsWith("/nested.jar");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void unpackedLocationsAreUniquePerArchive() throws Exception {
|
||||
setup(true);
|
||||
Entry entry = getEntriesMap(this.archive).get("nested.jar");
|
||||
URL firstNestedUrl;
|
||||
try (Archive firstNested = this.archive.getNestedArchive(entry)) {
|
||||
firstNestedUrl = firstNested.getUrl();
|
||||
}
|
||||
this.archive.close();
|
||||
setup(true);
|
||||
entry = getEntriesMap(this.archive).get("nested.jar");
|
||||
try (Archive secondNested = this.archive.getNestedArchive(entry)) {
|
||||
URL secondNestedUrl = secondNested.getUrl();
|
||||
assertThat(secondNestedUrl).isNotEqualTo(firstNestedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void unpackedLocationsFromSameArchiveShareSameParent() throws Exception {
|
||||
setup(true);
|
||||
try (Archive nestedArchive = this.archive.getNestedArchive(getEntriesMap(this.archive).get("nested.jar"));
|
||||
Archive anotherNestedArchive = this.archive
|
||||
.getNestedArchive(getEntriesMap(this.archive).get("another-nested.jar"))) {
|
||||
File nested = new File(nestedArchive.getUrl().toURI());
|
||||
File anotherNested = new File(anotherNestedArchive.getUrl().toURI());
|
||||
assertThat(nested).hasParent(anotherNested.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void filesInZip64ArchivesAreAllListed() throws IOException {
|
||||
File file = new File(this.tempDir, "test.jar");
|
||||
FileCopyUtils.copy(writeZip64Jar(), file);
|
||||
try (JarFileArchive zip64Archive = new JarFileArchive(file)) {
|
||||
@SuppressWarnings("deprecation")
|
||||
Iterator<Entry> entries = zip64Archive.iterator();
|
||||
for (int i = 0; i < 65537; i++) {
|
||||
assertThat(entries.hasNext()).as(i + "nth file is present").isTrue();
|
||||
entries.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedZip64ArchivesAreHandledGracefully() throws Exception {
|
||||
File file = new File(this.tempDir, "test.jar");
|
||||
try (JarOutputStream output = new JarOutputStream(new FileOutputStream(file))) {
|
||||
JarEntry zip64JarEntry = new JarEntry("nested/zip64.jar");
|
||||
output.putNextEntry(zip64JarEntry);
|
||||
byte[] zip64JarData = writeZip64Jar();
|
||||
zip64JarEntry.setSize(zip64JarData.length);
|
||||
zip64JarEntry.setCompressedSize(zip64JarData.length);
|
||||
zip64JarEntry.setMethod(ZipEntry.STORED);
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(zip64JarData);
|
||||
zip64JarEntry.setCrc(crc32.getValue());
|
||||
output.write(zip64JarData);
|
||||
output.closeEntry();
|
||||
}
|
||||
try (JarFile jarFile = new JarFile(file)) {
|
||||
ZipEntry nestedEntry = jarFile.getEntry("nested/zip64.jar");
|
||||
try (JarFile nestedJarFile = jarFile.getNestedJarFile(nestedEntry)) {
|
||||
Iterator<JarEntry> iterator = nestedJarFile.iterator();
|
||||
for (int i = 0; i < 65537; i++) {
|
||||
assertThat(iterator.hasNext()).as(i + "nth file is present").isTrue();
|
||||
iterator.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] writeZip64Jar() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (JarOutputStream jarOutput = new JarOutputStream(bytes)) {
|
||||
for (int i = 0; i < 65537; i++) {
|
||||
jarOutput.putNextEntry(new JarEntry(i + ".dat"));
|
||||
jarOutput.closeEntry();
|
||||
}
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
private Map<String, Archive.Entry> getEntriesMap(Archive archive) {
|
||||
Map<String, Archive.Entry> entries = new HashMap<>();
|
||||
for (Archive.Entry entry : archive) {
|
||||
entries.put(entry.getName(), entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.data;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
/**
|
||||
* Tests for {@link RandomAccessDataFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class RandomAccessDataFileTests {
|
||||
|
||||
private static final byte[] BYTES;
|
||||
|
||||
static {
|
||||
BYTES = new byte[256];
|
||||
for (int i = 0; i < BYTES.length; i++) {
|
||||
BYTES[i] = (byte) i;
|
||||
}
|
||||
}
|
||||
|
||||
private File tempFile;
|
||||
|
||||
private RandomAccessDataFile file;
|
||||
|
||||
private InputStream inputStream;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File tempDir) throws Exception {
|
||||
this.tempFile = new File(tempDir, "tempFile");
|
||||
FileOutputStream outputStream = new FileOutputStream(this.tempFile);
|
||||
outputStream.write(BYTES);
|
||||
outputStream.close();
|
||||
this.file = new RandomAccessDataFile(this.tempFile);
|
||||
this.inputStream = this.file.getInputStream();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() throws Exception {
|
||||
this.inputStream.close();
|
||||
this.file.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileNotNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RandomAccessDataFile(null))
|
||||
.withMessageContaining("File must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileExists() {
|
||||
File file = new File("/does/not/exist");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RandomAccessDataFile(file))
|
||||
.withMessageContaining(String.format("File %s must exist", file.getAbsolutePath()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithOffsetAndLengthShouldRead() throws Exception {
|
||||
byte[] read = this.file.read(2, 3);
|
||||
assertThat(read).isEqualTo(new byte[] { 2, 3, 4 });
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenOffsetIsBeyondEOFShouldThrowException() {
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.read(257, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenOffsetIsBeyondEndOfSubsectionShouldThrowException() {
|
||||
RandomAccessData subsection = this.file.getSubsection(0, 10);
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> subsection.read(11, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenOffsetPlusLengthGreaterThanEOFShouldThrowException() {
|
||||
assertThatExceptionOfType(EOFException.class).isThrownBy(() -> this.file.read(256, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenOffsetPlusLengthGreaterThanEndOfSubsectionShouldThrowException() {
|
||||
RandomAccessData subsection = this.file.getSubsection(0, 10);
|
||||
assertThatExceptionOfType(EOFException.class).isThrownBy(() -> subsection.read(10, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamRead() throws Exception {
|
||||
for (int i = 0; i <= 255; i++) {
|
||||
assertThat(this.inputStream.read()).isEqualTo(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadNullBytes() {
|
||||
assertThatNullPointerException().isThrownBy(() -> this.inputStream.read(null))
|
||||
.withMessage("Bytes must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadNullBytesWithOffset() {
|
||||
assertThatNullPointerException().isThrownBy(() -> this.inputStream.read(null, 0, 1))
|
||||
.withMessage("Bytes must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadBytes() throws Exception {
|
||||
byte[] b = new byte[256];
|
||||
int amountRead = this.inputStream.read(b);
|
||||
assertThat(b).isEqualTo(BYTES);
|
||||
assertThat(amountRead).isEqualTo(256);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadOffsetBytes() throws Exception {
|
||||
byte[] b = new byte[7];
|
||||
this.inputStream.skip(1);
|
||||
int amountRead = this.inputStream.read(b, 2, 3);
|
||||
assertThat(b).isEqualTo(new byte[] { 0, 0, 1, 2, 3, 0, 0 });
|
||||
assertThat(amountRead).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadMoreBytesThanAvailable() throws Exception {
|
||||
byte[] b = new byte[257];
|
||||
int amountRead = this.inputStream.read(b);
|
||||
assertThat(b).startsWith(BYTES);
|
||||
assertThat(amountRead).isEqualTo(256);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadPastEnd() throws Exception {
|
||||
this.inputStream.skip(255);
|
||||
assertThat(this.inputStream.read()).isEqualTo(0xFF);
|
||||
assertThat(this.inputStream.read()).isEqualTo(-1);
|
||||
assertThat(this.inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadZeroLength() throws Exception {
|
||||
byte[] b = new byte[] { 0x0F };
|
||||
int amountRead = this.inputStream.read(b, 0, 0);
|
||||
assertThat(b).isEqualTo(new byte[] { 0x0F });
|
||||
assertThat(amountRead).isZero();
|
||||
assertThat(this.inputStream.read()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamSkip() throws Exception {
|
||||
long amountSkipped = this.inputStream.skip(4);
|
||||
assertThat(this.inputStream.read()).isEqualTo(4);
|
||||
assertThat(amountSkipped).isEqualTo(4L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamSkipMoreThanAvailable() throws Exception {
|
||||
long amountSkipped = this.inputStream.skip(257);
|
||||
assertThat(this.inputStream.read()).isEqualTo(-1);
|
||||
assertThat(amountSkipped).isEqualTo(256L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamSkipPastEnd() throws Exception {
|
||||
this.inputStream.skip(256);
|
||||
long amountSkipped = this.inputStream.skip(1);
|
||||
assertThat(amountSkipped).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamAvailable() throws Exception {
|
||||
assertThat(this.inputStream.available()).isEqualTo(256);
|
||||
this.inputStream.skip(56);
|
||||
assertThat(this.inputStream.available()).isEqualTo(200);
|
||||
this.inputStream.skip(200);
|
||||
assertThat(this.inputStream.available()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void subsectionNegativeOffset() {
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.getSubsection(-1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void subsectionNegativeLength() {
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.getSubsection(0, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void subsectionZeroLength() throws Exception {
|
||||
RandomAccessData subsection = this.file.getSubsection(0, 0);
|
||||
assertThat(subsection.getInputStream().read()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void subsectionTooBig() {
|
||||
this.file.getSubsection(0, 256);
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.getSubsection(0, 257));
|
||||
}
|
||||
|
||||
@Test
|
||||
void subsectionTooBigWithOffset() {
|
||||
this.file.getSubsection(1, 255);
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.getSubsection(1, 256));
|
||||
}
|
||||
|
||||
@Test
|
||||
void subsection() throws Exception {
|
||||
RandomAccessData subsection = this.file.getSubsection(1, 1);
|
||||
assertThat(subsection.getInputStream().read()).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadPastSubsection() throws Exception {
|
||||
RandomAccessData subsection = this.file.getSubsection(1, 2);
|
||||
InputStream inputStream = subsection.getInputStream();
|
||||
assertThat(inputStream.read()).isOne();
|
||||
assertThat(inputStream.read()).isEqualTo(2);
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamReadBytesPastSubsection() throws Exception {
|
||||
RandomAccessData subsection = this.file.getSubsection(1, 2);
|
||||
InputStream inputStream = subsection.getInputStream();
|
||||
byte[] b = new byte[3];
|
||||
int amountRead = inputStream.read(b);
|
||||
assertThat(b).isEqualTo(new byte[] { 1, 2, 0 });
|
||||
assertThat(amountRead).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamSkipPastSubsection() throws Exception {
|
||||
RandomAccessData subsection = this.file.getSubsection(1, 2);
|
||||
InputStream inputStream = subsection.getInputStream();
|
||||
assertThat(inputStream.skip(3)).isEqualTo(2L);
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamSkipNegative() throws Exception {
|
||||
assertThat(this.inputStream.skip(-1)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFile() {
|
||||
assertThat(this.file.getFile()).isEqualTo(this.tempFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentReads() throws Exception {
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(20);
|
||||
List<Future<Boolean>> results = new ArrayList<>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
results.add(executorService.submit(() -> {
|
||||
InputStream subsectionInputStream = RandomAccessDataFileTests.this.file.getSubsection(0, 256)
|
||||
.getInputStream();
|
||||
byte[] b = new byte[256];
|
||||
subsectionInputStream.read(b);
|
||||
return Arrays.equals(b, BYTES);
|
||||
}));
|
||||
}
|
||||
for (Future<Boolean> future : results) {
|
||||
assertThat(future.get()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link AsciiBytes}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class AsciiBytesTests {
|
||||
|
||||
private static final char NO_SUFFIX = 0;
|
||||
|
||||
@Test
|
||||
void createFromBytes() {
|
||||
AsciiBytes bytes = new AsciiBytes(new byte[] { 65, 66 });
|
||||
assertThat(bytes).hasToString("AB");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromBytesWithOffset() {
|
||||
AsciiBytes bytes = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2);
|
||||
assertThat(bytes).hasToString("BC");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromString() {
|
||||
AsciiBytes bytes = new AsciiBytes("AB");
|
||||
assertThat(bytes).hasToString("AB");
|
||||
}
|
||||
|
||||
@Test
|
||||
void length() {
|
||||
AsciiBytes b1 = new AsciiBytes(new byte[] { 65, 66 });
|
||||
AsciiBytes b2 = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2);
|
||||
assertThat(b1.length()).isEqualTo(2);
|
||||
assertThat(b2.length()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startWith() {
|
||||
AsciiBytes abc = new AsciiBytes(new byte[] { 65, 66, 67 });
|
||||
AsciiBytes ab = new AsciiBytes(new byte[] { 65, 66 });
|
||||
AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67 }, 1, 2);
|
||||
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
|
||||
assertThat(abc.startsWith(abc)).isTrue();
|
||||
assertThat(abc.startsWith(ab)).isTrue();
|
||||
assertThat(abc.startsWith(bc)).isFalse();
|
||||
assertThat(abc.startsWith(abcd)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void endsWith() {
|
||||
AsciiBytes abc = new AsciiBytes(new byte[] { 65, 66, 67 });
|
||||
AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67 }, 1, 2);
|
||||
AsciiBytes ab = new AsciiBytes(new byte[] { 65, 66 });
|
||||
AsciiBytes aabc = new AsciiBytes(new byte[] { 65, 65, 66, 67 });
|
||||
assertThat(abc.endsWith(abc)).isTrue();
|
||||
assertThat(abc.endsWith(bc)).isTrue();
|
||||
assertThat(abc.endsWith(ab)).isFalse();
|
||||
assertThat(abc.endsWith(aabc)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void substringFromBeingIndex() {
|
||||
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
|
||||
assertThat(abcd.substring(0)).hasToString("ABCD");
|
||||
assertThat(abcd.substring(1)).hasToString("BCD");
|
||||
assertThat(abcd.substring(2)).hasToString("CD");
|
||||
assertThat(abcd.substring(3)).hasToString("D");
|
||||
assertThat(abcd.substring(4).toString()).isEmpty();
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> abcd.substring(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void substring() {
|
||||
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
|
||||
assertThat(abcd.substring(0, 4)).hasToString("ABCD");
|
||||
assertThat(abcd.substring(1, 3)).hasToString("BC");
|
||||
assertThat(abcd.substring(3, 4)).hasToString("D");
|
||||
assertThat(abcd.substring(3, 3).toString()).isEmpty();
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> abcd.substring(3, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeAndEquals() {
|
||||
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
|
||||
AsciiBytes bc = new AsciiBytes(new byte[] { 66, 67 });
|
||||
AsciiBytes bc_substring = new AsciiBytes(new byte[] { 65, 66, 67, 68 }).substring(1, 3);
|
||||
AsciiBytes bc_string = new AsciiBytes("BC");
|
||||
assertThat(bc).hasSameHashCodeAs(bc);
|
||||
assertThat(bc).hasSameHashCodeAs(bc_substring);
|
||||
assertThat(bc).hasSameHashCodeAs(bc_string);
|
||||
assertThat(bc).isEqualTo(bc);
|
||||
assertThat(bc).isEqualTo(bc_substring);
|
||||
assertThat(bc).isEqualTo(bc_string);
|
||||
assertThat(bc.hashCode()).isNotEqualTo(abcd.hashCode());
|
||||
assertThat(bc).isNotEqualTo(abcd);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeSameAsString() {
|
||||
hashCodeSameAsString("abcABC123xyz!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeSameAsStringWithSpecial() {
|
||||
hashCodeSameAsString("special/\u00EB.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeSameAsStringWithCyrillicCharacters() {
|
||||
hashCodeSameAsString("\u0432\u0435\u0441\u043D\u0430");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeSameAsStringWithEmoji() {
|
||||
hashCodeSameAsString("\ud83d\udca9");
|
||||
}
|
||||
|
||||
private void hashCodeSameAsString(String input) {
|
||||
assertThat(new AsciiBytes(input)).hasSameHashCodeAs(input);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesSameAsString() {
|
||||
matchesSameAsString("abcABC123xyz!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesSameAsStringWithSpecial() {
|
||||
matchesSameAsString("special/\u00EB.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesSameAsStringWithCyrillicCharacters() {
|
||||
matchesSameAsString("\u0432\u0435\u0441\u043D\u0430");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesDifferentLengths() {
|
||||
assertThat(new AsciiBytes("abc").matches("ab", NO_SUFFIX)).isFalse();
|
||||
assertThat(new AsciiBytes("abc").matches("abcd", NO_SUFFIX)).isFalse();
|
||||
assertThat(new AsciiBytes("abc").matches("abc", NO_SUFFIX)).isTrue();
|
||||
assertThat(new AsciiBytes("abc").matches("a", 'b')).isFalse();
|
||||
assertThat(new AsciiBytes("abc").matches("abc", 'd')).isFalse();
|
||||
assertThat(new AsciiBytes("abc").matches("ab", 'c')).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesSuffix() {
|
||||
assertThat(new AsciiBytes("ab").matches("a", 'b')).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesSameAsStringWithEmoji() {
|
||||
matchesSameAsString("\ud83d\udca9");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeFromInstanceMatchesHashCodeFromString() {
|
||||
String name = "fonts/宋体/simsun.ttf";
|
||||
assertThat(new AsciiBytes(name).hashCode()).isEqualTo(AsciiBytes.hashCode(name));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceCreatedFromCharSequenceMatchesSameCharSequence() {
|
||||
String name = "fonts/宋体/simsun.ttf";
|
||||
assertThat(new AsciiBytes(name).matches(name, NO_SUFFIX)).isTrue();
|
||||
}
|
||||
|
||||
private void matchesSameAsString(String input) {
|
||||
assertThat(new AsciiBytes(input).matches(input, NO_SUFFIX)).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.TestJarCreator;
|
||||
import org.springframework.boot.loader.data.RandomAccessData;
|
||||
import org.springframework.boot.loader.data.RandomAccessDataFile;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CentralDirectoryParser}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class CentralDirectoryParserTests {
|
||||
|
||||
private File jarFile;
|
||||
|
||||
private RandomAccessDataFile jarData;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File tempDir) throws Exception {
|
||||
this.jarFile = new File(tempDir, "test.jar");
|
||||
TestJarCreator.createTestJar(this.jarFile);
|
||||
this.jarData = new RandomAccessDataFile(this.jarFile);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
this.jarData.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitsInOrder() throws Exception {
|
||||
MockCentralDirectoryVisitor visitor = new MockCentralDirectoryVisitor();
|
||||
CentralDirectoryParser parser = new CentralDirectoryParser();
|
||||
parser.addVisitor(visitor);
|
||||
parser.parse(this.jarData, false);
|
||||
List<String> invocations = visitor.getInvocations();
|
||||
assertThat(invocations).startsWith("visitStart").endsWith("visitEnd").contains("visitFileHeader");
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitRecords() throws Exception {
|
||||
Collector collector = new Collector();
|
||||
CentralDirectoryParser parser = new CentralDirectoryParser();
|
||||
parser.addVisitor(collector);
|
||||
parser.parse(this.jarData, false);
|
||||
Iterator<CentralDirectoryFileHeader> headers = collector.getHeaders().iterator();
|
||||
assertThat(headers.next().getName()).hasToString("META-INF/");
|
||||
assertThat(headers.next().getName()).hasToString("META-INF/MANIFEST.MF");
|
||||
assertThat(headers.next().getName()).hasToString("1.dat");
|
||||
assertThat(headers.next().getName()).hasToString("2.dat");
|
||||
assertThat(headers.next().getName()).hasToString("d/");
|
||||
assertThat(headers.next().getName()).hasToString("d/9.dat");
|
||||
assertThat(headers.next().getName()).hasToString("special/");
|
||||
assertThat(headers.next().getName()).hasToString("special/\u00EB.dat");
|
||||
assertThat(headers.next().getName()).hasToString("nested.jar");
|
||||
assertThat(headers.next().getName()).hasToString("another-nested.jar");
|
||||
assertThat(headers.next().getName()).hasToString("space nested.jar");
|
||||
assertThat(headers.next().getName()).hasToString("multi-release.jar");
|
||||
assertThat(headers.hasNext()).isFalse();
|
||||
}
|
||||
|
||||
static class Collector implements CentralDirectoryVisitor {
|
||||
|
||||
private final List<CentralDirectoryFileHeader> headers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
|
||||
this.headers.add(fileHeader.clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
List<CentralDirectoryFileHeader> getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MockCentralDirectoryVisitor implements CentralDirectoryVisitor {
|
||||
|
||||
private final List<String> invocations = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) {
|
||||
this.invocations.add("visitStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
|
||||
this.invocations.add("visitFileHeader");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
this.invocations.add("visitEnd");
|
||||
}
|
||||
|
||||
List<String> getInvocations() {
|
||||
return this.invocations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.TestJarCreator;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Handler}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@ExtendWith(JarUrlProtocolHandler.class)
|
||||
class HandlerTests {
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
@Test
|
||||
void parseUrlWithJarRootContextAndAbsoluteSpecThatUsesContext() throws MalformedURLException {
|
||||
String spec = "/entry.txt";
|
||||
URL context = createUrl("file:example.jar!/");
|
||||
this.handler.parseURL(context, spec, 0, spec.length());
|
||||
assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWithDirectoryEntryContextAndAbsoluteSpecThatUsesContext() throws MalformedURLException {
|
||||
String spec = "/entry.txt";
|
||||
URL context = createUrl("file:example.jar!/dir/");
|
||||
this.handler.parseURL(context, spec, 0, spec.length());
|
||||
assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWithJarRootContextAndRelativeSpecThatUsesContext() throws MalformedURLException {
|
||||
String spec = "entry.txt";
|
||||
URL context = createUrl("file:example.jar!/");
|
||||
this.handler.parseURL(context, spec, 0, spec.length());
|
||||
assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWithDirectoryEntryContextAndRelativeSpecThatUsesContext() throws MalformedURLException {
|
||||
String spec = "entry.txt";
|
||||
URL context = createUrl("file:example.jar!/dir/");
|
||||
this.handler.parseURL(context, spec, 0, spec.length());
|
||||
assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/dir/entry.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWithFileEntryContextAndRelativeSpecThatUsesContext() throws MalformedURLException {
|
||||
String spec = "entry.txt";
|
||||
URL context = createUrl("file:example.jar!/dir/file");
|
||||
this.handler.parseURL(context, spec, 0, spec.length());
|
||||
assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/dir/entry.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWithSpecThatIgnoresContext() throws MalformedURLException {
|
||||
JarFile.registerUrlProtocolHandler();
|
||||
String spec = "jar:file:/other.jar!/nested!/entry.txt";
|
||||
URL context = createUrl("file:example.jar!/dir/file");
|
||||
this.handler.parseURL(context, spec, 0, spec.length());
|
||||
assertThat(context.toExternalForm()).isEqualTo("jar:jar:file:/other.jar!/nested!/entry.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileReturnsFalseForUrlsWithDifferentProtocols() throws MalformedURLException {
|
||||
assertThat(this.handler.sameFile(new URL("jar:file:foo.jar!/content.txt"), new URL("file:/foo.jar"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileReturnsFalseForDifferentFileInSameJar() throws MalformedURLException {
|
||||
assertThat(this.handler.sameFile(new URL("jar:file:foo.jar!/the/path/to/the/first/content.txt"),
|
||||
new URL("jar:file:/foo.jar!/content.txt")))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileReturnsFalseForSameFileInDifferentJars() throws MalformedURLException {
|
||||
assertThat(this.handler.sameFile(new URL("jar:file:/the/path/to/the/first.jar!/content.txt"),
|
||||
new URL("jar:file:/second.jar!/content.txt")))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileReturnsTrueForSameFileInSameJar() throws MalformedURLException {
|
||||
assertThat(this.handler.sameFile(new URL("jar:file:/the/path/to/the/first.jar!/content.txt"),
|
||||
new URL("jar:file:/the/path/to/the/first.jar!/content.txt")))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileReturnsTrueForUrlsThatReferenceSameFileViaNestedArchiveAndFromRootOfJar()
|
||||
throws MalformedURLException {
|
||||
assertThat(this.handler.sameFile(new URL("jar:file:/test.jar!/BOOT-INF/classes!/foo.txt"),
|
||||
new URL("jar:file:/test.jar!/BOOT-INF/classes/foo.txt")))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodesAreEqualForUrlsThatReferenceSameFileViaNestedArchiveAndFromRootOfJar() throws MalformedURLException {
|
||||
assertThat(this.handler.hashCode(new URL("jar:file:/test.jar!/BOOT-INF/classes!/foo.txt")))
|
||||
.isEqualTo(this.handler.hashCode(new URL("jar:file:/test.jar!/BOOT-INF/classes/foo.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void urlWithSpecReferencingParentDirectory() throws MalformedURLException {
|
||||
assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes!/xsd/directoryA/a.xsd",
|
||||
"../directoryB/c/d/e.xsd");
|
||||
}
|
||||
|
||||
@Test
|
||||
void urlWithSpecReferencingAncestorDirectoryOutsideJarStopsAtJarRoot() throws MalformedURLException {
|
||||
assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes!/xsd/directoryA/a.xsd",
|
||||
"../../../../../../directoryB/b.xsd");
|
||||
}
|
||||
|
||||
@Test
|
||||
void urlWithSpecReferencingCurrentDirectory() throws MalformedURLException {
|
||||
assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes!/xsd/directoryA/a.xsd",
|
||||
"./directoryB/c/d/e.xsd");
|
||||
}
|
||||
|
||||
@Test
|
||||
void urlWithRef() throws MalformedURLException {
|
||||
assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes", "!/foo.txt#alpha");
|
||||
}
|
||||
|
||||
@Test
|
||||
void urlWithQuery() throws MalformedURLException {
|
||||
assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes", "!/foo.txt?alpha");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fallbackToJdksJarUrlStreamHandler(@TempDir File tempDir) throws Exception {
|
||||
File testJar = new File(tempDir, "test.jar");
|
||||
TestJarCreator.createTestJar(testJar);
|
||||
URLConnection connection = new URL(null, "jar:" + testJar.toURI().toURL() + "!/nested.jar!/", this.handler)
|
||||
.openConnection();
|
||||
assertThat(connection).isInstanceOf(JarURLConnection.class);
|
||||
((JarURLConnection) connection).getJarFile().close();
|
||||
URLConnection jdkConnection = new URL(null, "jar:file:" + testJar.toURI().toURL() + "!/nested.jar!/",
|
||||
this.handler)
|
||||
.openConnection();
|
||||
assertThat(jdkConnection).isNotInstanceOf(JarURLConnection.class);
|
||||
assertThat(jdkConnection.getClass().getName()).endsWith(".JarURLConnection");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarHasAPlusInItsPathConnectionJarFileMatchesOriginalJarFile(@TempDir File tempDir) throws Exception {
|
||||
File testJar = new File(tempDir, "t+e+s+t.jar");
|
||||
TestJarCreator.createTestJar(testJar);
|
||||
URL url = new URL(null, "jar:" + testJar.toURI().toURL() + "!/nested.jar!/3.dat", this.handler);
|
||||
JarURLConnection connection = (JarURLConnection) url.openConnection();
|
||||
try (JarFile jarFile = JarFileWrapper.unwrap(connection.getJarFile())) {
|
||||
assertThat(jarFile.getRootJarFile().getFile()).isEqualTo(testJar);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarHasASpaceInItsPathConnectionJarFileMatchesOriginalJarFile(@TempDir File tempDir) throws Exception {
|
||||
File testJar = new File(tempDir, "t e s t.jar");
|
||||
TestJarCreator.createTestJar(testJar);
|
||||
URL url = new URL(null, "jar:" + testJar.toURI().toURL() + "!/nested.jar!/3.dat", this.handler);
|
||||
JarURLConnection connection = (JarURLConnection) url.openConnection();
|
||||
try (JarFile jarFile = JarFileWrapper.unwrap(connection.getJarFile())) {
|
||||
assertThat(jarFile.getRootJarFile().getFile()).isEqualTo(testJar);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertStandardAndCustomHandlerUrlsAreEqual(String context, String spec) throws MalformedURLException {
|
||||
URL standardUrl = new URL(new URL("jar:" + context), spec);
|
||||
URL customHandlerUrl = new URL(new URL("jar", null, -1, context, this.handler), spec);
|
||||
assertThat(customHandlerUrl).hasToString(standardUrl.toString());
|
||||
assertThat(customHandlerUrl.getFile()).isEqualTo(standardUrl.getFile());
|
||||
assertThat(customHandlerUrl.getPath()).isEqualTo(standardUrl.getPath());
|
||||
assertThat(customHandlerUrl.getQuery()).isEqualTo(standardUrl.getQuery());
|
||||
assertThat(customHandlerUrl.getRef()).isEqualTo(standardUrl.getRef());
|
||||
}
|
||||
|
||||
private URL createUrl(String file) throws MalformedURLException {
|
||||
return new URL("jar", null, -1, file, this.handler);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,736 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilePermission;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.TestJarCreator;
|
||||
import org.springframework.boot.loader.data.RandomAccessDataFile;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Martin Lau
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@ExtendWith(JarUrlProtocolHandler.class)
|
||||
class JarFileTests {
|
||||
|
||||
private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
|
||||
|
||||
private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
private File rootJarFile;
|
||||
|
||||
private JarFile jarFile;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
this.rootJarFile = new File(this.tempDir, "root.jar");
|
||||
TestJarCreator.createTestJar(this.rootJarFile);
|
||||
this.jarFile = new JarFile(this.rootJarFile);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
this.jarFile.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void jdkJarFile() throws Exception {
|
||||
// Sanity checks to see how the default jar file operates
|
||||
java.util.jar.JarFile jarFile = new java.util.jar.JarFile(this.rootJarFile);
|
||||
assertThat(jarFile.getComment()).isEqualTo("outer");
|
||||
Enumeration<java.util.jar.JarEntry> entries = jarFile.entries();
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("META-INF/");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("META-INF/MANIFEST.MF");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("1.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("2.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("d/");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("d/9.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("special/");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("special/\u00EB.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("nested.jar");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("another-nested.jar");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("space nested.jar");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("multi-release.jar");
|
||||
assertThat(entries.hasMoreElements()).isFalse();
|
||||
URL jarUrl = new URL("jar:" + this.rootJarFile.toURI() + "!/");
|
||||
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { jarUrl });
|
||||
assertThat(urlClassLoader.getResource("special/\u00EB.dat")).isNotNull();
|
||||
assertThat(urlClassLoader.getResource("d/9.dat")).isNotNull();
|
||||
urlClassLoader.close();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromFile() throws Exception {
|
||||
JarFile jarFile = new JarFile(this.rootJarFile);
|
||||
assertThat(jarFile.getName()).isNotNull();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifest() throws Exception {
|
||||
assertThat(this.jarFile.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestEntry() throws Exception {
|
||||
ZipEntry entry = this.jarFile.getJarEntry("META-INF/MANIFEST.MF");
|
||||
Manifest manifest = new Manifest(this.jarFile.getInputStream(entry));
|
||||
assertThat(manifest.getMainAttributes().getValue("Built-By")).isEqualTo("j1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntries() {
|
||||
Enumeration<java.util.jar.JarEntry> entries = this.jarFile.entries();
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("META-INF/");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("META-INF/MANIFEST.MF");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("1.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("2.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("d/");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("d/9.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("special/");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("special/\u00EB.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("nested.jar");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("another-nested.jar");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("space nested.jar");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("multi-release.jar");
|
||||
assertThat(entries.hasMoreElements()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSpecialResourceViaClassLoader() throws Exception {
|
||||
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { this.jarFile.getUrl() });
|
||||
assertThat(urlClassLoader.getResource("special/\u00EB.dat")).isNotNull();
|
||||
urlClassLoader.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getJarEntry() {
|
||||
java.util.jar.JarEntry entry = this.jarFile.getJarEntry("1.dat");
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.getName()).isEqualTo("1.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getJarEntryWhenClosed() throws Exception {
|
||||
this.jarFile.close();
|
||||
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getJarEntry("1.dat"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStream() throws Exception {
|
||||
InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("1.dat"));
|
||||
assertThat(inputStream.available()).isOne();
|
||||
assertThat(inputStream.read()).isOne();
|
||||
assertThat(inputStream.available()).isZero();
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamWhenClosed() throws Exception {
|
||||
ZipEntry entry = this.jarFile.getEntry("1.dat");
|
||||
this.jarFile.close();
|
||||
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getInputStream(entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getComment() {
|
||||
assertThat(this.jarFile.getComment()).isEqualTo("outer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCommentWhenClosed() throws Exception {
|
||||
this.jarFile.close();
|
||||
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getComment());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getName() {
|
||||
assertThat(this.jarFile.getName()).isEqualTo(this.rootJarFile.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
void size() throws Exception {
|
||||
try (ZipFile zip = new ZipFile(this.rootJarFile)) {
|
||||
assertThat(this.jarFile).hasSize(zip.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeWhenClosed() throws Exception {
|
||||
this.jarFile.close();
|
||||
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryTime() throws Exception {
|
||||
java.util.jar.JarFile jdkJarFile = new java.util.jar.JarFile(this.rootJarFile);
|
||||
assertThat(this.jarFile.getEntry("META-INF/MANIFEST.MF").getTime())
|
||||
.isEqualTo(jdkJarFile.getEntry("META-INF/MANIFEST.MF").getTime());
|
||||
jdkJarFile.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void close() throws Exception {
|
||||
RandomAccessDataFile randomAccessDataFile = spy(new RandomAccessDataFile(this.rootJarFile));
|
||||
JarFile jarFile = new JarFile(randomAccessDataFile);
|
||||
jarFile.close();
|
||||
then(randomAccessDataFile).should().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUrl() throws Exception {
|
||||
URL url = this.jarFile.getUrl();
|
||||
assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/");
|
||||
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
|
||||
assertThat(JarFileWrapper.unwrap(jarURLConnection.getJarFile())).isSameAs(this.jarFile);
|
||||
assertThat(jarURLConnection.getJarEntry()).isNull();
|
||||
assertThat(jarURLConnection.getContentLength()).isGreaterThan(1);
|
||||
assertThat(JarFileWrapper.unwrap((java.util.jar.JarFile) jarURLConnection.getContent())).isSameAs(this.jarFile);
|
||||
assertThat(jarURLConnection.getContentType()).isEqualTo("x-java/jar");
|
||||
assertThat(jarURLConnection.getJarFileURL().toURI()).isEqualTo(this.rootJarFile.toURI());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createEntryUrl() throws Exception {
|
||||
URL url = new URL(this.jarFile.getUrl(), "1.dat");
|
||||
assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/1.dat");
|
||||
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
|
||||
assertThat(JarFileWrapper.unwrap(jarURLConnection.getJarFile())).isSameAs(this.jarFile);
|
||||
assertThat(jarURLConnection.getJarEntry()).isSameAs(this.jarFile.getJarEntry("1.dat"));
|
||||
assertThat(jarURLConnection.getContentLength()).isOne();
|
||||
assertThat(jarURLConnection.getContent()).isInstanceOf(InputStream.class);
|
||||
assertThat(jarURLConnection.getContentType()).isEqualTo("content/unknown");
|
||||
assertThat(jarURLConnection.getPermission()).isInstanceOf(FilePermission.class);
|
||||
FilePermission permission = (FilePermission) jarURLConnection.getPermission();
|
||||
assertThat(permission.getActions()).isEqualTo("read");
|
||||
assertThat(permission.getName()).isEqualTo(this.rootJarFile.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMissingEntryUrl() throws Exception {
|
||||
URL url = new URL(this.jarFile.getUrl(), "missing.dat");
|
||||
assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/missing.dat");
|
||||
assertThatExceptionOfType(FileNotFoundException.class)
|
||||
.isThrownBy(((JarURLConnection) url.openConnection())::getJarEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUrlStream() throws Exception {
|
||||
URL url = this.jarFile.getUrl();
|
||||
url.openConnection();
|
||||
assertThatIOException().isThrownBy(url::openStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryUrlStream() throws Exception {
|
||||
URL url = new URL(this.jarFile.getUrl(), "1.dat");
|
||||
url.openConnection();
|
||||
try (InputStream stream = url.openStream()) {
|
||||
assertThat(stream.read()).isOne();
|
||||
assertThat(stream.read()).isEqualTo(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNestedJarFile() throws Exception {
|
||||
try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
assertThat(nestedJarFile.getComment()).isEqualTo("nested");
|
||||
Enumeration<java.util.jar.JarEntry> entries = nestedJarFile.entries();
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("META-INF/");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("META-INF/MANIFEST.MF");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("3.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("4.dat");
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("\u00E4.dat");
|
||||
assertThat(entries.hasMoreElements()).isFalse();
|
||||
|
||||
InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("3.dat"));
|
||||
assertThat(inputStream.read()).isEqualTo(3);
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
|
||||
URL url = nestedJarFile.getUrl();
|
||||
assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/nested.jar!/");
|
||||
JarURLConnection conn = (JarURLConnection) url.openConnection();
|
||||
assertThat(JarFileWrapper.unwrap(conn.getJarFile())).isSameAs(nestedJarFile);
|
||||
assertThat(conn.getJarFileURL()).hasToString("jar:" + this.rootJarFile.toURI() + "!/nested.jar");
|
||||
assertThat(conn.getInputStream()).isNotNull();
|
||||
JarInputStream jarInputStream = new JarInputStream(conn.getInputStream());
|
||||
assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("3.dat");
|
||||
assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("4.dat");
|
||||
assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("\u00E4.dat");
|
||||
jarInputStream.close();
|
||||
assertThat(conn.getPermission()).isInstanceOf(FilePermission.class);
|
||||
FilePermission permission = (FilePermission) conn.getPermission();
|
||||
assertThat(permission.getActions()).isEqualTo("read");
|
||||
assertThat(permission.getName()).isEqualTo(this.rootJarFile.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNestedJarDirectory() throws Exception {
|
||||
try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("d/"))) {
|
||||
Enumeration<java.util.jar.JarEntry> entries = nestedJarFile.entries();
|
||||
assertThat(entries.nextElement().getName()).isEqualTo("9.dat");
|
||||
assertThat(entries.hasMoreElements()).isFalse();
|
||||
|
||||
try (InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("9.dat"))) {
|
||||
assertThat(inputStream.read()).isEqualTo(9);
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
URL url = nestedJarFile.getUrl();
|
||||
assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/d!/");
|
||||
JarURLConnection connection = (JarURLConnection) url.openConnection();
|
||||
assertThat(JarFileWrapper.unwrap(connection.getJarFile())).isSameAs(nestedJarFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNestedJarEntryUrl() throws Exception {
|
||||
try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
URL url = nestedJarFile.getJarEntry("3.dat").getUrl();
|
||||
assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat");
|
||||
try (InputStream inputStream = url.openStream()) {
|
||||
assertThat(inputStream).isNotNull();
|
||||
assertThat(inputStream.read()).isEqualTo(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createUrlFromString() throws Exception {
|
||||
String spec = "jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat";
|
||||
URL url = new URL(spec);
|
||||
assertThat(url).hasToString(spec);
|
||||
JarURLConnection connection = (JarURLConnection) url.openConnection();
|
||||
try (InputStream inputStream = connection.getInputStream()) {
|
||||
assertThat(inputStream).isNotNull();
|
||||
assertThat(inputStream.read()).isEqualTo(3);
|
||||
assertThat(connection.getURL()).hasToString(spec);
|
||||
assertThat(connection.getJarFileURL()).hasToString("jar:" + this.rootJarFile.toURI() + "!/nested.jar");
|
||||
assertThat(connection.getEntryName()).isEqualTo("3.dat");
|
||||
connection.getJarFile().close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createNonNestedUrlFromString() throws Exception {
|
||||
nonNestedJarFileFromString("jar:" + this.rootJarFile.toURI() + "!/2.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createNonNestedUrlFromPathString() throws Exception {
|
||||
nonNestedJarFileFromString("jar:" + this.rootJarFile.toPath().toUri() + "!/2.dat");
|
||||
}
|
||||
|
||||
private void nonNestedJarFileFromString(String spec) throws Exception {
|
||||
JarFile.registerUrlProtocolHandler();
|
||||
URL url = new URL(spec);
|
||||
assertThat(url).hasToString(spec);
|
||||
JarURLConnection connection = (JarURLConnection) url.openConnection();
|
||||
try (InputStream inputStream = connection.getInputStream()) {
|
||||
assertThat(inputStream).isNotNull();
|
||||
assertThat(inputStream.read()).isEqualTo(2);
|
||||
assertThat(connection.getURL()).hasToString(spec);
|
||||
assertThat(connection.getJarFileURL().toURI()).isEqualTo(this.rootJarFile.toURI());
|
||||
assertThat(connection.getEntryName()).isEqualTo("2.dat");
|
||||
}
|
||||
connection.getJarFile().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDirectoryInputStream() throws Exception {
|
||||
InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("d/"));
|
||||
assertThat(inputStream).isNotNull();
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDirectoryInputStreamWithoutSlash() throws Exception {
|
||||
InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("d"));
|
||||
assertThat(inputStream).isNotNull();
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sensibleToString() throws Exception {
|
||||
assertThat(this.jarFile).hasToString(this.rootJarFile.getPath());
|
||||
try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
assertThat(nested).hasToString(this.rootJarFile.getPath() + "!/nested.jar");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifySignedJar() throws Exception {
|
||||
File signedJarFile = getSignedJarFile();
|
||||
assertThat(signedJarFile).exists();
|
||||
try (java.util.jar.JarFile expected = new java.util.jar.JarFile(signedJarFile)) {
|
||||
try (JarFile actual = new JarFile(signedJarFile)) {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
Enumeration<JarEntry> actualEntries = actual.entries();
|
||||
while (actualEntries.hasMoreElements()) {
|
||||
JarEntry actualEntry = actualEntries.nextElement();
|
||||
java.util.jar.JarEntry expectedEntry = expected.getJarEntry(actualEntry.getName());
|
||||
StreamUtils.drain(expected.getInputStream(expectedEntry));
|
||||
if (!actualEntry.getName().equals("META-INF/MANIFEST.MF")) {
|
||||
assertThat(actualEntry.getCertificates()).as(actualEntry.getName())
|
||||
.isEqualTo(expectedEntry.getCertificates());
|
||||
assertThat(actualEntry.getCodeSigners()).as(actualEntry.getName())
|
||||
.isEqualTo(expectedEntry.getCodeSigners());
|
||||
}
|
||||
}
|
||||
assertThat(stopWatch.getTotalTimeSeconds()).isLessThan(3.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File getSignedJarFile() {
|
||||
String[] entries = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
|
||||
for (String entry : entries) {
|
||||
if (entry.contains("bcprov")) {
|
||||
return new File(entry);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarFileWithScriptAtTheStart() throws Exception {
|
||||
File file = new File(this.tempDir, "test.jar");
|
||||
InputStream sourceJarContent = new FileInputStream(this.rootJarFile);
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
StreamUtils.copy("#/bin/bash", Charset.defaultCharset(), outputStream);
|
||||
FileCopyUtils.copy(sourceJarContent, outputStream);
|
||||
this.rootJarFile = file;
|
||||
this.jarFile.close();
|
||||
this.jarFile = new JarFile(file);
|
||||
// Call some other tests to verify
|
||||
getEntries();
|
||||
getNestedJarFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
void cannotLoadMissingJar() throws Exception {
|
||||
// relates to gh-1070
|
||||
try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
URL nestedUrl = nestedJarFile.getUrl();
|
||||
URL url = new URL(nestedUrl, nestedJarFile.getUrl() + "missing.jar!/3.dat");
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(url.openConnection()::getInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerUrlProtocolHandlerWithNoExistingRegistration() {
|
||||
String original = System.getProperty(PROTOCOL_HANDLER);
|
||||
try {
|
||||
System.clearProperty(PROTOCOL_HANDLER);
|
||||
JarFile.registerUrlProtocolHandler();
|
||||
String protocolHandler = System.getProperty(PROTOCOL_HANDLER);
|
||||
assertThat(protocolHandler).isEqualTo(HANDLERS_PACKAGE);
|
||||
}
|
||||
finally {
|
||||
if (original == null) {
|
||||
System.clearProperty(PROTOCOL_HANDLER);
|
||||
}
|
||||
else {
|
||||
System.setProperty(PROTOCOL_HANDLER, original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerUrlProtocolHandlerAddsToExistingRegistration() {
|
||||
String original = System.getProperty(PROTOCOL_HANDLER);
|
||||
try {
|
||||
System.setProperty(PROTOCOL_HANDLER, "com.example");
|
||||
JarFile.registerUrlProtocolHandler();
|
||||
String protocolHandler = System.getProperty(PROTOCOL_HANDLER);
|
||||
assertThat(protocolHandler).isEqualTo("com.example|" + HANDLERS_PACKAGE);
|
||||
}
|
||||
finally {
|
||||
if (original == null) {
|
||||
System.clearProperty(PROTOCOL_HANDLER);
|
||||
}
|
||||
else {
|
||||
System.setProperty(PROTOCOL_HANDLER, original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarFileCanBeDeletedOnceItHasBeenClosed() throws Exception {
|
||||
File jar = new File(this.tempDir, "test.jar");
|
||||
TestJarCreator.createTestJar(jar);
|
||||
JarFile jf = new JarFile(jar);
|
||||
jf.close();
|
||||
assertThat(jar.delete()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createUrlFromStringWithContextWhenNotFound() throws Exception {
|
||||
// gh-12483
|
||||
JarURLConnection.setUseFastExceptions(true);
|
||||
try {
|
||||
try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
URL context = nested.getUrl();
|
||||
new URL(context, "jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat").openConnection()
|
||||
.getInputStream()
|
||||
.close();
|
||||
assertThatExceptionOfType(FileNotFoundException.class)
|
||||
.isThrownBy(new URL(context, "jar:" + this.rootJarFile.toURI() + "!/no.dat")
|
||||
.openConnection()::getInputStream);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
JarURLConnection.setUseFastExceptions(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiReleaseEntry() throws Exception {
|
||||
try (JarFile multiRelease = this.jarFile.getNestedJarFile(this.jarFile.getEntry("multi-release.jar"))) {
|
||||
ZipEntry entry = multiRelease.getEntry("multi-release.dat");
|
||||
assertThat(entry.getName()).isEqualTo("multi-release.dat");
|
||||
InputStream inputStream = multiRelease.getInputStream(entry);
|
||||
assertThat(inputStream.available()).isOne();
|
||||
assertThat(inputStream.read()).isEqualTo(Runtime.version().feature());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void zip64JarThatExceedsZipEntryLimitCanBeRead() throws Exception {
|
||||
File zip64Jar = new File(this.tempDir, "zip64.jar");
|
||||
FileCopyUtils.copy(zip64Jar(), zip64Jar);
|
||||
try (JarFile zip64JarFile = new JarFile(zip64Jar)) {
|
||||
List<JarEntry> entries = Collections.list(zip64JarFile.entries());
|
||||
assertThat(entries).hasSize(65537);
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
JarEntry entry = entries.get(i);
|
||||
InputStream entryInput = zip64JarFile.getInputStream(entry);
|
||||
assertThat(entryInput).hasContent("Entry " + (i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void zip64JarThatExceedsZipSizeLimitCanBeRead() throws Exception {
|
||||
Assumptions.assumeTrue(this.tempDir.getFreeSpace() > 6 * 1024 * 1024 * 1024, "Insufficient disk space");
|
||||
File zip64Jar = new File(this.tempDir, "zip64.jar");
|
||||
File entry = new File(this.tempDir, "entry.dat");
|
||||
CRC32 crc32 = new CRC32();
|
||||
try (FileOutputStream entryOut = new FileOutputStream(entry)) {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
new Random().nextBytes(data);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
entryOut.write(data);
|
||||
crc32.update(data);
|
||||
}
|
||||
}
|
||||
try (JarOutputStream jarOutput = new JarOutputStream(new FileOutputStream(zip64Jar))) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
JarEntry storedEntry = new JarEntry("huge-" + i);
|
||||
storedEntry.setSize(entry.length());
|
||||
storedEntry.setCompressedSize(entry.length());
|
||||
storedEntry.setCrc(crc32.getValue());
|
||||
storedEntry.setMethod(ZipEntry.STORED);
|
||||
jarOutput.putNextEntry(storedEntry);
|
||||
try (FileInputStream entryIn = new FileInputStream(entry)) {
|
||||
StreamUtils.copy(entryIn, jarOutput);
|
||||
}
|
||||
jarOutput.closeEntry();
|
||||
}
|
||||
}
|
||||
try (JarFile zip64JarFile = new JarFile(zip64Jar)) {
|
||||
assertThat(Collections.list(zip64JarFile.entries())).hasSize(6);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedZip64JarCanBeRead() throws Exception {
|
||||
File outer = new File(this.tempDir, "outer.jar");
|
||||
try (JarOutputStream jarOutput = new JarOutputStream(new FileOutputStream(outer))) {
|
||||
JarEntry nestedEntry = new JarEntry("nested-zip64.jar");
|
||||
byte[] contents = zip64Jar();
|
||||
nestedEntry.setSize(contents.length);
|
||||
nestedEntry.setCompressedSize(contents.length);
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(contents);
|
||||
nestedEntry.setCrc(crc32.getValue());
|
||||
nestedEntry.setMethod(ZipEntry.STORED);
|
||||
jarOutput.putNextEntry(nestedEntry);
|
||||
jarOutput.write(contents);
|
||||
jarOutput.closeEntry();
|
||||
}
|
||||
try (JarFile outerJarFile = new JarFile(outer)) {
|
||||
try (JarFile nestedZip64JarFile = outerJarFile
|
||||
.getNestedJarFile(outerJarFile.getJarEntry("nested-zip64.jar"))) {
|
||||
List<JarEntry> entries = Collections.list(nestedZip64JarFile.entries());
|
||||
assertThat(entries).hasSize(65537);
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
JarEntry entry = entries.get(i);
|
||||
InputStream entryInput = nestedZip64JarFile.getInputStream(entry);
|
||||
assertThat(entryInput).hasContent("Entry " + (i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] zip64Jar() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
JarOutputStream jarOutput = new JarOutputStream(bytes);
|
||||
for (int i = 0; i < 65537; i++) {
|
||||
jarOutput.putNextEntry(new JarEntry(i + ".dat"));
|
||||
jarOutput.write(("Entry " + (i + 1)).getBytes(StandardCharsets.UTF_8));
|
||||
jarOutput.closeEntry();
|
||||
}
|
||||
jarOutput.close();
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarFileEntryWithEpochTimeOfZeroShouldNotFail() throws Exception {
|
||||
File file = createJarFileWithEpochTimeOfZero();
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
Enumeration<java.util.jar.JarEntry> entries = jar.entries();
|
||||
JarEntry entry = entries.nextElement();
|
||||
assertThat(entry.getLastModifiedTime().toInstant()).isEqualTo(Instant.EPOCH);
|
||||
assertThat(entry.getName()).isEqualTo("1.dat");
|
||||
}
|
||||
}
|
||||
|
||||
private File createJarFileWithEpochTimeOfZero() throws Exception {
|
||||
File jarFile = new File(this.tempDir, "temp.jar");
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(jarFile);
|
||||
String comment = "outer";
|
||||
try (JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream)) {
|
||||
jarOutputStream.setComment(comment);
|
||||
JarEntry entry = new JarEntry("1.dat");
|
||||
entry.setLastModifiedTime(FileTime.from(Instant.EPOCH));
|
||||
jarOutputStream.putNextEntry(entry);
|
||||
jarOutputStream.write(new byte[] { (byte) 1 });
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
byte[] data = Files.readAllBytes(jarFile.toPath());
|
||||
int headerPosition = data.length - ZipFile.ENDHDR - comment.getBytes().length;
|
||||
int centralHeaderPosition = (int) Bytes.littleEndianValue(data, headerPosition + ZipFile.ENDOFF, 1);
|
||||
int localHeaderPosition = (int) Bytes.littleEndianValue(data, centralHeaderPosition + ZipFile.CENOFF, 1);
|
||||
writeTimeBlock(data, centralHeaderPosition + ZipFile.CENTIM, 0);
|
||||
writeTimeBlock(data, localHeaderPosition + ZipFile.LOCTIM, 0);
|
||||
|
||||
File jar = new File(this.tempDir, "zerotimed.jar");
|
||||
Files.write(jar.toPath(), data);
|
||||
return jar;
|
||||
}
|
||||
|
||||
private static void writeTimeBlock(byte[] data, int pos, int value) {
|
||||
data[pos] = (byte) (value & 0xff);
|
||||
data[pos + 1] = (byte) ((value >> 8) & 0xff);
|
||||
data[pos + 2] = (byte) ((value >> 16) & 0xff);
|
||||
data[pos + 3] = (byte) ((value >> 24) & 0xff);
|
||||
}
|
||||
|
||||
@Test
|
||||
void iterator() {
|
||||
Iterator<JarEntry> iterator = this.jarFile.iterator();
|
||||
List<String> names = new ArrayList<>();
|
||||
while (iterator.hasNext()) {
|
||||
names.add(iterator.next().getName());
|
||||
}
|
||||
assertThat(names).hasSize(12).contains("1.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorWhenClosed() throws IOException {
|
||||
this.jarFile.close();
|
||||
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.iterator());
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorWhenClosedLater() throws IOException {
|
||||
Iterator<JarEntry> iterator = this.jarFile.iterator();
|
||||
iterator.next();
|
||||
this.jarFile.close();
|
||||
assertThatZipFileClosedIsThrownBy(() -> iterator.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stream() {
|
||||
Stream<String> stream = this.jarFile.stream().map(JarEntry::getName);
|
||||
assertThat(stream).hasSize(12).contains("1.dat");
|
||||
|
||||
}
|
||||
|
||||
private void assertThatZipFileClosedIsThrownBy(ThrowingCallable throwingCallable) {
|
||||
assertThatIllegalStateException().isThrownBy(throwingCallable).withMessage("zip file closed");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.Permission;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.jar.JarFileWrapperTests.SpyJarFile.Call;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarFileWrapper}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class JarFileWrapperTests {
|
||||
|
||||
private SpyJarFile parent;
|
||||
|
||||
private JarFileWrapper wrapper;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File temp) throws Exception {
|
||||
this.parent = new SpyJarFile(createTempJar(temp));
|
||||
this.wrapper = new JarFileWrapper(this.parent);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() throws Exception {
|
||||
this.parent.close();
|
||||
}
|
||||
|
||||
private File createTempJar(File temp) throws IOException {
|
||||
File file = new File(temp, "temp.jar");
|
||||
new JarOutputStream(new FileOutputStream(file)).close();
|
||||
return file;
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUrlDelegatesToParent() throws MalformedURLException {
|
||||
this.wrapper.getUrl();
|
||||
this.parent.verify(Call.GET_URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTypeDelegatesToParent() {
|
||||
this.wrapper.getType();
|
||||
this.parent.verify(Call.GET_TYPE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPermissionDelegatesToParent() {
|
||||
this.wrapper.getPermission();
|
||||
this.parent.verify(Call.GET_PERMISSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestDelegatesToParent() throws IOException {
|
||||
this.wrapper.getManifest();
|
||||
this.parent.verify(Call.GET_MANIFEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void entriesDelegatesToParent() {
|
||||
this.wrapper.entries();
|
||||
this.parent.verify(Call.ENTRIES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getJarEntryDelegatesToParent() {
|
||||
this.wrapper.getJarEntry("test");
|
||||
this.parent.verify(Call.GET_JAR_ENTRY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryDelegatesToParent() {
|
||||
this.wrapper.getEntry("test");
|
||||
this.parent.verify(Call.GET_ENTRY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamDelegatesToParent() throws IOException {
|
||||
this.wrapper.getInputStream();
|
||||
this.parent.verify(Call.GET_INPUT_STREAM);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryInputStreamDelegatesToParent() throws IOException {
|
||||
ZipEntry entry = new ZipEntry("test");
|
||||
this.wrapper.getInputStream(entry);
|
||||
this.parent.verify(Call.GET_ENTRY_INPUT_STREAM);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCommentDelegatesToParent() {
|
||||
this.wrapper.getComment();
|
||||
this.parent.verify(Call.GET_COMMENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeDelegatesToParent() {
|
||||
this.wrapper.size();
|
||||
this.parent.verify(Call.SIZE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringDelegatesToParent() {
|
||||
assertThat(this.wrapper.toString()).endsWith("temp.jar");
|
||||
}
|
||||
|
||||
@Test // gh-22991
|
||||
void wrapperMustNotImplementClose() {
|
||||
// If the wrapper overrides close then on Java 11 a FinalizableResource
|
||||
// instance will be used to perform cleanup. This can result in a lot
|
||||
// of additional memory being used since cleanup only occurs when the
|
||||
// finalizer thread runs. See gh-22991
|
||||
assertThatExceptionOfType(NoSuchMethodException.class)
|
||||
.isThrownBy(() -> JarFileWrapper.class.getDeclaredMethod("close"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamDelegatesToParent() {
|
||||
this.wrapper.stream();
|
||||
this.parent.verify(Call.STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JarFile} that we can spy (even on Java 11+)
|
||||
*/
|
||||
static class SpyJarFile extends JarFile {
|
||||
|
||||
private final Set<Call> calls = EnumSet.noneOf(Call.class);
|
||||
|
||||
SpyJarFile(File file) throws IOException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
Permission getPermission() {
|
||||
mark(Call.GET_PERMISSION);
|
||||
return super.getPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
mark(Call.GET_MANIFEST);
|
||||
return super.getManifest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<java.util.jar.JarEntry> entries() {
|
||||
mark(Call.ENTRIES);
|
||||
return super.entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<java.util.jar.JarEntry> stream() {
|
||||
mark(Call.STREAM);
|
||||
return super.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JarEntry getJarEntry(String name) {
|
||||
mark(Call.GET_JAR_ENTRY);
|
||||
return super.getJarEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZipEntry getEntry(String name) {
|
||||
mark(Call.GET_ENTRY);
|
||||
return super.getEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream getInputStream() throws IOException {
|
||||
mark(Call.GET_INPUT_STREAM);
|
||||
return super.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream getInputStream(String name) throws IOException {
|
||||
mark(Call.GET_ENTRY_INPUT_STREAM);
|
||||
return super.getInputStream(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
mark(Call.GET_COMMENT);
|
||||
return super.getComment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
mark(Call.SIZE);
|
||||
return super.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
mark(Call.GET_URL);
|
||||
return super.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
JarFileType getType() {
|
||||
mark(Call.GET_TYPE);
|
||||
return super.getType();
|
||||
}
|
||||
|
||||
private void mark(Call call) {
|
||||
this.calls.add(call);
|
||||
}
|
||||
|
||||
void verify(Call call) {
|
||||
assertThat(call).matches(this.calls::contains);
|
||||
}
|
||||
|
||||
enum Call {
|
||||
|
||||
GET_URL,
|
||||
|
||||
GET_TYPE,
|
||||
|
||||
GET_PERMISSION,
|
||||
|
||||
GET_MANIFEST,
|
||||
|
||||
ENTRIES,
|
||||
|
||||
GET_JAR_ENTRY,
|
||||
|
||||
GET_ENTRY,
|
||||
|
||||
GET_INPUT_STREAM,
|
||||
|
||||
GET_ENTRY_INPUT_STREAM,
|
||||
|
||||
GET_COMMENT,
|
||||
|
||||
SIZE,
|
||||
|
||||
STREAM
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.TestJarCreator;
|
||||
import org.springframework.boot.loader.jar.JarURLConnection.JarEntryName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarURLConnection}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @author Rostyslav Dudka
|
||||
*/
|
||||
class JarURLConnectionTests {
|
||||
|
||||
private File rootJarFile;
|
||||
|
||||
private JarFile jarFile;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File tempDir) throws Exception {
|
||||
this.rootJarFile = new File(tempDir, "root.jar");
|
||||
TestJarCreator.createTestJar(this.rootJarFile);
|
||||
this.jarFile = new JarFile(this.rootJarFile);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
this.jarFile.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToRootUsingAbsoluteUrl() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/");
|
||||
Object content = JarURLConnection.get(url, this.jarFile).getContent();
|
||||
assertThat(JarFileWrapper.unwrap((java.util.jar.JarFile) content)).isSameAs(this.jarFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToRootUsingRelativeUrl() throws Exception {
|
||||
URL url = new URL("jar:file:" + getRelativePath() + "!/");
|
||||
Object content = JarURLConnection.get(url, this.jarFile).getContent();
|
||||
assertThat(JarFileWrapper.unwrap((java.util.jar.JarFile) content)).isSameAs(this.jarFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryUsingAbsoluteUrl() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/1.dat");
|
||||
try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryUsingRelativeUrl() throws Exception {
|
||||
URL url = new URL("jar:file:" + getRelativePath() + "!/1.dat");
|
||||
try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryUsingAbsoluteUrlWithFileColonSlashSlashPrefix() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/1.dat");
|
||||
try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryUsingAbsoluteUrlForNestedEntry() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/nested.jar!/3.dat");
|
||||
JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 3 });
|
||||
}
|
||||
connection.getJarFile().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryUsingRelativeUrlForNestedEntry() throws Exception {
|
||||
URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat");
|
||||
JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 3 });
|
||||
}
|
||||
connection.getJarFile().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryUsingAbsoluteUrlForEntryFromNestedJarFile() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/nested.jar!/3.dat");
|
||||
try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 3 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryUsingRelativeUrlForEntryFromNestedJarFile() throws Exception {
|
||||
URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat");
|
||||
try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 3 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryInNestedJarFromUrlThatUsesExistingUrlAsContext() throws Exception {
|
||||
URL url = new URL(new URL("jar", null, -1, this.rootJarFile.toURI().toURL() + "!/nested.jar!/", new Handler()),
|
||||
"/3.dat");
|
||||
try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 3 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryWithSpaceNestedEntry() throws Exception {
|
||||
URL url = new URL("jar:file:" + getRelativePath() + "!/space nested.jar!/3.dat");
|
||||
JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 3 });
|
||||
}
|
||||
connection.getJarFile().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryWithEncodedSpaceNestedEntry() throws Exception {
|
||||
URL url = new URL("jar:file:" + getRelativePath() + "!/space%20nested.jar!/3.dat");
|
||||
JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
assertThat(input).hasBinaryContent(new byte[] { 3 });
|
||||
}
|
||||
connection.getJarFile().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionToEntryUsingWrongAbsoluteUrlForEntryFromNestedJarFile() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/w.jar!/3.dat");
|
||||
try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
assertThatExceptionOfType(FileNotFoundException.class)
|
||||
.isThrownBy(JarURLConnection.get(url, nested)::getInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthReturnsLengthOfUnderlyingEntry() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/nested.jar!/3.dat");
|
||||
try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
JarURLConnection connection = JarURLConnection.get(url, nested);
|
||||
assertThat(connection.getContentLength()).isOne();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthLongReturnsLengthOfUnderlyingEntry() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/nested.jar!/3.dat");
|
||||
try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
|
||||
JarURLConnection connection = JarURLConnection.get(url, nested);
|
||||
assertThat(connection.getContentLengthLong()).isOne();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLastModifiedReturnsLastModifiedTimeOfJarEntry() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/1.dat");
|
||||
JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
|
||||
assertThat(connection.getLastModified()).isEqualTo(connection.getJarEntry().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void entriesCanBeStreamedFromJarFileOfConnection() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/");
|
||||
JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
|
||||
List<String> entryNames = connection.getJarFile().stream().map(JarEntry::getName).toList();
|
||||
assertThat(entryNames).hasSize(12);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarEntryBasicName() {
|
||||
assertThat(new JarEntryName(new StringSequence("a/b/C.class"))).hasToString("a/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarEntryNameWithSingleByteEncodedCharacters() {
|
||||
assertThat(new JarEntryName(new StringSequence("%61/%62/%43.class"))).hasToString("a/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarEntryNameWithDoubleByteEncodedCharacters() {
|
||||
assertThat(new JarEntryName(new StringSequence("%c3%a1/b/C.class"))).hasToString("\u00e1/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarEntryNameWithMixtureOfEncodedAndUnencodedDoubleByteCharacters() {
|
||||
assertThat(new JarEntryName(new StringSequence("%c3%a1/b/\u00c7.class"))).hasToString("\u00e1/b/\u00c7.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void openConnectionCanBeClosedWithoutClosingSourceJar() throws Exception {
|
||||
URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/");
|
||||
JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
|
||||
java.util.jar.JarFile connectionJarFile = connection.getJarFile();
|
||||
connectionJarFile.close();
|
||||
assertThat(this.jarFile.isClosed()).isFalse();
|
||||
}
|
||||
|
||||
private String getRelativePath() {
|
||||
return this.rootJarFile.getPath().replace('\\', '/');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.Extension;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* JUnit 5 {@link Extension} for tests that interact with Spring Boot's {@link Handler}
|
||||
* for {@code jar:} URLs. Ensures that the handler is registered prior to test execution
|
||||
* and cleans up the handler's root file cache afterwards.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class JarUrlProtocolHandler implements BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
@Override
|
||||
public void beforeEach(ExtensionContext context) throws Exception {
|
||||
JarFile.registerUrlProtocolHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void afterEach(ExtensionContext context) throws Exception {
|
||||
Map<File, JarFile> rootFileCache = ((SoftReference<Map<File, JarFile>>) ReflectionTestUtils
|
||||
.getField(Handler.class, "rootFileCache")).get();
|
||||
if (rootFileCache != null) {
|
||||
for (JarFile rootJarFile : rootFileCache.values()) {
|
||||
rootJarFile.close();
|
||||
}
|
||||
rootFileCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.util.jar.Attributes.Name;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ManifestInfo}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ManifestInfoTests {
|
||||
|
||||
@Test
|
||||
void noneReturnsNoDetails() {
|
||||
assertThat(ManifestInfo.NONE.getManifest()).isNull();
|
||||
assertThat(ManifestInfo.NONE.isMultiRelease()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestReturnsManifest() {
|
||||
Manifest manifest = new Manifest();
|
||||
ManifestInfo info = new ManifestInfo(manifest);
|
||||
assertThat(info.getManifest()).isSameAs(manifest);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMultiReleaseWhenHasMultiReleaseAttributeReturnsTrue() {
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().put(new Name("Multi-Release"), "true");
|
||||
ManifestInfo info = new ManifestInfo(manifest);
|
||||
assertThat(info.isMultiRelease()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMultiReleaseWhenHasNoMultiReleaseAttributeReturnsFalse() {
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().put(new Name("Random-Release"), "true");
|
||||
ManifestInfo info = new ManifestInfo(manifest);
|
||||
assertThat(info.isMultiRelease()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.zip.ZipContent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link MetaInfVersionsInfo}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class MetaInfVersionsInfoTests {
|
||||
|
||||
@Test
|
||||
void getParsesVersionsAndEntries() {
|
||||
List<ZipContent.Entry> entries = new ArrayList<>();
|
||||
entries.add(mockEntry("META-INF/"));
|
||||
entries.add(mockEntry("META-INF/MANIFEST.MF"));
|
||||
entries.add(mockEntry("META-INF/versions/"));
|
||||
entries.add(mockEntry("META-INF/versions/9/"));
|
||||
entries.add(mockEntry("META-INF/versions/9/Foo.class"));
|
||||
entries.add(mockEntry("META-INF/versions/11/"));
|
||||
entries.add(mockEntry("META-INF/versions/11/Foo.class"));
|
||||
entries.add(mockEntry("META-INF/versions/10/"));
|
||||
entries.add(mockEntry("META-INF/versions/10/Foo.class"));
|
||||
MetaInfVersionsInfo info = MetaInfVersionsInfo.get(entries.size(), entries::get);
|
||||
assertThat(info.versions()).containsExactly(9, 10, 11);
|
||||
assertThat(info.directories()).containsExactly("META-INF/versions/9/", "META-INF/versions/10/",
|
||||
"META-INF/versions/11/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasBadEntryParsesGoodVersionsAndEntries() {
|
||||
List<ZipContent.Entry> entries = new ArrayList<>();
|
||||
entries.add(mockEntry("META-INF/versions/9/Foo.class"));
|
||||
entries.add(mockEntry("META-INF/versions/0x11/Foo.class"));
|
||||
MetaInfVersionsInfo info = MetaInfVersionsInfo.get(entries.size(), entries::get);
|
||||
assertThat(info.versions()).containsExactly(9);
|
||||
assertThat(info.directories()).containsExactly("META-INF/versions/9/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasNoEntriesReturnsNone() {
|
||||
List<ZipContent.Entry> entries = new ArrayList<>();
|
||||
MetaInfVersionsInfo info = MetaInfVersionsInfo.get(entries.size(), entries::get);
|
||||
assertThat(info.versions()).isEmpty();
|
||||
assertThat(info.directories()).isEmpty();
|
||||
assertThat(info).isSameAs(MetaInfVersionsInfo.NONE);
|
||||
}
|
||||
|
||||
private ZipContent.Entry mockEntry(String name) {
|
||||
ZipContent.Entry entry = mock(ZipContent.Entry.class);
|
||||
given(entry.getName()).willReturn(name);
|
||||
given(entry.hasNameStartingWith(any()))
|
||||
.willAnswer((invocation) -> name.startsWith(invocation.getArgument(0, CharSequence.class).toString()));
|
||||
given(entry.isDirectory()).willAnswer((invocation) -> name.endsWith("/"));
|
||||
return entry;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.assertj.core.extractor.Extractors;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.boot.loader.ref.Cleaner;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.boot.loader.zip.ZipContent;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.atMostOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link NestedJarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Martin Lau
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class NestedJarFileTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
private File file;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
this.file = new File(this.tempDir, "test.jar");
|
||||
TestJar.create(this.file);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOpensJar() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
try (JarFile jdkJar = new JarFile(this.file)) {
|
||||
assertThat(jar.size()).isEqualTo(jdkJar.size());
|
||||
assertThat(jar.getComment()).isEqualTo(jdkJar.getComment());
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
Enumeration<JarEntry> jdkEntries = jdkJar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
assertThat(entries.nextElement().getName()).isEqualTo(jdkEntries.nextElement().getName());
|
||||
}
|
||||
assertThat(jdkEntries.hasMoreElements()).isFalse();
|
||||
try (InputStream in = jar.getInputStream(jar.getEntry("1.dat"))) {
|
||||
assertThat(in.readAllBytes()).containsExactly(new byte[] { 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenNestedJarFileOpensJar() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file, "nested.jar")) {
|
||||
assertThat(jar.size()).isEqualTo(5);
|
||||
assertThat(jar.stream().map(JarEntry::getName)).containsExactly("META-INF/", "META-INF/MANIFEST.MF",
|
||||
"3.dat", "4.dat", "\u00E4.dat");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenNestedJarDirectoryOpensJar() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file, "d/")) {
|
||||
assertThat(jar.getName()).isEqualTo(this.file.getAbsolutePath() + "!/d/");
|
||||
assertThat(jar.size()).isEqualTo(3);
|
||||
assertThat(jar.stream().map(JarEntry::getName)).containsExactly("META-INF/", "META-INF/MANIFEST.MF",
|
||||
"9.dat");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenJarHasFrontMatterOpensJar() throws IOException {
|
||||
File file = new File(this.tempDir, "frontmatter.jar");
|
||||
InputStream sourceJarContent = new FileInputStream(this.file);
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
StreamUtils.copy("#/bin/bash", Charset.defaultCharset(), outputStream);
|
||||
FileCopyUtils.copy(sourceJarContent, outputStream);
|
||||
try (NestedJarFile jar = new NestedJarFile(file)) {
|
||||
assertThat(jar.size()).isEqualTo(12);
|
||||
}
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file, "nested.jar")) {
|
||||
assertThat(jar.size()).isEqualTo(5);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryReturnsEntry() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
JarEntry entry = jar.getEntry("1.dat");
|
||||
assertEntryOne(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenClosedThrowsException() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
jar.close();
|
||||
assertThatIllegalStateException().isThrownBy(() -> jar.getEntry("1.dat")).withMessage("Zip file closed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getJarEntryReturnsEntry() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
JarEntry entry = jar.getJarEntry("1.dat");
|
||||
assertEntryOne(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getJarEntryWhenClosedThrowsException() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
jar.close();
|
||||
assertThatIllegalStateException().isThrownBy(() -> jar.getJarEntry("1.dat")).withMessage("Zip file closed");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertEntryOne(JarEntry entry) {
|
||||
assertThat(entry.getName()).isEqualTo("1.dat");
|
||||
assertThat(entry.getRealName()).isEqualTo("1.dat");
|
||||
assertThat(entry.getSize()).isEqualTo(1);
|
||||
assertThat(entry.getCompressedSize()).isEqualTo(3);
|
||||
assertThat(entry.getCrc()).isEqualTo(2768625435L);
|
||||
assertThat(entry.getMethod()).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenMultiReleaseEntryReturnsEntry() throws IOException {
|
||||
File multiReleaseFile = new File(this.tempDir, "mutli.zip");
|
||||
try (ZipContent zip = ZipContent.open(this.file.toPath(), "multi-release.jar")) {
|
||||
try (InputStream in = zip.openRawZipData().asInputStream()) {
|
||||
try (FileOutputStream out = new FileOutputStream(multiReleaseFile)) {
|
||||
in.transferTo(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file, "multi-release.jar", JarFile.runtimeVersion())) {
|
||||
try (JarFile jdkJar = new JarFile(multiReleaseFile, true, ZipFile.OPEN_READ, JarFile.runtimeVersion())) {
|
||||
JarEntry entry = jar.getJarEntry("multi-release.dat");
|
||||
JarEntry jdkEntry = jdkJar.getJarEntry("multi-release.dat");
|
||||
assertThat(entry.getName()).isEqualTo(jdkEntry.getName());
|
||||
assertThat(entry.getRealName()).isEqualTo(jdkEntry.getRealName());
|
||||
try (InputStream inputStream = jdkJar.getInputStream(entry)) {
|
||||
assertThat(inputStream.available()).isOne();
|
||||
assertThat(inputStream.read()).isEqualTo(Runtime.version().feature());
|
||||
}
|
||||
try (InputStream inputStream = jar.getInputStream(entry)) {
|
||||
assertThat(inputStream.available()).isOne();
|
||||
assertThat(inputStream.read()).isEqualTo(Runtime.version().feature());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestReturnsManifest() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
Manifest manifest = jar.getManifest();
|
||||
assertThat(manifest).isNotNull();
|
||||
assertThat(manifest.getEntries()).isEmpty();
|
||||
assertThat(manifest.getMainAttributes().getValue("Manifest-Version")).isEqualTo("1.0");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCommentReturnsComment() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
assertThat(jar.getComment()).isEqualTo("outer");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCommentWhenClosedThrowsException() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
jar.close();
|
||||
assertThatIllegalStateException().isThrownBy(() -> jar.getComment()).withMessage("Zip file closed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNameReturnsName() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
assertThat(jar.getName()).isEqualTo(this.file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNameWhenNestedReturnsName() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file, "nested.jar")) {
|
||||
assertThat(jar.getName()).isEqualTo(this.file.getAbsolutePath() + "!/nested.jar");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeReturnsSize() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
assertThat(jar.size()).isEqualByComparingTo(12);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeWhenClosedThowsException() throws Exception {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
jar.close();
|
||||
assertThatIllegalStateException().isThrownBy(() -> jar.size()).withMessage("Zip file closed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryTime() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
try (JarFile jdkJar = new JarFile(this.file)) {
|
||||
assertThat(jar.getEntry("META-INF/MANIFEST.MF").getTime())
|
||||
.isEqualTo(jar.getEntry("META-INF/MANIFEST.MF").getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeTriggersCleanupOnlyOnce() throws IOException {
|
||||
Cleaner cleaner = mock(Cleaner.class);
|
||||
ArgumentCaptor<Runnable> action = ArgumentCaptor.forClass(Runnable.class);
|
||||
Cleanable cleanable = mock(Cleanable.class);
|
||||
given(cleaner.register(any(), action.capture())).willReturn(cleanable);
|
||||
NestedJarFile jar = new NestedJarFile(this.file, null, null, false, cleaner);
|
||||
jar.close();
|
||||
jar.close();
|
||||
then(cleanable).should(atMostOnce()).clean();
|
||||
action.getValue().run();
|
||||
}
|
||||
|
||||
@Test
|
||||
void cleanupFromReleasesResources() throws IOException {
|
||||
Cleaner cleaner = mock(Cleaner.class);
|
||||
ArgumentCaptor<Runnable> action = ArgumentCaptor.forClass(Runnable.class);
|
||||
Cleanable cleanable = mock(Cleanable.class);
|
||||
given(cleaner.register(any(), action.capture())).willReturn(cleanable);
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file, null, null, false, cleaner)) {
|
||||
Object channel = Extractors.byName("resources.zipContent.data.channel").apply(jar);
|
||||
assertThat(channel).extracting("referenceCount").isEqualTo(1);
|
||||
action.getValue().run();
|
||||
assertThat(channel).extracting("referenceCount").isEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamReturnsInputStream() throws IOException {
|
||||
try (NestedJarFile jarFile = new NestedJarFile(this.file)) {
|
||||
JarEntry entry = jarFile.getJarEntry("2.dat");
|
||||
try (InputStream in = jarFile.getInputStream(entry)) {
|
||||
assertThat(in).hasBinaryContent(new byte[] { 0x02 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamWhenIsDirectory() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
try (InputStream inputStream = jar.getInputStream(jar.getEntry("d/"))) {
|
||||
assertThat(inputStream).isNotNull();
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamWhenNameWithoutSlashAndIsDirectory() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file)) {
|
||||
try (InputStream inputStream = jar.getInputStream(jar.getEntry("d"))) {
|
||||
assertThat(inputStream).isNotNull();
|
||||
assertThat(inputStream.read()).isEqualTo(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifySignedJar() throws Exception {
|
||||
File signedJarFile = TestJar.getSigned();
|
||||
assertThat(signedJarFile).exists();
|
||||
try (JarFile expected = new JarFile(signedJarFile)) {
|
||||
try (NestedJarFile actual = new NestedJarFile(signedJarFile)) {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
Enumeration<JarEntry> actualEntries = actual.entries();
|
||||
while (actualEntries.hasMoreElements()) {
|
||||
JarEntry actualEntry = actualEntries.nextElement();
|
||||
JarEntry expectedEntry = expected.getJarEntry(actualEntry.getName());
|
||||
StreamUtils.drain(expected.getInputStream(expectedEntry));
|
||||
if (!actualEntry.getName().equals("META-INF/MANIFEST.MF")) {
|
||||
assertThat(actualEntry.getCertificates()).as(actualEntry.getName())
|
||||
.isEqualTo(expectedEntry.getCertificates());
|
||||
assertThat(actualEntry.getCodeSigners()).as(actualEntry.getName())
|
||||
.isEqualTo(expectedEntry.getCodeSigners());
|
||||
}
|
||||
}
|
||||
assertThat(stopWatch.getTotalTimeSeconds()).isLessThan(3.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeAllowsFileToBeDeleted() throws Exception {
|
||||
new NestedJarFile(this.file).close();
|
||||
assertThat(this.file.delete()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamStreamsEnties() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file, "multi-release.jar")) {
|
||||
assertThat(jar.stream().map((entry) -> entry.getName() + ":" + entry.getRealName())).containsExactly(
|
||||
"META-INF/:META-INF/", "META-INF/MANIFEST.MF:META-INF/MANIFEST.MF",
|
||||
"multi-release.dat:multi-release.dat",
|
||||
"META-INF/versions/17/multi-release.dat:META-INF/versions/17/multi-release.dat");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionedStreamStreamsEntries() throws IOException {
|
||||
try (NestedJarFile jar = new NestedJarFile(this.file, "multi-release.jar", Runtime.version())) {
|
||||
assertThat(jar.versionedStream().map((entry) -> entry.getName() + ":" + entry.getRealName()))
|
||||
.containsExactly("META-INF/:META-INF/", "META-INF/MANIFEST.MF:META-INF/MANIFEST.MF",
|
||||
"multi-release.dat:META-INF/versions/17/multi-release.dat");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.boot.loader.zip.ZipContent;
|
||||
import org.springframework.boot.loader.zip.ZipContent.Entry;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SecurityInfo}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class SecurityInfoTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@Test
|
||||
void getWhenNoSignatureFileReturnsNone() throws Exception {
|
||||
File file = new File(this.temp, "test.jar");
|
||||
TestJar.create(file);
|
||||
try (ZipContent content = ZipContent.open(file.toPath())) {
|
||||
SecurityInfo info = SecurityInfo.get(content);
|
||||
assertThat(info).isSameAs(SecurityInfo.NONE);
|
||||
for (int i = 0; i < content.size(); i++) {
|
||||
Entry entry = content.getEntry(i);
|
||||
assertThat(info.getCertificates(entry)).isNull();
|
||||
assertThat(info.getCodeSigners(entry)).isNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasSignatureFileButNoSecuityMaterialReturnsNone() throws Exception {
|
||||
File file = new File(this.temp, "test.jar");
|
||||
TestJar.create(file, false, true);
|
||||
try (ZipContent content = ZipContent.open(file.toPath())) {
|
||||
assertThat(content.hasJarSignatureFile()).isTrue();
|
||||
SecurityInfo info = SecurityInfo.get(content);
|
||||
assertThat(info).isSameAs(SecurityInfo.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenJarIsSigned() throws Exception {
|
||||
File file = TestJar.getSigned();
|
||||
try (ZipContent content = ZipContent.open(file.toPath())) {
|
||||
assertThat(content.hasJarSignatureFile()).isTrue();
|
||||
SecurityInfo info = SecurityInfo.get(content);
|
||||
for (int i = 0; i < content.size(); i++) {
|
||||
Entry entry = content.getEntry(i);
|
||||
if (entry.getName().endsWith(".class")) {
|
||||
assertThat(info.getCertificates(entry)).isNotNull();
|
||||
assertThat(info.getCodeSigners(entry)).isNotNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
/**
|
||||
* Tests for {@link StringSequence}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class StringSequenceTests {
|
||||
|
||||
@Test
|
||||
void createWhenSourceIsNullShouldThrowException() {
|
||||
assertThatNullPointerException().isThrownBy(() -> new StringSequence(null))
|
||||
.withMessage("Source must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithIndexWhenSourceIsNullShouldThrowException() {
|
||||
assertThatNullPointerException().isThrownBy(() -> new StringSequence(null, 0, 0))
|
||||
.withMessage("Source must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenStartIsLessThanZeroShouldThrowException() {
|
||||
assertThatExceptionOfType(StringIndexOutOfBoundsException.class)
|
||||
.isThrownBy(() -> new StringSequence("x", -1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenEndIsGreaterThanLengthShouldThrowException() {
|
||||
assertThatExceptionOfType(StringIndexOutOfBoundsException.class)
|
||||
.isThrownBy(() -> new StringSequence("x", 0, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromString() {
|
||||
assertThat(new StringSequence("test")).hasToString("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subSequenceWithJustStartShouldReturnSubSequence() {
|
||||
assertThat(new StringSequence("smiles").subSequence(1)).hasToString("miles");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subSequenceShouldReturnSubSequence() {
|
||||
assertThat(new StringSequence("hamburger").subSequence(4, 8)).hasToString("urge");
|
||||
assertThat(new StringSequence("smiles").subSequence(1, 5)).hasToString("mile");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subSequenceWhenCalledMultipleTimesShouldReturnSubSequence() {
|
||||
assertThat(new StringSequence("hamburger").subSequence(4, 8).subSequence(1, 3)).hasToString("rg");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subSequenceWhenEndPastExistingEndShouldThrowException() {
|
||||
StringSequence sequence = new StringSequence("abcde").subSequence(1, 4);
|
||||
assertThat(sequence).hasToString("bcd");
|
||||
assertThat(sequence.subSequence(2, 3)).hasToString("d");
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> sequence.subSequence(3, 4));
|
||||
}
|
||||
|
||||
@Test
|
||||
void subSequenceWhenStartPastExistingEndShouldThrowException() {
|
||||
StringSequence sequence = new StringSequence("abcde").subSequence(1, 4);
|
||||
assertThat(sequence).hasToString("bcd");
|
||||
assertThat(sequence.subSequence(2, 3)).hasToString("d");
|
||||
assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> sequence.subSequence(4, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWhenEmptyShouldReturnTrue() {
|
||||
assertThat(new StringSequence("").isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWhenNotEmptyShouldReturnFalse() {
|
||||
assertThat(new StringSequence("x").isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void lengthShouldReturnLength() {
|
||||
StringSequence sequence = new StringSequence("hamburger");
|
||||
assertThat(sequence).hasSize(9);
|
||||
assertThat(sequence.subSequence(4, 8)).hasSize(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void charAtShouldReturnChar() {
|
||||
StringSequence sequence = new StringSequence("hamburger");
|
||||
assertThat(sequence.charAt(0)).isEqualTo('h');
|
||||
assertThat(sequence.charAt(1)).isEqualTo('a');
|
||||
assertThat(sequence.subSequence(4, 8).charAt(0)).isEqualTo('u');
|
||||
assertThat(sequence.subSequence(4, 8).charAt(1)).isEqualTo('r');
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexOfCharShouldReturnIndexOf() {
|
||||
StringSequence sequence = new StringSequence("aabbaacc");
|
||||
assertThat(sequence.indexOf('a')).isZero();
|
||||
assertThat(sequence.indexOf('b')).isEqualTo(2);
|
||||
assertThat(sequence.subSequence(2).indexOf('a')).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexOfStringShouldReturnIndexOf() {
|
||||
StringSequence sequence = new StringSequence("aabbaacc");
|
||||
assertThat(sequence.indexOf('a')).isZero();
|
||||
assertThat(sequence.indexOf('b')).isEqualTo(2);
|
||||
assertThat(sequence.subSequence(2).indexOf('a')).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexOfStringFromIndexShouldReturnIndexOf() {
|
||||
StringSequence sequence = new StringSequence("aabbaacc");
|
||||
assertThat(sequence.indexOf("a", 2)).isEqualTo(4);
|
||||
assertThat(sequence.indexOf("b", 3)).isEqualTo(3);
|
||||
assertThat(sequence.subSequence(2).indexOf("a", 3)).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeShouldBeSameAsString() {
|
||||
assertThat(new StringSequence("hamburger")).hasSameHashCodeAs("hamburger");
|
||||
assertThat(new StringSequence("hamburger").subSequence(4, 8)).hasSameHashCodeAs("urge");
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWhenSameContentShouldMatch() {
|
||||
StringSequence a = new StringSequence("hamburger").subSequence(4, 8);
|
||||
StringSequence b = new StringSequence("urge");
|
||||
StringSequence c = new StringSequence("urgh");
|
||||
assertThat(a).isEqualTo(b).isNotEqualTo(c);
|
||||
}
|
||||
|
||||
@Test
|
||||
void notEqualsWhenSequencesOfDifferentLength() {
|
||||
StringSequence a = new StringSequence("abcd");
|
||||
StringSequence b = new StringSequence("ef");
|
||||
assertThat(a).isNotEqualTo(b);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithWhenExactMatch() {
|
||||
assertThat(new StringSequence("abc").startsWith("abc")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithWhenLongerAndStartsWith() {
|
||||
assertThat(new StringSequence("abcd").startsWith("abc")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithWhenLongerAndDoesNotStartWith() {
|
||||
assertThat(new StringSequence("abcd").startsWith("abx")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithWhenShorterAndDoesNotStartWith() {
|
||||
assertThat(new StringSequence("ab").startsWith("abc")).isFalse();
|
||||
assertThat(new StringSequence("ab").startsWith("c")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithOffsetWhenExactMatch() {
|
||||
assertThat(new StringSequence("xabc").startsWith("abc", 1)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithOffsetWhenLongerAndStartsWith() {
|
||||
assertThat(new StringSequence("xabcd").startsWith("abc", 1)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithOffsetWhenLongerAndDoesNotStartWith() {
|
||||
assertThat(new StringSequence("xabcd").startsWith("abx", 1)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithOffsetWhenShorterAndDoesNotStartWith() {
|
||||
assertThat(new StringSequence("xab").startsWith("abc", 1)).isFalse();
|
||||
assertThat(new StringSequence("xab").startsWith("c", 1)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithOnSubstringTailWhenMatch() {
|
||||
StringSequence subSequence = new StringSequence("xabc").subSequence(1);
|
||||
assertThat(subSequence.startsWith("abc")).isTrue();
|
||||
assertThat(subSequence.startsWith("abcd")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithOnSubstringMiddleWhenMatch() {
|
||||
StringSequence subSequence = new StringSequence("xabc").subSequence(1, 3);
|
||||
assertThat(subSequence.startsWith("ab")).isTrue();
|
||||
assertThat(subSequence.startsWith("abc")).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jarmode;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* {@link JarMode} for testing.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class TestJarMode implements JarMode {
|
||||
|
||||
@Override
|
||||
public boolean accepts(String mode) {
|
||||
return "test".equals(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String mode, String[] args) {
|
||||
System.out.println("running in " + mode + " jar mode " + Arrays.asList(args));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@@ -27,9 +27,7 @@ import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
@@ -39,7 +37,6 @@ import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
@@ -49,7 +46,7 @@ import org.springframework.util.FileCopyUtils;
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
public abstract class AbstractExecutableArchiveLauncherTests {
|
||||
abstract class AbstractExecutableArchiveLauncherTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
@@ -58,13 +55,11 @@ public abstract class AbstractExecutableArchiveLauncherTests {
|
||||
return createJarArchive(name, entryPrefix, false, Collections.emptyList());
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected File createJarArchive(String name, String entryPrefix, boolean indexed, List<String> extraLibs)
|
||||
throws IOException {
|
||||
return createJarArchive(name, null, entryPrefix, indexed, extraLibs);
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected File createJarArchive(String name, Manifest manifest, String entryPrefix, boolean indexed,
|
||||
List<String> extraLibs) throws IOException {
|
||||
File archive = new File(this.tempDir, name);
|
||||
@@ -129,14 +124,6 @@ public abstract class AbstractExecutableArchiveLauncherTests {
|
||||
return exploded;
|
||||
}
|
||||
|
||||
protected Set<URL> getUrls(List<Archive> archives) throws MalformedURLException {
|
||||
Set<URL> urls = new LinkedHashSet<>(archives.size());
|
||||
for (Archive archive : archives) {
|
||||
urls.add(archive.getUrl());
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
protected final URL toUrl(File file) {
|
||||
try {
|
||||
return file.toURI().toURL();
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.launch.Archive.Entry;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
/**
|
||||
* Tests for {@link Archive}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ArchiveTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWithOnlyIncludeFilterSearchesAllDirectories() throws Exception {
|
||||
Archive archive = mock(Archive.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
|
||||
Predicate<Entry> includeFilter = (entry) -> false;
|
||||
archive.getClassPathUrls(includeFilter);
|
||||
then(archive).should().getClassPathUrls(includeFilter, Archive.ALL_ENTRIES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isExplodedWhenHasRootDirectoryReturnsTrue() {
|
||||
Archive archive = mock(Archive.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
|
||||
given(archive.getRootDirectory()).willReturn(this.temp);
|
||||
assertThat(archive.isExploded()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isExplodedWhenHasNoRootDirectoryReturnsFalse() {
|
||||
Archive archive = mock(Archive.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
|
||||
given(archive.getRootDirectory()).willReturn(null);
|
||||
assertThat(archive.isExploded()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromProtectionDomainCreatesJarArchive() throws Exception {
|
||||
File jarFile = new File(this.temp, "test.jar");
|
||||
TestJar.create(jarFile);
|
||||
ProtectionDomain protectionDomain = mock(ProtectionDomain.class);
|
||||
CodeSource codeSource = mock(CodeSource.class);
|
||||
given(protectionDomain.getCodeSource()).willReturn(codeSource);
|
||||
given(codeSource.getLocation()).willReturn(jarFile.toURI().toURL());
|
||||
Archive archive = Archive.create(protectionDomain);
|
||||
assertThat(archive).isInstanceOf(JarFileArchive.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromProtectionDomainWhenNoLocationThrowsException() throws Exception {
|
||||
File jarFile = new File(this.temp, "test.jar");
|
||||
TestJar.create(jarFile);
|
||||
ProtectionDomain protectionDomain = mock(ProtectionDomain.class);
|
||||
assertThatIllegalStateException().isThrownBy(() -> Archive.create(protectionDomain))
|
||||
.withMessage("Unable to determine code source archive");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromFileWhenFileDoesNotExistThrowsException() {
|
||||
File target = new File(this.temp, "missing");
|
||||
assertThatIllegalStateException().isThrownBy(() -> Archive.create(target))
|
||||
.withMessageContaining("Unable to determine code source archive");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromFileWhenJarFileReturnsJarFileArchive() throws Exception {
|
||||
File target = new File(this.temp, "missing");
|
||||
TestJar.create(target);
|
||||
assertThat(Archive.create(target)).isInstanceOf(JarFileArchive.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromFileWhenDirectoryReturnsExplodedFileArchive() throws Exception {
|
||||
File target = this.temp;
|
||||
assertThat(Archive.create(target)).isInstanceOf(ExplodedArchive.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -28,7 +28,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassPathIndexFile}.
|
||||
@@ -41,24 +40,17 @@ class ClassPathIndexFileTests {
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@Test
|
||||
void loadIfPossibleWhenRootIsNotFileReturnsNull() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> ClassPathIndexFile.loadIfPossible(new URL("https://example.com/file"), "test.idx"))
|
||||
.withMessage("URL does not reference a file");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadIfPossibleWhenRootDoesNotExistReturnsNull() throws Exception {
|
||||
File root = new File(this.temp, "missing");
|
||||
assertThat(ClassPathIndexFile.loadIfPossible(root.toURI().toURL(), "test.idx")).isNull();
|
||||
assertThat(ClassPathIndexFile.loadIfPossible(root, "test.idx")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadIfPossibleWhenRootIsDirectoryThrowsException() throws Exception {
|
||||
File root = new File(this.temp, "directory");
|
||||
root.mkdirs();
|
||||
assertThat(ClassPathIndexFile.loadIfPossible(root.toURI().toURL(), "test.idx")).isNull();
|
||||
assertThat(ClassPathIndexFile.loadIfPossible(root, "test.idx")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,7 +89,7 @@ class ClassPathIndexFileTests {
|
||||
|
||||
private ClassPathIndexFile copyAndLoadTestIndexFile() throws IOException {
|
||||
copyTestIndexFile();
|
||||
ClassPathIndexFile indexFile = ClassPathIndexFile.loadIfPossible(this.temp.toURI().toURL(), "test.idx");
|
||||
ClassPathIndexFile indexFile = ClassPathIndexFile.loadIfPossible(this.temp, "test.idx");
|
||||
return indexFile;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.launch.Archive.Entry;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ExplodedArchive}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class ExplodedArchiveTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
private File rootDirectory;
|
||||
|
||||
private ExplodedArchive archive;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
createArchive();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
if (this.archive != null) {
|
||||
this.archive.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isExplodedReturnsTrue() {
|
||||
assertThat(this.archive.isExploded()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRootDirectoryReturnsRootDirectory() {
|
||||
assertThat(this.archive.getRootDirectory()).isEqualTo(this.rootDirectory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestReturnsManifest() throws Exception {
|
||||
assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenNoPredicartesReturnsUrls() throws Exception {
|
||||
Set<URL> urls = this.archive.getClassPathUrls(Archive.ALL_ENTRIES);
|
||||
URL[] expectedUrls = TestJar.expectedEntries().stream().map(this::toUrl).toArray(URL[]::new);
|
||||
assertThat(urls).containsExactlyInAnyOrder(expectedUrls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenHasIncludeFilterReturnsUrls() throws Exception {
|
||||
Set<URL> urls = this.archive.getClassPathUrls(this::entryNameIsNestedJar);
|
||||
assertThat(urls).containsOnly(toUrl("nested.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenHasIncludeFilterAndSpaceInRootNameReturnsUrls() throws Exception {
|
||||
createArchive("spaces in the name");
|
||||
Set<URL> urls = this.archive.getClassPathUrls(this::entryNameIsNestedJar);
|
||||
assertThat(urls).containsOnly(toUrl("nested.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenHasSearchFilterReturnsUrls() throws Exception {
|
||||
Set<URL> urls = this.archive.getClassPathUrls(Archive.ALL_ENTRIES, (entry) -> !entry.name().equals("d/"));
|
||||
assertThat(urls).contains(toUrl("nested.jar")).doesNotContain(toUrl("d/9.dat"));
|
||||
}
|
||||
|
||||
private void createArchive() throws Exception {
|
||||
createArchive(null);
|
||||
}
|
||||
|
||||
private void createArchive(String directoryName) throws Exception {
|
||||
File file = new File(this.tempDir, "test.jar");
|
||||
TestJar.create(file);
|
||||
this.rootDirectory = (StringUtils.hasText(directoryName) ? new File(this.tempDir, directoryName)
|
||||
: new File(this.tempDir, UUID.randomUUID().toString()));
|
||||
try (JarFile jarFile = new JarFile(file)) {
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
File destination = new File(this.rootDirectory.getAbsolutePath() + File.separator + entry.getName());
|
||||
destination.getParentFile().mkdirs();
|
||||
if (entry.isDirectory()) {
|
||||
destination.mkdir();
|
||||
}
|
||||
else {
|
||||
try (InputStream in = jarFile.getInputStream(entry);
|
||||
OutputStream out = new FileOutputStream(destination)) {
|
||||
in.transferTo(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.archive = new ExplodedArchive(this.rootDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private URL toUrl(String name) {
|
||||
return toUrl(new File(this.rootDirectory, name));
|
||||
}
|
||||
|
||||
private URL toUrl(File file) {
|
||||
try {
|
||||
return file.toURI().toURL();
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean entryNameIsNestedJar(Entry entry) {
|
||||
return entry.name().equals("nested.jar");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.launch.Archive.Entry;
|
||||
import org.springframework.boot.loader.net.protocol.jar.JarUrl;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarFileArchive}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Camille Vienot
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class JarFileArchiveTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
private File file;
|
||||
|
||||
private JarFileArchive archive;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
createTestJarArchive(false);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
this.archive.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isExplodedReturnsFalse() {
|
||||
assertThat(this.archive.isExploded()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRootDirectoryReturnsNull() {
|
||||
assertThat(this.archive.getRootDirectory()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestReturnsManifest() throws Exception {
|
||||
assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenNoPredicartesReturnsUrls() throws Exception {
|
||||
Set<URL> urls = this.archive.getClassPathUrls(Archive.ALL_ENTRIES);
|
||||
URL[] expected = TestJar.expectedEntries()
|
||||
.stream()
|
||||
.map((name) -> JarUrl.create(this.file, name))
|
||||
.toArray(URL[]::new);
|
||||
assertThat(urls).containsExactly(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenHasIncludeFilterReturnsUrls() throws Exception {
|
||||
Set<URL> urls = this.archive.getClassPathUrls(this::entryNameIsNestedJar);
|
||||
assertThat(urls).containsOnly(JarUrl.create(this.file, "nested.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenHasSearchFilterAllUrlsSinceSearchFilterIsNotUsed() throws Exception {
|
||||
Set<URL> urls = this.archive.getClassPathUrls(Archive.ALL_ENTRIES, (entry) -> false);
|
||||
URL[] expected = TestJar.expectedEntries()
|
||||
.stream()
|
||||
.map((name) -> JarUrl.create(this.file, name))
|
||||
.toArray(URL[]::new);
|
||||
assertThat(urls).containsExactly(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenHasUnpackCommentUnpacksAndReturnsUrls() throws Exception {
|
||||
createTestJarArchive(true);
|
||||
Set<URL> urls = this.archive.getClassPathUrls(this::entryNameIsNestedJar);
|
||||
assertThat(urls).hasSize(1);
|
||||
URL url = urls.iterator().next();
|
||||
assertThat(url).isNotEqualTo(JarUrl.create(this.file, "nested.jar"));
|
||||
assertThat(url.toString()).startsWith("jar:file:").endsWith("/nested.jar!/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenHasUnpackCommentUnpacksToUniqueLocationsPerArchive() throws Exception {
|
||||
createTestJarArchive(true);
|
||||
URL firstNestedUrl = this.archive.getClassPathUrls(this::entryNameIsNestedJar).iterator().next();
|
||||
createTestJarArchive(true);
|
||||
URL secondNestedUrl = this.archive.getClassPathUrls(this::entryNameIsNestedJar).iterator().next();
|
||||
assertThat(secondNestedUrl).isNotEqualTo(firstNestedUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenHasUnpackCommentUnpacksAndShareSameParent() throws Exception {
|
||||
createTestJarArchive(true);
|
||||
URL nestedUrl = this.archive.getClassPathUrls(this::entryNameIsNestedJar).iterator().next();
|
||||
URL anotherNestedUrl = this.archive.getClassPathUrls((entry) -> entry.name().equals("another-nested.jar"))
|
||||
.iterator()
|
||||
.next();
|
||||
assertThat(nestedUrl.toString())
|
||||
.isEqualTo(anotherNestedUrl.toString().replace("another-nested.jar", "nested.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassPathUrlsWhenZip64ListsAllEntries() throws Exception {
|
||||
File file = new File(this.tempDir, "test.jar");
|
||||
FileCopyUtils.copy(writeZip64Jar(), file);
|
||||
try (Archive jarArchive = new JarFileArchive(file)) {
|
||||
Set<URL> urls = jarArchive.getClassPathUrls(Archive.ALL_ENTRIES);
|
||||
assertThat(urls).hasSize(65537);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] writeZip64Jar() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (JarOutputStream jarOutput = new JarOutputStream(bytes)) {
|
||||
for (int i = 0; i < 65537; i++) {
|
||||
jarOutput.putNextEntry(new JarEntry(i + ".dat"));
|
||||
jarOutput.closeEntry();
|
||||
}
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
private void createTestJarArchive(boolean unpackNested) throws Exception {
|
||||
if (this.archive != null) {
|
||||
this.archive.close();
|
||||
}
|
||||
this.file = new File(this.tempDir, "root.jar");
|
||||
TestJar.create(this.file, unpackNested);
|
||||
this.archive = new JarFileArchive(this.file);
|
||||
}
|
||||
|
||||
private boolean entryNameIsNestedJar(Entry entry) {
|
||||
return entry.name().equals("nested.jar");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -23,17 +23,16 @@ import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Attributes.Name;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.ExplodedArchive;
|
||||
import org.springframework.boot.loader.archive.JarFileArchive;
|
||||
import org.springframework.boot.loader.net.protocol.jar.JarUrl;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.test.tools.SourceFile;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
@@ -47,19 +46,17 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
||||
|
||||
@Test
|
||||
void explodedJarHasOnlyBootInfClassesAndContentsOfBootInfLibOnClasspath() throws Exception {
|
||||
File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF"));
|
||||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(getUrls(archives)).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot));
|
||||
for (Archive archive : archives) {
|
||||
archive.close();
|
||||
}
|
||||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot));
|
||||
Set<URL> urls = launcher.getClassPathUrls();
|
||||
assertThat(urls).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -67,41 +64,33 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
||||
File jarRoot = createJarArchive("archive.jar", "BOOT-INF");
|
||||
try (JarFileArchive archive = new JarFileArchive(jarRoot)) {
|
||||
JarLauncher launcher = new JarLauncher(archive);
|
||||
List<Archive> classPathArchives = new ArrayList<>();
|
||||
launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add);
|
||||
assertThat(classPathArchives).hasSize(4);
|
||||
assertThat(getUrls(classPathArchives)).containsOnly(
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/bar.jar!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/baz.jar!/"));
|
||||
for (Archive classPathArchive : classPathArchives) {
|
||||
classPathArchive.close();
|
||||
}
|
||||
Set<URL> urls = launcher.getClassPathUrls();
|
||||
List<URL> expectedUrls = new ArrayList<>();
|
||||
expectedUrls.add(JarUrl.create(jarRoot, "BOOT-INF/classes/"));
|
||||
expectedUrls.add(JarUrl.create(jarRoot, "BOOT-INF/lib/foo.jar"));
|
||||
expectedUrls.add(JarUrl.create(jarRoot, "BOOT-INF/lib/bar.jar"));
|
||||
expectedUrls.add(JarUrl.create(jarRoot, "BOOT-INF/lib/baz.jar"));
|
||||
assertThat(urls).containsOnlyOnceElementsOf(expectedUrls);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void explodedJarShouldPreserveClasspathOrderWhenIndexPresent() throws Exception {
|
||||
File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF", true, Collections.emptyList()));
|
||||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
Iterator<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
URL[] urls = classLoader.getURLs();
|
||||
assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot));
|
||||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot));
|
||||
URLClassLoader classLoader = createClassLoader(launcher);
|
||||
assertThat(classLoader.getURLs()).containsExactly(getExpectedFileUrls(explodedRoot));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarFilesPresentInBootInfLibsAndNotInClasspathIndexShouldBeAddedAfterBootInfClasses() throws Exception {
|
||||
ArrayList<String> extraLibs = new ArrayList<>(Arrays.asList("extra-1.jar", "extra-2.jar"));
|
||||
File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF", true, extraLibs));
|
||||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
Iterator<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
URL[] urls = classLoader.getURLs();
|
||||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot));
|
||||
URLClassLoader classLoader = createClassLoader(launcher);
|
||||
List<File> expectedFiles = getExpectedFilesWithExtraLibs(explodedRoot);
|
||||
URL[] expectedFileUrls = expectedFiles.stream().map(this::toUrl).toArray(URL[]::new);
|
||||
assertThat(urls).containsExactly(expectedFileUrls);
|
||||
assertThat(classLoader.getURLs()).containsExactly(expectedFileUrls);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -119,19 +108,22 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
||||
target.getParentFile().mkdirs();
|
||||
FileCopyUtils.copy(compiled.getClassLoader().getResourceAsStream("explodedsample/ExampleClass.class"),
|
||||
new FileOutputStream(target));
|
||||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
Iterator<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot));
|
||||
URLClassLoader classLoader = createClassLoader(launcher);
|
||||
Class<?> loaded = classLoader.loadClass("explodedsample.ExampleClass");
|
||||
assertThat(loaded.getPackage().getImplementationTitle()).isEqualTo("test");
|
||||
}));
|
||||
}
|
||||
|
||||
protected final URL[] getExpectedFileUrls(File explodedRoot) {
|
||||
private URLClassLoader createClassLoader(JarLauncher launcher) throws Exception {
|
||||
return (URLClassLoader) launcher.createClassLoader(launcher.getClassPathUrls());
|
||||
}
|
||||
|
||||
private URL[] getExpectedFileUrls(File explodedRoot) {
|
||||
return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new);
|
||||
}
|
||||
|
||||
protected final List<File> getExpectedFiles(File parent) {
|
||||
private List<File> getExpectedFiles(File parent) {
|
||||
List<File> expected = new ArrayList<>();
|
||||
expected.add(new File(parent, "BOOT-INF/classes"));
|
||||
expected.add(new File(parent, "BOOT-INF/lib/foo.jar"));
|
||||
@@ -140,7 +132,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
||||
return expected;
|
||||
}
|
||||
|
||||
protected final List<File> getExpectedFilesWithExtraLibs(File parent) {
|
||||
private List<File> getExpectedFilesWithExtraLibs(File parent) {
|
||||
List<File> expected = new ArrayList<>();
|
||||
expected.add(new File(parent, "BOOT-INF/classes"));
|
||||
expected.add(new File(parent, "BOOT-INF/lib/extra-1.jar"));
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.jarmode.JarMode;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link LaunchedClassLoader}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class LaunchedClassLoaderTests {
|
||||
|
||||
@Test
|
||||
void loadClassWhenJarModeClassLoadsInLaunchedClassLoader() throws Exception {
|
||||
try (LaunchedClassLoader classLoader = new LaunchedClassLoader(false, new URL[] {},
|
||||
getClass().getClassLoader())) {
|
||||
Class<?> jarModeClass = classLoader.loadClass(JarMode.class.getName());
|
||||
Class<?> jarModeRunnerClass = classLoader.loadClass(JarModeRunner.class.getName());
|
||||
assertThat(jarModeClass.getClassLoader()).isSameAs(classLoader);
|
||||
assertThat(jarModeRunnerClass.getClassLoader()).isSameAs(classLoader);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,54 +14,64 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jarmode;
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.loader.Launcher;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.boot.testsupport.system.CapturedOutput;
|
||||
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Launcher} with jar mode support.
|
||||
* Tests for {@link Launcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class LauncherJarModeTests {
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class LauncherTests {
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
System.setProperty(JarModeLauncher.DISABLE_SYSTEM_EXIT, "true");
|
||||
}
|
||||
/**
|
||||
* Jar Mode tests.
|
||||
*/
|
||||
@Nested
|
||||
class JarMode {
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
System.clearProperty("jarmode");
|
||||
System.clearProperty(JarModeLauncher.DISABLE_SYSTEM_EXIT);
|
||||
}
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
System.setProperty(JarModeRunner.DISABLE_SYSTEM_EXIT, "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchWhenJarModePropertyIsSetLaunchesJarMode(CapturedOutput out) throws Exception {
|
||||
System.setProperty("jarmode", "test");
|
||||
new TestLauncher().launch(new String[] { "boot" });
|
||||
assertThat(out).contains("running in test jar mode [boot]");
|
||||
}
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
System.clearProperty("jarmode");
|
||||
System.clearProperty(JarModeRunner.DISABLE_SYSTEM_EXIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchWhenJarModePropertyIsSetLaunchesJarMode(CapturedOutput out) throws Exception {
|
||||
System.setProperty("jarmode", "test");
|
||||
new TestLauncher().launch(new String[] { "boot" });
|
||||
assertThat(out).contains("running in test jar mode [boot]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchWhenJarModePropertyIsNotAcceptedThrowsException(CapturedOutput out) throws Exception {
|
||||
System.setProperty("jarmode", "idontexist");
|
||||
new TestLauncher().launch(new String[] { "boot" });
|
||||
assertThat(out).contains("Unsupported jarmode 'idontexist'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchWhenJarModePropertyIsNotAcceptedThrowsException(CapturedOutput out) throws Exception {
|
||||
System.setProperty("jarmode", "idontexist");
|
||||
new TestLauncher().launch(new String[] { "boot" });
|
||||
assertThat(out).contains("Unsupported jarmode 'idontexist'");
|
||||
}
|
||||
|
||||
private static class TestLauncher extends Launcher {
|
||||
@@ -72,8 +82,13 @@ class LauncherJarModeTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
|
||||
return Collections.emptyIterator();
|
||||
protected Archive getArchive() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<URL> getClassPathUrls() throws Exception {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -14,20 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
@@ -39,11 +36,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.ExplodedArchive;
|
||||
import org.springframework.boot.loader.archive.JarFileArchive;
|
||||
import org.springframework.boot.loader.jar.Handler;
|
||||
import org.springframework.boot.loader.jar.JarFile;
|
||||
import org.springframework.boot.loader.net.protocol.jar.JarUrl;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.boot.testsupport.system.CapturedOutput;
|
||||
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
@@ -51,7 +46,7 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
/**
|
||||
@@ -61,6 +56,7 @@ import static org.hamcrest.Matchers.containsString;
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class PropertiesLauncherTests {
|
||||
|
||||
@TempDir
|
||||
@@ -73,9 +69,8 @@ class PropertiesLauncherTests {
|
||||
private CapturedOutput output;
|
||||
|
||||
@BeforeEach
|
||||
void setup(CapturedOutput capturedOutput) throws Exception {
|
||||
void setup(CapturedOutput capturedOutput) {
|
||||
this.contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
clearHandlerCache();
|
||||
System.setProperty("loader.home", new File("src/test/resources").getAbsolutePath());
|
||||
this.output = capturedOutput;
|
||||
}
|
||||
@@ -90,26 +85,13 @@ class PropertiesLauncherTests {
|
||||
System.clearProperty("loader.config.location");
|
||||
System.clearProperty("loader.system");
|
||||
System.clearProperty("loader.classLoader");
|
||||
clearHandlerCache();
|
||||
if (this.launcher != null) {
|
||||
this.launcher.close();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void clearHandlerCache() throws Exception {
|
||||
Map<File, JarFile> rootFileCache = ((SoftReference<Map<File, JarFile>>) ReflectionTestUtils
|
||||
.getField(Handler.class, "rootFileCache")).get();
|
||||
if (rootFileCache != null) {
|
||||
for (JarFile rootJarFile : rootFileCache.values()) {
|
||||
rootJarFile.close();
|
||||
}
|
||||
rootFileCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultHome() {
|
||||
void testDefaultHome() throws Exception {
|
||||
System.clearProperty("loader.home");
|
||||
this.launcher = new PropertiesLauncher();
|
||||
assertThat(this.launcher.getHomeDirectory()).isEqualTo(new File(System.getProperty("user.dir")));
|
||||
@@ -126,9 +108,8 @@ class PropertiesLauncherTests {
|
||||
@Test
|
||||
void testNonExistentHome() {
|
||||
System.setProperty("loader.home", "src/test/resources/nonexistent");
|
||||
assertThatIllegalStateException().isThrownBy(PropertiesLauncher::new)
|
||||
.withMessageContaining("Invalid source directory")
|
||||
.withCauseInstanceOf(IllegalArgumentException.class);
|
||||
assertThatIllegalArgumentException().isThrownBy(PropertiesLauncher::new)
|
||||
.withMessageContaining("Invalid source directory");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -154,7 +135,7 @@ class PropertiesLauncherTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUserSpecifiedDotPath() {
|
||||
void testUserSpecifiedDotPath() throws Exception {
|
||||
System.setProperty("loader.path", ".");
|
||||
this.launcher = new PropertiesLauncher();
|
||||
assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[.]");
|
||||
@@ -165,9 +146,8 @@ class PropertiesLauncherTests {
|
||||
System.setProperty("loader.path", "jars/");
|
||||
this.launcher = new PropertiesLauncher();
|
||||
assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[jars/]");
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(archives).areExactly(1, endingWith("app.jar"));
|
||||
Set<URL> urls = this.launcher.getClassPathUrls();
|
||||
assertThat(urls).areExactly(1, endingWith("app.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -196,29 +176,26 @@ class PropertiesLauncherTests {
|
||||
this.launcher = new PropertiesLauncher();
|
||||
assertThat(ReflectionTestUtils.getField(this.launcher, "paths"))
|
||||
.hasToString("[jar:file:./src/test/resources/nested-jars/app.jar!/]");
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(archives).areExactly(1, endingWith("app.jar"));
|
||||
Set<URL> urls = this.launcher.getClassPathUrls();
|
||||
assertThat(urls).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(urls).areExactly(1, endingWith("app.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUserSpecifiedRootOfJarPathWithDot() throws Exception {
|
||||
System.setProperty("loader.path", "nested-jars/app.jar!/./");
|
||||
this.launcher = new PropertiesLauncher();
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(archives).areExactly(1, endingWith("app.jar"));
|
||||
Set<URL> urls = this.launcher.getClassPathUrls();
|
||||
assertThat(urls).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(urls).areExactly(1, endingWith("app.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUserSpecifiedRootOfJarPathWithDotAndJarPrefix() throws Exception {
|
||||
System.setProperty("loader.path", "jar:file:./src/test/resources/nested-jars/app.jar!/./");
|
||||
this.launcher = new PropertiesLauncher();
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
|
||||
Set<URL> urls = this.launcher.getClassPathUrls();
|
||||
assertThat(urls).areExactly(1, endingWith("foo.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -226,10 +203,9 @@ class PropertiesLauncherTests {
|
||||
System.setProperty("loader.path", "nested-jars/app.jar");
|
||||
System.setProperty("loader.main", "demo.Application");
|
||||
this.launcher = new PropertiesLauncher();
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(archives).areExactly(1, endingWith("app.jar"));
|
||||
Set<URL> urls = this.launcher.getClassPathUrls();
|
||||
assertThat(urls).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(urls).areExactly(1, endingWith("app.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -287,32 +263,21 @@ class PropertiesLauncherTests {
|
||||
void testCustomClassLoaderCreation() throws Exception {
|
||||
System.setProperty("loader.classLoader", TestLoader.class.getName());
|
||||
this.launcher = new PropertiesLauncher();
|
||||
ClassLoader loader = this.launcher.createClassLoader(archives());
|
||||
ClassLoader loader = this.launcher.createClassLoader(classPathUrls());
|
||||
assertThat(loader).isNotNull();
|
||||
assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName());
|
||||
}
|
||||
|
||||
private Iterator<Archive> archives() throws Exception {
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
String path = System.getProperty("java.class.path");
|
||||
for (String url : path.split(File.pathSeparator)) {
|
||||
Archive archive = archive(url);
|
||||
if (archive != null) {
|
||||
archives.add(archive);
|
||||
private Set<URL> classPathUrls() throws Exception {
|
||||
Set<URL> urls = new LinkedHashSet<>();
|
||||
String classPath = System.getProperty("java.class.path");
|
||||
for (String path : classPath.split(File.pathSeparator)) {
|
||||
File file = new FileSystemResource(path).getFile();
|
||||
if (file.exists()) {
|
||||
urls.add(file.toURI().toURL());
|
||||
}
|
||||
}
|
||||
return archives.iterator();
|
||||
}
|
||||
|
||||
private Archive archive(String url) throws IOException {
|
||||
File file = new FileSystemResource(url).getFile();
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
if (url.endsWith(".jar")) {
|
||||
return new JarFileArchive(file);
|
||||
}
|
||||
return new ExplodedArchive(file);
|
||||
return urls;
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -331,7 +296,7 @@ class PropertiesLauncherTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSystemPropertiesSet() {
|
||||
void testSystemPropertiesSet() throws Exception {
|
||||
System.setProperty("loader.system", "true");
|
||||
new PropertiesLauncher();
|
||||
assertThat(System.getProperty("loader.main")).isEqualTo("demo.Application");
|
||||
@@ -374,17 +339,15 @@ class PropertiesLauncherTests {
|
||||
loaderPath.mkdir();
|
||||
System.setProperty("loader.path", loaderPath.toURI().toURL().toString());
|
||||
this.launcher = new PropertiesLauncher();
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(archives).hasSize(1);
|
||||
File archiveRoot = (File) ReflectionTestUtils.getField(archives.get(0), "root");
|
||||
assertThat(archiveRoot).isEqualTo(loaderPath);
|
||||
Set<URL> urls = this.launcher.getClassPathUrls();
|
||||
assertThat(urls).hasSize(1);
|
||||
assertThat(urls.iterator().next()).isEqualTo(loaderPath.toURI().toURL());
|
||||
}
|
||||
|
||||
@Test // gh-21575
|
||||
void loadResourceFromJarFile() throws Exception {
|
||||
File jarFile = new File(this.tempDir, "app.jar");
|
||||
TestJarCreator.createTestJar(jarFile);
|
||||
File file = new File(this.tempDir, "app.jar");
|
||||
TestJar.create(file);
|
||||
System.setProperty("loader.home", this.tempDir.getAbsolutePath());
|
||||
System.setProperty("loader.path", "app.jar");
|
||||
this.launcher = new PropertiesLauncher();
|
||||
@@ -393,11 +356,10 @@ class PropertiesLauncherTests {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Expected ClassNotFoundException
|
||||
LaunchedURLClassLoader classLoader = (LaunchedURLClassLoader) Thread.currentThread()
|
||||
.getContextClassLoader();
|
||||
LaunchedClassLoader classLoader = (LaunchedClassLoader) Thread.currentThread().getContextClassLoader();
|
||||
classLoader.close();
|
||||
}
|
||||
URL resource = new URL("jar:" + jarFile.toURI() + "!/nested.jar!/3.dat");
|
||||
URL resource = JarUrl.create(file, "nested.jar", "3.dat");
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(resource.openStream());
|
||||
assertThat(bytes).isNotEmpty();
|
||||
}
|
||||
@@ -406,11 +368,11 @@ class PropertiesLauncherTests {
|
||||
Awaitility.waitAtMost(Duration.ofSeconds(5)).until(this.output::toString, containsString(value));
|
||||
}
|
||||
|
||||
private Condition<Archive> endingWith(String value) {
|
||||
private Condition<URL> endingWith(String value) {
|
||||
return new Condition<>() {
|
||||
|
||||
@Override
|
||||
public boolean matches(Archive archive) {
|
||||
public boolean matches(URL archive) {
|
||||
return archive.toString().endsWith(value);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
package org.springframework.boot.loader.launch;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
@@ -22,14 +22,13 @@ import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.ExplodedArchive;
|
||||
import org.springframework.boot.loader.archive.JarFileArchive;
|
||||
import org.springframework.boot.loader.net.protocol.jar.JarUrl;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -38,45 +37,39 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
||||
|
||||
@Test
|
||||
void explodedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception {
|
||||
File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF"));
|
||||
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
|
||||
assertThat(getUrls(archives)).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot));
|
||||
for (Archive archive : archives) {
|
||||
archive.close();
|
||||
}
|
||||
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot));
|
||||
Set<URL> urls = launcher.getClassPathUrls();
|
||||
assertThat(urls).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot));
|
||||
}
|
||||
|
||||
@Test
|
||||
void archivedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception {
|
||||
File jarRoot = createJarArchive("archive.war", "WEB-INF");
|
||||
try (JarFileArchive archive = new JarFileArchive(jarRoot)) {
|
||||
File file = createJarArchive("archive.war", "WEB-INF");
|
||||
try (JarFileArchive archive = new JarFileArchive(file)) {
|
||||
WarLauncher launcher = new WarLauncher(archive);
|
||||
List<Archive> classPathArchives = new ArrayList<>();
|
||||
launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add);
|
||||
assertThat(getUrls(classPathArchives)).containsOnly(
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/bar.jar!/"),
|
||||
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/baz.jar!/"));
|
||||
for (Archive classPathArchive : classPathArchives) {
|
||||
classPathArchive.close();
|
||||
}
|
||||
Set<URL> urls = launcher.getClassPathUrls();
|
||||
List<URL> expected = new ArrayList<>();
|
||||
expected.add(JarUrl.create(file, "WEB-INF/classes/"));
|
||||
expected.add(JarUrl.create(file, "WEB-INF/lib/foo.jar"));
|
||||
expected.add(JarUrl.create(file, "WEB-INF/lib/bar.jar"));
|
||||
expected.add(JarUrl.create(file, "WEB-INF/lib/baz.jar"));
|
||||
assertThat(urls).containsOnly(expected.toArray(URL[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void explodedWarShouldPreserveClasspathOrderWhenIndexPresent() throws Exception {
|
||||
File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF", true, Collections.emptyList()));
|
||||
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
Iterator<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot));
|
||||
URLClassLoader classLoader = createClassLoader(launcher);
|
||||
URL[] urls = classLoader.getURLs();
|
||||
assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot));
|
||||
}
|
||||
@@ -85,20 +78,23 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
||||
void warFilesPresentInWebInfLibsAndNotInClasspathIndexShouldBeAddedAfterWebInfClasses() throws Exception {
|
||||
ArrayList<String> extraLibs = new ArrayList<>(Arrays.asList("extra-1.jar", "extra-2.jar"));
|
||||
File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF", true, extraLibs));
|
||||
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true));
|
||||
Iterator<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot));
|
||||
URLClassLoader classLoader = createClassLoader(launcher);
|
||||
URL[] urls = classLoader.getURLs();
|
||||
List<File> expectedFiles = getExpectedFilesWithExtraLibs(explodedRoot);
|
||||
URL[] expectedFileUrls = expectedFiles.stream().map(this::toUrl).toArray(URL[]::new);
|
||||
assertThat(urls).containsExactly(expectedFileUrls);
|
||||
}
|
||||
|
||||
protected final URL[] getExpectedFileUrls(File explodedRoot) {
|
||||
private URLClassLoader createClassLoader(Launcher launcher) throws Exception {
|
||||
return (URLClassLoader) launcher.createClassLoader(launcher.getClassPathUrls());
|
||||
}
|
||||
|
||||
private URL[] getExpectedFileUrls(File explodedRoot) {
|
||||
return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new);
|
||||
}
|
||||
|
||||
protected final List<File> getExpectedFiles(File parent) {
|
||||
private List<File> getExpectedFiles(File parent) {
|
||||
List<File> expected = new ArrayList<>();
|
||||
expected.add(new File(parent, "WEB-INF/classes"));
|
||||
expected.add(new File(parent, "WEB-INF/lib/foo.jar"));
|
||||
@@ -107,7 +103,7 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
||||
return expected;
|
||||
}
|
||||
|
||||
protected final List<File> getExpectedFilesWithExtraLibs(File parent) {
|
||||
private List<File> getExpectedFilesWithExtraLibs(File parent) {
|
||||
List<File> expected = new ArrayList<>();
|
||||
expected.add(new File(parent, "WEB-INF/classes"));
|
||||
expected.add(new File(parent, "WEB-INF/lib/extra-1.jar"));
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Canonicalizer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class CanonicalizerTests {
|
||||
|
||||
@Test
|
||||
void canonicalizeAfterOnlyChangesAfterPos() {
|
||||
String prefix = "/foo/.././bar/.!/foo/.././bar/.";
|
||||
String canonicalized = Canonicalizer.canonicalizeAfter(prefix, prefix.indexOf("!/"));
|
||||
assertThat(canonicalized).isEqualTo("/foo/.././bar/.!/bar/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void canonicalizeWhenHasEmbdeddSlashDotDotSlash() {
|
||||
assertThat(Canonicalizer.canonicalize("/foo/../bar/bif/bam/../../baz")).isEqualTo("/bar/baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void canonicalizeWhenHasEmbdeddSlashDotSlash() {
|
||||
assertThat(Canonicalizer.canonicalize("/foo/./bar/bif/bam/././baz")).isEqualTo("/foo/bar/bif/bam/baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void canonicalizeWhenHasTrailingSlashDotDot() {
|
||||
assertThat(Canonicalizer.canonicalize("/foo/bar/baz/../..")).isEqualTo("/foo/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void canonicalizeWhenHasTrailingSlashDot() {
|
||||
assertThat(Canonicalizer.canonicalize("/foo/bar/baz/./.")).isEqualTo("/foo/bar/baz/");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link Handler}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class HandlerTests {
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
@Test
|
||||
void indexOfSeparator() {
|
||||
String spec = "jar:nested:foo!bar!/some/entry#foo";
|
||||
assertThat(Handler.indexOfSeparator(spec, 0, spec.indexOf('#'))).isEqualTo(spec.lastIndexOf("!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexOfSeparatorWhenHasStartAndLimit() {
|
||||
String spec = "a!/jar:nested:foo!bar!/some/entry#foo!/b";
|
||||
int beginIndex = 3;
|
||||
int endIndex = spec.length() - 4;
|
||||
String substring = spec.substring(beginIndex, endIndex);
|
||||
assertThat(Handler.indexOfSeparator(spec, 0, spec.indexOf('#')))
|
||||
.isEqualTo(substring.lastIndexOf("!/") + beginIndex);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenAbsoluteParses() throws MalformedURLException {
|
||||
URL url = createJarUrl("");
|
||||
String spec = "jar:file:example.jar!/entry.txt";
|
||||
this.handler.parseURL(url, spec, 4, spec.length());
|
||||
assertThat(url.toExternalForm()).isEqualTo(spec);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenAbsoluteWithAnchorParses() throws MalformedURLException {
|
||||
URL url = createJarUrl("");
|
||||
String spec = "jar:file:example.jar!/entry.txt";
|
||||
this.handler.parseURL(url, spec + "#foo", 4, spec.length());
|
||||
assertThat(url.toExternalForm()).isEqualTo(spec + "#foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenAbsoluteWithNoSeparatorThrowsException() throws MalformedURLException {
|
||||
URL url = createJarUrl("");
|
||||
String spec = "jar:file:example.jar!\\entry.txt";
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.handler.parseURL(url, spec, 4, spec.length()))
|
||||
.withMessage("no !/ in spec");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenAbsoluteWithMalformedInnerUrlThrowsException() throws MalformedURLException {
|
||||
URL url = createJarUrl("");
|
||||
String spec = "jar:example.jar!/entry.txt";
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.handler.parseURL(url, spec, 4, spec.length()))
|
||||
.withMessage(
|
||||
"invalid url: jar:example.jar!/entry.txt (java.net.MalformedURLException: no protocol: example.jar)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenRelativeWithLeadingSlashParses() throws MalformedURLException {
|
||||
URL url = createJarUrl("file:example.jar!/entry.txt");
|
||||
String spec = "/other.txt";
|
||||
this.handler.parseURL(url, spec, 0, spec.length());
|
||||
assertThat(url.toExternalForm()).isEqualTo("jar:file:example.jar!/other.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenRelativeWithLeadingSlashAndAnchorParses() throws MalformedURLException {
|
||||
URL url = createJarUrl("file:example.jar!/entry.txt");
|
||||
String spec = "/other.txt";
|
||||
this.handler.parseURL(url, spec + "#relative", 0, spec.length());
|
||||
assertThat(url.toExternalForm()).isEqualTo("jar:file:example.jar!/other.txt#relative");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenRelativeWithLeadingSlashAndNoSeparator() throws MalformedURLException {
|
||||
URL url = createJarUrl("file:example.jar/entry.txt");
|
||||
String spec = "/other.txt";
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.handler.parseURL(url, spec, 0, spec.length()))
|
||||
.withMessage("malformed context url:jar:file:example.jar/entry.txt: no !/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenRelativeWithoutLeadingSlashParses() throws MalformedURLException {
|
||||
URL url = createJarUrl("file:example.jar!/foo/");
|
||||
String spec = "bar.txt";
|
||||
this.handler.parseURL(url, spec, 0, spec.length());
|
||||
assertThat(url.toExternalForm()).isEqualTo("jar:file:example.jar!/foo/bar.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenRelativeWithoutLeadingSlashAndWithoutTrailingSlashParses() throws MalformedURLException {
|
||||
URL url = createJarUrl("file:example.jar!/foo/baz");
|
||||
String spec = "bar.txt";
|
||||
this.handler.parseURL(url, spec, 0, spec.length());
|
||||
assertThat(url.toExternalForm()).isEqualTo("jar:file:example.jar!/foo/bar.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenRelativeWithoutLeadingSlashAndWithoutContextSlashThrowsException() throws MalformedURLException {
|
||||
URL url = createJarUrl("file:example.jar");
|
||||
String spec = "bar.txt";
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.handler.parseURL(url, spec, 0, spec.length()))
|
||||
.withMessage("malformed context url:jar:file:example.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUrlWhenAnchorOnly() throws MalformedURLException {
|
||||
URL url = createJarUrl("file:example.jar!/entry.txt");
|
||||
String spec = "#runtime";
|
||||
this.handler.parseURL(url, spec, 0, 0);
|
||||
assertThat(url.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt#runtime");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeGeneratesHashCode() throws MalformedURLException {
|
||||
URL url = createJarUrl("file:example.jar!/entry.txt");
|
||||
assertThat(this.handler.hashCode(url)).isEqualTo(1873709601);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeWhenMalformedInnerUrlGeneratesHashCode() throws MalformedURLException {
|
||||
URL url = createJarUrl("example.jar!/entry.txt");
|
||||
assertThat(this.handler.hashCode(url)).isEqualTo(1870566566);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileWhenSameReturnsTrue() throws MalformedURLException {
|
||||
URL url1 = createJarUrl("file:example.jar!/entry.txt");
|
||||
URL url2 = createJarUrl("file:example.jar!/entry.txt");
|
||||
assertThat(this.handler.sameFile(url1, url2)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileWhenMissingSeparatorReturnsFalse() throws MalformedURLException {
|
||||
URL url1 = createJarUrl("file:example.jar!/entry.txt");
|
||||
URL url2 = createJarUrl("file:example.jar/entry.txt");
|
||||
assertThat(this.handler.sameFile(url1, url2)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileWhenDifferentEntryReturnsFalse() throws MalformedURLException {
|
||||
URL url1 = createJarUrl("file:example.jar!/entry1.txt");
|
||||
URL url2 = createJarUrl("file:example.jar!/entry2.txt");
|
||||
assertThat(this.handler.sameFile(url1, url2)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileWhenDifferentInnerUrlReturnsFalse() throws MalformedURLException {
|
||||
URL url1 = createJarUrl("file:example1.jar!/entry.txt");
|
||||
URL url2 = createJarUrl("file:example2.jar!/entry.txt");
|
||||
assertThat(this.handler.sameFile(url1, url2)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileWhenSameMalformedInnerUrlReturnsTrue() throws MalformedURLException {
|
||||
URL url1 = createJarUrl("example.jar!/entry.txt");
|
||||
URL url2 = createJarUrl("example.jar!/entry.txt");
|
||||
assertThat(this.handler.sameFile(url1, url2)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameFileWhenDifferentMalformedInnerUrlReturnsFalse() throws MalformedURLException {
|
||||
URL url1 = createJarUrl("example1.jar!/entry.txt");
|
||||
URL url2 = createJarUrl("example2.jar!/entry.txt");
|
||||
assertThat(this.handler.sameFile(url1, url2)).isFalse();
|
||||
}
|
||||
|
||||
private URL createJarUrl(String file) throws MalformedURLException {
|
||||
return new URL("jar", null, -1, file, this.handler);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.Handlers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarFileUrlKey}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class JarFileUrlKeyTests {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
Handlers.register();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCreatesKey() throws Exception {
|
||||
URL url = new URL("jar:nested:/my.jar/!mynested.jar!/my/path");
|
||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("jar:nested:/my.jar/!mynested.jar!/my/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenUppercaseProtocolCreatesKey() throws Exception {
|
||||
URL url = new URL("JAR:nested:/my.jar/!mynested.jar!/my/path");
|
||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("jar:nested:/my.jar/!mynested.jar!/my/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasHostAndPortCreatesKey() throws Exception {
|
||||
URL url = new URL("https://example.com:1234/test");
|
||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("https:example.com:1234/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasUppercaseHostCreatesKey() throws Exception {
|
||||
URL url = new URL("https://EXAMPLE.com:1234/test");
|
||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("https:example.com:1234/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasNoPortCreatesKeyWithDefaultPort() throws Exception {
|
||||
URL url = new URL("https://EXAMPLE.com/test");
|
||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("https:example.com:443/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasNoFileCreatesKey() throws Exception {
|
||||
URL url = new URL("https://EXAMPLE.com");
|
||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("https:example.com:443");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasRuntimeRefCreatesKey() throws Exception {
|
||||
URL url = new URL("jar:nested:/my.jar/!mynested.jar!/my/path#runtime");
|
||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("jar:nested:/my.jar/!mynested.jar!/my/path#runtime");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasOtherRefCreatesKeyWithoutRef() throws Exception {
|
||||
URL url = new URL("jar:nested:/my.jar/!mynested.jar!/my/path#example");
|
||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("jar:nested:/my.jar/!mynested.jar!/my/path");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.Handlers;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarUrlClassLoader}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class JarUrlClassLoaderTests {
|
||||
|
||||
private static final URL APP_JAR;
|
||||
static {
|
||||
try {
|
||||
APP_JAR = new URL("jar:file:src/test/resources/jars/app.jar!/");
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
Handlers.register();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveResourceFromArchive() throws Exception {
|
||||
try (JarUrlClassLoader loader = new TestJarUrlClassLoader(APP_JAR)) {
|
||||
assertThat(loader.getResource("demo/Application.java")).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveResourcesFromArchive() throws Exception {
|
||||
try (JarUrlClassLoader loader = new TestJarUrlClassLoader(APP_JAR)) {
|
||||
assertThat(loader.getResources("demo/Application.java").hasMoreElements()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRootPathFromArchive() throws Exception {
|
||||
try (JarUrlClassLoader loader = new TestJarUrlClassLoader(APP_JAR)) {
|
||||
assertThat(loader.getResource("")).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRootResourcesFromArchive() throws Exception {
|
||||
try (JarUrlClassLoader loader = new TestJarUrlClassLoader(APP_JAR)) {
|
||||
assertThat(loader.getResources("").hasMoreElements()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveFromNested() throws Exception {
|
||||
File jarFile = new File(this.tempDir, "test.jar");
|
||||
TestJar.create(jarFile);
|
||||
URL url = JarUrl.create(jarFile, "nested.jar");
|
||||
try (JarUrlClassLoader loader = new TestJarUrlClassLoader(url)) {
|
||||
URL resource = loader.getResource("3.dat");
|
||||
assertThat(resource).hasToString(url + "3.dat");
|
||||
try (InputStream input = resource.openConnection().getInputStream()) {
|
||||
assertThat(input.read()).isEqualTo(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadClass() throws Exception {
|
||||
try (JarUrlClassLoader loader = new TestJarUrlClassLoader(APP_JAR)) {
|
||||
assertThat(loader.loadClass("demo.Application")).isNotNull().hasToString("class demo.Application");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadClassFromNested() throws Exception {
|
||||
File appJar = new File("src/test/resources/jars/app.jar");
|
||||
File jarFile = new File(this.tempDir, "test.jar");
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(jarFile);
|
||||
try (JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream)) {
|
||||
JarEntry nestedEntry = new JarEntry("app.jar");
|
||||
byte[] nestedJarData = Files.readAllBytes(appJar.toPath());
|
||||
nestedEntry.setSize(nestedJarData.length);
|
||||
nestedEntry.setCompressedSize(nestedJarData.length);
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(nestedJarData);
|
||||
nestedEntry.setCrc(crc32.getValue());
|
||||
nestedEntry.setMethod(ZipEntry.STORED);
|
||||
jarOutputStream.putNextEntry(nestedEntry);
|
||||
jarOutputStream.write(nestedJarData);
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
URL url = JarUrl.create(jarFile, "app.jar");
|
||||
try (JarUrlClassLoader loader = new TestJarUrlClassLoader(url)) {
|
||||
assertThat(loader.loadClass("demo.Application")).isNotNull().hasToString("class demo.Application");
|
||||
}
|
||||
}
|
||||
|
||||
static class TestJarUrlClassLoader extends JarUrlClassLoader {
|
||||
|
||||
TestJarUrlClassLoader(URL... urls) {
|
||||
super(urls, JarUrlClassLoaderTests.class.getClassLoader());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Permission;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.Handlers;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.boot.loader.zip.ZipContent;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarUrlConnection}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class JarUrlConnectionTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
private File file;
|
||||
|
||||
private URL url;
|
||||
|
||||
@BeforeAll
|
||||
static void registerHandlers() {
|
||||
Handlers.register();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
void reset() {
|
||||
JarUrlConnection.clearCache();
|
||||
Optimizations.disable();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
this.file = new File(this.temp, "test.jar");
|
||||
TestJar.create(this.file);
|
||||
this.url = JarUrl.create(this.file, "nested.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getJarFileReturnsJarFile() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
JarFile jarFile = connection.getJarFile();
|
||||
assertThat(jarFile).isNotNull();
|
||||
assertThat(jarFile.getEntry("3.dat")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getJarEntryReturnsJarEntry() throws Exception {
|
||||
URL url = JarUrl.create(this.file, "nested.jar", "3.dat");
|
||||
JarUrlConnection connection = JarUrlConnection.open(url);
|
||||
JarEntry entry = connection.getJarEntry();
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.getName()).isEqualTo("3.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getJarEntryWhenHasNoEntryNameReturnsNull() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
JarEntry entry = connection.getJarEntry();
|
||||
assertThat(entry).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthReturnsContentLength() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
try (ZipContent content = ZipContent.open(this.file.toPath())) {
|
||||
int expected = content.getEntry("nested.jar").getUncompressedSize();
|
||||
assertThat(connection.getContentLength()).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthWhenLengthIsLargerThanMaxIntReturnsMinusOne() {
|
||||
JarUrlConnection connection = mock(JarUrlConnection.class);
|
||||
given(connection.getContentLength()).willCallRealMethod();
|
||||
given(connection.getContentLengthLong()).willReturn((long) Integer.MAX_VALUE + 1);
|
||||
assertThat(connection.getContentLength()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthLongWhenHasNoEntryReturnsSizeOfJar() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
try (ZipContent content = ZipContent.open(this.file.toPath())) {
|
||||
int expected = content.getEntry("nested.jar").getUncompressedSize();
|
||||
assertThat(connection.getContentLengthLong()).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthLongWhenHasEntryReturnsEntrySize() throws Exception {
|
||||
URL url = JarUrl.create(this.file, "nested.jar", "3.dat");
|
||||
JarUrlConnection connection = JarUrlConnection.open(url);
|
||||
assertThat(connection.getContentLengthLong()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthLongWhenCannotConnectReturnsMinusOne() throws IOException {
|
||||
JarUrlConnection connection = mock(JarUrlConnection.class);
|
||||
willThrow(IOException.class).given(connection).connect();
|
||||
given(connection.getContentLengthLong()).willCallRealMethod();
|
||||
assertThat(connection.getContentLengthLong()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentTypeWhenHasNoEntryReturnsJavaJar() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
assertThat(connection.getContentType()).isEqualTo("x-java/jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentTypeWhenHasKnownStreamReturnsDeducedType() throws Exception {
|
||||
String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ok></ok>";
|
||||
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(this.file))) {
|
||||
out.putNextEntry(new ZipEntry("test.dat"));
|
||||
out.write(content.getBytes(StandardCharsets.UTF_8));
|
||||
out.closeEntry();
|
||||
}
|
||||
JarUrlConnection connection = JarUrlConnection
|
||||
.open(new URL("jar:file:" + this.file.getAbsolutePath() + "!/test.dat"));
|
||||
assertThat(connection.getContentType()).isEqualTo("application/xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentTypeWhenNotKnownInStreamButKnownNameReturnsDeducedType() throws Exception {
|
||||
String content = "nothinguseful";
|
||||
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(this.file))) {
|
||||
out.putNextEntry(new ZipEntry("test.xml"));
|
||||
out.write(content.getBytes(StandardCharsets.UTF_8));
|
||||
out.closeEntry();
|
||||
}
|
||||
JarUrlConnection connection = JarUrlConnection
|
||||
.open(new URL("jar:file:" + this.file.getAbsolutePath() + "!/test.xml"));
|
||||
assertThat(connection.getContentType()).isEqualTo("application/xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentTypeWhenCannotBeDeducedReturnsContentUnknown() throws Exception {
|
||||
String content = "nothinguseful";
|
||||
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(this.file))) {
|
||||
out.putNextEntry(new ZipEntry("test.dat"));
|
||||
out.write(content.getBytes(StandardCharsets.UTF_8));
|
||||
out.closeEntry();
|
||||
}
|
||||
JarUrlConnection connection = JarUrlConnection
|
||||
.open(new URL("jar:file:" + this.file.getAbsolutePath() + "!/test.dat"));
|
||||
assertThat(connection.getContentType()).isEqualTo("content/unknown");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHeaderFieldDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
given(jarFileConnection.getHeaderField("test")).willReturn("test");
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
assertThat(connection.getHeaderField("test")).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentWhenHasEntryReturnsContentFromEntry() throws Exception {
|
||||
String content = "hello";
|
||||
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(this.file))) {
|
||||
out.putNextEntry(new ZipEntry("test.txt"));
|
||||
out.write(content.getBytes(StandardCharsets.UTF_8));
|
||||
out.closeEntry();
|
||||
}
|
||||
JarUrlConnection connection = JarUrlConnection
|
||||
.open(new URL("jar:file:" + this.file.getAbsolutePath() + "!/test.txt"));
|
||||
assertThat(connection.getContent()).isInstanceOf(FilterInputStream.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentWhenHasNoEntryReturnsJarFile() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
assertThat(connection.getContent()).isInstanceOf(JarFile.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPermissionReturnJarConnectionPermission() throws IOException {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
Permission permission = mock(Permission.class);
|
||||
given(jarFileConnection.getPermission()).willReturn(permission);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
assertThat(connection.getPermission()).isSameAs(permission);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamWhenHasNoEntryThrowsException() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
assertThatIOException().isThrownBy(() -> connection.getInputStream()).withMessage("no entry name specified");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamWhenOptimizedWithoutReadAndHasCachedJarWithEntryReturnsEmptyInputStream() throws Exception {
|
||||
JarUrlConnection setupConnection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar"));
|
||||
setupConnection.connect();
|
||||
assertThat(JarUrlConnection.jarFiles.getCached(setupConnection.getJarFileURL())).isNotNull();
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "3.dat"));
|
||||
connection.setUseCaches(false);
|
||||
Optimizations.enable(false);
|
||||
assertThat(connection.getInputStream()).isSameAs(JarUrlConnection.emptyInputStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamWhenNoEntryAndOptimzedThrowsException() throws Exception {
|
||||
JarUrlConnection setupConnection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar"));
|
||||
setupConnection.connect();
|
||||
assertThat(JarUrlConnection.jarFiles.getCached(setupConnection.getJarFileURL())).isNotNull();
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "missing.dat"));
|
||||
Optimizations.enable(false);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(connection::getInputStream)
|
||||
.isSameAs(JarUrlConnection.FILE_NOT_FOUND_EXCEPTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamWhenNoEntryAndNotOptimzedThrowsException() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "missing.dat"));
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(connection::getInputStream)
|
||||
.withMessageContaining("JAR entry missing.dat not found in");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamReturnsInputStream() throws IOException {
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "3.dat"));
|
||||
try (InputStream in = connection.getInputStream()) {
|
||||
assertThat(in).hasBinaryContent(new byte[] { 3 });
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamWhenNoCachedClosesJarFileOnClose() throws IOException {
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "3.dat"));
|
||||
connection.setUseCaches(false);
|
||||
InputStream in = connection.getInputStream();
|
||||
JarFile jarFile = (JarFile) ReflectionTestUtils.getField(connection, "jarFile");
|
||||
jarFile = spy(jarFile);
|
||||
ReflectionTestUtils.setField(connection, "jarFile", jarFile);
|
||||
in.close();
|
||||
then(jarFile).should().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllowUserInteractionDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
given(jarFileConnection.getAllowUserInteraction()).willReturn(true);
|
||||
assertThat(connection.getAllowUserInteraction()).isTrue();
|
||||
then(jarFileConnection).should().getAllowUserInteraction();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setAllowUserInteractionDelegatesToJarFileConnection() throws IOException {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
connection.setAllowUserInteraction(true);
|
||||
then(jarFileConnection).should().setAllowUserInteraction(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUseCachesDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
given(jarFileConnection.getUseCaches()).willReturn(true);
|
||||
assertThat(connection.getUseCaches()).isTrue();
|
||||
then(jarFileConnection).should().getUseCaches();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setUseCachesDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
connection.setUseCaches(true);
|
||||
then(jarFileConnection).should().setUseCaches(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultUseCachesDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
given(jarFileConnection.getDefaultUseCaches()).willReturn(true);
|
||||
assertThat(connection.getDefaultUseCaches()).isTrue();
|
||||
then(jarFileConnection).should().getDefaultUseCaches();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setDefaultUseCachesDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
connection.setDefaultUseCaches(true);
|
||||
then(jarFileConnection).should().setDefaultUseCaches(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setIfModifiedSinceDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
connection.setIfModifiedSince(123L);
|
||||
then(jarFileConnection).should().setIfModifiedSince(123L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRequestPropertyDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
given(jarFileConnection.getRequestProperty("test")).willReturn("test");
|
||||
assertThat(connection.getRequestProperty("test")).isEqualTo("test");
|
||||
then(jarFileConnection).should().getRequestProperty("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setRequestPropertyDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
connection.setRequestProperty("test", "testvalue");
|
||||
then(jarFileConnection).should().setRequestProperty("test", "testvalue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addRequestPropertyDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
connection.addRequestProperty("test", "testvalue");
|
||||
then(jarFileConnection).should().addRequestProperty("test", "testvalue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRequestPropertiesDelegatesToJarFileConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
URLConnection jarFileConnection = mock(URLConnection.class);
|
||||
ReflectionTestUtils.setField(connection, "jarFileConnection", jarFileConnection);
|
||||
Map<String, List<String>> properties = Map.of("test", List.of("testvalue"));
|
||||
given(jarFileConnection.getRequestProperties()).willReturn(properties);
|
||||
assertThat(connection.getRequestProperties()).isEqualTo(properties);
|
||||
then(jarFileConnection).should().getRequestProperties();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWhenConnectedDoesNotReconnect() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
connection.connect();
|
||||
ReflectionTestUtils.setField(connection, "jarFile", null);
|
||||
connection.connect();
|
||||
assertThat(ReflectionTestUtils.getField(connection, "jarFile")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWhenHasNotFoundSupplierThrowsException() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "missing.dat"));
|
||||
assertThat(connection).extracting("notFound").isNotNull();
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(connection::connect)
|
||||
.withMessageContaining("JAR entry missing.dat not found in");
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWhenOptimizationsEnabledAndHasCachedJarWithoutEntryThrowsException() throws Exception {
|
||||
JarUrlConnection setupConnection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar"));
|
||||
setupConnection.connect();
|
||||
assertThat(JarUrlConnection.jarFiles.getCached(setupConnection.getJarFileURL())).isNotNull();
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "missing.dat"));
|
||||
Optimizations.enable(true);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(connection::connect)
|
||||
.isSameAs(JarUrlConnection.FILE_NOT_FOUND_EXCEPTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWhenHasNoEntryConnects() throws Exception {
|
||||
JarUrlConnection setupConnection = JarUrlConnection.open(this.url);
|
||||
setupConnection.connect();
|
||||
assertThat(setupConnection.getJarFile()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWhenEntryDoesNotExistAndOptimizationsEnabledThrowsException() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "missing.dat"));
|
||||
Optimizations.enable(true);
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(connection::connect)
|
||||
.isSameAs(JarUrlConnection.FILE_NOT_FOUND_EXCEPTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWhenEntryDoesNotExistAndNoOptimizationsEnabledThrowsException() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "missing.dat"));
|
||||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(connection::connect)
|
||||
.withMessageContaining("JAR entry missing.dat not found in");
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWhenEntryExists() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "3.dat"));
|
||||
connection.connect();
|
||||
assertThat(connection.getJarEntry()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWhenAddedToCacheReconnects() throws IOException {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
Object originalConnection = ReflectionTestUtils.getField(connection, "jarFileConnection");
|
||||
connection.connect();
|
||||
assertThat(connection).extracting("jarFileConnection").isNotSameAs(originalConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void openWhenNestedAndInCachedWithoutEntryAndOptimzationsEnabledReturnsNoFoundConnection() throws Exception {
|
||||
JarUrlConnection setupConnection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar"));
|
||||
setupConnection.connect();
|
||||
assertThat(JarUrlConnection.jarFiles.getCached(setupConnection.getJarFileURL())).isNotNull();
|
||||
Optimizations.enable(true);
|
||||
JarUrlConnection connection = JarUrlConnection.open(JarUrl.create(this.file, "nested.jar", "missing.dat"));
|
||||
assertThat(connection).isSameAs(JarUrlConnection.NOT_FOUND_CONNECTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void openReturnsConnection() throws Exception {
|
||||
JarUrlConnection connection = JarUrlConnection.open(this.url);
|
||||
assertThat(connection).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarUrl}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class JarUrlTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
File jarFile;
|
||||
|
||||
String jarFileUrlPath;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws MalformedURLException {
|
||||
this.jarFile = new File(this.temp, "my.jar");
|
||||
this.jarFileUrlPath = this.temp.toURI().toURL().toString().substring("file:".length());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithFileReturnsUrl() {
|
||||
URL url = JarUrl.create(this.temp);
|
||||
assertThat(url).hasToString("jar:file:%s!/".formatted(this.jarFileUrlPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithFileAndEntryReturnsUrl() {
|
||||
JarEntry entry = new JarEntry("lib.jar");
|
||||
URL url = JarUrl.create(this.temp, entry);
|
||||
assertThat(url).hasToString("jar:nested:%s/!lib.jar!/".formatted(this.jarFileUrlPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithFileAndNullEntryReturnsUrl() {
|
||||
URL url = JarUrl.create(this.temp, (JarEntry) null);
|
||||
assertThat(url).hasToString("jar:file:%s!/".formatted(this.jarFileUrlPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithFileAndNameReturnsUrl() {
|
||||
URL url = JarUrl.create(this.temp, "lib.jar");
|
||||
assertThat(url).hasToString("jar:nested:%s/!lib.jar!/".formatted(this.jarFileUrlPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithFileAndNullNameReturnsUrl() {
|
||||
URL url = JarUrl.create(this.temp, (String) null);
|
||||
assertThat(url).hasToString("jar:file:%s!/".formatted(this.jarFileUrlPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithFileNameAndPathReturnsUrl() {
|
||||
URL url = JarUrl.create(this.temp, "lib.jar", "com/example/My.class");
|
||||
assertThat(url).hasToString("jar:nested:%s/!lib.jar!/com/example/My.class".formatted(this.jarFileUrlPath));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link LazyDelegatingInputStream}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LazyDelegatingInputStreamTests {
|
||||
|
||||
private InputStream delegate = mock(InputStream.class);
|
||||
|
||||
private TestLazyDelegatingInputStream inputStream = new TestLazyDelegatingInputStream();
|
||||
|
||||
@Test
|
||||
void noOperationsDoesNotGetDelegateInputStream() {
|
||||
then(this.delegate).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDelegatesToInputStream() throws Exception {
|
||||
this.inputStream.read();
|
||||
then(this.delegate).should().read();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithByteArrayDelegatesToInputStream() throws Exception {
|
||||
byte[] bytes = new byte[1];
|
||||
this.inputStream.read(bytes);
|
||||
then(this.delegate).should().read(bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithByteArrayAndOffsetAndLenDelegatesToInputStream() throws Exception {
|
||||
byte[] bytes = new byte[1];
|
||||
this.inputStream.read(bytes, 0, 1);
|
||||
then(this.delegate).should().read(bytes, 0, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void skipDelegatesToInputStream() throws Exception {
|
||||
this.inputStream.skip(10);
|
||||
then(this.delegate).should().skip(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void availableDelegatesToInputStream() throws Exception {
|
||||
this.inputStream.available();
|
||||
then(this.delegate).should().available();
|
||||
}
|
||||
|
||||
@Test
|
||||
void markSupportedDelegatesToInputStream() {
|
||||
this.inputStream.markSupported();
|
||||
then(this.delegate).should().markSupported();
|
||||
}
|
||||
|
||||
@Test
|
||||
void markDelegatesToInputStream() {
|
||||
this.inputStream.mark(10);
|
||||
then(this.delegate).should().mark(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetDelegatesToInputStream() throws Exception {
|
||||
this.inputStream.reset();
|
||||
then(this.delegate).should().reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeWhenDelegateNotCreatedDoesNothing() throws Exception {
|
||||
this.inputStream.close();
|
||||
then(this.delegate).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeDelegatesToInputStream() throws Exception {
|
||||
this.inputStream.available();
|
||||
this.inputStream.close();
|
||||
then(this.delegate).should().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDelegateInputStreamIsOnlyCalledOnce() throws Exception {
|
||||
this.inputStream.available();
|
||||
this.inputStream.mark(10);
|
||||
this.inputStream.read();
|
||||
assertThat(this.inputStream.count).isOne();
|
||||
}
|
||||
|
||||
private class TestLazyDelegatingInputStream extends LazyDelegatingInputStream {
|
||||
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
protected InputStream getDelegateInputStream() throws IOException {
|
||||
this.count++;
|
||||
return LazyDelegatingInputStreamTests.this.delegate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Optimizations}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class OptimizationsTests {
|
||||
|
||||
@AfterEach
|
||||
void reset() {
|
||||
Optimizations.disable();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultIsNotEnabled() {
|
||||
assertThat(Optimizations.isEnabled()).isFalse();
|
||||
assertThat(Optimizations.isEnabled(true)).isFalse();
|
||||
assertThat(Optimizations.isEnabled(false)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableWithReadContentsEnables() {
|
||||
Optimizations.enable(true);
|
||||
assertThat(Optimizations.isEnabled()).isTrue();
|
||||
assertThat(Optimizations.isEnabled(true)).isTrue();
|
||||
assertThat(Optimizations.isEnabled(false)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableWithoutReadContentsEnables() {
|
||||
Optimizations.enable(false);
|
||||
assertThat(Optimizations.isEnabled()).isTrue();
|
||||
assertThat(Optimizations.isEnabled(true)).isFalse();
|
||||
assertThat(Optimizations.isEnabled(false)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableIsByThread() throws InterruptedException {
|
||||
Optimizations.enable(true);
|
||||
boolean[] enabled = new boolean[1];
|
||||
Thread thread = new Thread(() -> enabled[0] = Optimizations.isEnabled());
|
||||
thread.start();
|
||||
thread.join();
|
||||
assertThat(enabled[0]).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableDisables() {
|
||||
Optimizations.enable(true);
|
||||
Optimizations.disable();
|
||||
assertThat(Optimizations.isEnabled()).isFalse();
|
||||
assertThat(Optimizations.isEnabled(true)).isFalse();
|
||||
assertThat(Optimizations.isEnabled(false)).isFalse();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link UrlJarEntry}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class UrlJarEntryTests {
|
||||
|
||||
@Test
|
||||
void ofWhenEntryIsNullReturnsNull() {
|
||||
assertThat(UrlJarEntry.of(null, null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofReturnsUrlJarEntry() {
|
||||
JarEntry entry = new JarEntry("test");
|
||||
assertThat(UrlJarEntry.of(entry, null)).isNotNull();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAttributesDelegatesToUrlJarManifest() throws Exception {
|
||||
JarEntry entry = new JarEntry("test");
|
||||
UrlJarManifest manifest = mock(UrlJarManifest.class);
|
||||
Attributes attributes = mock(Attributes.class);
|
||||
given(manifest.getEntryAttributes(any())).willReturn(attributes);
|
||||
assertThat(UrlJarEntry.of(entry, manifest).getAttributes()).isSameAs(attributes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URL;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.Handlers;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link UrlJarFileFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class UrlJarFileFactoryTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
private final UrlJarFileFactory factory = new UrlJarFileFactory();
|
||||
|
||||
@Mock
|
||||
private Consumer<JarFile> closeAction;
|
||||
|
||||
@BeforeAll
|
||||
static void registerHandlers() {
|
||||
Handlers.register();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJarFileWhenLocalFile() throws Throwable {
|
||||
File file = new File(this.temp, "test.jar");
|
||||
TestJar.create(file);
|
||||
URL url = file.toURI().toURL();
|
||||
JarFile jarFile = this.factory.createJarFile(url, this.closeAction);
|
||||
assertThat(jarFile).isInstanceOf(UrlJarFile.class);
|
||||
assertThat(jarFile).hasFieldOrPropertyWithValue("closeAction", this.closeAction);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJarFileWhenNested() throws Throwable {
|
||||
File file = new File(this.temp, "test.jar");
|
||||
TestJar.create(file);
|
||||
URL url = new URL("nested:" + file.getPath() + "/!nested.jar");
|
||||
JarFile jarFile = this.factory.createJarFile(url, this.closeAction);
|
||||
assertThat(jarFile).isInstanceOf(UrlNestedJarFile.class);
|
||||
assertThat(jarFile).hasFieldOrPropertyWithValue("closeAction", this.closeAction);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJarFileWhenStream() throws Exception {
|
||||
File file = new File(this.temp, "test.jar");
|
||||
TestJar.create(file);
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
|
||||
server.createContext("/test", (exchange) -> {
|
||||
exchange.sendResponseHeaders(200, file.length());
|
||||
try (InputStream in = new FileInputStream(file)) {
|
||||
in.transferTo(exchange.getResponseBody());
|
||||
}
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
try {
|
||||
URL url = new URL("http://localhost:" + server.getAddress().getPort() + "/test");
|
||||
JarFile jarFile = this.factory.createJarFile(url, this.closeAction);
|
||||
assertThat(jarFile).isInstanceOf(UrlJarFile.class);
|
||||
assertThat(jarFile).hasFieldOrPropertyWithValue("closeAction", this.closeAction);
|
||||
}
|
||||
finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasRuntimeRef() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
|
||||
/**
|
||||
* Tests for {@link UrlJarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class UrlJarFileTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
private UrlJarFile jarFile;
|
||||
|
||||
@Mock
|
||||
private Consumer<JarFile> closeAction;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
File file = new File(this.temp, "test.jar");
|
||||
TestJar.create(file);
|
||||
this.jarFile = new UrlJarFile(file, Runtime.version(), this.closeAction);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() throws Exception {
|
||||
this.jarFile.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenNotfoundReturnsNull() {
|
||||
assertThat(this.jarFile.getEntry("missing")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenFoundReturnsUrlJarEntry() {
|
||||
assertThat(this.jarFile.getEntry("1.dat")).isInstanceOf(UrlJarEntry.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestReturnsNewCopy() throws Exception {
|
||||
Manifest manifest1 = this.jarFile.getManifest();
|
||||
Manifest manifest2 = this.jarFile.getManifest();
|
||||
assertThat(manifest1).isNotSameAs(manifest2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeCallsCloseAction() throws Exception {
|
||||
this.jarFile.close();
|
||||
then(this.closeAction).should().accept(this.jarFile);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.Handlers;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
/**
|
||||
* Tests for {@link UrlJarFiles}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class UrlJarFilesTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
private UrlJarFileFactory factory = mock(UrlJarFileFactory.class);
|
||||
|
||||
private final UrlJarFiles jarFiles = new UrlJarFiles(this.factory);
|
||||
|
||||
private File file;
|
||||
|
||||
private URL url;
|
||||
|
||||
@BeforeAll
|
||||
static void registerHandlers() {
|
||||
Handlers.register();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
this.file = new File(this.temp, "test.jar");
|
||||
this.url = new URL("nested:" + this.file.getAbsolutePath() + "/!nested.jar");
|
||||
TestJar.create(this.file);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrCreateWhenNotUsingCachesAlwaysCreatesNewJarFile() throws Exception {
|
||||
given(this.factory.createJarFile(any(), any())).willCallRealMethod();
|
||||
JarFile jarFile1 = this.jarFiles.getOrCreate(false, this.url);
|
||||
JarFile jarFile2 = this.jarFiles.getOrCreate(false, this.url);
|
||||
JarFile jarFile3 = this.jarFiles.getOrCreate(false, this.url);
|
||||
assertThat(jarFile1).isNotSameAs(jarFile2).isNotSameAs(jarFile3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrCreateWhenUsingCachingReturnsCachedWhenAvailable() throws Exception {
|
||||
given(this.factory.createJarFile(any(), any())).willCallRealMethod();
|
||||
JarFile jarFile1 = this.jarFiles.getOrCreate(true, this.url);
|
||||
this.jarFiles.cacheIfAbsent(true, this.url, jarFile1);
|
||||
JarFile jarFile2 = this.jarFiles.getOrCreate(true, this.url);
|
||||
JarFile jarFile3 = this.jarFiles.getOrCreate(true, this.url);
|
||||
assertThat(jarFile1).isSameAs(jarFile2).isSameAs(jarFile3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCachedWhenNotCachedReturnsNull() {
|
||||
assertThat(this.jarFiles.getCached(this.url)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCachedWhenCachedReturnsCachedJar() throws Exception {
|
||||
given(this.factory.createJarFile(any(), any())).willCallRealMethod();
|
||||
JarFile jarFile = this.factory.createJarFile(this.url, null);
|
||||
this.jarFiles.cacheIfAbsent(true, this.url, jarFile);
|
||||
assertThat(this.jarFiles.getCached(this.url)).isSameAs(jarFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheIfAbsentWhenNotUsingCachesDoesNotCacheAndReturnsFalse() throws Exception {
|
||||
given(this.factory.createJarFile(any(), any())).willCallRealMethod();
|
||||
JarFile jarFile = this.factory.createJarFile(this.url, null);
|
||||
this.jarFiles.cacheIfAbsent(false, this.url, jarFile);
|
||||
assertThat(this.jarFiles.getCached(this.url)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheIfAbsentWhenUsingCachingAndNotAlreadyCachedCachesAndReturnsTrue() throws Exception {
|
||||
given(this.factory.createJarFile(any(), any())).willCallRealMethod();
|
||||
JarFile jarFile = this.factory.createJarFile(this.url, null);
|
||||
assertThat(this.jarFiles.cacheIfAbsent(true, this.url, jarFile)).isTrue();
|
||||
assertThat(this.jarFiles.getCached(this.url)).isSameAs(jarFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheIfAbsentWhenUsingCachingAndAlreadyCachedLeavesCacheAndReturnsFalse() throws Exception {
|
||||
given(this.factory.createJarFile(any(), any())).willCallRealMethod();
|
||||
JarFile jarFile1 = this.factory.createJarFile(this.url, null);
|
||||
JarFile jarFile2 = this.factory.createJarFile(this.url, null);
|
||||
assertThat(this.jarFiles.cacheIfAbsent(true, this.url, jarFile1)).isTrue();
|
||||
assertThat(this.jarFiles.cacheIfAbsent(true, this.url, jarFile2)).isFalse();
|
||||
assertThat(this.jarFiles.getCached(this.url)).isSameAs(jarFile1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeIfNotCachedWhenNotCachedClosesJarFile() throws Exception {
|
||||
JarFile jarFile = mock(JarFile.class);
|
||||
this.jarFiles.closeIfNotCached(this.url, jarFile);
|
||||
then(jarFile).should().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeIfNotCachedWhenCachedDoesNotCloseJarFile() throws Exception {
|
||||
JarFile jarFile = mock(JarFile.class);
|
||||
this.jarFiles.cacheIfAbsent(true, this.url, jarFile);
|
||||
this.jarFiles.closeIfNotCached(this.url, jarFile);
|
||||
then(jarFile).should(never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconnectReconnectsAndAppliesUseCaches() throws Exception {
|
||||
JarFile jarFile = mock(JarFile.class);
|
||||
this.jarFiles.cacheIfAbsent(true, this.url, jarFile);
|
||||
URLConnection existingConnection = mock(URLConnection.class);
|
||||
given(existingConnection.getUseCaches()).willReturn(true);
|
||||
URLConnection connection = this.jarFiles.reconnect(jarFile, existingConnection);
|
||||
assertThat(connection).isNotSameAs(existingConnection);
|
||||
assertThat(connection.getUseCaches()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconnectWhenExistingConnectionIsNullReconnects() throws Exception {
|
||||
JarFile jarFile = mock(JarFile.class);
|
||||
this.jarFiles.cacheIfAbsent(true, this.url, jarFile);
|
||||
URLConnection connection = this.jarFiles.reconnect(jarFile, null);
|
||||
assertThat(connection).isNotNull();
|
||||
assertThat(connection.getUseCaches()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.jar.UrlJarManifest.ManifestSupplier;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
/**
|
||||
* Tests for {@link UrlJarManifest}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class UrlJarManifestTests {
|
||||
|
||||
@Test
|
||||
void getWhenSuppliedManifestIsNullReturnsNull() throws Exception {
|
||||
UrlJarManifest urlJarManifest = new UrlJarManifest(() -> null);
|
||||
assertThat(urlJarManifest.get()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAlwaysReturnsDeepCopy() throws Exception {
|
||||
Manifest manifest = new Manifest();
|
||||
UrlJarManifest urlJarManifest = new UrlJarManifest(() -> manifest);
|
||||
manifest.getMainAttributes().putValue("test", "one");
|
||||
manifest.getEntries().put("spring", new Attributes());
|
||||
Manifest copy = urlJarManifest.get();
|
||||
assertThat(copy).isNotSameAs(manifest);
|
||||
manifest.getMainAttributes().clear();
|
||||
manifest.getEntries().clear();
|
||||
assertThat(copy.getMainAttributes()).isNotEmpty();
|
||||
assertThat(copy.getAttributes("spring")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntrtyAttributesWhenSuppliedManifestIsNullReturnsNull() throws Exception {
|
||||
UrlJarManifest urlJarManifest = new UrlJarManifest(() -> null);
|
||||
assertThat(urlJarManifest.getEntryAttributes(new JarEntry("test"))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryAttributesReturnsDeepCopy() throws Exception {
|
||||
Manifest manifest = new Manifest();
|
||||
UrlJarManifest urlJarManifest = new UrlJarManifest(() -> manifest);
|
||||
Attributes attributes = new Attributes();
|
||||
attributes.putValue("test", "test");
|
||||
manifest.getEntries().put("spring", attributes);
|
||||
Attributes copy = urlJarManifest.getEntryAttributes(new JarEntry("spring"));
|
||||
assertThat(copy).isNotSameAs(attributes);
|
||||
attributes.clear();
|
||||
assertThat(copy.getValue("test")).isNotNull();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void supplierIsOnlyCalledOnce() throws IOException {
|
||||
ManifestSupplier supplier = mock(ManifestSupplier.class);
|
||||
UrlJarManifest urlJarManifest = new UrlJarManifest(supplier);
|
||||
urlJarManifest.get();
|
||||
urlJarManifest.get();
|
||||
then(supplier).should(times(1)).getManifest();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
|
||||
/**
|
||||
* Tests for {@link UrlNestedJarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class UrlNestedJarFileTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
private UrlNestedJarFile jarFile;
|
||||
|
||||
@Mock
|
||||
private Consumer<JarFile> closeAction;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
File file = new File(this.temp, "test.jar");
|
||||
TestJar.create(file);
|
||||
this.jarFile = new UrlNestedJarFile(file, "multi-release.jar", Runtime.version(), this.closeAction);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() throws Exception {
|
||||
this.jarFile.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenNotfoundReturnsNull() {
|
||||
assertThat(this.jarFile.getEntry("missing")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenFoundReturnsUrlJarEntry() {
|
||||
assertThat(this.jarFile.getEntry("multi-release.dat")).isInstanceOf(UrlJarEntry.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestReturnsNewCopy() throws Exception {
|
||||
Manifest manifest1 = this.jarFile.getManifest();
|
||||
Manifest manifest2 = this.jarFile.getManifest();
|
||||
assertThat(manifest1).isNotSameAs(manifest2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeCallsCloseAction() throws Exception {
|
||||
this.jarFile.close();
|
||||
then(this.closeAction).should().accept(this.jarFile);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.nested;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.Handlers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link Handler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class HandlerTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@BeforeAll
|
||||
static void registerHandlers() {
|
||||
Handlers.register();
|
||||
}
|
||||
|
||||
@Test
|
||||
void openConnectionReturnsNestedUrlConnection() throws Exception {
|
||||
URL url = new URL("nested:" + this.temp.getAbsolutePath() + "/!nested.jar");
|
||||
assertThat(url.openConnection()).isInstanceOf(NestedUrlConnection.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertUrlIsNotMalformedWhenUrlIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> Handler.assertUrlIsNotMalformed(null))
|
||||
.withMessageContaining("'url' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertUrlIsNotMalformedWhenUrlIsNotNestedThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> Handler.assertUrlIsNotMalformed("file:"))
|
||||
.withMessageContaining("must use 'nested'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertUrlIsNotMalformedWhenUrlIsMalformedThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> Handler.assertUrlIsNotMalformed("nested:bad"))
|
||||
.withMessageContaining("'path' must contain '/!'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertUrlIsNotMalformedWhenUrlIsValidDoesNotThrowException() {
|
||||
String url = "nested:" + this.temp.getAbsolutePath() + "/!nested.jar";
|
||||
assertThatNoException().isThrownBy(() -> Handler.assertUrlIsNotMalformed(url));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.nested;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.Handlers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link NestedLocation}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class NestedLocationTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@BeforeAll
|
||||
static void registerHandlers() {
|
||||
Handlers.register();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenFileIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new NestedLocation(null, "nested.jar"))
|
||||
.withMessageContaining("'file' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenNestedEntryNameIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new NestedLocation(new File("test.jar"), null))
|
||||
.withMessageContaining("'nestedEntryName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenNestedEntryNameIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new NestedLocation(new File("test.jar"), null))
|
||||
.withMessageContaining("'nestedEntryName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromUrlWhenUrlIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> NestedLocation.fromUrl(null))
|
||||
.withMessageContaining("'url' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromUrlWhenNotNestedProtocolThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> NestedLocation.fromUrl(new URL("file://test.jar")))
|
||||
.withMessageContaining("must use 'nested' protocol");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromUrlWhenNoPathThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> NestedLocation.fromUrl(new URL("nested:")))
|
||||
.withMessageContaining("'path' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromUrlWhenNoSeparatorThrowsExceptiuon() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> NestedLocation.fromUrl(new URL("nested:test.jar!nested.jar")))
|
||||
.withMessageContaining("'path' must contain '/!'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromUrlReturnsNestedLocation() throws Exception {
|
||||
File file = new File(this.temp, "test.jar");
|
||||
NestedLocation location = NestedLocation
|
||||
.fromUrl(new URL("nested:" + file.getAbsolutePath() + "/!lib/nested.jar"));
|
||||
assertThat(location.file()).isEqualTo(file);
|
||||
assertThat(location.nestedEntryName()).isEqualTo("lib/nested.jar");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.protocol.nested;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilePermission;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.Permission;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.boot.loader.net.protocol.Handlers;
|
||||
import org.springframework.boot.loader.ref.Cleaner;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.boot.loader.zip.ZipContent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link NestedUrlConnection}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class NestedUrlConnectionTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
private File jarFile;
|
||||
|
||||
private URL url;
|
||||
|
||||
@BeforeAll
|
||||
static void registerHandlers() {
|
||||
Handlers.register();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
this.jarFile = new File(this.temp, "test.jar");
|
||||
TestJar.create(this.jarFile);
|
||||
this.url = new URL("nested:" + this.jarFile.getAbsolutePath() + "/!nested.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenMalformedUrlThrowsException() throws Exception {
|
||||
URL url = new URL("nested:bad.jar");
|
||||
assertThatExceptionOfType(MalformedURLException.class).isThrownBy(() -> new NestedUrlConnection(url))
|
||||
.withMessage("'path' must contain '/!'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthWhenContentLengthMoreThanMaxIntReturnsMinusOne() {
|
||||
NestedUrlConnection connection = mock(NestedUrlConnection.class);
|
||||
given(connection.getContentLength()).willCallRealMethod();
|
||||
given(connection.getContentLengthLong()).willReturn((long) Integer.MAX_VALUE + 1);
|
||||
assertThat(connection.getContentLength()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthGetsContentLength() throws Exception {
|
||||
NestedUrlConnection connection = new NestedUrlConnection(this.url);
|
||||
try (ZipContent zipContent = ZipContent.open(this.jarFile.toPath())) {
|
||||
int expectedSize = zipContent.getEntry("nested.jar").getUncompressedSize();
|
||||
assertThat(connection.getContentLength()).isEqualTo(expectedSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentLengthLongReturnsContentLength() throws Exception {
|
||||
NestedUrlConnection connection = new NestedUrlConnection(this.url);
|
||||
try (ZipContent zipContent = ZipContent.open(this.jarFile.toPath())) {
|
||||
int expectedSize = zipContent.getEntry("nested.jar").getUncompressedSize();
|
||||
assertThat(connection.getContentLengthLong()).isEqualTo(expectedSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContentTypeReturnsJavaJar() throws Exception {
|
||||
NestedUrlConnection connection = new NestedUrlConnection(this.url);
|
||||
assertThat(connection.getContentType()).isEqualTo("x-java/jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLastModifiedReturnsFileLastModified() throws Exception {
|
||||
NestedUrlConnection connection = new NestedUrlConnection(this.url);
|
||||
assertThat(connection.getLastModified()).isEqualTo(this.jarFile.lastModified());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPermissionReturnsFilePermission() throws Exception {
|
||||
NestedUrlConnection connection = new NestedUrlConnection(this.url);
|
||||
Permission permission = connection.getPermission();
|
||||
assertThat(permission).isInstanceOf(FilePermission.class);
|
||||
assertThat(permission.getName()).isEqualTo(this.jarFile.getCanonicalPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInputStreamReturnsContentOfNestedJar() throws Exception {
|
||||
NestedUrlConnection connection = new NestedUrlConnection(this.url);
|
||||
try (InputStream actual = connection.getInputStream()) {
|
||||
try (ZipContent zipContent = ZipContent.open(this.jarFile.toPath())) {
|
||||
try (InputStream expected = zipContent.getEntry("nested.jar").openContent().asInputStream()) {
|
||||
assertThat(actual).hasSameContentAs(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputStreamCloseCleansResource() throws Exception {
|
||||
Cleaner cleaner = mock(Cleaner.class);
|
||||
Cleanable cleanable = mock(Cleanable.class);
|
||||
given(cleaner.register(any(), any())).willReturn(cleanable);
|
||||
NestedUrlConnection connection = new NestedUrlConnection(this.url, cleaner);
|
||||
connection.getInputStream().close();
|
||||
then(cleanable).should().clean();
|
||||
ArgumentCaptor<Runnable> actionCaptor = ArgumentCaptor.forClass(Runnable.class);
|
||||
then(cleaner).should().register(any(), actionCaptor.capture());
|
||||
actionCaptor.getValue().run();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.net.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link UrlDecoder}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class UrlDecoderTests {
|
||||
|
||||
@Test
|
||||
void decodeWhenBasicString() {
|
||||
assertThat(UrlDecoder.decode("a/b/C.class")).isEqualTo("a/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeWhenHasSingleByteEncodedCharacters() {
|
||||
assertThat(UrlDecoder.decode("%61/%62/%43.class")).isEqualTo("a/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeWhenHasDoubleByteEncodedCharacters() {
|
||||
assertThat(UrlDecoder.decode("%c3%a1/b/C.class")).isEqualTo("\u00e1/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeWhenHasMixtureOfEncodedAndUnencodedDoubleByteCharacters() {
|
||||
assertThat(UrlDecoder.decode("%c3%a1/b/\u00c7.class")).isEqualTo("\u00e1/b/\u00c7.class");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.ref;
|
||||
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Utility that allows tests to set a tracker on {@link DefaultCleaner}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public final class DefaultCleanerTracking {
|
||||
|
||||
private DefaultCleanerTracking() {
|
||||
}
|
||||
|
||||
public static void set(Consumer<Cleanable> tracker) {
|
||||
DefaultCleaner.tracker = tracker;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
package org.springframework.boot.loader.testsupport;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
@@ -28,11 +29,11 @@ import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
* Creates a simple test jar.
|
||||
* Support class to create or get test jars.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public abstract class TestJarCreator {
|
||||
public abstract class TestJar {
|
||||
|
||||
private static final int BASE_VERSION = 8;
|
||||
|
||||
@@ -50,15 +51,22 @@ public abstract class TestJarCreator {
|
||||
RUNTIME_VERSION = version;
|
||||
}
|
||||
|
||||
public static void createTestJar(File file) throws Exception {
|
||||
createTestJar(file, false);
|
||||
public static void create(File file) throws Exception {
|
||||
create(file, false);
|
||||
}
|
||||
|
||||
public static void createTestJar(File file, boolean unpackNested) throws Exception {
|
||||
public static void create(File file, boolean unpackNested) throws Exception {
|
||||
create(file, unpackNested, false);
|
||||
}
|
||||
|
||||
public static void create(File file, boolean unpackNested, boolean addSignatureFile) throws Exception {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
try (JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream)) {
|
||||
jarOutputStream.setComment("outer");
|
||||
writeManifest(jarOutputStream, "j1");
|
||||
if (addSignatureFile) {
|
||||
writeEntry(jarOutputStream, "META-INF/some.DSA", 0);
|
||||
}
|
||||
writeEntry(jarOutputStream, "1.dat", 1);
|
||||
writeEntry(jarOutputStream, "2.dat", 2);
|
||||
writeDirEntry(jarOutputStream, "d/");
|
||||
@@ -72,6 +80,11 @@ public abstract class TestJarCreator {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> expectedEntries() {
|
||||
return List.of("META-INF/", "META-INF/MANIFEST.MF", "1.dat", "2.dat", "d/", "d/9.dat", "special/",
|
||||
"special/\u00EB.dat", "nested.jar", "another-nested.jar", "space nested.jar", "multi-release.jar");
|
||||
}
|
||||
|
||||
private static void writeNestedEntry(String name, boolean unpackNested, JarOutputStream jarOutputStream)
|
||||
throws Exception {
|
||||
writeNestedEntry(name, unpackNested, jarOutputStream, false);
|
||||
@@ -148,4 +161,14 @@ public abstract class TestJarCreator {
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
public static File getSigned() {
|
||||
String[] entries = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
|
||||
for (String entry : entries) {
|
||||
if (entry.contains("bcprov")) {
|
||||
return new File(entry);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.util;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SystemPropertyUtils}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
class SystemPropertyUtilsTests {
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
System.setProperty("foo", "bar");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void close() {
|
||||
System.clearProperty("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVanillaPlaceholder() {
|
||||
assertThat(SystemPropertyUtils.resolvePlaceholders("${foo}")).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultValue() {
|
||||
assertThat(SystemPropertyUtils.resolvePlaceholders("${bar:foo}")).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedPlaceholder() {
|
||||
assertThat(SystemPropertyUtils.resolvePlaceholders("${bar:${spam:foo}}")).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEnvVar() {
|
||||
assertThat(SystemPropertyUtils.getProperty("lang")).isEqualTo(System.getenv("LANG"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
/**
|
||||
* Annotation that can be added to tests to assert that {@link FileChannelDataBlock} files
|
||||
* are not left open.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@ExtendWith(AssertFileChannelDataBlocksClosedExtension.class)
|
||||
public @interface AssertFileChannelDataBlocksClosed {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
||||
import org.springframework.boot.loader.ref.DefaultCleanerTracking;
|
||||
import org.springframework.boot.loader.zip.FileChannelDataBlock.Tracker;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Extension for {@link AssertFileChannelDataBlocksClosed @TrackFileChannelDataBlock}.
|
||||
*/
|
||||
class AssertFileChannelDataBlocksClosedExtension implements BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
private static OpenFilesTracker tracker = new OpenFilesTracker();
|
||||
|
||||
@Override
|
||||
public void beforeEach(ExtensionContext context) throws Exception {
|
||||
tracker.clear();
|
||||
FileChannelDataBlock.tracker = tracker;
|
||||
DefaultCleanerTracking.set(tracker::addedCleanable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterEach(ExtensionContext context) throws Exception {
|
||||
tracker.assertAllClosed();
|
||||
FileChannelDataBlock.tracker = null;
|
||||
}
|
||||
|
||||
private static class OpenFilesTracker implements Tracker {
|
||||
|
||||
private final Set<Path> paths = new LinkedHashSet<>();
|
||||
|
||||
private final List<Cleanable> cleanup = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void openedFileChannel(Path path, FileChannel fileChannel) {
|
||||
this.paths.add(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closedFileChannel(Path path, FileChannel fileChannel) {
|
||||
this.paths.remove(path);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
this.paths.clear();
|
||||
this.cleanup.clear();
|
||||
}
|
||||
|
||||
void assertAllClosed() {
|
||||
this.cleanup.forEach(Cleanable::clean);
|
||||
assertThat(this.paths).as("open paths").isEmpty();
|
||||
}
|
||||
|
||||
private void addedCleanable(Cleanable cleanable) {
|
||||
this.cleanup.add(cleanable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ByteArrayDataBlock}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ByteArrayDataBlockTests {
|
||||
|
||||
private final byte[] BYTES = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||
|
||||
@Test
|
||||
void sizeReturnsByteArrayLength() throws Exception {
|
||||
ByteArrayDataBlock dataBlock = new ByteArrayDataBlock(this.BYTES);
|
||||
assertThat(dataBlock.size()).isEqualTo(this.BYTES.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readPutsBytes() throws Exception {
|
||||
ByteArrayDataBlock dataBlock = new ByteArrayDataBlock(this.BYTES);
|
||||
ByteBuffer dst = ByteBuffer.allocate(8);
|
||||
int result = dataBlock.read(dst, 0);
|
||||
assertThat(result).isEqualTo(8);
|
||||
assertThat(dst.array()).containsExactly(this.BYTES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenLessBytesThanRemainingInBufferPutsBytes() throws Exception {
|
||||
ByteArrayDataBlock dataBlock = new ByteArrayDataBlock(this.BYTES);
|
||||
ByteBuffer dst = ByteBuffer.allocate(9);
|
||||
int result = dataBlock.read(dst, 0);
|
||||
assertThat(result).isEqualTo(8);
|
||||
assertThat(dst.array()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenLessRemainingInBufferThanLengthPutsBytes() throws Exception {
|
||||
ByteArrayDataBlock dataBlock = new ByteArrayDataBlock(this.BYTES);
|
||||
ByteBuffer dst = ByteBuffer.allocate(7);
|
||||
int result = dataBlock.read(dst, 0);
|
||||
assertThat(result).isEqualTo(7);
|
||||
assertThat(dst.array()).containsExactly(0, 1, 2, 3, 4, 5, 6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenHasPosOffsetReadsBytes() throws Exception {
|
||||
ByteArrayDataBlock dataBlock = new ByteArrayDataBlock(this.BYTES);
|
||||
ByteBuffer dst = ByteBuffer.allocate(3);
|
||||
int result = dataBlock.read(dst, 4);
|
||||
assertThat(result).isEqualTo(3);
|
||||
assertThat(dst.array()).containsExactly(4, 5, 6);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
/**
|
||||
* Tests for {@link DataBlock}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DataBlockTests {
|
||||
|
||||
@Test
|
||||
void readFullyReadsAllBytesByCallingReadMultipleTimes() throws IOException {
|
||||
DataBlock dataBlock = mock(DataBlock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
|
||||
given(dataBlock.read(any(), anyLong()))
|
||||
.will(putBytes(new byte[] { 0, 1 }, new byte[] { 2 }, new byte[] { 3, 4, 5 }));
|
||||
ByteBuffer dst = ByteBuffer.allocate(6);
|
||||
dataBlock.readFully(dst, 0);
|
||||
assertThat(dst.array()).containsExactly(0, 1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
private Answer<?> putBytes(byte[]... bytes) {
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
return (invocation) -> {
|
||||
int index = count.getAndIncrement();
|
||||
invocation.getArgument(0, ByteBuffer.class).put(bytes[index]);
|
||||
return bytes.length;
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
void readFullyWhenReadReturnsNegativeResultThrowsException() throws Exception {
|
||||
DataBlock dataBlock = mock(DataBlock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
|
||||
given(dataBlock.read(any(), anyLong())).willReturn(-1);
|
||||
ByteBuffer dst = ByteBuffer.allocate(8);
|
||||
assertThatExceptionOfType(EOFException.class).isThrownBy(() -> dataBlock.readFully(dst, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void asInputStreamReturnsDataBlockInputStream() {
|
||||
DataBlock dataBlock = mock(DataBlock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
|
||||
assertThat(dataBlock.asInputStream()).isInstanceOf(DataBlockInputStream.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.zip.FileChannelDataBlock.Tracker;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileChannelDataBlock}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class FileChannelDataBlockTests {
|
||||
|
||||
private static final byte[] CONTENT = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
File tempFile;
|
||||
|
||||
@BeforeEach
|
||||
void writeTempFile() throws IOException {
|
||||
this.tempFile = new File(this.tempDir, "content");
|
||||
Files.write(this.tempFile.toPath(), CONTENT);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void resetTracker() {
|
||||
FileChannelDataBlock.tracker = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeReturnsFileSize() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
assertThat(block.size()).isEqualTo(CONTENT.length);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readReadsFile() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(CONTENT.length);
|
||||
assertThat(block.read(buffer, 0)).isEqualTo(6);
|
||||
assertThat(buffer.array()).containsExactly(CONTENT);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDoesNotReadPastEndOfFile() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(CONTENT.length);
|
||||
assertThat(block.read(buffer, 2)).isEqualTo(4);
|
||||
assertThat(buffer.array()).containsExactly(0x02, 0x03, 0x04, 0x05, 0x0, 0x0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenPosAtSizeReturnsMinusOne() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(CONTENT.length);
|
||||
assertThat(block.read(buffer, 6)).isEqualTo(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenPosOverSizeReturnsMinusOne() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(CONTENT.length);
|
||||
assertThat(block.read(buffer, 7)).isEqualTo(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWhenPosIsNegativeThrowsException() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(CONTENT.length);
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> block.read(buffer, -1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sliceWhenOffsetIsNegativeThrowsException() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> block.slice(-1, 0))
|
||||
.withMessage("Offset must not be negative");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sliceWhenSizeIsNegativeThrowsException() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> block.slice(0, -1))
|
||||
.withMessage("Size must not be negative and must be within bounds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sliceWhenSizeIsOutOfBoundsThrowsException() throws IOException {
|
||||
try (FileChannelDataBlock block = createAndOpenBlock()) {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> block.slice(2, 5))
|
||||
.withMessage("Size must not be negative and must be within bounds");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sliceReturnsSlice() throws IOException {
|
||||
try (FileChannelDataBlock slice = createAndOpenBlock().slice(1, 4)) {
|
||||
assertThat(slice.size()).isEqualTo(4);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(4);
|
||||
assertThat(slice.read(buffer, 0)).isEqualTo(4);
|
||||
assertThat(buffer.array()).containsExactly(0x01, 0x02, 0x03, 0x04);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void openAndCloseHandleReferenceCounting() throws IOException {
|
||||
TestTracker tracker = new TestTracker();
|
||||
FileChannelDataBlock.tracker = tracker;
|
||||
FileChannelDataBlock block = createBlock();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(0);
|
||||
tracker.assertOpenCloseCounts(0, 0);
|
||||
block.open();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(1);
|
||||
tracker.assertOpenCloseCounts(1, 0);
|
||||
block.open();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(2);
|
||||
tracker.assertOpenCloseCounts(1, 0);
|
||||
block.close();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(1);
|
||||
tracker.assertOpenCloseCounts(1, 0);
|
||||
block.close();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(0);
|
||||
tracker.assertOpenCloseCounts(1, 1);
|
||||
block.open();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(1);
|
||||
tracker.assertOpenCloseCounts(2, 1);
|
||||
block.close();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(0);
|
||||
tracker.assertOpenCloseCounts(2, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void openAndCloseSliceHandleReferenceCounting() throws IOException {
|
||||
TestTracker tracker = new TestTracker();
|
||||
FileChannelDataBlock.tracker = tracker;
|
||||
FileChannelDataBlock block = createBlock();
|
||||
FileChannelDataBlock slice = block.slice(1, 4);
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(0);
|
||||
tracker.assertOpenCloseCounts(0, 0);
|
||||
block.open();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(1);
|
||||
tracker.assertOpenCloseCounts(1, 0);
|
||||
slice.open();
|
||||
assertThat(slice).extracting("channel.referenceCount").isEqualTo(2);
|
||||
tracker.assertOpenCloseCounts(1, 0);
|
||||
slice.open();
|
||||
assertThat(slice).extracting("channel.referenceCount").isEqualTo(3);
|
||||
tracker.assertOpenCloseCounts(1, 0);
|
||||
slice.close();
|
||||
assertThat(slice).extracting("channel.referenceCount").isEqualTo(2);
|
||||
tracker.assertOpenCloseCounts(1, 0);
|
||||
slice.close();
|
||||
assertThat(slice).extracting("channel.referenceCount").isEqualTo(1);
|
||||
tracker.assertOpenCloseCounts(1, 0);
|
||||
block.close();
|
||||
assertThat(block).extracting("channel.referenceCount").isEqualTo(0);
|
||||
tracker.assertOpenCloseCounts(1, 1);
|
||||
slice.open();
|
||||
assertThat(slice).extracting("channel.referenceCount").isEqualTo(1);
|
||||
tracker.assertOpenCloseCounts(2, 1);
|
||||
slice.close();
|
||||
assertThat(slice).extracting("channel.referenceCount").isEqualTo(0);
|
||||
tracker.assertOpenCloseCounts(2, 2);
|
||||
}
|
||||
|
||||
private FileChannelDataBlock createAndOpenBlock() throws IOException {
|
||||
FileChannelDataBlock block = createBlock();
|
||||
block.open();
|
||||
return block;
|
||||
}
|
||||
|
||||
private FileChannelDataBlock createBlock() throws IOException {
|
||||
return new FileChannelDataBlock(this.tempFile.toPath());
|
||||
}
|
||||
|
||||
static class TestTracker implements Tracker {
|
||||
|
||||
private int openCount;
|
||||
|
||||
private int closeCount;
|
||||
|
||||
@Override
|
||||
public void openedFileChannel(Path path, FileChannel fileChannel) {
|
||||
this.openCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closedFileChannel(Path path, FileChannel fileChannel) {
|
||||
this.closeCount++;
|
||||
}
|
||||
|
||||
void assertOpenCloseCounts(int expectedOpenCount, int expectedCloseCount) {
|
||||
assertThat(this.openCount).as("openCount").isEqualTo(expectedOpenCount);
|
||||
assertThat(this.closeCount).as("closeCount").isEqualTo(expectedCloseCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link VirtualDataBlock}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class VirtualDataBlockTests {
|
||||
|
||||
private VirtualDataBlock virtualDataBlock;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws IOException {
|
||||
List<DataBlock> subsections = new ArrayList<>();
|
||||
subsections.add(new ByteArrayDataBlock("abc".getBytes(StandardCharsets.UTF_8)));
|
||||
subsections.add(new ByteArrayDataBlock("defg".getBytes(StandardCharsets.UTF_8)));
|
||||
subsections.add(new ByteArrayDataBlock("h".getBytes(StandardCharsets.UTF_8)));
|
||||
this.virtualDataBlock = new VirtualDataBlock(subsections);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeReturnsSize() throws IOException {
|
||||
assertThat(this.virtualDataBlock.size()).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readFullyReadsAllBlocks() throws IOException {
|
||||
ByteBuffer dst = ByteBuffer.allocate((int) this.virtualDataBlock.size());
|
||||
this.virtualDataBlock.readFully(dst, 0);
|
||||
assertThat(dst.array()).containsExactly("abcdefgh".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithShortBlock() throws IOException {
|
||||
ByteBuffer dst = ByteBuffer.allocate(2);
|
||||
assertThat(this.virtualDataBlock.read(dst, 1)).isEqualTo(2);
|
||||
assertThat(dst.array()).containsExactly("bc".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithShortBlockAcrossSubsections() throws IOException {
|
||||
ByteBuffer dst = ByteBuffer.allocate(3);
|
||||
assertThat(this.virtualDataBlock.read(dst, 2)).isEqualTo(3);
|
||||
assertThat(dst.array()).containsExactly("cde".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithBigBlock() throws IOException {
|
||||
ByteBuffer dst = ByteBuffer.allocate(16);
|
||||
assertThat(this.virtualDataBlock.read(dst, 1)).isEqualTo(7);
|
||||
assertThat(dst.array()).startsWith("bcdefgh".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link VirtualZipDataBlock}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@AssertFileChannelDataBlocksClosed
|
||||
class VirtualZipDataBlockTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
private File file;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
this.file = new File(this.tempDir, "test.jar");
|
||||
TestJar.create(this.file);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createContainsValidZipContent() throws IOException {
|
||||
FileChannelDataBlock data = new FileChannelDataBlock(this.file.toPath());
|
||||
data.open();
|
||||
List<ZipCentralDirectoryFileHeaderRecord> centralRecords = new ArrayList<>();
|
||||
List<Long> centralRecordPositions = new ArrayList<>();
|
||||
ZipEndOfCentralDirectoryRecord eocd = ZipEndOfCentralDirectoryRecord.load(data).endOfCentralDirectoryRecord();
|
||||
long pos = eocd.offsetToStartOfCentralDirectory();
|
||||
for (int i = 0; i < eocd.totalNumberOfCentralDirectoryEntries(); i++) {
|
||||
ZipCentralDirectoryFileHeaderRecord centralRecord = ZipCentralDirectoryFileHeaderRecord.load(data, pos);
|
||||
String name = ZipString.readString(data, pos + ZipCentralDirectoryFileHeaderRecord.FILE_NAME_OFFSET,
|
||||
centralRecord.fileNameLength());
|
||||
if (name.endsWith(".jar")) {
|
||||
centralRecords.add(centralRecord);
|
||||
centralRecordPositions.add(pos);
|
||||
}
|
||||
pos += centralRecord.size();
|
||||
}
|
||||
NameOffsetLookups nameOffsetLookups = new NameOffsetLookups(2, centralRecords.size());
|
||||
for (int i = 0; i < centralRecords.size(); i++) {
|
||||
nameOffsetLookups.enable(i, true);
|
||||
}
|
||||
nameOffsetLookups.enable(0, true);
|
||||
File outputFile = new File(this.tempDir, "out.jar");
|
||||
try (VirtualZipDataBlock block = new VirtualZipDataBlock(data, nameOffsetLookups,
|
||||
centralRecords.toArray(ZipCentralDirectoryFileHeaderRecord[]::new),
|
||||
centralRecordPositions.stream().mapToLong(Long::longValue).toArray())) {
|
||||
try (FileOutputStream out = new FileOutputStream(outputFile)) {
|
||||
block.asInputStream().transferTo(out);
|
||||
}
|
||||
}
|
||||
try (FileSystem fileSystem = FileSystems.newFileSystem(outputFile.toPath())) {
|
||||
assertThatExceptionOfType(NoSuchFileException.class)
|
||||
.isThrownBy(() -> Files.size(fileSystem.getPath("nessted.jar")));
|
||||
assertThat(Files.size(fileSystem.getPath("sted.jar"))).isGreaterThan(0);
|
||||
assertThat(Files.size(fileSystem.getPath("other-nested.jar"))).isGreaterThan(0);
|
||||
assertThat(Files.size(fileSystem.getPath("ace nested.jar"))).isGreaterThan(0);
|
||||
assertThat(Files.size(fileSystem.getPath("lti-release.jar"))).isGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Zip64EndOfCentralDirectoryLocator}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class Zip64EndOfCentralDirectoryLocatorTests {
|
||||
|
||||
@Test
|
||||
void findReturnsRecord() throws Exception {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x50, 0x4b, 0x06, 0x07, //
|
||||
0x01, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x03, 0x00, 0x00, 0x00 }); //
|
||||
Zip64EndOfCentralDirectoryLocator eocd = Zip64EndOfCentralDirectoryLocator.find(dataBlock, 20);
|
||||
assertThat(eocd.pos()).isEqualTo(0);
|
||||
assertThat(eocd.numberOfThisDisk()).isEqualTo(1);
|
||||
assertThat(eocd.offsetToZip64EndOfCentralDirectoryRecord()).isEqualTo(2);
|
||||
assertThat(eocd.totalNumberOfDisks()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findWhenSignatureDoesNotMatchReturnsNull() throws IOException {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x51, 0x4b, 0x06, 0x07, //
|
||||
0x01, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x03, 0x00, 0x00, 0x00 }); //
|
||||
Zip64EndOfCentralDirectoryLocator eocd = Zip64EndOfCentralDirectoryLocator.find(dataBlock, 20);
|
||||
assertThat(eocd).isNull();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
|
||||
/**
|
||||
* Tests for {@link Zip64EndOfCentralDirectoryRecord}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class Zip64EndOfCentralDirectoryRecordTests {
|
||||
|
||||
@Test
|
||||
void loadLoadsData() throws Exception {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x50, 0x4b, 0x06, 0x06, //
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, 0x00, 0x00, //
|
||||
0x05, 0x00, 0x00, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); //
|
||||
Zip64EndOfCentralDirectoryLocator locator = new Zip64EndOfCentralDirectoryLocator(56, 0, 0, 0);
|
||||
Zip64EndOfCentralDirectoryRecord eocd = Zip64EndOfCentralDirectoryRecord.load(dataBlock, locator);
|
||||
assertThat(eocd.size()).isEqualTo(56);
|
||||
assertThat(eocd.sizeOfZip64EndOfCentralDirectoryRecord()).isEqualTo(1);
|
||||
assertThat(eocd.versionMadeBy()).isEqualTo((short) 2);
|
||||
assertThat(eocd.versionNeededToExtract()).isEqualTo((short) 3);
|
||||
assertThat(eocd.numberOfThisDisk()).isEqualTo(4);
|
||||
assertThat(eocd.diskWhereCentralDirectoryStarts()).isEqualTo(5);
|
||||
assertThat(eocd.numberOfCentralDirectoryEntriesOnThisDisk()).isEqualTo(6);
|
||||
assertThat(eocd.totalNumberOfCentralDirectoryEntries()).isEqualTo(7);
|
||||
assertThat(eocd.sizeOfCentralDirectory()).isEqualTo(8);
|
||||
assertThat(eocd.offsetToStartOfCentralDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenSignatureDoesNotMatchThrowsException() {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x51, 0x4b, 0x06, 0x06, //
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, 0x00, 0x00, //
|
||||
0x05, 0x00, 0x00, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
|
||||
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); //
|
||||
Zip64EndOfCentralDirectoryLocator locator = new Zip64EndOfCentralDirectoryLocator(56, 0, 0, 0);
|
||||
assertThatIOException().isThrownBy(() -> Zip64EndOfCentralDirectoryRecord.load(dataBlock, locator))
|
||||
.withMessageContaining("Zip64 'End Of Central Directory Record' not found at position");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ZipCentralDirectoryFileHeaderRecord}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ZipCentralDirectoryFileHeaderRecordTests {
|
||||
|
||||
@Test
|
||||
void loadLoadsData() throws Exception {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x50, 0x4b, 0x01, 0x02, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, //
|
||||
0x06, 0x00, //
|
||||
0x07, 0x00, 0x00, 0x00, //
|
||||
0x08, 0x00, 0x00, 0x00, //
|
||||
0x09, 0x00, 0x00, 0x00, //
|
||||
0x0A, 0x00, //
|
||||
0x0B, 0x00, //
|
||||
0x0C, 0x00, //
|
||||
0x0D, 0x00, //
|
||||
0x0E, 0x00, //
|
||||
0x0F, 0x00, 0x00, 0x00, //
|
||||
0x10, 0x00, 0x00, 0x00 }); //
|
||||
ZipCentralDirectoryFileHeaderRecord record = ZipCentralDirectoryFileHeaderRecord.load(dataBlock, 0);
|
||||
assertThat(record.versionMadeBy()).isEqualTo((short) 1);
|
||||
assertThat(record.versionNeededToExtract()).isEqualTo((short) 2);
|
||||
assertThat(record.generalPurposeBitFlag()).isEqualTo((short) 3);
|
||||
assertThat(record.compressionMethod()).isEqualTo((short) 4);
|
||||
assertThat(record.lastModFileTime()).isEqualTo((short) 5);
|
||||
assertThat(record.lastModFileDate()).isEqualTo((short) 6);
|
||||
assertThat(record.crc32()).isEqualTo(7);
|
||||
assertThat(record.compressedSize()).isEqualTo(8);
|
||||
assertThat(record.uncompressedSize()).isEqualTo(9);
|
||||
assertThat(record.fileNameLength()).isEqualTo((short) 10);
|
||||
assertThat(record.extraFieldLength()).isEqualTo((short) 11);
|
||||
assertThat(record.fileCommentLength()).isEqualTo((short) 12);
|
||||
assertThat(record.diskNumberStart()).isEqualTo((short) 13);
|
||||
assertThat(record.internalFileAttributes()).isEqualTo((short) 14);
|
||||
assertThat(record.externalFileAttributes()).isEqualTo(15);
|
||||
assertThat(record.offsetToLocalHeader()).isEqualTo(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenSignatureDoesNotMatchThrowsException() {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x51, 0x4b, 0x01, 0x02, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, //
|
||||
0x06, 0x00, //
|
||||
0x07, 0x00, 0x00, 0x00, //
|
||||
0x08, 0x00, 0x00, 0x00, //
|
||||
0x09, 0x00, 0x00, 0x00, //
|
||||
0x0A, 0x00, //
|
||||
0x0B, 0x00, //
|
||||
0x0C, 0x00, //
|
||||
0x0D, 0x00, //
|
||||
0x0E, 0x00, //
|
||||
0x0F, 0x00, 0x00, 0x00, //
|
||||
0x10, 0x00, 0x00, 0x00 }); //
|
||||
assertThatIOException().isThrownBy(() -> ZipCentralDirectoryFileHeaderRecord.load(dataBlock, 0))
|
||||
.withMessageContaining("'Central Directory File Header Record' not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeReturnsSize() {
|
||||
ZipCentralDirectoryFileHeaderRecord record = new ZipCentralDirectoryFileHeaderRecord((short) 1, (short) 2,
|
||||
(short) 3, (short) 4, (short) 5, (short) 6, 7, 8, 9, (short) 10, (short) 11, (short) 12, (short) 13,
|
||||
(short) 14, 15, 16);
|
||||
assertThat(record.size()).isEqualTo(79L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyToCopiesDataToZipEntry() throws Exception {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x50, 0x4b, 0x01, 0x02, //
|
||||
0x00, 0x00, //
|
||||
0x00, 0x00, //
|
||||
0x00, 0x00, //
|
||||
0x08, 0x00, //
|
||||
0x23, 0x74, //
|
||||
0x58, 0x36, //
|
||||
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, //
|
||||
0x01, 0x00, 0x00, 0x00, //
|
||||
0x02, 0x00, 0x00, 0x00, //
|
||||
0x01, 0x00, //
|
||||
0x01, 0x00, //
|
||||
0x01, 0x00, //
|
||||
0x00, 0x00, //
|
||||
0x00, 0x00, //
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0x61, //
|
||||
0x62, //
|
||||
0x63 }); //
|
||||
ZipCentralDirectoryFileHeaderRecord record = ZipCentralDirectoryFileHeaderRecord.load(dataBlock, 0);
|
||||
ZipEntry entry = new ZipEntry("");
|
||||
record.copyTo(dataBlock, 0, entry);
|
||||
assertThat(entry.getMethod()).isEqualTo(ZipEntry.DEFLATED);
|
||||
assertThat(entry.getTimeLocal()).hasYear(2007);
|
||||
assertThat(entry.getTime()).isEqualTo(1172356386000L);
|
||||
assertThat(entry.getCrc()).isEqualTo(0xFFFFFFFFL);
|
||||
assertThat(entry.getCompressedSize()).isEqualTo(1);
|
||||
assertThat(entry.getSize()).isEqualTo(2);
|
||||
assertThat(entry.getExtra()).containsExactly(0x62);
|
||||
assertThat(entry.getComment()).isEqualTo("c");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withFileNameLengthReturnsUpdatedInstance() {
|
||||
ZipCentralDirectoryFileHeaderRecord record = new ZipCentralDirectoryFileHeaderRecord((short) 1, (short) 2,
|
||||
(short) 3, (short) 4, (short) 5, (short) 6, 7, 8, 9, (short) 10, (short) 11, (short) 12, (short) 13,
|
||||
(short) 14, 15, 16)
|
||||
.withFileNameLength((short) 100);
|
||||
assertThat(record.versionMadeBy()).isEqualTo((short) 1);
|
||||
assertThat(record.versionNeededToExtract()).isEqualTo((short) 2);
|
||||
assertThat(record.generalPurposeBitFlag()).isEqualTo((short) 3);
|
||||
assertThat(record.compressionMethod()).isEqualTo((short) 4);
|
||||
assertThat(record.lastModFileTime()).isEqualTo((short) 5);
|
||||
assertThat(record.lastModFileDate()).isEqualTo((short) 6);
|
||||
assertThat(record.crc32()).isEqualTo(7);
|
||||
assertThat(record.compressedSize()).isEqualTo(8);
|
||||
assertThat(record.uncompressedSize()).isEqualTo(9);
|
||||
assertThat(record.fileNameLength()).isEqualTo((short) 100);
|
||||
assertThat(record.extraFieldLength()).isEqualTo((short) 11);
|
||||
assertThat(record.fileCommentLength()).isEqualTo((short) 12);
|
||||
assertThat(record.diskNumberStart()).isEqualTo((short) 13);
|
||||
assertThat(record.internalFileAttributes()).isEqualTo((short) 14);
|
||||
assertThat(record.externalFileAttributes()).isEqualTo(15);
|
||||
assertThat(record.offsetToLocalHeader()).isEqualTo(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withOffsetToLocalHeaderReturnsUpdatedInstance() {
|
||||
ZipCentralDirectoryFileHeaderRecord record = new ZipCentralDirectoryFileHeaderRecord((short) 1, (short) 2,
|
||||
(short) 3, (short) 4, (short) 5, (short) 6, 7, 8, 9, (short) 10, (short) 11, (short) 12, (short) 13,
|
||||
(short) 14, 15, 16)
|
||||
.withOffsetToLocalHeader(100);
|
||||
assertThat(record.versionMadeBy()).isEqualTo((short) 1);
|
||||
assertThat(record.versionNeededToExtract()).isEqualTo((short) 2);
|
||||
assertThat(record.generalPurposeBitFlag()).isEqualTo((short) 3);
|
||||
assertThat(record.compressionMethod()).isEqualTo((short) 4);
|
||||
assertThat(record.lastModFileTime()).isEqualTo((short) 5);
|
||||
assertThat(record.lastModFileDate()).isEqualTo((short) 6);
|
||||
assertThat(record.crc32()).isEqualTo(7);
|
||||
assertThat(record.compressedSize()).isEqualTo(8);
|
||||
assertThat(record.uncompressedSize()).isEqualTo(9);
|
||||
assertThat(record.fileNameLength()).isEqualTo((short) 10);
|
||||
assertThat(record.extraFieldLength()).isEqualTo((short) 11);
|
||||
assertThat(record.fileCommentLength()).isEqualTo((short) 12);
|
||||
assertThat(record.diskNumberStart()).isEqualTo((short) 13);
|
||||
assertThat(record.internalFileAttributes()).isEqualTo((short) 14);
|
||||
assertThat(record.externalFileAttributes()).isEqualTo(15);
|
||||
assertThat(record.offsetToLocalHeader()).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void asByteArrayReturnsByteArray() throws Exception {
|
||||
byte[] bytes = new byte[] { //
|
||||
0x50, 0x4b, 0x01, 0x02, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, //
|
||||
0x06, 0x00, //
|
||||
0x07, 0x00, 0x00, 0x00, //
|
||||
0x08, 0x00, 0x00, 0x00, //
|
||||
0x09, 0x00, 0x00, 0x00, //
|
||||
0x0A, 0x00, //
|
||||
0x0B, 0x00, //
|
||||
0x0C, 0x00, //
|
||||
0x0D, 0x00, //
|
||||
0x0E, 0x00, //
|
||||
0x0F, 0x00, 0x00, 0x00, //
|
||||
0x10, 0x00, 0x00, 0x00 };
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(bytes);
|
||||
ZipCentralDirectoryFileHeaderRecord record = ZipCentralDirectoryFileHeaderRecord.load(dataBlock, 0);
|
||||
assertThat(record.asByteArray()).containsExactly(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.ZipContent.Entry;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ZipContent}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Martin Lau
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ZipContentTests {
|
||||
|
||||
@TempDir
|
||||
File tempDir;
|
||||
|
||||
private File file;
|
||||
|
||||
private ZipContent zipContent;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
this.file = new File(this.tempDir, "test.jar");
|
||||
TestJar.create(this.file);
|
||||
this.zipContent = ZipContent.open(this.file.toPath());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
if (this.zipContent != null) {
|
||||
try {
|
||||
this.zipContent.close();
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCommentReturnsComment() {
|
||||
assertThat(this.zipContent.getComment()).isEqualTo("outer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCommentWhenClosedThrowsException() throws IOException {
|
||||
this.zipContent.close();
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.zipContent.getComment())
|
||||
.withMessage("Zip content closed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenPresentReturnsEntry() {
|
||||
Entry entry = this.zipContent.getEntry("1.dat");
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.getName()).isEqualTo("1.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenMissingReturnsNull() {
|
||||
assertThat(this.zipContent.getEntry("missing.dat")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWithPrefixWhenPresentReturnsEntry() {
|
||||
Entry entry = this.zipContent.getEntry("1", ".dat");
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.getName()).isEqualTo("1.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWithLongPrefixWhenNameIsShorterReturnsNull() {
|
||||
Entry entry = this.zipContent.getEntry("iamaverylongprefixandiwontfindanything", "1.dat");
|
||||
assertThat(entry).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWithPrefixWhenMissingReturnsNull() {
|
||||
assertThat(this.zipContent.getEntry("miss", "ing.dat")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryWhenUsingSlashesIsCompatibleWithZipFile() throws IOException {
|
||||
try (ZipFile zipFile = new ZipFile(this.file)) {
|
||||
assertThat(zipFile.getEntry("META-INF").getName()).isEqualTo("META-INF/");
|
||||
assertThat(this.zipContent.getEntry("META-INF").getName()).isEqualTo("META-INF/");
|
||||
assertThat(zipFile.getEntry("META-INF/").getName()).isEqualTo("META-INF/");
|
||||
assertThat(this.zipContent.getEntry("META-INF/").getName()).isEqualTo("META-INF/");
|
||||
assertThat(zipFile.getEntry("d/9.dat").getName()).isEqualTo("d/9.dat");
|
||||
assertThat(this.zipContent.getEntry("d/9.dat").getName()).isEqualTo("d/9.dat");
|
||||
assertThat(zipFile.getEntry("d/9.dat/")).isNull();
|
||||
assertThat(this.zipContent.getEntry("d/9.dat/")).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getManifestEntry() throws Exception {
|
||||
Entry entry = this.zipContent.getEntry("META-INF/MANIFEST.MF");
|
||||
try (CloseableDataBlock dataBlock = entry.openContent()) {
|
||||
Manifest manifest = new Manifest(asInflaterInputStream(dataBlock));
|
||||
assertThat(manifest.getMainAttributes().getValue("Built-By")).isEqualTo("j1");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEntryAsCreatesCompatibleEntries() throws IOException {
|
||||
try (ZipFile zipFile = new ZipFile(this.file)) {
|
||||
Iterator<? extends ZipEntry> expected = zipFile.entries().asIterator();
|
||||
int i = 0;
|
||||
while (expected.hasNext()) {
|
||||
Entry actual = this.zipContent.getEntry(i++);
|
||||
assertThatFieldsAreEqual(actual.as(ZipEntry::new), expected.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertThatFieldsAreEqual(ZipEntry actual, ZipEntry expected) {
|
||||
assertThat(actual.getName()).isEqualTo(expected.getName());
|
||||
assertThat(actual.getTime()).isEqualTo(expected.getTime());
|
||||
assertThat(actual.getLastModifiedTime()).isEqualTo(expected.getLastModifiedTime());
|
||||
assertThat(actual.getLastAccessTime()).isEqualTo(expected.getLastAccessTime());
|
||||
assertThat(actual.getCreationTime()).isEqualTo(expected.getCreationTime());
|
||||
assertThat(actual.getSize()).isEqualTo(expected.getSize());
|
||||
assertThat(actual.getCompressedSize()).isEqualTo(expected.getCompressedSize());
|
||||
assertThat(actual.getCrc()).isEqualTo(expected.getCrc());
|
||||
assertThat(actual.getMethod()).isEqualTo(expected.getMethod());
|
||||
assertThat(actual.getExtra()).isEqualTo(expected.getExtra());
|
||||
assertThat(actual.getComment()).isEqualTo(expected.getComment());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeReturnsNumberOfEntries() {
|
||||
assertThat(this.zipContent.size()).isEqualTo(12);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedJarFileReturnsNestedJar() throws IOException {
|
||||
try (ZipContent nested = ZipContent.open(this.file.toPath(), "nested.jar")) {
|
||||
assertThat(nested.size()).isEqualTo(5);
|
||||
assertThat(nested.getComment()).isEqualTo("nested");
|
||||
assertThat(nested.size()).isEqualTo(5);
|
||||
assertThat(nested.getEntry(0).getName()).isEqualTo("META-INF/");
|
||||
assertThat(nested.getEntry(1).getName()).isEqualTo("META-INF/MANIFEST.MF");
|
||||
assertThat(nested.getEntry(2).getName()).isEqualTo("3.dat");
|
||||
assertThat(nested.getEntry(3).getName()).isEqualTo("4.dat");
|
||||
assertThat(nested.getEntry(4).getName()).isEqualTo("\u00E4.dat");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedJarFileWhenNameEndsInSlashThrowsException() {
|
||||
assertThatIOException().isThrownBy(() -> ZipContent.open(this.file.toPath(), "nested.jar/"))
|
||||
.withMessageStartingWith("Nested entry 'nested.jar/' not found in container zip");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedDirectoryReturnsNestedJar() throws IOException {
|
||||
try (ZipContent nested = ZipContent.open(this.file.toPath(), "d/")) {
|
||||
assertThat(nested.size()).isEqualTo(3);
|
||||
assertThat(nested.getEntry("9.dat")).isNotNull();
|
||||
assertThat(nested.getEntry(0).getName()).isEqualTo("META-INF/");
|
||||
assertThat(nested.getEntry(1).getName()).isEqualTo("META-INF/MANIFEST.MF");
|
||||
assertThat(nested.getEntry(2).getName()).isEqualTo("9.dat");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedDirectoryWhenNotEndingInSlashThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ZipContent.open(this.file.toPath(), "d"))
|
||||
.withMessage("Nested entry name must end with '/'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDataWhenNestedDirectoryReturnsVirtualZipDataBlock() throws IOException {
|
||||
try (ZipContent nested = ZipContent.open(this.file.toPath(), "d/")) {
|
||||
File file = new File(this.tempDir, "included.zip");
|
||||
write(file, nested.openRawZipData());
|
||||
try (ZipFile loadedZipFile = new ZipFile(file)) {
|
||||
assertThat(loadedZipFile.size()).isEqualTo(3);
|
||||
assertThat(loadedZipFile.stream().map(ZipEntry::getName)).containsExactly("META-INF/",
|
||||
"META-INF/MANIFEST.MF", "9.dat");
|
||||
assertThat(loadedZipFile.getEntry("9.dat")).isNotNull();
|
||||
try (InputStream in = loadedZipFile.getInputStream(loadedZipFile.getEntry("9.dat"))) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
in.transferTo(out);
|
||||
assertThat(out.toByteArray()).containsExactly(0x09);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenHasFrontMatterOpensZip() throws IOException {
|
||||
File fileWithFrontMatter = new File(this.tempDir, "withfrontmatter.jar");
|
||||
FileOutputStream outputStream = new FileOutputStream(fileWithFrontMatter);
|
||||
StreamUtils.copy("#/bin/bash", Charset.defaultCharset(), outputStream);
|
||||
FileCopyUtils.copy(new FileInputStream(this.file), outputStream);
|
||||
try (ZipContent zip = ZipContent.open(fileWithFrontMatter.toPath())) {
|
||||
assertThat(zip.size()).isEqualTo(12);
|
||||
assertThat(zip.getEntry(0).getName()).isEqualTo("META-INF/");
|
||||
assertThat(zip.getEntry(1).getName()).isEqualTo("META-INF/MANIFEST.MF");
|
||||
assertThat(zip.getEntry(2).getName()).isEqualTo("1.dat");
|
||||
assertThat(zip.getEntry(3).getName()).isEqualTo("2.dat");
|
||||
assertThat(zip.getEntry(4).getName()).isEqualTo("d/");
|
||||
assertThat(zip.getEntry(5).getName()).isEqualTo("d/9.dat");
|
||||
assertThat(zip.getEntry(6).getName()).isEqualTo("special/");
|
||||
assertThat(zip.getEntry(7).getName()).isEqualTo("special/\u00EB.dat");
|
||||
assertThat(zip.getEntry(8).getName()).isEqualTo("nested.jar");
|
||||
assertThat(zip.getEntry(9).getName()).isEqualTo("another-nested.jar");
|
||||
assertThat(zip.getEntry(10).getName()).isEqualTo("space nested.jar");
|
||||
assertThat(zip.getEntry(11).getName()).isEqualTo("multi-release.jar");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void openWhenZip64ThatExceedsZipEntryLimitOpensZip() throws Exception {
|
||||
File zip64File = new File(this.tempDir, "zip64.zip");
|
||||
FileCopyUtils.copy(zip64Bytes(), zip64File);
|
||||
try (ZipContent zip64Content = ZipContent.open(zip64File.toPath())) {
|
||||
assertThat(zip64Content.size()).isEqualTo(65537);
|
||||
for (int i = 0; i < zip64Content.size(); i++) {
|
||||
Entry entry = zip64Content.getEntry(i);
|
||||
try (CloseableDataBlock dataBlock = entry.openContent()) {
|
||||
assertThat(asInflaterInputStream(dataBlock)).hasContent("Entry " + (i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void openWhenZip64ThatExceedsZipSizeLimitOpensZip() throws Exception {
|
||||
Assumptions.assumeTrue(this.tempDir.getFreeSpace() > 6 * 1024 * 1024 * 1024, "Insufficient disk space");
|
||||
File zip64File = new File(this.tempDir, "zip64.zip");
|
||||
File entryFile = new File(this.tempDir, "entry.dat");
|
||||
CRC32 crc32 = new CRC32();
|
||||
try (FileOutputStream entryOut = new FileOutputStream(entryFile)) {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
new Random().nextBytes(data);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
entryOut.write(data);
|
||||
crc32.update(data);
|
||||
}
|
||||
}
|
||||
try (ZipOutputStream zipOutput = new ZipOutputStream(new FileOutputStream(zip64File))) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
ZipEntry storedEntry = new ZipEntry("huge-" + i);
|
||||
storedEntry.setSize(entryFile.length());
|
||||
storedEntry.setCompressedSize(entryFile.length());
|
||||
storedEntry.setCrc(crc32.getValue());
|
||||
storedEntry.setMethod(ZipEntry.STORED);
|
||||
zipOutput.putNextEntry(storedEntry);
|
||||
try (FileInputStream entryIn = new FileInputStream(entryFile)) {
|
||||
StreamUtils.copy(entryIn, zipOutput);
|
||||
}
|
||||
zipOutput.closeEntry();
|
||||
}
|
||||
}
|
||||
try (ZipContent zip64Content = ZipContent.open(zip64File.toPath())) {
|
||||
assertThat(zip64Content.size()).isEqualTo(6);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedZip64CanBeRead() throws Exception {
|
||||
File containerFile = new File(this.tempDir, "outer.zip");
|
||||
try (ZipOutputStream jarOutput = new ZipOutputStream(new FileOutputStream(containerFile))) {
|
||||
ZipEntry nestedEntry = new ZipEntry("nested-zip64.zip");
|
||||
byte[] contents = zip64Bytes();
|
||||
nestedEntry.setSize(contents.length);
|
||||
nestedEntry.setCompressedSize(contents.length);
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(contents);
|
||||
nestedEntry.setCrc(crc32.getValue());
|
||||
nestedEntry.setMethod(ZipEntry.STORED);
|
||||
jarOutput.putNextEntry(nestedEntry);
|
||||
jarOutput.write(contents);
|
||||
jarOutput.closeEntry();
|
||||
}
|
||||
try (ZipContent nestedZip = ZipContent.open(containerFile.toPath(), "nested-zip64.zip")) {
|
||||
assertThat(nestedZip.size()).isEqualTo(65537);
|
||||
for (int i = 0; i < nestedZip.size(); i++) {
|
||||
Entry entry = nestedZip.getEntry(i);
|
||||
try (CloseableDataBlock content = entry.openContent()) {
|
||||
assertThat(asInflaterInputStream(content)).hasContent("Entry " + (i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] zip64Bytes() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOutput = new ZipOutputStream(bytes);
|
||||
for (int i = 0; i < 65537; i++) {
|
||||
zipOutput.putNextEntry(new ZipEntry(i + ".dat"));
|
||||
zipOutput.write(("Entry " + (i + 1)).getBytes(StandardCharsets.UTF_8));
|
||||
zipOutput.closeEntry();
|
||||
}
|
||||
zipOutput.close();
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
void entryWithEpochTimeOfZeroShouldNotFail() throws Exception {
|
||||
File file = createZipFileWithEpochTimeOfZero();
|
||||
try (ZipContent zip = ZipContent.open(file.toPath())) {
|
||||
ZipEntry entry = zip.getEntry(0).as(ZipEntry::new);
|
||||
assertThat(entry.getLastModifiedTime().toInstant()).isEqualTo(Instant.EPOCH);
|
||||
assertThat(entry.getName()).isEqualTo("1.dat");
|
||||
}
|
||||
}
|
||||
|
||||
private File createZipFileWithEpochTimeOfZero() throws Exception {
|
||||
File file = new File(this.tempDir, "temp.zip");
|
||||
String comment = "outer";
|
||||
try (ZipOutputStream zipOutput = new ZipOutputStream(new FileOutputStream(file))) {
|
||||
zipOutput.setComment(comment);
|
||||
ZipEntry entry = new ZipEntry("1.dat");
|
||||
entry.setLastModifiedTime(FileTime.from(Instant.EPOCH));
|
||||
zipOutput.putNextEntry(entry);
|
||||
zipOutput.write(new byte[] { (byte) 1 });
|
||||
zipOutput.closeEntry();
|
||||
}
|
||||
ByteBuffer data = ByteBuffer.wrap(Files.readAllBytes(file.toPath()));
|
||||
data.order(ByteOrder.LITTLE_ENDIAN);
|
||||
int endOfCentralDirectoryRecordPos = data.remaining() - ZipFile.ENDHDR - comment.getBytes().length;
|
||||
data.position(endOfCentralDirectoryRecordPos + ZipFile.ENDOFF);
|
||||
int startOfCentralDirectoryOffset = data.getInt();
|
||||
data.position(startOfCentralDirectoryOffset + ZipFile.CENOFF);
|
||||
int localHeaderPosition = data.getInt();
|
||||
writeTimeBlock(data.array(), startOfCentralDirectoryOffset + ZipFile.CENTIM, 0);
|
||||
writeTimeBlock(data.array(), localHeaderPosition + ZipFile.LOCTIM, 0);
|
||||
File zerotimedFile = new File(this.tempDir, "zerotimed.zip");
|
||||
Files.write(zerotimedFile.toPath(), data.array());
|
||||
return zerotimedFile;
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInfoReturnsComputedInfo() {
|
||||
ZipInfo info = this.zipContent.getInfo(ZipInfo.class, ZipInfo::get);
|
||||
assertThat(info.size()).isEqualTo(12);
|
||||
}
|
||||
|
||||
private static void writeTimeBlock(byte[] data, int pos, int value) {
|
||||
data[pos] = (byte) (value & 0xff);
|
||||
data[pos + 1] = (byte) ((value >> 8) & 0xff);
|
||||
data[pos + 2] = (byte) ((value >> 16) & 0xff);
|
||||
data[pos + 3] = (byte) ((value >> 24) & 0xff);
|
||||
}
|
||||
|
||||
private InputStream asInflaterInputStream(DataBlock dataBlock) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate((int) dataBlock.size() + 1);
|
||||
buffer.limit(buffer.limit() - 1);
|
||||
dataBlock.readFully(buffer, 0);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(buffer.array());
|
||||
return new InflaterInputStream(in, new Inflater(true));
|
||||
}
|
||||
|
||||
private void write(File file, CloseableDataBlock dataBlock) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate((int) dataBlock.size());
|
||||
dataBlock.readFully(buffer, 0);
|
||||
Files.write(file.toPath(), buffer.array());
|
||||
dataBlock.close();
|
||||
}
|
||||
|
||||
private static class ZipInfo {
|
||||
|
||||
private int size;
|
||||
|
||||
ZipInfo(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
int size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
static ZipInfo get(ZipContent content) {
|
||||
return new ZipInfo(content.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ZipEndOfCentralDirectoryRecord}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ZipEndOfCentralDirectoryRecordTests {
|
||||
|
||||
@Test
|
||||
void loadLocatesAndLoadsData() throws Exception {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x50, 0x4b, 0x05, 0x06, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, 0x00, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00 }); //
|
||||
ZipEndOfCentralDirectoryRecord.Located located = ZipEndOfCentralDirectoryRecord.load(dataBlock);
|
||||
assertThat(located.pos()).isEqualTo(0L);
|
||||
ZipEndOfCentralDirectoryRecord record = located.endOfCentralDirectoryRecord();
|
||||
assertThat(record.numberOfThisDisk()).isEqualTo((short) 1);
|
||||
assertThat(record.diskWhereCentralDirectoryStarts()).isEqualTo((short) 2);
|
||||
assertThat(record.numberOfCentralDirectoryEntriesOnThisDisk()).isEqualTo((short) 3);
|
||||
assertThat(record.totalNumberOfCentralDirectoryEntries()).isEqualTo((short) 4);
|
||||
assertThat(record.sizeOfCentralDirectory()).isEqualTo(5);
|
||||
assertThat(record.offsetToStartOfCentralDirectory()).isEqualTo(6);
|
||||
assertThat(record.commentLength()).isEqualTo((short) 7);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenMultipleBuffersBackLoadsData() throws Exception {
|
||||
byte[] bytes = new byte[ZipEndOfCentralDirectoryRecord.BUFFER_SIZE * 4];
|
||||
byte[] data = new byte[] { //
|
||||
0x50, 0x4b, 0x05, 0x06, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, 0x00, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00 }; //
|
||||
System.arraycopy(data, 0, bytes, 4, data.length);
|
||||
ZipEndOfCentralDirectoryRecord.Located located = ZipEndOfCentralDirectoryRecord
|
||||
.load(new ByteArrayDataBlock(bytes));
|
||||
assertThat(located.pos()).isEqualTo(4L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenSignatureDoesNotMatchThrowsException() {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x51, 0x4b, 0x05, 0x06, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, 0x00, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00 }); //
|
||||
assertThatIOException().isThrownBy(() -> ZipEndOfCentralDirectoryRecord.load(dataBlock))
|
||||
.withMessageContaining("'End Of Central Directory Record' not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void asByteArrayReturnsByteArray() throws Exception {
|
||||
byte[] bytes = new byte[] { //
|
||||
0x50, 0x4b, 0x05, 0x06, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, 0x00, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00 }; //
|
||||
ZipEndOfCentralDirectoryRecord.Located located = ZipEndOfCentralDirectoryRecord
|
||||
.load(new ByteArrayDataBlock(bytes));
|
||||
assertThat(located.endOfCentralDirectoryRecord().asByteArray()).isEqualTo(bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeReturnsSize() {
|
||||
ZipEndOfCentralDirectoryRecord record = new ZipEndOfCentralDirectoryRecord((short) 1, (short) 2, (short) 3,
|
||||
(short) 4, 5, 6, (short) 7);
|
||||
assertThat(record.size()).isEqualTo(29L);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ZipLocalFileHeaderRecord}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ZipLocalFileHeaderRecordTests {
|
||||
|
||||
@Test
|
||||
void loadLoadsData() throws Exception {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x50, 0x4b, 0x03, 0x04, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00, 0x00, 0x00, //
|
||||
0x08, 0x00, 0x00, 0x00, //
|
||||
0x09, 0x00, //
|
||||
0x0A, 0x00 }); //
|
||||
ZipLocalFileHeaderRecord record = ZipLocalFileHeaderRecord.load(dataBlock, 0);
|
||||
assertThat(record.versionNeededToExtract()).isEqualTo((short) 1);
|
||||
assertThat(record.generalPurposeBitFlag()).isEqualTo((short) 2);
|
||||
assertThat(record.compressionMethod()).isEqualTo((short) 3);
|
||||
assertThat(record.lastModFileTime()).isEqualTo((short) 4);
|
||||
assertThat(record.lastModFileDate()).isEqualTo((short) 5);
|
||||
assertThat(record.crc32()).isEqualTo(6);
|
||||
assertThat(record.compressedSize()).isEqualTo(7);
|
||||
assertThat(record.uncompressedSize()).isEqualTo(8);
|
||||
assertThat(record.fileNameLength()).isEqualTo((short) 9);
|
||||
assertThat(record.extraFieldLength()).isEqualTo((short) 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenSignatureDoesNotMatchThrowsException() {
|
||||
DataBlock dataBlock = new ByteArrayDataBlock(new byte[] { //
|
||||
0x51, 0x4b, 0x03, 0x04, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00, 0x00, 0x00, //
|
||||
0x08, 0x00, 0x00, 0x00, //
|
||||
0x09, 0x00, //
|
||||
0x0A, 0x00 }); //
|
||||
assertThatIOException().isThrownBy(() -> ZipLocalFileHeaderRecord.load(dataBlock, 0))
|
||||
.withMessageContaining("'Local File Header Record' not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeReturnsSize() {
|
||||
ZipLocalFileHeaderRecord record = new ZipLocalFileHeaderRecord((short) 1, (short) 2, (short) 3, (short) 4,
|
||||
(short) 5, 6, 7, 8, (short) 9, (short) 10);
|
||||
assertThat(record.size()).isEqualTo(49L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withExtraFieldLengthReturnsUpdatedInstance() {
|
||||
ZipLocalFileHeaderRecord record = new ZipLocalFileHeaderRecord((short) 1, (short) 2, (short) 3, (short) 4,
|
||||
(short) 5, 6, 7, 8, (short) 9, (short) 10)
|
||||
.withExtraFieldLength((short) 100);
|
||||
assertThat(record.extraFieldLength()).isEqualTo((short) 100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withFileNameLengthReturnsUpdatedInstance() {
|
||||
ZipLocalFileHeaderRecord record = new ZipLocalFileHeaderRecord((short) 1, (short) 2, (short) 3, (short) 4,
|
||||
(short) 5, 6, 7, 8, (short) 9, (short) 10)
|
||||
.withFileNameLength((short) 100);
|
||||
assertThat(record.fileNameLength()).isEqualTo((short) 100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void asByteArrayReturnsByteArray() throws Exception {
|
||||
byte[] bytes = new byte[] { //
|
||||
0x50, 0x4b, 0x03, 0x04, //
|
||||
0x01, 0x00, //
|
||||
0x02, 0x00, //
|
||||
0x03, 0x00, //
|
||||
0x04, 0x00, //
|
||||
0x05, 0x00, //
|
||||
0x06, 0x00, 0x00, 0x00, //
|
||||
0x07, 0x00, 0x00, 0x00, //
|
||||
0x08, 0x00, 0x00, 0x00, //
|
||||
0x09, 0x00, //
|
||||
0x0A, 0x00 }; //
|
||||
ZipLocalFileHeaderRecord record = ZipLocalFileHeaderRecord.load(new ByteArrayDataBlock(bytes), 0);
|
||||
assertThat(record.asByteArray()).isEqualTo(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.zip;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.assertj.core.api.AbstractBooleanAssert;
|
||||
import org.assertj.core.api.AbstractIntegerAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ZipString}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class ZipStringTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void hashGeneratesCorrectHashCode(HashSourceType sourceType) throws Exception {
|
||||
testHash(sourceType, true, "abcABC123xyz!");
|
||||
testHash(sourceType, false, "abcABC123xyz!");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void hashWhenHasSpecialCharsGeneratesCorrectHashCode(HashSourceType sourceType) throws Exception {
|
||||
testHash(sourceType, true, "special/\u00EB.dat");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void hashWhenHasCyrillicCharsGeneratesCorrectHashCode(HashSourceType sourceType) throws Exception {
|
||||
testHash(sourceType, true, "\u0432\u0435\u0441\u043D\u0430");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void hashWhenHasEmojiGeneratesCorrectHashCode(HashSourceType sourceType) throws Exception {
|
||||
testHash(sourceType, true, "\ud83d\udca9");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void hashWhenOnlyDifferenceIsEndSlashGeneratesSameHashCode(HashSourceType sourceType) throws Exception {
|
||||
testHash(sourceType, "", true, "/".hashCode());
|
||||
testHash(sourceType, "/", true, "/".hashCode());
|
||||
testHash(sourceType, "a/b", true, "a/b/".hashCode());
|
||||
testHash(sourceType, "a/b/", true, "a/b/".hashCode());
|
||||
}
|
||||
|
||||
void testHash(HashSourceType sourceType, boolean addSlash, String source) throws Exception {
|
||||
String expected = (addSlash && !source.endsWith("/")) ? source + "/" : source;
|
||||
testHash(sourceType, source, addSlash, expected.hashCode());
|
||||
}
|
||||
|
||||
void testHash(HashSourceType sourceType, String source, boolean addEndSlash, int expected) throws Exception {
|
||||
switch (sourceType) {
|
||||
case STRING -> {
|
||||
assertThat(ZipString.hash(source, addEndSlash)).isEqualTo(expected);
|
||||
}
|
||||
case CHAR_SEQUENCE -> {
|
||||
CharSequence charSequence = new StringBuilder(source);
|
||||
assertThat(ZipString.hash(charSequence, addEndSlash)).isEqualTo(expected);
|
||||
}
|
||||
case DATA_BLOCK -> {
|
||||
ByteArrayDataBlock dataBlock = new ByteArrayDataBlock(source.getBytes(StandardCharsets.UTF_8));
|
||||
assertThat(ZipString.hash(null, dataBlock, 0, (int) dataBlock.size(), addEndSlash)).isEqualTo(expected);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenExactMatchReturnsTrue() throws Exception {
|
||||
assertMatches("one/two/three", "one/two/three", false).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenNotMatchWithSameLengthReturnsFalse() throws Exception {
|
||||
assertMatches("one/two/three", "one/too/three", false).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenExactMatchWithSpecialCharsReturnsTrue() throws Exception {
|
||||
assertMatches("special/\u00EB.dat", "special/\u00EB.dat", false).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenExactMatchWithCyrillicCharsReturnsTrue() throws Exception {
|
||||
assertMatches("\u0432\u0435\u0441\u043D\u0430", "\u0432\u0435\u0441\u043D\u0430", false).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenNoMatchWithCyrillicCharsReturnsFalse() throws Exception {
|
||||
assertMatches("\u0432\u0435\u0441\u043D\u0430", "\u0432\u0435\u0441\u043D\u043D", false).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenExactMatchWithEmojiCharsReturnsTrue() throws Exception {
|
||||
assertMatches("\ud83d\udca9", "\ud83d\udca9", false).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWithAddSlash() throws Exception {
|
||||
assertMatches("META-INF/MANFIFEST.MF", "META-INF/MANFIFEST.MF", true).isTrue();
|
||||
assertMatches("one/two/three/", "one/two/three", true).isTrue();
|
||||
assertMatches("one/two/three", "one/two/three/", true).isFalse();
|
||||
assertMatches("one/two/three/", "one/too/three", true).isFalse();
|
||||
assertMatches("one/two/three", "one/too/three/", true).isFalse();
|
||||
assertMatches("one/two/three//", "one/two/three", true).isFalse();
|
||||
assertMatches("one/two/three", "one/two/three//", true).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenDataBlockShorterThenCharSequenceReturnsFalse() throws Exception {
|
||||
assertMatches("one/two/thre", "one/two/three", false).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenCharSequenceShorterThanDataBlockReturnsFalse() throws Exception {
|
||||
assertMatches("one/two/three", "one/two/thre", false).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithWhenStartsWith() throws Exception {
|
||||
assertStartsWith("one/two", "one/").isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithWhenExact() throws Exception {
|
||||
assertStartsWith("one/", "one/").isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithWhenTooShort() throws Exception {
|
||||
assertStartsWith("one/two", "one/two/three/").isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWithWhenDoesNotStartWith() throws Exception {
|
||||
assertStartsWith("one/three/", "one/two/").isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void zipStringWhenMultiCodePointAtBufferBoundary() throws Exception {
|
||||
StringBuilder source = new StringBuilder();
|
||||
for (int i = 0; i < ZipString.BUFFER_SIZE - 1; i++) {
|
||||
source.append("A");
|
||||
}
|
||||
source.append("\u1EFF");
|
||||
String charSequence = source.toString();
|
||||
source.append("suffix");
|
||||
assertStartsWith(source.toString(), charSequence);
|
||||
}
|
||||
|
||||
private AbstractBooleanAssert<?> assertMatches(String source, CharSequence charSequence, boolean addSlash)
|
||||
throws Exception {
|
||||
ByteArrayDataBlock dataBlock = new ByteArrayDataBlock(source.getBytes(StandardCharsets.UTF_8));
|
||||
return assertThat(ZipString.matches(null, dataBlock, 0, (int) dataBlock.size(), charSequence, addSlash));
|
||||
}
|
||||
|
||||
private AbstractIntegerAssert<?> assertStartsWith(String source, CharSequence charSequence) throws IOException {
|
||||
ByteArrayDataBlock dataBlock = new ByteArrayDataBlock(source.getBytes(StandardCharsets.UTF_8));
|
||||
return assertThat(ZipString.startsWith(null, dataBlock, 0, (int) dataBlock.size(), charSequence));
|
||||
}
|
||||
|
||||
enum HashSourceType {
|
||||
|
||||
STRING, CHAR_SEQUENCE, DATA_BLOCK
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user