Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions MagicBytesValidator.Tests/DocXlsDisambiguationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
namespace MagicBytesValidator.Tests;

public class DocXlsDisambiguationTests
{
private static readonly byte[] OleHeader =
[
0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1
];

[Fact]
public async Task XlsWorkbookStreamName_ShouldMatchXls_AndNotDoc()
{
var validator = new Validator();
var xls = new Xls();
var doc = new Doc();

using var stream = BuildOleLikeStreamWithUtf16Marker("Workbook");

var isXls = await validator.IsValidAsync(stream, xls, CancellationToken.None);
stream.Position = 0;
var isDoc = await validator.IsValidAsync(stream, doc, CancellationToken.None);

Assert.True(isXls);
Assert.False(isDoc);
}

[Fact]
public async Task DocWordDocumentStreamName_ShouldMatchDoc_AndNotXls()
{
var validator = new Validator();
var doc = new Doc();
var xls = new Xls();

using var stream = BuildOleLikeStreamWithUtf16Marker("WordDocument");

var isDoc = await validator.IsValidAsync(stream, doc, CancellationToken.None);
stream.Position = 0;
var isXls = await validator.IsValidAsync(stream, xls, CancellationToken.None);

Assert.True(isDoc);
Assert.False(isXls);
}

[Fact]
public async Task LegacyXlsBookStreamName_ShouldMatchXls()
{
var validator = new Validator();
var xls = new Xls();

using var stream = BuildOleLikeStreamWithUtf16Marker("Book");

var isXls = await validator.IsValidAsync(stream, xls, CancellationToken.None);

Assert.True(isXls);
}

[Fact]
public async Task ClassicDocOffset512Marker_ShouldMatchDoc()
{
var validator = new Validator();
var doc = new Doc();

using var stream = BuildOleLikeStreamWithOffset512Marker([0xEC, 0xA5, 0xC1, 0x00]);

var isDoc = await validator.IsValidAsync(stream, doc, CancellationToken.None);

Assert.True(isDoc);
}

[Fact]
public async Task ClassicXlsOffset512Marker_ShouldMatchXls()
{
var validator = new Validator();
var xls = new Xls();

using var stream = BuildOleLikeStreamWithOffset512Marker([0xFD, 0xFF, 0xFF, 0xFF, 0x24, 0x00]);

var isXls = await validator.IsValidAsync(stream, xls, CancellationToken.None);

Assert.True(isXls);
}

private static MemoryStream BuildOleLikeStreamWithUtf16Marker(string marker)
{
var bytes = new byte[4096];
var markerBytes = System.Text.Encoding.Unicode.GetBytes(marker);

Array.Copy(OleHeader, 0, bytes, 0, OleHeader.Length);
Array.Copy(markerBytes, 0, bytes, 1536, markerBytes.Length);

return new MemoryStream(bytes);
}

private static MemoryStream BuildOleLikeStreamWithOffset512Marker(byte[] marker)
{
var bytes = new byte[2048];

Array.Copy(OleHeader, 0, bytes, 0, OleHeader.Length);
Array.Copy(marker, 0, bytes, 512, marker.Length);

return new MemoryStream(bytes);
}
}
198 changes: 193 additions & 5 deletions MagicBytesValidator.Tests/Http/FindValidatedTypeAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

public class FindValidatedTypeAsync
{
private static readonly byte[] OleHeader =
[
0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1
];

[Fact]
public async Task Should_find_by_extension()
{
Expand Down Expand Up @@ -82,14 +87,120 @@ await sut.FindValidatedTypeAsync(
);
}

private static IFormFile ProvideGifFile(string name, string contentType)
[Fact]
public async Task Should_validate_legacy_doc()
{
byte[] gifSequence = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
var fileContents = gifSequence.Concat(new byte[] { 0x11, 0x12 }).ToArray();
var fileStream = new MemoryStream(fileContents.ToArray());
var formFile = ProvideFile("legacy.doc", "application/msword", BuildLegacyDocBytes());

var sut = new FormFileTypeProvider();

var result = await sut.FindValidatedTypeAsync(
formFile,
null,
CancellationToken.None
);

Assert.IsType<Doc>(result);
}

[Fact]
public async Task Should_validate_legacy_xls()
{
var formFile = ProvideFile("legacy.xls", "application/msexcel", BuildLegacyXlsBytes());

var sut = new FormFileTypeProvider();

var result = await sut.FindValidatedTypeAsync(
formFile,
null,
CancellationToken.None
);

Assert.IsType<Xls>(result);
}

[Fact]
public async Task Should_validate_legacy_ppt()
{
var formFile = ProvideFile("legacy.ppt", "application/vnd.ms-powerpoint", BuildLegacyPptBytes());

var sut = new FormFileTypeProvider();

var result = await sut.FindValidatedTypeAsync(
formFile,
null,
CancellationToken.None
);

Assert.IsType<Ppt>(result);
}

[Fact]
public async Task Should_validate_modern_docx()
{
var formFile = ProvideFile(
"modern.docx",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
BuildOpenXmlBytes("word/_rels/document.xml.rels")
);

var sut = new FormFileTypeProvider();

var result = await sut.FindValidatedTypeAsync(
formFile,
null,
CancellationToken.None
);

Assert.IsType<Docx>(result);
}

[Fact]
public async Task Should_validate_modern_xlsx()
{
var formFile = ProvideFile(
"modern.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
BuildOpenXmlBytes("xl/_rels/workbook.xml.rels")
);

var sut = new FormFileTypeProvider();

var result = await sut.FindValidatedTypeAsync(
formFile,
null,
CancellationToken.None
);

Assert.IsType<Xlsx>(result);
}

[Fact]
public async Task Should_validate_modern_pptx()
{
var formFile = ProvideFile(
"modern.pptx",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
BuildOpenXmlBytes("ppt/_rels/presentation.xml.rels")
);

var sut = new FormFileTypeProvider();

var result = await sut.FindValidatedTypeAsync(
formFile,
null,
CancellationToken.None
);

Assert.IsType<Pptx>(result);
}

private static IFormFile ProvideFile(string name, string contentType, byte[] fileContents)
{
var fileStream = new MemoryStream(fileContents);

return new FormFile(
new MemoryStream(fileContents.ToArray()),
new MemoryStream(fileContents),
0,
fileStream.Length,
name,
Expand All @@ -102,4 +213,81 @@ private static IFormFile ProvideGifFile(string name, string contentType)
}
};
}

private static IFormFile ProvideGifFile(string name, string contentType)
{
byte[] gifSequence = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
var fileContents = gifSequence.Concat(new byte[] { 0x11, 0x12 }).ToArray();

return ProvideFile(name, contentType, fileContents);
}

private static byte[] BuildLegacyDocBytes()
{
var bytes = new byte[4096];
var markerBytes = System.Text.Encoding.Unicode.GetBytes("WordDocument");

Array.Copy(OleHeader, 0, bytes, 0, OleHeader.Length);
Array.Copy(markerBytes, 0, bytes, 1536, markerBytes.Length);

return bytes;
}

private static byte[] BuildLegacyXlsBytes()
{
var bytes = new byte[4096];
var markerBytes = System.Text.Encoding.Unicode.GetBytes("Workbook");

Array.Copy(OleHeader, 0, bytes, 0, OleHeader.Length);
Array.Copy(markerBytes, 0, bytes, 1536, markerBytes.Length);

return bytes;
}

private static byte[] BuildLegacyPptBytes()
{
var bytes = new byte[2048];
var marker = new byte[] { 0xA0, 0x46, 0x1D, 0xF0 };

Array.Copy(OleHeader, 0, bytes, 0, OleHeader.Length);
Array.Copy(marker, 0, bytes, 512, marker.Length);

return bytes;
}

private static byte[] BuildOpenXmlBytes(string marker)
{
var bytes = BuildZipLikeBytesWithEocd(0x12345678);
var markerBytes = System.Text.Encoding.ASCII.GetBytes(marker);

Array.Copy(markerBytes, 0, bytes, 64, markerBytes.Length);

return bytes;
}

private static byte[] BuildZipLikeBytesWithEocd(uint centralDirectoryOffset)
{
var bytes = new byte[512];

bytes[0] = 0x50;
bytes[1] = 0x4B;
bytes[2] = 0x03;
bytes[3] = 0x04;

var eocdStart = bytes.Length - 22;
bytes[eocdStart + 0] = 0x50;
bytes[eocdStart + 1] = 0x4B;
bytes[eocdStart + 2] = 0x05;
bytes[eocdStart + 3] = 0x06;

bytes[eocdStart + 16] = (byte)(centralDirectoryOffset & 0xFF);
bytes[eocdStart + 17] = (byte)((centralDirectoryOffset >> 8) & 0xFF);
bytes[eocdStart + 18] = (byte)((centralDirectoryOffset >> 16) & 0xFF);
bytes[eocdStart + 19] = (byte)((centralDirectoryOffset >> 24) & 0xFF);

bytes[eocdStart + 20] = 0x00;
bytes[eocdStart + 21] = 0x00;

return bytes;
}
}
Loading
Loading