1 /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
2 *
3 * This library is open source and may be redistributed and/or modified under
4 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
5 * (at your option) any later version. The full license is in LICENSE file
6 * included with this distribution, and on the openscenegraph.org website.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * OpenSceneGraph Public License for more details.
12 */
13
ReaderWriterZIP()14 #include "ZipArchive.h"
15
16 #include <osgDB/FileUtils>
17 #include <osgDB/FileNameUtils>
18 #include <osgDB/ReadFile>
19 #include <osgDB/Registry>
20
21 #include <sys/types.h>
22 #include <sys/stat.h>
23
24 #include <sstream>
25 #include <cstdio>
26 #include "unzip.h"
27
28 #if !defined(S_ISDIR)
29 # if defined( _S_IFDIR) && !defined( __S_IFDIR)
30 # define __S_IFDIR _S_IFDIR
31 # endif
32 # define S_ISDIR(mode) (mode&__S_IFDIR)
33 #endif
34
35 #ifndef S_ISREG
36 #define S_ISREG(x) (((x) & S_IFMT) == S_IFREG)
37 #endif
38
39
40 ZipArchive::ZipArchive() :
41 _zipLoaded( false )
42 {
43 }
44
45 ZipArchive::~ZipArchive()
46 {
47 }
48
49 /** close the archive (on all threads) */
50 void ZipArchive::close()
51 {
openArchive(std::istream & fin,const Options * options) const52 if ( _zipLoaded )
53 {
54 OpenThreads::ScopedLock<OpenThreads::Mutex> exclusive(_zipMutex);
55 if ( _zipLoaded )
56 {
57 // close the file (on one thread since it's a shared file)
58 const PerThreadData& data = getDataNoLock();
59 CloseZip( data._zipHandle );
60
61 // clear out the file handles
62 _perThreadData.clear();
63
64 // clear out the index.
65 _zipIndex.clear();
66
67 _zipLoaded = false;
68 }
69 }
70 }
71
72 /** return true if file exists in archive.*/
73 bool ZipArchive::fileExists(const std::string& filename) const
74 {
75 return GetZipEntry(filename) != NULL;
76 }
77
78 /** Get the file name which represents the master file recorded in the Archive.*/
79 std::string ZipArchive::getMasterFileName() const
80 {
81 return std::string();
82 }
83
84 std::string ZipArchive::getArchiveFileName() const
85 {
86 std::string result;
87 if( _zipLoaded )
88 {
89 result = _mainRecord.name;
90 }
91 return result;
92 }
93
94 /** Get the full list of file names available in the archive.*/
95 bool ZipArchive::getFileNames(osgDB::Archive::FileNameList& fileNameList) const
96 {
97 if(_zipLoaded)
98 {
99 ZipEntryMap::const_iterator iter = _zipIndex.begin();
100
101 for(;iter != _zipIndex.end(); ++iter)
102 {
103 fileNameList.push_back((*iter).first);
104 }
105
106 return true;
107 }
108 else
109 {
readNode(const std::string & file,const osgDB::ReaderWriter::Options * options) const110 return false;
111 }
112 }
113
114 bool ZipArchive::open(const std::string& file, ArchiveStatus status, const osgDB::ReaderWriter::Options* options)
115 {
116 if ( !_zipLoaded )
117 {
118 // exclusive lock when we open for the first time:
119 OpenThreads::ScopedLock<OpenThreads::Mutex> exclusiveLock( _zipMutex );
120
121 if ( !_zipLoaded ) // double-check avoids race condition
122 {
123 std::string ext = osgDB::getLowerCaseFileExtension(file);
124 if (!acceptsExtension(ext)) return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
125
126 // save the filename + password so other threads can open the file
127 _filename = osgDB::findDataFile( file, options );
128 if (_filename.empty()) return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
129
130 _password = ReadPassword(options);
131
132 // open the zip file in this thread:
133 const PerThreadData& data = getDataNoLock();
134
readNode(std::istream & fin,const osgDB::ReaderWriter::Options * options) const135 // establish a shared (read-only) index:
136 if ( data._zipHandle != NULL )
137 {
138 IndexZipFiles( data._zipHandle );
139 _zipLoaded = true;
140 }
141 }
142 }
143
144 return _zipLoaded;
145 }
146
147 bool ZipArchive::open(std::istream& fin, const osgDB::ReaderWriter::Options* options)
148 {
149 if ( !_zipLoaded )
150 {
151 // exclusive lock when we open for the first time:
readImageFromArchive(osgDB::Archive & archive,const osgDB::ReaderWriter::Options * options) const152 OpenThreads::ScopedLock<OpenThreads::Mutex> exclusive(_zipMutex);
153
154 if ( !_zipLoaded ) // double-check avoids race condition
155 {
156 osgDB::ReaderWriter::ReadResult result = osgDB::ReaderWriter::ReadResult(osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED);
157
158 if (fin.fail()) return false;
159
160 // read the stream into a memory buffer that we'll keep around for any other
161 // threads that want to open the file
162 std::stringstream buf;
163 buf << fin.rdbuf();
164 _membuffer = buf.str();
165
166 _password = ReadPassword(options);
167
168 // open on this thread:
169 const PerThreadData& data = getDataNoLock();
170
171 if ( data._zipHandle != NULL )
172 {
173 IndexZipFiles( data._zipHandle );
174 _zipLoaded = true;
175 }
readImage(const std::string & file,const Options * options) const176 }
177 }
178
179 return _zipLoaded;
180 }
181
182 osgDB::ReaderWriter::ReadResult ZipArchive::readObject(const std::string& file, const osgDB::ReaderWriter::Options* options) const
183 {
184 osgDB::ReaderWriter::ReadResult rresult = osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
185
186 std::string ext = osgDB::getLowerCaseFileExtension(file);
187 if (!_zipLoaded || !acceptsExtension(ext)) return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
188
189 const ZIPENTRY* ze = GetZipEntry(file);
190 if(ze != NULL)
191 {
192 std::stringstream buffer;
193
194 osgDB::ReaderWriter* rw = ReadFromZipEntry(ze, options, buffer);
195 if (rw != NULL)
196 {
197 // Setup appropriate options
198 osg::ref_ptr<osgDB::ReaderWriter::Options> local_opt = options ?
199 static_cast<osgDB::ReaderWriter::Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) :
200 new osgDB::ReaderWriter::Options;
readImage(std::istream & fin,const osgDB::ReaderWriter::Options * options) const201
202 local_opt->setPluginStringData("STREAM_FILENAME", osgDB::getSimpleFileName(ze->name));
203
204 osgDB::ReaderWriter::ReadResult readResult = rw->readObject(buffer,local_opt.get());
205 if (readResult.success())
206 {
207 return readResult;
208 }
209 }
210 }
211
212 return rresult;
213 }
214
215 osgDB::ReaderWriter::ReadResult ZipArchive::readImage(const std::string& file,const osgDB::ReaderWriter::Options* options) const
216 {
217 osgDB::ReaderWriter::ReadResult rresult = osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
218
219 std::string ext = osgDB::getLowerCaseFileExtension(file);
220 if (!_zipLoaded || !acceptsExtension(ext)) return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
221
222 const ZIPENTRY* ze = GetZipEntry(file);
223 if(ze != NULL)
224 {
225 std::stringstream buffer;
226
227 osgDB::ReaderWriter* rw = ReadFromZipEntry(ze, options, buffer);
228 if (rw != NULL)
229 {
230 // Setup appropriate options
231 osg::ref_ptr<osgDB::ReaderWriter::Options> local_opt = options ?
232 static_cast<osgDB::ReaderWriter::Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) :
233 new osgDB::ReaderWriter::Options;
234
235 local_opt->setPluginStringData("STREAM_FILENAME", osgDB::getSimpleFileName(ze->name));
236
237 osgDB::ReaderWriter::ReadResult readResult = rw->readImage(buffer,local_opt.get());
238 if (readResult.success())
239 {
240 return readResult;
241 }
242 }
243 }
244
245 return rresult;
246 }
247
248 osgDB::ReaderWriter::ReadResult ZipArchive::readHeightField(const std::string& file,const osgDB::ReaderWriter::Options* options) const
249 {
250 osgDB::ReaderWriter::ReadResult rresult = osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
251
252 std::string ext = osgDB::getLowerCaseFileExtension(file);
253 if (!_zipLoaded || !acceptsExtension(ext)) return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
254
255 const ZIPENTRY* ze = GetZipEntry(file);
256 if(ze != NULL)
257 {
258 std::stringstream buffer;
259
260 osgDB::ReaderWriter* rw = ReadFromZipEntry(ze, options, buffer);
261 if (rw != NULL)
262 {
263 // Setup appropriate options
264 osg::ref_ptr<osgDB::ReaderWriter::Options> local_opt = options ?
265 options->cloneOptions() :
266 new osgDB::ReaderWriter::Options;
267
268 local_opt->setPluginStringData("STREAM_FILENAME", osgDB::getSimpleFileName(ze->name));
269
270 osgDB::ReaderWriter::ReadResult readResult = rw->readObject(buffer,local_opt.get());
271 if (readResult.success())
272 {
273 return readResult;
274 }
275 }
276 }
277
278 return rresult;
279 }
280
281 osgDB::ReaderWriter::ReadResult ZipArchive::readNode(const std::string& file,const osgDB::ReaderWriter::Options* options) const
282 {
283 osgDB::ReaderWriter::ReadResult rresult = osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
284
285 std::string ext = osgDB::getLowerCaseFileExtension(file);
286 if (!_zipLoaded || !acceptsExtension(ext)) return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
287
288 const ZIPENTRY* ze = GetZipEntry(file);
289 if(ze != NULL)
290 {
291 std::stringstream buffer;
292
293 osgDB::ReaderWriter* rw = ReadFromZipEntry(ze, options, buffer);
294 if (rw != NULL)
295 {
296 // Setup appropriate options
297 osg::ref_ptr<osgDB::ReaderWriter::Options> local_opt = options ?
298 options->cloneOptions() :
299 new osgDB::ReaderWriter::Options;
300
301 local_opt->setPluginStringData("STREAM_FILENAME", osgDB::getSimpleFileName(ze->name));
302
303 osgDB::ReaderWriter::ReadResult readResult = rw->readNode(buffer,local_opt.get());
304 if (readResult.success())
305 {
306 return readResult;
307 }
308 }
309 }
310
311 return rresult;
312 }
313
314
315 osgDB::ReaderWriter::ReadResult ZipArchive::readShader(const std::string& file,const osgDB::ReaderWriter::Options* options) const
316 {
317 osgDB::ReaderWriter::ReadResult rresult = osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
318
319 std::string ext = osgDB::getLowerCaseFileExtension(file);
320 if (!_zipLoaded || !acceptsExtension(ext)) return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
321
322 const ZIPENTRY* ze = GetZipEntry(file);
323 if(ze != NULL)
324 {
325 std::stringstream buffer;
326
327 osgDB::ReaderWriter* rw = ReadFromZipEntry(ze, options, buffer);
328 if (rw != NULL)
329 {
330 // Setup appropriate options
331 osg::ref_ptr<osgDB::ReaderWriter::Options> local_opt = options ?
332 options->cloneOptions() :
333 new osgDB::ReaderWriter::Options;
334
335 local_opt->setPluginStringData("STREAM_FILENAME", osgDB::getSimpleFileName(ze->name));
336
337 osgDB::ReaderWriter::ReadResult readResult = rw->readShader(buffer,local_opt.get());
338 if (readResult.success())
339 {
340 return readResult;
341 }
342 }
343 }
344
345 return rresult;
346 }
347
348 osgDB::ReaderWriter::WriteResult ZipArchive::writeObject(const osg::Object& /*obj*/,const std::string& /*fileName*/,const osgDB::ReaderWriter::Options*) const
349 {
350 return osgDB::ReaderWriter::WriteResult(osgDB::ReaderWriter::WriteResult::FILE_NOT_HANDLED);
351 }
352
353 osgDB::ReaderWriter::WriteResult ZipArchive::writeImage(const osg::Image& /*image*/,const std::string& /*fileName*/,const osgDB::ReaderWriter::Options*) const
354 {
355 return osgDB::ReaderWriter::WriteResult(osgDB::ReaderWriter::WriteResult::FILE_NOT_HANDLED);
356 }
357
358 osgDB::ReaderWriter::WriteResult ZipArchive::writeHeightField(const osg::HeightField& /*heightField*/,const std::string& /*fileName*/,const osgDB::ReaderWriter::Options*) const
359 {
360 return osgDB::ReaderWriter::WriteResult(osgDB::ReaderWriter::WriteResult::FILE_NOT_HANDLED);
361 }
362
363 osgDB::ReaderWriter::WriteResult ZipArchive::writeNode(const osg::Node& /*node*/,const std::string& /*fileName*/,const osgDB::ReaderWriter::Options*) const
364 {
365 return osgDB::ReaderWriter::WriteResult(osgDB::ReaderWriter::WriteResult::FILE_NOT_HANDLED);
366 }
367
368 osgDB::ReaderWriter::WriteResult ZipArchive::writeShader(const osg::Shader& /*shader*/,const std::string& /*fileName*/,const osgDB::ReaderWriter::Options*) const
369 {
370 return osgDB::ReaderWriter::WriteResult(osgDB::ReaderWriter::WriteResult::FILE_NOT_HANDLED);
371 }
372
373
374 osgDB::ReaderWriter* ZipArchive::ReadFromZipEntry(const ZIPENTRY* ze, const osgDB::ReaderWriter::Options* options, std::stringstream& buffer) const
375 {
376 if (ze != 0)
377 {
378 char* ibuf = new (std::nothrow) char[ze->unc_size];
379 if (ibuf)
380 {
381 // fetch the handle for the current thread:
382 const PerThreadData& data = getData();
383 if ( data._zipHandle != NULL )
384 {
385 ZRESULT result = UnzipItem(data._zipHandle, ze->index, ibuf, ze->unc_size);
386 bool unzipSuccesful = CheckZipErrorCode(result);
387 if(unzipSuccesful)
388 {
389 buffer.write(ibuf,ze->unc_size);
390 }
391
392 delete[] ibuf;
393
394 std::string file_ext = osgDB::getFileExtension(ze->name);
395
396 osgDB::ReaderWriter* rw = osgDB::Registry::instance()->getReaderWriterForExtension(file_ext);
397 if (rw != NULL)
398 {
399 return rw;
400 }
401 }
402 }
403 else
404 {
405 //std::cout << "Error- failed to allocate enough memory to unzip file '" << ze->name << ", with size '" << ze->unc_size << std::endl;
406 }
407 }
408
409 return NULL;
410 }
411
412 void CleanupFileString(std::string& strFileOrDir)
413 {
414 if (strFileOrDir.empty())
415 {
416 return;
417 }
418
419 // convert all separators to unix-style for conformity
420 for (unsigned int i = 0; i < strFileOrDir.length(); ++i)
421 {
422 if (strFileOrDir[i] == '\\')
423 {
424 strFileOrDir[i] = '/';
425 }
426 }
427
428 // get rid of trailing separators
429 if (strFileOrDir[strFileOrDir.length()-1] == '/')
430 {
431 strFileOrDir = strFileOrDir.substr(0, strFileOrDir.length()-1);
432 }
433
434 //add a beginning separator
435 if(strFileOrDir[0] != '/')
436 {
437 strFileOrDir.insert(0, "/");
438 }
439 }
440
441 void ZipArchive::IndexZipFiles(HZIP hz)
442 {
443 if(hz != NULL && !_zipLoaded)
444 {
445 //mZipRecord = hz;
446
447 GetZipItem(hz, -1, &_mainRecord);
448 int numitems = _mainRecord.index;
449
450 // Now loop through each file in zip
451 for (int i = 0; i < numitems; i++)
452 {
453 ZIPENTRY* ze = new ZIPENTRY();
454
455 GetZipItem(hz, i, ze);
456 std::string name = ze->name;
457
458 CleanupFileString(name);
459
460 if(!name.empty())
461 {
462 _zipIndex.insert(ZipEntryMapping(name, ze));
463 }
464 }
465 }
466 }
467
468 ZIPENTRY* ZipArchive::GetZipEntry(const std::string& filename)
469 {
470 ZIPENTRY* ze = NULL;
471 std::string fileToLoad = filename;
472 CleanupFileString(fileToLoad);
473
474 ZipEntryMap::iterator iter = _zipIndex.find(fileToLoad);
475 if(iter != _zipIndex.end())
476 {
477 ze = (*iter).second;
478 }
479
480 return ze;
481 }
482
483 const ZIPENTRY* ZipArchive::GetZipEntry(const std::string& filename) const
484 {
485 ZIPENTRY* ze = NULL;
486 std::string fileToLoad = filename;
487 CleanupFileString(fileToLoad);
488
489 ZipEntryMap::const_iterator iter = _zipIndex.find(fileToLoad);
490 if(iter != _zipIndex.end())
491 {
492 ze = (*iter).second;
493 }
494
495 return ze;
496 }
497
498 osgDB::FileType ZipArchive::getFileType(const std::string& filename) const
499 {
500 const ZIPENTRY* ze = GetZipEntry(filename);
501 if(ze != NULL)
502 {
503 #ifdef ZIP_STD
504 if (ze->attr & S_IFDIR)
505 #else
506 if (ze->attr & FILE_ATTRIBUTE_DIRECTORY)
507 #endif
508 {
509 return osgDB::DIRECTORY;
510 }
511 else
512 {
513 return osgDB::REGULAR_FILE;
514 }
515 }
516
517 return osgDB::FILE_NOT_FOUND;
518 }
519
520 osgDB::DirectoryContents ZipArchive::getDirectoryContents(const std::string& dirName) const
521 {
522 osgDB::DirectoryContents dirContents;
523
524 ZipEntryMap::const_iterator iter = _zipIndex.begin();
525 ZipEntryMap::const_iterator iterEnd = _zipIndex.end();
526
527 for(; iter != iterEnd; ++iter)
528 {
529 std::string searchPath = dirName;
530 CleanupFileString(searchPath);
531
532 if(iter->first.size() > searchPath.size())
533 {
534 size_t endSubElement = iter->first.find(searchPath);
535
536 //we match the whole string in the beginning of the path
537 if(endSubElement == 0)
538 {
539 std::string remainingFile = iter->first.substr(searchPath.size() + 1, std::string::npos);
540 size_t endFileToken = remainingFile.find_first_of('/');
541
542 if(endFileToken == std::string::npos)
543 {
544 dirContents.push_back(remainingFile);
545 }
546 }
547 }
548 }
549
550 return dirContents;
551 }
552
553 std::string ZipArchive::ReadPassword(const osgDB::ReaderWriter::Options* options) const
554 {
555 //try pulling it off the options first
556 std::string password = "";
557 if(options != NULL)
558 {
559 const osgDB::AuthenticationMap* credentials = options->getAuthenticationMap();
560 if(credentials != NULL)
561 {
562 const osgDB::AuthenticationDetails* details = credentials->getAuthenticationDetails("ZipPlugin");
563 if(details != NULL)
564 {
565 password = details->password;
566 }
567 }
568 }
569
570 //if no password, try the registry
571 if(password.empty())
572 {
573 osgDB::Registry* reg = osgDB::Registry::instance();
574 if(reg != NULL)
575 {
576 const osgDB::AuthenticationMap* credentials = reg->getAuthenticationMap();
577 if(credentials != NULL)
578 {
579 const osgDB::AuthenticationDetails* details = credentials->getAuthenticationDetails("ZipPlugin");
580 if(details != NULL)
581 {
582 password = details->password;
583 }
584 }
585 }
586 }
587
588 return password;
589 }
590
591 bool ZipArchive::CheckZipErrorCode(ZRESULT result) const
592 {
593 if(result == ZR_OK)
594 {
595 return true;
596 }
597 else
598 {
599 unsigned buf_size = 1025;
600 char* buf = new (std::nothrow) char[buf_size];
601 buf[buf_size - 1] = 0;
602
603 if (buf)
604 {
605 FormatZipMessage(result, buf, buf_size - 1);
606
607 //print error message
608 //sprintf(buf, "%s");
609 OSG_WARN << "Error loading zip file: " << getArchiveFileName() << ", Zip loader returned error: " << buf << "\n";
610 delete [] buf;
611 }
612
613 return false;
614 }
615 }
616
617 const ZipArchive::PerThreadData&
618 ZipArchive::getData() const
619 {
620 OpenThreads::ScopedLock<OpenThreads::Mutex> exclusive( const_cast<ZipArchive*>(this)->_zipMutex );
621 return getDataNoLock();
622 }
623
624
625 const ZipArchive::PerThreadData&
626 ZipArchive::getDataNoLock() const
627 {
628 // get/create data for the currently running thread:
629 OpenThreads::Thread* current = OpenThreads::Thread::CurrentThread();
630
631 PerThreadDataMap::const_iterator i = _perThreadData.find( current );
632
633 if ( i == _perThreadData.end() || i->second._zipHandle == NULL )
634 {
635 // cache pattern: cast to const for caching purposes
636 ZipArchive* ncThis = const_cast<ZipArchive*>(this);
637
638 // data does not already exist, so open the ZIP with a handle exclusively for this thread:
639 PerThreadData& data = ncThis->_perThreadData[current];
640 if ( !_filename.empty() )
641 {
642 data._zipHandle = OpenZip( _filename.c_str(), _password.c_str() );
643 }
644 else if ( !_membuffer.empty() )
645 {
646 data._zipHandle = OpenZip( (void*)_membuffer.c_str(), _membuffer.length(), _password.c_str() );
647 }
648 else
649 {
650 data._zipHandle = NULL;
651 }
652 return data;
653 }
654 else
655 {
656 return i->second;
657 }
658 }
659