From 730b83a222594f68a0798b19fe0f067fb5edbb7d Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 19 Nov 2024 16:49:48 +1300 Subject: [PATCH] Add and use FileFormatException --- OpenMcdf.Tests/BinaryReaderTests.cs | 15 +++++---------- OpenMcdf/CfbBinaryReader.cs | 16 ++++++++-------- OpenMcdf/DifatSectorEnumerator.cs | 2 +- OpenMcdf/DirectoryEntries.cs | 2 +- OpenMcdf/DirectoryEntry.cs | 2 +- OpenMcdf/DirectoryTree.cs | 4 ++-- OpenMcdf/Fat.cs | 6 +++--- OpenMcdf/FatStream.cs | 8 ++++---- OpenMcdf/FileFormatException.cs | 19 +++++++++++++++++++ OpenMcdf/Header.cs | 10 +++++----- OpenMcdf/MiniFat.cs | 2 +- OpenMcdf/MiniFatChainEnumerator.cs | 2 +- OpenMcdf/MiniFatStream.cs | 12 ++++++------ StructuredStorageExplorer/MainForm.cs | 7 ++++--- 14 files changed, 61 insertions(+), 46 deletions(-) create mode 100644 OpenMcdf/FileFormatException.cs diff --git a/OpenMcdf.Tests/BinaryReaderTests.cs b/OpenMcdf.Tests/BinaryReaderTests.cs index 0c0a8e93..5ad2fe99 100644 --- a/OpenMcdf.Tests/BinaryReaderTests.cs +++ b/OpenMcdf.Tests/BinaryReaderTests.cs @@ -39,31 +39,26 @@ public void ReadHeader(string fileName) stream.CopyAllTo(memoryStream); memoryStream.WriteByte(1); // Corrupt signature - Assert.ThrowsException(() => reader.ReadHeader()); + Assert.ThrowsException(() => reader.ReadHeader()); stream.CopyAllTo(memoryStream); memoryStream.Position = 24; memoryStream.WriteByte(1); // Corrupt CLSID - Assert.ThrowsException(() => reader.ReadHeader()); + Assert.ThrowsException(() => reader.ReadHeader()); stream.CopyAllTo(memoryStream); memoryStream.Position = 26; memoryStream.WriteByte(1); // Corrupt Major version - Assert.ThrowsException(() => reader.ReadHeader()); + Assert.ThrowsException(() => reader.ReadHeader()); stream.CopyAllTo(memoryStream); memoryStream.Position = 28; memoryStream.WriteByte(1); // Corrupt byte order - Assert.ThrowsException(() => reader.ReadHeader()); + Assert.ThrowsException(() => reader.ReadHeader()); stream.CopyAllTo(memoryStream); memoryStream.Position = 32; memoryStream.WriteByte(1); // Corrupt mini sector shift - Assert.ThrowsException(() => reader.ReadHeader()); - - stream.CopyAllTo(memoryStream); - memoryStream.Position = 32; - memoryStream.WriteByte(1); // Corrupt mini sector shift - Assert.ThrowsException(() => reader.ReadHeader()); + Assert.ThrowsException(() => reader.ReadHeader()); } } diff --git a/OpenMcdf/CfbBinaryReader.cs b/OpenMcdf/CfbBinaryReader.cs index 6079c400..354a07b9 100644 --- a/OpenMcdf/CfbBinaryReader.cs +++ b/OpenMcdf/CfbBinaryReader.cs @@ -73,19 +73,19 @@ public Header ReadHeader() ReadExactly(buffer, 0, Header.Signature.Length); Span signature = buffer.AsSpan(0, Header.Signature.Length); if (!signature.SequenceEqual(Header.Signature)) - throw new FormatException("Invalid header signature."); + throw new FileFormatException("Invalid header signature."); header.CLSID = ReadGuid(); if (header.CLSID != Guid.Empty) - throw new FormatException($"Invalid header CLSID: {header.CLSID}."); + throw new FileFormatException($"Invalid header CLSID: {header.CLSID}."); header.MinorVersion = ReadUInt16(); header.MajorVersion = ReadUInt16(); if (header.MajorVersion is not (ushort)Version.V3 and not (ushort)Version.V4) - throw new FormatException($"Unsupported major version: {header.MajorVersion}."); + throw new FileFormatException($"Unsupported major version: {header.MajorVersion}."); else if (header.MinorVersion is not Header.ExpectedMinorVersion) Trace.WriteLine($"Unexpected minor version: {header.MinorVersion}."); ushort byteOrder = ReadUInt16(); if (byteOrder != Header.LittleEndian) - throw new FormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4})."); + throw new FileFormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4})."); header.SectorShift = ReadUInt16(); header.MiniSectorShift = ReadUInt16(); FillBuffer(6); @@ -95,7 +95,7 @@ public Header ReadHeader() FillBuffer(4); uint miniStreamCutoffSize = ReadUInt32(); if (miniStreamCutoffSize != Header.MiniStreamCutoffSize) - throw new FormatException($"Mini stream cutoff size must be {Header.MiniStreamCutoffSize} bytes."); + throw new FileFormatException($"Mini stream cutoff size must be {Header.MiniStreamCutoffSize} bytes."); header.FirstMiniFatSectorId = ReadUInt32(); header.MiniFatSectorCount = ReadUInt32(); header.FirstDifatSectorId = ReadUInt32(); @@ -113,7 +113,7 @@ public StorageType ReadStorageType() { var type = (StorageType)ReadByte(); if (type is not StorageType.Storage and not StorageType.Stream and not StorageType.Root and not StorageType.Unallocated) - throw new FormatException($"Invalid storage type: {type}."); + throw new FileFormatException($"Invalid storage type: {type}."); return type; } @@ -121,7 +121,7 @@ public NodeColor ReadColor() { var color = (NodeColor)ReadByte(); if (color is not NodeColor.Black and not NodeColor.Red) - throw new FormatException($"Invalid node color: {color}."); + throw new FileFormatException($"Invalid node color: {color}."); return color; } @@ -154,7 +154,7 @@ public DirectoryEntry ReadDirectoryEntry(Version version, uint sid) { entry.StreamLength = ReadUInt32(); if (entry.StreamLength > DirectoryEntry.MaxV3StreamLength) - throw new FormatException($"Stream length {entry.StreamLength} exceeds maximum value {DirectoryEntry.MaxV3StreamLength}."); + throw new FileFormatException($"Stream length {entry.StreamLength} exceeds maximum value {DirectoryEntry.MaxV3StreamLength}."); ReadUInt32(); // Skip unused 4 bytes } else if (version == Version.V4) diff --git a/OpenMcdf/DifatSectorEnumerator.cs b/OpenMcdf/DifatSectorEnumerator.cs index 455e30b9..6ed675a3 100644 --- a/OpenMcdf/DifatSectorEnumerator.cs +++ b/OpenMcdf/DifatSectorEnumerator.cs @@ -105,7 +105,7 @@ public void Add() { bool ok = MoveTo(header.DifatSectorCount - 1); if (!ok) - throw new InvalidOperationException("Failed to move to last DIFAT sector."); + throw new FileFormatException("The DIFAT sector count is invalid."); writer.Position = current.EndPosition - sizeof(uint); writer.Write(newDifatSector.Id); diff --git a/OpenMcdf/DirectoryEntries.cs b/OpenMcdf/DirectoryEntries.cs index f81a42c8..bd3c40e1 100644 --- a/OpenMcdf/DirectoryEntries.cs +++ b/OpenMcdf/DirectoryEntries.cs @@ -40,7 +40,7 @@ public bool TryGetDictionaryEntry(uint streamId, out DirectoryEntry? entry) } if (streamId > StreamId.Maximum) - throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}.", nameof(streamId)); + throw new FileFormatException($"Invalid directory entry stream ID: ${streamId:X8}."); uint chainIndex = GetChainIndexAndEntryIndex(streamId, out long entryIndex); if (!fatChainEnumerator.MoveTo(chainIndex)) diff --git a/OpenMcdf/DirectoryEntry.cs b/OpenMcdf/DirectoryEntry.cs index cf49616e..8b4152df 100644 --- a/OpenMcdf/DirectoryEntry.cs +++ b/OpenMcdf/DirectoryEntry.cs @@ -224,7 +224,7 @@ public void Recycle(StorageType storageType, string name) StorageType.Stream => EntryType.Stream, StorageType.Storage => EntryType.Storage, StorageType.Root => EntryType.Storage, - _ => throw new InvalidOperationException("Invalid storage type.") + _ => throw new FileFormatException($"Invalid storage type: {Type}.") }; public EntryInfo ToEntryInfo(string path) => new(EntryType, path, NameString, StreamLength, CLSID, CreationTime, ModifiedTime); diff --git a/OpenMcdf/DirectoryTree.cs b/OpenMcdf/DirectoryTree.cs index 2f8a08e0..e22e82f6 100644 --- a/OpenMcdf/DirectoryTree.cs +++ b/OpenMcdf/DirectoryTree.cs @@ -43,7 +43,7 @@ public bool TryGetDirectoryEntry(string name, out DirectoryEntry? entry) { compare = DirectoryEntryComparer.Compare(leftChild.NameCharSpan, child.NameCharSpan); if (compare >= 0) - throw new FormatException("Directory tree is not sorted."); + throw new FileFormatException("Directory tree is not sorted."); } child = leftChild; @@ -55,7 +55,7 @@ public bool TryGetDirectoryEntry(string name, out DirectoryEntry? entry) { compare = DirectoryEntryComparer.Compare(rightChild.NameCharSpan, child.NameCharSpan); if (compare <= 0) - throw new FormatException("Directory tree is not sorted."); + throw new FileFormatException("Directory tree is not sorted."); } child = rightChild; diff --git a/OpenMcdf/Fat.cs b/OpenMcdf/Fat.cs index af8214e2..6c8ad14c 100644 --- a/OpenMcdf/Fat.cs +++ b/OpenMcdf/Fat.cs @@ -190,7 +190,7 @@ internal void Validate() { Sector sector = new(entry.Index, Context.SectorSize); if (entry.Value <= SectorType.Maximum && sector.EndPosition > Context.Length) - throw new FormatException($"FAT entry {entry} is beyond the end of the stream."); + throw new FileFormatException($"FAT entry {entry} is beyond the end of the stream."); if (entry.Value == SectorType.Fat) fatSectorCount++; if (entry.Value == SectorType.Difat) @@ -198,9 +198,9 @@ internal void Validate() } if (Context.Header.FatSectorCount != fatSectorCount) - throw new FormatException($"FAT sector count mismatch. Expected: {Context.Header.FatSectorCount} Actual: {fatSectorCount}."); + throw new FileFormatException($"FAT sector count mismatch. Expected: {Context.Header.FatSectorCount} Actual: {fatSectorCount}."); if (Context.Header.DifatSectorCount != difatSectorCount) - throw new FormatException($"DIFAT sector count mismatch: Expected: {Context.Header.DifatSectorCount} Actual: {difatSectorCount}."); + throw new FileFormatException($"DIFAT sector count mismatch: Expected: {Context.Header.DifatSectorCount} Actual: {difatSectorCount}."); } [ExcludeFromCodeCoverage] diff --git a/OpenMcdf/FatStream.cs b/OpenMcdf/FatStream.cs index 6254c0b2..8c61a651 100644 --- a/OpenMcdf/FatStream.cs +++ b/OpenMcdf/FatStream.cs @@ -89,7 +89,7 @@ public override int Read(byte[] buffer, int offset, int count) uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!chain.MoveTo(chainIndex)) - throw new FormatException($"The FAT chain was shorter than the stream length."); + throw new FileFormatException($"The FAT chain was shorter than the stream length."); int realCount = Math.Min(count, maxCount); int readCount = 0; @@ -109,7 +109,7 @@ public override int Read(byte[] buffer, int offset, int count) if (readCount >= realCount) return readCount; if (!chain.MoveNext()) - throw new FormatException($"The FAT chain was shorter than the stream length."); + throw new FileFormatException($"The FAT chain was shorter than the stream length."); }; } @@ -215,7 +215,7 @@ public override int Read(Span buffer) uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!chain.MoveTo(chainIndex)) - throw new FormatException($"The FAT chain was shorter than the stream length."); + throw new FileFormatException($"The FAT chain was shorter than the stream length."); int realCount = Math.Min(buffer.Length, maxCount); int readCount = 0; @@ -236,7 +236,7 @@ public override int Read(Span buffer) if (readCount >= realCount) return readCount; if (!chain.MoveNext()) - throw new FormatException($"The FAT chain was shorter than the stream length."); + throw new FileFormatException($"The FAT chain was shorter than the stream length."); } } diff --git a/OpenMcdf/FileFormatException.cs b/OpenMcdf/FileFormatException.cs new file mode 100644 index 00000000..a786f597 --- /dev/null +++ b/OpenMcdf/FileFormatException.cs @@ -0,0 +1,19 @@ +namespace OpenMcdf; + +/// +/// The exception that is thrown when an compound file data stream contains invalid data. +/// +public class FileFormatException : FormatException +{ + public FileFormatException() + { + } + + public FileFormatException(string message) : base(message) + { + } + + public FileFormatException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/OpenMcdf/Header.cs b/OpenMcdf/Header.cs index 4593a4b0..f4f884e6 100644 --- a/OpenMcdf/Header.cs +++ b/OpenMcdf/Header.cs @@ -44,7 +44,7 @@ public ushort MajorVersion get => majorVersion; set { if (value is not 3 and not 4) - throw new FormatException($"Unsupported major version: {value}. Only 3 and 4 are supported"); + throw new FileFormatException($"Unsupported major version: {value}. Only 3 and 4 are supported"); majorVersion = value; } } @@ -57,9 +57,9 @@ public ushort SectorShift get => sectorShift; set { if (MajorVersion == 3 && value != SectorShiftV3) - throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV3:X4} is supported for Major Version 3."); + throw new FileFormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV3:X4} is supported for Major Version 3."); if (MajorVersion == 4 && value != SectorShiftV4) - throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV4:X4} is supported for Major Version 4."); + throw new FileFormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV4:X4} is supported for Major Version 4."); sectorShift = value; } @@ -71,7 +71,7 @@ public ushort MiniSectorShift set { if (value != ExpectedMiniSectorShift) - throw new FormatException($"Unsupported sector shift {value:X4}. Only {ExpectedMiniSectorShift:X4} is supported."); + throw new FileFormatException($"Unsupported sector shift {value:X4}. Only {ExpectedMiniSectorShift:X4} is supported."); miniSectorShift = value; } @@ -130,7 +130,7 @@ public Header(Version version = Version.V3) { Version.V3 => SectorShiftV3, Version.V4 => SectorShiftV4, - _ => throw new FormatException($"Unsupported version: {version}.") + _ => throw new FileFormatException($"Unsupported version: {version}.") }; FirstDirectorySectorId = SectorType.EndOfChain; DirectorySectorCount = 0; // Not used in v3 diff --git a/OpenMcdf/MiniFat.cs b/OpenMcdf/MiniFat.cs index 1432e486..542f26ec 100644 --- a/OpenMcdf/MiniFat.cs +++ b/OpenMcdf/MiniFat.cs @@ -162,7 +162,7 @@ internal void Validate() FatEntry current = miniFatEnumerator.Current; if (current.Value <= SectorType.Maximum && miniFatEnumerator.CurrentSector.EndPosition > Context.MiniStream.Length) { - throw new FormatException($"Mini FAT entry {current} is beyond the end of the mini stream."); + throw new FileFormatException($"Mini FAT entry {current} is beyond the end of the mini stream."); } } } diff --git a/OpenMcdf/MiniFatChainEnumerator.cs b/OpenMcdf/MiniFatChainEnumerator.cs index 2a9aee7d..77b565bc 100644 --- a/OpenMcdf/MiniFatChainEnumerator.cs +++ b/OpenMcdf/MiniFatChainEnumerator.cs @@ -70,7 +70,7 @@ public bool MoveNext() uint nextIndex = index + 1; if (nextIndex > SectorType.Maximum) - throw new FormatException("Mini FAT chain is corrupt."); + throw new FileFormatException("Mini FAT chain is corrupt."); index = nextIndex; current = sectorId; diff --git a/OpenMcdf/MiniFatStream.cs b/OpenMcdf/MiniFatStream.cs index 21a86ea0..4ba8f20b 100644 --- a/OpenMcdf/MiniFatStream.cs +++ b/OpenMcdf/MiniFatStream.cs @@ -79,7 +79,7 @@ public override int Read(byte[] buffer, int offset, int count) uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) - throw new FormatException($"The mini FAT chain was shorter than the stream length."); + throw new FileFormatException($"The mini FAT chain was shorter than the stream length."); FatStream miniStream = Context.MiniStream; int realCount = Math.Min(count, maxCount); @@ -100,7 +100,7 @@ public override int Read(byte[] buffer, int offset, int count) if (readCount >= realCount) return readCount; if (!miniChain.MoveNext()) - throw new FormatException($"The mini FAT chain was shorter than the stream length."); + throw new FileFormatException($"The mini FAT chain was shorter than the stream length."); } } @@ -165,7 +165,7 @@ public override void Write(byte[] buffer, int offset, int count) uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) - throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}."); + throw new FileFormatException($"Failed to move to mini FAT chain index: {chainIndex}."); FatStream miniStream = Context.MiniStream; int writeCount = 0; @@ -211,7 +211,7 @@ public override int Read(Span buffer) uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) - throw new FormatException($"The mini FAT chain was shorter than the stream length."); + throw new FileFormatException($"The mini FAT chain was shorter than the stream length."); FatStream miniStream = Context.MiniStream; int realCount = Math.Min(buffer.Length, maxCount); @@ -233,7 +233,7 @@ public override int Read(Span buffer) if (readCount >= realCount) return readCount; if (!miniChain.MoveNext()) - throw new FormatException($"The mini FAT chain was shorter than the stream length."); + throw new FileFormatException($"The mini FAT chain was shorter than the stream length."); } } @@ -251,7 +251,7 @@ public override void Write(ReadOnlySpan buffer) uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) - throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}."); + throw new FileFormatException($"Failed to move to mini FAT chain index: {chainIndex}."); FatStream miniStream = Context.MiniStream; int writeCount = 0; diff --git a/StructuredStorageExplorer/MainForm.cs b/StructuredStorageExplorer/MainForm.cs index 5ba64e8b..9a89515e 100644 --- a/StructuredStorageExplorer/MainForm.cs +++ b/StructuredStorageExplorer/MainForm.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using System.Data; using System.Globalization; +using FileFormatException = OpenMcdf.FileFormatException; // Author Federico Blaseotto @@ -110,7 +111,7 @@ private void CreateNewFile() RefreshTree(); } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FileFormatException) { CloseCurrentFile(); @@ -149,7 +150,7 @@ private void LoadFile(string fileName) RefreshTree(); } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FileFormatException) { CloseCurrentFile(); @@ -372,7 +373,7 @@ private void TreeView1_MouseUp(object sender, MouseEventArgs e) propertyGrid1.SelectedObject = nodeSelection.EntryInfo; } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FileFormatException) { CloseCurrentFile();