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