#include "directoryentry.h" #include "cfexception.h" #include "streamrw.h" #include using namespace CFCPP; DirectoryEntry::DirectoryEntry(std::wstring name, StgType stgType, SVector &dirRepository) : dirRepository(dirRepository) { this->stgType = stgType; if (stgType == StgType::StgStorage) { // creationDate = BitConverter.GetBytes((DateTime.Now.ToFileTime())); startSetc = ZERO; } if (stgType == StgType::StgInvalid) { startSetc = ZERO; } if (name.size()) { DirectoryEntry::SetEntryName(name); } } DirectoryEntry::DirectoryEntry(std::wstring name, StgType stgType) : dirRepository(emptyDir) { this->stgType = stgType; if (stgType == StgType::StgStorage) { // creationDate = BitConverter.GetBytes((DateTime.Now.ToFileTime())); startSetc = ZERO; } if (stgType == StgType::StgInvalid) { startSetc = ZERO; } if (name.size()) { DirectoryEntry::SetEntryName(name); } } int DirectoryEntry::getSid() const { return sid; } void DirectoryEntry::setSid(int newSid) { sid = newSid; } std::wstring DirectoryEntry::GetEntryName() const { if (entryName[0] != '\0' && nameLength > 0) { wchar_t name[32]; for (int i = 0; i < 32; i++) { name[i] = entryName[2*i] + (entryName[2*i+1] << 8); } return std::wstring (name, name + nameLength/2 - 1); } else return L""; } void DirectoryEntry::SetEntryName(const std::wstring &entryName) { if (entryName.empty()) { std::fill(this->entryName, this->entryName+64, '\0'); this->nameLength = 0; } else { if ( entryName.find(L"\\") != std::wstring::npos || entryName.find(L"/") != std::wstring::npos || entryName.find(L":") != std::wstring::npos || entryName.find(L"!") != std::wstring::npos ) throw CFException("Invalid character in entry: the characters '\\', '/', ':','!' cannot be used in entry name"); if (entryName.length() > 31) throw CFException("Entry name MUST NOT exceed 31 characters"); std::fill(this->entryName, this->entryName + 64, 0); for (size_t i = 0; i < entryName.size(); i++) { wchar_t sym = entryName[i]; this->entryName[i*2+0] = sym % 256; this->entryName[i*2+1] = sym / 256; } this->nameLength = (ushort)entryName.size() * 2 + 2; } } int DirectoryEntry::GetHashCode() const { return (int)fnv_hash(entryName, nameLength); } void DirectoryEntry::Write(Stream stream) const { StreamRW rw(stream); rw.WriteArray(entryName, sizeof (entryName)); rw.Write(nameLength); rw.Write((BYTE)stgType); rw.Write((BYTE)stgColor); rw.Write(leftSibling); rw.Write(rightSibling); rw.Write(child); rw.WriteArray(reinterpret_cast(&storageCLSID), sizeof (storageCLSID)); rw.Write(stateBits); rw.WriteArray(reinterpret_cast(&creationDate), sizeof (creationDate)); rw.WriteArray(reinterpret_cast(&modifyDate), sizeof (modifyDate)); rw.Write(startSetc); rw.Write(size); rw.Close(); } void DirectoryEntry::Read(Stream stream, CFSVersion ver) { StreamRW rw(stream); rw.ReadArray(entryName, 64); nameLength = rw.Read(); stgType = (StgType)rw.Read(); stgColor = (StgColor)rw.Read(); leftSibling = rw.Read(); rightSibling = rw.Read(); child = rw.Read(); // Thanks to bugaccount (BugTrack id 3519554) if (stgType == StgType::StgInvalid) { leftSibling = NOSTREAM; rightSibling = NOSTREAM; child = NOSTREAM; } rw.ReadArray(reinterpret_cast(&storageCLSID), 16); stateBits = rw.Read(); rw.ReadArray(reinterpret_cast(&creationDate), 8); rw.ReadArray(reinterpret_cast(&modifyDate), 8); startSetc = rw.Read(); if (ver == CFSVersion::Ver_3) { // avoid dirty read for version 3 files (max size: 32bit integer) // where most significant bits are not initialized to zero size = rw.Read(); rw.Read(); //discard most significant 4 (possibly) dirty bytes } else { size = rw.Read(); } } std::wstring DirectoryEntry::ToString() const { std::wstringstream wss; wss << Name() << L" [" << sid << L"]" << (stgType == StgType::StgStream ? L"Stream" : L"Storage"); return wss.str(); } RedBlackTree::PIRBNode DirectoryEntry::getLeft() const { if (leftSibling == NOSTREAM) return {}; return dirRepository[leftSibling]; } RedBlackTree::PIRBNode DirectoryEntry::getRight() const { if (rightSibling == DirectoryEntry::NOSTREAM) return {}; return dirRepository[rightSibling]; } void DirectoryEntry::setLeft(RedBlackTree::PIRBNode pNode) { leftSibling = pNode != nullptr ? std::static_pointer_cast(pNode)->getSid() : DirectoryEntry::NOSTREAM; if (leftSibling != DirectoryEntry::NOSTREAM) dirRepository[leftSibling]->setParent(shared_from_this()); } void DirectoryEntry::setRight(RedBlackTree::PIRBNode pNode) { rightSibling = pNode != nullptr ? std::static_pointer_cast(pNode)->getSid() : DirectoryEntry::NOSTREAM; if (rightSibling != DirectoryEntry::NOSTREAM) dirRepository[rightSibling]->setParent(shared_from_this()); } RedBlackTree::PIRBNode DirectoryEntry::Sibling() const { if (shared_from_this() == getParent()->getLeft()) return getParent()->getRight(); else return getParent()->getLeft(); } RedBlackTree::PIRBNode DirectoryEntry::Uncle() const { return parent.use_count() != 0 ? getParent()->Sibling() : RedBlackTree::PIRBNode(); } void DirectoryEntry::AssignValueTo(RedBlackTree::PIRBNode other) { auto d = std::dynamic_pointer_cast(other); // as if (d == nullptr) return; d->SetEntryName(this->GetEntryName()); d->creationDate = creationDate; d->modifyDate = modifyDate; d->size = this->size; d->startSetc = this->startSetc; d->stateBits = this->stateBits; d->stgType = this->stgType; d->storageCLSID = this->storageCLSID; d->child = this->child; } int DirectoryEntry::CompareTo(const RedBlackTree::PIRBNode &other) const { IDirectoryEntry* otherDir = dynamic_cast(other.get()); if (otherDir == nullptr) throw CFException("Invalid casting: compared object does not implement IDirectorEntry interface"); if (this->getNameLength() > otherDir->getNameLength()) { return THIS_IS_GREATER; } else if (this->getNameLength() < otherDir->getNameLength()) { return OTHER_IS_GREATER; } else { std::wstring thisName = GetEntryName(); std::wstring otherName = otherDir->GetEntryName(); for (int z = 0; z < (int)thisName.size(); z++) { char thisChar = toupper(thisName[z]); char otherChar = toupper(otherName[z]); if (thisChar > otherChar) return THIS_IS_GREATER; else if (thisChar < otherChar) return OTHER_IS_GREATER; } return 0; } } ULONG64 DirectoryEntry::fnv_hash(const char *buffer, int lenght) { ULONG64 h = 2166136261; int i; for (i = 0; i < lenght; i++) h = (h * 16777619) ^ buffer[i]; return h; } std::shared_ptr DirectoryEntry::New(std::wstring name, StgType stgType, SVector& dirRepository) { std::shared_ptr de; if (/*dirRepository != null*/true) { de.reset(new DirectoryEntry(name, stgType, dirRepository)); // No invalid directory entry found dirRepository.push_back(de); de->setSid(dirRepository.size() - 1); } else throw std::invalid_argument("dirRepository Directory repository cannot be null in New() method"); return de; } std::shared_ptr DirectoryEntry::TryNew(std::wstring name, StgType stgType, SVector& dirRepository) { std::shared_ptr de(new DirectoryEntry(name, stgType, dirRepository)); // If we are not adding an invalid dirEntry as // in a normal loading from file (invalid dirs MAY pad a sector) if (de != nullptr) { // Find first available invalid slot (if any) to reuse it for (int i = 0; i < (int)dirRepository.size(); i++) { if (dirRepository[i]->getStgType() == StgType::StgInvalid) { dirRepository[i] = de; de->sid = i; return de; } } } // No invalid directory entry found dirRepository.push_back(de); de->sid = dirRepository.size() - 1; return de; } std::shared_ptr DirectoryEntry::Mock(std::wstring name, StgType stgType) { auto de = std::shared_ptr(new DirectoryEntry(name, stgType)); return de; }