diff --git a/src/UglyToad.PdfPig.Tests/Tokenization/Scanner/PdfTokenScannerTests.cs b/src/UglyToad.PdfPig.Tests/Tokenization/Scanner/PdfTokenScannerTests.cs index 6931a9b1..0df6cd62 100644 --- a/src/UglyToad.PdfPig.Tests/Tokenization/Scanner/PdfTokenScannerTests.cs +++ b/src/UglyToad.PdfPig.Tests/Tokenization/Scanner/PdfTokenScannerTests.cs @@ -417,6 +417,35 @@ endobj"; Assert.Equal(3, third.Number.ObjectNumber); } + [Fact] + public void ReadsDictionaryContainingNull() + { + const string input = @"14224 0 obj +<> +endobj"; + + var scanner = GetScanner(input); + + var tokens = ReadToEnd(scanner); + + var dictionaryToken = tokens[0].Data as DictionaryToken; + + Assert.NotNull(dictionaryToken); + + var encryptValue = dictionaryToken.Data["Encrypt"]; + + Assert.IsType(encryptValue); + } + private static PdfTokenScanner GetScanner(string s, TestObjectLocationProvider locationProvider = null) { var input = StringBytesTestConverter.Convert(s, false); diff --git a/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs b/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs index c5ac1eb4..316e9410 100644 --- a/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs +++ b/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs @@ -53,7 +53,7 @@ private static PdfDocument Open(IInputBytes inputBytes, ParsingOptions options = null) { var isLenientParsing = options?.UseLenientParsing ?? true; - + var tokenScanner = new CoreTokenScanner(inputBytes); var passwords = new List(); @@ -80,7 +80,7 @@ return document; } - private static PdfDocument OpenDocument(IInputBytes inputBytes, ISeekableTokenScanner scanner, ILog log, bool isLenientParsing, + private static PdfDocument OpenDocument(IInputBytes inputBytes, ISeekableTokenScanner scanner, ILog log, bool isLenientParsing, IReadOnlyList passwords, bool clipPaths) { var filterProvider = new FilterProviderWithLookup(DefaultFilterProvider.Instance); @@ -96,25 +96,25 @@ var crossReferenceStreamParser = new CrossReferenceStreamParser(filterProvider); var crossReferenceParser = new CrossReferenceParser(log, xrefValidator, crossReferenceStreamParser); - + var version = FileHeaderParser.Parse(scanner, isLenientParsing, log); - + var crossReferenceOffset = FileTrailerParser.GetFirstCrossReferenceOffset(inputBytes, scanner, isLenientParsing) + version.OffsetInFile; - + // TODO: make this use the scanner. var validator = new CrossReferenceOffsetValidator(xrefValidator); crossReferenceOffset = validator.Validate(crossReferenceOffset, scanner, inputBytes, isLenientParsing); - - crossReferenceTable = crossReferenceParser.Parse(inputBytes, isLenientParsing, + + crossReferenceTable = crossReferenceParser.Parse(inputBytes, isLenientParsing, crossReferenceOffset, version.OffsetInFile, - pdfScanner, + pdfScanner, scanner); - - var (rootReference, rootDictionary) = ParseTrailer(crossReferenceTable, isLenientParsing, - pdfScanner, + + var (rootReference, rootDictionary) = ParseTrailer(crossReferenceTable, isLenientParsing, + pdfScanner, out var encryptionDictionary); var encryptionHandler = encryptionDictionary != null ? @@ -134,22 +134,22 @@ type1Handler), type1Handler, new Type3FontHandler(pdfScanner, filterProvider, encodingReader)); - + var resourceContainer = new ResourceStore(pdfScanner, fontFactory); - + var information = DocumentInformationFactory.Create(pdfScanner, crossReferenceTable.Trailer); var catalog = CatalogFactory.Create(rootReference, rootDictionary, pdfScanner, isLenientParsing); - var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider, - new PageContentParser(new ReflectionGraphicsStateOperationFactory()), + var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider, + new PageContentParser(new ReflectionGraphicsStateOperationFactory()), log); var caching = new ParsingCachingProviders(resourceContainer); var acroFormFactory = new AcroFormFactory(pdfScanner, filterProvider, crossReferenceTable); var bookmarksProvider = new BookmarksProvider(log, pdfScanner); - + return new PdfDocument(log, inputBytes, version, crossReferenceTable, caching, pageFactory, catalog, information, encryptionDictionary, pdfScanner, @@ -162,20 +162,10 @@ private static (IndirectReference, DictionaryToken) ParseTrailer(CrossReferenceTable crossReferenceTable, bool isLenientParsing, IPdfTokenScanner pdfTokenScanner, out EncryptionDictionary encryptionDictionary) { - encryptionDictionary = null; + encryptionDictionary = GetEncryptionDictionary(crossReferenceTable, pdfTokenScanner); - if (crossReferenceTable.Trailer.EncryptionToken != null) - { - if (!DirectObjectFinder.TryGet(crossReferenceTable.Trailer.EncryptionToken, pdfTokenScanner, out DictionaryToken encryptionDictionaryToken)) - { - throw new PdfDocumentFormatException($"Unrecognized encryption token in trailer: {crossReferenceTable.Trailer.EncryptionToken}."); - } - - encryptionDictionary = EncryptionDictionaryFactory.Read(encryptionDictionaryToken, pdfTokenScanner); - } - var rootDictionary = DirectObjectFinder.Get(crossReferenceTable.Trailer.Root, pdfTokenScanner); - + if (!rootDictionary.ContainsKey(NameToken.Type) && isLenientParsing) { rootDictionary = rootDictionary.With(NameToken.Type, NameToken.Catalog); @@ -183,5 +173,28 @@ return (crossReferenceTable.Trailer.Root, rootDictionary); } + + private static EncryptionDictionary GetEncryptionDictionary(CrossReferenceTable crossReferenceTable, IPdfTokenScanner pdfTokenScanner) + { + if (crossReferenceTable.Trailer.EncryptionToken == null) + { + return null; + } + + + if (!DirectObjectFinder.TryGet(crossReferenceTable.Trailer.EncryptionToken, pdfTokenScanner, out DictionaryToken encryptionDictionaryToken)) + { + if (DirectObjectFinder.TryGet(crossReferenceTable.Trailer.EncryptionToken, pdfTokenScanner, out NullToken _)) + { + return null; + } + + throw new PdfDocumentFormatException($"Unrecognized encryption token in trailer: {crossReferenceTable.Trailer.EncryptionToken}."); + } + + var result = EncryptionDictionaryFactory.Read(encryptionDictionaryToken, pdfTokenScanner); + + return result; + } } }