Introduce StackDepthGuard class to check for stack depth in CoreTokenScanner and fix #1217
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled

This commit is contained in:
BobLd
2025-12-02 12:05:43 +00:00
parent bd573b2a1c
commit 7c4f5e2424
26 changed files with 1522 additions and 52 deletions

View File

@@ -0,0 +1,63 @@
namespace UglyToad.PdfPig.Core
{
/// <summary>
/// Provides a guard for tracking and limiting the depth of nested stack operations, such as recursive calls or
/// nested parsing.
/// </summary>
/// <remarks>Use this class to prevent excessive stack usage by enforcing a maximum nesting depth. This is
/// particularly useful in scenarios where untrusted or deeply nested input could cause stack overflows or
/// performance issues.</remarks>
public sealed class StackDepthGuard
{
/// <summary>
/// Represents a stack depth guard with no effective limit on the allowed depth.
/// </summary>
/// <remarks>Use this instance when stack depth restrictions are not required.</remarks>
public static readonly StackDepthGuard Infinite = new StackDepthGuard(int.MaxValue);
private readonly int maxStackDepth;
private int depth;
/// <summary>
/// Initializes a new instance of the StackDepthGuard class with the specified maximum stack depth.
/// </summary>
/// <param name="maxStackDepth">The maximum allowed stack depth for guarded operations. Must be a positive integer.</param>
public StackDepthGuard(int maxStackDepth)
{
if (maxStackDepth <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxStackDepth));
}
this.maxStackDepth = maxStackDepth;
}
/// <summary>
/// Increments the current nesting depth and checks against the maximum allowed stack depth.
/// </summary>
/// <exception cref="PdfDocumentFormatException">Thrown if the maximum allowed nesting depth is exceeded.</exception>
public void Enter()
{
if (++depth > maxStackDepth)
{
depth--; // Decrement so Exit remains balanced if someone catches this
throw new PdfDocumentFormatException($"Exceeded maximum nesting depth of {maxStackDepth}.");
}
}
/// <summary>
/// Decreases the current depth level by one, ensuring that the depth does not become negative.
/// </summary>
/// <remarks>If the current depth is already zero, calling this method has no effect. This method
/// is typically used to track or manage nested operations or scopes where depth must remain
/// non-negative.</remarks>
public void Exit()
{
depth--;
if (depth < 0)
{
depth = 0;
}
}
}
}

View File

@@ -21,15 +21,16 @@
private static readonly char[] Separators = [' '];
private static readonly Type1EncryptedPortionParser EncryptedPortionParser = new Type1EncryptedPortionParser();
/// <summary>
/// Parses an embedded Adobe Type 1 font file.
/// </summary>
/// <param name="inputBytes">The bytes of the font program.</param>
/// <param name="length1">The length in bytes of the clear text portion of the font program.</param>
/// <param name="length2">The length in bytes of the encrypted portion of the font program.</param>
/// <param name="stackDepthGuard"></param>
/// <returns>The parsed type 1 font.</returns>
public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2)
public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2, StackDepthGuard stackDepthGuard)
{
// Sometimes the entire PFB file including the header bytes can be included which prevents parsing in the normal way.
var isEntirePfbFile = inputBytes.Peek() == PfbFileIndicator;
@@ -44,7 +45,7 @@
inputBytes = new MemoryInputBytes(ascii);
}
var scanner = new CoreTokenScanner(inputBytes, false);
var scanner = new CoreTokenScanner(inputBytes, false, stackDepthGuard);
if (!scanner.TryReadToken(out CommentToken comment) || !comment.Data.StartsWith("!"))
{

View File

@@ -105,7 +105,7 @@
{
var parser = new CodespaceRangeParser();
var byteArrayInput = new MemoryInputBytes(OtherEncodings.StringAsLatin1Bytes("1 begincodespacerange\nendcodespacerange"));
var tokenScanner = new CoreTokenScanner(byteArrayInput, false);
var tokenScanner = new CoreTokenScanner(byteArrayInput, false, new StackDepthGuard(256));
Assert.True(tokenScanner.MoveNext());
Assert.True(tokenScanner.CurrentToken is NumericToken);

View File

@@ -11,7 +11,7 @@
{
var bytes = GetFileBytes("AdobeUtopia.pfa");
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
}
[Fact]
@@ -20,7 +20,7 @@
// TODO: support reading in these pfb files
var bytes = GetFileBytes("Raleway-Black.pfb");
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
}
[Fact]
@@ -28,7 +28,7 @@
{
var bytes = GetFileBytes("CMBX10.pfa");
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
}
[Fact]
@@ -36,7 +36,7 @@
{
var bytes = GetFileBytes("CMCSC10");
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
}
[Fact]
@@ -44,7 +44,7 @@
{
var bytes = GetFileBytes("CMBX12");
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
}
[Fact]
@@ -52,7 +52,7 @@
{
var bytes = GetFileBytes("CMBX10");
var result = Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
var result = Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
var builder = new StringBuilder("<!DOCTYPE html><html><head></head><body>");
foreach (var charString in result.CharStrings.CharStrings)
@@ -71,7 +71,7 @@
{
var bytes = GetFileBytes("CMR10");
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
}
private static byte[] GetFileBytes(string name)

View File

@@ -11,6 +11,20 @@
public class GithubIssuesTests
{
[Fact]
public void Issue1217()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("stackoverflow_error.pdf");
var options = new ParsingOptions()
{
UseLenientParsing = true,
MaxStackDepth = 100
};
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, options));
Assert.Equal($"Exceeded maximum nesting depth of {options.MaxStackDepth}.", ex.Message);
}
[Fact]
public void Issue1223()
{

View File

@@ -42,7 +42,7 @@ public class FirstPassParserTests
var results = FirstPassParser.Parse(
new FileHeaderOffset(0),
ib.Bytes,
new CoreTokenScanner(ib.Bytes, true));
new CoreTokenScanner(ib.Bytes, true, new StackDepthGuard(256)));
Assert.Equal(2, results.Parts.Count);
Assert.NotNull(results.Trailer);
@@ -114,7 +114,7 @@ public class FirstPassParserTests
var ib = StringBytesTestConverter.Convert(content, false);
var results = FirstPassParser.Parse(new FileHeaderOffset(0), ib.Bytes, new CoreTokenScanner(ib.Bytes, true));
var results = FirstPassParser.Parse(new FileHeaderOffset(0), ib.Bytes, new CoreTokenScanner(ib.Bytes, true, new StackDepthGuard(256)));
var offsets = results.Parts.Select(x => x.Offset).OrderBy(x => x).ToList();
@@ -123,7 +123,7 @@ public class FirstPassParserTests
Assert.NotNull(results.Trailer);
ib.Bytes.Seek(98);
var scanner = new CoreTokenScanner(ib.Bytes, false);
var scanner = new CoreTokenScanner(ib.Bytes, false, new StackDepthGuard(256));
scanner.MoveNext();
Assert.Equal(scanner.CurrentToken, OperatorToken.Xref);
}

View File

@@ -15,7 +15,7 @@
public class PageContentParserTests
{
private readonly PageContentParser parser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance);
private readonly PageContentParser parser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, new StackDepthGuard(256));
private readonly ILog log = new NoOpLog();
[Fact]
@@ -210,7 +210,7 @@ l";
var content = File.ReadAllText(path);
var input = StringBytesTestConverter.Convert(content, false);
var lenientParser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, true);
var lenientParser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, new StackDepthGuard(256), true);
var result = lenientParser.Parse(1, input.Bytes, log);
Assert.NotEmpty(result);

View File

@@ -149,7 +149,7 @@ three %PDF-1.6";
var bytes = new MemoryInputBytes(input);
var scanner = new CoreTokenScanner(bytes, true, ScannerScope.None);
var scanner = new CoreTokenScanner(bytes, true, new StackDepthGuard(256), ScannerScope.None);
var result = FileHeaderParser.Parse(scanner, bytes, false, log);

View File

@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Tests.Parser.Parts;
using PdfPig.Core;
using PdfPig.Parser.FileStructure;
using PdfPig.Tokenization.Scanner;
@@ -26,7 +27,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(456, result.StartXRefDeclaredOffset);
@@ -59,7 +60,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(17, result.StartXRefDeclaredOffset);
@@ -93,7 +94,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(1384733, result.StartXRefDeclaredOffset);
@@ -106,7 +107,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Null(result.StartXRefDeclaredOffset);
@@ -130,7 +131,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Null(result.StartXRefDeclaredOffset);
@@ -151,7 +152,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Null(result.StartXRefDeclaredOffset);
@@ -185,7 +186,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(1274665676543, result.StartXRefDeclaredOffset);
@@ -207,7 +208,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(57695, result.StartXRefDeclaredOffset);

View File

@@ -34,7 +34,7 @@
internal static (CoreTokenScanner scanner, IInputBytes bytes) Scanner(string s)
{
var inputBytes = new MemoryInputBytes(OtherEncodings.StringAsLatin1Bytes(s));
var result = new CoreTokenScanner(inputBytes, true);
var result = new CoreTokenScanner(inputBytes, true, new StackDepthGuard(256));
return (result, inputBytes);
}

View File

@@ -1,11 +1,12 @@
namespace UglyToad.PdfPig.Tests.Tokenization
{
using PdfPig.Core;
using PdfPig.Tokenization;
using PdfPig.Tokens;
public class ArrayTokenizerTests
{
private readonly ArrayTokenizer tokenizer = new ArrayTokenizer(true);
private readonly ArrayTokenizer tokenizer = new ArrayTokenizer(true, new StackDepthGuard(256));
[Theory]
[InlineData("]")]

View File

@@ -7,7 +7,7 @@ namespace UglyToad.PdfPig.Tests.Tokenization
public class DictionaryTokenizerTests
{
private readonly DictionaryTokenizer tokenizer = new DictionaryTokenizer(true);
private readonly DictionaryTokenizer tokenizer = new DictionaryTokenizer(true, new StackDepthGuard(256));
[Theory]
[InlineData("[rjee]")]

View File

@@ -11,7 +11,7 @@ namespace UglyToad.PdfPig.Tests.Tokenization.Scanner
public CoreTokenScannerTests()
{
scannerFactory = x => new CoreTokenScanner(x, true);
scannerFactory = x => new CoreTokenScanner(x, true, new StackDepthGuard(256));
}
[Fact]
@@ -231,7 +231,7 @@ endobj";
var scanner = new CoreTokenScanner(
StringBytesTestConverter.Convert(content, false).Bytes,
true,
true, new StackDepthGuard(256),
isStream: true);
while (scanner.MoveNext())
@@ -247,7 +247,7 @@ endobj";
var nonStreamScanner = new CoreTokenScanner(
StringBytesTestConverter.Convert(content, false).Bytes,
true,
true, new StackDepthGuard(256),
isStream: false);
while (nonStreamScanner.MoveNext())
@@ -293,7 +293,7 @@ endobj";
var scanner = new CoreTokenScanner(
StringBytesTestConverter.Convert(content, false).Bytes,
true,
true, new StackDepthGuard(256),
isStream: true);
while (scanner.MoveNext())

View File

@@ -726,7 +726,8 @@ endobj";
new TestFilterProvider(),
NoOpEncryptionHandler.Instance,
new FileHeaderOffset(0),
useLenientParsing ? new ParsingOptions() : ParsingOptions.LenientParsingOff);
useLenientParsing ? new ParsingOptions() : ParsingOptions.LenientParsingOff,
new StackDepthGuard(256));
}
private static IReadOnlyList<ObjectToken> ReadToEnd(PdfTokenScanner scanner)

View File

@@ -8,12 +8,14 @@
internal sealed class ArrayTokenizer : ITokenizer
{
private readonly bool usePdfDocEncoding;
private readonly StackDepthGuard stackDepthGuard;
public bool ReadsNextByte { get; } = false;
public ArrayTokenizer(bool usePdfDocEncoding)
public ArrayTokenizer(bool usePdfDocEncoding, StackDepthGuard stackDepthGuard)
{
this.usePdfDocEncoding = usePdfDocEncoding;
this.stackDepthGuard = stackDepthGuard;
}
public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken token)
@@ -25,7 +27,7 @@
return false;
}
var scanner = new CoreTokenScanner(inputBytes, usePdfDocEncoding, ScannerScope.Array);
var scanner = new CoreTokenScanner(inputBytes, usePdfDocEncoding, stackDepthGuard, ScannerScope.Array);
var contents = new List<IToken>();

View File

@@ -10,6 +10,7 @@
private readonly bool usePdfDocEncoding;
private readonly IReadOnlyList<NameToken> requiredKeys;
private readonly bool useLenientParsing;
private readonly StackDepthGuard stackDepthGuard;
public bool ReadsNextByte { get; } = false;
@@ -19,14 +20,16 @@
/// <param name="usePdfDocEncoding">
/// Whether to read strings using the PdfDocEncoding.
/// </param>
/// <param name="stackDepthGuard"></param>
/// <param name="requiredKeys">
/// Can be provided to recover from errors with missing dictionary end symbols if the
/// set of keys expected in the dictionary are known.
/// </param>
/// <param name="useLenientParsing">Whether to use lenient parsing.</param>
public DictionaryTokenizer(bool usePdfDocEncoding, IReadOnlyList<NameToken> requiredKeys = null, bool useLenientParsing = false)
public DictionaryTokenizer(bool usePdfDocEncoding, StackDepthGuard stackDepthGuard, IReadOnlyList<NameToken> requiredKeys = null, bool useLenientParsing = false)
{
this.usePdfDocEncoding = usePdfDocEncoding;
this.stackDepthGuard = stackDepthGuard;
this.requiredKeys = requiredKeys;
this.useLenientParsing = useLenientParsing;
}
@@ -83,7 +86,7 @@
return false;
}
var coreScanner = new CoreTokenScanner(inputBytes, usePdfDocEncoding, ScannerScope.Dictionary, useLenientParsing: useLenientParsing);
var coreScanner = new CoreTokenScanner(inputBytes, usePdfDocEncoding, stackDepthGuard, ScannerScope.Dictionary, useLenientParsing: useLenientParsing);
var tokens = new List<IToken>();

View File

@@ -52,12 +52,15 @@
/// </remarks>
private readonly bool isStream;
private readonly StackDepthGuard stackDepthGuard;
/// <summary>
/// Create a new <see cref="CoreTokenScanner"/> from the input.
/// </summary>
public CoreTokenScanner(
IInputBytes inputBytes,
bool usePdfDocEncoding,
StackDepthGuard stackDepthGuard,
ScannerScope scope = ScannerScope.None,
IReadOnlyDictionary<NameToken, IReadOnlyList<NameToken>> namedDictionaryRequiredKeys = null,
bool useLenientParsing = false,
@@ -65,9 +68,10 @@
{
this.inputBytes = inputBytes ?? throw new ArgumentNullException(nameof(inputBytes));
this.usePdfDocEncoding = usePdfDocEncoding;
this.stackDepthGuard = stackDepthGuard;
this.stringTokenizer = new StringTokenizer(usePdfDocEncoding);
this.arrayTokenizer = new ArrayTokenizer(usePdfDocEncoding);
this.dictionaryTokenizer = new DictionaryTokenizer(usePdfDocEncoding, useLenientParsing: useLenientParsing);
this.arrayTokenizer = new ArrayTokenizer(usePdfDocEncoding, this.stackDepthGuard);
this.dictionaryTokenizer = new DictionaryTokenizer(usePdfDocEncoding, this.stackDepthGuard, useLenientParsing: useLenientParsing);
this.scope = scope;
this.namedDictionaryRequiredKeys = namedDictionaryRequiredKeys;
this.useLenientParsing = useLenientParsing;
@@ -101,6 +105,19 @@
/// <inheritdoc />
public bool MoveNext()
{
stackDepthGuard.Enter();
try
{
return MoveNextInternal();
}
finally
{
stackDepthGuard.Exit();
}
}
private bool MoveNextInternal()
{
var endAngleBracesRead = 0;
@@ -169,7 +186,7 @@
&& CurrentToken is NameToken name
&& namedDictionaryRequiredKeys.TryGetValue(name, out var requiredKeys))
{
tokenizer = new DictionaryTokenizer(usePdfDocEncoding, requiredKeys, useLenientParsing);
tokenizer = new DictionaryTokenizer(usePdfDocEncoding, stackDepthGuard, requiredKeys, useLenientParsing);
}
}
else

View File

@@ -21,6 +21,7 @@
{
private readonly IGraphicsStateOperationFactory operationFactory;
private readonly bool useLenientParsing;
private readonly StackDepthGuard stackDepthGuard;
/// <summary>
/// Initialises a new instance of the <see cref="PageContentParser"/> class.
@@ -28,12 +29,14 @@
/// <param name="operationFactory">
/// The factory responsible for creating graphics state operations.
/// </param>
/// <param name="stackDepthGuard"></param>
/// <param name="useLenientParsing">
/// A value indicating whether lenient parsing should be used. Defaults to <c>false</c>.
/// </param>
public PageContentParser(IGraphicsStateOperationFactory operationFactory, bool useLenientParsing = false)
public PageContentParser(IGraphicsStateOperationFactory operationFactory, StackDepthGuard stackDepthGuard, bool useLenientParsing = false)
{
this.operationFactory = operationFactory;
this.stackDepthGuard = stackDepthGuard;
this.useLenientParsing = useLenientParsing;
}
@@ -55,7 +58,7 @@
IInputBytes inputBytes,
ILog log)
{
var scanner = new CoreTokenScanner(inputBytes, false, useLenientParsing: useLenientParsing);
var scanner = new CoreTokenScanner(inputBytes, false, stackDepthGuard, useLenientParsing: useLenientParsing);
var precedingTokens = new List<IToken>();
var graphicsStateOperations = new List<IGraphicsStateOperation>();

View File

@@ -89,7 +89,9 @@
SkipMissingFonts = false
};
var tokenScanner = new CoreTokenScanner(inputBytes, true, useLenientParsing: options.UseLenientParsing);
var stackDepthGuard = new StackDepthGuard(options.MaxStackDepth);
var tokenScanner = new CoreTokenScanner(inputBytes, true, stackDepthGuard, useLenientParsing: options.UseLenientParsing);
var passwords = new List<string>();
@@ -110,7 +112,7 @@
options.Passwords = passwords;
var document = OpenDocument(inputBytes, tokenScanner, options);
var document = OpenDocument(inputBytes, tokenScanner, options, stackDepthGuard);
return document;
}
@@ -118,7 +120,8 @@
private static PdfDocument OpenDocument(
IInputBytes inputBytes,
ISeekableTokenScanner scanner,
ParsingOptions parsingOptions)
ParsingOptions parsingOptions,
StackDepthGuard stackDepthGuard)
{
var filterProvider = new FilterProviderWithLookup(parsingOptions.FilterProvider ?? DefaultFilterProvider.Instance);
@@ -145,7 +148,7 @@
initialParse.BruteForceOffsets,
inputBytes);
var pdfScanner = new PdfTokenScanner(inputBytes, locationProvider, filterProvider, NoOpEncryptionHandler.Instance, fileHeaderOffset, parsingOptions);
var pdfScanner = new PdfTokenScanner(inputBytes, locationProvider, filterProvider, NoOpEncryptionHandler.Instance, fileHeaderOffset, parsingOptions, stackDepthGuard);
var (rootReference, rootDictionary) = ParseTrailer(
trailer,
@@ -182,6 +185,7 @@
filterProvider,
encodingReader,
cmapCache,
stackDepthGuard,
parsingOptions.UseLenientParsing);
var trueTypeHandler = new TrueTypeFontHandler(
@@ -208,7 +212,7 @@
parsingOptions.UseLenientParsing);
var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider,
new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, parsingOptions.UseLenientParsing), parsingOptions);
new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, stackDepthGuard, parsingOptions.UseLenientParsing), parsingOptions);
var catalog = CatalogFactory.Create(
rootReference,

View File

@@ -52,6 +52,13 @@
/// </summary>
public bool SkipMissingFonts { get; set; } = false;
/// <summary>
/// Gets or sets the maximum allowed stack depth.
/// </summary>
/// <remarks>This property can be used to limit the depth of recursive or nested operations to
/// prevent stack overflows or excessive resource usage.</remarks>
public int MaxStackDepth { get; set; } = 256;
/// <summary>
/// Filter provider to use while parsing the document. The <see cref="DefaultFilterProvider"/> will be used if set to <c>null</c>.
/// </summary>

View File

@@ -24,6 +24,7 @@
{
var scanner = new CoreTokenScanner(inputBytes,
false,
StackDepthGuard.Infinite, // We don't check for stack overflows, we might want to change that.
namedDictionaryRequiredKeys: new Dictionary<NameToken, IReadOnlyList<NameToken>>
{
{ NameToken.CidSystemInfo, new[] { NameToken.Registry, NameToken.Ordering, NameToken.Supplement } }

View File

@@ -21,12 +21,14 @@
private readonly IEncodingReader encodingReader;
private readonly CMapLocalCache cmapLocalCache;
private readonly bool isLenientParsing;
private readonly StackDepthGuard stackDepthGuard;
public Type1FontHandler(
IPdfTokenScanner pdfScanner,
ILookupFilterProvider filterProvider,
IEncodingReader encodingReader,
CMapLocalCache cmapLocalCache,
StackDepthGuard stackDepthGuard,
bool isLenientParsing)
{
this.pdfScanner = pdfScanner;
@@ -34,6 +36,7 @@
this.encodingReader = encodingReader;
this.cmapLocalCache = cmapLocalCache;
this.isLenientParsing = isLenientParsing;
this.stackDepthGuard = stackDepthGuard;
}
public IFont Generate(DictionaryToken dictionary)
@@ -172,7 +175,7 @@
var length1 = stream.StreamDictionary.Get<NumericToken>(NameToken.Length1, pdfScanner);
var length2 = stream.StreamDictionary.Get<NumericToken>(NameToken.Length2, pdfScanner);
var font = Type1FontParser.Parse(new MemoryInputBytes(bytes), length1.Int, length2.Int);
var font = Type1FontParser.Parse(new MemoryInputBytes(bytes), length1.Int, length2.Int, stackDepthGuard);
return Union<Type1Font, CompactFontFormatFontCollection>.One(font);
}

View File

@@ -51,13 +51,16 @@
public long Length => coreTokenScanner.Length;
private readonly StackDepthGuard stackDepthGuard;
public PdfTokenScanner(
IInputBytes inputBytes,
IObjectLocationProvider objectLocationProvider,
ILookupFilterProvider filterProvider,
IEncryptionHandler encryptionHandler,
FileHeaderOffset fileHeaderOffset,
ParsingOptions parsingOptions)
ParsingOptions parsingOptions,
StackDepthGuard stackDepthGuard)
{
this.inputBytes = inputBytes;
this.objectLocationProvider = objectLocationProvider;
@@ -65,7 +68,8 @@
this.encryptionHandler = encryptionHandler;
this.fileHeaderOffset = fileHeaderOffset;
this.parsingOptions = parsingOptions;
coreTokenScanner = new CoreTokenScanner(inputBytes, true, useLenientParsing: parsingOptions.UseLenientParsing);
this.stackDepthGuard = stackDepthGuard;
coreTokenScanner = new CoreTokenScanner(inputBytes, true, stackDepthGuard, useLenientParsing: parsingOptions.UseLenientParsing);
}
public void UpdateEncryptionHandler(IEncryptionHandler newHandler)
@@ -871,6 +875,7 @@
var scanner = new CoreTokenScanner(
bytes,
true,
stackDepthGuard,
useLenientParsing: parsingOptions.UseLenientParsing,
isStream: true);

View File

@@ -74,7 +74,7 @@ namespace UglyToad.PdfPig.Writer
return false;
}
var pageContentParser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance);
var pageContentParser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, StackDepthGuard.Infinite);
IReadOnlyList<IGraphicsStateOperation> operations;
try
{

View File

@@ -347,7 +347,7 @@ namespace UglyToad.PdfPig.Writer
}
var page = document.GetPage(pageNumber);
var pcp = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, true);
var pcp = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, StackDepthGuard.Infinite, true);
// copy content streams
var streams = new List<PdfPageBuilder.CopiedContentStream>();