/**************************************************************************** Copyright (c) 2006, Radon Labs GmbH Copyright (c) 2011-2013,WebJet Business Division,CYOU http://www.genesis-3d.com.cn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "stdneb.h" #include "io/zipfs/ziparchive.h" #include "io/zipfs/zipfileentry.h" #include "io/zipfs/zipdirentry.h" #include "io/assignregistry.h" namespace IO { __ImplementClass(IO::ZipArchive, 'ZPAR', IO::ArchiveBase); using namespace Util; //------------------------------------------------------------------------------ /** */ ZipArchive::ZipArchive() : zipFileHandle(0) { // empty } //------------------------------------------------------------------------------ /** */ ZipArchive::~ZipArchive() { if (this->IsValid()) { this->Discard(); } } //------------------------------------------------------------------------------ /** This opens the zip archive and reads the table of content as a tree of ZipDirEntry and ZipFileEntry objects. */ bool ZipArchive::Setup(const URI& zipFileURI) { n_assert(!this->IsValid()); n_assert(0 == this->zipFileHandle); n_warning(zipFileURI.AsString().AsCharPtr()); if (ArchiveBase::Setup(zipFileURI)) { // extract the root location of the zip archive this->rootPath = this->uri.LocalPath().ExtractDirName(); n_warning( this->rootPath.AsCharPtr()); // open the zip file URI absPath = AssignRegistry::Instance()->ResolveAssigns(this->uri); String localPath = absPath.LocalPath(); #if WIN32 // localPath.Append(".zip"); #endif n_warning( localPath.AsCharPtr()); this->zipFileHandle = unzOpen(localPath.AsCharPtr()); if (0 == this->zipFileHandle) { return false; } // read the table of contents this->ParseTableOfContents(); return true; } else { return false; } } //------------------------------------------------------------------------------ /** This closes the zip archive, releasing the table of contents and closing the zip file. */ void ZipArchive::Discard() { n_assert(this->IsValid()); unzClose(this->zipFileHandle); this->zipFileHandle = 0; ArchiveBase::Discard(); } //------------------------------------------------------------------------------ /** Internal method which parses the table of contents of the into a tree of ZipDirEntry and ZipFileEntry objects. */ void ZipArchive::ParseTableOfContents() { n_assert(this->IsValid()); // for each entry of the zip file... int walkRes = unzGoToFirstFile(this->zipFileHandle); if (UNZ_OK == walkRes) do { // get info about current file char curFileName[512]; int fileInfoRes = unzGetCurrentFileInfo(this->zipFileHandle, 0, curFileName, sizeof(curFileName), 0, 0, 0, 0); n_assert(UNZ_OK == fileInfoRes); this->AddEntry(curFileName); walkRes = unzGoToNextFile(this->zipFileHandle); } while (UNZ_OK == walkRes); if (UNZ_END_OF_LIST_OF_FILE != walkRes) { n_error("ZipArchive: error in parsing zip file '%s'!\n", this->uri.AsString().AsCharPtr()); } } //------------------------------------------------------------------------------ /** This will create a new ZipFileEntry or ZipDirEntry object and sort it into the entry tree. Missing ZipDirEntry objects in the path will be created as needed. */ void ZipArchive::AddEntry(const String& path) { n_assert(path.IsValid()); // first check if the path end with a slash, if // yes it's a directory bool isDirectory = false; char lastChar = path[path.Length() - 1]; if ((lastChar == '/') || (lastChar == '\\')) { isDirectory = true; } // tokenize the path into directory and filename components Array pathTokens; path.Tokenize("/\\", pathTokens); ZipDirEntry* dirEntry = &(this->rootEntry); if (pathTokens.Size() > 1) { // find directory, create missing directory entries on the way IndexT i; for (i = 0; i < (pathTokens.Size() - 1); i++) { StringAtom curToken = pathTokens[i]; ZipDirEntry* childDirEntry = dirEntry->FindDirEntry(curToken); if (0 == childDirEntry) { // need to create new dir entry childDirEntry = dirEntry->AddDirEntry(curToken); } dirEntry = childDirEntry; } } // create final entry and add to last dir entry StringAtom finalName(pathTokens.Back()); if (isDirectory) { dirEntry->AddDirEntry(finalName); } else { ZipFileEntry* finalFileEntry = dirEntry->AddFileEntry(finalName); finalFileEntry->Setup(finalName, this->zipFileHandle, &this->archiveCritSect); } } //------------------------------------------------------------------------------ /** Test if an absolute path points into the zip archive and return a locale path into the zip archive. This will not test, whether the file or directory inside the zip archive actually exists, only if the path points INTO the zip archive by checking against the location directory of the zip archive. */ String ZipArchive::ConvertToPathInArchive(const Util::String& absPath) const { // test if the absolute path starts with our root path IndexT rootPathIndex = absPath.FindStringIndex(this->rootPath, 0); if (0 == rootPathIndex) { // strip the root path from the absolute path String localPath = absPath; localPath.SubstituteString(this->rootPath, ""); return localPath; } // path doesn't point into this archive return ""; } //------------------------------------------------------------------------------ /** */ const ZipFileEntry* ZipArchive::FindFileEntry(const String& pathInZipArchive) const { // convert to local path into zip archive and split into components, // fail if string doesn't point into archive Array pathTokens; if (0 == pathInZipArchive.Tokenize("/\\", pathTokens)) { return 0; } // n_warning(pathInZipArchive.AsCharPtr()); // walk directory entries const ZipDirEntry* dirEntry = &this->rootEntry; if (pathTokens.Size() > 1) { IndexT i; #if WIN32 for (i = 1; i < (pathTokens.Size() - 1); i++) { ZipDirEntry* subDirEntry = dirEntry->FindDirEntry(pathTokens[i]); if (0 != subDirEntry) { dirEntry = subDirEntry; } else { // missing subdirectory return 0; } } #else for (i = 1; i < (pathTokens.Size() - 1); i++) { ZipDirEntry* subDirEntry = dirEntry->FindDirEntry(pathTokens[i]); if (0 != subDirEntry) { dirEntry = subDirEntry; } else { // missing subdirectory n_warning("zip file missing dic"); n_warning(pathTokens[i].AsCharPtr()); return 0; } } #endif } // find the final file entry const ZipFileEntry* fileEntry = dirEntry->FindFileEntry(pathTokens.Back()); return fileEntry; } //------------------------------------------------------------------------------ /** */ ZipFileEntry* ZipArchive::FindFileEntry(const String& pathInZipArchive) { const ZipArchive *a = static_cast(this); return const_cast(a->FindFileEntry(pathInZipArchive)); } //------------------------------------------------------------------------------ /** */ const ZipDirEntry* ZipArchive::FindDirEntry(const String& pathInZipArchive) const { // convert to local path into zip archive and split into components, // fail if string doesn't point into archive Array pathTokens; if (0 == pathInZipArchive.Tokenize("/\\", pathTokens)) { return 0; } // walk directory entries const ZipDirEntry* dirEntry = &(this->rootEntry); IndexT i; for (i = 0; i < pathTokens.Size(); i++) { ZipDirEntry* subDirEntry = dirEntry->FindDirEntry(pathTokens[i]); if (0 != subDirEntry) { dirEntry = subDirEntry; } else { // missing subdirectory return 0; } } return dirEntry; } //------------------------------------------------------------------------------ /** */ Array ZipArchive::ListFiles(const String& dirPathInZipArchive, const String& pattern) const { Array result; const ZipDirEntry* dirEntry = this->FindDirEntry(dirPathInZipArchive); if (0 != dirEntry) { const Array& files = dirEntry->GetFileEntries(); String fileName; IndexT i; for (i = 0; i < files.Size(); i++) { fileName = files[i].GetName().Value(); if (String::MatchPattern(fileName, pattern)) { result.Append(fileName); } } } return result; } //------------------------------------------------------------------------------ /** */ Array ZipArchive::ListDirectories(const String& dirPathInZipArchive, const String& pattern) const { Array result; const ZipDirEntry* dirEntry = this->FindDirEntry(dirPathInZipArchive); if (0 != dirEntry) { const Array& subDirs = dirEntry->GetDirEntries(); String subDirName; IndexT i; for (i = 0; i < subDirs.Size(); i++) { subDirName = subDirs[i].GetName().Value(); if (String::MatchPattern(subDirName, pattern)) { result.Append(subDirName); } } } return result; } //------------------------------------------------------------------------------ /** This method takes a normal "file:" scheme URI and convertes it into a "zip:" scheme URI which points to the file in this zip archive. This is used by the IoServer for transparent file access into zip archives. */ URI ZipArchive::ConvertToArchiveURI(const URI& fileURI) const { n_assert(fileURI.LocalPath().IsValid()); // localize path into archive, fail hard if URI doesn't point into archive String localPath = this->ConvertToPathInArchive(fileURI.LocalPath()); if (!localPath.IsValid()) { n_error("ZipArchive::ConvertToZipURI(): file '%s' doesn't point into this zip archive (%s)!\n", fileURI.AsString().AsCharPtr(), this->uri.AsString().AsCharPtr()); } URI zipURI = this->uri; zipURI.SetScheme("zip"); String query; query.Append("file="); query.Append(localPath); zipURI.SetQuery(query); return zipURI; } } // namespace IO