Skip to content

Commit

Permalink
Improve check for circular reference and throw exception
Browse files Browse the repository at this point in the history
  • Loading branch information
BobLd committed Aug 5, 2023
1 parent 893e26a commit fff22c9
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 4 deletions.
23 changes: 22 additions & 1 deletion src/UglyToad.PdfPig.Tests/Integration/XObjectFormTests.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using UglyToad.PdfPig.Core;
using Xunit;

public class XObjectFormTests
{
[Fact]
public void CanReadDocumentWithoutStackOverflow()
public void CanReadDocumentWithoutStackOverflowIssue671()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("issue_671")))
{
var page = document.GetPage(1);
}
}

[Fact]
public void CanReadDocumentThrowsIssue671()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("issue_671"), ParsingOptions.LenientParsingOff))
{
var exception = Assert.Throws<PdfDocumentFormatException>(() => document.GetPage(1));
Assert.Contains("is referencing itself which can cause unexpected behaviour", exception.Message);
}
}

[Fact]
public void CanReadDocumentMOZILLA_3136_0()
{
// This document does not actually contain circular references
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("MOZILLA-3136-0"), ParsingOptions.LenientParsingOff))
{
var page = document.GetPage(1);
}
}
}
}
28 changes: 25 additions & 3 deletions src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -603,10 +603,18 @@ private void ProcessFormXObject(StreamToken formStream, NameToken xObjectName)
// 3. We don't respect clipping currently.

// 4. Paint the objects.
if (parsingOptions.UseLenientParsing && xObjectName != null && operations.OfType<InvokeNamedXObject>()?.Any(o => o.Name == xObjectName) == true)
bool hasCircularReference = HasFormXObjectCircularReference(formStream, xObjectName, operations);
if (hasCircularReference)
{
operations = operations.Where(o => o is not InvokeNamedXObject xo || xo.Name != xObjectName).ToArray();
parsingOptions.Logger.Warn($"An XObject form named '{xObjectName}' is referencing itself which can cause unexpected behaviour. The self reference was removed from the operations before further processing.");
if (parsingOptions.UseLenientParsing)
{
operations = operations.Where(o => o is not InvokeNamedXObject xo || xo.Name != xObjectName).ToArray();
parsingOptions.Logger.Warn($"An XObject form named '{xObjectName}' is referencing itself which can cause unexpected behaviour. The self reference was removed from the operations before further processing.");
}
else
{
throw new PdfDocumentFormatException($"An XObject form named '{xObjectName}' is referencing itself which can cause unexpected behaviour.");
}
}

ProcessOperations(operations);
Expand All @@ -620,6 +628,20 @@ private void ProcessFormXObject(StreamToken formStream, NameToken xObjectName)
}
}

/// <summary>
/// Check for circular reference in the XObject form.
/// </summary>
/// <param name="formStream">The original form stream.</param>
/// <param name="xObjectName">The form's name.</param>
/// <param name="operations">The form operations parsed from original form stream.</param>
private bool HasFormXObjectCircularReference(StreamToken formStream, NameToken xObjectName, IReadOnlyList<IGraphicsStateOperation> operations)
{
return xObjectName != null
&& operations.OfType<InvokeNamedXObject>()?.Any(o => o.Name == xObjectName) == true // operations contain another form with same name
&& resourceStore.TryGetXObject(xObjectName, out var result)
&& result.Data.SequenceEqual(formStream.Data); // The form contained in the operations has identical data to current form
}

public void BeginSubpath()
{
if (CurrentPath == null)
Expand Down

0 comments on commit fff22c9

Please sign in to comment.