Cannot extract file from ZIP archive created on Android (device/OS specific)

こ雲淡風輕ζ 提交于 2020-01-21 05:30:11

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!