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:
Phillip Webb 2014-06-13 08:58:05 -07:00
parent a374929c90
commit 54dc46f777
3 changed files with 47 additions and 4 deletions

View File

@ -87,6 +87,20 @@ class CentralDirectoryEndRecord {
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
* record.

View File

@ -119,15 +119,25 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<JarEntryD
private JarFile(RandomAccessDataFile rootFile, String name, RandomAccessData data,
JarEntryFilter... filters) throws IOException {
super(rootFile.getFile());
CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data);
this.data = getArchiveData(endRecord, data);
this.rootFile = rootFile;
this.name = name;
this.data = data;
this.size = data.getSize();
loadJarEntries(filters);
loadJarEntries(endRecord, filters);
}
private void loadJarEntries(JarEntryFilter[] filters) throws IOException {
CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(this.data);
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord,
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);
int numberOfRecords = endRecord.getNumberOfRecords();
this.entries = new ArrayList<JarEntryData>(numberOfRecords);

View File

@ -17,11 +17,14 @@
package org.springframework.boot.loader.jar;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.jar.JarEntry;
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.data.RandomAccessDataFile;
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.greaterThan;
@ -389,4 +394,18 @@ public class JarFileTests {
}
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();
}
}