#include "compoundfile.h" #include "cfexception.h" #include "streamview.h" #include "../../DesktopEditor/common/File.h" #include #include using namespace CFCPP; CompoundFile::CompoundFile() : CompoundFile(CFSVersion::Ver_3, CFSConfiguration::Default) {} CompoundFile::CompoundFile(const std::wstring &fileName, CFSUpdateMode updateMode, CFSConfiguration configParameters) { configuration = configParameters; validationExceptionEnabled = !(configParameters & CFSConfiguration::NoValidationException); sectorRecycle = configParameters & CFSConfiguration::SectorRecycle; this->updateMode = updateMode; eraseFreeSectors = configParameters & CFSConfiguration::EraseFreeSectors; LoadFile(fileName); DIFAT_SECTOR_FAT_ENTRIES_COUNT = (GetSectorSize() / 4) - 1; FAT_SECTOR_ENTRIES_COUNT = (GetSectorSize() / 4); } CompoundFile::CompoundFile(CFSVersion cfsVersion, CFSConfiguration configFlags) : header((ushort)cfsVersion) { configuration = configFlags; sectorRecycle = configFlags & CFSConfiguration::SectorRecycle; bool eraseFreeSectors = configFlags & CFSConfiguration::EraseFreeSectors; // if (cfsVersion == CFSVersion::Ver_4) // sectors.OnVer3SizeLimitReached += new Ver3SizeLimitReached(OnSizeLimitReached); DIFAT_SECTOR_FAT_ENTRIES_COUNT = (GetSectorSize() / 4) - 1; FAT_SECTOR_ENTRIES_COUNT = (GetSectorSize() / 4); //Root -- std::shared_ptr rootDir = DirectoryEntry::New("Root Entry", StgType.StgRoot, directoryEntries); rootDir.StgColor = StgColor.Black; //InsertNewDirectoryEntry(rootDir); rootStorage = new CFStorage(this, rootDir); } CompoundFile::CompoundFile(const std::wstring &fileName) { sectorRecycle = false; updateMode = CFSUpdateMode::ReadOnly; eraseFreeSectors = false; LoadFile(fileName); DIFAT_SECTOR_FAT_ENTRIES_COUNT = (GetSectorSize() / 4) - 1; FAT_SECTOR_ENTRIES_COUNT = (GetSectorSize() / 4); } CompoundFile::CompoundFile(Stream stream) { LoadStream(stream); DIFAT_SECTOR_FAT_ENTRIES_COUNT = (GetSectorSize() / 4) - 1; FAT_SECTOR_ENTRIES_COUNT = (GetSectorSize() / 4); } void CompoundFile::OnSizeLimitReached() { std::shared_ptr rangeLockSector( new Sector(GetSectorSize(), sourceStream)); rangeLockSector->type = SectorType::RangeLockSector; _transactionLockAdded = true; _lockSectorId = rangeLockSector->id; sectors.Add(rangeLockSector); } void CompoundFile::Commit() { Commit(false); } void CompoundFile::Commit(bool releaseMemory) { if (_disposed) throw new CFDisposedException("Compound File closed: cannot commit data"); if (updateMode != CFSUpdateMode::Update) throw new CFInvalidOperation("Cannot commit data in Read-Only update mode"); #if !defined(FLAT_WRITE) int sId = -1; int sCount = 0; int bufOffset = 0; #endif int sSize = GetSectorSize(); if (header.majorVersion != (ushort)CFSVersion::Ver_3) CheckForLockSector(); sourceStream->seekp(0, std::ios::beg); std::vector zeroArray(sSize, 0); sourceStream->write(zeroArray.data(), zeroArray.size()); zeroArray.clear(); CommitDirectory(); bool gap = true; for (int i = 0; i < (int)sectors.largeArraySlices.size(); i++) { #if defined (FLAT_WRITE) //Note: //Here sectors should not be loaded dynamically because //if they are null it means that no change has involved them; std::shared_ptr s = sectors[i]; if (s.get() != nullptr && s->dirtyFlag) { if (gap) sourceStream->seekp((long)((long)(sSize) + (long)i * (long)sSize), std::ios::beg); sourceStream->write(reinterpret_cast(s->GetData().data()), sSize); sourceStream->flush(); s->dirtyFlag = false; gap = false; } else { gap = true; } if (s.get() != nullptr && releaseMemory) { s->ReleaseData(); s.reset(); sectors[i].reset(); } #else std::shared_ptr s = sectors[i]; if (s.get() != nullptr && s->dirtyFlag && flushingQueue.size() < (int)(buffer.Length / sSize)) { //First of a block of contiguous sectors, mark id, start enqueuing if (gap) { sId = s->id; gap = false; } flushingQueue.push(s); } else { //Found a gap, stop enqueuing, flush a write operation gap = true; sCount = flushingQueue.size(); if (sCount == 0) continue; bufOffset = 0; while (flushingQueue.Count > 0) { Sector r = flushingQueue.Dequeue(); Buffer.BlockCopy(r.GetData(), 0, buffer, bufOffset, sSize); r.DirtyFlag = false; if (releaseMemory) { r.ReleaseData(); } bufOffset += sSize; } sourceStream.Seek(((long)sSize + (long)sId * (long)sSize), SeekOrigin.Begin); sourceStream.Write(buffer, 0, sCount * sSize); //Console.WriteLine("W - " + (int)(sCount * sSize )); } #endif } #if !FLAT_WRITE sCount = flushingQueue.Count; bufOffset = 0; while (flushingQueue.Count > 0) { Sector r = flushingQueue.Dequeue(); Buffer.BlockCopy(r.GetData(), 0, buffer, bufOffset, sSize); r.DirtyFlag = false; if (releaseMemory) { r.ReleaseData(); r = null; } bufOffset += sSize; } if (sCount != 0) { sourceStream.Seek((long)sSize + (long)sId * (long)sSize, SeekOrigin.Begin); sourceStream.Write(buffer, 0, sCount * sSize); //Console.WriteLine("W - " + (int)(sCount * sSize)); } #endif // Seek to beginning position and save header (first 512 or 4096 bytes) sourceStream->seekg(0, std::ios::beg); header.Write(sourceStream); sourceStream-> SetLength((long)(sectors.Count + 1) * sSize); sourceStream->flush(); if (releaseMemory) GC.Collect(); //} //catch (Exception ex) //{ // throw new CFException("Internal error while committing data", ex); //} } void CompoundFile::Close() { Close(true); } std::shared_ptr CompoundFile::CreateNewTree() { return std::shared_ptr(new RedBlackTree::RBTree); } std::shared_ptr CompoundFile::GetChildrenTree(int sid) { std::shared_ptr bst(new RedBlackTree::RBTree()); DoLoadChildren(bst, directoryEntries[sid]); return bst; } bool CompoundFile::IsClosed() const { return _disposed; } /// /// Load compound file from an existing stream. /// /// Stream to load compound file from void CompoundFile::Load(Stream stream) { try { this->header = Header(); this.directoryEntries = new List(); this->sourceStream = stream; header.Read(stream); int n_sector = std::ceil(((double)(Length(stream) - GetSectorSize()) / (double)GetSectorSize())); if (Length(stream) > 0x7FFFFF0) this->_transactionLockAllocated = true; sectors.Clear(); //sectors = new ArrayList(); for (int i = 0; i < n_sector; i++) { sectors.Add({}); } LoadDirectories(); this.rootStorage = new CFStorage(this, directoryEntries[0]); } catch (...) { if (stream.get() != nullptr && closeStream) // stream->clear(); // close throw; } } void CompoundFile::Save(std::wstring wFileName) { if (_disposed) throw new CFException("Compound File closed: cannot save data"); BYTE* pUtf8 = NULL; LONG lLen = 0; NSFile::CUtf8Converter::GetUtf8StringFromUnicode(wFileName.c_str(), wFileName.length(), pUtf8, lLen, false); std::string utf8FileName = std::string(pUtf8, pUtf8 + lLen); delete [] pUtf8; Stream fs(new std::fstream(utf8FileName, std::ios::out)); try { Save(fs); } catch (Exception ex) { throw new CFException("Error saving file [" + fileName + "]", ex); } // finally { if (fs.get() != nullptr) fs->flush(); if (fs.get() != nullptr) static_cast(fs.get())->close(); } } void CompoundFile::Save(Stream stream) { if (_disposed) throw new CFDisposedException("Compound File closed: cannot save data"); // if (!stream.CanSeek) // throw new CFException("Cannot save on a non-seekable stream"); CheckForLockSector(); int sSize = GetSectorSize(); try { std::vector zeroArray(sSize, 0); stream->write(zeroArray.data(), zeroArray.size()); zeroArray.clear(); CommitDirectory(); for (int i = 0; i < sectors.Count(); i++) { auto s = sectors[i]; if (s == nullptr) { // Load source (unmodified) sectors // Here we have to ignore "Dirty flag" of // sectors because we are NOT modifying the source // in a differential way but ALL sectors need to be // persisted on the destination stream s.reset(new Sector(sSize, sourceStream)); s->id = i; //sectors[i] = s; } stream->write(reinterpret_cast(s->GetData().data()), sSize); //s.ReleaseData(); } stream->seekp(0, std::ios::beg); header.Write(stream); } catch (std::exception &ex) { throw new CFException("Internal error while saving compound file to stream ", ex); } } SVector CompoundFile::GetFatSectorChain() { int N_HEADER_FAT_ENTRY = 109; //Number of FAT sectors id in the header SVector result; int nextSecID = Sector::ENDOFCHAIN; SVector difatSectors = GetDifatSectorChain(); int idx = 0; // Read FAT entries from the header Fat entry array (max 109 entries) while (idx < header.fatSectorsNumber && idx < N_HEADER_FAT_ENTRY) { nextSecID = header.difat[idx]; auto s = sectors[nextSecID]; if (s.get() == nullptr) { s.reset(new Sector(GetSectorSize(), sourceStream)); s->id = nextSecID; s->type = SectorType::FAT; sectors[nextSecID] = s; } result.push_back(s); idx++; } //Is there any DIFAT sector containing other FAT entries ? if (difatSectors.size() > 0) { std::unordered_set processedSectors; std::streamsize stLength = header.fatSectorsNumber > N_HEADER_FAT_ENTRY ? (header.fatSectorsNumber - N_HEADER_FAT_ENTRY) * 4 : 0; SList zeroQueue; std::shared_ptr difatStream( new StreamView ( difatSectors, GetSectorSize(), stLength, zeroQueue, sourceStream ) ); char nextDIFATSectorBuffer[4]; int i = 0; while ((int)result.size() < header.fatSectorsNumber) { difatStream->Read(nextDIFATSectorBuffer, 0, 4); // IsLittleEndian ? nextSecID = *reinterpret_cast(nextDIFATSectorBuffer); EnsureUniqueSectorIndex(nextSecID, processedSectors); auto s = sectors[nextSecID]; if (s.get() == nullptr) { s.reset(new Sector(GetSectorSize(), sourceStream)); s->type = SectorType::FAT; s->id = nextSecID; sectors[nextSecID] = s;//UUU } result.push_back(s); //difatStream.Read(nextDIFATSectorBuffer, 0, 4); //nextSecID = BitConverter.ToInt32(nextDIFATSectorBuffer, 0); if (difatStream->position == ((GetSectorSize() - 4) + i * GetSectorSize())) { // Skip DIFAT chain fields considering the possibility that the last FAT entry has been already read difatStream->Read(nextDIFATSectorBuffer, 0, 4); if (*reinterpret_cast(nextDIFATSectorBuffer) == Sector::ENDOFCHAIN) break; else { i++; continue; } } } } return result; } SVector CompoundFile::GetDifatSectorChain() { int validationCount = 0; SVector result; int nextSecID = Sector::ENDOFCHAIN; std::unordered_set processedSectors; if (header.difatSectorsNumber != 0) { validationCount = (int)header.difatSectorsNumber; std::shared_ptr s = sectors[header.firstDIFATSectorID]; if (s == nullptr) //Lazy loading { s.reset(new Sector(GetSectorSize(), sourceStream)); s->type = SectorType::DIFAT; s->id = header.firstDIFATSectorID; sectors[header.firstDIFATSectorID] = s; } result.push_back(s); while (validationCount >= 0) { int startPos = GetSectorSize() - 4; nextSecID = *reinterpret_cast(s->GetData().data() + startPos); EnsureUniqueSectorIndex(nextSecID, processedSectors); // Strictly speaking, the following condition is not correct from // a specification point of view: // only ENDOFCHAIN should break DIFAT chain but // a lot of existing compound files use FREESECT as DIFAT chain termination if (nextSecID == Sector::FREESECT || nextSecID == Sector::ENDOFCHAIN) break; validationCount--; if (validationCount < 0) { if (this->closeStream) this->Close(); if (this->validationExceptionEnabled) throw new CFCorruptedFileException("DIFAT sectors count mismatched. Corrupted compound file"); } s = sectors[nextSecID]; if (s == nullptr) { s.reset(new Sector(GetSectorSize(), sourceStream)); s->id = nextSecID; sectors[nextSecID] = s; } result.push_back(s); } } return result; } SVector CompoundFile::GetNormalSectorChain(int secID) { SVector result; int nextSecID = secID; SVector fatSectors = GetFatSectorChain(); std::unordered_set processedSectors; SList zeroQueue; StreamView fatStream(fatSectors, GetSectorSize(), fatSectors.size() * GetSectorSize(), zeroQueue, sourceStream); while (true) { if (nextSecID == Sector::ENDOFCHAIN) break; if (nextSecID < 0) throw new CFCorruptedFileException("Next Sector ID reference is below zero. NextID : " + std::to_string(nextSecID)); if (nextSecID >= sectors.Count()) throw new CFCorruptedFileException("Next Sector ID reference an out of range sector. NextID : " + std::to_string(nextSecID) + " while sector count " + std::to_string(sectors.Count())); std::shared_ptr s = sectors[nextSecID]; if (s == nullptr) { s.reset(new Sector(GetSectorSize(), sourceStream)); s->id = nextSecID; s->type = SectorType::Normal; sectors[nextSecID] = s; } result.push_back(s); fatStream.Seek(nextSecID * 4, std::ios::beg); int next = fatStream.ReadInt32(); EnsureUniqueSectorIndex(next, processedSectors); nextSecID = next; } return result; } // TODO SVector CompoundFile::GetMiniSectorChain(int secID) { SVector result; if (secID != Sector::ENDOFCHAIN) { int nextSecID = secID; SVector miniFAT = GetNormalSectorChain(header.firstMiniFATSectorID); SVector miniStream = GetNormalSectorChain(RootEntry()->getStartSetc()); SList zeroQueue; StreamView miniFATView(miniFAT, GetSectorSize(), header.miniFATSectorsNumber * Sector::MINISECTOR_SIZE, zeroQueue, sourceStream); // TODO here StreamView miniStreamView(miniStream, GetSectorSize(), rootStorage.Size, zeroQueue, sourceStream); BinaryReader miniFATReader = new BinaryReader(miniFATView); nextSecID = secID; std::unordered_set processedSectors; while (true) { if (nextSecID == Sector::ENDOFCHAIN) break; std::shared_ptr ms(new Sector(Sector::MINISECTOR_SIZE, sourceStream)); ms->id = nextSecID; ms->type = SectorType::Mini; miniStreamView.Seek(nextSecID * Sector::MINISECTOR_SIZE, std::ios::beg); miniStreamView.Read(reinterpret_cast(ms->GetData().data()), 0, Sector::MINISECTOR_SIZE); result.push_back(ms); miniFATView.Seek(nextSecID * 4, std::ios::beg); int next = miniFATReader.ReadInt32(); nextSecID = next; EnsureUniqueSectorIndex(nextSecID, processedSectors); } } return result; } SVector CompoundFile::GetSectorChain(int secID, SectorType chainType) { switch (chainType) { case SectorType::DIFAT: return GetDifatSectorChain(); case SectorType::FAT: return GetFatSectorChain(); case SectorType::Normal: return GetNormalSectorChain(secID); case SectorType::Mini: return GetMiniSectorChain(secID); default: throw new CFException("Unsupproted chain type"); } } void CompoundFile::EnsureUniqueSectorIndex(int nextSecID, std::unordered_set& processedSectors) { if (processedSectors.find(nextSecID) != processedSectors.end() && this->validationExceptionEnabled) { throw new CFCorruptedFileException("The file is corrupted."); } processedSectors.insert(nextSecID); } void CompoundFile::CommitDirectory() { const int DIRECTORY_SIZE = 128; auto directorySectors = GetSectorChain(header.firstDirectorySectorID, SectorType::Normal); SList zeroQueue; std::shared_ptr sv( new StreamView( directorySectors, GetSectorSize(), 0, zeroQueue, sourceStream ) ); for (const auto& di : directoryEntries) { di->Write(sv); } int delta = directoryEntries.size(); while (delta % (GetSectorSize() / DIRECTORY_SIZE) != 0) { std::shared_ptr dummy = DirectoryEntry::New(L"", StgType::StgInvalid, directoryEntries.cast()); dummy->Write(sv); delta++; } for (auto s : directorySectors) { s->type = SectorType::Directory; } AllocateSectorChain(directorySectors); header.firstDirectorySectorID = directorySectors[0]->id; //Version 4 supports directory sectors count if (header.majorVersion == 3) { header.directorySectorsNumber = 0; } else { header.directorySectorsNumber = directorySectors.size(); } } void CompoundFile::Close(bool closeStream) { } std::shared_ptr CompoundFile::RootEntry() { if (directoryEntries.empty()) return {}; return directoryEntries[0]; } SVector CompoundFile::FindDirectoryEntries(std::wstring entryName) { SVector result; for (auto d : directoryEntries) { if (d->GetEntryName() == entryName && d->getStgType() != StgType::StgInvalid) result.push_back(d); } return result; } std::shared_ptr CompoundFile::DoLoadChildrenTrusted(std::shared_ptr de) { std::shared_ptr bst; if (de->getChild() != DirectoryEntry::NOSTREAM) { bst.reset(new RedBlackTree::RBTree(directoryEntries[de->getChild()])); } return bst; } void CompoundFile::DoLoadChildren(std::shared_ptr bst, std::shared_ptr de) { if (de->getChild() != DirectoryEntry::NOSTREAM) { if (directoryEntries[de->getChild()]->getStgType() == StgType::StgInvalid) return; LoadSiblings(bst, directoryEntries[de->getChild()]); NullifyChildNodes(std::static_pointer_cast(directoryEntries[de->getChild()])); bst->Insert(std::static_pointer_cast(directoryEntries[de->getChild()])); } } void CompoundFile::NullifyChildNodes(std::shared_ptr de) { de->setParent({}); de->setParent({}); de->setParent({}); } void CompoundFile::LoadSiblings(std::shared_ptr bst, std::shared_ptr de) { levelSIDs.clear(); if (de->getLeftSibling() != DirectoryEntry::NOSTREAM) { // If there're more left siblings load them... DoLoadSiblings(bst, directoryEntries[de->getLeftSibling()]); //NullifyChildNodes(directoryEntries[de.LeftSibling]); } if (de->getRightSibling() != DirectoryEntry::NOSTREAM) { levelSIDs.push_back(de->getRightSibling()); // If there're more right siblings load them... DoLoadSiblings(bst, directoryEntries[de->getRightSibling()]); //NullifyChildNodes(directoryEntries[de.RightSibling]); } } void CompoundFile::DoLoadSiblings(std::shared_ptr bst, std::shared_ptr de) { if (ValidateSibling(de->getLeftSibling())) { levelSIDs.push_back(de->getLeftSibling()); // If there're more left siblings load them... DoLoadSiblings(bst, directoryEntries[de->getLeftSibling()]); } if (ValidateSibling(de->getRightSibling())) { levelSIDs.push_back(de->getRightSibling()); // If there're more right siblings load them... DoLoadSiblings(bst, directoryEntries[de->getRightSibling()]); } NullifyChildNodes(de); bst->Insert(de); } bool CompoundFile::ValidateSibling(int sid) { if (sid != DirectoryEntry::NOSTREAM) { // if this siblings id does not overflow current list if (sid >= (int)directoryEntries.size()) { if (this->validationExceptionEnabled) { //this.Close(); throw new CFCorruptedFileException("A Directory Entry references the non-existent sid number " + std::to_string(sid)); } else return false; } //if this sibling is valid... if (directoryEntries[sid]->getStgType() == StgType::StgInvalid) { if (this->validationExceptionEnabled) { //this.Close(); throw new CFCorruptedFileException("A Directory Entry has a valid reference to an Invalid Storage Type directory [" + std::to_string(sid) + "]"); } else return false; } int stgtype = directoryEntries[sid]->getStgType(); if (false == (stgtype >= 0 && stgtype <= 5)) { if (this->validationExceptionEnabled) { //this.Close(); throw new CFCorruptedFileException("A Directory Entry has an invalid Storage Type"); } else return false; } if (std::find(levelSIDs.begin(), levelSIDs.end(), sid) != levelSIDs.end()) throw new CFCorruptedFileException("Cyclic reference of directory item"); return true; //No fault condition encountered for sid being validated } return false; } void CompoundFile::LoadDirectories() { SVector directoryChain = GetSectorChain(header.firstDirectorySectorID, SectorType::Normal); if (!(directoryChain.size() > 0)) throw new CFCorruptedFileException("Directory sector chain MUST contain at least 1 sector"); if (header.firstDirectorySectorID == Sector::ENDOFCHAIN) header.firstDirectorySectorID = directoryChain[0]->id; SList zeroQueue; StreamView dirReader(directoryChain, GetSectorSize(), directoryChain.size() * GetSectorSize(), zeroQueue, sourceStream); while (dirReader.position < directoryChain.size() * GetSectorSize()) { std::shared_ptr de = DirectoryEntry::New(L"", StgType::StgInvalid, directoryEntries.cast()); //We are not inserting dirs. Do not use 'InsertNewDirectoryEntry' de->Read(dirReader, getVersion()); } } SVector CompoundFile::GetDirectories() { return directoryEntries; } void CompoundFile::ResetDirectoryEntry(int sid) { directoryEntries[sid]->SetEntryName(L""); directoryEntries[sid]->setLeft({}); directoryEntries[sid]->setRight({}); directoryEntries[sid]->setParent({}); directoryEntries[sid]->setStgType(StgType::StgInvalid); directoryEntries[sid]->setStartSetc(DirectoryEntry::ZERO); directoryEntries[sid]->setStorageCLSID(GUID()); directoryEntries[sid]->setSize(0); directoryEntries[sid]->stateBits = 0; directoryEntries[sid]->setColor(RedBlackTree::RED); directoryEntries[sid]->setCreationDate(0); directoryEntries[sid]->setModifyDate(0); } int CompoundFile::GetSectorSize() { return 2 << (header.sectorShift - 1); } void CompoundFile::CheckForLockSector() { //If transaction lock has been added and not yet allocated in the FAT... if (_transactionLockAdded && !_transactionLockAllocated) { StreamView fatStream(GetFatSectorChain(), GetSectorSize(), sourceStream); fatStream.Seek(_lockSectorId * 4, std::ios::beg); fatStream.Write(reinterpret_cast(&Sector::ENDOFCHAIN), 0, 4); _transactionLockAllocated = true; } } void CompoundFile::LoadFile(std::wstring fileName) { SetFileName(fileName); Stream fs; try { NSFile::CFileBinary file; file.OpenFile(fileName); if (updateMode == CFSUpdateMode::ReadOnly) { fs.reset(new std::fstream(this->fileName, std::ios::in | std::ios::out)); } else { fs.reset(new std::fstream(this->fileName, std::ios::in | std::ios::out)); } Load(fs); } catch(...) { if (fs.get() != nullptr) fs->clear(); // close throw; } } void CompoundFile::SetFileName(std::wstring fileName) { BYTE* pUtf8 = NULL; LONG lLen = 0; NSFile::CUtf8Converter::GetUtf8StringFromUnicode(fileName.c_str(), fileName.length(), pUtf8, lLen, false); this->fileName = std::string(pUtf8, pUtf8 + lLen); delete [] pUtf8; } void CompoundFile::LoadStream(Stream stream) { if (stream.get() == nullptr) throw new CFException("Stream parameter cannot be null"); if (/*!stream.CanSeek*/false) throw new CFException("Cannot load a non-seekable Stream"); stream->seekp(0, std::ios::beg); Load(stream); }