using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
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 = new byte [] { 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);
foreach (var chunk in Chunks)
{
var compoundFileStream = cf.RootStorage.GetStream(chunk);
if (compoundFileStream == null || !IsValidChunk(chunk, compoundFileStream.GetData()))
{
return false;
}
}
return true;
}
catch
{
return false;
}
}
#region Modified OpenMCDF
// -------------------------------------------------------------
// This is a porting from java code, under MIT license of |
// the beautiful Red-Black Tree implementation you can find at |
// http://en.literateprograms.org/Red-black_tree_(Java)#chunk |
// Many Thanks to original Implementors. |
// -------------------------------------------------------------
internal enum Color
{ RED = 0, BLACK = 1 }
internal enum NodeOp
{
LAssigned, RAssigned, CAssigned, PAssigned, VAssigned
}
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 == null ? Color.BLACK : n.Color;
public RBTree()
{ }
public RBTree(IRBNode root)
{ Root = root; }
private IRBNode Lookup(IRBNode template)
{
IRBNode 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)
{
IRBNode 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)
{
IRBNode 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;
IRBNode insertedNode = newNode;
if (Root == null) Root = insertedNode;
else
{
IRBNode n = Root;
while (true)
{
int 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 = insertedNode;
break;
}
n = n.Left;
}
else
{
if (n.Right == null)
{
n.Right = insertedNode;
break;
}
n = n.Right;
}
}
insertedNode.Parent = n;
}
Insert1(insertedNode);
NodeInserted?.Invoke(insertedNode);
}
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());
}
private static IRBNode MaximumNode(IRBNode n)
{
while (n.Right != null)
n = n.Right;
return n;
}
public void VisitTree(Action action)
{
IRBNode walker = Root;
if (walker != null)
DoVisitTree(action, walker);
}
private void DoVisitTree(Action action, IRBNode walker)
{
if (walker.Left != null)
{
DoVisitTree(action, walker.Left);
}
action?.Invoke(walker);
if (walker.Right != null)
{
DoVisitTree(action, walker.Right);
}
}
internal void VisitTreeNodes(Action action)
{
IRBNode walker = Root;
if (walker != null)
{
DoVisitTreeNodes(action, walker);
}
}
private void DoVisitTreeNodes(Action action, IRBNode walker)
{
if (walker.Left != null)
{
DoVisitTreeNodes(action, walker.Left);
}
action?.Invoke(walker);
if (walker.Right != null)
{
DoVisitTreeNodes(action, walker.Right);
}
}
public class RBTreeEnumerator : IEnumerator
{
private int position = -1;
private Queue heap = new();
internal RBTreeEnumerator(RBTree tree)
{ tree.VisitTreeNodes(item => heap.Enqueue(item)); }
public IRBNode Current => heap.ElementAt(position);
public void Dispose()
{ }
object System.Collections.IEnumerator.Current => heap.ElementAt(position);
public bool MoveNext() => (++position < heap.Count);
public void Reset()
{ position = -1; }
}
public RBTreeEnumerator GetEnumerator() => new RBTreeEnumerator(this);
internal void FireNodeOperation(IRBNode node, NodeOp operation)
{
NodeOperation?.Invoke(node, operation);
}
internal event Action NodeInserted;
internal event Action NodeOperation;
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The Original Code is OpenMCDF - Compound Document Format library.
*
* The Initial Developer of the Original Code is Federico Blaseotto.*/
internal enum SectorType
{
Normal, Mini, FAT, DIFAT, RangeLockSector, Directory
}
internal sealed class Sector : IDisposable
{
public static int MinisectorSize = 64;
public const int Freesect = unchecked((int)0xFFFFFFFF);
public const int Endofchain = unchecked((int)0xFFFFFFFE);
public const int Fatsect = unchecked((int)0xFFFFFFFD);
public const int Difsect = unchecked((int)0xFFFFFFFC);
public bool DirtyFlag { get; set; }
public bool IsStreamed => _stream != null && Size != MinisectorSize && Id * Size + Size < _stream.Length;
private readonly Stream _stream;
public Sector(int size, Stream stream)
{
this.Size = size;
this._stream = stream;
}
public Sector(int size, byte[] data)
{
this.Size = size;
this._data = data;
this._stream = null;
}
public Sector(int size)
{
this.Size = size;
this._data = null;
this._stream = null;
}
internal SectorType Type { get; set; }
public int Id { get; set; } = -1;
public int Size { get; private set; } = 0;
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 ZeroData()
{
_data = new byte[Size];
DirtyFlag = true;
}
public void InitFATData()
{
_data = new byte[Size];
for (int i = 0; i < Size; i++)
_data[i] = 0xFF;
DirtyFlag = true;
}
internal void ReleaseData() => this._data = null;
private readonly object _lockObject = new Object();
#region IDisposable Members
private bool _disposed;
void IDisposable.Dispose()
{
try
{
if (!_disposed)
{
lock (_lockObject)
{
this._data = null;
this.DirtyFlag = false;
this.Id = Sector.Endofchain;
this.Size = 0;
}
}
}
finally { _disposed = true; }
GC.SuppressFinalize(this);
}
#endregion IDisposable Members
}
internal enum StgType : int
{
StgInvalid = 0,
StgStorage = 1,
StgStream = 2,
StgLockbytes = 3,
StgProperty = 4,
StgRoot = 5
}
internal sealed class DirectoryEntry : IRBNode
{
internal const int THIS_IS_GREATER = 1;
internal const int OTHER_IS_GREATER = -1;
private IList dirRepository;
public int SID { get; set; } = -1;
internal const Int32 NOSTREAM = unchecked((int)0xFFFFFFFF);
private DirectoryEntry(String name, StgType stgType, IList dirRepository)
{
this.dirRepository = dirRepository;
this.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;
}
this.SetEntryName(name);
}
public byte[] EntryName { get; private set; } = new byte[64];
public String GetEntryName()
{
if (EntryName != null && EntryName.Length > 0)
return Encoding.Unicode.GetString(EntryName).Remove((NameLength - 1) / 2);
else return String.Empty;
}
public void SetEntryName(String entryName)
{
if (entryName.Contains(@"\") || entryName.Contains(@"/") ||
entryName.Contains(@":") || entryName.Contains(@"!"))
throw new Exception("Invalid character in entry: the characters '\\', '/', ':','!' cannot be used in entry name");
if (entryName.Length > 31)
throw new Exception("Entry name MUST be smaller than 31 characters");
byte[] newName = null;
byte[] temp = Encoding.Unicode.GetBytes(entryName);
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; } = StgType.StgInvalid;
public Color Color { get; set; } = Color.BLACK;
public Int32 LeftSibling { get; set; } = NOSTREAM;
public Int32 RightSibling { get; set; } = NOSTREAM;
public Int32 Child { get; set; } = NOSTREAM;
public Guid StorageCLSID { get; set; } = Guid.NewGuid();
public Int32 StateBits { get; set; }
public byte[] CreationDate { get; set; } = new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public byte[] ModifyDate { get; set; } = new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public Int32 StartSetc { get; set; } = Sector.Endofchain;
public long Size { get; set; }
public int CompareTo(object obj)
{
DirectoryEntry otherDir = obj as DirectoryEntry;
if (otherDir == null)
throw new Exception("Invalid casting: compared object does not implement IDirectorEntry interface");
if (this.NameLength > otherDir.NameLength)
return THIS_IS_GREATER;
else if (this.NameLength < otherDir.NameLength)
return OTHER_IS_GREATER;
else
{
String thisName = Encoding.Unicode.GetString(this.EntryName, 0, this.NameLength);
String otherName = Encoding.Unicode.GetString(otherDir.EntryName, 0, otherDir.NameLength);
for (int z = 0; z < thisName.Length; z++)
{
char thisChar = char.ToUpperInvariant(thisName[z]);
char otherChar = char.ToUpperInvariant(otherName[z]);
if (thisChar > otherChar)
return THIS_IS_GREATER;
else if (thisChar < otherChar)
return OTHER_IS_GREATER;
}
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 (BinaryReader 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
{
if (LeftSibling == NOSTREAM)
return null;
return dirRepository[LeftSibling];
}
set
{
LeftSibling = value != null ? (value as DirectoryEntry).SID : NOSTREAM;
if (LeftSibling != NOSTREAM)
dirRepository[LeftSibling].Parent = this;
}
}
public IRBNode Right
{
get
{
if (RightSibling == NOSTREAM)
return null;
return dirRepository[RightSibling];
}
set
{
RightSibling = value != null ? ((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 = null;
if (dirRepository != null)
{
de = new DirectoryEntry(name, stgType, dirRepository);
dirRepository.Add(de);
de.SID = dirRepository.Count - 1;
}
else
throw new ArgumentNullException("dirRepository", "Directory repository cannot be null in New() method");
return de;
}
internal static DirectoryEntry Mock(String name, StgType stgType) => new DirectoryEntry(name, stgType, null);
internal static DirectoryEntry TryNew(String name, StgType stgType, IList dirRepository)
{
DirectoryEntry de = new DirectoryEntry(name, stgType, dirRepository);
if (de != null)
{
for (int i = 0; i < dirRepository.Count; i++)
{
if (dirRepository[i].StgType == StgType.StgInvalid)
{
dirRepository[i] = de;
de.SID = i;
return de;
}
}
}
dirRepository.Add(de);
de.SID = dirRepository.Count - 1;
return de;
}
public override string ToString() => $"{Name} [{SID}]{(StgType == StgType.StgStream ? "Stream" : "Storage")}";
public void AssignValueTo(IRBNode other)
{
DirectoryEntry d = other as DirectoryEntry;
d.SetEntryName(GetEntryName());
d.CreationDate = new byte[CreationDate.Length];
CreationDate.CopyTo(d.CreationDate, 0);
d.ModifyDate = new byte[ModifyDate.Length];
ModifyDate.CopyTo(d.ModifyDate, 0);
d.Size = Size;
d.StartSetc = StartSetc;
d.StateBits = StateBits;
d.StgType = StgType;
d.StorageCLSID = new Guid(StorageCLSID.ToByteArray());
d.Child = Child;
}
}
internal abstract class CFItem : IComparable
{
private CompoundFile compoundFile;
protected CompoundFile CompoundFile => compoundFile;
protected void CheckDisposed()
{
if (compoundFile.IsClosed)
throw new ObjectDisposedException("Owner Compound file has been closed and owned items have been invalidated");
}
protected CFItem()
{ }
protected CFItem(CompoundFile compoundFile)
{ this.compoundFile = compoundFile; }
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 (System.Object.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 string Name
{
get
{
var n = DirEntry.GetEntryName();
return (n != null && n.Length > 0) ? n.TrimEnd('\0') : string.Empty;
}
}
public long Size => DirEntry.Size;
public bool IsStorage => DirEntry.StgType == StgType.StgStorage;
public bool IsStream => DirEntry.StgType == StgType.StgStream;
public bool IsRoot => DirEntry.StgType == StgType.StgRoot;
public DateTime CreationDate
{
get => DateTime.FromFileTime(BitConverter.ToInt64(DirEntry.CreationDate, 0));
set
{
if (DirEntry.StgType != StgType.StgStream && DirEntry.StgType != StgType.StgRoot)
DirEntry.CreationDate = BitConverter.GetBytes((value.ToFileTime()));
else
throw new Exception("Creation Date can only be set on storage entries");
}
}
public DateTime ModifyDate
{
get => DateTime.FromFileTime(BitConverter.ToInt64(DirEntry.ModifyDate, 0));
set
{
if (DirEntry.StgType != StgType.StgStream && DirEntry.StgType != StgType.StgRoot)
DirEntry.ModifyDate = BitConverter.GetBytes((value.ToFileTime()));
else
throw new Exception("Modify Date can only be set on storage entries");
}
}
public Guid CLSID
{
get => DirEntry.StorageCLSID;
set
{
if (DirEntry.StgType != StgType.StgStream)
DirEntry.StorageCLSID = value;
else
throw new Exception("Object class GUID can only be set on Root and Storage entries");
}
}
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");
this.DirEntry = dirEntry;
}
public Byte[] GetData()
{
CheckDisposed();
return this.CompoundFile.GetData(this);
}
public int Read(byte[] buffer, long position, int count)
{
CheckDisposed();
return this.CompoundFile.ReadData(this, position, buffer, 0, count);
}
internal int Read(byte[] buffer, long position, int offset, int count)
{
CheckDisposed();
return this.CompoundFile.ReadData(this, position, buffer, offset, count);
}
}
internal sealed class CFStorage : CFItem
{
private RBTree children;
internal RBTree Children
{
get
{
if (children == null)
children = LoadChildren(this.DirEntry.SID) ?? this.CompoundFile.CreateNewTree();
return children;
}
}
internal CFStorage(CompoundFile compFile, DirectoryEntry dirEntry)
: base(compFile)
{
if (dirEntry == null || dirEntry.SID < 0)
throw new Exception("Attempting to create a CFStorage using an unitialized directory");
this.DirEntry = dirEntry;
}
private RBTree LoadChildren(int SID)
{
RBTree childrenTree = this.CompoundFile.GetChildrenTree(SID);
if (childrenTree.Root != null)
this.DirEntry.Child = (childrenTree.Root as DirectoryEntry).SID;
else
this.DirEntry.Child = DirectoryEntry.NOSTREAM;
return childrenTree;
}
public CFStream GetStream(String streamName)
{
CheckDisposed();
DirectoryEntry tmp = DirectoryEntry.Mock(streamName, StgType.StgStream);
if (Children.TryLookup(tmp, out IRBNode outDe) && (((DirectoryEntry)outDe).StgType == StgType.StgStream))
return new CFStream(this.CompoundFile, (DirectoryEntry)outDe);
else
throw new KeyNotFoundException("Cannot find item [" + streamName + "] within the current storage");
}
public CFStream TryGetStream(String streamName)
{
CheckDisposed();
DirectoryEntry tmp = DirectoryEntry.Mock(streamName, StgType.StgStream);
if (Children.TryLookup(tmp, out IRBNode outDe) && ((outDe as DirectoryEntry).StgType == StgType.StgStream))
return new CFStream(this.CompoundFile, (DirectoryEntry)outDe);
else
return null;
}
public CFStorage GetStorage(String storageName)
{
CheckDisposed();
DirectoryEntry template = DirectoryEntry.Mock(storageName, StgType.StgInvalid);
if (Children.TryLookup(template, out IRBNode outDe) && (outDe as DirectoryEntry).StgType == StgType.StgStorage)
return new CFStorage(this.CompoundFile, outDe as DirectoryEntry);
else
throw new KeyNotFoundException("Cannot find item [" + storageName + "] within the current storage");
}
public CFStorage TryGetStorage(String storageName)
{
CheckDisposed();
DirectoryEntry template = DirectoryEntry.Mock(storageName, StgType.StgInvalid);
if (Children.TryLookup(template, out IRBNode outDe) && ((DirectoryEntry)outDe).StgType == StgType.StgStorage)
return new CFStorage(this.CompoundFile, outDe as DirectoryEntry);
else
return null;
}
public void VisitEntries(Action action, bool recursive)
{
CheckDisposed();
if (action != null)
{
List subStorages = new List();
void internalAction(IRBNode targetNode)
{
DirectoryEntry d = targetNode as DirectoryEntry;
if (d.StgType == StgType.StgStream)
action(new CFStream(this.CompoundFile, d));
else
action(new CFStorage(this.CompoundFile, d));
if (d.Child != DirectoryEntry.NOSTREAM)
subStorages.Add(targetNode);
return;
}
this.Children.VisitTreeNodes(internalAction);
if (recursive && subStorages.Count > 0)
foreach (IRBNode n in subStorages)
(new CFStorage(this.CompoundFile, n as DirectoryEntry)).VisitEntries(action, recursive);
}
}
}
internal class CFItemComparer : IComparer
{
public int Compare(CFItem x, CFItem y) => (x.DirEntry.CompareTo(y.DirEntry));
}
internal sealed class Header
{
public byte[] HeaderSignature { get; private set; } = new byte[] { 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; private set; } = 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)
{
using (BinaryReader 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 byte[] OLE_CFS_SIGNATURE = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
private void CheckSignature()
{
for (int i = 0; i < HeaderSignature.Length; i++)
if (HeaderSignature[i] != OLE_CFS_SIGNATURE[i])
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 = false;
private readonly List _freeSectors = new List();
public IEnumerable FreeSectors => _freeSectors;
public StreamView(List sectorChain, int sectorSize, Stream stream)
{
if (sectorSize <= 0)
throw new Exception("Sector size must be greater than zero");
this._sectorChain = sectorChain ?? throw new Exception("Sector Chain cannot be null");
this._sectorSize = sectorSize;
this._stream = stream;
}
public StreamView(List sectorChain, int sectorSize, long length, Queue availableSectors, Stream stream, bool isFatStream = false)
: this(sectorChain, sectorSize, stream)
{
this._isFatStream = isFatStream;
AdjustLength(length, availableSectors);
}
public List BaseSectorChain => _sectorChain;
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("value");
_position = value;
}
}
private byte[] buf = new byte[4];
public int ReadInt32()
{
this.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;
int nToRead = 0;
if (_sectorChain != null && _sectorChain.Count > 0)
{
// First sector
int secIndex = (int)(_position / _sectorSize);
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;
// Central sectors
while (nRead < (count - _sectorSize))
{
nToRead = _sectorSize;
Buffer.BlockCopy(_sectorChain[secIndex].GetData(), 0, buffer, offset + nRead, nToRead);
nRead += nToRead;
++secIndex;
}
// Last sector
nToRead = count - nRead;
if (nToRead != 0)
{
Buffer.BlockCopy(_sectorChain[secIndex].GetData(), 0, buffer, offset + nRead, nToRead);
nRead += nToRead;
}
_position += nRead;
return nRead;
}
else
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)
{
this._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 = null;
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 { get; private set; } = CFSConfiguration.Default;
internal int GetSectorSize() => 2 << (header.SectorShift - 1);
private const int HEADER_DIFAT_ENTRIES_COUNT = 109;
private readonly int DIFAT_SECTOR_FAT_ENTRIES_COUNT = 127;
private readonly int FAT_SECTOR_ENTRIES_COUNT = 128;
private const int SIZE_OF_SID = 4;
private const int FLUSHING_QUEUE_SIZE = 6000;
private const int FLUSHING_BUFFER_MAX_SIZE = 1024 * 1024 * 16;
private List sectors = new List();
private Header header;
internal Stream sourceStream = null;
public CompoundFile(Stream stream, CFSConfiguration configParameters)
{
this.closeStream = !configParameters.HasFlag(CFSConfiguration.LeaveOpen);
LoadStream(stream);
DIFAT_SECTOR_FAT_ENTRIES_COUNT = (GetSectorSize() / 4) - 1;
FAT_SECTOR_ENTRIES_COUNT = (GetSectorSize() / 4);
}
private string fileName = string.Empty;
private void Load(Stream stream)
{
try
{
this.header = new Header();
this.directoryEntries = new List();
this.sourceStream = stream;
header.Read(stream);
int n_sector = Ceiling(((stream.Length - GetSectorSize()) / (double)GetSectorSize()));
if (stream.Length > 0x7FFFFF0)
this._transactionLockAllocated = true;
sectors = new List();
for (int i = 0; i < n_sector; i++)
sectors.Add(null);
LoadDirectories();
this.RootStorage = new CFStorage(this, directoryEntries[0]);
}
catch (Exception)
{
if (stream != null && closeStream)
stream.Dispose();
throw;
}
}
private void LoadFile(String fileName)
{
this.fileName = fileName;
FileStream fs = null;
try
{
fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
Load(fs);
}
catch
{
if (fs != null)
fs.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);
}
public bool HasSourceStream => sourceStream != null;
private void PersistMiniStreamToStream(List miniSectorChain)
{
List miniStream = GetSectorChain(RootEntry.StartSetc, SectorType.Normal);
StreamView miniStreamView = new StreamView(miniStream, GetSectorSize(), this.RootStorage.Size, null, sourceStream);
for (int i = 0; i < miniSectorChain.Count; i++)
{
Sector s = miniSectorChain[i];
if (s.Id == -1)
throw new Exception("Invalid minisector index");
miniStreamView.Seek(Sector.MinisectorSize * s.Id, SeekOrigin.Begin);
miniStreamView.Write(s.GetData(), 0, Sector.MinisectorSize);
}
}
private void AllocateMiniSectorChain(List sectorChain)
{
List miniFAT = GetSectorChain(header.FirstMiniFATSectorID, SectorType.Normal);
List miniStream = GetSectorChain(RootEntry.StartSetc, SectorType.Normal);
StreamView miniFATView = new StreamView(miniFAT, GetSectorSize(),
header.MiniFATSectorsNumber * Sector.MinisectorSize,
null, this.sourceStream, true);
StreamView miniStreamView = new StreamView(miniStream, GetSectorSize(),
this.RootStorage.Size, null, sourceStream);
for (int i = 0; i < sectorChain.Count; i++)
{
Sector s = sectorChain[i];
if (s.Id == -1)
{
miniStreamView.Seek(this.RootStorage.Size + Sector.MinisectorSize, SeekOrigin.Begin);
s.Id = (int)(miniStreamView.Position - Sector.MinisectorSize) / Sector.MinisectorSize;
this.RootStorage.DirEntry.Size = miniStreamView.Length;
}
}
for (int i = 0; i < sectorChain.Count - 1; i++)
{
Int32 currentId = sectorChain[i].Id;
Int32 nextId = sectorChain[i + 1].Id;
miniFATView.Seek(currentId * 4, SeekOrigin.Begin);
miniFATView.Write(BitConverter.GetBytes(nextId), 0, 4);
}
miniFATView.Seek(sectorChain[sectorChain.Count - 1].Id * SIZE_OF_SID, SeekOrigin.Begin);
miniFATView.Write(BitConverter.GetBytes(Sector.Endofchain), 0, 4);
AllocateSectorChain(miniStreamView.BaseSectorChain);
AllocateSectorChain(miniFATView.BaseSectorChain);
if (miniFAT.Count > 0)
{
this.RootStorage.DirEntry.StartSetc = miniStream[0].Id;
header.MiniFATSectorsNumber = (uint)miniFAT.Count;
header.FirstMiniFATSectorID = miniFAT[0].Id;
}
}
private void SetSectorChain(List sectorChain)
{
if (sectorChain == null || sectorChain.Count == 0)
return;
SectorType _st = sectorChain[0].Type;
if (_st == SectorType.Normal)
AllocateSectorChain(sectorChain);
else if (_st == SectorType.Mini)
AllocateMiniSectorChain(sectorChain);
}
private void AllocateSectorChain(List sectorChain)
{
foreach (Sector s in sectorChain)
{
if (s.Id == -1)
{
sectors.Add(s);
s.Id = sectors.Count - 1;
}
}
AllocateFATSectorChain(sectorChain);
}
internal bool _transactionLockAdded = false;
internal int _lockSectorId = -1;
internal bool _transactionLockAllocated = false;
private void CheckForLockSector()
{
if (_transactionLockAdded && !_transactionLockAllocated)
{
StreamView fatStream = new StreamView(GetFatSectorChain(), GetSectorSize(), sourceStream);
fatStream.Seek(_lockSectorId * 4, SeekOrigin.Begin);
fatStream.Write(BitConverter.GetBytes(Sector.Endofchain), 0, 4);
_transactionLockAllocated = true;
}
}
private void AllocateFATSectorChain(List sectorChain)
{
List fatSectors = GetSectorChain(-1, SectorType.FAT);
StreamView fatStream = new StreamView(fatSectors, GetSectorSize(),
header.FATSectorsNumber * GetSectorSize(), null,
sourceStream, true);
for (int i = 0; i < sectorChain.Count - 1; i++)
{
Sector sN = sectorChain[i + 1];
Sector sC = sectorChain[i];
fatStream.Seek(sC.Id * 4, SeekOrigin.Begin);
fatStream.Write(BitConverter.GetBytes(sN.Id), 0, 4);
}
fatStream.Seek(sectorChain[sectorChain.Count - 1].Id * 4, SeekOrigin.Begin);
fatStream.Write(BitConverter.GetBytes(Sector.Endofchain), 0, 4);
AllocateDIFATSectorChain(fatStream.BaseSectorChain);
}
private void AllocateDIFATSectorChain(List FATsectorChain)
{
header.FATSectorsNumber = FATsectorChain.Count;
foreach (Sector s in FATsectorChain)
{
if (s.Id == -1)
{
sectors.Add(s);
s.Id = sectors.Count - 1;
s.Type = SectorType.FAT;
}
}
int nCurrentSectors = sectors.Count;
int nDIFATSectors = (int)header.DIFATSectorsNumber;
if (FATsectorChain.Count > HEADER_DIFAT_ENTRIES_COUNT)
{
nDIFATSectors = Ceiling((double)(FATsectorChain.Count - HEADER_DIFAT_ENTRIES_COUNT) / DIFAT_SECTOR_FAT_ENTRIES_COUNT);
nDIFATSectors = LowSaturation(nDIFATSectors - (int)header.DIFATSectorsNumber); //required DIFAT
}
nCurrentSectors += nDIFATSectors;
while (header.FATSectorsNumber * FAT_SECTOR_ENTRIES_COUNT < nCurrentSectors)
{
Sector extraFATSector = new Sector(GetSectorSize(), sourceStream);
sectors.Add(extraFATSector);
extraFATSector.Id = sectors.Count - 1;
extraFATSector.Type = SectorType.FAT;
FATsectorChain.Add(extraFATSector);
header.FATSectorsNumber++;
nCurrentSectors++;
if (nDIFATSectors * DIFAT_SECTOR_FAT_ENTRIES_COUNT <
(header.FATSectorsNumber > HEADER_DIFAT_ENTRIES_COUNT ?
header.FATSectorsNumber - HEADER_DIFAT_ENTRIES_COUNT :
0))
{
nDIFATSectors++;
nCurrentSectors++;
}
}
List difatSectors = GetSectorChain(-1, SectorType.DIFAT);
StreamView difatStream = new StreamView(difatSectors, GetSectorSize(), sourceStream);
for (int i = 0; i < FATsectorChain.Count; i++)
{
if (i < HEADER_DIFAT_ENTRIES_COUNT)
header.DIFAT[i] = FATsectorChain[i].Id;
else
{
if (i != HEADER_DIFAT_ENTRIES_COUNT && (i - HEADER_DIFAT_ENTRIES_COUNT) % DIFAT_SECTOR_FAT_ENTRIES_COUNT == 0)
{
difatStream.Write(new byte[sizeof(int)], 0, sizeof(int));
}
difatStream.Write(BitConverter.GetBytes(FATsectorChain[i].Id), 0, sizeof(int));
}
}
for (int i = 0; i < difatStream.BaseSectorChain.Count; i++)
{
if (difatStream.BaseSectorChain[i].Id == -1)
{
sectors.Add(difatStream.BaseSectorChain[i]);
difatStream.BaseSectorChain[i].Id = sectors.Count - 1;
difatStream.BaseSectorChain[i].Type = SectorType.DIFAT;
}
}
header.DIFATSectorsNumber = (uint)nDIFATSectors;
if (difatStream.BaseSectorChain != null && difatStream.BaseSectorChain.Count > 0)
{
header.FirstDIFATSectorID = difatStream.BaseSectorChain[0].Id;
header.DIFATSectorsNumber = (uint)difatStream.BaseSectorChain.Count;
for (int i = 0; i < difatStream.BaseSectorChain.Count - 1; i++)
Buffer.BlockCopy(BitConverter.GetBytes(difatStream.BaseSectorChain[i + 1].Id),
0, difatStream.BaseSectorChain[i].GetData(),
GetSectorSize() - sizeof(int), 4);
Buffer.BlockCopy(BitConverter.GetBytes(Sector.Endofchain), 0,
difatStream.BaseSectorChain[difatStream.BaseSectorChain.Count - 1].GetData(),
GetSectorSize() - sizeof(int), sizeof(int));
}
else header.FirstDIFATSectorID = Sector.Endofchain;
StreamView fatSv = new StreamView(FATsectorChain, GetSectorSize(), header.FATSectorsNumber * GetSectorSize(), null, sourceStream);
for (int i = 0; i < header.DIFATSectorsNumber; i++)
{
fatSv.Seek(difatStream.BaseSectorChain[i].Id * 4, SeekOrigin.Begin);
fatSv.Write(BitConverter.GetBytes(Sector.Difsect), 0, 4);
}
for (int i = 0; i < header.FATSectorsNumber; i++)
{
fatSv.Seek(fatSv.BaseSectorChain[i].Id * 4, SeekOrigin.Begin);
fatSv.Write(BitConverter.GetBytes(Sector.Fatsect), 0, 4);
}
header.FATSectorsNumber = fatSv.BaseSectorChain.Count;
}
private List GetDifatSectorChain()
{
int validationCount = 0;
List result = new List();
int nextSecID
= Sector.Endofchain;
if (header.DIFATSectorsNumber != 0)
{
validationCount = (int)header.DIFATSectorsNumber;
Sector 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 (true && validationCount >= 0)
{
nextSecID = BitConverter.ToInt32(s.GetData(), GetSectorSize() - 4);
if (nextSecID == Sector.Freesect || nextSecID == 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()
{
int N_HEADER_FAT_ENTRY = 109;
List result = new List();
int nextSecID = Sector.Endofchain;
List difatSectors = GetDifatSectorChain();
int idx = 0;
while (idx < header.FATSectorsNumber && idx < N_HEADER_FAT_ENTRY)
{
nextSecID = header.DIFAT[idx];
Sector 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 > N_HEADER_FAT_ENTRY ? (header.FATSectorsNumber - N_HEADER_FAT_ENTRY) * 4 : 0,
null, sourceStream);
byte[] nextDIFATSectorBuffer = new byte[4];
difatStream.Read(nextDIFATSectorBuffer, 0, 4);
nextSecID = BitConverter.ToInt32(nextDIFATSectorBuffer, 0);
int i = 0;
int nFat = N_HEADER_FAT_ENTRY;
while (nFat < header.FATSectorsNumber)
{
if (difatStream.Position == ((GetSectorSize() - 4) + i * GetSectorSize()))
{
difatStream.Seek(4, SeekOrigin.Current);
++i;
continue;
}
Sector 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)
{
List result = new List();
int nextSecID = secID;
List 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(String.Format("Next Sector ID reference is below zero. NextID : {0}", nextSecID));
if (nextSecID >= sectors.Count)
throw new InvalidDataException(String.Format("Next Sector ID reference an out of range sector. NextID : {0} while sector count {1}", nextSecID, sectors.Count));
Sector 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 = new List();
if (secID != Sector.Endofchain)
{
int nextSecID = secID;
List miniFAT = GetNormalSectorChain(header.FirstMiniFATSectorID);
List miniStream = GetNormalSectorChain(RootEntry.StartSetc);
StreamView miniFATView = new StreamView(miniFAT, GetSectorSize(), header.MiniFATSectorsNumber * Sector.MinisectorSize, null, sourceStream);
StreamView miniStreamView = new StreamView(miniStream, GetSectorSize(), RootStorage.Size, null, sourceStream);
BinaryReader miniFATReader = new BinaryReader(miniFATView);
nextSecID = secID;
while (true)
{
if (nextSecID == Sector.Endofchain)
break;
Sector ms = new Sector(Sector.MinisectorSize, sourceStream);
byte[] temp = new byte[Sector.MinisectorSize];
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 => this.header.MajorVersion;
internal RBTree CreateNewTree()
{
RBTree bst = new RBTree();
return bst;
}
internal RBTree GetChildrenTree(int sid)
{
RBTree bst = new RBTree();
DoLoadChildren(bst, directoryEntries[sid]);
return bst;
}
private RBTree DoLoadChildrenTrusted(DirectoryEntry de)
{
RBTree bst = null;
if (de.Child != DirectoryEntry.NOSTREAM)
bst = new RBTree(directoryEntries[de.Child]);
return bst;
}
private void DoLoadChildren(RBTree bst, DirectoryEntry de)
{
if (de.Child != DirectoryEntry.NOSTREAM)
{
if (directoryEntries[de.Child].StgType == StgType.StgInvalid) return;
LoadSiblings(bst, directoryEntries[de.Child]);
NullifyChildNodes(directoryEntries[de.Child]);
bst.Insert(directoryEntries[de.Child]);
}
}
private void NullifyChildNodes(DirectoryEntry de)
{
de.Parent = null;
de.Left = null;
de.Right = null;
}
private readonly List _levelSiDs = new List();
private void LoadSiblings(RBTree bst, DirectoryEntry de)
{
_levelSiDs.Clear();
if (de.LeftSibling != DirectoryEntry.NOSTREAM)
DoLoadSiblings(bst, directoryEntries[de.LeftSibling]);
if (de.RightSibling != DirectoryEntry.NOSTREAM)
{
_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)
{
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;
}
return false;
}
private void LoadDirectories()
{
List directoryChain = GetSectorChain(header.FirstDirectorySectorID, SectorType.Normal);
if (header.FirstDirectorySectorID == Sector.Endofchain)
header.FirstDirectorySectorID = directoryChain[0].Id;
StreamView dirReader = new StreamView(directoryChain, GetSectorSize(), directoryChain.Count * GetSectorSize(), null, sourceStream);
while (dirReader.Position < directoryChain.Count * GetSectorSize())
{
DirectoryEntry de = DirectoryEntry.New(String.Empty, StgType.StgInvalid, directoryEntries);
de.Read(dirReader, this.Version);
}
}
private void CheckFileLength() => throw new NotImplementedException();
internal int ReadData(CFStream cFStream, long position, byte[] buffer, int count)
{
if (count > buffer.Length)
throw new ArgumentException("count parameter exceeds buffer size");
DirectoryEntry de = cFStream.DirEntry;
count = (int)Math.Min(de.Size - position, count);
StreamView sView = null;
if (de.Size < header.MinSizeStandardStream)
sView = new StreamView(GetSectorChain(de.StartSetc, SectorType.Mini), Sector.MinisectorSize, de.Size, null, sourceStream);
else
sView = new StreamView(GetSectorChain(de.StartSetc, SectorType.Normal), GetSectorSize(), de.Size, null, sourceStream);
sView.Seek(position, SeekOrigin.Begin);
int result = sView.Read(buffer, 0, count);
return result;
}
internal int ReadData(CFStream cFStream, long position, byte[] buffer, int offset, int count)
{
DirectoryEntry de = cFStream.DirEntry;
count = (int)Math.Min(de.Size - offset, count);
StreamView sView = null;
if (de.Size < header.MinSizeStandardStream)
sView = new StreamView(GetSectorChain(de.StartSetc, SectorType.Mini), Sector.MinisectorSize, de.Size, null, sourceStream);
else
sView = new StreamView(GetSectorChain(de.StartSetc, SectorType.Normal), GetSectorSize(), de.Size, null, sourceStream);
sView.Seek(position, SeekOrigin.Begin);
int result = sView.Read(buffer, offset, count);
return result;
}
internal byte[] GetData(CFStream cFStream)
{
AssertDisposed();
byte[] result = null;
DirectoryEntry de = cFStream.DirEntry;
if (de.Size < header.MinSizeStandardStream)
{
var miniView = new StreamView(GetSectorChain(de.StartSetc, SectorType.Mini), Sector.MinisectorSize, de.Size, null, sourceStream);
using (BinaryReader 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;
}
public byte[] GetDataBySID(int sid)
{
AssertDisposed();
if (sid < 0)
return null;
byte[] result = null;
try
{
DirectoryEntry de = directoryEntries[sid];
if (de.Size < header.MinSizeStandardStream)
{
var miniView = new StreamView(GetSectorChain(de.StartSetc, SectorType.Mini), Sector.MinisectorSize, de.Size, null, sourceStream);
BinaryReader br = new BinaryReader(miniView);
result = br.ReadBytes((int)de.Size);
br.Dispose();
}
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);
}
}
catch
{
throw new Exception("Cannot get data for SID");
}
return result;
}
public Guid GetGuidBySID(int sid)
{
AssertDisposed();
if (sid < 0)
throw new Exception("Invalid SID");
DirectoryEntry de = directoryEntries[sid];
return de.StorageCLSID;
}
public Guid GetGuidForStream(int sid)
{
AssertDisposed();
if (sid < 0)
throw new Exception("Invalid SID");
Guid g = new Guid("00000000000000000000000000000000");
for (int i = sid - 1; i >= 0; i--)
{
if (directoryEntries[i].StorageCLSID != g && directoryEntries[i].StgType == StgType.StgStorage)
return directoryEntries[i].StorageCLSID;
}
return g;
}
private static int Ceiling(double d) => (int)Math.Ceiling(d);
private static int LowSaturation(int i) => i > 0 ? i : 0;
private bool closeStream = true;
internal bool IsClosed => _disposed;
#region IDisposable Members
private bool _disposed;//false
private object lockObject = new Object();
public void Dispose()
{
try
{
if (!_disposed)
{
lock (lockObject)
{
if (sectors != null)
{
sectors.Clear();
sectors = null;
}
this.RootStorage = null;
this.header = null;
this.directoryEntries.Clear();
this.directoryEntries = null;
this.fileName = null;
}
if (this.sourceStream != null && closeStream && !Configuration.HasFlag(CFSConfiguration.LeaveOpen))
this.sourceStream.Dispose();
}
}
finally
{
_disposed = true;
}
GC.SuppressFinalize(this);
}
#endregion IDisposable Members
private List directoryEntries = new List();
internal IList GetDirectories() => directoryEntries;
internal DirectoryEntry RootEntry => directoryEntries[0];
private IList FindDirectoryEntries(String entryName)
{
List result = new List();
foreach (DirectoryEntry d in directoryEntries)
if (d.GetEntryName() == entryName && d.StgType != StgType.StgInvalid)
result.Add(d);
return result;
}
public IList GetAllNamedEntries(String entryName)
{
IList r = FindDirectoryEntries(entryName);
List result = new List();
foreach (DirectoryEntry id in r)
if (id.GetEntryName() == entryName && id.StgType != StgType.StgInvalid)
result.Add(id.StgType == StgType.StgStorage ? new CFStorage(this, id) : new CFStream(this, id));
return result;
}
public int GetNumDirectories()
{
AssertDisposed();
return directoryEntries.Count;
}
public string GetNameDirEntry(int id)
{
AssertDisposed();
if (id < 0)
throw new Exception("Invalid Storage ID");
return directoryEntries[id].Name;
}
public StgType GetStorageType(int id)
{
AssertDisposed();
if (id < 0)
throw new Exception("Invalid Storage ID");
return directoryEntries[id].StgType;
}
private void AssertDisposed()
{
if (_disposed)
throw new ObjectDisposedException("Compound File closed: cannot access data");
}
}
#endregion Modified OpenMCDF
}