using System.Reflection;
using System.Text;
using Masuit.Tools.Mime;
namespace Masuit.Tools.Files.FileDetector;
public abstract class AbstractCompoundFileDetailDetector : AbstractSignatureDetector
{
private static readonly SignatureInformation[] CfSignatureInfo = {
new() { Position = 0, Signature = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]
},
};
///
protected override SignatureInformation[] SignatureInformations => CfSignatureInfo;
public abstract IEnumerable Chunks { get; }
public override string MimeType => new MimeMapper().GetMimeFromExtension("." + Extension);
public override List FormatCategories => GetType().GetCustomAttributes().Select(a => a.Category).ToList();
protected abstract bool IsValidChunk(string chunkName, byte[] chunkData);
public override bool Detect(Stream stream)
{
if (!base.Detect(stream))
{
return false;
}
stream.Position = 0;
try
{
using var cf = new CompoundFile(stream, CFSConfiguration.LeaveOpen | CFSConfiguration.Default);
return !(from chunk in Chunks
let compoundFileStream = cf.RootStorage.GetStream(chunk)
where compoundFileStream == null || !IsValidChunk(chunk, compoundFileStream.GetData())
select chunk).Any();
}
catch
{
return false;
}
}
#region Modified OpenMCDF
internal enum Color
{
Red = 0,
Black = 1
}
internal interface IRBNode : IComparable
{
IRBNode Left { get; set; }
IRBNode Right { get; set; }
Color Color { get; set; }
IRBNode Parent { get; set; }
IRBNode Grandparent();
IRBNode Sibling();
IRBNode Uncle();
}
internal sealed class RBTree
{
public IRBNode Root { get; set; }
private static Color NodeColor(IRBNode n) => n?.Color ?? Color.Black;
public RBTree()
{
}
public RBTree(IRBNode root)
{
Root = root;
}
private IRBNode Lookup(IRBNode template)
{
var n = Root;
while (n != null)
{
int compResult = template.CompareTo(n);
if (compResult == 0)
{
return n;
}
n = compResult < 0 ? n.Left : n.Right;
}
return n;
}
public bool TryLookup(IRBNode template, out IRBNode val)
{
val = Lookup(template);
return val != null;
}
private void Replace(IRBNode oldn, IRBNode newn)
{
if (oldn.Parent == null)
{
Root = newn;
}
else
{
if (oldn == oldn.Parent.Left)
{
oldn.Parent.Left = newn;
}
else
{
oldn.Parent.Right = newn;
}
}
if (newn != null)
{
newn.Parent = oldn.Parent;
}
}
private void RotateL(IRBNode n)
{
var r = n.Right;
Replace(n, r);
n.Right = r.Left;
if (r.Left != null)
{
r.Left.Parent = n;
}
r.Left = n;
n.Parent = r;
}
private void RotateR(IRBNode n)
{
var l = n.Left;
Replace(n, l);
n.Left = l.Right;
if (l.Right != null)
{
l.Right.Parent = n;
}
l.Right = n;
n.Parent = l;
}
public void Insert(IRBNode newNode)
{
newNode.Color = Color.Red;
if (Root == null)
{
Root = newNode;
}
else
{
var n = Root;
while (true)
{
var compResult = newNode.CompareTo(n);
if (compResult == 0)
{
throw new Exception($"RBNode {newNode} already present in tree");
}
if (compResult < 0)
{
if (n.Left == null)
{
n.Left = newNode;
break;
}
n = n.Left;
}
else
{
if (n.Right == null)
{
n.Right = newNode;
break;
}
n = n.Right;
}
}
newNode.Parent = n;
}
Insert1(newNode);
NodeInserted?.Invoke(newNode);
}
private void Insert1(IRBNode n)
{
if (n.Parent == null)
{
n.Color = Color.Black;
}
else Insert2(n);
}
private void Insert2(IRBNode n)
{
if (NodeColor(n.Parent) == Color.Black)
{
return;
}
Insert3(n);
}
private void Insert3(IRBNode n)
{
if (NodeColor(n.Uncle()) == Color.Red)
{
n.Parent.Color = Color.Black;
n.Uncle().Color = Color.Black;
n.Grandparent().Color = Color.Red;
Insert1(n.Grandparent());
}
else
{
Insert4(n);
}
}
private void Insert4(IRBNode n)
{
if (n == n.Parent.Right && n.Parent == n.Grandparent().Left)
{
RotateL(n.Parent);
n = n.Left;
}
else if (n == n.Parent.Left && n.Parent == n.Grandparent().Right)
{
RotateR(n.Parent);
n = n.Right;
}
Insert5(n);
}
private void Insert5(IRBNode n)
{
n.Parent.Color = Color.Black;
n.Grandparent().Color = Color.Red;
if (n == n.Parent.Left && n.Parent == n.Grandparent().Left)
{
RotateR(n.Grandparent());
}
else
{
RotateL(n.Grandparent());
}
}
internal void VisitTreeNodes(Action action)
{
var walker = Root;
if (walker != null)
{
DoVisitTreeNodes(action, walker);
}
}
private static void DoVisitTreeNodes(Action action, IRBNode walker)
{
while (true)
{
if (walker.Left != null)
{
DoVisitTreeNodes(action, walker.Left);
}
action?.Invoke(walker);
if (walker.Right != null)
{
walker = walker.Right;
continue;
}
break;
}
}
internal event Action NodeInserted;
}
// http://mozilla.org/MPL/2.0/.
internal enum SectorType
{
Normal,
Mini,
FAT,
DIFAT,
RangeLockSector,
Directory
}
internal sealed class Sector(int size, Stream stream) : IDisposable
{
public static int MinisectorSize = 64;
public const int Freesect = unchecked((int) 0xFFFFFFFF);
public const int Endofchain = unchecked((int) 0xFFFFFFFE);
public bool DirtyFlag { get; set; }
public bool IsStreamed => stream != null && Size != MinisectorSize && Id * Size + Size < stream.Length;
internal SectorType Type { get; set; }
public int Id { get; set; } = -1;
public int Size { get; private set; } = size;
private byte[] _data;
public byte[] GetData()
{
if (_data == null)
{
_data = new byte[Size];
if (IsStreamed)
{
stream.Seek(Size + Id * Size, SeekOrigin.Begin);
_=stream.Read(_data, 0, Size);
}
}
return _data;
}
public void InitFATData()
{
_data = new byte[Size];
for (int i = 0; i < Size; i++)
{
_data[i] = 0xFF;
}
DirtyFlag = true;
}
private readonly object _lockObject = new();
#region IDisposable Members
private bool _disposed;
void IDisposable.Dispose()
{
try
{
if (!_disposed)
{
lock (_lockObject)
{
_data = null;
DirtyFlag = false;
Id = Endofchain;
Size = 0;
}
}
}
finally
{
_disposed = true;
}
GC.SuppressFinalize(this);
}
#endregion IDisposable Members
}
internal enum StgType
{
StgInvalid = 0,
StgStorage = 1,
StgStream = 2,
StgLockbytes = 3,
StgProperty = 4,
StgRoot = 5
}
internal sealed class DirectoryEntry : IRBNode
{
internal const int ThisIsGreater = 1;
internal const int OtherIsGreater = -1;
private readonly IList _dirRepository;
public int Sid { get; set; } = -1;
internal const int Nostream = unchecked((int) 0xFFFFFFFF);
private DirectoryEntry(string name, StgType stgType, IList dirRepository)
{
_dirRepository = dirRepository;
StgType = stgType;
switch (stgType)
{
case StgType.StgStream:
StorageClsid = new Guid(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
CreationDate = new byte[8];
ModifyDate = new byte[8];
break;
case StgType.StgStorage:
CreationDate = BitConverter.GetBytes((DateTime.Now.ToFileTime()));
break;
case StgType.StgRoot:
CreationDate = new byte[8];
ModifyDate = new byte[8];
break;
}
SetEntryName(name);
}
public byte[] EntryName { get; private set; } = new byte[64];
public string GetEntryName()
{
return EntryName is {Length: > 0} ? Encoding.Unicode.GetString(EntryName).Remove((NameLength - 1) / 2) : string.Empty;
}
public void SetEntryName(string entryName)
{
if (entryName.Contains(@"\") || entryName.Contains("/") || entryName.Contains(":") || entryName.Contains("!"))
{
throw new ArgumentException("Invalid character in entry: the characters '\\', '/', ':','!' cannot be used in entry name");
}
if (entryName.Length > 31)
{
throw new ArgumentException("Entry name MUST be smaller than 31 characters");
}
byte[] temp = Encoding.Unicode.GetBytes(entryName);
var newName = new byte[64];
Buffer.BlockCopy(temp, 0, newName, 0, temp.Length);
newName[temp.Length] = 0x00;
newName[temp.Length + 1] = 0x00;
EntryName = newName;
NameLength = (ushort) (temp.Length + 2);
}
public ushort NameLength { get; private set; }
public StgType StgType { get; set; }
public Color Color { get; set; } = Color.Black;
public int LeftSibling { get; set; } = Nostream;
public int RightSibling { get; set; } = Nostream;
public int Child { get; set; } = Nostream;
public Guid StorageClsid { get; set; } = Guid.NewGuid();
public int StateBits { get; set; }
public byte[] CreationDate { get; set; } = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
public byte[] ModifyDate { get; set; } = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
public int StartSetc { get; set; } = Sector.Endofchain;
public long Size { get; set; }
public int CompareTo(object obj)
{
if (obj is not DirectoryEntry otherDir)
{
throw new ArgumentException("Invalid casting: compared object does not implement IDirectorEntry interface");
}
if (NameLength > otherDir.NameLength)
{
return ThisIsGreater;
}
if (NameLength < otherDir.NameLength)
{
return OtherIsGreater;
}
var thisName = Encoding.Unicode.GetString(EntryName, 0, NameLength);
var otherName = Encoding.Unicode.GetString(otherDir.EntryName, 0, otherDir.NameLength);
for (int z = 0; z < thisName.Length; z++)
{
var thisChar = char.ToUpperInvariant(thisName[z]);
var otherChar = char.ToUpperInvariant(otherName[z]);
if (thisChar > otherChar)
{
return ThisIsGreater;
}
if (thisChar < otherChar)
{
return OtherIsGreater;
}
}
return 0;
}
public override bool Equals(object obj)
{
return CompareTo(obj) == 0;
}
private static ulong Fnv_hash(byte[] buffer)
{
ulong h = 2166136261;
int i;
for (i = 0; i < buffer.Length; i++)
{
h = (h * 16777619) ^ buffer[i];
}
return h;
}
public override int GetHashCode()
{
return (int) Fnv_hash(EntryName);
}
public void Read(Stream stream, int ver = 3)
{
using var rw = new BinaryReader(stream, Encoding.UTF8, true);
EntryName = rw.ReadBytes(64);
NameLength = rw.ReadUInt16();
StgType = (StgType) rw.ReadByte();
Color = (Color) rw.ReadByte();
LeftSibling = rw.ReadInt32();
RightSibling = rw.ReadInt32();
Child = rw.ReadInt32();
if (StgType == StgType.StgInvalid)
{
LeftSibling = Nostream;
RightSibling = Nostream;
Child = Nostream;
}
StorageClsid = new Guid(rw.ReadBytes(16));
StateBits = rw.ReadInt32();
CreationDate = rw.ReadBytes(8);
ModifyDate = rw.ReadBytes(8);
StartSetc = rw.ReadInt32();
if (ver == 3)
{
Size = rw.ReadInt32();
rw.ReadBytes(4);
}
else
{
Size = rw.ReadInt64();
}
}
public string Name => GetEntryName();
public IRBNode Left
{
get => LeftSibling == Nostream ? null : _dirRepository[LeftSibling];
set
{
LeftSibling = (value as DirectoryEntry)?.Sid ?? Nostream;
if (LeftSibling != Nostream)
{
_dirRepository[LeftSibling].Parent = this;
}
}
}
public IRBNode Right
{
get => RightSibling == Nostream ? null : _dirRepository[RightSibling];
set
{
RightSibling = ((DirectoryEntry) value)?.Sid ?? Nostream;
if (RightSibling != Nostream)
{
_dirRepository[RightSibling].Parent = this;
}
}
}
public IRBNode Parent { get; set; }
public IRBNode Grandparent() => Parent?.Parent;
public IRBNode Sibling() => (this == Parent.Left) ? Parent.Right : Parent.Left;
public IRBNode Uncle() => Parent?.Sibling();
internal static DirectoryEntry New(string name, StgType stgType, IList dirRepository)
{
DirectoryEntry de;
if (dirRepository != null)
{
de = new DirectoryEntry(name, stgType, dirRepository);
dirRepository.Add(de);
de.Sid = dirRepository.Count - 1;
}
else
{
throw new ArgumentNullException(nameof(dirRepository), "Directory repository cannot be null in New() method");
}
return de;
}
internal static DirectoryEntry Mock(string name, StgType stgType) => new(name, stgType, null);
public override string ToString() => $"{Name} [{Sid}]{(StgType == StgType.StgStream ? "Stream" : "Storage")}";
}
internal abstract class CFItem(CompoundFile compoundFile) : IComparable
{
protected CompoundFile CompoundFile { get; } = compoundFile;
protected void CheckDisposed()
{
if (CompoundFile.IsClosed)
{
throw new ObjectDisposedException("Owner Compound file has been closed and owned items have been invalidated");
}
}
internal DirectoryEntry DirEntry { get; set; }
internal int CompareTo(CFItem other) => DirEntry.CompareTo(other.DirEntry);
public int CompareTo(object obj) => DirEntry.CompareTo((obj as CFItem).DirEntry);
public static bool operator ==(CFItem leftItem, CFItem rightItem)
{
if (ReferenceEquals(leftItem, rightItem))
{
return true;
}
if (((object) leftItem == null) || (object) rightItem == null)
{
return false;
}
return leftItem.CompareTo(rightItem) == 0;
}
public static bool operator !=(CFItem leftItem, CFItem rightItem) => !(leftItem == rightItem);
public override bool Equals(object obj) => CompareTo(obj) == 0;
public override int GetHashCode() => DirEntry.GetEntryName().GetHashCode();
public long Size => DirEntry.Size;
int IComparable.CompareTo(CFItem other) => DirEntry.CompareTo(other.DirEntry);
public override string ToString()
{
return DirEntry != null ? $"[{DirEntry.LeftSibling},{DirEntry.Sid},{DirEntry.RightSibling}] {DirEntry.GetEntryName()}" : string.Empty;
}
}
internal sealed class CFStream : CFItem
{
internal CFStream(CompoundFile compoundFile, DirectoryEntry dirEntry) : base(compoundFile)
{
if (dirEntry == null || dirEntry.Sid < 0)
{
throw new Exception("Attempting to add a CFStream using an unitialized directory");
}
DirEntry = dirEntry;
}
public byte[] GetData()
{
CheckDisposed();
return CompoundFile.GetData(this);
}
}
internal sealed class CfStorage : CFItem
{
private RBTree _children;
internal RBTree Children => _children ??= LoadChildren(DirEntry.Sid) ?? CompoundFile.CreateNewTree();
internal CfStorage(CompoundFile compFile, DirectoryEntry dirEntry) : base(compFile)
{
if (dirEntry == null || dirEntry.Sid < 0)
{
throw new ArgumentException("Attempting to create a CFStorage using an unitialized directory");
}
DirEntry = dirEntry;
}
private RBTree LoadChildren(int sid)
{
var childrenTree = CompoundFile.GetChildrenTree(sid);
DirEntry.Child = (childrenTree.Root as DirectoryEntry)?.Sid ?? DirectoryEntry.Nostream;
return childrenTree;
}
public CFStream GetStream(string streamName)
{
CheckDisposed();
var tmp = DirectoryEntry.Mock(streamName, StgType.StgStream);
if (Children.TryLookup(tmp, out var outDe) && ((DirectoryEntry) outDe).StgType == StgType.StgStream)
{
return new CFStream(CompoundFile, (DirectoryEntry) outDe);
}
throw new KeyNotFoundException("Cannot find item [" + streamName + "] within the current storage");
}
public void VisitEntries(Action action, bool recursive)
{
CheckDisposed();
if (action != null)
{
var subStorages = new List();
void InternalAction(IRBNode targetNode)
{
var d = targetNode as DirectoryEntry;
if (d.StgType == StgType.StgStream)
{
action(new CFStream(CompoundFile, d));
}
else
{
action(new CfStorage(CompoundFile, d));
}
if (d.Child != DirectoryEntry.Nostream)
{
subStorages.Add(targetNode);
}
}
Children.VisitTreeNodes(InternalAction);
if (recursive)
{
foreach (var n in subStorages)
{
new CfStorage(CompoundFile, n as DirectoryEntry).VisitEntries(action, true);
}
}
}
}
}
internal sealed class Header
{
public byte[] HeaderSignature { get; private set; } = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
public byte[] CLSID { get; set; } = new byte[16];
public ushort MinorVersion { get; private set; } = 0x003E;
public ushort MajorVersion { get; private set; } = 0x0003;
public ushort ByteOrder { get; private set; } = 0xFFFE;
public ushort SectorShift { get; private set; } = 9;
public ushort MiniSectorShift { get; private set; } = 6;
public int DirectorySectorsNumber { get; set; }
public int FATSectorsNumber { get; set; }
public int FirstDirectorySectorID { get; set; } = Sector.Endofchain;
public uint MinSizeStandardStream { get; set; } = 4096;
public int FirstMiniFATSectorID { get; set; } = unchecked((int) 0xFFFFFFFE);
public uint MiniFATSectorsNumber { get; set; }
public int FirstDIFATSectorID { get; set; } = Sector.Endofchain;
public uint DIFATSectorsNumber { get; set; }
public int[] DIFAT { get; } = new int[109];
public Header() : this(3)
{
}
public Header(ushort version)
{
switch (version)
{
case 3:
MajorVersion = 3;
SectorShift = 0x0009;
break;
case 4:
MajorVersion = 4;
SectorShift = 0x000C;
break;
default:
throw new Exception("Invalid Compound File Format version");
}
for (int i = 0; i < 109; i++)
{
DIFAT[i] = Sector.Freesect;
}
}
public void Read(Stream stream)
{
var rw = new BinaryReader(stream, Encoding.UTF8, true);
HeaderSignature = rw.ReadBytes(8);
CheckSignature();
CLSID = rw.ReadBytes(16);
MinorVersion = rw.ReadUInt16();
MajorVersion = rw.ReadUInt16();
CheckVersion();
ByteOrder = rw.ReadUInt16();
SectorShift = rw.ReadUInt16();
MiniSectorShift = rw.ReadUInt16();
rw.ReadBytes(6);
DirectorySectorsNumber = rw.ReadInt32();
FATSectorsNumber = rw.ReadInt32();
FirstDirectorySectorID = rw.ReadInt32();
rw.ReadUInt32();
MinSizeStandardStream = rw.ReadUInt32();
FirstMiniFATSectorID = rw.ReadInt32();
MiniFATSectorsNumber = rw.ReadUInt32();
FirstDIFATSectorID = rw.ReadInt32();
DIFATSectorsNumber = rw.ReadUInt32();
for (int i = 0; i < 109; i++)
{
DIFAT[i] = rw.ReadInt32();
}
}
private void CheckVersion()
{
if (MajorVersion != 3 && MajorVersion != 4)
{
throw new InvalidDataException("Unsupported Binary File Format version: OpenMcdf only supports Compound Files with major version equal to 3 or 4 ");
}
}
private readonly byte[] _oleCfsSignature = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
private void CheckSignature()
{
if (HeaderSignature.Where((t, i) => t != _oleCfsSignature[i]).Any())
{
throw new InvalidDataException("Invalid OLE structured storage file");
}
}
}
internal sealed class StreamView : Stream
{
private readonly int _sectorSize;
private long _position;
private readonly List _sectorChain;
private readonly Stream _stream;
private readonly bool _isFatStream;
public StreamView(List sectorChain, int sectorSize, Stream stream)
{
if (sectorSize <= 0)
{
throw new Exception("Sector size must be greater than zero");
}
_sectorChain = sectorChain ?? throw new Exception("Sector Chain cannot be null");
_sectorSize = sectorSize;
_stream = stream;
}
public StreamView(List sectorChain, int sectorSize, long length, Queue availableSectors, Stream stream, bool isFatStream = false) : this(sectorChain, sectorSize, stream)
{
_isFatStream = isFatStream;
AdjustLength(length, availableSectors);
}
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => true;
public override void Flush()
{
}
private long _length;
public override long Length => _length;
public override long Position
{
get => _position;
set
{
if (_position > _length - 1)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_position = value;
}
}
private byte[] buf = new byte[4];
public int ReadInt32()
{
_=Read(buf, 0, 4);
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
}
public override int Read(byte[] buffer, int offset, int count)
{
int nRead = 0;
if (_sectorChain is {Count: > 0})
{
int secIndex = (int) (_position / _sectorSize);
var nToRead = Math.Min(_sectorChain[0].Size - ((int) _position % _sectorSize), count);
if (secIndex < _sectorChain.Count)
{
Buffer.BlockCopy(_sectorChain[secIndex].GetData(), (int) (_position % _sectorSize), buffer, offset, nToRead);
}
nRead += nToRead;
++secIndex;
while (nRead < (count - _sectorSize))
{
nToRead = _sectorSize;
Buffer.BlockCopy(_sectorChain[secIndex].GetData(), 0, buffer, offset + nRead, nToRead);
nRead += nToRead;
++secIndex;
}
nToRead = count - nRead;
if (nToRead != 0)
{
Buffer.BlockCopy(_sectorChain[secIndex].GetData(), 0, buffer, offset + nRead, nToRead);
nRead += nToRead;
}
_position += nRead;
return nRead;
}
return 0;
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
_position = offset;
break;
case SeekOrigin.Current:
_position += offset;
break;
case SeekOrigin.End:
_position = Length - offset;
break;
}
AdjustLength(_position);
return _position;
}
private void AdjustLength(long value, Queue availableSectors = null)
{
_length = value;
long delta = value - _sectorChain.Count * (long) _sectorSize;
if (delta > 0)
{
int nSec = (int) Math.Ceiling(((double) delta / _sectorSize));
while (nSec > 0)
{
Sector t;
if (availableSectors == null || availableSectors.Count == 0)
{
t = new Sector(_sectorSize, _stream);
if (_sectorSize == Sector.MinisectorSize)
{
t.Type = SectorType.Mini;
}
}
else
{
t = availableSectors.Dequeue();
}
if (_isFatStream)
{
t.InitFATData();
}
_sectorChain.Add(t);
nSec--;
}
}
}
public override void SetLength(long value) => AdjustLength(value);
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
}
[Flags]
internal enum CFSConfiguration
{
Default = 1,
LeaveOpen = 16,
}
internal sealed class CompoundFile : IDisposable
{
public CFSConfiguration Configuration => CFSConfiguration.Default;
internal int GetSectorSize() => 2 << (_header.SectorShift - 1);
private List _sectors = [];
private Header _header;
internal Stream SourceStream;
public CompoundFile(Stream stream, CFSConfiguration configParameters)
{
_closeStream = !configParameters.HasFlag(CFSConfiguration.LeaveOpen);
LoadStream(stream);
}
private void Load(Stream stream)
{
try
{
_header = new Header();
_directoryEntries = new List();
SourceStream = stream;
_header.Read(stream);
int nSector = Ceiling((stream.Length - GetSectorSize()) / (double) GetSectorSize());
if (stream.Length > 0x7FFFFF0)
{
TransactionLockAllocated = true;
}
_sectors = [];
for (int i = 0; i < nSector; i++)
{
_sectors.Add(null);
}
LoadDirectories();
RootStorage = new CfStorage(this, _directoryEntries[0]);
}
catch (Exception)
{
if (stream != null && _closeStream)
{
stream.Dispose();
}
throw;
}
}
private void LoadStream(Stream stream)
{
if (stream == null)
{
throw new Exception("Stream parameter cannot be null");
}
if (!stream.CanSeek)
{
throw new Exception("Cannot load a non-seekable Stream");
}
stream.Seek(0, SeekOrigin.Begin);
Load(stream);
}
internal bool TransactionLockAllocated;
private List GetDifatSectorChain()
{
var result = new List();
if (_header.DIFATSectorsNumber != 0)
{
var validationCount = (int) _header.DIFATSectorsNumber;
var s = _sectors[_header.FirstDIFATSectorID];
if (s == null)
{
_sectors[_header.FirstDIFATSectorID] = s = new Sector(GetSectorSize(), SourceStream)
{
Type = SectorType.DIFAT,
Id = _header.FirstDIFATSectorID
};
}
result.Add(s);
while (validationCount >= 0)
{
var nextSecId = BitConverter.ToInt32(s.GetData(), GetSectorSize() - 4);
if (nextSecId is Sector.Freesect or Sector.Endofchain)
{
break;
}
validationCount--;
if (validationCount < 0)
{
Dispose();
throw new InvalidDataException("DIFAT sectors count mismatched. Corrupted compound file");
}
s = _sectors[nextSecId];
if (s == null)
{
_sectors[nextSecId] = s = new Sector(GetSectorSize(), SourceStream)
{
Id = nextSecId
};
}
result.Add(s);
}
}
return result;
}
private List GetFatSectorChain()
{
const int nHeaderFatEntry = 109;
List result = [];
int nextSecId;
var difatSectors = GetDifatSectorChain();
var idx = 0;
while (idx < _header.FATSectorsNumber && idx < nHeaderFatEntry)
{
nextSecId = _header.DIFAT[idx];
var s = _sectors[nextSecId];
if (s == null)
{
_sectors[nextSecId] = s = new Sector(GetSectorSize(), SourceStream)
{
Id = nextSecId,
Type = SectorType.FAT
};
}
result.Add(s);
++idx;
}
if (difatSectors.Count > 0)
{
var difatStream = new StreamView(difatSectors, GetSectorSize(), _header.FATSectorsNumber > nHeaderFatEntry ? (_header.FATSectorsNumber - nHeaderFatEntry) * 4 : 0, null, SourceStream);
var nextDifatSectorBuffer = new byte[4];
_=difatStream.Read(nextDifatSectorBuffer, 0, 4);
nextSecId = BitConverter.ToInt32(nextDifatSectorBuffer, 0);
int i = 0;
int nFat = nHeaderFatEntry;
while (nFat < _header.FATSectorsNumber)
{
if (difatStream.Position == ((GetSectorSize() - 4) + i * GetSectorSize()))
{
difatStream.Seek(4, SeekOrigin.Current);
++i;
continue;
}
var s = _sectors[nextSecId];
if (s == null)
{
_sectors[nextSecId] = s = new Sector(GetSectorSize(), SourceStream)
{
Type = SectorType.FAT,
Id = nextSecId
};
}
result.Add(s);
_=difatStream.Read(nextDifatSectorBuffer, 0, 4);
nextSecId = BitConverter.ToInt32(nextDifatSectorBuffer, 0);
nFat++;
}
}
return result;
}
private List GetNormalSectorChain(int secId)
{
var result = new List();
int nextSecId = secId;
var fatSectors = GetFatSectorChain();
var fatStream = new StreamView(fatSectors, GetSectorSize(), fatSectors.Count * GetSectorSize(), null, SourceStream);
while (true)
{
if (nextSecId == Sector.Endofchain)
{
break;
}
if (nextSecId < 0)
{
throw new InvalidDataException($"Next Sector ID reference is below zero. NextID : {nextSecId}");
}
if (nextSecId >= _sectors.Count)
{
throw new InvalidDataException($"Next Sector ID reference an out of range sector. NextID : {nextSecId} while sector count {_sectors.Count}");
}
var s = _sectors[nextSecId];
if (s == null)
{
_sectors[nextSecId] = s = new Sector(GetSectorSize(), SourceStream)
{
Id = nextSecId,
Type = SectorType.Normal
};
}
result.Add(s);
fatStream.Seek(nextSecId * 4, SeekOrigin.Begin);
int next = fatStream.ReadInt32();
if (next != nextSecId)
{
nextSecId = next;
}
else
{
throw new InvalidDataException("Cyclic sector chain found. File is corrupted");
}
}
return result;
}
private List GetMiniSectorChain(int secId)
{
List result = [];
if (secId == Sector.Endofchain)
{
return result;
}
var miniFat = GetNormalSectorChain(_header.FirstMiniFATSectorID);
var miniStream = GetNormalSectorChain(RootEntry.StartSetc);
var miniFatView = new StreamView(miniFat, GetSectorSize(), _header.MiniFATSectorsNumber * Sector.MinisectorSize, null, SourceStream);
var miniStreamView = new StreamView(miniStream, GetSectorSize(), RootStorage.Size, null, SourceStream);
var miniFatReader = new BinaryReader(miniFatView);
var nextSecId = secId;
while (true)
{
if (nextSecId == Sector.Endofchain)
{
break;
}
var ms = new Sector(Sector.MinisectorSize, SourceStream);
ms.Id = nextSecId;
ms.Type = SectorType.Mini;
miniStreamView.Seek(nextSecId * Sector.MinisectorSize, SeekOrigin.Begin);
_=miniStreamView.Read(ms.GetData(), 0, Sector.MinisectorSize);
result.Add(ms);
miniFatView.Seek(nextSecId * 4, SeekOrigin.Begin);
nextSecId = miniFatReader.ReadInt32();
}
return result;
}
internal List 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 Exception("Unsupproted chain type");
}
}
public CfStorage RootStorage { get; private set; }
public int Version => _header.MajorVersion;
internal RBTree CreateNewTree()
{
return new RBTree();
}
internal RBTree GetChildrenTree(int sid)
{
var bst = new RBTree();
DoLoadChildren(bst, _directoryEntries[sid]);
return bst;
}
private void DoLoadChildren(RBTree bst, DirectoryEntry de)
{
if (de.Child == DirectoryEntry.Nostream)
{
return;
}
if (_directoryEntries[de.Child].StgType == StgType.StgInvalid)
{
return;
}
LoadSiblings(bst, _directoryEntries[de.Child]);
NullifyChildNodes(_directoryEntries[de.Child]);
bst.Insert(_directoryEntries[de.Child]);
}
private static void NullifyChildNodes(DirectoryEntry de)
{
de.Parent = null;
de.Left = null;
de.Right = null;
}
private readonly List _levelSiDs = [];
private void LoadSiblings(RBTree bst, DirectoryEntry de)
{
_levelSiDs.Clear();
if (de.LeftSibling != DirectoryEntry.Nostream)
{
DoLoadSiblings(bst, _directoryEntries[de.LeftSibling]);
}
if (de.RightSibling == DirectoryEntry.Nostream)
{
return;
}
_levelSiDs.Add(de.RightSibling);
DoLoadSiblings(bst, _directoryEntries[de.RightSibling]);
}
private void DoLoadSiblings(RBTree bst, DirectoryEntry de)
{
if (ValidateSibling(de.LeftSibling))
{
_levelSiDs.Add(de.LeftSibling);
DoLoadSiblings(bst, _directoryEntries[de.LeftSibling]);
}
if (ValidateSibling(de.RightSibling))
{
_levelSiDs.Add(de.RightSibling);
DoLoadSiblings(bst, _directoryEntries[de.RightSibling]);
}
NullifyChildNodes(de);
bst.Insert(de);
}
private bool ValidateSibling(int sid)
{
if (sid == DirectoryEntry.Nostream)
{
return false;
}
if (sid >= _directoryEntries.Count)
{
return false;
}
if (_directoryEntries[sid].StgType == StgType.StgInvalid)
{
return false;
}
if (!Enum.IsDefined(typeof(StgType), _directoryEntries[sid].StgType))
{
return false;
}
if (_levelSiDs.Contains(sid))
{
throw new InvalidDataException("Cyclic reference of directory item");
}
return true;
}
private void LoadDirectories()
{
var directoryChain = GetSectorChain(_header.FirstDirectorySectorID, SectorType.Normal);
if (_header.FirstDirectorySectorID == Sector.Endofchain)
{
_header.FirstDirectorySectorID = directoryChain[0].Id;
}
var dirReader = new StreamView(directoryChain, GetSectorSize(), directoryChain.Count * GetSectorSize(), null, SourceStream);
while (dirReader.Position < directoryChain.Count * GetSectorSize())
{
var de = DirectoryEntry.New(string.Empty, StgType.StgInvalid, _directoryEntries);
de.Read(dirReader, Version);
}
}
internal byte[] GetData(CFStream cFStream)
{
AssertDisposed();
byte[] result;
var de = cFStream.DirEntry;
if (de.Size < _header.MinSizeStandardStream)
{
var miniView = new StreamView(GetSectorChain(de.StartSetc, SectorType.Mini), Sector.MinisectorSize, de.Size, null, SourceStream);
using var br = new BinaryReader(miniView);
result = br.ReadBytes((int) de.Size);
}
else
{
var sView = new StreamView(GetSectorChain(de.StartSetc, SectorType.Normal), GetSectorSize(), de.Size, null, SourceStream);
result = new byte[(int) de.Size];
_=sView.Read(result, 0, result.Length);
}
return result;
}
private static int Ceiling(double d) => (int) Math.Ceiling(d);
private readonly bool _closeStream = true;
internal bool IsClosed { get; private set; }
#region IDisposable Members
private readonly object _lockObject = new();
public void Dispose()
{
try
{
if (!IsClosed)
{
lock (_lockObject)
{
if (_sectors != null)
{
_sectors.Clear();
_sectors = null;
}
RootStorage = null;
_header = null;
_directoryEntries.Clear();
_directoryEntries = null;
}
if (SourceStream != null && _closeStream && !Configuration.HasFlag(CFSConfiguration.LeaveOpen))
{
SourceStream.Dispose();
}
}
}
finally
{
IsClosed = true;
}
GC.SuppressFinalize(this);
}
#endregion IDisposable Members
private List _directoryEntries = [];
internal DirectoryEntry RootEntry => _directoryEntries[0];
private void AssertDisposed()
{
if (IsClosed)
{
throw new ObjectDisposedException("Compound File closed: cannot access data");
}
}
}
#endregion Modified OpenMCDF
}