Allow prefixed bytes with fat JARs
Update `spring-boot-loader` ZIP processing code to support prefixed bytes within the fat jar. This technique allows a bash script to be embedded at the start of the JAR whilst still allowing `java -jar` execution. Fixes gh-1073
This commit is contained in:
parent
a374929c90
commit
54dc46f777
|
|
@ -87,6 +87,20 @@ class CentralDirectoryEndRecord {
|
||||||
return this.size == MINIMUM_SIZE + commentLength;
|
return this.size == MINIMUM_SIZE + commentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the location in the data that the archive actually starts. For most files
|
||||||
|
* the archive data will start at 0, however, it is possible to have prefixed bytes
|
||||||
|
* (often used for startup scripts) at the beginning of the data.
|
||||||
|
* @param data the source data
|
||||||
|
* @return the offset within the data where the archive begins
|
||||||
|
*/
|
||||||
|
public long getStartOfArchive(RandomAccessData data) {
|
||||||
|
long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4);
|
||||||
|
long specifiedOffset = Bytes.littleEndianValue(this.block, this.offset + 16, 4);
|
||||||
|
long actualOffset = data.getSize() - this.size - length;
|
||||||
|
return actualOffset - specifiedOffset;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the bytes of the "Central directory" based on the offset indicated in this
|
* Return the bytes of the "Central directory" based on the offset indicated in this
|
||||||
* record.
|
* record.
|
||||||
|
|
|
||||||
|
|
@ -119,15 +119,25 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
|
||||||
private JarFile(RandomAccessDataFile rootFile, String name, RandomAccessData data,
|
private JarFile(RandomAccessDataFile rootFile, String name, RandomAccessData data,
|
||||||
JarEntryFilter... filters) throws IOException {
|
JarEntryFilter... filters) throws IOException {
|
||||||
super(rootFile.getFile());
|
super(rootFile.getFile());
|
||||||
|
CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data);
|
||||||
|
this.data = getArchiveData(endRecord, data);
|
||||||
this.rootFile = rootFile;
|
this.rootFile = rootFile;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.data = data;
|
|
||||||
this.size = data.getSize();
|
this.size = data.getSize();
|
||||||
loadJarEntries(filters);
|
loadJarEntries(endRecord, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadJarEntries(JarEntryFilter[] filters) throws IOException {
|
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord,
|
||||||
CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(this.data);
|
RandomAccessData data) {
|
||||||
|
long offset = endRecord.getStartOfArchive(data);
|
||||||
|
if (offset == 0) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return data.getSubsection(offset, data.getSize() - offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadJarEntries(CentralDirectoryEndRecord endRecord,
|
||||||
|
JarEntryFilter[] filters) throws IOException {
|
||||||
RandomAccessData centralDirectory = endRecord.getCentralDirectory(this.data);
|
RandomAccessData centralDirectory = endRecord.getCentralDirectory(this.data);
|
||||||
int numberOfRecords = endRecord.getNumberOfRecords();
|
int numberOfRecords = endRecord.getNumberOfRecords();
|
||||||
this.entries = new ArrayList<JarEntryData>(numberOfRecords);
|
this.entries = new ArrayList<JarEntryData>(numberOfRecords);
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,14 @@
|
||||||
package org.springframework.boot.loader.jar;
|
package org.springframework.boot.loader.jar;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
|
|
@ -35,6 +38,8 @@ import org.junit.rules.TemporaryFolder;
|
||||||
import org.springframework.boot.loader.TestJarCreator;
|
import org.springframework.boot.loader.TestJarCreator;
|
||||||
import org.springframework.boot.loader.data.RandomAccessDataFile;
|
import org.springframework.boot.loader.data.RandomAccessDataFile;
|
||||||
import org.springframework.boot.loader.util.AsciiBytes;
|
import org.springframework.boot.loader.util.AsciiBytes;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
|
|
@ -389,4 +394,18 @@ public class JarFileTests {
|
||||||
}
|
}
|
||||||
jarFile.close();
|
jarFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jarFileWithScriptAtTheStart() throws Exception {
|
||||||
|
File file = this.temporaryFolder.newFile();
|
||||||
|
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 = new JarFile(file);
|
||||||
|
// Call some other tests to verify
|
||||||
|
getEntries();
|
||||||
|
getNestedJarFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue