mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
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
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:
63
src/UglyToad.PdfPig.Core/StackDepthGuard.cs
Normal file
63
src/UglyToad.PdfPig.Core/StackDepthGuard.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("!"))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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("]")]
|
||||
|
||||
@@ -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]")]
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 } }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>();
|
||||
|
||||
Reference in New Issue
Block a user