Coverage Report

Created: 2026-06-09 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ogre/OgreMain/src/OgreZip.cpp
Line
Count
Source
1
/*
2
-----------------------------------------------------------------------------
3
This source file is part of OGRE
4
(Object-oriented Graphics Rendering Engine)
5
For the latest info, see http://www.ogre3d.org/
6
7
Copyright (c) 2000-2014 Torus Knot Software Ltd
8
9
Permission is hereby granted, free of charge, to any person obtaining a copy
10
of this software and associated documentation files (the "Software"), to deal
11
in the Software without restriction, including without limitation the rights
12
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
copies of the Software, and to permit persons to whom the Software is
14
furnished to do so, subject to the following conditions:
15
16
The above copyright notice and this permission notice shall be included in
17
all copies or substantial portions of the Software.
18
19
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
THE SOFTWARE.
26
-----------------------------------------------------------------------------
27
*/
28
#include "OgreStableHeaders.h"
29
30
#if OGRE_NO_ZIP_ARCHIVE == 0
31
#include <zip.h>
32
33
namespace Ogre {
34
namespace {
35
    class ZipArchive : public Archive
36
    {
37
    protected:
38
        /// Handle to root zip file
39
        zip_t* mZipFile;
40
        MemoryDataStreamPtr mBuffer;
41
        /// File list (since zziplib seems to only allow scanning of dir tree once)
42
        FileInfoList mFileList;
43
        OGRE_AUTO_MUTEX;
44
    public:
45
        ZipArchive(const String& name, const String& archType, const uint8* externBuf = 0, size_t externBufSz = 0);
46
        ~ZipArchive();
47
        /// @copydoc Archive::isCaseSensitive
48
0
        bool isCaseSensitive(void) const override { return OGRE_RESOURCEMANAGER_STRICT != 0; }
49
50
        /// @copydoc Archive::load
51
        void load() override;
52
        /// @copydoc Archive::unload
53
        void unload() final override;
54
55
        /// @copydoc Archive::open
56
        DataStreamPtr open(const String& filename, bool readOnly = true) const override;
57
58
        /// @copydoc Archive::create
59
        DataStreamPtr create(const String& filename) override;
60
61
        /// @copydoc Archive::remove
62
        void remove(const String& filename) override;
63
64
        /// @copydoc Archive::list
65
        StringVectorPtr list(bool recursive = true, bool dirs = false) const override;
66
67
        /// @copydoc Archive::listFileInfo
68
        FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false) const override;
69
70
        /// @copydoc Archive::find
71
        StringVectorPtr find(const String& pattern, bool recursive = true,
72
            bool dirs = false) const override;
73
74
        /// @copydoc Archive::findFileInfo
75
        FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
76
            bool dirs = false) const override;
77
78
        /// @copydoc Archive::exists
79
        bool exists(const String& filename) const override;
80
81
        /// @copydoc Archive::getModifiedTime
82
        time_t getModifiedTime(const String& filename) const override;
83
    };
84
}
85
    //-----------------------------------------------------------------------
86
    ZipArchive::ZipArchive(const String& name, const String& archType, const uint8* externBuf, size_t externBufSz)
87
4.15k
        : Archive(name, archType), mZipFile(0)
88
4.15k
    {
89
4.15k
        if(externBuf)
90
4.15k
            mBuffer.reset(new MemoryDataStream(const_cast<uint8*>(externBuf), externBufSz));
91
4.15k
    }
92
    //-----------------------------------------------------------------------
93
    ZipArchive::~ZipArchive()
94
4.15k
    {
95
4.15k
        unload();
96
4.15k
    }
97
    //-----------------------------------------------------------------------
98
    void ZipArchive::load()
99
4.15k
    {
100
4.15k
        OGRE_LOCK_AUTO_MUTEX;
101
4.15k
        if (!mZipFile)
102
4.15k
        {
103
4.15k
            if(!mBuffer)
104
0
                mBuffer.reset(new MemoryDataStream(_openFileStream(mName, std::ios::binary)));
105
106
4.15k
            mZipFile = zip_stream_open((const char*)mBuffer->getPtr(), mBuffer->size(), 0, 'r');
107
108
            // Cache names
109
4.15k
            int n = zip_entries_total(mZipFile);
110
25.7k
            for (int i = 0; i < n; ++i) {
111
21.6k
                FileInfo info;
112
21.6k
                info.archive = this;
113
114
21.6k
                zip_entry_openbyindex(mZipFile, i);
115
116
21.6k
                info.filename = zip_entry_name(mZipFile);
117
                // Get basename / path
118
21.6k
                StringUtil::splitFilename(info.filename, info.basename, info.path);
119
120
                // Get sizes
121
21.6k
                info.uncompressedSize = zip_entry_size(mZipFile);
122
21.6k
                info.compressedSize = zip_entry_comp_size(mZipFile);
123
124
21.6k
                if (zip_entry_isdir(mZipFile))
125
2.36k
                {
126
2.36k
                    info.filename = info.filename.substr(0, info.filename.length() - 1);
127
2.36k
                    StringUtil::splitFilename(info.filename, info.basename, info.path);
128
                    // Set compressed size to -1 for folders; anyway nobody will check
129
                    // the compressed size of a folder, and if he does, its useless anyway
130
2.36k
                    info.compressedSize = size_t(-1);
131
2.36k
                }
132
#if !OGRE_RESOURCEMANAGER_STRICT
133
                else
134
                {
135
                    info.filename = info.basename;
136
                }
137
#endif
138
21.6k
                zip_entry_close(mZipFile);
139
21.6k
                mFileList.push_back(info);
140
21.6k
            }
141
4.15k
        }
142
4.15k
    }
143
    //-----------------------------------------------------------------------
144
    void ZipArchive::unload()
145
8.31k
    {
146
8.31k
        OGRE_LOCK_AUTO_MUTEX;
147
8.31k
        if (mZipFile)
148
2.79k
        {
149
2.79k
            zip_close(mZipFile);
150
2.79k
            mZipFile = 0;
151
2.79k
            mFileList.clear();
152
2.79k
            mBuffer.reset();
153
2.79k
        }
154
    
155
8.31k
    }
156
    //-----------------------------------------------------------------------
157
    DataStreamPtr ZipArchive::open(const String& filename, bool readOnly) const
158
4.43k
    {
159
        // zip is not threadsafe
160
4.43k
        OGRE_LOCK_AUTO_MUTEX;
161
4.43k
        String lookUpFileName = filename;
162
163
4.43k
#if OGRE_RESOURCEMANAGER_STRICT
164
4.43k
        bool open = zip_entry_opencasesensitive(mZipFile, lookUpFileName.c_str()) == 0;
165
#else
166
        bool open = zip_entry_open(mZipFile, lookUpFileName.c_str()) == 0;
167
        if (!open) // Try if we find the file
168
        {
169
            String basename, path;
170
            StringUtil::splitFilename(lookUpFileName, basename, path);
171
            const FileInfoListPtr fileNfo = findFileInfo(basename, true);
172
            if (fileNfo->size() == 1) // If there are more files with the same do not open anyone
173
            {
174
                Ogre::FileInfo info = fileNfo->at(0);
175
                lookUpFileName = info.path + info.basename;
176
                open = zip_entry_open(mZipFile, lookUpFileName.c_str(), OGRE_RESOURCEMANAGER_STRICT) == 0;
177
            }
178
        }
179
#endif
180
181
4.43k
        if (!open)
182
881
        {
183
881
            OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, "could not open "+lookUpFileName);
184
881
        }
185
186
        // Construct & return stream
187
3.55k
        size_t entrySize = zip_entry_size(mZipFile);
188
3.55k
        size_t compSize  = zip_entry_comp_size(mZipFile);
189
        // repetitive log files have a typical ratio of ~50:1, XML 30:1, images 5:1
190
3.55k
        const size_t MAX_RATIO = 100;
191
3.55k
        if (entrySize > 0 && (compSize == 0 || entrySize / compSize > MAX_RATIO))
192
449
        {
193
449
            zip_entry_close(mZipFile);
194
449
            OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
195
449
                        "zip entry has suspicious compression ratio (possible decompression bomb): " + lookUpFileName);
196
449
        }
197
3.10k
        auto ret = std::make_shared<MemoryDataStream>(lookUpFileName, entrySize, true, true);
198
199
3.10k
        if(zip_entry_noallocread(mZipFile, ret->getPtr(), ret->size()) < 0)
200
2.63k
        {
201
2.63k
            zip_entry_close(mZipFile);
202
2.63k
            OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, "could not read "+lookUpFileName);
203
2.63k
        }
204
468
        zip_entry_close(mZipFile);
205
206
468
        return ret;
207
3.10k
    }
208
    //---------------------------------------------------------------------
209
    DataStreamPtr ZipArchive::create(const String& filename)
210
0
    {
211
0
        OGRE_EXCEPT(Exception::ERR_NOT_IMPLEMENTED, "Modification of zipped archives is not implemented");
212
0
    }
213
    //---------------------------------------------------------------------
214
    void ZipArchive::remove(const String& filename)
215
0
    {
216
0
        OGRE_EXCEPT(Exception::ERR_NOT_IMPLEMENTED, "Modification of zipped archives is not implemented");
217
0
    }
218
    //-----------------------------------------------------------------------
219
    StringVectorPtr ZipArchive::list(bool recursive, bool dirs) const
220
4.15k
    {
221
4.15k
        OGRE_LOCK_AUTO_MUTEX;
222
4.15k
        auto ret = std::make_shared<StringVector>();
223
224
4.15k
        for (auto& f : mFileList)
225
21.6k
            if ((dirs == (f.compressedSize == size_t (-1))) &&
226
19.2k
                (recursive || f.path.empty()))
227
19.2k
                ret->push_back(f.filename);
228
229
4.15k
        return ret;
230
4.15k
    }
231
    //-----------------------------------------------------------------------
232
    FileInfoListPtr ZipArchive::listFileInfo(bool recursive, bool dirs) const
233
0
    {
234
0
        OGRE_LOCK_AUTO_MUTEX;
235
0
        auto ret = std::make_shared<FileInfoList>();
236
0
        for (auto& f : mFileList)
237
0
            if ((dirs == (f.compressedSize == size_t (-1))) &&
238
0
                (recursive || f.path.empty()))
239
0
                ret->push_back(f);
240
241
0
        return ret;
242
0
    }
243
    //-----------------------------------------------------------------------
244
    StringVectorPtr ZipArchive::find(const String& pattern, bool recursive, bool dirs) const
245
0
    {
246
0
        OGRE_LOCK_AUTO_MUTEX;
247
0
        auto ret = std::make_shared<StringVector>();
248
        // If pattern contains a directory name, do a full match
249
0
        bool full_match = (pattern.find ('/') != String::npos) ||
250
0
                          (pattern.find ('\\') != String::npos);
251
0
        bool wildCard = pattern.find('*') != String::npos;
252
            
253
0
        for (auto& f : mFileList)
254
0
            if ((dirs == (f.compressedSize == size_t (-1))) &&
255
0
                (recursive || full_match || wildCard))
256
                // Check basename matches pattern (zip is case insensitive)
257
0
                if (StringUtil::match(full_match ? f.filename : f.basename, pattern, false))
258
0
                    ret->push_back(f.filename);
259
260
0
        return ret;
261
0
    }
262
    //-----------------------------------------------------------------------
263
    FileInfoListPtr ZipArchive::findFileInfo(const String& pattern, 
264
        bool recursive, bool dirs) const
265
0
    {
266
0
        OGRE_LOCK_AUTO_MUTEX;
267
0
        auto ret = std::make_shared<FileInfoList>();
268
        // If pattern contains a directory name, do a full match
269
0
        bool full_match = (pattern.find ('/') != String::npos) ||
270
0
                          (pattern.find ('\\') != String::npos);
271
0
        bool wildCard = pattern.find('*') != String::npos;
272
273
0
        for (auto& f : mFileList)
274
0
            if ((dirs == (f.compressedSize == size_t (-1))) &&
275
0
                (recursive || full_match || wildCard))
276
                // Check name matches pattern (zip is case insensitive)
277
0
                if (StringUtil::match(full_match ? f.filename : f.basename, pattern, false))
278
0
                    ret->push_back(f);
279
280
0
        return ret;
281
0
    }
282
    //-----------------------------------------------------------------------
283
    bool ZipArchive::exists(const String& filename) const
284
0
    {       
285
0
        OGRE_LOCK_AUTO_MUTEX;
286
0
        String cleanName = filename;
287
#if !OGRE_RESOURCEMANAGER_STRICT
288
        if(filename.rfind('/') != String::npos)
289
        {
290
            StringVector tokens = StringUtil::split(filename, "/");
291
            cleanName = tokens[tokens.size() - 1];
292
        }
293
#endif
294
295
0
        return std::find_if(mFileList.begin(), mFileList.end(), [&cleanName](const Ogre::FileInfo& fi) {
296
0
                   return fi.filename == cleanName;
297
0
               }) != mFileList.end();
298
0
    }
299
    //---------------------------------------------------------------------
300
    time_t ZipArchive::getModifiedTime(const String& filename) const
301
0
    {
302
        // Zziplib doesn't yet support getting the modification time of individual files
303
        // so just check the mod time of the zip itself
304
0
        struct stat tagStat;
305
0
        bool ret = (stat(mName.c_str(), &tagStat) == 0);
306
307
0
        if (ret)
308
0
        {
309
0
            return tagStat.st_mtime;
310
0
        }
311
0
        else
312
0
        {
313
0
            return 0;
314
0
        }
315
316
0
    }
317
    //-----------------------------------------------------------------------
318
    //  ZipArchiveFactory
319
    //-----------------------------------------------------------------------
320
    Archive *ZipArchiveFactory::createInstance( const String& name, bool readOnly )
321
0
    {
322
0
        if(!readOnly)
323
0
            return NULL;
324
325
0
        return OGRE_NEW ZipArchive(name, getType());
326
0
    }
327
    //-----------------------------------------------------------------------
328
    const String& ZipArchiveFactory::getType(void) const
329
2
    {
330
2
        static String name = "Zip";
331
2
        return name;
332
2
    }
333
    //-----------------------------------------------------------------------
334
    //-----------------------------------------------------------------------
335
    //  EmbeddedZipArchiveFactory
336
    //-----------------------------------------------------------------------
337
    //-----------------------------------------------------------------------
338
    /// a struct to hold embedded file data
339
    struct EmbeddedFileData
340
    {
341
        const uint8 * fileData;
342
        size_t fileSize;
343
        size_t curPos;
344
        bool isFileOpened;
345
        EmbeddedZipArchiveFactory::DecryptEmbeddedZipFileFunc decryptFunc;
346
    };
347
    //-----------------------------------------------------------------------
348
    /// A type for a map between the file names to file index
349
    typedef std::map<String, EmbeddedFileData> EmbbedFileDataList;
350
351
    namespace {
352
    /// A static list to store the embedded files data
353
    EmbbedFileDataList *gEmbeddedFileDataList;
354
    } // namespace {
355
    //-----------------------------------------------------------------------
356
2
    EmbeddedZipArchiveFactory::EmbeddedZipArchiveFactory() {}
357
1
    EmbeddedZipArchiveFactory::~EmbeddedZipArchiveFactory() {}
358
    //-----------------------------------------------------------------------
359
    Archive *EmbeddedZipArchiveFactory::createInstance( const String& name, bool readOnly )
360
4.15k
    {
361
4.15k
        auto it = gEmbeddedFileDataList->find(name);
362
4.15k
        if(it == gEmbeddedFileDataList->end())
363
0
            return NULL;
364
365
        // TODO: decryptFunc
366
367
4.15k
        return new ZipArchive(name, getType(), it->second.fileData, it->second.fileSize);
368
4.15k
    }
369
    void EmbeddedZipArchiveFactory::destroyInstance(Archive* ptr)
370
4.15k
    {
371
4.15k
        removeEmbbeddedFile(ptr->getName());
372
4.15k
        ZipArchiveFactory::destroyInstance(ptr);
373
4.15k
    }
374
    //-----------------------------------------------------------------------
375
    const String& EmbeddedZipArchiveFactory::getType(void) const
376
4.16k
    {
377
4.16k
        static String name = "EmbeddedZip";
378
4.16k
        return name;
379
4.16k
    }
380
    //-----------------------------------------------------------------------
381
    void EmbeddedZipArchiveFactory::addEmbbeddedFile(const String& name, const uint8 * fileData, 
382
                                        size_t fileSize, DecryptEmbeddedZipFileFunc decryptFunc)
383
4.15k
    {
384
4.15k
        static bool needToInit = true;
385
4.15k
        if(needToInit)
386
1
        {
387
1
            needToInit = false;
388
389
            // we can't be sure when global variables get initialized
390
            // meaning it is possible our list has not been init when this
391
            // function is being called. The solution is to use local
392
            // static members in this function an init the pointers for the
393
            // global here. We know for use that the local static variables
394
            // are create in this stage.
395
1
            static EmbbedFileDataList sEmbbedFileDataList;
396
1
            gEmbeddedFileDataList = &sEmbbedFileDataList;
397
1
        }
398
399
4.15k
        EmbeddedFileData newEmbeddedFileData;
400
4.15k
        newEmbeddedFileData.curPos = 0;
401
4.15k
        newEmbeddedFileData.isFileOpened = false;
402
4.15k
        newEmbeddedFileData.fileData = fileData;
403
4.15k
        newEmbeddedFileData.fileSize = fileSize;
404
4.15k
        newEmbeddedFileData.decryptFunc = decryptFunc;
405
4.15k
        gEmbeddedFileDataList->emplace(name, newEmbeddedFileData);
406
4.15k
    }
407
    //-----------------------------------------------------------------------
408
    void EmbeddedZipArchiveFactory::removeEmbbeddedFile( const String& name )
409
4.15k
    {
410
4.15k
        gEmbeddedFileDataList->erase(name);
411
4.15k
    }
412
}
413
414
#endif