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..986de51e 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(); - // Read all trailers. - while (true) + // 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; + } + + _lexer.Position = startxref; + + // Read all trailers. + while (true) { PdfTrailer trailer = ReadXRefTableAndTrailer(_document._irefTable); // 1st trailer seems to be the best. @@ -1052,9 +1070,180 @@ internal PdfTrailer ReadTrailer() } /// - /// Reads cross reference table(s) and trailer(s). + /// Checks that the current _lexer location is a valid xref. /// - private PdfTrailer ReadXRefTableAndTrailer(PdfCrossReferenceTable xrefTable) + /// + 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); + int eof = content.IndexOf("%%EOF", StringComparison.Ordinal); + + 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 (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; + } + } + + _lexer.Position = position; + + 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 (trail_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);