Java NIO doesn't allow the preservation of the compression method (STORED vs DEFLATED), so unfortunately we need to fall back to ZipEntry-based output for unknown files.

This commit is contained in:
Greg Leach 2015-03-24 20:14:05 -07:00
parent 5cf7696839
commit 628286c022
4 changed files with 92 additions and 27 deletions

View File

@ -33,9 +33,11 @@ import java.nio.file.*;
import java.nio.file.Path;
import java.util.*;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.nio.file.Files;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
@ -544,41 +546,97 @@ public class Androlib {
public void buildUnknownFiles(File appDir, File outFile, Map<String, Object> meta)
throws AndrolibException {
Path globalPath = Paths.get(appDir.getPath() + File.separatorChar + UNK_DIRNAME);
if (meta.containsKey("unknownFiles")) {
LOGGER.info("Copying unknown files/dir...");
Map<String, String> files = (Map<String, String>)meta.get("unknownFiles");
File tempFile = new File(outFile.getParent(), outFile.getName() + ".apktool_temp");
boolean renamed = outFile.renameTo(tempFile);
if(!renamed) {
throw new AndrolibException("Unable to rename temporary file");
}
try {
// set our filesystem options
Map<String, String> zip_properties = new HashMap<>();
zip_properties.put("create", "false");
zip_properties.put("encoding", "UTF-8");
// create filesystem
Path path = Paths.get(outFile.getAbsolutePath());
try(
FileSystem fs = FileSystems.newFileSystem(path, null)
) {
// loop through files inside
for (Map.Entry<String,String> entry : files.entrySet()) {
File file = new File(globalPath.toFile(), entry.getKey());
Path dest = fs.getPath(entry.getKey());
Path destParent = dest.getParent();
if (destParent != null && !Files.exists(destParent)) {
Files.createDirectories(destParent);
}
Path newFile = Paths.get(file.getAbsolutePath());
Files.copy(newFile, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
try (
ZipFile inputFile = new ZipFile(tempFile);
ZipOutputStream actualOutput = new ZipOutputStream(new FileOutputStream(outFile));
) {
byte[] buffer = new byte[4096 * 1024];
copyExistingFiles(inputFile, actualOutput, buffer);
copyUnknownFiles(appDir, actualOutput, files, buffer);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
// Remove our temporary file.
tempFile.delete();
}
}
private void copyExistingFiles(ZipFile inputFile, ZipOutputStream outputFile, byte[] buffer) throws IOException {
// First, copy the contents from the existing outFile:
Enumeration<? extends ZipEntry> entries = inputFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = new ZipEntry(entries.nextElement());
// We can't reuse the compressed size because it depends on compression sizes.
entry.setCompressedSize(-1);
outputFile.putNextEntry(entry);
// No need to create directory entries in the final apk
if (!entry.isDirectory()) {
copy(inputFile.getInputStream(entry), outputFile, buffer);
}
outputFile.closeEntry();
}
}
private void copyUnknownFiles(File appDir, ZipOutputStream outputFile, Map<String, String> files, byte[] buffer) throws IOException {
File unknownFileDir = new File(appDir, UNK_DIRNAME);
// loop through unknown files
for (Map.Entry<String,String> unknownFileInfo : files.entrySet()) {
File inputFile = new File(unknownFileDir, unknownFileInfo.getKey());
if(inputFile.isDirectory()) {
continue;
}
ZipEntry newEntry = new ZipEntry(unknownFileInfo.getKey());
int method = Integer.valueOf(unknownFileInfo.getValue());
LOGGER.fine("Copying unknown file " + unknownFileInfo.getKey() + " with method " + unknownFileInfo.getValue());
if(method == ZipEntry.STORED) {
newEntry.setMethod(ZipEntry.STORED);
newEntry.setSize(inputFile.length());
newEntry.setCompressedSize(-1);
BufferedInputStream unknownFile = new BufferedInputStream(new FileInputStream(inputFile));
CRC32 crc = calculateCrc(unknownFile, buffer);
newEntry.setCrc(crc.getValue());
LOGGER.fine("\tsize: " + newEntry.getSize());
}
else {
newEntry.setMethod(ZipEntry.DEFLATED);
}
outputFile.putNextEntry(newEntry);
BufferedInputStream unknownFile = new BufferedInputStream(new FileInputStream(inputFile));
copy(unknownFile, outputFile, buffer);
outputFile.closeEntry();
}
}
private CRC32 calculateCrc(InputStream input, byte[] buffer) throws IOException {
CRC32 crc = new CRC32();
int bytesRead = 0;
while((bytesRead = input.read(buffer)) != -1) {
crc.update(buffer, 0, bytesRead);
}
return crc;
}
private static void copy(InputStream input, OutputStream output, byte[] buffer) throws IOException {
int bytesRead;
while((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
}

View File

@ -308,6 +308,11 @@ public class BuildAndDecodeTest {
Map<String, String> control_files = (Map<String, String>)control.get("unknownFiles");
Map<String, String> test_files = (Map<String, String>)test.get("unknownFiles");
assertTrue(control_files.size() == test_files.size());
// Make sure that the compression methods are still the same
for(Map.Entry<String, String> controlEntry : control_files.entrySet()) {
assertTrue(controlEntry.getValue().equals(test_files.get(controlEntry.getKey())));
}
}
private boolean compareBinaryFolder(String path, boolean res)

View File

@ -12,5 +12,6 @@ versionInfo:
compressionType: false
unknownFiles:
hidden.file: '8'
stored.file: '0'
unk_folder/unknown_file: '8'
lib_bug603/bug603: '8'

View File

@ -0,0 +1 @@
This file is not compressed.