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