1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//
18// General-purpose Zip archive access.  This class allows both reading and
19// writing to Zip archives, including deletion of existing entries.
20//
21#ifndef __LIBS_ZIPFILE_H
22#define __LIBS_ZIPFILE_H
23
24#include "BigBuffer.h"
25#include "ZipEntry.h"
26
27#include <stdio.h>
28#include <utils/Errors.h>
29#include <vector>
30
31namespace aapt {
32
33using android::status_t;
34
35/*
36 * Manipulate a Zip archive.
37 *
38 * Some changes will not be visible in the until until "flush" is called.
39 *
40 * The correct way to update a file archive is to make all changes to a
41 * copy of the archive in a temporary file, and then unlink/rename over
42 * the original after everything completes.  Because we're only interested
43 * in using this for packaging, we don't worry about such things.  Crashing
44 * after making changes and before flush() completes could leave us with
45 * an unusable Zip archive.
46 */
47class ZipFile {
48public:
49    ZipFile(void)
50      : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
51      {}
52    ~ZipFile(void) {
53        if (!mReadOnly)
54            flush();
55        if (mZipFp != NULL)
56            fclose(mZipFp);
57        discardEntries();
58    }
59
60    /*
61     * Open a new or existing archive.
62     */
63    enum {
64        kOpenReadOnly   = 0x01,
65        kOpenReadWrite  = 0x02,
66        kOpenCreate     = 0x04,     // create if it doesn't exist
67        kOpenTruncate   = 0x08,     // if it exists, empty it
68    };
69    status_t open(const char* zipFileName, int flags);
70
71    /*
72     * Add a file to the end of the archive.  Specify whether you want the
73     * library to try to store it compressed.
74     *
75     * If "storageName" is specified, the archive will use that instead
76     * of "fileName".
77     *
78     * If there is already an entry with the same name, the call fails.
79     * Existing entries with the same name must be removed first.
80     *
81     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
82     */
83    status_t add(const char* fileName, int compressionMethod,
84        ZipEntry** ppEntry)
85    {
86        return add(fileName, fileName, compressionMethod, ppEntry);
87    }
88    status_t add(const char* fileName, const char* storageName,
89        int compressionMethod, ZipEntry** ppEntry)
90    {
91        return addCommon(fileName, NULL, 0, storageName,
92                         ZipEntry::kCompressStored,
93                         compressionMethod, ppEntry);
94    }
95
96    /*
97     * Add a file that is already compressed with gzip.
98     *
99     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
100     */
101    status_t addGzip(const char* fileName, const char* storageName,
102        ZipEntry** ppEntry)
103    {
104        return addCommon(fileName, NULL, 0, storageName,
105                         ZipEntry::kCompressDeflated,
106                         ZipEntry::kCompressDeflated, ppEntry);
107    }
108
109    /*
110     * Add a file from an in-memory data buffer.
111     *
112     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
113     */
114    status_t add(const void* data, size_t size, const char* storageName,
115        int compressionMethod, ZipEntry** ppEntry)
116    {
117        return addCommon(NULL, data, size, storageName,
118                         ZipEntry::kCompressStored,
119                         compressionMethod, ppEntry);
120    }
121
122    status_t add(const BigBuffer& data, const char* storageName,
123        int compressionMethod, ZipEntry** ppEntry);
124
125    /*
126     * Add an entry by copying it from another zip file.  If storageName is
127     * non-NULL, the entry will be inserted with the name storageName, otherwise
128     * it will have the same name as the source entry.  If "padding" is
129     * nonzero, the specified number of bytes will be added to the "extra"
130     * field in the header.
131     *
132     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
133     */
134    status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
135                 const char* storageName, int padding, ZipEntry** ppEntry);
136
137    /*
138     * Mark an entry as having been removed.  It is not actually deleted
139     * from the archive or our internal data structures until flush() is
140     * called.
141     */
142    status_t remove(ZipEntry* pEntry);
143
144    /*
145     * Flush changes.  If mNeedCDRewrite is set, this writes the central dir.
146     */
147    status_t flush(void);
148
149    /*
150     * Expand the data into the buffer provided.  The buffer must hold
151     * at least <uncompressed len> bytes.  Variation expands directly
152     * to a file.
153     *
154     * Returns "false" if an error was encountered in the compressed data.
155     */
156    //bool uncompress(const ZipEntry* pEntry, void* buf) const;
157    //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
158    void* uncompress(const ZipEntry* pEntry);
159
160    /*
161     * Get an entry, by name.  Returns NULL if not found.
162     *
163     * Does not return entries pending deletion.
164     */
165    ZipEntry* getEntryByName(const char* fileName) const;
166
167    /*
168     * Get the Nth entry in the archive.
169     *
170     * This will return an entry that is pending deletion.
171     */
172    int getNumEntries(void) const { return mEntries.size(); }
173    ZipEntry* getEntryByIndex(int idx) const;
174
175private:
176    /* these are private and not defined */
177    ZipFile(const ZipFile& src);
178    ZipFile& operator=(const ZipFile& src);
179
180    class EndOfCentralDir {
181    public:
182        EndOfCentralDir(void) :
183            mDiskNumber(0),
184            mDiskWithCentralDir(0),
185            mNumEntries(0),
186            mTotalNumEntries(0),
187            mCentralDirSize(0),
188            mCentralDirOffset(0),
189            mCommentLen(0),
190            mComment(NULL)
191            {}
192        virtual ~EndOfCentralDir(void) {
193            delete[] mComment;
194        }
195
196        status_t readBuf(const unsigned char* buf, int len);
197        status_t write(FILE* fp);
198
199        //unsigned long   mSignature;
200        unsigned short  mDiskNumber;
201        unsigned short  mDiskWithCentralDir;
202        unsigned short  mNumEntries;
203        unsigned short  mTotalNumEntries;
204        unsigned long   mCentralDirSize;
205        unsigned long   mCentralDirOffset;      // offset from first disk
206        unsigned short  mCommentLen;
207        unsigned char*  mComment;
208
209        enum {
210            kSignature      = 0x06054b50,
211            kEOCDLen        = 22,       // EndOfCentralDir len, excl. comment
212
213            kMaxCommentLen  = 65535,    // longest possible in ushort
214            kMaxEOCDSearch  = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
215
216        };
217
218        void dump(void) const;
219    };
220
221
222    /* read all entries in the central dir */
223    status_t readCentralDir(void);
224
225    /* crunch deleted entries out */
226    status_t crunchArchive(void);
227
228    /* clean up mEntries */
229    void discardEntries(void);
230
231    /* common handler for all "add" functions */
232    status_t addCommon(const char* fileName, const void* data, size_t size,
233        const char* storageName, int sourceType, int compressionMethod,
234        ZipEntry** ppEntry);
235
236    /* copy all of "srcFp" into "dstFp" */
237    status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
238    /* copy all of "data" into "dstFp" */
239    status_t copyDataToFp(FILE* dstFp,
240        const void* data, size_t size, unsigned long* pCRC32);
241    /* copy some of "srcFp" into "dstFp" */
242    status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
243        unsigned long* pCRC32);
244    /* like memmove(), but on parts of a single file */
245    status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
246    /* compress all of "srcFp" into "dstFp", using Deflate */
247    status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
248        const void* data, size_t size, unsigned long* pCRC32);
249
250    /* get modification date from a file descriptor */
251    time_t getModTime(int fd);
252
253    /*
254     * We use stdio FILE*, which gives us buffering but makes dealing
255     * with files >2GB awkward.  Until we support Zip64, we're fine.
256     */
257    FILE*           mZipFp;             // Zip file pointer
258
259    /* one of these per file */
260    EndOfCentralDir mEOCD;
261
262    /* did we open this read-only? */
263    bool            mReadOnly;
264
265    /* set this when we trash the central dir */
266    bool            mNeedCDRewrite;
267
268    /*
269     * One ZipEntry per entry in the zip file.  I'm using pointers instead
270     * of objects because it's easier than making operator= work for the
271     * classes and sub-classes.
272     */
273    std::vector<ZipEntry*>   mEntries;
274};
275
276}; // namespace aapt
277
278#endif // __LIBS_ZIPFILE_H
279