GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/fs/virtfs/zipreader.cpp Lines: 89 105 84.8 %
Date: 2017-11-29 Branches: 54 192 28.1 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2017  The ManaPlus Developers
4
 *
5
 *  This file is part of The ManaPlus Client.
6
 *
7
 *  This program is free software; you can redistribute it and/or modify
8
 *  it under the terms of the GNU General Public License as published by
9
 *  the Free Software Foundation; either version 2 of the License, or
10
 *  any later version.
11
 *
12
 *  This program is distributed in the hope that it will be useful,
13
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 *  GNU General Public License for more details.
16
 *
17
 *  You should have received a copy of the GNU General Public License
18
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "fs/virtfs/zipreader.h"
22
23
#include "fs/paths.h"
24
25
#include "fs/virtfs/zipentry.h"
26
#include "fs/virtfs/ziplocalheader.h"
27
28
#include "utils/cast.h"
29
#include "utils/checkutils.h"
30
#include "utils/delete2.h"
31
#include "utils/stringutils.h"
32
33
#include <zlib.h>
34
PRAGMA48(GCC diagnostic push)
35
PRAGMA48(GCC diagnostic ignored "-Wshadow")
36
#include <SDL_endian.h>
37
PRAGMA48(GCC diagnostic pop)
38
39
#include "debug.h"
40
41
#ifndef SDL_BIG_ENDIAN
42
#error missing SDL_endian.h
43
#endif  // SDL_BYTEORDER
44
45
// #define DEBUG_ZIP
46
47
extern const char *dirSeparator;
48
49
#define readVal(val, sz, msg) \
50
    cnt = fread(static_cast<void*>(val), 1, sz, arcFile); \
51
    if (cnt != sz) \
52
    { \
53
        reportAlways("Error reading " msg " in file %s", \
54
            archiveName.c_str()); \
55
        delete2(header); \
56
        delete [] buf; \
57
        fclose(arcFile); \
58
        return false; \
59
    }
60
61
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
62
#define swapVal16(val) val = __builtin_bswap16(val);
63
#define swapVal32(val) val = __builtin_bswap32(val);
64
#else  // SDL_BYTEORDER == SDL_BIG_ENDIAN
65
#define swapVal16(val)
66
#define swapVal32(val)
67
#endif  // SDL_BYTEORDER == SDL_BIG_ENDIAN
68
69
namespace VirtFs
70
{
71
72
namespace ZipReader
73
{
74
206
    bool readArchiveInfo(ZipEntry *const entry)
75
    {
76
206
        if (entry == nullptr)
77
        {
78
            reportAlways("Entry is null.");
79
            return false;
80
        }
81
618
        const std::string archiveName = entry->root;
82
206
        STD_VECTOR<ZipLocalHeader*> &restrict headers = entry->mHeaders;
83
206
        STD_VECTOR<std::string> &restrict dirs = entry->mDirs;
84
206
        FILE *restrict const arcFile = fopen(archiveName.c_str(),
85
206
            "rb");
86
206
        if (arcFile == nullptr)
87
        {
88
            reportAlways("Can't open zip file %s",
89
                archiveName.c_str());
90
            return false;
91
        }
92
206
        uint8_t *const buf = new uint8_t[65535 + 10];
93
206
        uint16_t val16 = 0U;
94
206
        uint16_t method = 0U;
95
206
        ZipLocalHeader *header = nullptr;
96
97
#ifdef DEBUG_ZIP
98
        logger->log("Read archive: %s", archiveName.c_str());
99
#endif  // DEBUG_ZIP
100
101
        // format source https://en.wikipedia.org/wiki/Zip_%28file_format%29
102
2298
        while (feof(arcFile) == 0)
103
        {
104
2298
            size_t cnt = 0U;
105
            // file header pointer on 0
106
            // read file header signature
107


2298
            readVal(buf, 4, "zip file header");  // + 4
108
            // pointer on 4
109
110

4596
            if (buf[0] == 0x50 &&
111
4596
                buf[1] == 0x4B &&
112
4390
                buf[2] == 0x03 &&
113
2092
                buf[3] == 0x04)
114
            {   // local file header
115

2092
                header = new ZipLocalHeader;
116
2092
                header->zipEntry = entry;
117
                // skip useless fields
118
2092
                fseek(arcFile, 4, SEEK_CUR);  // + 4
119
                // file header pointer on 8
120


2092
                readVal(&method, 2, "compression method")  // + 2
121
                swapVal16(method)
122
2092
                header->compressed = (method != 0);
123
                // file header pointer on 10
124
2092
                fseek(arcFile, 8, SEEK_CUR);  // + 8
125
                // file header pointer on 18
126


4184
                readVal(&header->compressSize, 4,
127
                    "zip compressed size")  // + 4
128
                swapVal32(header->compressSize)
129
                // file header pointer on 22
130


4184
                readVal(&header->uncompressSize, 4,
131
                    "zip uncompressed size")  // + 4
132
                swapVal32(header->uncompressSize)
133
                // file header pointer on 26
134


2092
                readVal(&val16, 2, "file name length")  // + 2
135
                swapVal16(val16)
136
                // file header pointer on 28
137
2092
                const uint32_t fileNameLen = CAST_U32(val16);
138
2092
                if (fileNameLen > 1000)
139
                {
140
                    reportAlways("Error too long file name in file %s",
141
                        archiveName.c_str());
142
                    delete header;
143
                    delete [] buf;
144
                    fclose(arcFile);
145
                    return false;
146
                }
147


2092
                readVal(&val16, 2, "extra field length")  // + 2
148
                swapVal16(val16)
149
                // file header pointer on 30
150
2092
                const uint32_t extraFieldLen = CAST_U32(val16);
151


4184
                readVal(buf, fileNameLen, "file name");
152
                // file header pointer on 30 + fileNameLen
153
2092
                buf[fileNameLen] = 0;
154
10460
                header->fileName = std::string(
155
2092
                    reinterpret_cast<char*>(buf));
156
2092
                prepareFsPath(header->fileName);
157
2092
                header->dataOffset = CAST_S32(ftell(arcFile) + extraFieldLen);
158
2092
                fseek(arcFile, extraFieldLen + header->compressSize, SEEK_CUR);
159
                // pointer on 30 + fileNameLen + extraFieldLen + compressSize
160

8368
                if (findLast(header->fileName, dirSeparator) == false)
161
                {
162
1520
                    headers.push_back(header);
163
#ifdef DEBUG_ZIP
164
                    logger->log(" file name: %s",
165
                        header->fileName.c_str());
166
                    logger->log(" compression method: %u",
167
                        CAST_U32(method));
168
                    logger->log(" compressed size: %u",
169
                        header->compressSize);
170
                    logger->log(" uncompressed size: %u",
171
                        header->uncompressSize);
172
#endif  // DEBUG_ZIP
173
                }
174
                else
175
                {
176
#ifdef DEBUG_ZIP
177
                    logger->log(" dir name: %s",
178
                        header->fileName.c_str());
179
#endif  // DEBUG_ZIP
180
572
                    dirs.push_back(header->fileName);
181
1144
                    delete header;
182
                }
183
            }
184

412
            else if (buf[0] == 0x50 &&
185
412
                     buf[1] == 0x4B &&
186
410
                     buf[2] == 0x01 &&
187
204
                     buf[3] == 0x02)
188
            {   // central directory file header
189
                // !!! This is quick way for read zip archives. !!!
190
                // !!! It ignore modified files in archive. !!!
191
                // ignoring central directory entries
192
                break;
193
            }
194

4
            else if (buf[0] == 0x50 &&
195
4
                     buf[1] == 0x4B &&
196
4
                     buf[2] == 0x05 &&
197
2
                     buf[3] == 0x06)
198
            {   // end of central directory
199
                // !!! This is quick way for read zip archives. !!!
200
                // !!! It ignore modified files in archive. !!!
201
                // ignoring end of central directory
202
                break;
203
            }
204
            else
205
            {
206
                reportAlways("Error in header signature (0x%02x%02x%02x%02x)"
207
                    " in file %s",
208
                    buf[0],
209
                    buf[1],
210
                    buf[2],
211
                    buf[3],
212
                    archiveName.c_str());
213
                delete [] buf;
214
                fclose(arcFile);
215
                return false;
216
            }
217
        }
218
206
        delete [] buf;
219
206
        fclose(arcFile);
220
206
        return true;
221
    }
222
223
    void reportZlibError(const std::string &text,
224
                         const int err)
225
    {
226
        reportAlways("Zlib error: '%s' in %s",
227
            text.c_str(),
228
            getZlibError(err).c_str());
229
    }
230
231
    std::string getZlibError(const int err)
232
    {
233
        switch (err)
234
        {
235
            case Z_OK:
236
                return std::string();
237
            default:
238
                return "unknown zlib error";
239
        }
240
    }
241
242
132
    uint8_t *readCompressedFile(const ZipLocalHeader *restrict const header)
243
    {
244
132
        if (header == nullptr)
245
        {
246
4
            reportAlways("ZipReader::readCompressedFile: header is null");
247
            return nullptr;
248
        }
249
260
        FILE *restrict const arcFile = fopen(
250
130
            header->zipEntry->root.c_str(),
251
130
            "rb");
252
130
        if (arcFile == nullptr)
253
        {
254
            reportAlways("Can't open zip file %s",
255
                header->zipEntry->root.c_str());
256
            return nullptr;
257
        }
258
259
130
        fseek(arcFile, header->dataOffset, SEEK_SET);
260
130
        const uint32_t compressSize = header->compressSize;
261
130
        uint8_t *const buf = new uint8_t[compressSize];
262
260
        if (fread(static_cast<void*>(buf), 1, compressSize, arcFile) !=
263
            compressSize)
264
        {
265
            reportAlways("Read zip compressed file error from archive: %s",
266
                header->zipEntry->root.c_str());
267
            fclose(arcFile);
268
            delete [] buf;
269
            return nullptr;
270
        }
271
130
        fclose(arcFile);
272
        return buf;
273
    }
274
275
130
    const uint8_t *readFile(const ZipLocalHeader *restrict const header)
276
    {
277
130
        if (header == nullptr)
278
        {
279
4
            reportAlways("Open zip file error. header is null.");
280
            return nullptr;
281
        }
282
128
        uint8_t *restrict const in = readCompressedFile(header);
283
128
        if (in == nullptr)
284
            return nullptr;
285
128
        if (header->compressed == false)
286
            return in;  //  return as is if data not compressed
287
110
        const size_t outSize = header->uncompressSize;
288
110
        uint8_t *restrict const out = new uint8_t[outSize];
289
110
        if (outSize == 0)
290
        {
291
            delete [] in;
292
            return out;
293
        }
294
295
        z_stream strm;
296
110
        strm.zalloc = nullptr;
297
110
        strm.zfree = nullptr;
298
110
        strm.opaque = nullptr;
299
110
        strm.next_in = in;
300
110
        strm.avail_in = header->compressSize;
301
110
        strm.next_out = out;
302
110
        strm.avail_out = CAST_U32(outSize);
303
304
PRAGMACLANG6GCC(GCC diagnostic push)
305
PRAGMACLANG6GCC(GCC diagnostic ignored "-Wold-style-cast")
306
110
        int ret = inflateInit2(&strm, -MAX_WBITS);
307
PRAGMACLANG6GCC(GCC diagnostic pop)
308
309
110
        if (ret != Z_OK)
310
        {
311
            reportZlibError(header->zipEntry->root, ret);
312
            delete [] in;
313
            delete [] out;
314
            return nullptr;
315
        }
316
110
        ret = inflate(&strm, Z_FINISH);
317
//        ret = inflate(&strm, Z_SYNC_FLUSH);
318
110
        if (ret != Z_OK &&
319
            ret != Z_STREAM_END)
320
        {
321
            reportZlibError("file decompression error",
322
                ret);
323
            inflateEnd(&strm);
324
            delete [] in;
325
            delete [] out;
326
            return nullptr;
327
        }
328
110
        inflateEnd(&strm);
329
110
        delete [] in;
330
110
        return out;
331
    }
332
}  // namespace ZipReader
333
334
}  // namespace VirtFs