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