Skip to content

Commit 1c165bd

Browse files
authored
Dispose stream in initializer if loading file fails (#1829)
Fixes #1681
1 parent b515e91 commit 1c165bd

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,17 @@ public StreamPackageFeature(Stream stream, PackageOpenMode openMode, bool isOwne
5050
Mode = initialMode == FileMode.Create ? Mode = FileMode.Open : initialMode;
5151
Access = openMode == PackageOpenMode.Read ? FileAccess.Read : FileAccess.ReadWrite;
5252

53-
InitializePackage(initialMode, Access);
53+
try
54+
{
55+
InitializePackage(initialMode, Access);
56+
}
57+
catch when (isOwned)
58+
{
59+
// Ensure that the stream if created is disposed before leaving the constructor so we don't hold onto it
60+
_stream?.Dispose();
61+
throw;
62+
}
63+
5464
_isOwned = isOwned;
5565
}
5666

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using DocumentFormat.OpenXml.Builder;
5+
using System.IO;
6+
using Xunit;
7+
8+
namespace DocumentFormat.OpenXml.Features.Tests;
9+
10+
public class FilePackageFeatureTests
11+
{
12+
[Fact]
13+
public void OpenInvalidFileFailsGracefully()
14+
{
15+
// Arrange
16+
var path = Path.GetTempFileName();
17+
File.WriteAllText(path, "This is not a valid document");
18+
19+
// Act
20+
Assert.Throws<FileFormatException>(() => new FilePackageFeature(path, PackageOpenMode.Read));
21+
22+
// Assert
23+
using var reopened = File.Open(path, FileMode.Open, FileAccess.Read);
24+
Assert.NotNull(reopened);
25+
}
26+
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using DocumentFormat.OpenXml.Builder;
5+
using DocumentFormat.OpenXml.Framework;
56
using DocumentFormat.OpenXml.Packaging;
67
using NSubstitute;
78
using System;
@@ -399,6 +400,37 @@ public void PartReload()
399400
Assert.Same(relationshipBefore, relationshipAfter);
400401
}
401402

403+
[Theory]
404+
[InlineData(true)]
405+
[InlineData(false)]
406+
public void OpenInvalidStreamFailsGracefully(bool isOwned)
407+
{
408+
// Arrange
409+
var stream = new DisposingWatcherInvalidStream([1, 2, 3, 4]);
410+
411+
// Act
412+
Assert.Throws<FileFormatException>(() => new StreamPackageFeature(stream, PackageOpenMode.Read, isOwned: isOwned));
413+
414+
// Assert
415+
Assert.Equal(isOwned, stream.IsDisposed);
416+
}
417+
418+
private sealed class DisposingWatcherInvalidStream : DelegatingStream
419+
{
420+
public DisposingWatcherInvalidStream(byte[] bytes)
421+
: base(new MemoryStream(bytes))
422+
{
423+
}
424+
425+
public bool IsDisposed { get; private set; }
426+
427+
protected override void Dispose(bool disposing)
428+
{
429+
IsDisposed = true;
430+
base.Dispose(disposing);
431+
}
432+
}
433+
402434
private static readonly PartInfo Part1 = new(new("/part1", UriKind.Relative), "type1/content");
403435
private static readonly PartInfo Part2 = new(new("/part2", UriKind.Relative), "type2/content");
404436
private static readonly PartInfo PartRels = new(new("/_rels/.rels", UriKind.Relative), "application/vnd.openxmlformats-package.relationships+xml");

0 commit comments

Comments
 (0)