Improve HasFormXObjectCircularReference and fix #1250

This commit is contained in:
BobLd
2026-02-15 18:06:35 +00:00
parent f732718852
commit 0a2b1e076f
3 changed files with 76 additions and 8 deletions
@@ -11,6 +11,31 @@
public class GithubIssuesTests public class GithubIssuesTests
{ {
[Fact]
public void Issues1250()
{
// Issue comes from HasFormXObjectCircularReference
var path = IntegrationHelpers.GetDocumentPath("SPE8EF26T0545.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
{
var page = document.GetPage(1);
Assert.NotNull(page);
Assert.NotEmpty(page.Letters);
page = document.GetPage(7);
Assert.NotNull(page);
Assert.NotEmpty(page.Letters);
}
// Ensure still no StackOverflowException
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("issue_671")))
{
var page = document.GetPage(1);
Assert.NotNull(page);
Assert.NotEmpty(page.Letters);
}
}
[Fact] [Fact]
public void Issues1248() public void Issues1248()
{ {
@@ -29,7 +54,7 @@
} }
} }
} }
[Fact] [Fact]
public void Issues1238() public void Issues1238()
{ {
@@ -587,7 +587,8 @@
if (hasCircularReference) if (hasCircularReference)
{ {
if (ParsingOptions.UseLenientParsing) if (ParsingOptions.UseLenientParsing)
{ {
// TODO - We might be removing too much, good for the moment. See Issues1250() for examples
operations = operations.Where(o => o is not InvokeNamedXObject xo || xo.Name != xObjectName) operations = operations.Where(o => o is not InvokeNamedXObject xo || xo.Name != xObjectName)
.ToArray(); .ToArray();
ParsingOptions.Logger.Warn( ParsingOptions.Logger.Warn(
@@ -618,14 +619,56 @@
/// <param name="xObjectName">The form's name.</param> /// <param name="xObjectName">The form's name.</param>
/// <param name="operations">The form operations parsed from original form stream.</param> /// <param name="operations">The form operations parsed from original form stream.</param>
protected virtual bool HasFormXObjectCircularReference(StreamToken formStream, protected virtual bool HasFormXObjectCircularReference(StreamToken formStream,
NameToken xObjectName, NameToken? xObjectName,
IReadOnlyList<IGraphicsStateOperation> operations) IReadOnlyList<IGraphicsStateOperation> operations)
{ {
return xObjectName != null if (xObjectName is null)
&& operations.OfType<InvokeNamedXObject>()?.Any(o => o.Name == xObjectName) == {
true // operations contain another form with same name return false;
&& ResourceStore.TryGetXObject(xObjectName, out var result) }
&& result.Data.Span.SequenceEqual(formStream.Data.Span); // The form contained in the operations has identical data to current form
if (operations.OfType<InvokeNamedXObject>()?.Any(o => o.Name == xObjectName) != true)
{
return false;
}
if (!TryGetXObjectToken(formStream, xObjectName, PdfScanner, out var t1))
{
return false;
}
if (!ResourceStore.TryGetXObject(xObjectName, out var resourceStream))
{
return false;
}
if (!TryGetXObjectToken(resourceStream, xObjectName, PdfScanner, out var t2))
{
return false;
}
if (t1 is null || t2 is null)
{
return false;
}
return t1.Equals(t2);
static bool TryGetXObjectToken(StreamToken streamToken, NameToken xObjectName, IPdfTokenScanner scanner, out IToken? token)
{
token = null;
if (!streamToken.StreamDictionary.TryGet<DictionaryToken>(NameToken.Resources, scanner, out var formResources))
{
return false;
}
if (!formResources.TryGet<DictionaryToken>(NameToken.Xobject, out var xObjectBase) || !xObjectBase.TryGet(xObjectName, out token))
{
return false;
}
return token is not null;
}
} }
/// <inheritdoc/> /// <inheritdoc/>