1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4 
5 Copyright (c) 2006-2021, assimp team
6 
7 All rights reserved.
8 
9 Redistribution and use of this software in source and binary forms,
10 with or without modification, are permitted provided that the
11 following conditions are met:
12 
13 * Redistributions of source code must retain the above
14   copyright notice, this list of conditions and the
15   following disclaimer.
16 
17 * Redistributions in binary form must reproduce the above
18   copyright notice, this list of conditions and the
19   following disclaimer in the documentation and/or other
20   materials provided with the distribution.
21 
22 * Neither the name of the assimp team, nor the names of its
23   contributors may be used to endorse or promote products
24   derived from this software without specific prior
25   written permission of the assimp team.
26 
27 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 
39 ----------------------------------------------------------------------
40 */
41 
42 /** @file  ZipArchiveIOSystem.cpp
43  *  @brief Zip File I/O implementation for #Importer
44  */
45 
46 #include <assimp/BaseImporter.h>
47 #include <assimp/ZipArchiveIOSystem.h>
48 
49 #include <assimp/ai_assert.h>
50 
51 #include <map>
52 #include <memory>
53 
54 #ifdef ASSIMP_USE_HUNTER
55 #    include <minizip/unzip.h>
56 #else
57 #    include <unzip.h>
58 #endif
59 
60 namespace Assimp {
61 
62 // ----------------------------------------------------------------
63 // Wraps an existing Assimp::IOSystem for unzip
64 class IOSystem2Unzip {
65 public:
66     static voidpf open(voidpf opaque, const char *filename, int mode);
67     static uLong read(voidpf opaque, voidpf stream, void *buf, uLong size);
68     static uLong write(voidpf opaque, voidpf stream, const void *buf, uLong size);
69     static long tell(voidpf opaque, voidpf stream);
70     static long seek(voidpf opaque, voidpf stream, uLong offset, int origin);
71     static int close(voidpf opaque, voidpf stream);
72     static int testerror(voidpf opaque, voidpf stream);
73     static zlib_filefunc_def get(IOSystem *pIOHandler);
74 };
75 
open(voidpf opaque,const char * filename,int mode)76 voidpf IOSystem2Unzip::open(voidpf opaque, const char *filename, int mode) {
77     IOSystem *io_system = reinterpret_cast<IOSystem *>(opaque);
78 
79     const char *mode_fopen = nullptr;
80     if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) {
81         mode_fopen = "rb";
82     } else {
83         if (mode & ZLIB_FILEFUNC_MODE_EXISTING) {
84             mode_fopen = "r+b";
85         } else {
86             if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
87                 mode_fopen = "wb";
88             }
89         }
90     }
91 
92     return (voidpf)io_system->Open(filename, mode_fopen);
93 }
94 
read(voidpf,voidpf stream,void * buf,uLong size)95 uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void *buf, uLong size) {
96     IOStream *io_stream = (IOStream *)stream;
97 
98     return static_cast<uLong>(io_stream->Read(buf, 1, size));
99 }
100 
write(voidpf,voidpf stream,const void * buf,uLong size)101 uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void *buf, uLong size) {
102     IOStream *io_stream = (IOStream *)stream;
103 
104     return static_cast<uLong>(io_stream->Write(buf, 1, size));
105 }
106 
tell(voidpf,voidpf stream)107 long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) {
108     IOStream *io_stream = (IOStream *)stream;
109 
110     return static_cast<long>(io_stream->Tell());
111 }
112 
seek(voidpf,voidpf stream,uLong offset,int origin)113 long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) {
114     IOStream *io_stream = (IOStream *)stream;
115 
116     aiOrigin assimp_origin;
117     switch (origin) {
118         default:
119         case ZLIB_FILEFUNC_SEEK_CUR:
120             assimp_origin = aiOrigin_CUR;
121             break;
122         case ZLIB_FILEFUNC_SEEK_END:
123             assimp_origin = aiOrigin_END;
124             break;
125         case ZLIB_FILEFUNC_SEEK_SET:
126             assimp_origin = aiOrigin_SET;
127             break;
128     }
129 
130     return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1);
131 }
132 
close(voidpf opaque,voidpf stream)133 int IOSystem2Unzip::close(voidpf opaque, voidpf stream) {
134     IOSystem *io_system = (IOSystem *)opaque;
135     IOStream *io_stream = (IOStream *)stream;
136 
137     io_system->Close(io_stream);
138 
139     return 0;
140 }
141 
testerror(voidpf,voidpf)142 int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) {
143     return 0;
144 }
145 
get(IOSystem * pIOHandler)146 zlib_filefunc_def IOSystem2Unzip::get(IOSystem *pIOHandler) {
147     zlib_filefunc_def mapping;
148 
149     mapping.zopen_file = (open_file_func)open;
150     mapping.zread_file = (read_file_func)read;
151     mapping.zwrite_file = (write_file_func)write;
152     mapping.ztell_file = (tell_file_func)tell;
153     mapping.zseek_file = (seek_file_func)seek;
154     mapping.zclose_file = (close_file_func)close;
155     mapping.zerror_file = testerror;
156 
157     mapping.opaque = reinterpret_cast<voidpf>(pIOHandler);
158 
159     return mapping;
160 }
161 
162 // ----------------------------------------------------------------
163 // A read-only file inside a ZIP
164 
165 class ZipFile : public IOStream {
166     friend class ZipFileInfo;
167     explicit ZipFile(size_t size);
168 
169 public:
170     virtual ~ZipFile();
171 
172     // IOStream interface
173     size_t Read(void *pvBuffer, size_t pSize, size_t pCount) override;
Write(const void *,size_t,size_t)174     size_t Write(const void * /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) override { return 0; }
175     size_t FileSize() const override;
176     aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override;
177     size_t Tell() const override;
Flush()178     void Flush() override {}
179 
180 private:
181     size_t m_Size = 0;
182     size_t m_SeekPtr = 0;
183     std::unique_ptr<uint8_t[]> m_Buffer;
184 };
185 
186 // ----------------------------------------------------------------
187 // Info about a read-only file inside a ZIP
188 class ZipFileInfo {
189 public:
190     explicit ZipFileInfo(unzFile zip_handle, size_t size);
191 
192     // Allocate and Extract data from the ZIP
193     ZipFile *Extract(unzFile zip_handle) const;
194 
195 private:
196     size_t m_Size = 0;
197     unz_file_pos_s m_ZipFilePos;
198 };
199 
ZipFileInfo(unzFile zip_handle,size_t size)200 ZipFileInfo::ZipFileInfo(unzFile zip_handle, size_t size) :
201         m_Size(size) {
202     ai_assert(m_Size != 0);
203     // Workaround for MSVC 2013 - C2797
204     m_ZipFilePos.num_of_file = 0;
205     m_ZipFilePos.pos_in_zip_directory = 0;
206     unzGetFilePos(zip_handle, &(m_ZipFilePos));
207 }
208 
Extract(unzFile zip_handle) const209 ZipFile *ZipFileInfo::Extract(unzFile zip_handle) const {
210     // Find in the ZIP. This cannot fail
211     unz_file_pos_s *filepos = const_cast<unz_file_pos_s *>(&(m_ZipFilePos));
212     if (unzGoToFilePos(zip_handle, filepos) != UNZ_OK)
213         return nullptr;
214 
215     if (unzOpenCurrentFile(zip_handle) != UNZ_OK)
216         return nullptr;
217 
218     ZipFile *zip_file = new ZipFile(m_Size);
219 
220     // Unzip has a limit of UINT16_MAX bytes buffer
221     uint16_t unzipBufferSize = zip_file->m_Size <= UINT16_MAX ? static_cast<uint16_t>(zip_file->m_Size) : UINT16_MAX;
222     std::unique_ptr<uint8_t[]> unzipBuffer = std::unique_ptr<uint8_t[]>(new uint8_t[unzipBufferSize]);
223     size_t readCount = 0;
224     while (readCount < zip_file->m_Size)
225     {
226         size_t bufferSize = zip_file->m_Size - readCount;
227         if (bufferSize > UINT16_MAX) {
228             bufferSize = UINT16_MAX;
229         }
230 
231         int ret = unzReadCurrentFile(zip_handle, unzipBuffer.get(), static_cast<unsigned int>(bufferSize));
232         if (ret != static_cast<int>(bufferSize))
233         {
234             // Failed, release the memory
235             delete zip_file;
236             zip_file = nullptr;
237             break;
238         }
239 
240         std::memcpy(zip_file->m_Buffer.get() + readCount, unzipBuffer.get(), ret);
241         readCount += ret;
242     }
243 
244     ai_assert(unzCloseCurrentFile(zip_handle) == UNZ_OK);
245     return zip_file;
246 }
247 
ZipFile(size_t size)248 ZipFile::ZipFile(size_t size) :
249         m_Size(size) {
250     ai_assert(m_Size != 0);
251     m_Buffer = std::unique_ptr<uint8_t[]>(new uint8_t[m_Size]);
252 }
253 
~ZipFile()254 ZipFile::~ZipFile() {
255 }
256 
Read(void * pvBuffer,size_t pSize,size_t pCount)257 size_t ZipFile::Read(void *pvBuffer, size_t pSize, size_t pCount) {
258     // Should be impossible
259     ai_assert(m_Buffer != nullptr);
260     ai_assert(nullptr != pvBuffer);
261     ai_assert(0 != pSize);
262     ai_assert(0 != pCount);
263 
264     // Clip down to file size
265     size_t byteSize = pSize * pCount;
266     if ((byteSize + m_SeekPtr) > m_Size) {
267         pCount = (m_Size - m_SeekPtr) / pSize;
268         byteSize = pSize * pCount;
269         if (byteSize == 0) {
270             return 0;
271         }
272     }
273 
274     std::memcpy(pvBuffer, m_Buffer.get() + m_SeekPtr, byteSize);
275 
276     m_SeekPtr += byteSize;
277 
278     return pCount;
279 }
280 
FileSize() const281 size_t ZipFile::FileSize() const {
282     return m_Size;
283 }
284 
Seek(size_t pOffset,aiOrigin pOrigin)285 aiReturn ZipFile::Seek(size_t pOffset, aiOrigin pOrigin) {
286     switch (pOrigin) {
287         case aiOrigin_SET: {
288             if (pOffset > m_Size) return aiReturn_FAILURE;
289             m_SeekPtr = pOffset;
290             return aiReturn_SUCCESS;
291         }
292 
293         case aiOrigin_CUR: {
294             if ((pOffset + m_SeekPtr) > m_Size) return aiReturn_FAILURE;
295             m_SeekPtr += pOffset;
296             return aiReturn_SUCCESS;
297         }
298 
299         case aiOrigin_END: {
300             if (pOffset > m_Size) return aiReturn_FAILURE;
301             m_SeekPtr = m_Size - pOffset;
302             return aiReturn_SUCCESS;
303         }
304         default:;
305     }
306 
307     return aiReturn_FAILURE;
308 }
309 
Tell() const310 size_t ZipFile::Tell() const {
311     return m_SeekPtr;
312 }
313 
314 // ----------------------------------------------------------------
315 // pImpl of the Zip Archive IO
316 class ZipArchiveIOSystem::Implement {
317 public:
318     static const unsigned int FileNameSize = 256;
319 
320     Implement(IOSystem *pIOHandler, const char *pFilename, const char *pMode);
321     ~Implement();
322 
323     bool isOpen() const;
324     void getFileList(std::vector<std::string> &rFileList);
325     void getFileListExtension(std::vector<std::string> &rFileList, const std::string &extension);
326     bool Exists(std::string &filename);
327     IOStream *OpenFile(std::string &filename);
328 
329     static void SimplifyFilename(std::string &filename);
330 
331 private:
332     void MapArchive();
333 
334 private:
335     typedef std::map<std::string, ZipFileInfo> ZipFileInfoMap;
336 
337     unzFile m_ZipFileHandle = nullptr;
338     ZipFileInfoMap m_ArchiveMap;
339 };
340 
Implement(IOSystem * pIOHandler,const char * pFilename,const char * pMode)341 ZipArchiveIOSystem::Implement::Implement(IOSystem *pIOHandler, const char *pFilename, const char *pMode) {
342     ai_assert(strcmp(pMode, "r") == 0);
343     ai_assert(pFilename != nullptr);
344     if (pFilename[0] == 0 || nullptr == pMode) {
345         return;
346     }
347 
348     zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler);
349     m_ZipFileHandle = unzOpen2(pFilename, &mapping);
350 }
351 
~Implement()352 ZipArchiveIOSystem::Implement::~Implement() {
353     if (m_ZipFileHandle != nullptr) {
354         unzClose(m_ZipFileHandle);
355     }
356 }
357 
MapArchive()358 void ZipArchiveIOSystem::Implement::MapArchive() {
359     if (m_ZipFileHandle == nullptr)
360         return;
361 
362     if (!m_ArchiveMap.empty())
363         return;
364 
365     //  At first ensure file is already open
366     if (unzGoToFirstFile(m_ZipFileHandle) != UNZ_OK)
367         return;
368 
369     // Loop over all files
370     do {
371         char filename[FileNameSize];
372         unz_file_info fileInfo;
373 
374         if (unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, nullptr, 0, nullptr, 0) == UNZ_OK) {
375             if (fileInfo.uncompressed_size != 0) {
376                 std::string filename_string(filename, fileInfo.size_filename);
377                 SimplifyFilename(filename_string);
378                 m_ArchiveMap.emplace(filename_string, ZipFileInfo(m_ZipFileHandle, fileInfo.uncompressed_size));
379             }
380         }
381     } while (unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE);
382 }
383 
isOpen() const384 bool ZipArchiveIOSystem::Implement::isOpen() const {
385     return (m_ZipFileHandle != nullptr);
386 }
387 
getFileList(std::vector<std::string> & rFileList)388 void ZipArchiveIOSystem::Implement::getFileList(std::vector<std::string> &rFileList) {
389     MapArchive();
390     rFileList.clear();
391 
392     for (const auto &file : m_ArchiveMap) {
393         rFileList.push_back(file.first);
394     }
395 }
396 
getFileListExtension(std::vector<std::string> & rFileList,const std::string & extension)397 void ZipArchiveIOSystem::Implement::getFileListExtension(std::vector<std::string> &rFileList, const std::string &extension) {
398     MapArchive();
399     rFileList.clear();
400 
401     for (const auto &file : m_ArchiveMap) {
402         if (extension == BaseImporter::GetExtension(file.first))
403             rFileList.push_back(file.first);
404     }
405 }
406 
Exists(std::string & filename)407 bool ZipArchiveIOSystem::Implement::Exists(std::string &filename) {
408     MapArchive();
409 
410     ZipFileInfoMap::const_iterator it = m_ArchiveMap.find(filename);
411     return (it != m_ArchiveMap.end());
412 }
413 
OpenFile(std::string & filename)414 IOStream *ZipArchiveIOSystem::Implement::OpenFile(std::string &filename) {
415     MapArchive();
416 
417     SimplifyFilename(filename);
418 
419     // Find in the map
420     ZipFileInfoMap::const_iterator zip_it = m_ArchiveMap.find(filename);
421     if (zip_it == m_ArchiveMap.cend())
422         return nullptr;
423 
424     const ZipFileInfo &zip_file = (*zip_it).second;
425     return zip_file.Extract(m_ZipFileHandle);
426 }
427 
ReplaceAll(std::string & data,const std::string & before,const std::string & after)428 inline void ReplaceAll(std::string &data, const std::string &before, const std::string &after) {
429     size_t pos = data.find(before);
430     while (pos != std::string::npos) {
431         data.replace(pos, before.size(), after);
432         pos = data.find(before, pos + after.size());
433     }
434 }
435 
ReplaceAllChar(std::string & data,const char before,const char after)436 inline void ReplaceAllChar(std::string &data, const char before, const char after) {
437     size_t pos = data.find(before);
438     while (pos != std::string::npos) {
439         data[pos] = after;
440         pos = data.find(before, pos + 1);
441     }
442 }
443 
SimplifyFilename(std::string & filename)444 void ZipArchiveIOSystem::Implement::SimplifyFilename(std::string &filename) {
445     ReplaceAllChar(filename, '\\', '/');
446 
447     // Remove all . and / from the beginning of the path
448     size_t pos = filename.find_first_not_of("./");
449     if (pos != 0)
450         filename.erase(0, pos);
451 
452     // Simplify "my/folder/../file.png" constructions, if any
453     static const std::string relative("/../");
454     const size_t relsize = relative.size() - 1;
455     pos = filename.find(relative);
456     while (pos != std::string::npos) {
457         // Previous slash
458         size_t prevpos = filename.rfind('/', pos - 1);
459         if (prevpos == pos)
460             filename.erase(0, pos + relative.size());
461         else
462             filename.erase(prevpos, pos + relsize - prevpos);
463 
464         pos = filename.find(relative);
465     }
466 }
467 
ZipArchiveIOSystem(IOSystem * pIOHandler,const char * pFilename,const char * pMode)468 ZipArchiveIOSystem::ZipArchiveIOSystem(IOSystem *pIOHandler, const char *pFilename, const char *pMode) :
469         pImpl(new Implement(pIOHandler, pFilename, pMode)) {
470 }
471 
472 // ----------------------------------------------------------------
473 // The ZipArchiveIO
ZipArchiveIOSystem(IOSystem * pIOHandler,const std::string & rFilename,const char * pMode)474 ZipArchiveIOSystem::ZipArchiveIOSystem(IOSystem *pIOHandler, const std::string &rFilename, const char *pMode) :
475         pImpl(new Implement(pIOHandler, rFilename.c_str(), pMode)) {
476 }
477 
~ZipArchiveIOSystem()478 ZipArchiveIOSystem::~ZipArchiveIOSystem() {
479     delete pImpl;
480 }
481 
Exists(const char * pFilename) const482 bool ZipArchiveIOSystem::Exists(const char *pFilename) const {
483     ai_assert(pFilename != nullptr);
484 
485     if (pFilename == nullptr) {
486         return false;
487     }
488 
489     std::string filename(pFilename);
490     return pImpl->Exists(filename);
491 }
492 
493 // This is always '/' in a ZIP
getOsSeparator() const494 char ZipArchiveIOSystem::getOsSeparator() const {
495     return '/';
496 }
497 
498 // Only supports Reading
Open(const char * pFilename,const char * pMode)499 IOStream *ZipArchiveIOSystem::Open(const char *pFilename, const char *pMode) {
500     ai_assert(pFilename != nullptr);
501 
502     for (size_t i = 0; pMode[i] != 0; ++i) {
503         ai_assert(pMode[i] != 'w');
504         if (pMode[i] == 'w')
505             return nullptr;
506     }
507 
508     std::string filename(pFilename);
509     return pImpl->OpenFile(filename);
510 }
511 
Close(IOStream * pFile)512 void ZipArchiveIOSystem::Close(IOStream *pFile) {
513     delete pFile;
514 }
515 
isOpen() const516 bool ZipArchiveIOSystem::isOpen() const {
517     return (pImpl->isOpen());
518 }
519 
getFileList(std::vector<std::string> & rFileList) const520 void ZipArchiveIOSystem::getFileList(std::vector<std::string> &rFileList) const {
521     return pImpl->getFileList(rFileList);
522 }
523 
getFileListExtension(std::vector<std::string> & rFileList,const std::string & extension) const524 void ZipArchiveIOSystem::getFileListExtension(std::vector<std::string> &rFileList, const std::string &extension) const {
525     return pImpl->getFileListExtension(rFileList, extension);
526 }
527 
isZipArchive(IOSystem * pIOHandler,const char * pFilename)528 bool ZipArchiveIOSystem::isZipArchive(IOSystem *pIOHandler, const char *pFilename) {
529     Implement tmp(pIOHandler, pFilename, "r");
530     return tmp.isOpen();
531 }
532 
isZipArchive(IOSystem * pIOHandler,const std::string & rFilename)533 bool ZipArchiveIOSystem::isZipArchive(IOSystem *pIOHandler, const std::string &rFilename) {
534     return isZipArchive(pIOHandler, rFilename.c_str());
535 }
536 
537 } // namespace Assimp
538