247 lines
9.4 KiB
Odin
247 lines
9.4 KiB
Odin
package pdf
|
|
|
|
// Object streams (ISO 32000-2, 7.5.7).
|
|
//
|
|
// An /ObjStm is a stream whose decoded bytes hold /N complete objects, one
|
|
// after another, preceded by a header of /N "object-number offset" integer
|
|
// pairs. /First is where the objects start; each pair's offset is measured
|
|
// from there.
|
|
//
|
|
// Two properties of the format matter to everything below. The objects inside
|
|
// have no "N G obj" header of their own, so the pair table is the only thing
|
|
// that says which object is where -- and when it disagrees with the
|
|
// cross-reference table, one of the two is wrong and the pair table is the one
|
|
// that was written next to the bytes it describes. And the whole stream is
|
|
// decrypted as a unit (7.5.7, and 7.6.2 for the reason), so the objects taken
|
|
// out of it must not be decrypted a second time: they are parsed with the zero
|
|
// Ref, which is what tells the parser to leave their strings alone.
|
|
|
|
// Objstm is a decoded object stream and its pair table, cached on the Document
|
|
// for as long as the Document lives.
|
|
Objstm :: struct {
|
|
// The container object, so a repair can name it.
|
|
ref: Ref,
|
|
// Object numbers, in the order the pair table lists them.
|
|
nums: []u32,
|
|
// Where each object starts in `data`, or -1 for a pair whose offset does
|
|
// not land inside the stream.
|
|
offsets: []int,
|
|
// The decoded stream. Owned by the Document arena; never freed here.
|
|
data: []byte,
|
|
// Byte offset the objects start at, after any repair to /First.
|
|
first: int,
|
|
// What /N claimed, which is not always what the pair table holds. The
|
|
// dictionary is never encrypted even when the payload is, so the two
|
|
// disagreeing is evidence that the payload was not decoded correctly.
|
|
declared_n: int,
|
|
// Set while the header is being read, so an object stream that reaches
|
|
// itself -- directly, or through a second stream that points back -- is
|
|
// caught instead of recursed into.
|
|
loading: bool,
|
|
failed: bool,
|
|
}
|
|
|
|
// objstm_get_object returns object `num`, which the cross-reference table says
|
|
// is entry `index` of object stream `container`.
|
|
//
|
|
// `index` is treated as a hint rather than as the truth. A file that was
|
|
// incrementally updated without its cross-reference stream being rewritten has
|
|
// stale indices, and the pair table is right where the index is wrong, so a
|
|
// mismatch is resolved by searching the table for the number that was asked
|
|
// for.
|
|
objstm_get_object :: proc(doc: ^Document, container: u32, index: u32, num: u32) -> (Object, Error) {
|
|
if doc == nil || num == 0 { return nil, .Invalid_Argument }
|
|
|
|
stm, err := objstm_load(doc, container)
|
|
if err != .None { return nil, err }
|
|
if stm == nil || len(stm.nums) == 0 { return nil, .Object_Not_Found }
|
|
|
|
slot := -1
|
|
if int(index) < len(stm.nums) && stm.nums[index] == num {
|
|
slot = int(index)
|
|
} else {
|
|
for n, i in stm.nums {
|
|
if n == num {
|
|
slot = i
|
|
break
|
|
}
|
|
}
|
|
if slot >= 0 {
|
|
add_repair(doc, .Xref_Entry_Mismatch, 0, Ref{num, 0})
|
|
}
|
|
}
|
|
if slot < 0 { return nil, .Object_Not_Found }
|
|
|
|
off := stm.offsets[slot]
|
|
if off < 0 || off >= len(stm.data) {
|
|
add_repair(doc, .Missing_Object, 0, Ref{num, 0})
|
|
return nil, .Object_Not_Found
|
|
}
|
|
|
|
p := parser_make(doc, stm.data, doc.allocator)
|
|
p.lex.pos = off
|
|
// p.obj_ref stays {0,0}: this object's strings were decrypted with the
|
|
// container and must not be touched again (7.5.7).
|
|
obj, perr := parse_object(&p)
|
|
if perr != .None {
|
|
add_repair(doc, .Missing_Object, i64(off), Ref{num, 0})
|
|
return nil, perr
|
|
}
|
|
return obj, .None
|
|
}
|
|
|
|
// objstm_load decodes an object stream and reads its pair table, from the
|
|
// Document's cache if it has been read before.
|
|
@(private)
|
|
objstm_load :: proc(doc: ^Document, container: u32) -> (^Objstm, Error) {
|
|
if doc == nil || container == 0 || container > MAX_OBJECT_NUMBER {
|
|
return nil, .Invalid_Argument
|
|
}
|
|
|
|
if cached, ok := doc.objstm_cache[container]; ok && cached != nil {
|
|
if cached.loading {
|
|
add_repair(doc, .Missing_Object, 0, cached.ref)
|
|
return nil, .Cyclic_Reference
|
|
}
|
|
if cached.failed { return nil, .Bad_Stream }
|
|
return cached, .None
|
|
}
|
|
|
|
gen := u16(0)
|
|
if int(container) < len(doc.xref) {
|
|
gen = doc.xref[container].gen
|
|
}
|
|
|
|
stm := new(Objstm, doc.allocator)
|
|
stm.ref = Ref{container, gen}
|
|
stm.loading = true
|
|
// Cached before it is read, so a stream that contains itself meets its own
|
|
// half-built entry rather than recursing.
|
|
doc.objstm_cache[container] = stm
|
|
|
|
err := objstm_read(doc, stm)
|
|
stm.loading = false
|
|
if err != .None {
|
|
stm.failed = true
|
|
stm.nums = nil
|
|
stm.offsets = nil
|
|
stm.data = nil
|
|
return nil, err
|
|
}
|
|
return stm, .None
|
|
}
|
|
|
|
// objstm_pairs reads the "object-number offset" table at the front of the
|
|
// decoded stream. `hit_limit` says it stopped because it reached `header_limit`
|
|
// rather than because the tokens stopped being a pair.
|
|
@(private)
|
|
objstm_pairs :: proc(doc: ^Document, data: []byte, limit, header_limit: int) ->
|
|
(nums: [dynamic]u32, rels: [dynamic]i64, header_end: int, hit_limit: bool) {
|
|
nums = make([dynamic]u32, 0, min(limit, 64), doc.allocator)
|
|
rels = make([dynamic]i64, 0, min(limit, 64), doc.allocator)
|
|
|
|
lex := Lexer{data = data, pos = 0}
|
|
for len(nums) < limit {
|
|
save := lex.pos
|
|
t1 := lexer_next_structural(&lex)
|
|
t2 := lexer_next_structural(&lex)
|
|
if t1.kind != .Integer || t2.kind != .Integer {
|
|
lex.pos = save
|
|
break
|
|
}
|
|
if lex.pos > header_limit {
|
|
lex.pos = save
|
|
hit_limit = true
|
|
break
|
|
}
|
|
if t1.int_value < 1 || t1.int_value > MAX_OBJECT_NUMBER {
|
|
// A pair whose object number is impossible still occupies a slot:
|
|
// dropping it would shift every index after it.
|
|
append(&nums, u32(0))
|
|
} else {
|
|
append(&nums, u32(t1.int_value))
|
|
}
|
|
append(&rels, t2.int_value)
|
|
}
|
|
header_end = lex.pos
|
|
return
|
|
}
|
|
|
|
@(private)
|
|
objstm_read :: proc(doc: ^Document, stm: ^Objstm) -> Error {
|
|
s := get_stream(doc, stm.ref)
|
|
if s == nil { return .Object_Not_Found }
|
|
// A missing /Type is tolerated -- it is missing from a great many
|
|
// dictionaries that are otherwise perfectly formed -- but a /Type that
|
|
// says something else means the cross-reference table sent us to the
|
|
// wrong object, and taking its bytes apart as pairs would invent objects.
|
|
if ty := dict_name(doc, s.dict, "Type"); ty != "" && ty != "ObjStm" {
|
|
return .Type_Mismatch
|
|
}
|
|
|
|
data, derr := stream_data(doc, s)
|
|
if derr != .None { return derr }
|
|
if len(data) == 0 || len(data) > MAX_OBJSTM_SIZE { return .Bad_Stream }
|
|
stm.data = data
|
|
|
|
declared_n := dict_int(doc, s.dict, "N", 0)
|
|
declared_first := dict_int(doc, s.dict, "First", -1)
|
|
stm.declared_n = int(clamp(declared_n, 0, i64(max(i32))))
|
|
|
|
// A pair is two integers and a separator, so three bytes at the very
|
|
// least. Whatever /N claims, the header cannot hold more than that -- and
|
|
// a stream is allowed to decode to a gigabyte, so the file's own length
|
|
// bounds it too: every object in here also needs an entry in the
|
|
// cross-reference table, which is bounded the same way.
|
|
max_pairs := min(len(data) / 3 + 1, xref_max_objects(doc))
|
|
limit := max_pairs
|
|
if declared_n > 0 && declared_n <= i64(max_pairs) {
|
|
limit = int(declared_n)
|
|
} else {
|
|
// /N missing, zero, negative or larger than the stream can hold. Read
|
|
// pairs until they stop being pairs and take that as the count.
|
|
add_repair(doc, .Bad_Stream_Length, 0, stm.ref)
|
|
}
|
|
|
|
// The header ends where /First says it does, when /First is believable.
|
|
// Reading past it would take the first object's own bytes for pairs.
|
|
header_limit := len(data)
|
|
if declared_first > 0 && declared_first <= i64(len(data)) {
|
|
header_limit = int(declared_first)
|
|
}
|
|
|
|
nums, rels, header_end, hit_limit := objstm_pairs(doc, data, limit, header_limit)
|
|
// A /First that lands inside the pair table cuts it short. Reading the
|
|
// header again without that ceiling costs nothing -- the pairs stop at the
|
|
// first token that is not an integer either way -- and it is the
|
|
// difference between reading the stream and losing every object in it.
|
|
if hit_limit && (len(nums) == 0 || (declared_n > 0 && len(nums) < int(declared_n))) {
|
|
nums, rels, header_end, _ = objstm_pairs(doc, data, limit, len(data))
|
|
}
|
|
|
|
first := declared_first
|
|
if first < i64(header_end) || first > i64(len(data)) {
|
|
// /First is measured from the start of the decoded stream and must be
|
|
// at or after the end of the pair table. When it is not, the pair
|
|
// table itself says where the objects begin.
|
|
add_repair(doc, .Bad_Stream_Length, 0, stm.ref)
|
|
first = i64(header_end)
|
|
}
|
|
stm.first = int(first)
|
|
|
|
offsets := make([]int, len(rels), doc.allocator)
|
|
for r, i in rels {
|
|
abs := first + r
|
|
if r < 0 || abs < 0 || abs >= i64(len(data)) {
|
|
offsets[i] = -1
|
|
continue
|
|
}
|
|
offsets[i] = int(abs)
|
|
}
|
|
|
|
stm.nums = nums[:]
|
|
stm.offsets = offsets
|
|
if len(stm.nums) == 0 { return .Bad_Stream }
|
|
return .None
|
|
}
|