Module: Pdfrb::Source::Recovery
- Defined in:
- lib/pdfrb/source/recovery.rb
Overview
Last-resort recovery: scan the entire file for \d+ \d+ obj
patterns and synthesise an XrefSection from the offsets found.
Triggered when the xref table is missing or points to a wrong
offset.
Extended recovery capabilities:
* Cross-reference table reconstruction (the original mode).
* Trailer recovery: find /Root and /Encrypt by scanning for
the keys directly when the trailer is corrupt.
* Hybrid xref detection: read both xref table and xref stream
when both are present.
* Object-stream reconstruction: rebuild an XrefSection from a
/Type /ObjStm stream when its header is intact but the
xref stream is missing.
Class Method Summary collapse
-
.hybrid_xref?(io) ⇒ Boolean
Detect hybrid xref: PDF 1.7+ allows both a classical xref table AND an xref stream in the same revision, referenced from the table via /XRefStm.
-
.rebuild_from_object_stream(objstm, document) ⇒ Object
Reconstruct an XrefSection from a /Type /ObjStm stream by reading its /N + /First + decompressed body.
- .rebuild_xref(io, recover: true) ⇒ Object
-
.recover_trailer_references(io) ⇒ Object
Scan the file for /Root, /Info, /Encrypt references when the trailer dict is unreadable.
Class Method Details
.hybrid_xref?(io) ⇒ Boolean
Detect hybrid xref: PDF 1.7+ allows both a classical xref
table AND an xref stream in the same revision, referenced
from the table via /XRefStm. Returns true if the file has
both. Pure-Ruby: scans for startxref followed by both
xref and a Type=XRef stream object.
72 73 74 75 76 77 78 79 80 |
# File 'lib/pdfrb/source/recovery.rb', line 72 def hybrid_xref?(io) io.seek(0, IO::SEEK_SET) data = io.read.to_s data.force_encoding(Encoding::BINARY) has_table = data.include?("\nxref\n") has_stream = data.match?(/\/Type\s*\/XRef\b/) has_table && has_stream end |
.rebuild_from_object_stream(objstm, document) ⇒ Object
Reconstruct an XrefSection from a /Type /ObjStm stream by reading its /N + /First + decompressed body. Used when the outer xref stream is corrupt but a known ObjStm is intact.
85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/pdfrb/source/recovery.rb', line 85 def rebuild_from_object_stream(objstm, document) return nil unless objstm pairs = ObjectStreamReader.read(objstm, document) section = XrefSection.new pairs.each_key do |oid| # Mark as compressed in objstm 0 (placeholder; caller # fills in the real objstm_oid). section.add_compressed(oid, 0, 0, objstm.oid) end section end |
.rebuild_xref(io, recover: true) ⇒ Object
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/pdfrb/source/recovery.rb', line 29 def rebuild_xref(io, recover: true) return nil unless recover io.seek(0, IO::SEEK_SET) section = XrefSection.new data = io.read data.force_encoding(Encoding::BINARY) offset = 0 while (m = data.match(OBJ_PATTERN, offset)) oid = m[1].to_i gen = m[2].to_i entry_offset = m.begin(0) # Take the last-seen gen per oid (PDF spec: highest gen wins). section.add_in_use(oid, gen, entry_offset) offset = m.end(0) end section end |
.recover_trailer_references(io) ⇒ Object
Scan the file for /Root, /Info, /Encrypt references when the trailer dict is unreadable. Returns a Hash with :Root, :Info, :Encrypt keys as References (or nil per key).
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/pdfrb/source/recovery.rb', line 51 def recover_trailer_references(io) io.seek(0, IO::SEEK_SET) data = io.read.to_s data.force_encoding(Encoding::BINARY) root_match = data.match(TRAILER_ROOT_PATTERN) info_match = data.match(TRAILER_INFO_PATTERN) encrypt_match = data.match(TRAILER_ENCRYPT_PATTERN) { Root: root_match ? Pdfrb::Model::Reference.new(root_match[1].to_i, root_match[2].to_i) : nil, Info: info_match ? Pdfrb::Model::Reference.new(info_match[1].to_i, info_match[2].to_i) : nil, Encrypt: encrypt_match ? Pdfrb::Model::Reference.new(encrypt_match[1].to_i, encrypt_match[2].to_i) : nil, } end |