From 61be30e7e8ac534c84a230341e631affc33e6071 Mon Sep 17 00:00:00 2001 From: Gerard Gunnewijk Date: Tue, 22 Sep 2026 12:50:34 +0200 Subject: [PATCH 1/4] Fix broken constructor by replacing File.OpenRead with File.OpenWrite --- src/Synercoding.FileFormats.Pdf/PdfWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs index 8c841b4..d010144 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs +++ b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs @@ -36,7 +36,7 @@ public PdfWriter(string filePath) /// The path to the PDF file to create. /// The writer settings. public PdfWriter(string filePath, WriterSettings settings) - : this(File.OpenRead(filePath), settings, true) + : this(File.OpenWrite(filePath), settings, true) { } /// From c67d02a1afc4430ba5c8ff81f97f890504a44dc1 Mon Sep 17 00:00:00 2001 From: Gerard Gunnewijk Date: Tue, 22 Sep 2026 12:51:02 +0200 Subject: [PATCH 2/4] Added some tests for the constructors --- .../PdfWriterConstructorTests.cs | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 tests/Synercoding.FileFormats.Pdf.Tests/PdfWriterConstructorTests.cs diff --git a/tests/Synercoding.FileFormats.Pdf.Tests/PdfWriterConstructorTests.cs b/tests/Synercoding.FileFormats.Pdf.Tests/PdfWriterConstructorTests.cs new file mode 100644 index 0000000..6529dfb --- /dev/null +++ b/tests/Synercoding.FileFormats.Pdf.Tests/PdfWriterConstructorTests.cs @@ -0,0 +1,268 @@ +using Synercoding.FileFormats.Pdf.Generation; + +namespace Synercoding.FileFormats.Pdf.Tests; + +/// +/// Tests for the constructors. +/// +/// +/// The file path based tests are regression tests: the file used to be opened for reading instead +/// of for writing, which made every file path based unusable. +/// +public class PdfWriterConstructorTests : IDisposable +{ + private readonly string _tempDirectory; + + public PdfWriterConstructorTests() + { + _tempDirectory = Path.Combine(Path.GetTempPath(), "PdfWriterConstructorTests", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(_tempDirectory); + } + + public void Dispose() + { + try + { + Directory.Delete(_tempDirectory, true); + } + catch (IOException) + { + // Best effort cleanup, a leftover temp file should never fail a test. + } + GC.SuppressFinalize(this); + } + + private string _getFilePath() + => Path.Combine(_tempDirectory, Guid.NewGuid().ToString("N") + ".pdf"); + + [Fact] + public void Test_PdfWriter_FilePathConstructor_CreatesPdfFile() + { + // Arrange + var filePath = _getFilePath(); + + // Act + using (var writer = new PdfWriter(filePath)) + { + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + } + + // Assert + Assert.True(File.Exists(filePath)); + var pdfContent = File.ReadAllText(filePath, System.Text.Encoding.ASCII); + Assert.StartsWith("%PDF-", pdfContent); + Assert.EndsWith("%%EOF", pdfContent); + } + + [Fact] + public void Test_PdfWriter_FilePathAndSettingsConstructor_CreatesPdfFile() + { + // Arrange + var filePath = _getFilePath(); + + // Act + using (var writer = new PdfWriter(filePath, new WriterSettings())) + { + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + } + + // Assert + Assert.True(File.Exists(filePath)); + var pdfContent = File.ReadAllText(filePath, System.Text.Encoding.ASCII); + Assert.StartsWith("%PDF-", pdfContent); + Assert.EndsWith("%%EOF", pdfContent); + } + + [Fact] + public void Test_PdfWriter_FilePathConstructor_WithAlreadyExistingFile_WritesPdfContent() + { + // Arrange + var filePath = _getFilePath(); + File.WriteAllText(filePath, string.Empty); + + // Act + using (var writer = new PdfWriter(filePath)) + { + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + } + + // Assert + var pdfContent = File.ReadAllText(filePath, System.Text.Encoding.ASCII); + Assert.StartsWith("%PDF-", pdfContent); + Assert.EndsWith("%%EOF", pdfContent); + } + + [Fact] + public void Test_PdfWriter_FilePathConstructor_WithNonExistingFile_CreatesFile() + { + // Arrange + var filePath = _getFilePath(); + Assert.False(File.Exists(filePath)); + + // Act + using var writer = new PdfWriter(filePath); + + // Assert + // The file is created by the constructor, opening an existing file for reading would have + // thrown a FileNotFoundException instead. + Assert.True(File.Exists(filePath)); + } + + [Fact] + public void Test_PdfWriter_FilePathConstructor_DisposeReleasesFileHandle() + { + // Arrange + var filePath = _getFilePath(); + var writer = new PdfWriter(filePath); + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + + // Act + writer.Dispose(); + + // Assert + // Opening with FileShare.None only succeeds when no other handle to the file is left open, + // proving the writer owns and disposes the file stream it created. + using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None); + Assert.True(fileStream.Length > 0); + } + + [Fact] + public void Test_PdfWriter_StreamConstructor_DoesNotOwnStream() + { + // Arrange + var stream = new MemoryStream(); + + // Act + using (var writer = new PdfWriter(stream)) + { + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + } + + // Assert + Assert.True(stream.CanWrite); + stream.Dispose(); + } + + [Fact] + public void Test_PdfWriter_StreamAndSettingsConstructor_DoesNotOwnStream() + { + // Arrange + var stream = new MemoryStream(); + + // Act + using (var writer = new PdfWriter(stream, new WriterSettings())) + { + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + } + + // Assert + Assert.True(stream.CanWrite); + stream.Dispose(); + } + + [Fact] + public void Test_PdfWriter_StreamConstructor_WithOwnsStreamTrue_DisposesStream() + { + // Arrange + var stream = new MemoryStream(); + + // Act + using (var writer = new PdfWriter(stream, new WriterSettings(), true)) + { + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + } + + // Assert + Assert.False(stream.CanWrite); + } + + [Fact] + public void Test_PdfWriter_StreamConstructor_WithOwnsStreamFalse_LeavesStreamOpen() + { + // Arrange + var stream = new MemoryStream(); + + // Act + using (var writer = new PdfWriter(stream, new WriterSettings(), false)) + { + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + } + + // Assert + Assert.True(stream.CanWrite); + stream.Dispose(); + } + + [Fact] + public void Test_PdfWriter_PdfStreamConstructor_WritesPdf() + { + // Arrange + using var stream = new MemoryStream(); + var pdfStream = new PdfStream(stream, false); + + // Act + using (var writer = new PdfWriter(pdfStream, new WriterSettings())) + { + writer.AddPage(page => page.MediaBox = Sizes.A4.AsRectangle()); + } + + // Assert + var pdfContent = System.Text.Encoding.ASCII.GetString(stream.ToArray()); + Assert.StartsWith("%PDF-", pdfContent); + Assert.EndsWith("%%EOF", pdfContent); + } + + [Fact] + public void Test_PdfWriter_WithNonWritableStream_Throws() + { + // Arrange + using var stream = new MemoryStream(new byte[16], writable: false); + + // Act & Assert + Assert.Throws(() => new PdfWriter(stream)); + } + + [Fact] + public void Test_PdfWriter_WithReadOnlyFileStream_Throws() + { + // Arrange + var filePath = _getFilePath(); + File.WriteAllText(filePath, string.Empty); + using var stream = File.OpenRead(filePath); + + // Act & Assert + Assert.Throws(() => new PdfWriter(stream)); + } + + [Fact] + public void Test_PdfWriter_Constructor_WritesHeader() + { + // Arrange + using var stream = new MemoryStream(); + + // Act + using var writer = new PdfWriter(stream); + + // Assert + var pdfContent = System.Text.Encoding.ASCII.GetString(stream.ToArray()); + Assert.StartsWith("%PDF-2.0", pdfContent); + } + + [Fact] + public void Test_PdfWriter_Constructor_SetsDefaults() + { + // Arrange + using var stream = new MemoryStream(); + + // Act + using var writer = new PdfWriter(stream); + + // Assert + Assert.Equal(0, writer.PageCount); + Assert.Null(writer.PageMode); + Assert.Null(writer.PageLayout); + Assert.NotNull(writer.DocumentInformation); + Assert.StartsWith("Synercoding.FileFormats.Pdf ", writer.DocumentInformation.Producer); + Assert.NotNull(writer.DocumentInformation.CreationDate); + } +} From 16231f4ae207360ac21db0602628c6bdbc4b5c9f Mon Sep 17 00:00:00 2001 From: Gerard Gunnewijk Date: Tue, 22 Sep 2026 12:51:28 +0200 Subject: [PATCH 3/4] Updated vulnerable package --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 49a45b3..934cc8a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,7 +13,7 @@ - + From 7660d7f32ff32ef0f36243863e7e087fc43e594a Mon Sep 17 00:00:00 2001 From: Gerard Gunnewijk Date: Tue, 22 Sep 2026 12:55:26 +0200 Subject: [PATCH 4/4] Updated the release notes --- src/Synercoding.FileFormats.Pdf/PackageDetails.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Synercoding.FileFormats.Pdf/PackageDetails.props b/src/Synercoding.FileFormats.Pdf/PackageDetails.props index 60b217e..2784a92 100644 --- a/src/Synercoding.FileFormats.Pdf/PackageDetails.props +++ b/src/Synercoding.FileFormats.Pdf/PackageDetails.props @@ -10,7 +10,7 @@ Synercoding.FileFormats.Pdf Synercoding.FileFormats.Pdf Contains classes which makes it easy to quickly create a pdf file. - Added support for the current alpha constant and blend mode via the extended graphic state. And added basic support for clickable hyperlinks on a page. + Fixed a broken constructor. README.md LICENSE