GP-6862 Improvements on Defaltor/Inflator stream use and other cleanup.

DmgFileReader improvements and removal of its GPL ZLIB
implementation.
This commit is contained in:
ghidra1
2026-05-24 23:25:24 -04:00
parent b205bc1428
commit b8c07ec79b
11 changed files with 318 additions and 327 deletions

View File

@@ -18,12 +18,13 @@ public final class GFileUtilityMethods {
public final static File writeTemporaryFile( InputStream inputStream, int maxBytesToWrite ) throws IOException {
File tempOutputFile = File.createTempFile( GHIDRA_FILE_SYSTEM_PREFIX, GHIDRA_FILE_SYSTEM_SUFFIX );
tempOutputFile.deleteOnExit();
OutputStream outputStream = new FileOutputStream( tempOutputFile );
try {
try (OutputStream outputStream = new FileOutputStream(tempOutputFile)) {
int nWritten = 0;
byte [] buffer = new byte[ 8192 ];
byte[] buffer = new byte[8192];
while ( true ) {
int nRead = inputStream.read( buffer );
int limit = Math.min(maxBytesToWrite - nWritten, buffer.length);
int nRead = inputStream.read(buffer, 0, limit);
if ( nRead == -1 ) {
break;
}
@@ -34,13 +35,10 @@ public final class GFileUtilityMethods {
}
}
}
finally {
outputStream.close();
}
return tempOutputFile;
}
public final static File writeTemporaryFile( byte [] bytes, String prefix ) throws IOException {
public final static File writeTemporaryFile(byte[] bytes, String prefix) throws IOException {
if ( prefix == null ) {
prefix = GHIDRA_FILE_SYSTEM_PREFIX;
}
@@ -51,13 +49,11 @@ public final class GFileUtilityMethods {
}
File tempFile = File.createTempFile( prefix , GHIDRA_FILE_SYSTEM_SUFFIX );
tempFile.deleteOnExit();
OutputStream tempFileOut = new FileOutputStream( tempFile );
try {
try (OutputStream tempFileOut = new FileOutputStream(tempFile)) {
tempFileOut.write( bytes );
}
finally {
tempFileOut.close();
}
return tempFile;
}
}

View File

@@ -6,6 +6,7 @@ package mobiledevices.dmg.reader;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.InflaterInputStream;
import org.catacombae.dmgextractor.encodings.encrypted.ReadableCEncryptedEncodingStream;
import org.catacombae.hfsexplorer.FileSystemRecognizer;
@@ -27,7 +28,6 @@ import mobiledevices.dmg.decmpfs.DecmpfsCompressionTypes;
import mobiledevices.dmg.decmpfs.DecmpfsHeader;
import mobiledevices.dmg.ghidra.*;
import mobiledevices.dmg.hfsplus.AttributesFileParser;
import mobiledevices.dmg.zlib.ZLIB;
public class DmgFileReader implements Closeable {
private final static GDataConverter ledc = new GDataConverterLE();
@@ -191,24 +191,16 @@ public class DmgFileReader implements Closeable {
if ( decmpfsHeader.getCompressionType() == DecmpfsCompressionTypes.CMP_Type3 ) {
if ( decmpfsHeader.getAttrBytes()[ 0 ] == -1 ) {
return new ByteArrayInputStream( decmpfsHeader.getAttrBytes(), 1, decmpfsHeader.getAttrBytes().length - 1 );
return new ByteArrayInputStream(decmpfsHeader.getAttrBytes(), 1,
decmpfsHeader.getAttrBytes().length - 1);
}
ZLIB zlib = new ZLIB();
InputStream inputStream =
new ByteArrayInputStream(decmpfsHeader.getAttrBytes());
ByteArrayOutputStream uncompressedBytes =
zlib.decompress(inputStream, (int) decmpfsHeader.getUncompressedSize());
File tempDecompressedFile = GFileUtilityMethods.writeTemporaryFile(
uncompressedBytes.toByteArray(), entry.getName());
return new FileInputStream(tempDecompressedFile);
return new RestrictedInflaterInputStream(decmpfsHeader.getAttrBytes(),
(int) decmpfsHeader.getUncompressedSize());
}
else if ( decmpfsHeader.getCompressionType() == DecmpfsCompressionTypes.CMP_Type4 ) {
return decompressResourceFork( entry, resourceForkStream, (int)decmpfsHeader.getUncompressedSize() );
return decompressResourceFork(entry, resourceForkStream,
(int) decmpfsHeader.getUncompressedSize());
}
}
}
@@ -223,34 +215,102 @@ public class DmgFileReader implements Closeable {
System.err.println(
"dmg resource fork for " + entry.getName() + ": " + tempFile.getAbsolutePath());
InputStream input = new FileInputStream( tempFile );
// Copy compressed portion of tempFile to tempCompressedFile
File tempCompressedFile;
try (InputStream input = new FileInputStream(tempFile)) {
for ( int i = 0 ; i < 0x100 ; ++i ) {
input.read();
for (int i = 0; i < 0x100; ++i) {
input.read();
}
byte[] sizeBytes = new byte[4];
input.read(sizeBytes);
int size = sizeBytes[0] == 0 ? bedc.getInt(sizeBytes) : ledc.getInt(sizeBytes);
byte[] flagsBytes = new byte[4];
input.read(flagsBytes);
byte[] startDistanceBytes = new byte[4];
input.read(startDistanceBytes);
int startDistance = ledc.getInt(startDistanceBytes);
input.skip(startDistance - 8);//skip to the start of the zlib compressed file
tempCompressedFile =
GFileUtilityMethods.writeTemporaryFile(input, size - startDistance);
}
finally {
tempFile.delete();
}
byte [] sizeBytes = new byte[ 4 ];
input.read( sizeBytes );
int size = sizeBytes[ 0 ] == 0 ?
bedc.getInt( sizeBytes ) :
ledc.getInt( sizeBytes );
return new RestrictedInflaterInputStream(tempCompressedFile, expectedLength);
}
byte [] flagsBytes = new byte[ 4 ];
input.read( flagsBytes );
private class RestrictedInflaterInputStream extends InflaterInputStream {
byte [] startDistanceBytes = new byte[ 4 ];
input.read( startDistanceBytes );
int startDistance = ledc.getInt( startDistanceBytes );
private File tempCompressedFile;
private int readLimit;
private int readCount = 0;
input.skip( startDistance - 8 );//skip to the start of the zlib compressed file
/**
* Creates a new Inflater input stream with a default decompressor and buffer size.
* NOTE: The default Inflater instance will be ended when this stream closes.
* @param tempCompressedFile the temporary file containing compressed data. File will be
* removed when this stream is closed.
* @param readLimit maximum data read count. Exceeding this limit will result in an
* IOException.
* @throws FileNotFoundException if tempCompressedFile does not exist
*/
RestrictedInflaterInputStream(File tempCompressedFile, int readLimit)
throws FileNotFoundException {
super(new FileInputStream(tempCompressedFile));
this.tempCompressedFile = tempCompressedFile;
this.readLimit = readLimit;
}
File tempCompressedFile = GFileUtilityMethods.writeTemporaryFile( input, size - startDistance );
InputStream inputStream = new FileInputStream( tempCompressedFile );
/**
* Creates a new Inflater input stream with a default decompressor and buffer size.
* NOTE: The default Inflater instance will be ended when this stream closes.
* @param compressedData the byte array containing compressed data.
* @param readLimit maximum data read count. Exceeding this limit will result in an
* IOException.
*/
RestrictedInflaterInputStream(byte[] compressedData, int readLimit) {
super(new ByteArrayInputStream(compressedData));
this.tempCompressedFile = null;
this.readLimit = readLimit;
}
ZLIB zlib = new ZLIB( );
ByteArrayOutputStream uncompressedByteStream = zlib.decompress( inputStream, expectedLength );
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
// Cleanup temporary file input stream and remove file
in.close();
if (tempCompressedFile != null) {
tempCompressedFile.delete();
}
}
}
return new ByteArrayInputStream( uncompressedByteStream.toByteArray() );
@Override
public int read(byte[] b, int off, int length) throws IOException {
if (length == 0) {
return 0;
}
if (readCount >= readLimit) {
throw new IOException("Decompression limit exceeded: " + readLimit);
}
// Limit read length to avoid exceeding readLimit
int limit = Math.min(readLimit - readCount, length);
int count = super.read(b, off, limit);
if (count > 0) {
readCount += count;
}
return count;
}
}
public List<String> getInfo( String path ) {

View File

@@ -128,6 +128,8 @@ public class DmgServer {
sendResponse(temporaryFile.getAbsolutePath());
// TODO: When can we remove temporaryFile
if (expectedFileLength != temporaryFile.length()) {
log("file sizes do not match!");
}

View File

@@ -1,120 +0,0 @@
/* ###
* IP: Public Domain
*/
package mobiledevices.dmg.zlib;
import java.io.*;
import java.util.zip.*;
/**
*
* TODO make this more memory efficient!!
*
*/
public class ZLIB {
public ZLIB() {
}
public ByteArrayOutputStream decompress( InputStream compressedIn, int expectedDecompressedLength ) throws IOException {
return decompress( compressedIn, expectedDecompressedLength, false );
}
public ByteArrayOutputStream decompress( InputStream compressedIn, int expectedDecompressedLength, boolean noWrap ) throws IOException {
byte [] compressedBytes = convertInputStreamToByteArray( compressedIn );
ByteArrayOutputStream decompressedBOS = new ByteArrayOutputStream();
byte [] tempDecompressedBytes = new byte[ 0x10000 ];
int totalDecompressed = 0;
int offset = 0;
try {
while ( offset < compressedBytes.length && totalDecompressed < expectedDecompressedLength ) {
if ( !noWrap && compressedBytes [ offset ] != 0x78 ) {
break;
}
Inflater inflater = new Inflater( noWrap );
inflater.setInput( compressedBytes, offset, compressedBytes.length - offset );
int nDecompressed = inflater.inflate( tempDecompressedBytes );
if ( nDecompressed == 0 ) {
break;
}
totalDecompressed += nDecompressed;
decompressedBOS.write( tempDecompressedBytes, 0, nDecompressed );
offset += inflater.getTotalIn();//increment total compressed bytes consumed
}
}
catch ( DataFormatException e ) {
throw new IOException( e.getMessage() );
}
return decompressedBOS;
}
public ByteArrayOutputStream compress(byte[] decompressedBytes) {
return compress( false, decompressedBytes );
}
public ByteArrayOutputStream compress(boolean noWrap, byte[] decompressedBytes) {
ByteArrayOutputStream compressedBOS = new ByteArrayOutputStream();
byte [] tempBuffer = new byte[ 0x10000 ];
int offset = 0;
while ( offset < decompressedBytes.length ) {
Deflater deflater = new Deflater( 0, noWrap );
deflater.setInput( decompressedBytes, offset, decompressedBytes.length - offset );
deflater.finish();
if ( deflater.needsInput() ) {
System.err.println( "needs input??" );
}
int nDeflated = deflater.deflate( tempBuffer );
if ( nDeflated == 0 ) {
break;
}
compressedBOS.write( tempBuffer, 0, nDeflated );
offset += deflater.getTotalIn();
}
return compressedBOS;
}
/**
* Converts the contents of an input stream to a byte array
* @param compressedIn
* @return
* @throws IOException
*/
private byte [] convertInputStreamToByteArray( InputStream compressedIn ) throws IOException {
byte [] bytes = new byte[ 8096 ];
ByteArrayOutputStream compressedBOS = new ByteArrayOutputStream();
while ( true ) {
int nRead = compressedIn.read( bytes );
if ( nRead == -1 ) {
break;
}
compressedBOS.write( bytes, 0, nRead );
}
return compressedBOS.toByteArray();
}
}