1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17/* Apache Harmony HEADER because the code in this class comes mostly from ZipFile, ZipEntry and
18 * ZipConstants from android libcore.
19 */
20
21package android.support.multidex;
22
23import java.io.File;
24import java.io.IOException;
25import java.io.RandomAccessFile;
26import java.util.zip.CRC32;
27import java.util.zip.ZipException;
28
29/**
30 * Tools to build a quick partial crc of zip files.
31 */
32final class ZipUtil {
33    static class CentralDirectory {
34        long offset;
35        long size;
36    }
37
38    /* redefine those constant here because of bug 13721174 preventing to compile using the
39     * constants defined in ZipFile */
40    private static final int ENDHDR = 22;
41    private static final int ENDSIG = 0x6054b50;
42
43    /**
44     * Size of reading buffers.
45     */
46    private static final int BUFFER_SIZE = 0x4000;
47
48    /**
49     * Compute crc32 of the central directory of an apk. The central directory contains
50     * the crc32 of each entries in the zip so the computed result is considered valid for the whole
51     * zip file. Does not support zip64 nor multidisk but it should be OK for now since ZipFile does
52     * not either.
53     */
54    static long getZipCrc(File apk) throws IOException {
55        RandomAccessFile raf = new RandomAccessFile(apk, "r");
56        try {
57            CentralDirectory dir = findCentralDirectory(raf);
58
59            return computeCrcOfCentralDir(raf, dir);
60        } finally {
61            raf.close();
62        }
63    }
64
65    /* Package visible for testing */
66    static CentralDirectory findCentralDirectory(RandomAccessFile raf) throws IOException,
67            ZipException {
68        long scanOffset = raf.length() - ENDHDR;
69        if (scanOffset < 0) {
70            throw new ZipException("File too short to be a zip file: " + raf.length());
71        }
72
73        long stopOffset = scanOffset - 0x10000 /* ".ZIP file comment"'s max length */;
74        if (stopOffset < 0) {
75            stopOffset = 0;
76        }
77
78        int endSig = Integer.reverseBytes(ENDSIG);
79        while (true) {
80            raf.seek(scanOffset);
81            if (raf.readInt() == endSig) {
82                break;
83            }
84
85            scanOffset--;
86            if (scanOffset < stopOffset) {
87                throw new ZipException("End Of Central Directory signature not found");
88            }
89        }
90        // Read the End Of Central Directory. ENDHDR includes the signature
91        // bytes,
92        // which we've already read.
93
94        // Pull out the information we need.
95        raf.skipBytes(2); // diskNumber
96        raf.skipBytes(2); // diskWithCentralDir
97        raf.skipBytes(2); // numEntries
98        raf.skipBytes(2); // totalNumEntries
99        CentralDirectory dir = new CentralDirectory();
100        dir.size = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL;
101        dir.offset = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL;
102        return dir;
103    }
104
105    /* Package visible for testing */
106    static long computeCrcOfCentralDir(RandomAccessFile raf, CentralDirectory dir)
107            throws IOException {
108        CRC32 crc = new CRC32();
109        long stillToRead = dir.size;
110        raf.seek(dir.offset);
111        int length = (int) Math.min(BUFFER_SIZE, stillToRead);
112        byte[] buffer = new byte[BUFFER_SIZE];
113        length = raf.read(buffer, 0, length);
114        while (length != -1) {
115            crc.update(buffer, 0, length);
116            stillToRead -= length;
117            if (stillToRead == 0) {
118                break;
119            }
120            length = (int) Math.min(BUFFER_SIZE, stillToRead);
121            length = raf.read(buffer, 0, length);
122        }
123        return crc.getValue();
124    }
125}
126