Skip to content

Commit f697d4e

Browse files
committed
make IsEncryptedOfficeFile public method and add override that takes a string
1 parent 17ee5b6 commit f697d4e

File tree

4 files changed

+96
-53
lines changed

4 files changed

+96
-53
lines changed

src/DocumentFormat.OpenXml.Framework/Features/StreamPackageFeature.cs

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System.Diagnostics.CodeAnalysis;
99
using System.IO;
1010
using System.IO.Packaging;
11-
using System.Linq;
1211

1312
namespace DocumentFormat.OpenXml.Features;
1413

@@ -57,7 +56,7 @@ public StreamPackageFeature(Stream stream, PackageOpenMode openMode, bool isOwne
5756
}
5857
catch when (isOwned)
5958
{
60-
if (_stream is not null && IsEncryptedOfficeFile(_stream))
59+
if (_stream is not null && OpenXmlPackage.IsEncryptedOfficeFile(_stream))
6160
{
6261
_stream.Dispose();
6362
throw new OpenXmlPackageException(ExceptionMessages.EncryptedPackageNotSupported);
@@ -178,54 +177,4 @@ protected override void Register(IFeatureCollection features)
178177
features.Set<IPackageStreamFeature>(this);
179178
features.GetRequired<IDisposableFeature>().Register(this);
180179
}
181-
182-
private static bool IsEncryptedOfficeFile(Stream inputStream)
183-
{
184-
if (!inputStream.CanSeek)
185-
{
186-
throw new ArgumentException("Stream must be seekable.");
187-
}
188-
189-
long originalPosition = inputStream.Position;
190-
191-
try
192-
{
193-
byte[] header = new byte[8];
194-
inputStream.Seek(0, SeekOrigin.Begin);
195-
int read = inputStream.Read(header, 0, header.Length);
196-
inputStream.Seek(originalPosition, SeekOrigin.Begin);
197-
198-
// OLE Compound File signature for encrypted Office files
199-
byte[] oleSignature = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
200-
if (read == 8 && header.SequenceEqual(oleSignature))
201-
{
202-
return true;
203-
}
204-
205-
// If not OLE, try to open as package and check for encrypted part
206-
try
207-
{
208-
using (var package = System.IO.Packaging.Package.Open(inputStream, FileMode.Open, FileAccess.Read))
209-
{
210-
foreach (var part in package.GetParts())
211-
{
212-
if (part.ContentType.Equals("application/vnd.openxmlformats-officedocument.encrypted-package", StringComparison.OrdinalIgnoreCase))
213-
{
214-
return true;
215-
}
216-
}
217-
}
218-
}
219-
catch
220-
{
221-
return false;
222-
}
223-
224-
return false;
225-
}
226-
finally
227-
{
228-
inputStream.Seek(originalPosition, SeekOrigin.Begin);
229-
}
230-
}
231180
}

src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,5 +612,97 @@ public void Save()
612612

613613
/// <inheritdoc/>
614614
public override IFeatureCollection Features => _features ??= new PackageFeatureCollection(this);
615+
616+
/// <summary>
617+
/// Determines whether the provided stream represents an encrypted Office Open XML file.
618+
/// </summary>
619+
/// <param name="inputStream">The <see cref="Stream"/> to check. The stream must be seekable and not null.</param>
620+
/// <returns>
621+
/// <c>true</c> if the stream is an encrypted Office file (either OLE Compound File or contains an encrypted package part); otherwise, <c>false</c>.
622+
/// </returns>
623+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="inputStream"/> is null.</exception>
624+
/// <exception cref="ArgumentException">Thrown if <paramref name="inputStream"/> is not seekable.</exception>
625+
/// <remarks>
626+
/// This method checks for the OLE Compound File signature at the start of the stream, which is used for encrypted Office files.
627+
/// If not found, it attempts to open the stream as an OPC package and checks for the presence of an encrypted package part.
628+
/// The stream position is restored after the check.
629+
/// </remarks>
630+
public static bool IsEncryptedOfficeFile(Stream inputStream)
631+
{
632+
if (inputStream is null)
633+
{
634+
throw new ArgumentNullException(nameof(inputStream));
635+
}
636+
637+
if (!inputStream.CanSeek)
638+
{
639+
throw new ArgumentException("Stream must be seekable.");
640+
}
641+
642+
long originalPosition = inputStream.Position;
643+
644+
try
645+
{
646+
byte[] header = new byte[8];
647+
inputStream.Seek(0, SeekOrigin.Begin);
648+
int read = inputStream.Read(header, 0, header.Length);
649+
inputStream.Seek(originalPosition, SeekOrigin.Begin);
650+
651+
// OLE Compound File signature for encrypted Office files
652+
if (read == 8 && header.SequenceEqual(new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }))
653+
{
654+
return true;
655+
}
656+
657+
// If not OLE, try to open as package and check for encrypted part
658+
try
659+
{
660+
using (var package = System.IO.Packaging.Package.Open(inputStream, FileMode.Open, FileAccess.Read))
661+
{
662+
foreach (var part in package.GetParts())
663+
{
664+
if (part.ContentType.Equals("application/vnd.openxmlformats-officedocument.encrypted-package", StringComparison.OrdinalIgnoreCase))
665+
{
666+
return true;
667+
}
668+
}
669+
}
670+
}
671+
catch
672+
{
673+
return false;
674+
}
675+
676+
return false;
677+
}
678+
finally
679+
{
680+
inputStream.Seek(originalPosition, SeekOrigin.Begin);
681+
}
682+
}
683+
684+
/// <summary>
685+
/// Determines whether the file at the specified path is an encrypted Office Open XML file.
686+
/// </summary>
687+
/// <param name="filePath">The path to the file to check. Must not be null.</param>
688+
/// <returns>
689+
/// <c>true</c> if the file is an encrypted Office file (either OLE Compound File or contains an encrypted package part); otherwise, <c>false</c>.
690+
/// </returns>
691+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="filePath"/> is null.</exception>
692+
/// <remarks>
693+
/// This method opens the file at the specified path and checks its contents using <see cref="IsEncryptedOfficeFile(Stream)"/>.
694+
/// </remarks>
695+
public static bool IsEncryptedOfficeFile(string filePath)
696+
{
697+
if (filePath is null)
698+
{
699+
throw new ArgumentNullException(nameof(filePath));
700+
}
701+
702+
using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
703+
{
704+
return IsEncryptedOfficeFile(fileStream);
705+
}
706+
}
615707
}
616708
}

src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,3 +1010,5 @@ DocumentFormat.OpenXml.OpenXmlPartWriterSettings.OpenXmlPartWriterSettings() ->
10101010
DocumentFormat.OpenXml.OpenXmlPartWriter.OpenXmlPartWriter(DocumentFormat.OpenXml.Packaging.OpenXmlPart! openXmlPart, DocumentFormat.OpenXml.OpenXmlPartWriterSettings! settings) -> void
10111011
DocumentFormat.OpenXml.OpenXmlPartWriter.OpenXmlPartWriter(System.IO.Stream! partStream, DocumentFormat.OpenXml.OpenXmlPartWriterSettings! settings) -> void
10121012
virtual DocumentFormat.OpenXml.OpenXmlCompositeElement.IsValidChild(DocumentFormat.OpenXml.OpenXmlElement! element) -> bool
1013+
static DocumentFormat.OpenXml.Packaging.OpenXmlPackage.IsEncryptedOfficeFile(System.IO.Stream! inputStream) -> bool
1014+
static DocumentFormat.OpenXml.Packaging.OpenXmlPackage.IsEncryptedOfficeFile(string! filePath) -> bool

test/DocumentFormat.OpenXml.Framework.Tests/Features/StreamPackageFeatureTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.IO.Packaging;
1111
using System.Linq;
1212
using Xunit;
13+
1314
using static DocumentFormat.OpenXml.Tests.TestAssets;
1415

1516
namespace DocumentFormat.OpenXml.Features.Tests;
@@ -441,7 +442,6 @@ public void ThrowsForEncryptedOfficeFile()
441442
var ex = Assert.Throws<OpenXmlPackageException>(() => new StreamPackageFeature(stream, PackageOpenMode.Read, isOwned: true));
442443
Assert.Equal(ExceptionMessages.EncryptedPackageNotSupported, ex.Message);
443444
}
444-
445445
}
446446

447447
private static readonly PartInfo Part1 = new(new("/part1", UriKind.Relative), "type1/content");

0 commit comments

Comments
 (0)