Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/pdf/pdf-xref.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/pdf/pdf-xref.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,5298 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" +#include "pdf-annot-imp.h" +#include "pdf-imp.h" + +#include <assert.h> +#include <limits.h> +#include <string.h> + +#undef DEBUG_PROGESSIVE_ADVANCE + +#ifdef DEBUG_PROGESSIVE_ADVANCE +#define DEBUGMESS(A) do { fz_warn A; } while (0) +#else +#define DEBUGMESS(A) do { } while (0) +#endif + +#define isdigit(c) (c >= '0' && c <= '9') + +static inline int iswhite(int ch) +{ + return + ch == '\000' || ch == '\011' || ch == '\012' || + ch == '\014' || ch == '\015' || ch == '\040'; +} + +/* + * xref tables + */ + +static void +pdf_drop_xref_subsec(fz_context *ctx, pdf_xref *xref) +{ + pdf_xref_subsec *sub = xref->subsec; + pdf_unsaved_sig *usig; + int e; + + while (sub != NULL) + { + pdf_xref_subsec *next_sub = sub->next; + for (e = 0; e < sub->len; e++) + { + pdf_xref_entry *entry = &sub->table[e]; + pdf_drop_obj(ctx, entry->obj); + fz_drop_buffer(ctx, entry->stm_buf); + } + fz_free(ctx, sub->table); + fz_free(ctx, sub); + sub = next_sub; + } + + pdf_drop_obj(ctx, xref->pre_repair_trailer); + pdf_drop_obj(ctx, xref->trailer); + + while ((usig = xref->unsaved_sigs) != NULL) + { + xref->unsaved_sigs = usig->next; + pdf_drop_obj(ctx, usig->field); + pdf_drop_signer(ctx, usig->signer); + fz_free(ctx, usig); + } +} + +static void pdf_drop_xref_sections_imp(fz_context *ctx, pdf_document *doc, pdf_xref *xref_sections, int num_xref_sections) +{ + int x; + + for (x = 0; x < num_xref_sections; x++) + pdf_drop_xref_subsec(ctx, &xref_sections[x]); + + fz_free(ctx, xref_sections); +} + +static void pdf_drop_xref_sections(fz_context *ctx, pdf_document *doc) +{ + pdf_drop_xref_sections_imp(ctx, doc, doc->saved_xref_sections, doc->saved_num_xref_sections); + pdf_drop_xref_sections_imp(ctx, doc, doc->xref_sections, doc->num_xref_sections); + + doc->saved_xref_sections = NULL; + doc->saved_num_xref_sections = 0; + doc->xref_sections = NULL; + doc->num_xref_sections = 0; + doc->num_incremental_sections = 0; +} + +static void +extend_xref_index(fz_context *ctx, pdf_document *doc, int newlen) +{ + int i; + + doc->xref_index = fz_realloc_array(ctx, doc->xref_index, newlen, int); + for (i = doc->max_xref_len; i < newlen; i++) + { + doc->xref_index[i] = 0; + } + doc->max_xref_len = newlen; +} + +static void +resize_xref_sub(fz_context *ctx, pdf_xref *xref, int base, int newlen) +{ + pdf_xref_subsec *sub; + int i; + + assert(xref != NULL); + sub = xref->subsec; + assert(sub->next == NULL && sub->start == base && sub->len+base == xref->num_objects); + assert(newlen+base > xref->num_objects); + + sub->table = fz_realloc_array(ctx, sub->table, newlen, pdf_xref_entry); + for (i = sub->len; i < newlen; i++) + { + sub->table[i].type = 0; + sub->table[i].ofs = 0; + sub->table[i].gen = 0; + sub->table[i].num = 0; + sub->table[i].stm_ofs = 0; + sub->table[i].stm_buf = NULL; + sub->table[i].obj = NULL; + } + sub->len = newlen; + if (newlen+base > xref->num_objects) + xref->num_objects = newlen+base; +} + +/* This is only ever called when we already have an incremental + * xref. This means there will only be 1 subsec, and it will be + * a complete subsec. */ +static void pdf_resize_xref(fz_context *ctx, pdf_document *doc, int newlen) +{ + pdf_xref *xref = &doc->xref_sections[doc->xref_base]; + + resize_xref_sub(ctx, xref, 0, newlen); + if (doc->max_xref_len < newlen) + extend_xref_index(ctx, doc, newlen); +} + +static void pdf_populate_next_xref_level(fz_context *ctx, pdf_document *doc) +{ + pdf_xref *xref; + doc->xref_sections = fz_realloc_array(ctx, doc->xref_sections, doc->num_xref_sections + 1, pdf_xref); + doc->num_xref_sections++; + + xref = &doc->xref_sections[doc->num_xref_sections - 1]; + xref->subsec = NULL; + xref->num_objects = 0; + xref->trailer = NULL; + xref->pre_repair_trailer = NULL; + xref->unsaved_sigs = NULL; + xref->unsaved_sigs_end = NULL; +} + +pdf_obj *pdf_trailer(fz_context *ctx, pdf_document *doc) +{ + /* Return the document's trailer (of the appropriate vintage) */ + pdf_xref *xrefs = doc->xref_sections; + + return xrefs ? xrefs[doc->xref_base].trailer : NULL; +} + +void pdf_set_populating_xref_trailer(fz_context *ctx, pdf_document *doc, pdf_obj *trailer) +{ + /* Update the trailer of the xref section being populated */ + pdf_xref *xref = &doc->xref_sections[doc->num_xref_sections - 1]; + if (xref->trailer) + { + pdf_drop_obj(ctx, xref->pre_repair_trailer); + xref->pre_repair_trailer = xref->trailer; + } + xref->trailer = pdf_keep_obj(ctx, trailer); +} + +int pdf_xref_len(fz_context *ctx, pdf_document *doc) +{ + int i = doc->xref_base; + int xref_len = 0; + + if (doc->local_xref && doc->local_xref_nesting > 0) + xref_len = doc->local_xref->num_objects; + + while (i < doc->num_xref_sections) + xref_len = fz_maxi(xref_len, doc->xref_sections[i++].num_objects); + + return xref_len; +} + +/* Ensure that the given xref has a single subsection + * that covers the entire range. */ +static void +ensure_solid_xref(fz_context *ctx, pdf_document *doc, int num, int which) +{ + pdf_xref *xref = &doc->xref_sections[which]; + pdf_xref_subsec *sub = xref->subsec; + pdf_xref_subsec *new_sub; + + if (num < xref->num_objects) + num = xref->num_objects; + + if (sub != NULL && sub->next == NULL && sub->start == 0 && sub->len >= num) + return; + + new_sub = fz_malloc_struct(ctx, pdf_xref_subsec); + fz_try(ctx) + { + new_sub->table = fz_malloc_struct_array(ctx, num, pdf_xref_entry); + new_sub->start = 0; + new_sub->len = num; + new_sub->next = NULL; + } + fz_catch(ctx) + { + fz_free(ctx, new_sub); + fz_rethrow(ctx); + } + + /* Move objects over to the new subsection and destroy the old + * ones */ + sub = xref->subsec; + while (sub != NULL) + { + pdf_xref_subsec *next = sub->next; + int i; + + for (i = 0; i < sub->len; i++) + { + new_sub->table[i+sub->start] = sub->table[i]; + } + fz_free(ctx, sub->table); + fz_free(ctx, sub); + sub = next; + } + xref->num_objects = num; + xref->subsec = new_sub; + if (doc->max_xref_len < num) + extend_xref_index(ctx, doc, num); +} + +static pdf_xref_entry * +pdf_get_local_xref_entry(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref *xref = doc->local_xref; + pdf_xref_subsec *sub; + + if (xref == NULL || doc->local_xref_nesting == 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Local xref not present!"); + + /* Local xrefs only ever have 1 section, and it should be solid. */ + sub = xref->subsec; + assert(sub && !sub->next); + if (num >= sub->start && num < sub->start + sub->len) + return &sub->table[num - sub->start]; + + /* Expand the xref so we can return a pointer. */ + resize_xref_sub(ctx, xref, 0, num+1); + sub = xref->subsec; + return &sub->table[num - sub->start]; +} + +pdf_xref_entry *pdf_get_populating_xref_entry(fz_context *ctx, pdf_document *doc, int num) +{ + /* Return an entry within the xref currently being populated */ + pdf_xref *xref; + pdf_xref_subsec *sub; + + if (doc->num_xref_sections == 0) + { + doc->xref_sections = fz_malloc_struct(ctx, pdf_xref); + doc->num_xref_sections = 1; + } + + if (doc->local_xref && doc->local_xref_nesting > 0) + return pdf_get_local_xref_entry(ctx, doc, num); + + /* Prevent accidental heap underflow */ + if (num < 0 || num > PDF_MAX_OBJECT_NUMBER) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "object number out of range (%d)", num); + + /* Return the pointer to the entry in the last section. */ + xref = &doc->xref_sections[doc->num_xref_sections-1]; + + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + if (num >= sub->start && num < sub->start + sub->len) + return &sub->table[num-sub->start]; + } + + /* We've been asked for an object that's not in a subsec. */ + ensure_solid_xref(ctx, doc, num+1, doc->num_xref_sections-1); + xref = &doc->xref_sections[doc->num_xref_sections-1]; + sub = xref->subsec; + + return &sub->table[num-sub->start]; +} + +/* It is vital that pdf_get_xref_entry_aux called with !solidify_if_needed + * and a value object number, does NOT try/catch or throw. */ +static +pdf_xref_entry *pdf_get_xref_entry_aux(fz_context *ctx, pdf_document *doc, int i, int solidify_if_needed) +{ + pdf_xref *xref = NULL; + pdf_xref_subsec *sub; + int j; + + if (i < 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Negative object number requested"); + + if (i < doc->max_xref_len) + j = doc->xref_index[i]; + else + j = 0; + + /* If we have an active local xref, check there first. */ + if (doc->local_xref && doc->local_xref_nesting > 0) + { + xref = doc->local_xref; + + if (i < xref->num_objects) + { + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + pdf_xref_entry *entry; + + if (i < sub->start || i >= sub->start + sub->len) + continue; + + entry = &sub->table[i - sub->start]; + if (entry->type) + return entry; + } + } + } + + /* We may be accessing an earlier version of the document using xref_base + * and j may be an index into a later xref section */ + if (doc->xref_base > j) + j = doc->xref_base; + else + j = 0; + + + /* Find the first xref section where the entry is defined. */ + for (; j < doc->num_xref_sections; j++) + { + xref = &doc->xref_sections[j]; + + if (i < xref->num_objects) + { + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + pdf_xref_entry *entry; + + if (i < sub->start || i >= sub->start + sub->len) + continue; + + entry = &sub->table[i - sub->start]; + if (entry->type) + { + /* Don't update xref_index if xref_base may have + * influenced the value of j */ + if (doc->xref_base == 0) + doc->xref_index[i] = j; + return entry; + } + } + } + } + + /* Didn't find the entry in any section. Return the entry from + * the local_xref (if there is one active), or the final section. */ + if (doc->local_xref && doc->local_xref_nesting > 0) + { + if (xref == NULL || i < xref->num_objects) + { + xref = doc->local_xref; + sub = xref->subsec; + assert(sub != NULL && sub->next == NULL); + if (i >= sub->start && i < sub->start + sub->len) + return &sub->table[i - sub->start]; + } + + /* Expand the xref so we can return a pointer. */ + resize_xref_sub(ctx, xref, 0, i+1); + sub = xref->subsec; + return &sub->table[i - sub->start]; + } + + doc->xref_index[i] = 0; + if (xref == NULL || i < xref->num_objects) + { + xref = &doc->xref_sections[doc->xref_base]; + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + if (i >= sub->start && i < sub->start + sub->len) + return &sub->table[i - sub->start]; + } + } + + /* Some really hairy code here. When we are reading the file in + * initially, we read from 'newest' to 'oldest' (i.e. from 0 to + * doc->num_xref_sections-1). Each section is created initially + * with num_objects == 0 in it, and remains like that while we + * are parsing the stream from the file. This is the only time + * we'll ever have xref_sections with 0 objects in them. */ + if (doc->xref_sections[doc->num_xref_sections-1].num_objects == 0) + { + /* The oldest xref section has 0 objects in it. So we are + * parsing an xref stream while loading. We don't want to + * solidify the xref we are currently parsing for (as it'll + * get very confused, and end up a different 'shape' in + * memory to that which is in the file, and would hence + * render 'fingerprinting' for snapshotting invalid) so + * just give up at this point. */ + return NULL; + } + + if (!solidify_if_needed) + return NULL; + + /* At this point, we solidify the xref. This ensures that we + * can return a pointer. This is the only case where this function + * might throw an exception, and it will never happen when we are + * working within a 'solid' xref. */ + ensure_solid_xref(ctx, doc, i+1, 0); + xref = &doc->xref_sections[0]; + sub = xref->subsec; + return &sub->table[i - sub->start]; +} + +pdf_xref_entry *pdf_get_xref_entry(fz_context *ctx, pdf_document *doc, int i) +{ + return pdf_get_xref_entry_aux(ctx, doc, i, 1); +} + +pdf_xref_entry *pdf_get_xref_entry_no_change(fz_context *ctx, pdf_document *doc, int i) +{ + return pdf_get_xref_entry_aux(ctx, doc, i, 0); +} + +pdf_xref_entry *pdf_get_xref_entry_no_null(fz_context *ctx, pdf_document *doc, int i) +{ + pdf_xref_entry *entry = pdf_get_xref_entry(ctx, doc, i); + if (entry != NULL) + return entry; + fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot find object in xref (%d 0 R), but not allowed to return NULL", i); +} + +void pdf_xref_entry_map(fz_context *ctx, pdf_document *doc, void (*fn)(fz_context *, pdf_xref_entry *, int, pdf_document *, void *), void *arg) +{ + int i, j; + pdf_xref_subsec *sub; + int xref_base = doc->xref_base; + + fz_try(ctx) + { + /* Map over any active local xref first. */ + if (doc->local_xref && doc->local_xref_nesting > 0) + { + pdf_xref *xref = doc->local_xref; + + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + for (i = sub->start; i < sub->start + sub->len; i++) + { + pdf_xref_entry *entry = &sub->table[i - sub->start]; + if (entry->type) + fn(ctx, entry, i, doc, arg); + } + } + } + + for (j = 0; j < doc->num_xref_sections; j++) + { + pdf_xref *xref = &doc->xref_sections[j]; + doc->xref_base = j; + + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + for (i = sub->start; i < sub->start + sub->len; i++) + { + pdf_xref_entry *entry = &sub->table[i - sub->start]; + if (entry->type) + fn(ctx, entry, i, doc, arg); + } + } + } + } + fz_always(ctx) + { + doc->xref_base = xref_base; + } + fz_catch(ctx) + fz_rethrow(ctx); +} + +/* + Ensure we have an incremental xref section where we can store + updated versions of indirect objects. This is a new xref section + consisting of a single xref subsection. +*/ +static void ensure_incremental_xref(fz_context *ctx, pdf_document *doc) +{ + /* If there are as yet no incremental sections, or if the most recent + * one has been used to sign a signature field, then we need a new one. + * After a signing, any further document changes require a new increment */ + if ((doc->num_incremental_sections == 0 || doc->xref_sections[0].unsaved_sigs != NULL) + && !doc->disallow_new_increments) + { + pdf_xref *xref = &doc->xref_sections[0]; + pdf_xref *pxref; + pdf_xref_entry *new_table = fz_malloc_struct_array(ctx, xref->num_objects, pdf_xref_entry); + pdf_xref_subsec *sub = NULL; + pdf_obj *trailer = NULL; + int i; + + fz_var(trailer); + fz_var(sub); + fz_try(ctx) + { + sub = fz_malloc_struct(ctx, pdf_xref_subsec); + trailer = xref->trailer ? pdf_copy_dict(ctx, xref->trailer) : NULL; + doc->xref_sections = fz_realloc_array(ctx, doc->xref_sections, doc->num_xref_sections + 1, pdf_xref); + xref = &doc->xref_sections[0]; + pxref = &doc->xref_sections[1]; + memmove(pxref, xref, doc->num_xref_sections * sizeof(pdf_xref)); + /* xref->num_objects is already correct */ + xref->subsec = sub; + sub = NULL; + xref->trailer = trailer; + xref->pre_repair_trailer = NULL; + xref->unsaved_sigs = NULL; + xref->unsaved_sigs_end = NULL; + xref->subsec->next = NULL; + xref->subsec->len = xref->num_objects; + xref->subsec->start = 0; + xref->subsec->table = new_table; + doc->num_xref_sections++; + doc->num_incremental_sections++; + } + fz_catch(ctx) + { + fz_free(ctx, sub); + fz_free(ctx, new_table); + pdf_drop_obj(ctx, trailer); + fz_rethrow(ctx); + } + + /* Update the xref_index */ + for (i = 0; i < doc->max_xref_len; i++) + { + doc->xref_index[i]++; + } + } +} + +/* Used when altering a document */ +pdf_xref_entry *pdf_get_incremental_xref_entry(fz_context *ctx, pdf_document *doc, int i) +{ + pdf_xref *xref; + pdf_xref_subsec *sub; + + /* Make a new final xref section if we haven't already */ + ensure_incremental_xref(ctx, doc); + + xref = &doc->xref_sections[doc->xref_base]; + if (i >= xref->num_objects) + pdf_resize_xref(ctx, doc, i + 1); + + sub = xref->subsec; + assert(sub != NULL && sub->next == NULL); + assert(i >= sub->start && i < sub->start + sub->len); + doc->xref_index[i] = 0; + return &sub->table[i - sub->start]; +} + +int pdf_xref_is_incremental(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref *xref = &doc->xref_sections[doc->xref_base]; + pdf_xref_subsec *sub = xref->subsec; + + assert(sub != NULL && sub->next == NULL && sub->len == xref->num_objects && sub->start == 0); + + return num < xref->num_objects && sub->table[num].type; +} + +/* Used when clearing signatures. Removes the signature +from the list of unsaved signed signatures. */ +void pdf_xref_remove_unsaved_signature(fz_context *ctx, pdf_document *doc, pdf_obj *field) +{ + int num = pdf_to_num(ctx, field); + int idx = doc->xref_index[num]; + pdf_xref *xref = &doc->xref_sections[idx]; + pdf_unsaved_sig **usigptr = &xref->unsaved_sigs; + pdf_unsaved_sig *usig = xref->unsaved_sigs; + + while (usig) + { + pdf_unsaved_sig **nextptr = &usig->next; + pdf_unsaved_sig *next = usig->next; + + if (usig->field == field) + { + if (xref->unsaved_sigs_end == &usig->next) + { + if (usig->next) + xref->unsaved_sigs_end = &usig->next->next; + else + xref->unsaved_sigs_end = NULL; + } + if (usigptr) + *usigptr = usig->next; + + usig->next = NULL; + pdf_drop_obj(ctx, usig->field); + pdf_drop_signer(ctx, usig->signer); + fz_free(ctx, usig); + + break; + } + + usig = next; + usigptr = nextptr; + } +} + +void pdf_xref_store_unsaved_signature(fz_context *ctx, pdf_document *doc, pdf_obj *field, pdf_pkcs7_signer *signer) +{ + pdf_xref *xref = &doc->xref_sections[0]; + pdf_unsaved_sig *unsaved_sig; + + /* Record details within the document structure so that contents + * and byte_range can be updated with their correct values at + * saving time */ + unsaved_sig = fz_malloc_struct(ctx, pdf_unsaved_sig); + unsaved_sig->field = pdf_keep_obj(ctx, field); + unsaved_sig->signer = signer->keep(ctx, signer); + unsaved_sig->next = NULL; + if (xref->unsaved_sigs_end == NULL) + xref->unsaved_sigs_end = &xref->unsaved_sigs; + + *xref->unsaved_sigs_end = unsaved_sig; + xref->unsaved_sigs_end = &unsaved_sig->next; +} + +int pdf_xref_obj_is_unsaved_signature(pdf_document *doc, pdf_obj *obj) +{ + int i; + for (i = 0; i < doc->num_incremental_sections; i++) + { + pdf_xref *xref = &doc->xref_sections[i]; + pdf_unsaved_sig *usig; + + for (usig = xref->unsaved_sigs; usig; usig = usig->next) + { + if (usig->field == obj) + return 1; + } + } + + return 0; +} + +void pdf_ensure_solid_xref(fz_context *ctx, pdf_document *doc, int num) +{ + if (doc->num_xref_sections == 0) + pdf_populate_next_xref_level(ctx, doc); + + ensure_solid_xref(ctx, doc, num, 0); +} + +int pdf_xref_ensure_incremental_object(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref_entry *new_entry, *old_entry; + pdf_xref_subsec *sub = NULL; + int i; + pdf_obj *copy; + + /* Make sure we have created an xref section for incremental updates */ + ensure_incremental_xref(ctx, doc); + + /* Search for the section that contains this object */ + for (i = doc->xref_index[num]; i < doc->num_xref_sections; i++) + { + pdf_xref *xref = &doc->xref_sections[i]; + + if (num < 0 && num >= xref->num_objects) + break; + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + if (sub->start <= num && num < sub->start + sub->len && sub->table[num - sub->start].type) + break; + } + if (sub != NULL) + break; + } + /* sub == NULL implies we did not find it */ + + /* If we don't find it, or it's already in the incremental section, return */ + if (i == 0 || sub == NULL) + return 0; + + copy = pdf_deep_copy_obj(ctx, sub->table[num - sub->start].obj); + + /* Move the object to the incremental section */ + i = doc->xref_index[num]; + doc->xref_index[num] = 0; + old_entry = &sub->table[num - sub->start]; + fz_try(ctx) + new_entry = pdf_get_incremental_xref_entry(ctx, doc, num); + fz_catch(ctx) + { + pdf_drop_obj(ctx, copy); + doc->xref_index[num] = i; + fz_rethrow(ctx); + } + *new_entry = *old_entry; + if (new_entry->type == 'o') + { + new_entry->type = 'n'; + new_entry->gen = 0; + } + /* Better keep a copy. We must override the old entry with + * the copy because the caller may be holding a reference to + * the original and expect it to end up in the new entry */ + old_entry->obj = copy; + old_entry->stm_buf = NULL; + + return 1; +} + +void pdf_xref_ensure_local_object(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref_entry *new_entry, *old_entry; + pdf_xref_subsec *sub = NULL; + int i; + pdf_xref *xref; + pdf_obj *copy; + + /* Is it in the local section already? */ + xref = doc->local_xref; + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + if (sub->start <= num && num < sub->start + sub->len && sub->table[num - sub->start].type) + break; + } + /* If we found it, it's in the local section already. */ + if (sub != NULL) + return; + + /* Search for the section that contains this object */ + for (i = doc->xref_index[num]; i < doc->num_xref_sections; i++) + { + xref = &doc->xref_sections[i]; + + if (num < 0 && num >= xref->num_objects) + break; + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + if (sub->start <= num && num < sub->start + sub->len && sub->table[num - sub->start].type) + break; + } + if (sub != NULL) + break; + } + /* sub == NULL implies we did not find it */ + if (sub == NULL) + return; /* No object to find */ + + copy = pdf_deep_copy_obj(ctx, sub->table[num - sub->start].obj); + + /* Copy the object to the local section */ + i = doc->xref_index[num]; + doc->xref_index[num] = 0; + old_entry = &sub->table[num - sub->start]; + fz_try(ctx) + new_entry = pdf_get_local_xref_entry(ctx, doc, num); + fz_catch(ctx) + { + pdf_drop_obj(ctx, copy); + doc->xref_index[num] = i; + fz_rethrow(ctx); + } + *new_entry = *old_entry; + if (new_entry->type == 'o') + { + new_entry->type = 'n'; + new_entry->gen = 0; + } + new_entry->stm_buf = NULL; + new_entry->obj = NULL; + /* old entry is incremental and may have changes. + * Better keep a copy. We must override the old entry with + * the copy because the caller may be holding a reference to + * the original and expect it to end up in the new entry */ + new_entry->obj = old_entry->obj; + old_entry->obj = copy; + new_entry->stm_buf = NULL; /* FIXME */ +} + +void pdf_replace_xref(fz_context *ctx, pdf_document *doc, pdf_xref_entry *entries, int n) +{ + int *xref_index = NULL; + pdf_xref *xref = NULL; + pdf_xref_subsec *sub; + + fz_var(xref_index); + fz_var(xref); + + fz_try(ctx) + { + xref_index = fz_calloc(ctx, n, sizeof(int)); + xref = fz_malloc_struct(ctx, pdf_xref); + sub = fz_malloc_struct(ctx, pdf_xref_subsec); + } + fz_catch(ctx) + { + fz_free(ctx, xref); + fz_free(ctx, xref_index); + fz_rethrow(ctx); + } + + sub->table = entries; + sub->start = 0; + sub->len = n; + + xref->subsec = sub; + xref->num_objects = n; + xref->trailer = pdf_keep_obj(ctx, pdf_trailer(ctx, doc)); + + /* The new table completely replaces the previous separate sections */ + pdf_drop_xref_sections(ctx, doc); + + doc->xref_sections = xref; + doc->num_xref_sections = 1; + doc->num_incremental_sections = 0; + doc->xref_base = 0; + doc->disallow_new_increments = 0; + doc->max_xref_len = n; + + fz_free(ctx, doc->xref_index); + doc->xref_index = xref_index; +} + +void pdf_forget_xref(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *trailer = pdf_keep_obj(ctx, pdf_trailer(ctx, doc)); + + pdf_drop_local_xref_and_resources(ctx, doc); + + if (doc->saved_xref_sections) + pdf_drop_xref_sections_imp(ctx, doc, doc->saved_xref_sections, doc->saved_num_xref_sections); + + doc->saved_xref_sections = doc->xref_sections; + doc->saved_num_xref_sections = doc->num_xref_sections; + + doc->xref_sections = NULL; + doc->startxref = 0; + doc->num_xref_sections = 0; + doc->num_incremental_sections = 0; + doc->xref_base = 0; + doc->disallow_new_increments = 0; + + fz_try(ctx) + { + pdf_get_populating_xref_entry(ctx, doc, 0); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, trailer); + fz_rethrow(ctx); + } + + /* Set the trailer of the final xref section. */ + doc->xref_sections[0].trailer = trailer; +} + +/* + * magic version tag and startxref + */ + +int +pdf_version(fz_context *ctx, pdf_document *doc) +{ + int version = doc->version; + fz_try(ctx) + { + pdf_obj *obj = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(Version), NULL); + const char *str = pdf_to_name(ctx, obj); + if (*str) + version = 10 * (fz_atof(str) + 0.05f); + } + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + fz_warn(ctx, "Ignoring broken Root/Version number."); + } + return version; +} + +static void +pdf_load_version(fz_context *ctx, pdf_document *doc) +{ + char buf[1024]; + char *s = NULL; + size_t i, n; + + /* look for '%PDF' version marker within first kilobyte of file */ + fz_seek(ctx, doc->file, 0, SEEK_SET); + n = fz_read(ctx, doc->file, (unsigned char*) buf, sizeof buf); + if (n < 5) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot find version marker"); + buf[n-1] = 0; + for (i = 0; i < n - 5; i++) + { + if (memcmp(&buf[i], "%PDF-", 5) == 0 || memcmp(&buf[i], "%FDF-", 5) == 0) + { + s = buf + i; + break; + } + } + if (!s) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot find version marker"); + + if (s[1] == 'F') + doc->is_fdf = 1; + + doc->version = 10 * (fz_atof(s+5) + 0.05f); + if ((doc->version < 10 || doc->version > 17) && doc->version != 20) + fz_warn(ctx, "unknown PDF version: %d.%d", doc->version / 10, doc->version % 10); + + if (s != buf) + { + fz_warn(ctx, "garbage bytes before version marker"); + doc->bias = s - buf; + } + + fz_seek(ctx, doc->file, doc->bias, SEEK_SET); +} + +static void +pdf_read_start_xref(fz_context *ctx, pdf_document *doc) +{ + unsigned char buf[1024]; + size_t i, n; + int64_t t; + + fz_seek(ctx, doc->file, 0, SEEK_END); + + doc->file_size = fz_tell(ctx, doc->file); + + t = fz_maxi64(0, doc->file_size - (int64_t)sizeof buf); + fz_seek(ctx, doc->file, t, SEEK_SET); + + n = fz_read(ctx, doc->file, buf, sizeof buf); + if (n < 9) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot find startxref"); + + i = n - 9; + do + { + if (memcmp(buf + i, "startxref", 9) == 0) + { + i += 9; + while (i < n && iswhite(buf[i])) + i ++; + doc->startxref = 0; + while (i < n && isdigit(buf[i])) + { + if (doc->startxref >= INT64_MAX/10) + fz_throw(ctx, FZ_ERROR_LIMIT, "startxref too large"); + doc->startxref = doc->startxref * 10 + (buf[i++] - '0'); + } + if (doc->startxref != 0) + return; + break; + } + } while (i-- > 0); + + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot find startxref"); +} + +void fz_skip_space(fz_context *ctx, fz_stream *stm) +{ + do + { + int c = fz_peek_byte(ctx, stm); + if (c == EOF || c > 32) + return; + (void)fz_read_byte(ctx, stm); + } + while (1); +} + +int fz_skip_string(fz_context *ctx, fz_stream *stm, const char *str) +{ + while (*str) + { + int c = fz_peek_byte(ctx, stm); + if (c == EOF || c != *str++) + return 1; + (void)fz_read_byte(ctx, stm); + } + return 0; +} + +/* + * trailer dictionary + */ + +static int +pdf_xref_size_from_old_trailer(fz_context *ctx, pdf_document *doc) +{ + int len; + char *s; + int64_t t; + pdf_token tok; + int c; + int size = 0; + int64_t ofs; + pdf_obj *trailer = NULL; + size_t n; + pdf_lexbuf *buf = &doc->lexbuf.base; + pdf_obj *obj = NULL; + + fz_var(trailer); + + /* Record the current file read offset so that we can reinstate it */ + ofs = fz_tell(ctx, doc->file); + + fz_skip_space(ctx, doc->file); + if (fz_skip_string(ctx, doc->file, "xref")) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot find xref marker"); + fz_skip_space(ctx, doc->file); + + while (1) + { + c = fz_peek_byte(ctx, doc->file); + if (!isdigit(c)) + break; + + fz_read_line(ctx, doc->file, buf->scratch, buf->size); + s = buf->scratch; + fz_strsep(&s, " "); /* ignore start */ + if (!s) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref subsection length missing"); + len = fz_atoi(fz_strsep(&s, " ")); + if (len < 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref subsection length must be positive"); + + /* broken pdfs where the section is not on a separate line */ + if (s && *s != '\0') + fz_seek(ctx, doc->file, -(2 + (int)strlen(s)), SEEK_CUR); + + t = fz_tell(ctx, doc->file); + if (t < 0) + fz_throw(ctx, FZ_ERROR_SYSTEM, "cannot tell in file"); + + /* Spec says xref entries should be 20 bytes, but it's not infrequent + * to see 19, in particular for some PCLm drivers. Cope. */ + if (len > 0) + { + n = fz_read(ctx, doc->file, (unsigned char *)buf->scratch, 20); + if (n < 19) + fz_throw(ctx, FZ_ERROR_FORMAT, "malformed xref table"); + if (n == 20 && buf->scratch[19] > 32) + n = 19; + } + else + n = 20; + + if (len > (int64_t)((INT64_MAX - t) / n)) + fz_throw(ctx, FZ_ERROR_LIMIT, "xref has too many entries"); + + fz_seek(ctx, doc->file, t + n * (int64_t)len, SEEK_SET); + } + + fz_try(ctx) + { + tok = pdf_lex(ctx, doc->file, buf); + if (tok != PDF_TOK_TRAILER) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected trailer marker"); + + tok = pdf_lex(ctx, doc->file, buf); + if (tok != PDF_TOK_OPEN_DICT) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected trailer dictionary"); + + trailer = pdf_parse_dict(ctx, doc, doc->file, buf); + + obj = pdf_dict_get(ctx, trailer, PDF_NAME(Size)); + if (pdf_is_indirect(ctx, obj)) + fz_throw(ctx, FZ_ERROR_FORMAT, "trailer Size entry is indirect"); + + size = pdf_dict_get_int(ctx, trailer, PDF_NAME(Size)); + if (size < 0 || size > PDF_MAX_OBJECT_NUMBER + 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "trailer Size entry out of range"); + } + fz_always(ctx) + { + pdf_drop_obj(ctx, trailer); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + + fz_seek(ctx, doc->file, ofs, SEEK_SET); + + return size; +} + +static pdf_xref_entry * +pdf_xref_find_subsection(fz_context *ctx, pdf_document *doc, int start, int len) +{ + pdf_xref *xref = &doc->xref_sections[doc->num_xref_sections-1]; + pdf_xref_subsec *sub, *extend = NULL; + int num_objects; + int solidify = 0; + + if (len == 0) + return NULL; + + /* Different cases here. + * Case 1) We might be asking for a subsection (or a subset of a + * subsection) that we already have - Just return it. + * Case 2) We might be asking for a subsection that overlaps (or + * extends) a subsection we already have - extend the existing one. + * Case 3) We might be asking for a subsection that overlaps multiple + * existing subsections - solidify the whole set. + * Case 4) We might be asking for a completely new subsection - just + * allocate it. + */ + + /* Sanity check */ + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + if (start >= sub->start && start <= sub->start + sub->len) + { + /* 'start' is in (or immediately after) 'sub' */ + if (start + len <= sub->start + sub->len) + { + /* And so is start+len-1 - just return this! Case 1. */ + return &sub->table[start-sub->start]; + } + /* So we overlap with sub. */ + if (extend == NULL) + { + /* Maybe we can extend sub? */ + extend = sub; + } + else + { + /* OK, so we've already found an overlapping one. We'll need to solidify. Case 3. */ + solidify = 1; + break; + } + } + else if (start + len > sub->start && start + len < sub->start + sub->len) + { + /* The end of the start+len range is in 'sub'. */ + /* For now, we won't support extending sub backwards. Just take this as + * needing to solidify. Case 3. */ + solidify = 1; + break; + } + else if (start < sub->start && start + len >= sub->start + sub->len) + { + /* The end of the start+len range is beyond 'sub'. */ + /* For now, we won't support extending sub backwards. Just take this as + * needing to solidify. Another variant of case 3. */ + solidify = 1; + break; + } + } + + num_objects = xref->num_objects; + if (num_objects < start + len) + num_objects = start + len; + + if (solidify) + { + /* Case 3: Solidify the xref */ + ensure_solid_xref(ctx, doc, num_objects, doc->num_xref_sections-1); + xref = &doc->xref_sections[doc->num_xref_sections-1]; + sub = xref->subsec; + } + else if (extend) + { + /* Case 2: Extend the subsection */ + int newlen = start + len - extend->start; + sub = extend; + sub->table = fz_realloc_array(ctx, sub->table, newlen, pdf_xref_entry); + memset(&sub->table[sub->len], 0, sizeof(pdf_xref_entry) * (newlen - sub->len)); + sub->len = newlen; + if (xref->num_objects < sub->start + sub->len) + xref->num_objects = sub->start + sub->len; + if (doc->max_xref_len < sub->start + sub->len) + extend_xref_index(ctx, doc, sub->start + sub->len); + } + else + { + /* Case 4 */ + sub = fz_malloc_struct(ctx, pdf_xref_subsec); + fz_try(ctx) + { + sub->table = fz_malloc_struct_array(ctx, len, pdf_xref_entry); + sub->start = start; + sub->len = len; + sub->next = xref->subsec; + xref->subsec = sub; + } + fz_catch(ctx) + { + fz_free(ctx, sub); + fz_rethrow(ctx); + } + if (xref->num_objects < num_objects) + xref->num_objects = num_objects; + if (doc->max_xref_len < num_objects) + extend_xref_index(ctx, doc, num_objects); + } + return &sub->table[start-sub->start]; +} + +static inline void +validate_object_number_range(fz_context *ctx, int first, int len, const char *what) +{ + if (first < 0 || first > PDF_MAX_OBJECT_NUMBER) + fz_throw(ctx, FZ_ERROR_FORMAT, "first object number in %s out of range", what); + if (len < 0 || len > PDF_MAX_OBJECT_NUMBER) + fz_throw(ctx, FZ_ERROR_FORMAT, "number of objects in %s out of range", what); + if (len > 0 && len - 1 > PDF_MAX_OBJECT_NUMBER - first) + fz_throw(ctx, FZ_ERROR_FORMAT, "last object number in %s out of range", what); +} + +static pdf_obj * +pdf_read_old_xref(fz_context *ctx, pdf_document *doc) +{ + int start, len, c, i, xref_len, carried; + fz_stream *file = doc->file; + pdf_xref_entry *table; + pdf_token tok; + size_t n; + char *s, *e; + pdf_lexbuf *buf = &doc->lexbuf.base; + + xref_len = pdf_xref_size_from_old_trailer(ctx, doc); + + fz_skip_space(ctx, doc->file); + if (fz_skip_string(ctx, doc->file, "xref")) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot find xref marker"); + fz_skip_space(ctx, doc->file); + + while (1) + { + c = fz_peek_byte(ctx, file); + if (!isdigit(c)) + break; + + fz_read_line(ctx, file, buf->scratch, buf->size); + s = buf->scratch; + start = fz_atoi(fz_strsep(&s, " ")); + len = fz_atoi(fz_strsep(&s, " ")); + + /* broken pdfs where the section is not on a separate line */ + if (s && *s != '\0') + { + fz_warn(ctx, "broken xref subsection. proceeding anyway."); + fz_seek(ctx, file, -(2 + (int)strlen(s)), SEEK_CUR); + } + + validate_object_number_range(ctx, start, len, "xref subsection"); + + /* broken pdfs where size in trailer undershoots entries in xref sections */ + if (start + len > xref_len) + { + fz_warn(ctx, "broken xref subsection, proceeding anyway."); + } + + table = pdf_xref_find_subsection(ctx, doc, start, len); + + /* Xref entries SHOULD be 20 bytes long, but we see 19 byte + * ones more frequently than we'd like (e.g. PCLm drivers). + * Cope with this by 'carrying' data forward. */ + carried = 0; + for (i = 0; i < len; i++) + { + pdf_xref_entry *entry = &table[i]; + n = fz_read(ctx, file, (unsigned char *) buf->scratch + carried, 20-carried); + if (n != (size_t)(20-carried)) + fz_throw(ctx, FZ_ERROR_FORMAT, "unexpected EOF in xref table"); + n += carried; + buf->scratch[n] = '\0'; + if (!entry->type) + { + s = buf->scratch; + e = s + n; + + entry->num = start + i; + + /* broken pdfs where line start with white space */ + while (s < e && iswhite(*s)) + s++; + + if (s == e || !isdigit(*s)) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref offset missing"); + while (s < e && isdigit(*s)) + entry->ofs = entry->ofs * 10 + *s++ - '0'; + + while (s < e && iswhite(*s)) + s++; + if (s == e || !isdigit(*s)) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref generation number missing"); + while (s < e && isdigit(*s)) + entry->gen = entry->gen * 10 + *s++ - '0'; + + while (s < e && iswhite(*s)) + s++; + if (s == e || (*s != 'f' && *s != 'n' && *s != 'o')) + fz_throw(ctx, FZ_ERROR_FORMAT, "unexpected xref type: 0x%x (%d %d R)", s == e ? 0 : *s, entry->num, entry->gen); + entry->type = *s++; + + /* If the last byte of our buffer isn't an EOL (or space), carry one byte forward */ + carried = buf->scratch[19] > 32; + if (carried) + buf->scratch[0] = buf->scratch[19]; + } + } + if (carried) + fz_unread_byte(ctx, file); + } + + tok = pdf_lex(ctx, file, buf); + if (tok != PDF_TOK_TRAILER) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected trailer marker"); + + tok = pdf_lex(ctx, file, buf); + if (tok != PDF_TOK_OPEN_DICT) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected trailer dictionary"); + + doc->last_xref_was_old_style = 1; + + return pdf_parse_dict(ctx, doc, file, buf); +} + +static void +pdf_read_new_xref_section(fz_context *ctx, pdf_document *doc, fz_stream *stm, int i0, int i1, int w0, int w1, int w2) +{ + pdf_xref_entry *table; + int i, n; + + validate_object_number_range(ctx, i0, i1, "xref subsection"); + + table = pdf_xref_find_subsection(ctx, doc, i0, i1); + for (i = i0; i < i0 + i1; i++) + { + pdf_xref_entry *entry = &table[i-i0]; + int a = 0; + int64_t b = 0; + int c = 0; + + if (fz_is_eof(ctx, stm)) + fz_throw(ctx, FZ_ERROR_FORMAT, "truncated xref stream"); + + for (n = 0; n < w0; n++) + a = (a << 8) + fz_read_byte(ctx, stm); + for (n = 0; n < w1; n++) + b = (b << 8) + fz_read_byte(ctx, stm); + for (n = 0; n < w2; n++) + c = (c << 8) + fz_read_byte(ctx, stm); + + if (!entry->type) + { + int t = w0 ? a : 1; + entry->type = t == 0 ? 'f' : t == 1 ? 'n' : t == 2 ? 'o' : 0; + entry->ofs = w1 ? b : 0; + entry->gen = w2 ? c : 0; + entry->num = i; + } + } + + doc->last_xref_was_old_style = 0; +} + +/* Entered with file locked, remains locked throughout. */ +static pdf_obj * +pdf_read_new_xref(fz_context *ctx, pdf_document *doc) +{ + fz_stream *stm = NULL; + pdf_obj *trailer = NULL; + pdf_obj *index = NULL; + pdf_obj *obj = NULL; + int gen, num = 0; + int64_t ofs, stm_ofs; + int size, w0, w1, w2; + int t; + + fz_var(trailer); + fz_var(stm); + + fz_try(ctx) + { + ofs = fz_tell(ctx, doc->file); + trailer = pdf_parse_ind_obj(ctx, doc, doc->file, &num, &gen, &stm_ofs, NULL); + if (num == 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "Trailer object number cannot be 0\n"); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, trailer); + fz_rethrow(ctx); + } + + fz_try(ctx) + { + pdf_xref_entry *entry; + + obj = pdf_dict_get(ctx, trailer, PDF_NAME(Size)); + if (!obj) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref stream missing Size entry (%d 0 R)", num); + + size = pdf_to_int(ctx, obj); + + /* Bug708176: If the PDF file producer has declared Size without + * including this object, then increment it. */ + if (size == num) + pdf_dict_put_int(ctx, trailer, PDF_NAME(Size), size+1); + + obj = pdf_dict_get(ctx, trailer, PDF_NAME(W)); + if (!obj) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref stream missing W entry (%d R)", num); + + if (pdf_is_indirect(ctx, pdf_array_get(ctx, obj, 0))) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref stream object type field width an indirect object"); + if (pdf_is_indirect(ctx, pdf_array_get(ctx, obj, 1))) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref stream object field 2 width an indirect object"); + if (pdf_is_indirect(ctx, pdf_array_get(ctx, obj, 2))) + fz_throw(ctx, FZ_ERROR_FORMAT, "xref stream object field 3 width an indirect object"); + + if (doc->file_reading_linearly && pdf_dict_get(ctx, trailer, PDF_NAME(Encrypt))) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot read linearly with encryption"); + + w0 = pdf_array_get_int(ctx, obj, 0); + w1 = pdf_array_get_int(ctx, obj, 1); + w2 = pdf_array_get_int(ctx, obj, 2); + + if (w0 < 0) + fz_warn(ctx, "xref stream objects have corrupt type"); + if (w1 < 0) + fz_warn(ctx, "xref stream objects have corrupt offset"); + if (w2 < 0) + fz_warn(ctx, "xref stream objects have corrupt generation"); + + w0 = w0 < 0 ? 0 : w0; + w1 = w1 < 0 ? 0 : w1; + w2 = w2 < 0 ? 0 : w2; + + index = pdf_dict_get(ctx, trailer, PDF_NAME(Index)); + + stm = pdf_open_stream_with_offset(ctx, doc, num, trailer, stm_ofs); + + if (!index) + { + pdf_read_new_xref_section(ctx, doc, stm, 0, size, w0, w1, w2); + } + else + { + int n = pdf_array_len(ctx, index); + for (t = 0; t < n; t += 2) + { + int i0 = pdf_array_get_int(ctx, index, t + 0); + int i1 = pdf_array_get_int(ctx, index, t + 1); + pdf_read_new_xref_section(ctx, doc, stm, i0, i1, w0, w1, w2); + } + } + entry = pdf_get_populating_xref_entry(ctx, doc, num); + entry->ofs = ofs; + entry->gen = gen; + entry->num = num; + entry->stm_ofs = stm_ofs; + pdf_drop_obj(ctx, entry->obj); + entry->obj = pdf_keep_obj(ctx, trailer); + entry->type = 'n'; + pdf_set_obj_parent(ctx, trailer, num); + } + fz_always(ctx) + { + fz_drop_stream(ctx, stm); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, trailer); + fz_rethrow(ctx); + } + + return trailer; +} + +static pdf_obj * +pdf_read_xref(fz_context *ctx, pdf_document *doc, int64_t ofs) +{ + pdf_obj *trailer; + int c; + + fz_seek(ctx, doc->file, doc->bias + ofs, SEEK_SET); + + while (iswhite(fz_peek_byte(ctx, doc->file))) + fz_read_byte(ctx, doc->file); + + c = fz_peek_byte(ctx, doc->file); + if (c == 'x') + trailer = pdf_read_old_xref(ctx, doc); + else if (isdigit(c)) + trailer = pdf_read_new_xref(ctx, doc); + else + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot recognize xref format"); + + return trailer; +} + +static int64_t +read_xref_section(fz_context *ctx, pdf_document *doc, int64_t ofs) +{ + pdf_obj *trailer = NULL; + pdf_obj *prevobj; + int64_t xrefstmofs = 0; + int64_t prevofs = 0; + + trailer = pdf_read_xref(ctx, doc, ofs); + fz_try(ctx) + { + pdf_set_populating_xref_trailer(ctx, doc, trailer); + + /* FIXME: do we overwrite free entries properly? */ + /* FIXME: Does this work properly with progression? */ + xrefstmofs = pdf_to_int64(ctx, pdf_dict_get(ctx, trailer, PDF_NAME(XRefStm))); + if (xrefstmofs) + { + if (xrefstmofs < 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "negative xref stream offset"); + + /* + Read the XRefStm stream, but throw away the resulting trailer. We do not + follow any Prev tag therein, as specified on Page 108 of the PDF reference + 1.7 + */ + pdf_drop_obj(ctx, pdf_read_xref(ctx, doc, xrefstmofs)); + } + + prevobj = pdf_dict_get(ctx, trailer, PDF_NAME(Prev)); + if (pdf_is_int(ctx, prevobj)) + { + prevofs = pdf_to_int64(ctx, prevobj); + if (prevofs <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "invalid offset for previous xref section"); + } + } + fz_always(ctx) + pdf_drop_obj(ctx, trailer); + fz_catch(ctx) + fz_rethrow(ctx); + + return prevofs; +} + +static void +pdf_read_xref_sections(fz_context *ctx, pdf_document *doc, int64_t ofs, int read_previous) +{ + int i, len, cap; + int64_t *offsets; + int populated = 0; + int size, xref_len; + + len = 0; + cap = 10; + offsets = fz_malloc_array(ctx, cap, int64_t); + + fz_var(populated); + fz_var(offsets); + + fz_try(ctx) + { + while(ofs) + { + for (i = 0; i < len; i ++) + { + if (offsets[i] == ofs) + break; + } + if (i < len) + { + fz_warn(ctx, "ignoring xref section recursion at offset %d", (int)ofs); + break; + } + if (len == cap) + { + cap *= 2; + offsets = fz_realloc_array(ctx, offsets, cap, int64_t); + } + offsets[len++] = ofs; + + pdf_populate_next_xref_level(ctx, doc); + populated = 1; + ofs = read_xref_section(ctx, doc, ofs); + if (!read_previous) + break; + } + + /* For pathological files, such as chinese-example.pdf, where the original + * xref in the file is highly fragmented, we can safely solidify it here + * with no ill effects. */ + ensure_solid_xref(ctx, doc, 0, doc->num_xref_sections-1); + + size = pdf_dict_get_int(ctx, pdf_trailer(ctx, doc), PDF_NAME(Size)); + xref_len = pdf_xref_len(ctx, doc); + if (xref_len > size) + { + if (xref_len == size+1) + { + /* Bug 708456 && Bug 708176. Allow for (sadly, quite common + * PDF generators that can't get size right). */ + fz_warn(ctx, "Trailer Size is off-by-one. Ignoring."); + pdf_dict_put_int(ctx, pdf_trailer(ctx, doc), PDF_NAME(Size), size+1); + } + else + fz_throw(ctx, FZ_ERROR_FORMAT, "incorrect number of xref entries in trailer, repairing"); + } + } + fz_always(ctx) + { + fz_free(ctx, offsets); + } + fz_catch(ctx) + { + /* Undo pdf_populate_next_xref_level if we've done that already. */ + if (populated) + { + pdf_drop_xref_subsec(ctx, &doc->xref_sections[doc->num_xref_sections - 1]); + doc->num_xref_sections--; + } + fz_rethrow(ctx); + } +} + +void +pdf_prime_xref_index(fz_context *ctx, pdf_document *doc) +{ + int i, j; + int *idx = doc->xref_index; + + for (i = doc->num_xref_sections-1; i >= 0; i--) + { + pdf_xref *xref = &doc->xref_sections[i]; + pdf_xref_subsec *subsec = xref->subsec; + while (subsec != NULL) + { + int start = subsec->start; + int end = subsec->start + subsec->len; + for (j = start; j < end; j++) + { + char t = subsec->table[j-start].type; + if (t != 0 && t != 'f') + idx[j] = i; + } + + subsec = subsec->next; + } + } +} + +static void +check_xref_entry_offsets(fz_context *ctx, pdf_xref_entry *entry, int i, pdf_document *doc, void *arg) +{ + int xref_len = (int)(intptr_t)arg; + + if (entry->type == 'n') + { + /* Special case code: "0000000000 * n" means free, + * according to some producers (inc Quartz) */ + if (entry->ofs == 0) + entry->type = 'f'; + else if (entry->ofs <= 0 || entry->ofs >= doc->file_size) + fz_throw(ctx, FZ_ERROR_FORMAT, "object offset out of range: %d (%d 0 R)", (int)entry->ofs, i); + } + else if (entry->type == 'o') + { + /* Read this into a local variable here, because pdf_get_xref_entry + * may solidify the xref, hence invalidating "entry", meaning we + * need a stashed value for the throw. */ + int64_t ofs = entry->ofs; + if (ofs <= 0 || ofs >= xref_len || pdf_get_xref_entry_no_null(ctx, doc, ofs)->type != 'n') + fz_throw(ctx, FZ_ERROR_FORMAT, "invalid reference to an objstm that does not exist: %d (%d 0 R)", (int)ofs, i); + } +} + +/* + * load xref tables from pdf + * + * File locked on entry, throughout and on exit. + */ + +static void +pdf_load_xref(fz_context *ctx, pdf_document *doc) +{ + int xref_len; + pdf_xref_entry *entry; + + pdf_read_start_xref(ctx, doc); + + pdf_read_xref_sections(ctx, doc, doc->startxref, 1); + + if (pdf_xref_len(ctx, doc) == 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "found xref was empty"); + + pdf_prime_xref_index(ctx, doc); + + entry = pdf_get_xref_entry_no_null(ctx, doc, 0); + /* broken pdfs where first object is missing */ + if (!entry->type) + { + entry->type = 'f'; + entry->gen = 65535; + entry->num = 0; + } + /* broken pdfs where first object is not free */ + else if (entry->type != 'f') + fz_warn(ctx, "first object in xref is not free"); + + /* broken pdfs where object offsets are out of range */ + xref_len = pdf_xref_len(ctx, doc); + pdf_xref_entry_map(ctx, doc, check_xref_entry_offsets, (void *)(intptr_t)xref_len); +} + +static void +pdf_check_linear(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *dict = NULL; + pdf_obj *o; + int num, gen; + int64_t stmofs; + + fz_var(dict); + + fz_try(ctx) + { + dict = pdf_parse_ind_obj(ctx, doc, doc->file, &num, &gen, &stmofs, NULL); + if (!pdf_is_dict(ctx, dict)) + break; + o = pdf_dict_get(ctx, dict, PDF_NAME(Linearized)); + if (o == NULL) + break; + if (pdf_to_int(ctx, o) != 1) + break; + doc->has_linearization_object = 1; + } + fz_always(ctx) + pdf_drop_obj(ctx, dict); + fz_catch(ctx) + { + /* Silently swallow this error. */ + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + } +} + +static void +pdf_load_linear(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *dict = NULL; + pdf_obj *hint = NULL; + pdf_obj *o; + int num, gen, lin, len; + int64_t stmofs; + + fz_var(dict); + fz_var(hint); + + fz_try(ctx) + { + pdf_xref_entry *entry; + + dict = pdf_parse_ind_obj(ctx, doc, doc->file, &num, &gen, &stmofs, NULL); + if (!pdf_is_dict(ctx, dict)) + fz_throw(ctx, FZ_ERROR_FORMAT, "Failed to read linearized dictionary"); + o = pdf_dict_get(ctx, dict, PDF_NAME(Linearized)); + if (o == NULL) + fz_throw(ctx, FZ_ERROR_FORMAT, "Failed to read linearized dictionary"); + lin = pdf_to_int(ctx, o); + if (lin != 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "Unexpected version of Linearized tag (%d)", lin); + doc->has_linearization_object = 1; + len = pdf_dict_get_int(ctx, dict, PDF_NAME(L)); + if (len != doc->file_length) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "File has been updated since linearization"); + + pdf_read_xref_sections(ctx, doc, fz_tell(ctx, doc->file), 0); + + doc->linear_page_count = pdf_dict_get_int(ctx, dict, PDF_NAME(N)); + doc->linear_page_refs = fz_realloc_array(ctx, doc->linear_page_refs, doc->linear_page_count, pdf_obj *); + memset(doc->linear_page_refs, 0, doc->linear_page_count * sizeof(pdf_obj*)); + doc->linear_obj = dict; + doc->linear_pos = fz_tell(ctx, doc->file); + doc->linear_page1_obj_num = pdf_dict_get_int(ctx, dict, PDF_NAME(O)); + doc->linear_page_refs[0] = pdf_new_indirect(ctx, doc, doc->linear_page1_obj_num, 0); + doc->linear_page_num = 0; + hint = pdf_dict_get(ctx, dict, PDF_NAME(H)); + doc->hint_object_offset = pdf_array_get_int(ctx, hint, 0); + doc->hint_object_length = pdf_array_get_int(ctx, hint, 1); + + entry = pdf_get_populating_xref_entry(ctx, doc, 0); + entry->type = 'f'; + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, dict); + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + /* Drop back to non linearized reading mode */ + doc->file_reading_linearly = 0; + } +} + +static void +id_and_password(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *encrypt, *id; + + pdf_prime_xref_index(ctx, doc); + + encrypt = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Encrypt)); + id = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(ID)); + + if (pdf_is_dict(ctx, encrypt)) + doc->crypt = pdf_new_crypt(ctx, encrypt, id); + + /* Allow lazy clients to read encrypted files with a blank password */ + (void)pdf_authenticate_password(ctx, doc, ""); +} + +/* + * Initialize and load xref tables. + * If password is not null, try to decrypt. + */ +static void +pdf_init_document(fz_context *ctx, pdf_document *doc) +{ + int repaired = 0; + + fz_try(ctx) + { + /* Check to see if we should work in progressive mode */ + if (doc->file->progressive) + { + doc->file_reading_linearly = 1; + fz_seek(ctx, doc->file, 0, SEEK_END); + doc->file_length = fz_tell(ctx, doc->file); + if (doc->file_length < 0) + doc->file_length = 0; + fz_seek(ctx, doc->file, 0, SEEK_SET); + } + + pdf_load_version(ctx, doc); + + if (doc->is_fdf) + { + doc->file_reading_linearly = 0; + repaired = 1; + break; /* skip to end of try/catch */ + } + + /* Try to load the linearized file if we are in progressive + * mode. */ + if (doc->file_reading_linearly) + pdf_load_linear(ctx, doc); + else + /* Even if we're not in progressive mode, check to see + * if the file claims to be linearized. This is important + * for checking signatures later on. */ + pdf_check_linear(ctx, doc); + + /* If we aren't in progressive mode (or the linear load failed + * and has set us back to non-progressive mode), load normally. + */ + if (!doc->file_reading_linearly) + pdf_load_xref(ctx, doc); + } + fz_catch(ctx) + { + pdf_drop_xref_sections(ctx, doc); + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + doc->file_reading_linearly = 0; + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + fz_warn(ctx, "trying to repair broken xref"); + repaired = 1; + } + + if (repaired) + { + /* pdf_repair_xref may access xref_index, so reset it properly */ + if (doc->xref_index) + memset(doc->xref_index, 0, sizeof(int) * doc->max_xref_len); + pdf_repair_xref_aux(ctx, doc, id_and_password); + } + else + id_and_password(ctx, doc); +} + +void +pdf_invalidate_xfa(fz_context *ctx, pdf_document *doc) +{ + if (doc == NULL) + return; + fz_drop_xml(ctx, doc->xfa); + doc->xfa = NULL; +} + +static void +pdf_drop_document_imp(fz_context *ctx, fz_document *doc_) +{ + pdf_document *doc = (pdf_document*)doc_; + int i; + + fz_defer_reap_start(ctx); + + /* Type3 glyphs in the glyph cache can contain pdf_obj pointers + * that we are about to destroy. Simplest solution is to bin the + * glyph cache at this point. */ + fz_try(ctx) + fz_purge_glyph_cache(ctx); + fz_catch(ctx) + { + /* Swallow error, but continue dropping */ + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + } + + pdf_set_doc_event_callback(ctx, doc, NULL, NULL, NULL); + pdf_drop_js(ctx, doc->js); + + pdf_drop_journal(ctx, doc->journal); + + pdf_drop_resource_tables(ctx, doc); + + pdf_drop_local_xref(ctx, doc->local_xref); + + pdf_drop_xref_sections(ctx, doc); + fz_free(ctx, doc->xref_index); + + fz_drop_stream(ctx, doc->file); + pdf_drop_crypt(ctx, doc->crypt); + + pdf_drop_obj(ctx, doc->linear_obj); + if (doc->linear_page_refs) + { + for (i=0; i < doc->linear_page_count; i++) + pdf_drop_obj(ctx, doc->linear_page_refs[i]); + + fz_free(ctx, doc->linear_page_refs); + } + + fz_free(ctx, doc->hint_page); + fz_free(ctx, doc->hint_shared_ref); + fz_free(ctx, doc->hint_shared); + fz_free(ctx, doc->hint_obj_offsets); + + for (i=0; i < doc->num_type3_fonts; i++) + { + fz_try(ctx) + fz_decouple_type3_font(ctx, doc->type3_fonts[i], (void *)doc); + fz_always(ctx) + fz_drop_font(ctx, doc->type3_fonts[i]); + fz_catch(ctx) + { + /* Swallow error, but continue dropping */ + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + } + } + + fz_free(ctx, doc->type3_fonts); + + pdf_drop_ocg(ctx, doc); + + pdf_empty_store(ctx, doc); + + pdf_lexbuf_fin(ctx, &doc->lexbuf.base); + + fz_drop_colorspace(ctx, doc->oi); + + for (i = 0; i < doc->orphans_count; i++) + pdf_drop_obj(ctx, doc->orphans[i]); + + fz_free(ctx, doc->orphans); + + pdf_drop_page_tree_internal(ctx, doc); + + fz_defer_reap_end(ctx); + + pdf_invalidate_xfa(ctx, doc); +} + +void +pdf_drop_document(fz_context *ctx, pdf_document *doc) +{ + fz_drop_document(ctx, &doc->super); +} + +pdf_document * +pdf_keep_document(fz_context *ctx, pdf_document *doc) +{ + return (pdf_document *)fz_keep_document(ctx, &doc->super); +} + +/* + * compressed object streams + */ + +/* + Do not hold pdf_xref_entry's over call to this function as they + may be invalidated! +*/ +static pdf_xref_entry * +pdf_load_obj_stm(fz_context *ctx, pdf_document *doc, int num, pdf_lexbuf *buf, int target) +{ + fz_stream *stm = NULL; + pdf_obj *objstm = NULL; + int *numbuf = NULL; + int64_t *ofsbuf = NULL; + + pdf_obj *obj; + int64_t first; + int count; + int i; + pdf_token tok; + pdf_xref_entry *ret_entry = NULL; + int ret_idx; + int xref_len; + int found; + fz_stream *sub = NULL; + + fz_var(numbuf); + fz_var(ofsbuf); + fz_var(objstm); + fz_var(stm); + fz_var(sub); + + fz_try(ctx) + { + objstm = pdf_load_object(ctx, doc, num); + + if (pdf_obj_marked(ctx, objstm)) + fz_throw(ctx, FZ_ERROR_FORMAT, "recursive object stream lookup"); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, objstm); + fz_rethrow(ctx); + } + + fz_try(ctx) + { + (void)pdf_mark_obj(ctx, objstm); + + count = pdf_dict_get_int(ctx, objstm, PDF_NAME(N)); + first = pdf_dict_get_int(ctx, objstm, PDF_NAME(First)); + + if (count < 0 || count > PDF_MAX_OBJECT_NUMBER) + fz_throw(ctx, FZ_ERROR_FORMAT, "number of objects in object stream out of range"); + + numbuf = fz_calloc(ctx, count, sizeof(*numbuf)); + ofsbuf = fz_calloc(ctx, count, sizeof(*ofsbuf)); + + xref_len = pdf_xref_len(ctx, doc); + + found = 0; + + stm = pdf_open_stream_number(ctx, doc, num); + for (i = 0; i < count; i++) + { + tok = pdf_lex(ctx, stm, buf); + if (tok != PDF_TOK_INT) + fz_throw(ctx, FZ_ERROR_FORMAT, "corrupt object stream (%d 0 R)", num); + numbuf[found] = buf->i; + + tok = pdf_lex(ctx, stm, buf); + if (tok != PDF_TOK_INT) + fz_throw(ctx, FZ_ERROR_FORMAT, "corrupt object stream (%d 0 R)", num); + ofsbuf[found] = buf->i; + + if (numbuf[found] <= 0 || numbuf[found] >= xref_len) + fz_warn(ctx, "object stream object out of range, skipping"); + else + found++; + } + + ret_idx = -1; + for (i = 0; i < found; i++) + { + pdf_xref_entry *entry; + uint64_t length; + int64_t offset; + + offset = first + ofsbuf[i]; + if (i+1 < found) + length = ofsbuf[i+1] - ofsbuf[i]; + else + length = UINT64_MAX; + + sub = fz_open_null_filter(ctx, stm, length, offset); + + obj = pdf_parse_stm_obj(ctx, doc, sub, buf); + fz_drop_stream(ctx, sub); + sub = NULL; + + entry = pdf_get_xref_entry_no_null(ctx, doc, numbuf[i]); + + pdf_set_obj_parent(ctx, obj, numbuf[i]); + + /* We may have set entry->type to be 'O' from being 'o' to avoid nasty + * recursions in pdf_cache_object. Accept the type being 'O' here. */ + if ((entry->type == 'o' || entry->type == 'O') && entry->ofs == num) + { + /* If we already have an entry for this object, + * we'd like to drop it and use the new one - + * but this means that anyone currently holding + * a pointer to the old one will be left with a + * stale pointer. Instead, we drop the new one + * and trust that the old one is correct. */ + if (entry->obj) + { + if (pdf_objcmp(ctx, entry->obj, obj)) + fz_warn(ctx, "Encountered new definition for object %d - keeping the original one", numbuf[i]); + pdf_drop_obj(ctx, obj); + } + else + { + entry->obj = obj; + /* If we've just read a 'null' object, don't leave this as a NULL 'o' object, + * as that will a) confuse the code that called us into thinking that nothing + * was loaded, and b) cause the entire objstm to be reloaded every time that + * object is accessed. Instead, just mark it as an 'f'. */ + if (obj == NULL) + entry->type = 'f'; + fz_drop_buffer(ctx, entry->stm_buf); + entry->stm_buf = NULL; + } + if (numbuf[i] == target) + ret_idx = i; + } + else + { + pdf_drop_obj(ctx, obj); + } + } + /* Parsing our way through the stream can cause the xref to be + * solidified, which will move an entry. We therefore can't + * read the entry for returning until no more parsing is to be + * done. Thus we end up reading this entry twice. */ + if (ret_idx >= 0) + ret_entry = pdf_get_xref_entry_no_null(ctx, doc, numbuf[ret_idx]); + } + fz_always(ctx) + { + fz_drop_stream(ctx, stm); + fz_drop_stream(ctx, sub); + fz_free(ctx, ofsbuf); + fz_free(ctx, numbuf); + pdf_unmark_obj(ctx, objstm); + pdf_drop_obj(ctx, objstm); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + return ret_entry; +} + +/* + * object loading + */ +static int +pdf_obj_read(fz_context *ctx, pdf_document *doc, int64_t *offset, int *nump, pdf_obj **page) +{ + pdf_lexbuf *buf = &doc->lexbuf.base; + int num, gen, tok; + int64_t numofs, genofs, stmofs, tmpofs, newtmpofs; + int xref_len; + pdf_xref_entry *entry; + + numofs = *offset; + fz_seek(ctx, doc->file, doc->bias + numofs, SEEK_SET); + + /* We expect to read 'num' here */ + tok = pdf_lex(ctx, doc->file, buf); + genofs = fz_tell(ctx, doc->file); + if (tok != PDF_TOK_INT) + { + /* Failed! */ + DEBUGMESS((ctx, "skipping unexpected data (tok=%d) at %d", tok, *offset)); + *offset = genofs; + return tok == PDF_TOK_EOF; + } + *nump = num = buf->i; + + /* We expect to read 'gen' here */ + tok = pdf_lex(ctx, doc->file, buf); + tmpofs = fz_tell(ctx, doc->file); + if (tok != PDF_TOK_INT) + { + /* Failed! */ + DEBUGMESS((ctx, "skipping unexpected data after \"%d\" (tok=%d) at %d", num, tok, *offset)); + *offset = tmpofs; + return tok == PDF_TOK_EOF; + } + gen = buf->i; + + /* We expect to read 'obj' here */ + do + { + tmpofs = fz_tell(ctx, doc->file); + tok = pdf_lex(ctx, doc->file, buf); + if (tok == PDF_TOK_OBJ) + break; + if (tok != PDF_TOK_INT) + { + DEBUGMESS((ctx, "skipping unexpected data (tok=%d) at %d", tok, tmpofs)); + *offset = fz_tell(ctx, doc->file); + return tok == PDF_TOK_EOF; + } + DEBUGMESS((ctx, "skipping unexpected int %d at %d", num, numofs)); + *nump = num = gen; + numofs = genofs; + gen = buf->i; + genofs = tmpofs; + } + while (1); + + /* Now we read the actual object */ + xref_len = pdf_xref_len(ctx, doc); + + /* When we are reading a progressive file, we typically see: + * File Header + * obj m (Linearization params) + * xref #1 (refers to objects m-n) + * obj m+1 + * ... + * obj n + * obj 1 + * ... + * obj n-1 + * xref #2 + * + * The linearisation params are read elsewhere, hence + * whenever we read an object it should just go into the + * previous xref. + */ + tok = pdf_repair_obj(ctx, doc, buf, &stmofs, NULL, NULL, NULL, page, &newtmpofs, NULL); + + do /* So we can break out of it */ + { + if (num <= 0 || num >= xref_len) + { + fz_warn(ctx, "Not a valid object number (%d %d obj)", num, gen); + break; + } + if (gen != 0) + { + fz_warn(ctx, "Unexpected non zero generation number in linearized file"); + } + entry = pdf_get_populating_xref_entry(ctx, doc, num); + if (entry->type != 0) + { + DEBUGMESS((ctx, "Duplicate object found (%d %d obj)", num, gen)); + break; + } + if (page && *page) + { + DEBUGMESS((ctx, "Successfully read object %d @ %d - and found page %d!", num, numofs, doc->linear_page_num)); + if (!entry->obj) + entry->obj = pdf_keep_obj(ctx, *page); + + if (doc->linear_page_refs[doc->linear_page_num] == NULL) + doc->linear_page_refs[doc->linear_page_num] = pdf_new_indirect(ctx, doc, num, gen); + } + else + { + DEBUGMESS((ctx, "Successfully read object %d @ %d", num, numofs)); + } + entry->type = 'n'; + entry->gen = gen; // XXX: was 0 + entry->num = num; + entry->ofs = numofs; + entry->stm_ofs = stmofs; + } + while (0); + if (page && *page) + doc->linear_page_num++; + + if (tok == PDF_TOK_ENDOBJ) + { + *offset = fz_tell(ctx, doc->file); + } + else + { + *offset = newtmpofs; + } + return 0; +} + +static void +pdf_load_hinted_page(fz_context *ctx, pdf_document *doc, int pagenum) +{ + pdf_obj *page = NULL; + + if (!doc->hints_loaded || !doc->linear_page_refs) + return; + + if (doc->linear_page_refs[pagenum]) + return; + + fz_var(page); + + fz_try(ctx) + { + int num = doc->hint_page[pagenum].number; + page = pdf_load_object(ctx, doc, num); + if (pdf_name_eq(ctx, PDF_NAME(Page), pdf_dict_get(ctx, page, PDF_NAME(Type)))) + { + /* We have found the page object! */ + DEBUGMESS((ctx, "LoadHintedPage pagenum=%d num=%d", pagenum, num)); + doc->linear_page_refs[pagenum] = pdf_new_indirect(ctx, doc, num, 0); + } + } + fz_always(ctx) + pdf_drop_obj(ctx, page); + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + /* Swallow the error and proceed as normal */ + fz_report_error(ctx); + } +} + +static int +read_hinted_object(fz_context *ctx, pdf_document *doc, int num) +{ + /* Try to find the object using our hint table. Find the closest + * object <= the one we want that has a hint and read forward from + * there. */ + int expected = num; + int curr_pos; + int64_t start, offset; + + while (doc->hint_obj_offsets[expected] == 0 && expected > 0) + expected--; + if (expected != num) + DEBUGMESS((ctx, "object %d is unhinted, will search forward from %d", expected, num)); + if (expected == 0) /* No hints found, just bail */ + return 0; + + curr_pos = fz_tell(ctx, doc->file); + offset = doc->hint_obj_offsets[expected]; + + fz_var(expected); + + fz_try(ctx) + { + int found; + + /* Try to read forward from there */ + do + { + start = offset; + DEBUGMESS((ctx, "Searching for object %d @ %d", expected, offset)); + pdf_obj_read(ctx, doc, &offset, &found, 0); + DEBUGMESS((ctx, "Found object %d - next will be @ %d", found, offset)); + if (found <= expected) + { + /* We found the right one (or one earlier than + * we expected). Update the hints. */ + doc->hint_obj_offsets[expected] = offset; + doc->hint_obj_offsets[found] = start; + doc->hint_obj_offsets[found+1] = offset; + /* Retry with the next one */ + expected = found+1; + } + else + { + /* We found one later than we expected. */ + doc->hint_obj_offsets[expected] = 0; + doc->hint_obj_offsets[found] = start; + doc->hint_obj_offsets[found+1] = offset; + while (doc->hint_obj_offsets[expected] == 0 && expected > 0) + expected--; + if (expected == 0) /* No hints found, we give up */ + break; + } + } + while (found != num); + } + fz_always(ctx) + { + fz_seek(ctx, doc->file, curr_pos, SEEK_SET); + } + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + /* FIXME: Currently we ignore the hint. Perhaps we should + * drop back to non-hinted operation here. */ + doc->hint_obj_offsets[expected] = 0; + fz_rethrow(ctx); + } + return expected != 0; +} + +pdf_obj * +pdf_load_unencrypted_object(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref_entry *x; + + if (num <= 0 || num >= pdf_xref_len(ctx, doc)) + fz_throw(ctx, FZ_ERROR_FORMAT, "object out of range (%d 0 R); xref size %d", num, pdf_xref_len(ctx, doc)); + + x = pdf_get_xref_entry_no_null(ctx, doc, num); + if (x->type == 'n') + { + fz_seek(ctx, doc->file, doc->bias + x->ofs, SEEK_SET); + return pdf_parse_ind_obj(ctx, doc, doc->file, NULL, NULL, NULL, NULL); + } + return NULL; +} + +int +pdf_object_exists(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref_entry *x; + if (num <= 0 || num >= pdf_xref_len(ctx, doc)) + return 0; + x = pdf_get_xref_entry(ctx, doc, num); + if (x && (x->type == 'n' || x->type == 'o')) + return 1; + return 0; +} + +pdf_xref_entry * +pdf_cache_object(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref_entry *x; + int rnum, rgen, try_repair; + + fz_var(try_repair); + + if (num <= 0 || num >= pdf_xref_len(ctx, doc)) + fz_throw(ctx, FZ_ERROR_FORMAT, "object out of range (%d 0 R); xref size %d", num, pdf_xref_len(ctx, doc)); + +object_updated: + try_repair = 0; + rnum = num; + + x = pdf_get_xref_entry(ctx, doc, num); + if (x == NULL) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot find object in xref (%d 0 R)", num); + + if (x->obj != NULL) + return x; + + if (x->type == 'f') + { + x->obj = PDF_NULL; + } + else if (x->type == 'n') + { + fz_seek(ctx, doc->file, doc->bias + x->ofs, SEEK_SET); + + fz_try(ctx) + { + x->obj = pdf_parse_ind_obj(ctx, doc, doc->file, + &rnum, &rgen, &x->stm_ofs, &try_repair); + } + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + if (!try_repair) + fz_rethrow(ctx); + else + fz_report_error(ctx); + } + + if (!try_repair && rnum != num) + { + pdf_drop_obj(ctx, x->obj); + x->type = 'f'; + x->ofs = -1; + x->gen = 0; + x->num = 0; + x->stm_ofs = 0; + x->obj = NULL; + try_repair = (doc->repair_attempted == 0); + } + + if (try_repair) + { +perform_repair: + fz_try(ctx) + pdf_repair_xref(ctx, doc); + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_rethrow_if(ctx, FZ_ERROR_REPAIRED); + fz_report_error(ctx); + if (rnum == num) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse object (%d 0 R)", num); + else + fz_throw(ctx, FZ_ERROR_FORMAT, "found object (%d 0 R) instead of (%d 0 R)", rnum, num); + } + goto object_updated; + } + + if (doc->crypt) + pdf_crypt_obj(ctx, doc->crypt, x->obj, x->num, x->gen); + } + else if (x->type == 'o') + { + if (!x->obj) + { + pdf_xref_entry *orig_x = x; + pdf_xref_entry *ox = x; /* This init is unused, but it shuts warnings up. */ + orig_x->type = 'O'; /* Mark this node so we know we're recursing. */ + fz_try(ctx) + x = pdf_load_obj_stm(ctx, doc, x->ofs, &doc->lexbuf.base, num); + fz_always(ctx) + { + /* Most of the time ox == orig_x, but if pdf_load_obj_stm performed a + * repair, it may not be. It is safe to call pdf_get_xref_entry_no_change + * here, as it does not try/catch. */ + ox = pdf_get_xref_entry_no_change(ctx, doc, num); + /* Bug 706762: ox can be NULL if the object went away during a repair. */ + if (ox && ox->type == 'O') + ox->type = 'o'; /* Not recursing any more. */ + } + fz_catch(ctx) + fz_rethrow(ctx); + if (x == NULL) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot load object stream containing object (%d 0 R)", num); + if (!x->obj) + { + x->type = 'f'; + if (ox) + ox->type = 'f'; + if (doc->repair_attempted) + fz_throw(ctx, FZ_ERROR_FORMAT, "object (%d 0 R) was not found in its object stream", num); + goto perform_repair; + } + } + } + else if (doc->hint_obj_offsets && read_hinted_object(ctx, doc, num)) + { + goto object_updated; + } + else if (doc->file_length && doc->linear_pos < doc->file_length) + { + fz_throw(ctx, FZ_ERROR_TRYLATER, "cannot find object in xref (%d 0 R) - not loaded yet?", num); + } + else + { + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot find object in xref (%d 0 R)", num); + } + + pdf_set_obj_parent(ctx, x->obj, num); + return x; +} + +pdf_obj * +pdf_load_object(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref_entry *entry = pdf_cache_object(ctx, doc, num); + return pdf_keep_obj(ctx, entry->obj); +} + +pdf_obj * +pdf_resolve_indirect(fz_context *ctx, pdf_obj *ref) +{ + if (pdf_is_indirect(ctx, ref)) + { + pdf_document *doc = pdf_get_indirect_document(ctx, ref); + int num = pdf_to_num(ctx, ref); + pdf_xref_entry *entry; + + if (!doc) + return NULL; + if (num <= 0) + { + fz_warn(ctx, "invalid indirect reference (%d 0 R)", num); + return NULL; + } + + fz_try(ctx) + entry = pdf_cache_object(ctx, doc, num); + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_rethrow_if(ctx, FZ_ERROR_REPAIRED); + fz_report_error(ctx); + fz_warn(ctx, "cannot load object (%d 0 R) into cache", num); + return NULL; + } + + ref = entry->obj; + } + return ref; +} + +pdf_obj * +pdf_resolve_indirect_chain(fz_context *ctx, pdf_obj *ref) +{ + int sanity = 10; + + while (pdf_is_indirect(ctx, ref)) + { + if (--sanity == 0) + { + fz_warn(ctx, "too many indirections (possible indirection cycle involving %d 0 R)", pdf_to_num(ctx, ref)); + return NULL; + } + + ref = pdf_resolve_indirect(ctx, ref); + } + + return ref; +} + +int +pdf_count_objects(fz_context *ctx, pdf_document *doc) +{ + return pdf_xref_len(ctx, doc); +} + +int +pdf_is_local_object(fz_context *ctx, pdf_document *doc, pdf_obj *obj) +{ + pdf_xref *xref = doc->local_xref; + pdf_xref_subsec *sub; + int num; + + if (!pdf_is_indirect(ctx, obj)) + return 0; + + if (xref == NULL) + return 0; /* no local xref present */ + + num = pdf_to_num(ctx, obj); + + /* Local xrefs only ever have 1 section, and it should be solid. */ + sub = xref->subsec; + if (num >= sub->start && num < sub->start + sub->len) + return sub->table[num - sub->start].type != 0; + + return 0; +} + +static int +pdf_create_local_object(fz_context *ctx, pdf_document *doc) +{ + /* TODO: reuse free object slots by properly linking free object chains in the ofs field */ + pdf_xref_entry *entry; + int num; + + num = doc->local_xref->num_objects; + + entry = pdf_get_local_xref_entry(ctx, doc, num); + entry->type = 'f'; + entry->ofs = -1; + entry->gen = 0; + entry->num = num; + entry->stm_ofs = 0; + entry->stm_buf = NULL; + entry->obj = NULL; + return num; +} + +int +pdf_create_object(fz_context *ctx, pdf_document *doc) +{ + /* TODO: reuse free object slots by properly linking free object chains in the ofs field */ + pdf_xref_entry *entry; + int num; + + if (doc->local_xref && doc->local_xref_nesting > 0) + return pdf_create_local_object(ctx, doc); + + num = pdf_xref_len(ctx, doc); + + if (num > PDF_MAX_OBJECT_NUMBER) + fz_throw(ctx, FZ_ERROR_LIMIT, "too many objects stored in pdf"); + + entry = pdf_get_incremental_xref_entry(ctx, doc, num); + entry->type = 'f'; + entry->ofs = -1; + entry->gen = 0; + entry->num = num; + entry->stm_ofs = 0; + entry->stm_buf = NULL; + entry->obj = NULL; + + pdf_add_journal_fragment(ctx, doc, num, NULL, NULL, 1); + + return num; +} + +static void +pdf_delete_local_object(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref_entry *x; + + if (doc->local_xref == NULL || doc->local_xref_nesting == 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "No local xref to delete from!"); + + if (num <= 0 || num >= doc->local_xref->num_objects) + { + fz_warn(ctx, "local object out of range (%d 0 R); xref size %d", num, doc->local_xref->num_objects); + return; + } + + x = pdf_get_local_xref_entry(ctx, doc, num); + + fz_drop_buffer(ctx, x->stm_buf); + pdf_drop_obj(ctx, x->obj); + + x->type = 'f'; + x->ofs = 0; + x->gen += 1; + x->num = 0; + x->stm_ofs = 0; + x->stm_buf = NULL; + x->obj = NULL; +} + +void +pdf_delete_object(fz_context *ctx, pdf_document *doc, int num) +{ + pdf_xref_entry *x; + pdf_xref *xref; + int j; + + if (doc->local_xref && doc->local_xref_nesting > 0) + { + pdf_delete_local_object(ctx, doc, num); + return; + } + + if (num <= 0 || num >= pdf_xref_len(ctx, doc)) + { + fz_warn(ctx, "object out of range (%d 0 R); xref size %d", num, pdf_xref_len(ctx, doc)); + return; + } + + x = pdf_get_incremental_xref_entry(ctx, doc, num); + + fz_drop_buffer(ctx, x->stm_buf); + pdf_drop_obj(ctx, x->obj); + + x->type = 'f'; + x->ofs = 0; + x->gen += 1; + x->num = 0; + x->stm_ofs = 0; + x->stm_buf = NULL; + x->obj = NULL; + + /* Currently we've left a 'free' object in the incremental + * section. This is enough to cause us to think that the + * document has changes. Check back in the non-incremental + * sections to see if the last instance of the object there + * was free (or if this object never appeared). If so, we + * can mark this object as non-existent in the incremental + * xref. This is important so we can 'undo' back to emptiness + * after we save/when we reload a snapshot. */ + for (j = 1; j < doc->num_xref_sections; j++) + { + xref = &doc->xref_sections[j]; + + if (num < xref->num_objects) + { + pdf_xref_subsec *sub; + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + pdf_xref_entry *entry; + + if (num < sub->start || num >= sub->start + sub->len) + continue; + + entry = &sub->table[num - sub->start]; + if (entry->type) + { + if (entry->type == 'f') + { + /* It was free already! */ + x->type = 0; + x->gen = 0; + } + /* It was a real object. */ + return; + } + } + } + } + /* It never appeared before. */ + x->type = 0; + x->gen = 0; +} + +static void +pdf_update_local_object(fz_context *ctx, pdf_document *doc, int num, pdf_obj *newobj) +{ + pdf_xref_entry *x; + + if (doc->local_xref == NULL || doc->local_xref_nesting == 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Can't update local object without a local xref"); + + if (!newobj) + { + pdf_delete_local_object(ctx, doc, num); + return; + } + + x = pdf_get_local_xref_entry(ctx, doc, num); + + pdf_drop_obj(ctx, x->obj); + + x->type = 'n'; + x->ofs = 0; + x->obj = pdf_keep_obj(ctx, newobj); + + pdf_set_obj_parent(ctx, newobj, num); +} + +void +pdf_update_object(fz_context *ctx, pdf_document *doc, int num, pdf_obj *newobj) +{ + pdf_xref_entry *x; + + if (!doc) + return; + + if (doc->local_xref && doc->local_xref_nesting > 0) + { + pdf_update_local_object(ctx, doc, num, newobj); + return; + } + + if (num <= 0 || num >= pdf_xref_len(ctx, doc)) + { + fz_warn(ctx, "object out of range (%d 0 R); xref size %d", num, pdf_xref_len(ctx, doc)); + return; + } + + if (!newobj) + { + pdf_delete_object(ctx, doc, num); + return; + } + + x = pdf_get_incremental_xref_entry(ctx, doc, num); + + pdf_drop_obj(ctx, x->obj); + + x->type = 'n'; + x->ofs = 0; + x->obj = pdf_keep_obj(ctx, newobj); + + pdf_set_obj_parent(ctx, newobj, num); +} + +void +pdf_update_stream(fz_context *ctx, pdf_document *doc, pdf_obj *obj, fz_buffer *newbuf, int compressed) +{ + int num; + pdf_xref_entry *x; + + if (pdf_is_indirect(ctx, obj)) + num = pdf_to_num(ctx, obj); + else + num = pdf_obj_parent_num(ctx, obj); + + /* Write the Length first, as this has the effect of moving the + * old object into the journal for undo. This also moves the + * stream buffer with it, keeping it consistent. */ + pdf_dict_put_int(ctx, obj, PDF_NAME(Length), fz_buffer_storage(ctx, newbuf, NULL)); + + if (doc->local_xref && doc->local_xref_nesting > 0) + { + x = pdf_get_local_xref_entry(ctx, doc, num); + } + else + { + if (num <= 0 || num >= pdf_xref_len(ctx, doc)) + { + fz_warn(ctx, "object out of range (%d 0 R); xref size %d", num, pdf_xref_len(ctx, doc)); + return; + } + + x = pdf_get_xref_entry_no_null(ctx, doc, num); + } + + fz_drop_buffer(ctx, x->stm_buf); + x->stm_buf = fz_keep_buffer(ctx, newbuf); + + if (!compressed) + { + pdf_dict_del(ctx, obj, PDF_NAME(Filter)); + pdf_dict_del(ctx, obj, PDF_NAME(DecodeParms)); + } +} + +int +pdf_lookup_metadata(fz_context *ctx, pdf_document *doc, const char *key, char *buf, size_t size) +{ + if (!strcmp(key, FZ_META_FORMAT)) + { + int version = pdf_version(ctx, doc); + return 1 + (int)fz_snprintf(buf, size, "PDF %d.%d", version/10, version % 10); + } + + if (!strcmp(key, FZ_META_ENCRYPTION)) + { + if (doc->crypt) + { + const char *stream_method = pdf_crypt_stream_method(ctx, doc->crypt); + const char *string_method = pdf_crypt_string_method(ctx, doc->crypt); + if (stream_method == string_method) + return 1 + (int)fz_snprintf(buf, size, "Standard V%d R%d %d-bit %s", + pdf_crypt_version(ctx, doc->crypt), + pdf_crypt_revision(ctx, doc->crypt), + pdf_crypt_length(ctx, doc->crypt), + pdf_crypt_string_method(ctx, doc->crypt)); + else + return 1 + (int)fz_snprintf(buf, size, "Standard V%d R%d %d-bit streams: %s strings: %s", + pdf_crypt_version(ctx, doc->crypt), + pdf_crypt_revision(ctx, doc->crypt), + pdf_crypt_length(ctx, doc->crypt), + pdf_crypt_stream_method(ctx, doc->crypt), + pdf_crypt_string_method(ctx, doc->crypt)); + } + else + return 1 + (int)fz_strlcpy(buf, "None", size); + } + + if (strstr(key, "info:") == key) + { + pdf_obj *info; + const char *s; + int n; + + info = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Info)); + if (!info) + return -1; + + info = pdf_dict_gets(ctx, info, key + 5); + if (!info) + return -1; + + s = pdf_to_text_string(ctx, info); + if (strlen(s) <= 0) + return -1; + + n = 1 + (int)fz_strlcpy(buf, s, size); + return n; + } + + return -1; +} + +void +pdf_set_metadata(fz_context *ctx, pdf_document *doc, const char *key, const char *value) +{ + + pdf_obj *info = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Info)); + + pdf_begin_operation(ctx, doc, "Set Metadata"); + + fz_try(ctx) + { + /* Ensure we have an Info dictionary. */ + if (!pdf_is_dict(ctx, info)) + { + info = pdf_add_new_dict(ctx, doc, 8); + pdf_dict_put_drop(ctx, pdf_trailer(ctx, doc), PDF_NAME(Info), info); + } + + if (!strcmp(key, FZ_META_INFO_TITLE)) + pdf_dict_put_text_string(ctx, info, PDF_NAME(Title), value); + else if (!strcmp(key, FZ_META_INFO_AUTHOR)) + pdf_dict_put_text_string(ctx, info, PDF_NAME(Author), value); + else if (!strcmp(key, FZ_META_INFO_SUBJECT)) + pdf_dict_put_text_string(ctx, info, PDF_NAME(Subject), value); + else if (!strcmp(key, FZ_META_INFO_KEYWORDS)) + pdf_dict_put_text_string(ctx, info, PDF_NAME(Keywords), value); + else if (!strcmp(key, FZ_META_INFO_CREATOR)) + pdf_dict_put_text_string(ctx, info, PDF_NAME(Creator), value); + else if (!strcmp(key, FZ_META_INFO_PRODUCER)) + pdf_dict_put_text_string(ctx, info, PDF_NAME(Producer), value); + else if (!strcmp(key, FZ_META_INFO_CREATIONDATE)) + { + int64_t time = pdf_parse_date(ctx, value); + if (time >= 0) + pdf_dict_put_date(ctx, info, PDF_NAME(CreationDate), time); + } + else if (!strcmp(key, FZ_META_INFO_MODIFICATIONDATE)) + { + int64_t time = pdf_parse_date(ctx, value); + if (time >= 0) + pdf_dict_put_date(ctx, info, PDF_NAME(ModDate), time); + } + + if (!strncmp(key, FZ_META_INFO, strlen(FZ_META_INFO))) + key += strlen(FZ_META_INFO); + pdf_dict_put_text_string(ctx, info, pdf_new_name(ctx, key), value); + pdf_end_operation(ctx, doc); + } + fz_catch(ctx) + { + pdf_abandon_operation(ctx, doc); + fz_rethrow(ctx); + } +} + +static fz_link_dest +pdf_resolve_link_imp(fz_context *ctx, fz_document *doc_, const char *uri) +{ + pdf_document *doc = (pdf_document*)doc_; + return pdf_resolve_link_dest(ctx, doc, uri); +} + +char *pdf_format_link_uri(fz_context *ctx, fz_document *doc, fz_link_dest dest) +{ + return pdf_new_uri_from_explicit_dest(ctx, dest); +} + +static fz_document * +as_pdf(fz_context *ctx, fz_document *doc) +{ + return doc; +} + +/* + Initializers for the fz_document interface. + + The functions are split across two files to allow calls to a + version of the constructor that does not link in the interpreter. + The interpreter references the built-in font and cmap resources + which are quite big. Not linking those into the mutool binary + saves roughly 6MB of space. +*/ + +static fz_colorspace *pdf_document_output_intent_imp(fz_context *ctx, fz_document *doc) +{ + return pdf_document_output_intent(ctx, (pdf_document*)doc); +} + +int pdf_needs_password_imp(fz_context *ctx, fz_document *doc) +{ + return pdf_needs_password(ctx, (pdf_document*)doc); +} + +int pdf_authenticate_password_imp(fz_context *ctx, fz_document *doc, const char *pw) +{ + return pdf_authenticate_password(ctx, (pdf_document*)doc, pw); +} + +int pdf_has_permission_imp(fz_context *ctx, fz_document *doc, fz_permission p) +{ + return pdf_has_permission(ctx, (pdf_document*)doc, p); +} + +fz_outline_iterator *pdf_new_outline_iterator_imp(fz_context *ctx, fz_document *doc) +{ + return pdf_new_outline_iterator(ctx, (pdf_document*)doc); +} + +int pdf_lookup_metadata_imp(fz_context *ctx, fz_document *doc, const char *key, char *ptr, size_t size) +{ + return pdf_lookup_metadata(ctx, (pdf_document*)doc, key, ptr, size); +} + +void pdf_set_metadata_imp(fz_context *ctx, fz_document *doc, const char *key, const char *value) +{ + pdf_set_metadata(ctx, (pdf_document*)doc, key, value); +} + +void pdf_run_document_structure_imp(fz_context *ctx, fz_document *doc, fz_device *dev, fz_cookie *cookie) +{ + pdf_run_document_structure(ctx, (pdf_document*)doc, dev, cookie); +} + +#ifndef NDEBUG +void pdf_verify_name_table_sanity(void); +#endif + + +static pdf_document * +pdf_new_document(fz_context *ctx, fz_stream *file) +{ + pdf_document *doc = fz_new_derived_document(ctx, pdf_document); + +#ifndef NDEBUG + pdf_verify_name_table_sanity(); +#endif + + doc->super.drop_document = pdf_drop_document_imp; + doc->super.get_output_intent = pdf_document_output_intent_imp; + doc->super.needs_password = pdf_needs_password_imp; + doc->super.authenticate_password = pdf_authenticate_password_imp; + doc->super.has_permission = pdf_has_permission_imp; + doc->super.outline_iterator = pdf_new_outline_iterator_imp; + doc->super.resolve_link_dest = pdf_resolve_link_imp; + doc->super.format_link_uri = pdf_format_link_uri; + doc->super.count_pages = pdf_count_pages_imp; + doc->super.load_page = pdf_load_page_imp; + doc->super.page_label = pdf_page_label_imp; + doc->super.lookup_metadata = pdf_lookup_metadata_imp; + doc->super.set_metadata = pdf_set_metadata_imp; + doc->super.run_structure = pdf_run_document_structure_imp; + doc->super.as_pdf = as_pdf; + + pdf_lexbuf_init(ctx, &doc->lexbuf.base, PDF_LEXBUF_LARGE); + doc->file = fz_keep_stream(ctx, file); + + /* Default to PDF-1.7 if the version header is missing and for new documents */ + doc->version = 17; + + return doc; +} + +pdf_document * +pdf_open_document_with_stream(fz_context *ctx, fz_stream *file) +{ + pdf_document *doc = pdf_new_document(ctx, file); + fz_try(ctx) + { + pdf_init_document(ctx, doc); + } + fz_catch(ctx) + { + /* fz_drop_document may clobber our error code/message so we have to stash them temporarily. */ + char message[256]; + int code; + fz_strlcpy(message, fz_convert_error(ctx, &code), sizeof message); + fz_drop_document(ctx, &doc->super); + fz_throw(ctx, code, "%s", message); + } + return doc; +} + +/* Uncomment the following to test progressive loading. */ +/* #define TEST_PROGRESSIVE_HACK */ + +pdf_document * +pdf_open_document(fz_context *ctx, const char *filename) +{ + fz_stream *file = NULL; + pdf_document *doc = NULL; + + fz_var(file); + fz_var(doc); + + fz_try(ctx) + { + file = fz_open_file(ctx, filename); +#ifdef TEST_PROGRESSIVE_HACK + file->progressive = 1; +#endif + doc = pdf_new_document(ctx, file); + pdf_init_document(ctx, doc); + } + fz_always(ctx) + { + fz_drop_stream(ctx, file); + } + fz_catch(ctx) + { + /* fz_drop_document may clobber our error code/message so we have to stash them temporarily. */ + char message[256]; + int code; + fz_strlcpy(message, fz_convert_error(ctx, &code), sizeof message); + fz_drop_document(ctx, &doc->super); + fz_throw(ctx, code, "%s", message); + } + +#ifdef TEST_PROGRESSIVE_HACK + if (doc->file_reading_linearly) + { + fz_try(ctx) + pdf_progressive_advance(ctx, doc, doc->linear_page_count-1); + fz_catch(ctx) + { + doc->file_reading_linearly = 0; + /* swallow the error */ + } + } +#endif + + return doc; +} + +static void +pdf_load_hints(fz_context *ctx, pdf_document *doc, int objnum) +{ + fz_stream *stream = NULL; + pdf_obj *dict; + + fz_var(stream); + fz_var(dict); + + fz_try(ctx) + { + int i, j, least_num_page_objs, page_obj_num_bits; + int least_page_len, page_len_num_bits, shared_hint_offset; + /* int least_page_offset, page_offset_num_bits; */ + /* int least_content_stream_len, content_stream_len_num_bits; */ + int num_shared_obj_num_bits, shared_obj_num_bits; + /* int numerator_bits, denominator_bits; */ + int shared; + int shared_obj_num, shared_obj_offset, shared_obj_count_page1; + int shared_obj_count_total; + int least_shared_group_len, shared_group_len_num_bits; + int max_object_num = pdf_xref_len(ctx, doc); + + stream = pdf_open_stream_number(ctx, doc, objnum); + dict = pdf_get_xref_entry_no_null(ctx, doc, objnum)->obj; + if (dict == NULL || !pdf_is_dict(ctx, dict)) + fz_throw(ctx, FZ_ERROR_FORMAT, "malformed hint object"); + + shared_hint_offset = pdf_dict_get_int(ctx, dict, PDF_NAME(S)); + + /* Malloc the structures (use realloc to cope with the fact we + * may try this several times before enough data is loaded) */ + doc->hint_page = fz_realloc_array(ctx, doc->hint_page, doc->linear_page_count+1, pdf_hint_page); + memset(doc->hint_page, 0, sizeof(*doc->hint_page) * (doc->linear_page_count+1)); + doc->hint_obj_offsets = fz_realloc_array(ctx, doc->hint_obj_offsets, max_object_num, int64_t); + memset(doc->hint_obj_offsets, 0, sizeof(*doc->hint_obj_offsets) * max_object_num); + doc->hint_obj_offsets_max = max_object_num; + + /* Read the page object hints table: Header first */ + least_num_page_objs = fz_read_bits(ctx, stream, 32); + /* The following is sometimes a lie, but we read this version, + * as other table values are built from it. In + * pdf_reference17.pdf, this points to 2 objects before the + * first pages page object. */ + doc->hint_page[0].offset = fz_read_bits(ctx, stream, 32); + if (doc->hint_page[0].offset > doc->hint_object_offset) + doc->hint_page[0].offset += doc->hint_object_length; + page_obj_num_bits = fz_read_bits(ctx, stream, 16); + least_page_len = fz_read_bits(ctx, stream, 32); + page_len_num_bits = fz_read_bits(ctx, stream, 16); + /* least_page_offset = */ (void) fz_read_bits(ctx, stream, 32); + /* page_offset_num_bits = */ (void) fz_read_bits(ctx, stream, 16); + /* least_content_stream_len = */ (void) fz_read_bits(ctx, stream, 32); + /* content_stream_len_num_bits = */ (void) fz_read_bits(ctx, stream, 16); + num_shared_obj_num_bits = fz_read_bits(ctx, stream, 16); + shared_obj_num_bits = fz_read_bits(ctx, stream, 16); + /* numerator_bits = */ (void) fz_read_bits(ctx, stream, 16); + /* denominator_bits = */ (void) fz_read_bits(ctx, stream, 16); + + /* Item 1: Page object numbers */ + doc->hint_page[0].number = doc->linear_page1_obj_num; + /* We don't care about the number of objects in the first page */ + (void)fz_read_bits(ctx, stream, page_obj_num_bits); + j = 1; + for (i = 1; i < doc->linear_page_count; i++) + { + int delta_page_objs = fz_read_bits(ctx, stream, page_obj_num_bits); + + doc->hint_page[i].number = j; + j += least_num_page_objs + delta_page_objs; + } + doc->hint_page[i].number = j; /* Not a real page object */ + fz_sync_bits(ctx, stream); + /* Item 2: Page lengths */ + j = doc->hint_page[0].offset; + for (i = 0; i < doc->linear_page_count; i++) + { + int delta_page_len = fz_read_bits(ctx, stream, page_len_num_bits); + int old = j; + + doc->hint_page[i].offset = j; + j += least_page_len + delta_page_len; + if (old <= doc->hint_object_offset && j > doc->hint_object_offset) + j += doc->hint_object_length; + } + doc->hint_page[i].offset = j; + fz_sync_bits(ctx, stream); + /* Item 3: Shared references */ + shared = 0; + for (i = 0; i < doc->linear_page_count; i++) + { + int num_shared_objs = fz_read_bits(ctx, stream, num_shared_obj_num_bits); + doc->hint_page[i].index = shared; + shared += num_shared_objs; + } + doc->hint_page[i].index = shared; + doc->hint_shared_ref = fz_realloc_array(ctx, doc->hint_shared_ref, shared, int); + memset(doc->hint_shared_ref, 0, sizeof(*doc->hint_shared_ref) * shared); + fz_sync_bits(ctx, stream); + /* Item 4: Shared references */ + for (i = 0; i < shared; i++) + { + int ref = fz_read_bits(ctx, stream, shared_obj_num_bits); + doc->hint_shared_ref[i] = ref; + } + /* Skip items 5,6,7 as we don't use them */ + + fz_seek(ctx, stream, doc->bias + shared_hint_offset, SEEK_SET); + + /* Read the shared object hints table: Header first */ + shared_obj_num = fz_read_bits(ctx, stream, 32); + shared_obj_offset = fz_read_bits(ctx, stream, 32); + if (shared_obj_offset > doc->hint_object_offset) + shared_obj_offset += doc->hint_object_length; + shared_obj_count_page1 = fz_read_bits(ctx, stream, 32); + shared_obj_count_total = fz_read_bits(ctx, stream, 32); + shared_obj_num_bits = fz_read_bits(ctx, stream, 16); + least_shared_group_len = fz_read_bits(ctx, stream, 32); + shared_group_len_num_bits = fz_read_bits(ctx, stream, 16); + + /* Sanity check the references in Item 4 above to ensure we + * don't access out of range with malicious files. */ + for (i = 0; i < shared; i++) + { + if (doc->hint_shared_ref[i] >= shared_obj_count_total) + { + fz_throw(ctx, FZ_ERROR_FORMAT, "malformed hint stream (shared refs)"); + } + } + + doc->hint_shared = fz_realloc_array(ctx, doc->hint_shared, shared_obj_count_total+1, pdf_hint_shared); + memset(doc->hint_shared, 0, sizeof(*doc->hint_shared) * (shared_obj_count_total+1)); + + /* Item 1: Shared references */ + j = doc->hint_page[0].offset; + for (i = 0; i < shared_obj_count_page1; i++) + { + int off = fz_read_bits(ctx, stream, shared_group_len_num_bits); + int old = j; + doc->hint_shared[i].offset = j; + j += off + least_shared_group_len; + if (old <= doc->hint_object_offset && j > doc->hint_object_offset) + j += doc->hint_object_length; + } + /* FIXME: We would have problems recreating the length of the + * last page 1 shared reference group. But we'll never need + * to, so ignore it. */ + j = shared_obj_offset; + for (; i < shared_obj_count_total; i++) + { + int off = fz_read_bits(ctx, stream, shared_group_len_num_bits); + int old = j; + doc->hint_shared[i].offset = j; + j += off + least_shared_group_len; + if (old <= doc->hint_object_offset && j > doc->hint_object_offset) + j += doc->hint_object_length; + } + doc->hint_shared[i].offset = j; + fz_sync_bits(ctx, stream); + /* Item 2: Signature flags: read these just so we can skip */ + for (i = 0; i < shared_obj_count_total; i++) + { + doc->hint_shared[i].number = fz_read_bits(ctx, stream, 1); + } + fz_sync_bits(ctx, stream); + /* Item 3: Signatures: just skip */ + for (i = 0; i < shared_obj_count_total; i++) + { + if (doc->hint_shared[i].number) + { + (void) fz_read_bits(ctx, stream, 128); + } + } + fz_sync_bits(ctx, stream); + /* Item 4: Shared object object numbers */ + j = doc->linear_page1_obj_num; /* FIXME: This is a lie! */ + for (i = 0; i < shared_obj_count_page1; i++) + { + doc->hint_shared[i].number = j; + j += fz_read_bits(ctx, stream, shared_obj_num_bits) + 1; + } + j = shared_obj_num; + for (; i < shared_obj_count_total; i++) + { + doc->hint_shared[i].number = j; + j += fz_read_bits(ctx, stream, shared_obj_num_bits) + 1; + } + doc->hint_shared[i].number = j; + + /* Now, actually use the data we have gathered. */ + for (i = 0 /*shared_obj_count_page1*/; i < shared_obj_count_total; i++) + { + if (doc->hint_shared[i].number >= 0 && doc->hint_shared[i].number < max_object_num) + doc->hint_obj_offsets[doc->hint_shared[i].number] = doc->hint_shared[i].offset; + } + for (i = 0; i < doc->linear_page_count; i++) + { + if (doc->hint_page[i].number >= 0 && doc->hint_page[i].number < max_object_num) + doc->hint_obj_offsets[doc->hint_page[i].number] = doc->hint_page[i].offset; + } + } + fz_always(ctx) + { + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + /* Don't try to load hints again */ + doc->hints_loaded = 1; + /* We won't use the linearized object anymore. */ + doc->file_reading_linearly = 0; + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + /* Any other error becomes a TRYLATER */ + fz_report_error(ctx); + fz_throw(ctx, FZ_ERROR_TRYLATER, "malformed hints object"); + } + doc->hints_loaded = 1; +} + +static void +pdf_load_hint_object(fz_context *ctx, pdf_document *doc) +{ + pdf_lexbuf *buf = &doc->lexbuf.base; + int64_t curr_pos; + + curr_pos = fz_tell(ctx, doc->file); + fz_seek(ctx, doc->file, doc->bias + doc->hint_object_offset, SEEK_SET); + fz_try(ctx) + { + while (1) + { + pdf_obj *page = NULL; + int num, tok; + + tok = pdf_lex(ctx, doc->file, buf); + if (tok != PDF_TOK_INT) + break; + num = buf->i; + tok = pdf_lex(ctx, doc->file, buf); + if (tok != PDF_TOK_INT) + break; + /* Ignore gen = buf->i */ + tok = pdf_lex(ctx, doc->file, buf); + if (tok != PDF_TOK_OBJ) + break; + (void)pdf_repair_obj(ctx, doc, buf, NULL, NULL, NULL, NULL, &page, NULL, NULL); + pdf_load_hints(ctx, doc, num); + } + } + fz_always(ctx) + { + fz_seek(ctx, doc->file, curr_pos, SEEK_SET); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +pdf_obj *pdf_progressive_advance(fz_context *ctx, pdf_document *doc, int pagenum) +{ + int curr_pos; + pdf_obj *page = NULL; + + pdf_load_hinted_page(ctx, doc, pagenum); + + if (pagenum < 0 || pagenum >= doc->linear_page_count) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "page load out of range (%d of %d)", pagenum, doc->linear_page_count); + + if (doc->linear_pos == doc->file_length) + return doc->linear_page_refs[pagenum]; + + /* Only load hints once, and then only after we have got page 0 */ + if (pagenum > 0 && !doc->hints_loaded && doc->hint_object_offset > 0 && doc->linear_pos >= doc->hint_object_offset) + { + /* Found hint object */ + pdf_load_hint_object(ctx, doc); + } + + DEBUGMESS((ctx, "continuing to try to advance from %d", doc->linear_pos)); + curr_pos = fz_tell(ctx, doc->file); + + fz_var(page); + + fz_try(ctx) + { + int eof; + do + { + int num; + eof = pdf_obj_read(ctx, doc, &doc->linear_pos, &num, &page); + pdf_drop_obj(ctx, page); + page = NULL; + } + while (!eof); + + { + pdf_obj *catalog; + pdf_obj *pages; + doc->linear_pos = doc->file_length; + pdf_load_xref(ctx, doc); + catalog = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root)); + pages = pdf_dict_get(ctx, catalog, PDF_NAME(Pages)); + + if (!pdf_is_dict(ctx, pages)) + fz_throw(ctx, FZ_ERROR_FORMAT, "missing page tree"); + break; + } + } + fz_always(ctx) + { + fz_seek(ctx, doc->file, curr_pos, SEEK_SET); + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, page); + if (fz_caught(ctx) == FZ_ERROR_TRYLATER) + { + if (doc->linear_page_refs[pagenum] == NULL) + { + /* Still not got a page */ + fz_rethrow(ctx); + } + // TODO: should we really swallow this error? + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + } + else + fz_rethrow(ctx); + } + + return doc->linear_page_refs[pagenum]; +} + +pdf_document *fz_new_pdf_document_from_fz_document(fz_context *ctx, fz_document *ptr) +{ + if (!ptr || !ptr->as_pdf) + return NULL; + return (pdf_document *)fz_keep_document(ctx, ptr->as_pdf(ctx, ptr)); +} + +pdf_document *pdf_document_from_fz_document(fz_context *ctx, fz_document *ptr) +{ + return (pdf_document *)((ptr && ptr->count_pages == pdf_count_pages_imp) ? ptr : NULL); +} + +pdf_page *pdf_page_from_fz_page(fz_context *ctx, fz_page *page) +{ + if (pdf_document_from_fz_document(ctx, page->doc)) + return (pdf_page*) page; + return NULL; +} + +pdf_document *pdf_specifics(fz_context *ctx, fz_document *doc) +{ + return pdf_document_from_fz_document(ctx, doc); +} + +pdf_obj * +pdf_add_object(fz_context *ctx, pdf_document *doc, pdf_obj *obj) +{ + pdf_document *orig_doc; + int num; + + orig_doc = pdf_get_bound_document(ctx, obj); + if (orig_doc && orig_doc != doc) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "tried to add an object belonging to a different document"); + if (pdf_is_indirect(ctx, obj)) + return pdf_keep_obj(ctx, obj); + num = pdf_create_object(ctx, doc); + pdf_update_object(ctx, doc, num, obj); + return pdf_new_indirect(ctx, doc, num, 0); +} + +pdf_obj * +pdf_add_object_drop(fz_context *ctx, pdf_document *doc, pdf_obj *obj) +{ + pdf_obj *ind = NULL; + fz_try(ctx) + ind = pdf_add_object(ctx, doc, obj); + fz_always(ctx) + pdf_drop_obj(ctx, obj); + fz_catch(ctx) + fz_rethrow(ctx); + return ind; +} + +pdf_obj * +pdf_add_new_dict(fz_context *ctx, pdf_document *doc, int initial) +{ + return pdf_add_object_drop(ctx, doc, pdf_new_dict(ctx, doc, initial)); +} + +pdf_obj * +pdf_add_new_array(fz_context *ctx, pdf_document *doc, int initial) +{ + return pdf_add_object_drop(ctx, doc, pdf_new_array(ctx, doc, initial)); +} + +pdf_obj * +pdf_add_stream(fz_context *ctx, pdf_document *doc, fz_buffer *buf, pdf_obj *obj, int compressed) +{ + pdf_obj *ind; + if (!obj) + ind = pdf_add_new_dict(ctx, doc, 4); + else + ind = pdf_add_object(ctx, doc, obj); + fz_try(ctx) + pdf_update_stream(ctx, doc, ind, buf, compressed); + fz_catch(ctx) + { + pdf_drop_obj(ctx, ind); + fz_rethrow(ctx); + } + return ind; +} + +pdf_document *pdf_create_document(fz_context *ctx) +{ + pdf_document *doc; + pdf_obj *root; + pdf_obj *pages; + pdf_obj *trailer = NULL; + + fz_var(trailer); + + doc = pdf_new_document(ctx, NULL); + fz_try(ctx) + { + doc->file_size = 0; + doc->startxref = 0; + doc->num_xref_sections = 0; + doc->num_incremental_sections = 0; + doc->xref_base = 0; + doc->disallow_new_increments = 0; + pdf_get_populating_xref_entry(ctx, doc, 0); + + trailer = pdf_new_dict(ctx, doc, 2); + pdf_dict_put_int(ctx, trailer, PDF_NAME(Size), 3); + pdf_dict_put_drop(ctx, trailer, PDF_NAME(Root), root = pdf_add_new_dict(ctx, doc, 2)); + pdf_dict_put(ctx, root, PDF_NAME(Type), PDF_NAME(Catalog)); + pdf_dict_put_drop(ctx, root, PDF_NAME(Pages), pages = pdf_add_new_dict(ctx, doc, 3)); + pdf_dict_put(ctx, pages, PDF_NAME(Type), PDF_NAME(Pages)); + pdf_dict_put_int(ctx, pages, PDF_NAME(Count), 0); + pdf_dict_put_array(ctx, pages, PDF_NAME(Kids), 1); + + /* Set the trailer of the final xref section. */ + doc->xref_sections[0].trailer = trailer; + } + fz_catch(ctx) + { + pdf_drop_obj(ctx, trailer); + fz_drop_document(ctx, &doc->super); + fz_rethrow(ctx); + } + return doc; +} + +static const char *pdf_extensions[] = +{ + "pdf", + "fdf", + "pclm", + "ai", + NULL +}; + +static const char *pdf_mimetypes[] = +{ + "application/pdf", + "application/PCLm", + NULL +}; + +static int +pdf_recognize_doc_content(fz_context *ctx, const fz_document_handler *handler, fz_stream *stream, fz_archive *dir, void **state, fz_document_recognize_state_free_fn **free_state) +{ + const char *match = "%PDF-"; + const char *match2 = "%FDF-"; + int pos = 0; + int n = 4096+5; + int c; + + if (state) + *state = NULL; + if (free_state) + *free_state = NULL; + + if (stream == NULL) + return 0; + + do + { + c = fz_read_byte(ctx, stream); + if (c == EOF) + return 0; + if (c == match[pos] || c == match2[pos]) + { + pos++; + if (pos == 5) + return 100; + } + else + { + /* Restart matching, but recheck c against the start. */ + pos = (c == match[0]); + } + } + while (--n > 0); + + return 0; +} + +static fz_document * +open_document(fz_context *ctx, const fz_document_handler *handler, fz_stream *file, fz_stream *accel, fz_archive *zip, void *state) +{ + if (file == NULL) + return NULL; + return (fz_document *)pdf_open_document_with_stream(ctx, file); +} + +fz_document_handler pdf_document_handler = +{ + NULL, + open_document, + pdf_extensions, + pdf_mimetypes, + pdf_recognize_doc_content +}; + +void pdf_mark_xref(fz_context *ctx, pdf_document *doc) +{ + int x, e; + + for (x = 0; x < doc->num_xref_sections; x++) + { + pdf_xref *xref = &doc->xref_sections[x]; + pdf_xref_subsec *sub; + + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + for (e = 0; e < sub->len; e++) + { + pdf_xref_entry *entry = &sub->table[e]; + if (entry->obj) + { + entry->marked = 1; + } + } + } + } +} + +void pdf_clear_xref(fz_context *ctx, pdf_document *doc) +{ + int x, e; + + for (x = 0; x < doc->num_xref_sections; x++) + { + pdf_xref *xref = &doc->xref_sections[x]; + pdf_xref_subsec *sub; + + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + for (e = 0; e < sub->len; e++) + { + pdf_xref_entry *entry = &sub->table[e]; + /* We cannot drop objects if the stream + * buffer has been updated */ + if (entry->obj != NULL && entry->stm_buf == NULL) + { + if (pdf_obj_refs(ctx, entry->obj) == 1) + { + pdf_drop_obj(ctx, entry->obj); + entry->obj = NULL; + } + } + } + } + } +} + +void pdf_clear_xref_to_mark(fz_context *ctx, pdf_document *doc) +{ + int x, e; + + for (x = 0; x < doc->num_xref_sections; x++) + { + pdf_xref *xref = &doc->xref_sections[x]; + pdf_xref_subsec *sub; + + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + for (e = 0; e < sub->len; e++) + { + pdf_xref_entry *entry = &sub->table[e]; + + /* We cannot drop objects if the stream buffer has + * been updated */ + if (entry->obj != NULL && entry->stm_buf == NULL) + { + if (!entry->marked && pdf_obj_refs(ctx, entry->obj) == 1) + { + pdf_drop_obj(ctx, entry->obj); + entry->obj = NULL; + } + } + } + } + } +} + +int +pdf_count_versions(fz_context *ctx, pdf_document *doc) +{ + return doc->num_xref_sections-doc->num_incremental_sections-doc->has_linearization_object; +} + +int +pdf_count_unsaved_versions(fz_context *ctx, pdf_document *doc) +{ + return doc->num_incremental_sections; +} + +int +pdf_doc_was_linearized(fz_context *ctx, pdf_document *doc) +{ + return doc->has_linearization_object; +} + +static int pdf_obj_exists(fz_context *ctx, pdf_document *doc, int i) +{ + pdf_xref_subsec *sub; + int j; + + if (i < 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Negative object number requested"); + + if (i <= doc->max_xref_len) + j = doc->xref_index[i]; + else + j = 0; + + /* We may be accessing an earlier version of the document using xref_base + * and j may be an index into a later xref section */ + if (doc->xref_base > j) + j = doc->xref_base; + + /* Find the first xref section where the entry is defined. */ + for (; j < doc->num_xref_sections; j++) + { + pdf_xref *xref = &doc->xref_sections[j]; + + if (i < xref->num_objects) + { + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + if (i < sub->start || i >= sub->start + sub->len) + continue; + + if (sub->table[i - sub->start].type) + return 1; + } + } + } + + return 0; +} + +enum { + FIELD_CHANGED = 1, + FIELD_CHANGE_VALID = 2, + FIELD_CHANGE_INVALID = 4 +}; + +typedef struct +{ + int num_obj; + int obj_changes[FZ_FLEXIBLE_ARRAY]; +} pdf_changes; + +static int +check_unchanged_between(fz_context *ctx, pdf_document *doc, pdf_changes *changes, pdf_obj *nobj, pdf_obj *oobj) +{ + int marked = 0; + int changed = 0; + + /* Trivially identical => trivially unchanged. */ + if (nobj == oobj) + return 0; + + /* Strictly speaking we shouldn't need to call fz_var, + * but I suspect static analysis tools are not smart + * enough to figure that out. */ + fz_var(marked); + + if (pdf_is_indirect(ctx, nobj)) + { + int o_xref_base = doc->xref_base; + + /* Both must be indirect if one is. */ + if (!pdf_is_indirect(ctx, oobj)) + { + changes->obj_changes[pdf_to_num(ctx, nobj)] |= FIELD_CHANGE_INVALID; + return 1; + } + + /* Handle recursing back into ourselves. */ + if (pdf_obj_marked(ctx, nobj)) + { + if (pdf_obj_marked(ctx, oobj)) + return 0; + changes->obj_changes[pdf_to_num(ctx, nobj)] |= FIELD_CHANGE_INVALID; + return 1; + } + else if (pdf_obj_marked(ctx, oobj)) + { + changes->obj_changes[pdf_to_num(ctx, nobj)] |= FIELD_CHANGE_INVALID; + return 1; + } + + nobj = pdf_resolve_indirect_chain(ctx, nobj); + doc->xref_base = o_xref_base+1; + fz_try(ctx) + { + oobj = pdf_resolve_indirect_chain(ctx, oobj); + if (oobj != nobj) + { + /* Different objects, so lock them */ + if (!pdf_obj_marked(ctx, nobj) && !pdf_obj_marked(ctx, oobj)) + { + (void)pdf_mark_obj(ctx, nobj); + (void)pdf_mark_obj(ctx, oobj); + marked = 1; + } + } + } + fz_always(ctx) + doc->xref_base = o_xref_base; + fz_catch(ctx) + fz_rethrow(ctx); + + if (nobj == oobj) + return 0; /* Trivially identical */ + } + + fz_var(changed); + + fz_try(ctx) + { + if (pdf_is_dict(ctx, nobj)) + { + int i, n = pdf_dict_len(ctx, nobj); + + if (!pdf_is_dict(ctx, oobj) || n != pdf_dict_len(ctx, oobj)) + { +change_found: + changes->obj_changes[pdf_to_num(ctx, nobj)] |= FIELD_CHANGE_INVALID; + changed = 1; + break; + } + + for (i = 0; i < n; i++) + { + pdf_obj *key = pdf_dict_get_key(ctx, nobj, i); + pdf_obj *nval = pdf_dict_get(ctx, nobj, key); + pdf_obj *oval = pdf_dict_get(ctx, oobj, key); + + changed |= check_unchanged_between(ctx, doc, changes, nval, oval); + } + } + else if (pdf_is_array(ctx, nobj)) + { + int i, n = pdf_array_len(ctx, nobj); + + if (!pdf_is_array(ctx, oobj) || n != pdf_array_len(ctx, oobj)) + goto change_found; + + for (i = 0; i < n; i++) + { + pdf_obj *nval = pdf_array_get(ctx, nobj, i); + pdf_obj *oval = pdf_array_get(ctx, oobj, i); + + changed |= check_unchanged_between(ctx, doc, changes, nval, oval); + } + } + else if (pdf_objcmp(ctx, nobj, oobj)) + goto change_found; + } + fz_always(ctx) + { + if (marked) + { + pdf_unmark_obj(ctx, nobj); + pdf_unmark_obj(ctx, oobj); + } + } + fz_catch(ctx) + fz_rethrow(ctx); + + return changed; +} + +typedef struct +{ + int max; + int len; + char **list; +} char_list; + +/* This structure is used to hold the definition of which fields + * are locked. */ +struct pdf_locked_fields +{ + int p; + int all; + char_list includes; + char_list excludes; +}; + +static void +free_char_list(fz_context *ctx, char_list *c) +{ + int i; + + if (c == NULL) + return; + + for (i = c->len-1; i >= 0; i--) + fz_free(ctx, c->list[i]); + fz_free(ctx, c->list); + c->len = 0; + c->max = 0; +} + +void +pdf_drop_locked_fields(fz_context *ctx, pdf_locked_fields *fl) +{ + if (fl == NULL) + return; + + free_char_list(ctx, &fl->includes); + free_char_list(ctx, &fl->excludes); + fz_free(ctx, fl); +} + +static void +char_list_append(fz_context *ctx, char_list *list, const char *s) +{ + if (list->len == list->max) + { + int n = list->max * 2; + if (n == 0) n = 4; + + list->list = fz_realloc_array(ctx, list->list, n, char *); + list->max = n; + } + list->list[list->len] = fz_strdup(ctx, s); + list->len++; +} + +int +pdf_is_field_locked(fz_context *ctx, pdf_locked_fields *locked, const char *name) +{ + int i; + + if (locked->p == 1) + { + /* Permissions were set, and say that field changes are not to be allowed. */ + return 1; /* Locked */ + } + + if(locked->all) + { + /* The only way we might not be unlocked is if + * we are listed in the excludes. */ + for (i = 0; i < locked->excludes.len; i++) + if (!strcmp(locked->excludes.list[i], name)) + return 0; + return 1; + } + + /* The only way we can be locked is for us to be in the includes. */ + for (i = 0; i < locked->includes.len; i++) + if (strcmp(locked->includes.list[i], name) == 0) + return 1; + + /* Anything else is unlocked */ + return 0; +} + +/* Unfortunately, in C, there is no legal way to define a function + * type that returns itself. We therefore have to use a struct + * wrapper. */ +typedef struct filter_wrap +{ + struct filter_wrap (*func)(fz_context *ctx, pdf_obj *dict, pdf_obj *key); +} filter_wrap; + +typedef struct filter_wrap (*filter_fn)(fz_context *ctx, pdf_obj *dict, pdf_obj *key); + +#define RETURN_FILTER(f) { filter_wrap rf; rf.func = (f); return rf; } + +static filter_wrap filter_simple(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + RETURN_FILTER(NULL); +} + +static filter_wrap filter_transformparams(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + if (pdf_name_eq(ctx, key, PDF_NAME(Type)) || + pdf_name_eq(ctx, key, PDF_NAME(P)) || + pdf_name_eq(ctx, key, PDF_NAME(V)) || + pdf_name_eq(ctx, key, PDF_NAME(Document)) || + pdf_name_eq(ctx, key, PDF_NAME(Msg)) || + pdf_name_eq(ctx, key, PDF_NAME(V)) || + pdf_name_eq(ctx, key, PDF_NAME(Annots)) || + pdf_name_eq(ctx, key, PDF_NAME(Form)) || + pdf_name_eq(ctx, key, PDF_NAME(FormEx)) || + pdf_name_eq(ctx, key, PDF_NAME(EF)) || + pdf_name_eq(ctx, key, PDF_NAME(P)) || + pdf_name_eq(ctx, key, PDF_NAME(Action)) || + pdf_name_eq(ctx, key, PDF_NAME(Fields))) + RETURN_FILTER(&filter_simple); + RETURN_FILTER(NULL); +} + +static filter_wrap filter_reference(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + if (pdf_name_eq(ctx, key, PDF_NAME(Type)) || + pdf_name_eq(ctx, key, PDF_NAME(TransformMethod)) || + pdf_name_eq(ctx, key, PDF_NAME(DigestMethod)) || + pdf_name_eq(ctx, key, PDF_NAME(DigestValue)) || + pdf_name_eq(ctx, key, PDF_NAME(DigestLocation))) + RETURN_FILTER(&filter_simple); + if (pdf_name_eq(ctx, key, PDF_NAME(TransformParams))) + RETURN_FILTER(&filter_transformparams); + RETURN_FILTER(NULL); +} + +static filter_wrap filter_prop_build_sub(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + if (pdf_name_eq(ctx, key, PDF_NAME(Name)) || + pdf_name_eq(ctx, key, PDF_NAME(Date)) || + pdf_name_eq(ctx, key, PDF_NAME(R)) || + pdf_name_eq(ctx, key, PDF_NAME(PreRelease)) || + pdf_name_eq(ctx, key, PDF_NAME(OS)) || + pdf_name_eq(ctx, key, PDF_NAME(NonEFontNoWarn)) || + pdf_name_eq(ctx, key, PDF_NAME(TrustedMode)) || + pdf_name_eq(ctx, key, PDF_NAME(V)) || + pdf_name_eq(ctx, key, PDF_NAME(REx)) || + pdf_name_eq(ctx, key, PDF_NAME(Preview))) + RETURN_FILTER(&filter_simple); + RETURN_FILTER(NULL); +} + +static filter_wrap filter_prop_build(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + if (pdf_name_eq(ctx, key, PDF_NAME(Filter)) || + pdf_name_eq(ctx, key, PDF_NAME(PubSec)) || + pdf_name_eq(ctx, key, PDF_NAME(App)) || + pdf_name_eq(ctx, key, PDF_NAME(SigQ))) + RETURN_FILTER(&filter_prop_build_sub); + RETURN_FILTER(NULL); +} + +static filter_wrap filter_v(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + /* Text can point to a stream object */ + if (pdf_name_eq(ctx, key, PDF_NAME(Length)) && pdf_is_stream(ctx, dict)) + RETURN_FILTER(&filter_simple); + /* Sigs point to a dict. */ + if (pdf_name_eq(ctx, key, PDF_NAME(Type)) || + pdf_name_eq(ctx, key, PDF_NAME(Filter)) || + pdf_name_eq(ctx, key, PDF_NAME(SubFilter)) || + pdf_name_eq(ctx, key, PDF_NAME(Contents)) || + pdf_name_eq(ctx, key, PDF_NAME(Cert)) || + pdf_name_eq(ctx, key, PDF_NAME(ByteRange)) || + pdf_name_eq(ctx, key, PDF_NAME(Changes)) || + pdf_name_eq(ctx, key, PDF_NAME(Name)) || + pdf_name_eq(ctx, key, PDF_NAME(M)) || + pdf_name_eq(ctx, key, PDF_NAME(Location)) || + pdf_name_eq(ctx, key, PDF_NAME(Reason)) || + pdf_name_eq(ctx, key, PDF_NAME(ContactInfo)) || + pdf_name_eq(ctx, key, PDF_NAME(R)) || + pdf_name_eq(ctx, key, PDF_NAME(V)) || + pdf_name_eq(ctx, key, PDF_NAME(Prop_AuthTime)) || + pdf_name_eq(ctx, key, PDF_NAME(Prop_AuthType))) + RETURN_FILTER(&filter_simple); + if (pdf_name_eq(ctx, key, PDF_NAME(Reference))) + RETURN_FILTER(filter_reference); + if (pdf_name_eq(ctx, key, PDF_NAME(Prop_Build))) + RETURN_FILTER(filter_prop_build); + RETURN_FILTER(NULL); +} + +static filter_wrap filter_appearance(fz_context *ctx, pdf_obj *dict, pdf_obj *key); + +static filter_wrap filter_xobject_list(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + /* FIXME: Infinite recursion possible here? */ + RETURN_FILTER(&filter_appearance); +} + +static filter_wrap filter_font(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + /* In the example I've seen the /Name field was dropped, so we'll allow + * local changes, but none that follow an indirection. */ + RETURN_FILTER(NULL); +} + +/* FIXME: One idea here is to make filter_font_list and filter_xobject_list + * only accept NEW objects as changes. Will think about this. */ +static filter_wrap filter_font_list(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + RETURN_FILTER(&filter_font); +} + +static filter_wrap filter_resources(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + if (pdf_name_eq(ctx, key, PDF_NAME(XObject))) + RETURN_FILTER(&filter_xobject_list); + if (pdf_name_eq(ctx, key, PDF_NAME(Font))) + RETURN_FILTER(&filter_font_list); + RETURN_FILTER(NULL); +} + +static filter_wrap filter_appearance(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + if (pdf_name_eq(ctx, key, PDF_NAME(Resources))) + RETURN_FILTER(&filter_resources); + RETURN_FILTER(NULL); +} + +static filter_wrap filter_ap(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + /* Just the /N entry for now. May need to add more later. */ + if (pdf_name_eq(ctx, key, PDF_NAME(N)) && pdf_is_stream(ctx, pdf_dict_get(ctx, dict, key))) + RETURN_FILTER(&filter_appearance); + RETURN_FILTER(NULL); +} + +static filter_wrap filter_xfa(fz_context *ctx, pdf_obj *dict, pdf_obj *key) +{ + /* Text can point to a stream object */ + if (pdf_is_stream(ctx, dict)) + RETURN_FILTER(&filter_simple); + RETURN_FILTER(NULL); +} + +static void +filter_changes_accepted(fz_context *ctx, pdf_changes *changes, pdf_obj *obj, filter_fn filter) +{ + int obj_num; + + if (obj == NULL || pdf_obj_marked(ctx, obj)) + return; + + obj_num = pdf_to_num(ctx, obj); + + fz_try(ctx) + { + if (obj_num != 0) + { + (void)pdf_mark_obj(ctx, obj); + changes->obj_changes[obj_num] |= FIELD_CHANGE_VALID; + } + if (filter == NULL) + break; + if (pdf_is_dict(ctx, obj)) + { + int i, n = pdf_dict_len(ctx, obj); + + for (i = 0; i < n; i++) + { + pdf_obj *key = pdf_dict_get_key(ctx, obj, i); + pdf_obj *val = pdf_dict_get_val(ctx, obj, i); + filter_fn f = (filter(ctx, obj, key)).func; + if (f != NULL) + filter_changes_accepted(ctx, changes, val, f); + } + } + else if (pdf_is_array(ctx, obj)) + { + int i, n = pdf_array_len(ctx, obj); + + for (i = 0; i < n; i++) + { + pdf_obj *val = pdf_array_get(ctx, obj, i); + filter_changes_accepted(ctx, changes, val, filter); + } + } + } + fz_always(ctx) + if (obj_num != 0) + pdf_unmark_obj(ctx, obj); + fz_catch(ctx) + fz_rethrow(ctx); +} + +static void +check_field(fz_context *ctx, pdf_document *doc, pdf_changes *changes, pdf_obj *obj, pdf_locked_fields *locked, const char *name_prefix, pdf_obj *new_v, pdf_obj *old_v) +{ + pdf_obj *old_obj, *new_obj, *n_v, *o_v; + int o_xref_base; + int obj_num; + char *field_name = NULL; + + /* All fields MUST be indirections, either in the Fields array + * or AcroForms, or in the Kids array of other Fields. */ + if (!pdf_is_indirect(ctx, obj)) + return; + + obj_num = pdf_to_num(ctx, obj); + o_xref_base = doc->xref_base; + new_obj = pdf_resolve_indirect_chain(ctx, obj); + + /* Similarly, all fields must be dicts */ + if (!pdf_is_dict(ctx, new_obj)) + return; + + if (pdf_obj_marked(ctx, obj)) + return; + + fz_var(field_name); + + fz_try(ctx) + { + int i, len; + const char *name; + size_t n; + pdf_obj *t; + int is_locked; + + (void)pdf_mark_obj(ctx, obj); + + /* Do this within the try, so we can catch any problems */ + doc->xref_base = o_xref_base+1; + old_obj = pdf_resolve_indirect_chain(ctx, obj); + + t = pdf_dict_get(ctx, old_obj, PDF_NAME(T)); + if (t != NULL) + { + name = pdf_dict_get_text_string(ctx, old_obj, PDF_NAME(T)); + n = strlen(name)+1; + if (*name_prefix) + n += 1 + strlen(name_prefix); + field_name = fz_malloc(ctx, n); + if (*name_prefix) + { + strcpy(field_name, name_prefix); + strcat(field_name, "."); + } + else + *field_name = 0; + strcat(field_name, name); + name_prefix = field_name; + } + + doc->xref_base = o_xref_base; + + if (!pdf_is_dict(ctx, old_obj)) + break; + + /* Check V explicitly, allowing for it being inherited. */ + n_v = pdf_dict_get(ctx, new_obj, PDF_NAME(V)); + if (n_v == NULL) + n_v = new_v; + o_v = pdf_dict_get(ctx, old_obj, PDF_NAME(V)); + if (o_v == NULL) + o_v = old_v; + + is_locked = pdf_is_field_locked(ctx, locked, name_prefix); + if (pdf_name_eq(ctx, pdf_dict_get(ctx, new_obj, PDF_NAME(Type)), PDF_NAME(Annot)) && + pdf_name_eq(ctx, pdf_dict_get(ctx, new_obj, PDF_NAME(Subtype)), PDF_NAME(Widget))) + { + if (is_locked) + { + /* If locked, V must not change! */ + if (check_unchanged_between(ctx, doc, changes, n_v, o_v)) + changes->obj_changes[obj_num] |= FIELD_CHANGE_INVALID; + } + else + { + /* If not locked, V can change to be filled in! */ + filter_changes_accepted(ctx, changes, n_v, &filter_v); + changes->obj_changes[obj_num] |= FIELD_CHANGE_VALID; + } + } + + /* Check all the fields in the new object are + * either the same as the old object, or are + * expected changes. */ + len = pdf_dict_len(ctx, new_obj); + for (i = 0; i < len; i++) + { + pdf_obj *key = pdf_dict_get_key(ctx, new_obj, i); + pdf_obj *nval = pdf_dict_get(ctx, new_obj, key); + pdf_obj *oval = pdf_dict_get(ctx, old_obj, key); + + /* Kids arrays shouldn't change. */ + if (pdf_name_eq(ctx, key, PDF_NAME(Kids))) + { + int j, m; + + /* Kids must be an array. If it's not, count it as a difference. */ + if (!pdf_is_array(ctx, nval) || !pdf_is_array(ctx, oval)) + { +change_found: + changes->obj_changes[obj_num] |= FIELD_CHANGE_INVALID; + break; + } + m = pdf_array_len(ctx, nval); + /* Any change in length counts as a difference */ + if (m != pdf_array_len(ctx, oval)) + goto change_found; + for (j = 0; j < m; j++) + { + pdf_obj *nkid = pdf_array_get(ctx, nval, j); + pdf_obj *okid = pdf_array_get(ctx, oval, j); + /* Kids arrays are supposed to all be indirect. If they aren't, + * count it as a difference. */ + if (!pdf_is_indirect(ctx, nkid) || !pdf_is_indirect(ctx, okid)) + goto change_found; + /* For now at least, we'll count any change in number as a difference. */ + if (pdf_to_num(ctx, nkid) != pdf_to_num(ctx, okid)) + goto change_found; + check_field(ctx, doc, changes, nkid, locked, name_prefix, n_v, o_v); + } + } + else if (pdf_name_eq(ctx, key, PDF_NAME(V))) + { + /* V is checked above */ + } + else if (pdf_name_eq(ctx, key, PDF_NAME(AP))) + { + /* If we're locked, then nothing can change. If not, + * we can change to be filled in. */ + if (is_locked) + check_unchanged_between(ctx, doc, changes, nval, oval); + else + filter_changes_accepted(ctx, changes, nval, &filter_ap); + } + /* All other fields can't change */ + else + check_unchanged_between(ctx, doc, changes, nval, oval); + } + + /* Now check all the fields in the old object to + * make sure none were dropped. */ + len = pdf_dict_len(ctx, old_obj); + for (i = 0; i < len; i++) + { + pdf_obj *key = pdf_dict_get_key(ctx, old_obj, i); + pdf_obj *nval, *oval; + + /* V is checked above */ + if (pdf_name_eq(ctx, key, PDF_NAME(V))) + continue; + + nval = pdf_dict_get(ctx, new_obj, key); + oval = pdf_dict_get(ctx, old_obj, key); + + if (nval == NULL && oval != NULL) + changes->obj_changes[pdf_to_num(ctx, nval)] |= FIELD_CHANGE_INVALID; + } + changes->obj_changes[obj_num] |= FIELD_CHANGE_VALID; + + } + fz_always(ctx) + { + pdf_unmark_obj(ctx, obj); + fz_free(ctx, field_name); + doc->xref_base = o_xref_base; + } + fz_catch(ctx) + fz_rethrow(ctx); +} + +static int +pdf_obj_changed_in_version(fz_context *ctx, pdf_document *doc, int num, int version) +{ + if (num < 0 || num > doc->max_xref_len) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Invalid object number requested"); + + return version == doc->xref_index[num]; +} + +static void +merge_lock_specification(fz_context *ctx, pdf_locked_fields *fields, pdf_obj *lock) +{ + pdf_obj *action; + int i, r, w; + + if (lock == NULL) + return; + + action = pdf_dict_get(ctx, lock, PDF_NAME(Action)); + + if (pdf_name_eq(ctx, action, PDF_NAME(All))) + { + /* All fields locked means we don't need any stored + * includes/excludes. */ + fields->all = 1; + free_char_list(ctx, &fields->includes); + free_char_list(ctx, &fields->excludes); + } + else + { + pdf_obj *f = pdf_dict_get(ctx, lock, PDF_NAME(Fields)); + int len = pdf_array_len(ctx, f); + + if (pdf_name_eq(ctx, action, PDF_NAME(Include))) + { + if (fields->all) + { + /* Current state = "All except <excludes> are locked". + * We need to remove <Fields> from <excludes>. */ + for (i = 0; i < len; i++) + { + const char *s = pdf_array_get_text_string(ctx, f, i); + + for (r = w = 0; r < fields->excludes.len; r++) + { + if (strcmp(s, fields->excludes.list[r])) + fields->excludes.list[w++] = fields->excludes.list[r]; + } + fields->excludes.len = w; + } + } + else + { + /* Current state = <includes> are locked. + * We need to add <Fields> to <include> (avoiding repetition). */ + for (i = 0; i < len; i++) + { + const char *s = pdf_array_get_text_string(ctx, f, i); + + for (r = 0; r < fields->includes.len; r++) + { + if (!strcmp(s, fields->includes.list[r])) + break; + } + if (r == fields->includes.len) + char_list_append(ctx, &fields->includes, s); + } + } + } + else if (pdf_name_eq(ctx, action, PDF_NAME(Exclude))) + { + if (fields->all) + { + /* Current state = "All except <excludes> are locked. + * We need to remove anything from <excludes> that isn't in <Fields>. */ + for (r = w = 0; r < fields->excludes.len; r++) + { + for (i = 0; i < len; i++) + { + const char *s = pdf_array_get_text_string(ctx, f, i); + if (!strcmp(s, fields->excludes.list[r])) + break; + } + if (i != len) /* we found a match */ + fields->excludes.list[w++] = fields->excludes.list[r]; + } + fields->excludes.len = w; + } + else + { + /* Current state = <includes> are locked. + * Set all. <excludes> becomes <Fields> less <includes>. Remove <includes>. */ + fields->all = 1; + for (i = 0; i < len; i++) + { + const char *s = pdf_array_get_text_string(ctx, f, i); + for (r = 0; r < fields->includes.len; r++) + { + if (!strcmp(s, fields->includes.list[r])) + break; + } + if (r == fields->includes.len) + char_list_append(ctx, &fields->excludes, s); + } + free_char_list(ctx, &fields->includes); + } + } + } +} + +static void +find_locked_fields_value(fz_context *ctx, pdf_locked_fields *fields, pdf_obj *v) +{ + pdf_obj *ref = pdf_dict_get(ctx, v, PDF_NAME(Reference)); + int i, n; + + if (!ref) + return; + + n = pdf_array_len(ctx, ref); + for (i = 0; i < n; i++) + { + pdf_obj *sr = pdf_array_get(ctx, ref, i); + pdf_obj *tm, *tp, *type; + + /* Type is optional, but if it exists, it'd better be SigRef. */ + type = pdf_dict_get(ctx, sr, PDF_NAME(Type)); + if (type != NULL && !pdf_name_eq(ctx, type, PDF_NAME(SigRef))) + continue; + tm = pdf_dict_get(ctx, sr, PDF_NAME(TransformMethod)); + tp = pdf_dict_get(ctx, sr, PDF_NAME(TransformParams)); + if (pdf_name_eq(ctx, tm, PDF_NAME(DocMDP))) + { + int p = pdf_dict_get_int(ctx, tp, PDF_NAME(P)); + + if (p == 0) + p = 2; + if (fields->p == 0) + fields->p = p; + else + fields->p = fz_mini(fields->p, p); + } + else if (pdf_name_eq(ctx, tm, PDF_NAME(FieldMDP))) + merge_lock_specification(ctx, fields, tp); + } +} + +static void +find_locked_fields_aux(fz_context *ctx, pdf_obj *field, pdf_locked_fields *fields, pdf_obj *inherit_v, pdf_obj *inherit_ft) +{ + int i, n; + + if (!pdf_name_eq(ctx, pdf_dict_get(ctx, field, PDF_NAME(Type)), PDF_NAME(Annot))) + return; + + if (pdf_obj_marked(ctx, field)) + return; + + fz_try(ctx) + { + pdf_obj *kids, *v, *ft; + + (void)pdf_mark_obj(ctx, field); + + v = pdf_dict_get(ctx, field, PDF_NAME(V)); + if (v == NULL) + v = inherit_v; + ft = pdf_dict_get(ctx, field, PDF_NAME(FT)); + if (ft == NULL) + ft = inherit_ft; + + /* We are looking for Widget annotations of type Sig that are + * signed (i.e. have a 'V' field). */ + if (pdf_name_eq(ctx, pdf_dict_get(ctx, field, PDF_NAME(Subtype)), PDF_NAME(Widget)) && + pdf_name_eq(ctx, ft, PDF_NAME(Sig)) && + pdf_name_eq(ctx, pdf_dict_get(ctx, v, PDF_NAME(Type)), PDF_NAME(Sig))) + { + /* Signed Sig Widgets (i.e. ones with a 'V' field) need + * to have their lock field respected. */ + merge_lock_specification(ctx, fields, pdf_dict_get(ctx, field, PDF_NAME(Lock))); + + /* Look for DocMDP and FieldMDP entries to see what + * flavours of alterations are allowed. */ + find_locked_fields_value(ctx, fields, v); + } + + /* Recurse as required */ + kids = pdf_dict_get(ctx, field, PDF_NAME(Kids)); + if (kids) + { + n = pdf_array_len(ctx, kids); + for (i = 0; i < n; i++) + find_locked_fields_aux(ctx, pdf_array_get(ctx, kids, i), fields, v, ft); + } + } + fz_always(ctx) + pdf_unmark_obj(ctx, field); + fz_catch(ctx) + fz_rethrow(ctx); +} + +pdf_locked_fields * +pdf_find_locked_fields(fz_context *ctx, pdf_document *doc, int version) +{ + pdf_locked_fields *fields = fz_malloc_struct(ctx, pdf_locked_fields); + int o_xref_base = doc->xref_base; + doc->xref_base = version; + + fz_var(fields); + + fz_try(ctx) + { + pdf_obj *fobj = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm/Fields"); + int i, len = pdf_array_len(ctx, fobj); + + if (len == 0) + break; + + for (i = 0; i < len; i++) + find_locked_fields_aux(ctx, pdf_array_get(ctx, fobj, i), fields, NULL, NULL); + + /* Add in any DocMDP referenced directly from the Perms dict. */ + find_locked_fields_value(ctx, fields, pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/Perms/DocMDP")); + } + fz_always(ctx) + doc->xref_base = o_xref_base; + fz_catch(ctx) + { + pdf_drop_locked_fields(ctx, fields); + fz_rethrow(ctx); + } + + return fields; +} + +pdf_locked_fields * +pdf_find_locked_fields_for_sig(fz_context *ctx, pdf_document *doc, pdf_obj *sig) +{ + pdf_locked_fields *fields = fz_malloc_struct(ctx, pdf_locked_fields); + + fz_var(fields); + + fz_try(ctx) + { + pdf_obj *ref; + int i, len; + + /* Ensure it really is a sig */ + if (!pdf_name_eq(ctx, pdf_dict_get(ctx, sig, PDF_NAME(Subtype)), PDF_NAME(Widget)) || + !pdf_name_eq(ctx, pdf_dict_get_inheritable(ctx, sig, PDF_NAME(FT)), PDF_NAME(Sig))) + break; + + /* Check the locking details given in the V (i.e. what the signature value + * claims to lock). */ + ref = pdf_dict_getp(ctx, sig, "V/Reference"); + len = pdf_array_len(ctx, ref); + for (i = 0; i < len; i++) + { + pdf_obj *tp = pdf_dict_get(ctx, pdf_array_get(ctx, ref, i), PDF_NAME(TransformParams)); + merge_lock_specification(ctx, fields, tp); + } + + /* Also, check the locking details given in the Signature definition. This may + * not strictly be necessary as it's supposed to be "what the form author told + * the signature that it should lock". A well-formed signature should lock + * at least that much (possibly with extra fields locked from the XFA). If the + * signature doesn't lock as much as it was told to, we should be suspicious + * of the signing application. It is not clear that this test is actually + * necessary, or in keeping with what Acrobat does. */ + merge_lock_specification(ctx, fields, pdf_dict_get(ctx, sig, PDF_NAME(Lock))); + } + fz_catch(ctx) + { + pdf_drop_locked_fields(ctx, fields); + fz_rethrow(ctx); + } + + return fields; +} + +static int +validate_locked_fields(fz_context *ctx, pdf_document *doc, int version, pdf_locked_fields *locked) +{ + int o_xref_base = doc->xref_base; + pdf_changes *changes; + int num_objs; + int i, n; + int all_indirects = 1; + + num_objs = doc->max_xref_len; + changes = fz_malloc_flexible(ctx, pdf_changes, obj_changes, num_objs); + changes->num_obj = num_objs; + + fz_try(ctx) + { + pdf_obj *acroform, *new_acroform, *old_acroform; + int len, acroform_num; + + doc->xref_base = version; + + /* Detect every object that has changed */ + for (i = 1; i < num_objs; i++) + { + if (pdf_obj_changed_in_version(ctx, doc, i, version)) + changes->obj_changes[i] = FIELD_CHANGED; + } + + /* FIXME: Compare PageTrees and NumberTrees (just to allow for them being regenerated + * and having produced stuff that represents the same stuff). */ + + /* The metadata of a document may be regenerated. Allow for that. */ + filter_changes_accepted(ctx, changes, pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/Metadata"), &filter_simple); + + /* The ModDate of document info may be regenerated. Allow for that. */ + /* FIXME: We accept all changes in document info, when maybe we ought to just + * accept ModDate? */ + filter_changes_accepted(ctx, changes, pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Info"), &filter_simple); + + /* The Encryption dict may be rewritten for the new Xref. */ + filter_changes_accepted(ctx, changes, pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Encrypt"), &filter_simple); + + /* We have to accept certain changes in the top level AcroForms dict, + * so get the 2 versions... */ + acroform = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm"); + acroform_num = pdf_to_num(ctx, acroform); + new_acroform = pdf_resolve_indirect_chain(ctx, acroform); + doc->xref_base = version+1; + old_acroform = pdf_resolve_indirect_chain(ctx, pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm")); + doc->xref_base = version; + n = pdf_dict_len(ctx, new_acroform); + for (i = 0; i < n; i++) + { + pdf_obj *key = pdf_dict_get_key(ctx, new_acroform, i); + pdf_obj *nval = pdf_dict_get(ctx, new_acroform, key); + pdf_obj *oval = pdf_dict_get(ctx, old_acroform, key); + + if (pdf_name_eq(ctx, key, PDF_NAME(Fields))) + { + int j; + + len = pdf_array_len(ctx, nval); + for (j = 0; j < len; j++) + { + pdf_obj *field = pdf_array_get(ctx, nval, j); + if (!pdf_is_indirect(ctx, field)) + all_indirects = 0; + check_field(ctx, doc, changes, field, locked, "", NULL, NULL); + } + } + else if (pdf_name_eq(ctx, key, PDF_NAME(SigFlags))) + { + /* Accept this */ + changes->obj_changes[acroform_num] |= FIELD_CHANGE_VALID; + } + else if (pdf_name_eq(ctx, key, PDF_NAME(DR))) + { + /* Accept any changes from within the Document Resources */ + filter_changes_accepted(ctx, changes, nval, &filter_resources); + } + else if (pdf_name_eq(ctx, key, PDF_NAME(XFA))) + { + /* Allow any changes within the XFA streams. */ + filter_changes_accepted(ctx, changes, nval, &filter_xfa); + } + else if (pdf_objcmp(ctx, nval, oval)) + { + changes->obj_changes[acroform_num] |= FIELD_CHANGE_INVALID; + } + } + + /* Allow for any object streams/XRefs to be changed. */ + doc->xref_base = version+1; + for (i = 1; i < num_objs; i++) + { + pdf_obj *oobj, *otype; + if (changes->obj_changes[i] != FIELD_CHANGED) + continue; + if (!pdf_obj_exists(ctx, doc, i)) + { + /* Not present this version - must be newly created, can't be a change. */ + changes->obj_changes[i] |= FIELD_CHANGE_VALID; + continue; + } + oobj = pdf_load_object(ctx, doc, i); + otype = pdf_dict_get(ctx, oobj, PDF_NAME(Type)); + if (pdf_name_eq(ctx, otype, PDF_NAME(ObjStm)) || + pdf_name_eq(ctx, otype, PDF_NAME(XRef))) + { + changes->obj_changes[i] |= FIELD_CHANGE_VALID; + } + pdf_drop_obj(ctx, oobj); + } + } + fz_always(ctx) + doc->xref_base = o_xref_base; + fz_catch(ctx) + { + fz_free(ctx, changes); + fz_rethrow(ctx); + } + + for (i = 1; i < num_objs; i++) + { + if (changes->obj_changes[i] == FIELD_CHANGED) + /* Change with no reason */ + break; + if (changes->obj_changes[i] & FIELD_CHANGE_INVALID) + /* Illegal Change */ + break; + } + + fz_free(ctx, changes); + + return (i == num_objs) && all_indirects; +} + +int +pdf_validate_changes(fz_context *ctx, pdf_document *doc, int version) +{ + int unsaved_versions = pdf_count_unsaved_versions(ctx, doc); + int n = pdf_count_versions(ctx, doc); + pdf_locked_fields *locked = NULL; + int result; + + if (version < 0 || version >= n) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "There aren't that many changes to find in this document!"); + + /* We are wanting to compare version+1 with version to make sure + * that the only changes made in going to version are conformant + * with what was allowed in version+1. The production of version + * might have involved signing a signature field and locking down + * more fields - this means that taking the list of locked things + * from version rather than version+1 will give us bad results! */ + locked = pdf_find_locked_fields(ctx, doc, unsaved_versions+version+1); + + fz_try(ctx) + { + if (!locked->all && locked->includes.len == 0 && locked->p == 0) + { + /* If nothing is locked at all, then all changes are permissible. */ + result = 1; + } + else + result = validate_locked_fields(ctx, doc, unsaved_versions+version, locked); + } + fz_always(ctx) + pdf_drop_locked_fields(ctx, locked); + fz_catch(ctx) + fz_rethrow(ctx); + + return result; +} + +int +pdf_validate_change_history(fz_context *ctx, pdf_document *doc) +{ + int num_versions = pdf_count_versions(ctx, doc); + int v; + + if (num_versions < 2) + return 0; /* Unless there are at least 2 versions, there have been no updates. */ + + for(v = num_versions - 2; v >= 0; v--) + { + if (!pdf_validate_changes(ctx, doc, v)) + return v+1; + } + return 0; +} + +/* Return the version that obj appears in, or -1 for not found. */ +static int +pdf_find_incremental_update_num_for_obj(fz_context *ctx, pdf_document *doc, pdf_obj *obj) +{ + pdf_xref *xref = NULL; + pdf_xref_subsec *sub; + int i, j; + + if (obj == NULL) + return -1; + + /* obj needs to be indirect for us to get a num out of it. */ + i = pdf_to_num(ctx, obj); + if (i <= 0) + return -1; + + /* obj can't be indirect below, so resolve it here. */ + obj = pdf_resolve_indirect_chain(ctx, obj); + + /* Find the first xref section where the entry is defined. */ + for (j = 0; j < doc->num_xref_sections; j++) + { + xref = &doc->xref_sections[j]; + + if (i < xref->num_objects) + { + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + pdf_xref_entry *entry; + + if (i < sub->start || i >= sub->start + sub->len) + continue; + + entry = &sub->table[i - sub->start]; + if (entry->obj == obj) + return j; + } + } + } + return -1; +} + +int pdf_find_version_for_obj(fz_context *ctx, pdf_document *doc, pdf_obj *obj) +{ + int v = pdf_find_incremental_update_num_for_obj(ctx, doc, obj); + int n; + + if (v == -1) + return -1; + + n = pdf_count_versions(ctx, doc) + pdf_count_unsaved_versions(ctx, doc); + if (v > n) + return n; + + return v; +} + +int pdf_validate_signature(fz_context *ctx, pdf_annot *widget) +{ + pdf_document *doc; + int unsaved_versions, num_versions, version, i; + pdf_locked_fields *locked = NULL; + int o_xref_base; + + if (!widget->page) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation not bound to any page"); + + doc = widget->page->doc; + unsaved_versions = pdf_count_unsaved_versions(ctx, doc); + num_versions = pdf_count_versions(ctx, doc) + unsaved_versions; + version = pdf_find_version_for_obj(ctx, doc, widget->obj); + + if (version > num_versions-1) + version = num_versions-1; + + /* Get the locked definition from the object when it was signed. */ + o_xref_base = doc->xref_base; + doc->xref_base = version; + + fz_var(locked); /* Not really needed, but it stops warnings */ + + fz_try(ctx) + { + locked = pdf_find_locked_fields_for_sig(ctx, doc, widget->obj); + for (i = version-1; i >= unsaved_versions; i--) + { + doc->xref_base = i; + if (!validate_locked_fields(ctx, doc, i, locked)) + break; + } + } + fz_always(ctx) + { + doc->xref_base = o_xref_base; + pdf_drop_locked_fields(ctx, locked); + } + fz_catch(ctx) + fz_rethrow(ctx); + + return i+1-unsaved_versions; +} + +int pdf_was_pure_xfa(fz_context *ctx, pdf_document *doc) +{ + int num_unsaved_versions = pdf_count_unsaved_versions(ctx, doc); + int num_versions = pdf_count_versions(ctx, doc); + int v; + int o_xref_base = doc->xref_base; + int pure_xfa = 0; + + fz_var(pure_xfa); + + fz_try(ctx) + { + for(v = num_versions + num_unsaved_versions; !pure_xfa && v >= num_unsaved_versions; v--) + { + pdf_obj *o; + doc->xref_base = v; + o = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm"); + /* If we find a version that had an empty Root/AcroForm/Fields, but had a + * Root/AcroForm/XFA entry, then we deduce that this was at one time a + * pure XFA form. */ + if (pdf_array_len(ctx, pdf_dict_get(ctx, o, PDF_NAME(Fields))) == 0 && + pdf_dict_get(ctx, o, PDF_NAME(XFA)) != NULL) + pure_xfa = 1; + } + } + fz_always(ctx) + doc->xref_base = o_xref_base; + fz_catch(ctx) + fz_rethrow(ctx); + + return pure_xfa; +} + +pdf_xref *pdf_new_local_xref(fz_context *ctx, pdf_document *doc) +{ + int n = pdf_xref_len(ctx, doc); + pdf_xref *xref = fz_malloc_struct(ctx, pdf_xref); + + xref->subsec = NULL; + xref->num_objects = n; + xref->trailer = NULL; + xref->pre_repair_trailer = NULL; + xref->unsaved_sigs = NULL; + xref->unsaved_sigs_end = NULL; + + fz_try(ctx) + { + xref->subsec = fz_malloc_struct(ctx, pdf_xref_subsec); + xref->subsec->len = n; + xref->subsec->start = 0; + xref->subsec->table = fz_malloc_struct_array(ctx, n, pdf_xref_entry); + xref->subsec->next = NULL; + } + fz_catch(ctx) + { + fz_free(ctx, xref->subsec); + fz_free(ctx, xref); + fz_rethrow(ctx); + } + + return xref; +} + +void pdf_drop_local_xref(fz_context *ctx, pdf_xref *xref) +{ + if (xref == NULL) + return; + + pdf_drop_xref_subsec(ctx, xref); + + fz_free(ctx, xref); +} + +void pdf_drop_local_xref_and_resources(fz_context *ctx, pdf_document *doc) +{ + pdf_purge_local_resources(ctx, doc); + pdf_purge_locals_from_store(ctx, doc); + pdf_drop_local_xref(ctx, doc->local_xref); + doc->local_xref = NULL; + doc->resynth_required = 1; +} + +void +pdf_debug_doc_changes(fz_context *ctx, pdf_document *doc) +{ + int i, j; + + if (doc->num_incremental_sections == 0) + fz_write_printf(ctx, fz_stddbg(ctx), "No incremental xrefs"); + else + { + for (i = 0; i < doc->num_incremental_sections; i++) + { + pdf_xref *xref = &doc->xref_sections[i]; + pdf_xref_subsec *sub; + + fz_write_printf(ctx, fz_stddbg(ctx), "Incremental xref:\n"); + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + fz_write_printf(ctx, fz_stddbg(ctx), " Objects %d->%d\n", sub->start, sub->start + sub->len - 1); + for (j = 0; j < sub->len; j++) + { + pdf_xref_entry *e = &sub->table[j]; + if (e->type == 0) + continue; + fz_write_printf(ctx, fz_stddbg(ctx), "%d %d obj (%c)\n", j + sub->start, e->gen, e->type); + pdf_debug_obj(ctx, e->obj); + fz_write_printf(ctx, fz_stddbg(ctx), "\nendobj\n"); + } + } + } + } + + if (doc->local_xref == NULL) + fz_write_printf(ctx, fz_stddbg(ctx), "No local xref"); + else + { + for (i = 0; i < doc->num_incremental_sections; i++) + { + pdf_xref *xref = doc->local_xref; + pdf_xref_subsec *sub; + + fz_write_printf(ctx, fz_stddbg(ctx), "Local xref (%sin force):\n", doc->local_xref_nesting == 0 ? "not " : ""); + for (sub = xref->subsec; sub != NULL; sub = sub->next) + { + fz_write_printf(ctx, fz_stddbg(ctx), " Objects %d->%d\n", sub->start, sub->start + sub->len - 1); + for (j = 0; j < sub->len; j++) + { + pdf_xref_entry *e = &sub->table[j]; + if (e->type == 0) + continue; + fz_write_printf(ctx, fz_stddbg(ctx), "%d %d obj (%c)\n", j + sub->start, e->gen, e->type); + pdf_debug_obj(ctx, e->obj); + fz_write_printf(ctx, fz_stddbg(ctx), "\nendobj\n"); + } + } + } + } + +} + +pdf_obj * +pdf_metadata(fz_context *ctx, pdf_document *doc) +{ + int initial = doc->xref_base; + pdf_obj *obj = NULL; + + fz_var(obj); + + fz_try(ctx) + { + do + { + pdf_obj *root = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root)); + obj = pdf_dict_get(ctx, root, PDF_NAME(Metadata)); + if (obj) + break; + doc->xref_base++; + } + while (doc->xref_base < doc->num_xref_sections); + } + fz_always(ctx) + doc->xref_base = initial; + fz_catch(ctx) + fz_rethrow(ctx); + + return obj; +} + +int pdf_obj_is_incremental(fz_context *ctx, pdf_obj *obj) +{ + pdf_document *doc = pdf_get_bound_document(ctx, obj); + int v; + + if (doc == NULL || doc->num_incremental_sections == 0) + return 0; + + v = pdf_find_incremental_update_num_for_obj(ctx, doc, obj); + + return (v == 0); +} + +void pdf_minimize_document(fz_context *ctx, pdf_document *doc) +{ + int i; + + /* Don't throw anything away if we've done a repair! */ + if (doc == NULL || doc->repair_attempted) + return; + + /* Don't throw anything away in the incremental section, as that's where + * all our changes will be. */ + for (i = doc->num_incremental_sections; i < doc->num_xref_sections; i++) + { + pdf_xref *xref = &doc->xref_sections[i]; + pdf_xref_subsec *sub; + + for (sub = xref->subsec; sub; sub = sub->next) + { + int len = sub->len; + int j; + for (j = 0; j < len; j++) + { + pdf_xref_entry *e = &sub->table[j]; + if (e->obj == NULL) + continue; + e->obj = pdf_drop_singleton_obj(ctx, e->obj); + } + } + } +} + +void pdf_repair_xref(fz_context *ctx, pdf_document *doc) +{ + pdf_repair_xref_aux(ctx, doc, pdf_prime_xref_index); +}
