From b5c84aec8ce261edf384db21538c68480eeb422d Mon Sep 17 00:00:00 2001 From: Matthew Laukala Date: Fri, 3 Nov 2017 15:40:08 -0700 Subject: [PATCH 1/5] made fixes for really broken but readable files. --- src/PdfSharp/Pdf.IO/Lexer.cs | 55 +++++++++--- src/PdfSharp/Pdf.IO/Parser.cs | 154 ++++++++++++++++++++++++++++++++-- 2 files changed, 190 insertions(+), 19 deletions(-) diff --git a/src/PdfSharp/Pdf.IO/Lexer.cs b/src/PdfSharp/Pdf.IO/Lexer.cs index ddf78250..0c6e257b 100644 --- a/src/PdfSharp/Pdf.IO/Lexer.cs +++ b/src/PdfSharp/Pdf.IO/Lexer.cs @@ -76,21 +76,35 @@ public int Position } } - /// - /// Reads the next token and returns its type. If the token starts with a digit, the parameter - /// testReference specifies how to treat it. If it is false, the lexer scans for a single integer. - /// If it is true, the lexer checks if the digit is the prefix of a reference. If it is a reference, - /// the token is set to the object ID followed by the generation number separated by a blank - /// (the 'R' is omitted from the token). - /// - // /// Indicates whether to test the next token if it is a reference. - public Symbol ScanNextToken() + /// + /// Reads the next token and returns its type. If the token starts with a digit, the parameter + /// testReference specifies how to treat it. If it is false, the lexer scans for a single integer. + /// If it is true, the lexer checks if the digit is the prefix of a reference. If it is a reference, + /// the token is set to the object ID followed by the generation number separated by a blank + /// (the 'R' is omitted from the token). + /// + // /// Indicates whether to test the next token if it is a reference. + public Symbol ScanNextToken() + { + return ScanNextToken(out int location); + } + + /// + /// Reads the next token and returns its type. If the token starts with a digit, the parameter + /// testReference specifies how to treat it. If it is false, the lexer scans for a single integer. + /// If it is true, the lexer checks if the digit is the prefix of a reference. If it is a reference, + /// the token is set to the object ID followed by the generation number separated by a blank + /// (the 'R' is omitted from the token). + /// + // /// The start position of the next token. + public Symbol ScanNextToken(out int position) { Again: _token = new StringBuilder(); char ch = MoveToNonWhiteSpace(); - switch (ch) + position = Position; + switch (ch) { case '%': // Eat comments, the parser doesn't handle them @@ -190,7 +204,26 @@ public byte[] ReadStream(int length) else pos = _idxChar + 1; - _pdfSteam.Position = pos; + // Verify stream length and resolve if bad + string post_stream = ReadRawString(pos + length, ("endstream").Length); + if (post_stream != "endstream") + { + // find the first endstream occurrence + // first check to see if it is within the specified stream length. + int endstream_idx = post_stream.IndexOf("endstream", StringComparison.Ordinal); + if (endstream_idx == -1) + { + post_stream = ReadRawString(pos, _pdfLength - pos); + endstream_idx = post_stream.IndexOf("endstream", StringComparison.Ordinal); + } + + if (endstream_idx != -1) + { + length = endstream_idx; + } + } + + _pdfSteam.Position = pos; byte[] bytes = new byte[length]; int read = _pdfSteam.Read(bytes, 0, length); Debug.Assert(read == length); diff --git a/src/PdfSharp/Pdf.IO/Parser.cs b/src/PdfSharp/Pdf.IO/Parser.cs index a5d12ac3..d2609042 100644 --- a/src/PdfSharp/Pdf.IO/Parser.cs +++ b/src/PdfSharp/Pdf.IO/Parser.cs @@ -550,7 +550,12 @@ private Symbol ScanNextToken() return _lexer.ScanNextToken(); } - private Symbol ScanNextToken(out string token) + private Symbol ScanNextToken(out int position) + { + return _lexer.ScanNextToken(out position); + } + + private Symbol ScanNextToken(out string token) { Symbol symbol = _lexer.ScanNextToken(); token = _lexer.Token; @@ -1031,10 +1036,23 @@ internal PdfTrailer ReadTrailer() throw new Exception("The StartXRef table could not be found, the file cannot be opened."); ReadSymbol(Symbol.StartXRef); - _lexer.Position = ReadInteger(); + int startxref = _lexer.Position = ReadInteger(); + + // Check for valid startxref + if (!IsValidXref()) + { + PdfTrailer trailer = TryRecreateXRefTableAndTrailer(_document._irefTable); + if (trailer == null) + throw new Exception("Could not recreate the xref table or trailer."); + + _document._trailer = trailer; + return _document._trailer; + } - // Read all trailers. - while (true) + _lexer.Position = startxref; + + // Read all trailers. + while (true) { PdfTrailer trailer = ReadXRefTableAndTrailer(_document._irefTable); // 1st trailer seems to be the best. @@ -1051,10 +1069,130 @@ internal PdfTrailer ReadTrailer() return _document._trailer; } - /// - /// Reads cross reference table(s) and trailer(s). - /// - private PdfTrailer ReadXRefTableAndTrailer(PdfCrossReferenceTable xrefTable) + /// + /// Checks that the current _lexer location is a valid xref. + /// + /// + private bool IsValidXref() + { + Symbol symbol = ScanNextToken(); + if (symbol == Symbol.XRef) + { + return true; + } + + if (symbol == Symbol.Integer) + { + // Just because we have an integer, doesn't mean the startxref is actually valid + if (ScanNextToken() == Symbol.Integer && ScanNextToken() == Symbol.Obj) + { + return true; + } + } + + return false; + } + + private PdfTrailer TryRecreateXRefTableAndTrailer(PdfCrossReferenceTable xrefTable) + { + // Let's first check for a trailer + int length = _lexer.PdfLength; + + int trail_idx; + if (length >= 1024) + { + string trail = _lexer.ReadRawString(length - 1024, 1024); + trail_idx = trail.LastIndexOf("trailer", StringComparison.Ordinal); + _lexer.Position = length - 1024 + trail_idx; + } + else + { + string trail = _lexer.ReadRawString(0, length); + trail_idx = trail.LastIndexOf("trailer", StringComparison.Ordinal); + _lexer.Position = trail_idx; + } + + if (trail_idx == -1) + return null; //TODO: Look for compressed xref table that should contain the trailer + + ReadSymbol(Symbol.Trailer); + ReadSymbol(Symbol.BeginDictionary); + PdfTrailer trailer = new PdfTrailer(_document); + ReadDictionary(trailer, false); + + // Recreate the xref table. + // + // When symbol == Symbol.Obj + // [0] - generation + // [1] - id + TokenInfo[] token_stack = new TokenInfo[2]; + _lexer.Position = 0; + while (true) + { + Symbol symbol = ScanNextToken(out int position); + if (symbol == Symbol.Eof) + break; + + // we need to skip over streams entirely + if (symbol == Symbol.BeginStream) + { + // We're not reading any data from the object so wee need to find endstream + int pos = _lexer.Position; + string trail = ""; + int trail_pos = pos; + while (true) + { + // look for endstream in 1k chunks. + int trail_length = Math.Min(1024, length - trail_pos); + trail += _lexer.ReadRawString(trail_pos, trail_length); + int stop = trail.IndexOf("endstream", StringComparison.Ordinal); + if (stop != -1) + { + _lexer.Position = stop + pos; + break; + } + + trail_pos = trail_pos + trail_length; + if (pos + trail_length > length) + { + // No endstream was found. + throw new Exception("endstream not found."); + } + } + } + + if (symbol == Symbol.Obj && + token_stack[0].Symbol == Symbol.Integer && + token_stack[1].Symbol == Symbol.Integer) + { + PdfObjectID objectID = new PdfObjectID(token_stack[1].Number, token_stack[0].Number); + if (!xrefTable.Contains(objectID)) + xrefTable.Add(new PdfReference(objectID, token_stack[1].Position)); + //ReadObject(null, objectID, false, false); // Can't do this because the object value will never be set after + //SkipCharsUntil(Symbol.EndObj); // Can't do this because streams will cause exceptions + } + + token_stack[1] = token_stack[0]; + TokenInfo token_info = new TokenInfo { Symbol = symbol, Position = position }; + if (symbol == Symbol.Integer) + token_info.Number = _lexer.TokenToInteger; + token_stack[0] = token_info; + } + + return trailer; + } + + struct TokenInfo + { + public int Position; + public Symbol Symbol; + public int Number; + } + + /// + /// Reads cross reference table(s) and trailer(s). + /// + private PdfTrailer ReadXRefTableAndTrailer(PdfCrossReferenceTable xrefTable) { Debug.Assert(xrefTable != null); From 38c6b4e6d1f7394c49637e18a513e453aa2d60d1 Mon Sep 17 00:00:00 2001 From: Matthew Laukala Date: Mon, 6 Nov 2017 15:18:35 -0800 Subject: [PATCH 2/5] IsValidXref() now checks to see if it is inside a stream. --- src/PdfSharp/Pdf.IO/Parser.cs | 38 ++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/PdfSharp/Pdf.IO/Parser.cs b/src/PdfSharp/Pdf.IO/Parser.cs index d2609042..690002df 100644 --- a/src/PdfSharp/Pdf.IO/Parser.cs +++ b/src/PdfSharp/Pdf.IO/Parser.cs @@ -1075,6 +1075,42 @@ internal PdfTrailer ReadTrailer() /// private bool IsValidXref() { + int length = _lexer.PdfLength; + int position = _lexer.Position; + // Make sure not inside a stream. + + string content = ""; + int content_pos = position; + while (true) + { + // look for stream and endstream in 1k chunks. + int read_length = Math.Min(1024, length - content_pos); + content += _lexer.ReadRawString(content_pos, read_length); + + int ss = content.IndexOf("stream", StringComparison.Ordinal); + int es = content.IndexOf("endstream", StringComparison.Ordinal); + + if (ss < es) + { + // Not inside of stream + break; + } + else if (ss > es) + { + // inside of stream + return false; + } + + content_pos = content_pos + read_length; + if (content_pos + read_length >= length) + { + // reached the end of the document without finding either. + break; + } + } + + _lexer.Position = position; + Symbol symbol = ScanNextToken(); if (symbol == Symbol.XRef) { @@ -1153,7 +1189,7 @@ private PdfTrailer TryRecreateXRefTableAndTrailer(PdfCrossReferenceTable xrefTab } trail_pos = trail_pos + trail_length; - if (pos + trail_length > length) + if (trail_pos + trail_length >= length) { // No endstream was found. throw new Exception("endstream not found."); From 15ac2bba9263d965c98b3ecc58f27a12e921a011 Mon Sep 17 00:00:00 2001 From: Matthew Laukala Date: Wed, 8 Nov 2017 08:20:12 -0800 Subject: [PATCH 3/5] Fixed a function that checks if the current lexer.position is inside of a stream --- src/PdfSharp/Pdf.IO/Parser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PdfSharp/Pdf.IO/Parser.cs b/src/PdfSharp/Pdf.IO/Parser.cs index 690002df..bd21277b 100644 --- a/src/PdfSharp/Pdf.IO/Parser.cs +++ b/src/PdfSharp/Pdf.IO/Parser.cs @@ -1095,7 +1095,7 @@ private bool IsValidXref() // Not inside of stream break; } - else if (ss > es) + else if (es != -1 && ss > es) { // inside of stream return false; From d6337ee3a2c5103e1c96642792c95ac33b336b9f Mon Sep 17 00:00:00 2001 From: Matthew Laukala Date: Tue, 14 Nov 2017 10:10:51 -0800 Subject: [PATCH 4/5] IsValidXref() no will no longer check if it's inside of a stream beyong an EOF symbol. --- src/PdfSharp/Pdf.IO/Parser.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/PdfSharp/Pdf.IO/Parser.cs b/src/PdfSharp/Pdf.IO/Parser.cs index bd21277b..95a9b11e 100644 --- a/src/PdfSharp/Pdf.IO/Parser.cs +++ b/src/PdfSharp/Pdf.IO/Parser.cs @@ -1088,17 +1088,21 @@ private bool IsValidXref() content += _lexer.ReadRawString(content_pos, read_length); int ss = content.IndexOf("stream", StringComparison.Ordinal); + int eof = content.IndexOf("%%EOF", StringComparison.Ordinal); int es = content.IndexOf("endstream", StringComparison.Ordinal); - if (ss < es) - { - // Not inside of stream - break; - } - else if (es != -1 && ss > es) + int s = Math.Min(ss, eof); + + if (s != es) { - // inside of stream - return false; + if (s == -1) + return false; + else if (es == -1) + break; + else if (s < es) + break; + else if (s > es) + return false; } content_pos = content_pos + read_length; From a0b0b977e9173b101dcfff0b2d1ad09c82e82ac8 Mon Sep 17 00:00:00 2001 From: Matthew Laukala Date: Mon, 11 Dec 2017 13:52:10 -0800 Subject: [PATCH 5/5] Fixed IsValidRef(). Made bad checks. --- src/PdfSharp/Pdf.IO/Parser.cs | 121 ++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/src/PdfSharp/Pdf.IO/Parser.cs b/src/PdfSharp/Pdf.IO/Parser.cs index 95a9b11e..986de51e 100644 --- a/src/PdfSharp/Pdf.IO/Parser.cs +++ b/src/PdfSharp/Pdf.IO/Parser.cs @@ -1069,71 +1069,82 @@ internal PdfTrailer ReadTrailer() return _document._trailer; } - /// - /// Checks that the current _lexer location is a valid xref. - /// - /// - private bool IsValidXref() - { - int length = _lexer.PdfLength; - int position = _lexer.Position; - // Make sure not inside a stream. + /// + /// Checks that the current _lexer location is a valid xref. + /// + /// + private bool IsValidXref() + { + int length = _lexer.PdfLength; + int position = _lexer.Position; + // Make sure not inside a stream. - string content = ""; - int content_pos = position; - while (true) - { - // look for stream and endstream in 1k chunks. - int read_length = Math.Min(1024, length - content_pos); - content += _lexer.ReadRawString(content_pos, read_length); + string content = ""; + int content_pos = position; + while (true) + { + // look for stream and endstream in 1k chunks. + int read_length = Math.Min(1024, length - content_pos); + content += _lexer.ReadRawString(content_pos, read_length); - int ss = content.IndexOf("stream", StringComparison.Ordinal); - int eof = content.IndexOf("%%EOF", StringComparison.Ordinal); - int es = content.IndexOf("endstream", StringComparison.Ordinal); + int ss = content.IndexOf("stream", StringComparison.Ordinal); + int es = content.IndexOf("endstream", StringComparison.Ordinal); + int eof = content.IndexOf("%%EOF", StringComparison.Ordinal); - int s = Math.Min(ss, eof); + if (ss != es) + { + if (ss == -1) + { + if (eof != -1 && eof < es) + break; + else + return false; + } + else if (es == -1) + break; + else if (ss < es) + break; + else if (ss > es) + { + if (eof != -1 && eof < ss && eof < es) + break; + else + return false; + } + } - if (s != es) - { - if (s == -1) - return false; - else if (es == -1) - break; - else if (s < es) - break; - else if (s > es) - return false; - } + if (eof != -1) + break; - content_pos = content_pos + read_length; - if (content_pos + read_length >= length) - { - // reached the end of the document without finding either. - break; - } - } + content_pos = content_pos + read_length; + if (content_pos + read_length >= length) + { + // reached the end of the document without finding either. + break; + } + } - _lexer.Position = position; + _lexer.Position = position; - Symbol symbol = ScanNextToken(); - if (symbol == Symbol.XRef) - { - return true; - } + Symbol symbol = ScanNextToken(); + if (symbol == Symbol.XRef) + { + return true; + } - if (symbol == Symbol.Integer) - { - // Just because we have an integer, doesn't mean the startxref is actually valid - if (ScanNextToken() == Symbol.Integer && ScanNextToken() == Symbol.Obj) - { - return true; - } - } + if (symbol == Symbol.Integer) + { + // Just because we have an integer, doesn't mean the startxref is actually valid + if (ScanNextToken() == Symbol.Integer && ScanNextToken() == Symbol.Obj) + { + return true; + } + } - return false; - } + return false; + } - private PdfTrailer TryRecreateXRefTableAndTrailer(PdfCrossReferenceTable xrefTable) + private PdfTrailer TryRecreateXRefTableAndTrailer(PdfCrossReferenceTable xrefTable) { // Let's first check for a trailer int length = _lexer.PdfLength;