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 }