问题
I am creating an archive on Android using the code like this:
OutputStream os = new FileOutputStream(zipFile);
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
try
{
zos.setLevel(8);
byte[] buffer = new byte[32768];
for (VFile src : toPack)
{
ZipEntry entry = new ZipEntry(src.name);
zos.putNextEntry(entry);
src.pushToStream(zos, buffer);
src.close();
zos.closeEntry();
}
}
finally
{
zos.close();
}
I found that there's only one compression method available - DEFLATED
(there's only STORED
alternative available). This means that archive is always compressed with one method.
If I run this code on Android 2.3.4 - I can decompress files in Windows using 7Zip; if I run this on Android 3 (or Samsung Galaxy Tab; not sure who makes it wrong) - 7Zip shows archive list, but cannot decompress file saying Unsupported compression method. At the same time 7Zip shows Deflate as a compression method on file which means it can treat it properly.
Did anyone has this problem?
Thanks.
UDP: Found another topic with similar issue (may be not same though).
回答1:
The @user1269737's answer is correct; almost. But it only works for a single-file archives. Below is a code which parses the whole archive.
/**
* Replace wrong local file header byte
* http://sourceforge.net/tracker/?func=detail&aid=3477810&group_id=14481&atid=114481
* Applies to Android API 9-13
* @param zip file
* @throws IOException
*/
private static void fixInvalidZipFile(File zip) throws IOException
{
RandomAccessFile r = new RandomAccessFile(zip, "rw");
try
{
long eocd_offset = findEOCDRecord(r);
if (eocd_offset > 0)
{
r.seek(eocd_offset + 16); // offset of first CDE in EOCD
long cde_offset = readInt(r); // read offset of first Central Directory Entry
long lfh_offset = 0;
long fskip, dskip;
while (true)
{
r.seek(cde_offset);
if (readInt(r) != CDE_SIGNATURE) // got off sync!
return;
r.seek(cde_offset + 20); // compressed file size offset
fskip = readInt(r);
// fix the header
//
r.seek(lfh_offset + 7);
short localFlagsHi = r.readByte(); // hi-order byte of local header flags (general purpose)
r.seek(cde_offset + 9);
short realFlagsHi = r.readByte(); // hi-order byte of central directory flags (general purpose)
if (localFlagsHi != realFlagsHi)
{ // in latest versions this bug is fixed, so we're checking is bug exists.
r.seek(lfh_offset + 7);
r.write(realFlagsHi);
}
// calculate offset of next Central Directory Entry
//
r.seek(cde_offset + 28); // offset of variable CDE parts length in CDE
dskip = 46; // length of fixed CDE part
dskip += readShort(r); // file name
dskip += readShort(r); // extra field
dskip += readShort(r); // file comment
cde_offset += dskip;
if (cde_offset >= eocd_offset) // finished!
break;
// calculate offset of next Local File Header
//
r.seek(lfh_offset + 26); // offset of variable LFH parts length in LFH
fskip += readShort(r); // file name
fskip += readShort(r); // extra field
fskip += 30; // length of fixed LFH part
fskip += 16; // length of Data Descriptor (written after file data)
lfh_offset += fskip;
}
}
}
finally
{
r.close();
}
}
//http://www.pkware.com/documents/casestudies/APPNOTE.TXT
private static final int LFH_SIGNATURE = 0x04034b50;
private static final int DD_SIGNATURE = 0x08074b50;
private static final int CDE_SIGNATURE = 0x02014b50;
private static final int EOCD_SIGNATURE = 0x06054b50;
/** Find an offset of End Of Central Directory record in file */
private static long findEOCDRecord(RandomAccessFile f) throws IOException
{
long result = f.length() - 22; // 22 is minimal EOCD record length
while (result > 0)
{
f.seek(result);
if (readInt(f) == EOCD_SIGNATURE) return result;
result--;
}
return -1;
}
/** Read a 4-byte integer from file converting endianness. */
private static int readInt(RandomAccessFile f) throws IOException
{
int result = 0;
result |= f.read();
result |= (f.read() << 8);
result |= (f.read() << 16);
result |= (f.read() << 24);
return result;
}
/** Read a 2-byte integer from file converting endianness. */
private static short readShort(RandomAccessFile f) throws IOException
{
short result = 0;
result |= f.read();
result |= (f.read() << 8);
return result;
}
回答2:
Need to be updated. It fixes single-file-zip. You have to look following sequence in zip file { 0, 0x08, 0x08, 0x08, 0 } and replace it to { 0, 0x08, 0x00, 0x08, 0 }
/**
* Replace wrong byte http://sourceforge.net/tracker/?func=detail&aid=3477810&group_id=14481&atid=114481
* @param zip file
* @throws IOException
*/
private static void replaceWrongZipByte(File zip) throws IOException {
RandomAccessFile r = new RandomAccessFile(zip, "rw");
int flag = Integer.parseInt("00001000", 2); //wrong byte
r.seek(7);
int realFlags = r.read();
if( (realFlags & flag) > 0) { // in latest versions this bug is fixed, so we're checking is bug exists.
r.seek(7);
flag = (~flag & 0xff);
// removing only wrong bit, other bits remains the same.
r.write(realFlags & flag);
}
r.close();
}
Update version :
Following code removes all wrong bytes in ZIP. KMPMatch.java easy to find in google
public static void replaceWrongBytesInZip(File zip) throws IOException {
byte find[] = new byte[] { 0, 0x08, 0x08, 0x08, 0 };
int index;
while( (index = indexOfBytesInFile(zip,find)) != -1) {
replaceWrongZipByte(zip, index + 2);
}
}
private static int indexOfBytesInFile(File file,byte find[]) throws IOException {
byte fileContent[] = new byte[(int) file.length()];
FileInputStream fin = new FileInputStream(file);
fin.read(fileContent);
fin.close();
return KMPMatch.indexOf(fileContent, find);
}
/**
* Replace wrong byte http://sourceforge.net/tracker/?func=detail&aid=3477810&group_id=14481&atid=114481
* @param zip file
* @throws IOException
*/
private static void replaceWrongZipByte(File zip, int wrongByteIndex) throws IOException {
RandomAccessFile r = new RandomAccessFile(zip, "rw");
int flag = Integer.parseInt("00001000", 2);
r.seek(wrongByteIndex);
int realFlags = r.read();
if( (realFlags & flag) > 0) { // in latest versions this bug is fixed, so we're checking is bug exists.
r.seek(wrongByteIndex);
flag = (~flag & 0xff);
// removing only wrong bit, other bits remains the same.
r.write(realFlags & flag);
}
r.close();
}
来源:https://stackoverflow.com/questions/11039079/cannot-extract-file-from-zip-archive-created-on-android-device-os-specific