Catalog.cc 26.6 KB
Newer Older
Kristian Høgsberg's avatar
Kristian Høgsberg committed
1 2 3 4
//========================================================================
//
// Catalog.cc
//
5
// Copyright 1996-2007 Glyph & Cog, LLC
Kristian Høgsberg's avatar
Kristian Høgsberg committed
6 7 8
//
//========================================================================

9 10 11 12
//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
13 14 15
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
16
// Copyright (C) 2005 Kristian Høgsberg <krh@redhat.com>
17
// Copyright (C) 2005-2013, 2015, 2017 Albert Astals Cid <aacid@kde.org>
18 19 20 21
// Copyright (C) 2005 Jeff Muizelaar <jrmuizel@nit.ca>
// Copyright (C) 2005 Jonathan Blandford <jrb@redhat.com>
// Copyright (C) 2005 Marco Pesenti Gritti <mpg@redhat.com>
// Copyright (C) 2005, 2006, 2008 Brad Hards <bradh@frogmouth.net>
Albert Astals Cid's avatar
Albert Astals Cid committed
22
// Copyright (C) 2006, 2008, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
23
// Copyright (C) 2007 Julien Rebetez <julienr@svn.gnome.org>
Albert Astals Cid's avatar
Albert Astals Cid committed
24
// Copyright (C) 2008, 2011 Pino Toscano <pino@kde.org>
25
// Copyright (C) 2009 Ilya Gorenbein <igorenbein@finjan.com>
26
// Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
27
// Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
Thomas Freitag's avatar
Thomas Freitag committed
28
// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
29
// Copyright (C) 2013 Julien Nabet <serval2412@yahoo.fr>
Albert Astals Cid's avatar
Albert Astals Cid committed
30
// Copyright (C) 2013 Adrian Perez de Castro <aperez@igalia.com>
31
// Copyright (C) 2013, 2017 Adrian Johnson <ajohnson@redneon.com>
32
// Copyright (C) 2013 José Aliste <jaliste@src.gnome.org>
Ed Porras's avatar
Ed Porras committed
33
// Copyright (C) 2014 Ed Porras <ed@moto-research.com>
34
// Copyright (C) 2015 Even Rouault <even.rouault@spatialys.com>
35
// Copyright (C) 2016 Masamichi Hosoda <trueroad@trueroad.jp>
Albert Astals Cid's avatar
Albert Astals Cid committed
36
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
37 38 39 40 41 42
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

Kristian Høgsberg's avatar
Kristian Høgsberg committed
43 44 45 46 47 48 49
#include <config.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stddef.h>
50
#include <stdlib.h>
Kristian Høgsberg's avatar
Kristian Høgsberg committed
51 52
#include "goo/gmem.h"
#include "Object.h"
53
#include "PDFDoc.h"
Kristian Høgsberg's avatar
Kristian Høgsberg committed
54 55 56 57 58 59
#include "XRef.h"
#include "Array.h"
#include "Dict.h"
#include "Page.h"
#include "Error.h"
#include "Link.h"
60
#include "PageLabelInfo.h"
Kristian Høgsberg's avatar
Kristian Høgsberg committed
61
#include "Catalog.h"
62
#include "Form.h"
63
#include "OptionalContent.h"
64
#include "ViewerPreferences.h"
65
#include "FileSpec.h"
66
#include "StructTreeRoot.h"
Kristian Høgsberg's avatar
Kristian Høgsberg committed
67

68
#ifdef MULTITHREADED
69
#  define catalogLocker()   MutexLocker locker(&mutex)
Thomas Freitag's avatar
Thomas Freitag committed
70
#else
71
#  define catalogLocker()
Thomas Freitag's avatar
Thomas Freitag committed
72
#endif
Kristian Høgsberg's avatar
Kristian Høgsberg committed
73 74 75 76
//------------------------------------------------------------------------
// Catalog
//------------------------------------------------------------------------

77
Catalog::Catalog(PDFDoc *docA) {
78
#ifdef MULTITHREADED
Thomas Freitag's avatar
Thomas Freitag committed
79 80
  gInitMutex(&mutex);
#endif
Kristian Høgsberg's avatar
Kristian Høgsberg committed
81
  ok = gTrue;
82 83
  doc = docA;
  xref = doc->getXRef();
84 85
  pages = nullptr;
  pageRefs = nullptr;
Hib Eris's avatar
Hib Eris committed
86 87
  numPages = -1;
  pagesSize = 0;
88 89 90 91
  baseURI = nullptr;
  pageLabelInfo = nullptr;
  form = nullptr;
  optContent = nullptr;
92 93
  pageMode = pageModeNull;
  pageLayout = pageLayoutNull;
94 95 96 97 98 99 100 101 102 103
  destNameTree = nullptr;
  embeddedFileNameTree = nullptr;
  jsNameTree = nullptr;
  viewerPrefs = nullptr;
  structTreeRoot = nullptr;

  pagesList = nullptr;
  pagesRefList = nullptr;
  attrsList = nullptr;
  kidsIdxList = nullptr;
Hib Eris's avatar
Hib Eris committed
104
  lastCachedPage = 0;
105
  markInfo = markInfoNull;
Hib Eris's avatar
Hib Eris committed
106

Albert Astals Cid's avatar
Albert Astals Cid committed
107
  Object catDict = xref->getCatalog();
Kristian Høgsberg's avatar
Kristian Høgsberg committed
108
  if (!catDict.isDict()) {
109
    error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
Albert Astals Cid's avatar
Albert Astals Cid committed
110 111
    ok = gFalse;
    return;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
112
  }
113
  // get the AcroForm dictionary
Albert Astals Cid's avatar
Albert Astals Cid committed
114
  acroForm = catDict.dictLookup("AcroForm");
115

Kristian Høgsberg's avatar
Kristian Høgsberg committed
116
  // read base URI
Albert Astals Cid's avatar
Albert Astals Cid committed
117 118 119 120
  Object obj = catDict.dictLookup("URI");
  if (obj.isDict()) {
    Object obj2 = obj.dictLookup("Base");
    if (obj2.isString()) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
121 122 123 124
      baseURI = obj2.getString()->copy();
    }
  }

125
  // get the Optional Content dictionary
Albert Astals Cid's avatar
Albert Astals Cid committed
126 127
  Object optContentProps = catDict.dictLookup("OCProperties");
  if (optContentProps.isDict()) {
128
    optContent = new OCGs(&optContentProps, xref);
129 130
    if (!optContent->isOk ()) {
      delete optContent;
131
      optContent = nullptr;
132
    }
133
  }
134

135
  // actions
Albert Astals Cid's avatar
Albert Astals Cid committed
136
  additionalActions = catDict.dictLookupNF("AA");
137

138
  // get the ViewerPreferences dictionary
Albert Astals Cid's avatar
Albert Astals Cid committed
139
  viewerPreferences = catDict.dictLookup("ViewerPreferences");
Kristian Høgsberg's avatar
Kristian Høgsberg committed
140 141 142
}

Catalog::~Catalog() {
Hib Eris's avatar
Hib Eris committed
143 144
  delete kidsIdxList;
  if (attrsList) {
Albert Astals Cid's avatar
Albert Astals Cid committed
145
    std::vector<PageAttrs *>::iterator it;
146
    for (it = attrsList->begin() ; it != attrsList->end(); ++it ) {
Hib Eris's avatar
Hib Eris committed
147 148 149 150 151
      delete *it;
    }
    delete attrsList;
  }
  delete pagesRefList;
152
  delete pagesList;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
153
  if (pages) {
Hib Eris's avatar
Hib Eris committed
154
    for (int i = 0; i < pagesSize; ++i) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
155 156 157 158 159 160
      if (pages[i]) {
	delete pages[i];
      }
    }
    gfree(pages);
  }
161
  gfree(pageRefs);
Hib Eris's avatar
Hib Eris committed
162 163 164
  delete destNameTree;
  delete embeddedFileNameTree;
  delete jsNameTree;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
165 166 167
  if (baseURI) {
    delete baseURI;
  }
168
  delete pageLabelInfo;
169
  delete form;
170
  delete optContent;
171
  delete viewerPrefs;
172
  delete structTreeRoot;
173
#ifdef MULTITHREADED
Thomas Freitag's avatar
Thomas Freitag committed
174 175
  gDestroyMutex(&mutex);
#endif
Kristian Høgsberg's avatar
Kristian Høgsberg committed
176 177 178
}

GooString *Catalog::readMetadata() {
179
  catalogLocker();
Hib Eris's avatar
Hib Eris committed
180
  if (metadata.isNone()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
181
    Object catDict = xref->getCatalog();
Hib Eris's avatar
Hib Eris committed
182
    if (catDict.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
183
      metadata = catDict.dictLookup("Metadata");
Hib Eris's avatar
Hib Eris committed
184
    } else {
185
      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
Albert Astals Cid's avatar
Albert Astals Cid committed
186
      metadata.setToNull();
Hib Eris's avatar
Hib Eris committed
187 188 189
    }
  }

Kristian Høgsberg's avatar
Kristian Høgsberg committed
190
  if (!metadata.isStream()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
191
    return nullptr;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
192
  }
Albert Astals Cid's avatar
Albert Astals Cid committed
193 194
  Object obj = metadata.streamGetDict()->lookup("Subtype");
  if (!obj.isName("XML")) {
195
    error(errSyntaxWarning, -1, "Unknown Metadata type: '{0:s}'",
Kristian Høgsberg's avatar
Kristian Høgsberg committed
196 197
	  obj.isName() ? obj.getName() : "???");
  }
Albert Astals Cid's avatar
Albert Astals Cid committed
198
  GooString *s = new GooString();
199
  metadata.getStream()->fillGooString(s);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
200 201 202 203
  metadata.streamClose();
  return s;
}

Hib Eris's avatar
Hib Eris committed
204 205
Page *Catalog::getPage(int i)
{
206
  if (i < 1) return nullptr;
Hib Eris's avatar
Hib Eris committed
207

208
  catalogLocker();
Hib Eris's avatar
Hib Eris committed
209
  if (i > lastCachedPage) {
Thomas Freitag's avatar
Thomas Freitag committed
210 211
     GBool cached = cachePageTree(i);
     if ( cached == gFalse) {
212
       return nullptr;
Thomas Freitag's avatar
Thomas Freitag committed
213
     }
Hib Eris's avatar
Hib Eris committed
214 215 216 217
  }
  return pages[i-1];
}

218
Ref *Catalog::getPageRef(int i)
Hib Eris's avatar
Hib Eris committed
219
{
220
  if (i < 1) return nullptr;
Hib Eris's avatar
Hib Eris committed
221

222
  catalogLocker();
Hib Eris's avatar
Hib Eris committed
223
  if (i > lastCachedPage) {
Thomas Freitag's avatar
Thomas Freitag committed
224 225
     GBool cached = cachePageTree(i);
     if ( cached == gFalse) {
226
       return nullptr;
Thomas Freitag's avatar
Thomas Freitag committed
227
     }
Hib Eris's avatar
Hib Eris committed
228 229 230 231 232 233
  }
  return &pageRefs[i-1];
}

GBool Catalog::cachePageTree(int page)
{
234
  if (pagesList == nullptr) {
Hib Eris's avatar
Hib Eris committed
235 236 237

    Ref pagesRef;

Albert Astals Cid's avatar
Albert Astals Cid committed
238
    Object catDict = xref->getCatalog();
Hib Eris's avatar
Hib Eris committed
239

240
    if (catDict.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
241 242
      Object pagesDictRef = catDict.dictLookupNF("Pages");
      if (pagesDictRef.isRef() &&
243 244 245 246
          pagesDictRef.getRefNum() >= 0 &&
          pagesDictRef.getRefNum() < xref->getNumObjects()) {
        pagesRef = pagesDictRef.getRef();
      } else {
247
        error(errSyntaxError, -1, "Catalog dictionary does not contain a valid \"Pages\" entry");
248 249
        return gFalse;
      }
Hib Eris's avatar
Hib Eris committed
250
    } else {
251
      error(errSyntaxError, -1, "Could not find catalog dictionary");
252
      return gFalse;
Hib Eris's avatar
Hib Eris committed
253 254
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
255
    Object obj = catDict.dictLookup("Pages");
Hib Eris's avatar
Hib Eris committed
256 257
    // This should really be isDict("Pages"), but I've seen at least one
    // PDF file where the /Type entry is missing.
258
    if (!obj.isDict()) {
259
      error(errSyntaxError, -1, "Top-level pages object is wrong type ({0:s})", obj.getTypeName());
Hib Eris's avatar
Hib Eris committed
260 261 262
      return gFalse;
    }

263
    pagesSize = getNumPages();
264 265
    pages = (Page **)gmallocn_checkoverflow(pagesSize, sizeof(Page *));
    pageRefs = (Ref *)gmallocn_checkoverflow(pagesSize, sizeof(Ref));
266
    if (pages == nullptr || pageRefs == nullptr ) {
267 268 269 270
      error(errSyntaxError, -1, "Cannot allocate page cache");
      pagesSize = 0;
      return gFalse;
    }
Hib Eris's avatar
Hib Eris committed
271
    for (int i = 0; i < pagesSize; ++i) {
272
      pages[i] = nullptr;
Hib Eris's avatar
Hib Eris committed
273 274 275 276
      pageRefs[i].num = -1;
      pageRefs[i].gen = -1;
    }

277
    attrsList = new std::vector<PageAttrs *>();
278
    attrsList->push_back(new PageAttrs(nullptr, obj.getDict()));
279 280
    pagesList = new std::vector<Object>();
    pagesList->push_back(std::move(obj));
Albert Astals Cid's avatar
Albert Astals Cid committed
281
    pagesRefList = new std::vector<Ref>();
Hib Eris's avatar
Hib Eris committed
282
    pagesRefList->push_back(pagesRef);
Albert Astals Cid's avatar
Albert Astals Cid committed
283
    kidsIdxList = new std::vector<int>();
Hib Eris's avatar
Hib Eris committed
284 285 286 287 288 289 290 291 292 293 294
    kidsIdxList->push_back(0);
    lastCachedPage = 0;

  }

  while(1) {

    if (page <= lastCachedPage) return gTrue;

    if (pagesList->empty()) return gFalse;

295 296
    Object pagesDict = pagesList->back().copy();
    Object kids = pagesDict.dictLookup("Kids");
Hib Eris's avatar
Hib Eris committed
297
    if (!kids.isArray()) {
298
      error(errSyntaxError, -1, "Kids object (page {0:d}) is wrong type ({1:s})",
Hib Eris's avatar
Hib Eris committed
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
            lastCachedPage+1, kids.getTypeName());
      return gFalse;
    }

    int kidsIdx = kidsIdxList->back();
    if (kidsIdx >= kids.arrayGetLength()) {
       pagesList->pop_back();
       pagesRefList->pop_back();
       delete attrsList->back();
       attrsList->pop_back();
       kidsIdxList->pop_back();
       if (!kidsIdxList->empty()) kidsIdxList->back()++;
       continue;
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
314
    Object kidRef = kids.arrayGetNF(kidsIdx);
Hib Eris's avatar
Hib Eris committed
315
    if (!kidRef.isRef()) {
316
      error(errSyntaxError, -1, "Kid object (page {0:d}) is not an indirect reference ({1:s})",
Hib Eris's avatar
Hib Eris committed
317 318 319 320
            lastCachedPage+1, kidRef.getTypeName());
      return gFalse;
    }

Hib Eris's avatar
Hib Eris committed
321
    GBool loop = gFalse;;
Hib Eris's avatar
Hib Eris committed
322 323
    for (size_t i = 0; i < pagesRefList->size(); i++) {
      if (((*pagesRefList)[i]).num == kidRef.getRefNum()) {
Hib Eris's avatar
Hib Eris committed
324 325
         loop = gTrue;
         break;
326 327
      }
    }
Hib Eris's avatar
Hib Eris committed
328
    if (loop) {
329
      error(errSyntaxError, -1, "Loop in Pages tree");
Hib Eris's avatar
Hib Eris committed
330 331 332
      kidsIdxList->back()++;
      continue;
    }
Hib Eris's avatar
Hib Eris committed
333

Albert Astals Cid's avatar
Albert Astals Cid committed
334
    Object kid = kids.arrayGet(kidsIdx);
335
    if (kid.isDict("Page") || (kid.isDict() && !kid.getDict()->hasKey("Kids"))) {
Hib Eris's avatar
Hib Eris committed
336
      PageAttrs *attrs = new PageAttrs(attrsList->back(), kid.getDict());
337
      Page *p = new Page(doc, lastCachedPage+1, &kid,
Hib Eris's avatar
Hib Eris committed
338 339
                     kidRef.getRef(), attrs, form);
      if (!p->isOk()) {
340
        error(errSyntaxError, -1, "Failed to create page (page {0:d})", lastCachedPage+1);
Hib Eris's avatar
Hib Eris committed
341 342
        delete p;
        return gFalse;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
343
      }
Hib Eris's avatar
Hib Eris committed
344 345

      if (lastCachedPage >= numPages) {
346
        error(errSyntaxError, -1, "Page count in top-level pages object is incorrect");
347
        delete p;
Hib Eris's avatar
Hib Eris committed
348
        return gFalse;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
349
      }
Hib Eris's avatar
Hib Eris committed
350 351 352 353 354 355 356 357

      pages[lastCachedPage] = p;
      pageRefs[lastCachedPage].num = kidRef.getRefNum();
      pageRefs[lastCachedPage].gen = kidRef.getRefGen();

      lastCachedPage++;
      kidsIdxList->back()++;

Kristian Høgsberg's avatar
Kristian Høgsberg committed
358 359 360
    // This should really be isDict("Pages"), but I've seen at least one
    // PDF file where the /Type entry is missing.
    } else if (kid.isDict()) {
Hib Eris's avatar
Hib Eris committed
361 362
      attrsList->push_back(new PageAttrs(attrsList->back(), kid.getDict()));
      pagesRefList->push_back(kidRef.getRef());
363
      pagesList->push_back(std::move(kid));
Hib Eris's avatar
Hib Eris committed
364
      kidsIdxList->push_back(0);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
365
    } else {
366
      error(errSyntaxError, -1, "Kid object (page {0:d}) is wrong type ({1:s})",
Hib Eris's avatar
Hib Eris committed
367 368
            lastCachedPage+1, kid.getTypeName());
      kidsIdxList->back()++;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
369 370 371
    }
  }

Hib Eris's avatar
Hib Eris committed
372
  return gFalse;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
373 374
}

375
int Catalog::findPage(int num, int gen) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
376 377
  int i;

378 379
  for (i = 0; i < getNumPages(); ++i) {
    Ref *ref = getPageRef(i+1);
380
    if (ref != nullptr && ref->num == num && ref->gen == gen)
Kristian Høgsberg's avatar
Kristian Høgsberg committed
381 382 383 384 385
      return i + 1;
  }
  return 0;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
386
LinkDest *Catalog::findDest(const GooString *name) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
387
  // try named destination dictionary then name tree
Hib Eris's avatar
Hib Eris committed
388
  if (getDests()->isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
389 390
    Object obj1 = getDests()->dictLookup(name->getCString());
    return createLinkDest(&obj1);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
391 392
  }

Albert Astals Cid's avatar
Albert Astals Cid committed
393 394 395
  catalogLocker();
  Object obj2 = getDestNameTree()->lookup(name);
  return createLinkDest(&obj2);
396 397 398 399
}

LinkDest *Catalog::createLinkDest(Object *obj)
{
Albert Astals Cid's avatar
Albert Astals Cid committed
400
  LinkDest *dest = nullptr;
401 402 403
  if (obj->isArray()) {
    dest = new LinkDest(obj->getArray());
  } else if (obj->isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
404 405
    Object obj2 = obj->dictLookup("D");
    if (obj2.isArray())
Kristian Høgsberg's avatar
Kristian Høgsberg committed
406 407
      dest = new LinkDest(obj2.getArray());
    else
408
      error(errSyntaxWarning, -1, "Bad named destination value");
Kristian Høgsberg's avatar
Kristian Høgsberg committed
409
  } else {
410
    error(errSyntaxWarning, -1, "Bad named destination value");
Kristian Høgsberg's avatar
Kristian Høgsberg committed
411 412 413
  }
  if (dest && !dest->isOk()) {
    delete dest;
414
    dest = nullptr;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
415 416 417 418 419
  }

  return dest;
}

420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
int Catalog::numDests()
{
  Object *obj;

  obj= getDests();
  if (!obj->isDict()) {
    return 0;
  }
  return obj->dictGetLength();
}

char *Catalog::getDestsName(int i)
{
  Object *obj;

  obj= getDests();
  if (!obj->isDict()) {
437
    return nullptr;
438 439 440 441 442 443
  }
  return obj->dictGetKey(i);
}

LinkDest *Catalog::getDestsDest(int i)
{
Albert Astals Cid's avatar
Albert Astals Cid committed
444
  Object *obj = getDests();
445
  if (!obj->isDict()) {
446
    return nullptr;
447
  }
Albert Astals Cid's avatar
Albert Astals Cid committed
448 449
  Object obj1 = obj->dictGetVal(i);
  return createLinkDest(&obj1);
450 451
}

452 453 454 455 456
LinkDest *Catalog::getDestNameTreeDest(int i)
{
  Object obj;

  catalogLocker();
457 458
  Object *aux = getDestNameTree()->getValue(i);
  if (aux) {
Albert Astals Cid's avatar
Albert Astals Cid committed
459
    obj = aux->fetch(xref);
460
  }
Albert Astals Cid's avatar
Albert Astals Cid committed
461
  return createLinkDest(&obj);
462 463
}

464
FileSpec *Catalog::embeddedFile(int i)
465
{
466
    catalogLocker();
467
    Object *obj = getEmbeddedFileNameTree()->getValue(i);
468
    FileSpec *embeddedFile = nullptr;
469
    if (obj->isRef()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
470 471
      Object fsDict = obj->fetch(xref);
      embeddedFile = new FileSpec(&fsDict);
472 473
    } else if (obj->isDict()) {
      embeddedFile = new FileSpec(obj);
474
    } else {
475 476
      Object null;
      embeddedFile = new FileSpec(&null);
477 478 479 480
    }
    return embeddedFile;
}

481 482
GooString *Catalog::getJS(int i)
{
483 484 485
  Object obj;
  // getJSNameTree()->getValue(i) returns a shallow copy of the object so we
  // do not need to free it
486
  catalogLocker();
487 488
  Object *aux = getJSNameTree()->getValue(i);
  if (aux) {
Albert Astals Cid's avatar
Albert Astals Cid committed
489
    obj = aux->fetch(xref);
490
  }
491 492

  if (!obj.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
493
    return nullptr;
494
  }
Albert Astals Cid's avatar
Albert Astals Cid committed
495 496 497
  Object obj2 = obj.dictLookup("S");
  if (!obj2.isName()) {
    return nullptr;
498 499
  }
  if (strcmp(obj2.getName(), "JavaScript")) {
Albert Astals Cid's avatar
Albert Astals Cid committed
500
    return nullptr;
501
  }
Albert Astals Cid's avatar
Albert Astals Cid committed
502 503
  obj2 = obj.dictLookup("JS");
  GooString *js = nullptr;
504 505 506 507 508 509
  if (obj2.isString()) {
    js = new GooString(obj2.getString());
  }
  else if (obj2.isStream()) {
    Stream *stream = obj2.getStream();
    js = new GooString();
510
    stream->fillGooString(js);
511 512
  }
  return js;
513 514 515 516
}

Catalog::PageMode Catalog::getPageMode() {

517
  catalogLocker();
518 519 520 521
  if (pageMode == pageModeNull) {

    pageMode = pageModeNone;

Albert Astals Cid's avatar
Albert Astals Cid committed
522
    Object catDict = xref->getCatalog();
523
    if (!catDict.isDict()) {
524
      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
525 526 527
      return pageMode;
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
528 529
    Object obj = catDict.dictLookup("PageMode");
    if (obj.isName()) {
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
      if (obj.isName("UseNone"))
        pageMode = pageModeNone;
      else if (obj.isName("UseOutlines"))
        pageMode = pageModeOutlines;
      else if (obj.isName("UseThumbs"))
        pageMode = pageModeThumbs;
      else if (obj.isName("FullScreen"))
        pageMode = pageModeFullScreen;
      else if (obj.isName("UseOC"))
        pageMode = pageModeOC;
      else if (obj.isName("UseAttachments"))
        pageMode = pageModeAttach;
    }
  }
  return pageMode;
}

Catalog::PageLayout Catalog::getPageLayout() {

549
  catalogLocker();
550 551 552 553
  if (pageLayout == pageLayoutNull) {

    pageLayout = pageLayoutNone;

Albert Astals Cid's avatar
Albert Astals Cid committed
554
    Object catDict = xref->getCatalog();
555
    if (!catDict.isDict()) {
556
      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
557 558 559 560
      return pageLayout;
    }

    pageLayout = pageLayoutNone;
Albert Astals Cid's avatar
Albert Astals Cid committed
561 562
    Object obj = catDict.dictLookup("PageLayout");
    if (obj.isName()) {
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
      if (obj.isName("SinglePage"))
        pageLayout = pageLayoutSinglePage;
      if (obj.isName("OneColumn"))
        pageLayout = pageLayoutOneColumn;
      if (obj.isName("TwoColumnLeft"))
        pageLayout = pageLayoutTwoColumnLeft;
      if (obj.isName("TwoColumnRight"))
        pageLayout = pageLayoutTwoColumnRight;
      if (obj.isName("TwoPageLeft"))
        pageLayout = pageLayoutTwoPageLeft;
      if (obj.isName("TwoPageRight"))
        pageLayout = pageLayoutTwoPageRight;
    }
  }
  return pageLayout;
578 579
}

580
NameTree::NameTree()
581 582 583
{
  size = 0;
  length = 0;
584
  entries = nullptr;
585 586
}

Hib Eris's avatar
Hib Eris committed
587 588
NameTree::~NameTree()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
589 590 591 592 593 594
  int i;

  for (i = 0; i < length; i++)
    delete entries[i];

  gfree(entries);
Hib Eris's avatar
Hib Eris committed
595 596
}

597
NameTree::Entry::Entry(Array *array, int index) {
Albert Astals Cid's avatar
Albert Astals Cid committed
598 599 600 601 602
  if (!array->getString(index, &name)) {
    Object aux = array->get(index);
    if (aux.isString())
    {
      name.append(aux.getString());
603
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
604 605 606 607
    else
      error(errSyntaxError, -1, "Invalid page tree");
  }
  value = array->getNF(index + 1);
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
}

NameTree::Entry::~Entry() {
}

void NameTree::addEntry(Entry *entry)
{
  if (length == size) {
    if (length == 0) {
      size = 8;
    } else {
      size *= 2;
    }
    entries = (Entry **) grealloc (entries, sizeof (Entry *) * size);
  }

  entries[length] = entry;
  ++length;
}

628 629 630 631 632 633 634 635
int NameTree::Entry::cmpEntry(const void *voidEntry, const void *voidOtherEntry)
{
  Entry *entry = *(NameTree::Entry **) voidEntry;
  Entry *otherEntry = *(NameTree::Entry **) voidOtherEntry;

  return entry->name.cmp(&otherEntry->name);
}

636 637
void NameTree::init(XRef *xrefA, Object *tree) {
  xref = xrefA;
638 639
  std::set<int> seen;
  parse(tree, seen);
640 641 642
  if (entries && length > 0) {
    qsort(entries, length, sizeof(Entry *), Entry::cmpEntry);
  }
643 644
}

645
void NameTree::parse(Object *tree, std::set<int> &seen) {
646 647
  if (!tree->isDict())
    return;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
648 649

  // leaf node
Albert Astals Cid's avatar
Albert Astals Cid committed
650 651 652
  Object names = tree->dictLookup("Names");
  if (names.isArray()) {
    for (int i = 0; i < names.arrayGetLength(); i += 2) {
653 654 655 656
      NameTree::Entry *entry;

      entry = new Entry(names.getArray(), i);
      addEntry(entry);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
657 658 659 660
    }
  }

  // root or intermediate node
Albert Astals Cid's avatar
Albert Astals Cid committed
661 662 663
  Object kids = tree->dictLookup("Kids");
  if (kids.isArray()) {
    for (int i = 0; i < kids.arrayGetLength(); ++i) {
664 665 666 667 668 669 670 671 672
      Object kidRef = kids.arrayGetNF(i);
      if (kidRef.isRef()) {
	const int numObj = kidRef.getRef().num;
	if (seen.find(numObj) != seen.end()) {
	  error(errSyntaxError, -1, "loop in NameTree (numObj: {0:d})", numObj);
	  continue;
	}
	seen.insert(numObj);
      }
Albert Astals Cid's avatar
Albert Astals Cid committed
673 674
      Object kid = kids.arrayGet(i);
      if (kid.isDict())
675
	parse(&kid, seen);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
676 677
    }
  }
678 679 680 681
}

int NameTree::Entry::cmp(const void *voidKey, const void *voidEntry)
{
682
  GooString *key = (GooString *) voidKey;
683 684
  Entry *entry = *(NameTree::Entry **) voidEntry;

685
  return key->cmp(&entry->name);
686 687
}

Albert Astals Cid's avatar
Albert Astals Cid committed
688
Object NameTree::lookup(const GooString *name)
689
{
690
  Entry **entry;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
691

692 693
  entry = (Entry **) bsearch(name, entries,
			     length, sizeof(Entry *), Entry::cmp);
694
  if (entry != nullptr) {
Albert Astals Cid's avatar
Albert Astals Cid committed
695
    return (*entry)->value.fetch(xref);
696
  } else {
Ed Porras's avatar
Ed Porras committed
697
    error(errSyntaxError, -1, "failed to look up ({0:s})", name->getCString());
Albert Astals Cid's avatar
Albert Astals Cid committed
698
    return Object(objNull);
699 700 701
  }
}

702
Object *NameTree::getValue(int index)
703 704
{
  if (index < length) {
705
    return &entries[index]->value;
706
  } else {
707
    return nullptr;
708 709 710
  }
}

711
GooString *NameTree::getName(int index)
712 713
{
    if (index < length) {
714
	return &entries[index]->name;
715
    } else {
716
	return nullptr;
717 718 719
    }
}

720 721
GBool Catalog::labelToIndex(GooString *label, int *index)
{
722 723
  char *end;

Hib Eris's avatar
Hib Eris committed
724
  PageLabelInfo *pli = getPageLabelInfo();
725
  if (pli != nullptr) {
Hib Eris's avatar
Hib Eris committed
726
    if (!pli->labelToIndex(label, index))
727 728 729 730 731 732 733
      return gFalse;
  } else {
    *index = strtol(label->getCString(), &end, 10) - 1;
    if (*end != '\0')
      return gFalse;
  }

Hib Eris's avatar
Hib Eris committed
734
  if (*index < 0 || *index >= getNumPages())
735 736 737
    return gFalse;

  return gTrue;
738 739 740 741
}

GBool Catalog::indexToLabel(int index, GooString *label)
{
742 743
  char buffer[32];

Hib Eris's avatar
Hib Eris committed
744
  if (index < 0 || index >= getNumPages())
745 746
    return gFalse;

Hib Eris's avatar
Hib Eris committed
747
  PageLabelInfo *pli = getPageLabelInfo();
748
  if (pli != nullptr) {
Hib Eris's avatar
Hib Eris committed
749
    return pli->indexToLabel(index, label);
750 751 752 753 754
  } else {
    snprintf(buffer, sizeof (buffer), "%d", index + 1);
    label->append(buffer);	      
    return gTrue;
  }
755
}
756

757
int Catalog::getNumPages()
Hib Eris's avatar
Hib Eris committed
758
{
759
  catalogLocker();
Hib Eris's avatar
Hib Eris committed
760 761
  if (numPages == -1)
  {
Albert Astals Cid's avatar
Albert Astals Cid committed
762
    Object catDict = xref->getCatalog();
763
    if (!catDict.isDict()) {
764
      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
765 766
      return 0;
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
767
    Object pagesDict = catDict.dictLookup("Pages");
Hib Eris's avatar
Hib Eris committed
768 769 770 771

    // This should really be isDict("Pages"), but I've seen at least one
    // PDF file where the /Type entry is missing.
    if (!pagesDict.isDict()) {
772
      error(errSyntaxError, -1, "Top-level pages object is wrong type ({0:s})",
Hib Eris's avatar
Hib Eris committed
773 774 775 776
          pagesDict.getTypeName());
      return 0;
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
777
    Object obj = pagesDict.dictLookup("Count");
Hib Eris's avatar
Hib Eris committed
778 779
    // some PDF files actually use real numbers here ("/Count 9.0")
    if (!obj.isNum()) {
780
      if (pagesDict.dictIs("Page")) {
Albert Astals Cid's avatar
Albert Astals Cid committed
781
	Object pageRootRef = catDict.dictLookupNF("Pages");
782

Albert Astals Cid's avatar
Albert Astals Cid committed
783
	error(errSyntaxError, -1, "Pages top-level is a single Page. The document is malformed, trying to recover...");
784 785

	Dict *pageDict = pagesDict.getDict();
786 787
	if (pageRootRef.isRef()) {
	  const Ref pageRef = pageRootRef.getRef();
788
	  Page *p = new Page(doc, 1, &pagesDict, pageRef, new PageAttrs(nullptr, pageDict), form);
789 790 791 792 793 794 795 796 797 798 799 800 801 802 803
	  if (p->isOk()) {
	    pages = (Page **)gmallocn(1, sizeof(Page *));
	    pageRefs = (Ref *)gmallocn(1, sizeof(Ref));

	    pages[0] = p;
	    pageRefs[0].num = pageRef.num;
	    pageRefs[0].gen = pageRef.gen;

	    numPages = 1;
	    lastCachedPage = 1;
	    pagesSize = 1;
	  } else {
	    delete p;
	    numPages = 0;
	  }
804 805 806 807 808 809 810 811
	} else {
	  numPages = 0;
	}
      } else {
	error(errSyntaxError, -1, "Page count in top-level pages object is wrong type ({0:s})",
	  obj.getTypeName());
	numPages = 0;
      }
Hib Eris's avatar
Hib Eris committed
812 813
    } else {
      numPages = (int)obj.getNum();
814 815 816 817 818 819 820 821 822 823 824
      if (numPages <= 0) {
        error(errSyntaxError, -1,
              "Invalid page count {0:d}", numPages);
        numPages = 0;
      } else if (numPages > xref->getNumObjects()) {
        error(errSyntaxError, -1,
              "Page count ({0:d}) larger than number of objects ({1:d})",
              numPages, xref->getNumObjects());
        numPages = 0;
      }

Hib Eris's avatar
Hib Eris committed
825 826 827 828 829 830
    }
  }

  return numPages;
}

Hib Eris's avatar
Hib Eris committed
831 832
PageLabelInfo *Catalog::getPageLabelInfo()
{
833
  catalogLocker();
Hib Eris's avatar
Hib Eris committed
834
  if (!pageLabelInfo) {
Albert Astals Cid's avatar
Albert Astals Cid committed
835
    Object catDict = xref->getCatalog();
Hib Eris's avatar
Hib Eris committed
836
    if (!catDict.isDict()) {
837
      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
Albert Astals Cid's avatar
Albert Astals Cid committed
838
      return nullptr;
Hib Eris's avatar
Hib Eris committed
839 840
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
841 842
    Object obj = catDict.dictLookup("PageLabels");
    if (obj.isDict()) {
843
      pageLabelInfo = new PageLabelInfo(&obj, getNumPages());
Hib Eris's avatar
Hib Eris committed
844 845 846 847 848
    }
  }

  return pageLabelInfo;
}
Hib Eris's avatar
Hib Eris committed
849

850
StructTreeRoot *Catalog::getStructTreeRoot()
Hib Eris's avatar
Hib Eris committed
851
{
852
  catalogLocker();
853
  if (!structTreeRoot) {
Albert Astals Cid's avatar
Albert Astals Cid committed
854
    Object catalog = xref->getCatalog();
855 856
    if (!catalog.isDict()) {
      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catalog.getTypeName());
857
      return nullptr;
858 859
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
860 861
    Object root = catalog.dictLookup("StructTreeRoot");
    if (root.isDict("StructTreeRoot")) {
862 863 864 865
      structTreeRoot = new StructTreeRoot(doc, root.getDict());
    }
  }
  return structTreeRoot;
Hib Eris's avatar
Hib Eris committed
866
}
Hib Eris's avatar
Hib Eris committed
867

868 869 870 871 872 873
Guint Catalog::getMarkInfo()
{
  if (markInfo == markInfoNull) {
    markInfo = 0;

    catalogLocker();
Albert Astals Cid's avatar
Albert Astals Cid committed
874
    Object catDict = xref->getCatalog();
875 876

    if (catDict.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
877
      Object markInfoDict = catDict.dictLookup("MarkInfo");
878
      if (markInfoDict.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
879 880
        Object value = markInfoDict.dictLookup("Marked");
        if (value.isBool() && value.getBool())
881 882 883 884
          markInfo |= markInfoMarked;
        else if (!value.isNull())
          error(errSyntaxError, -1, "Marked object is wrong type ({0:s})", value.getTypeName());

Albert Astals Cid's avatar
Albert Astals Cid committed
885 886
        value = markInfoDict.dictLookup("Suspects");
        if (value.isBool() && value.getBool())
887 888 889 890
          markInfo |= markInfoSuspects;
        else if (!value.isNull())
          error(errSyntaxError, -1, "Suspects object is wrong type ({0:s})", value.getTypeName());

Albert Astals Cid's avatar
Albert Astals Cid committed
891 892
        value = markInfoDict.dictLookup("UserProperties");
        if (value.isBool() && value.getBool())
893 894 895 896 897 898 899 900 901 902 903 904 905
          markInfo |= markInfoUserProperties;
        else if (!value.isNull())
          error(errSyntaxError, -1, "UserProperties object is wrong type ({0:s})", value.getTypeName());
      } else if (!markInfoDict.isNull()) {
        error(errSyntaxError, -1, "MarkInfo object is wrong type ({0:s})", markInfoDict.getTypeName());
      }
    } else {
      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
    }
  }
  return markInfo;
}

Hib Eris's avatar
Hib Eris committed
906 907
Object *Catalog::getOutline()
{
908
  catalogLocker();
Hib Eris's avatar
Hib Eris committed
909 910
  if (outline.isNone())
  {
Albert Astals Cid's avatar
Albert Astals Cid committed
911
     Object catDict = xref->getCatalog();
Hib Eris's avatar
Hib Eris committed
912
     if (catDict.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
913
       outline = catDict.dictLookup("Outlines");
Hib Eris's avatar
Hib Eris committed
914
     } else {
915
       error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
Albert Astals Cid's avatar
Albert Astals Cid committed
916
       outline.setToNull();
Hib Eris's avatar
Hib Eris committed
917 918 919 920 921 922
     }
  }

  return &outline;
}

Hib Eris's avatar
Hib Eris committed
923 924
Object *Catalog::getDests()
{
925
  catalogLocker();
Hib Eris's avatar
Hib Eris committed
926 927
  if (dests.isNone())
  {
Albert Astals Cid's avatar
Albert Astals Cid committed
928
     Object catDict = xref->getCatalog();
Hib Eris's avatar
Hib Eris committed
929
     if (catDict.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
930
       dests = catDict.dictLookup("Dests");
Hib Eris's avatar
Hib Eris committed
931
     } else {
932
       error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
Albert Astals Cid's avatar
Albert Astals Cid committed
933
       dests.setToNull();
Hib Eris's avatar
Hib Eris committed
934 935 936 937 938 939
     }
  }

  return &dests;
}

940 941 942 943 944 945
Catalog::FormType Catalog::getFormType()
{
  Object xfa;
  FormType res = NoForm;

  if (acroForm.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
946
    xfa = acroForm.dictLookup("XFA");
947 948 949 950 951 952 953 954 955 956
    if (xfa.isStream() || xfa.isArray()) {
      res = XfaForm;
    } else {
      res = AcroForm;
    }
  }

  return res;
}

957
Form *Catalog::getForm()
Hib Eris's avatar
Hib Eris committed
958
{
959
  catalogLocker();
Hib Eris's avatar
Hib Eris committed
960 961
  if (!form) {
    if (acroForm.isDict()) {
962 963 964
      form = new Form(doc, &acroForm);
      // perform form-related loading after all widgets have been loaded
      form->postWidgetsLoad();
Hib Eris's avatar
Hib Eris committed
965 966 967 968 969 970
    }
  }

  return form;
}

971 972
ViewerPreferences *Catalog::getViewerPreferences()
{
973
  catalogLocker();
974 975 976
  if (!viewerPrefs) {
    if (viewerPreferences.isDict()) {
      viewerPrefs = new ViewerPreferences(viewerPreferences.getDict());
977 978 979
    }
  }

980
  return viewerPrefs;
981 982
}

Hib Eris's avatar
Hib Eris committed
983 984 985 986
Object *Catalog::getNames()
{
  if (names.isNone())
  {
Albert Astals Cid's avatar
Albert Astals Cid committed
987
     Object catDict = xref->getCatalog();
Hib Eris's avatar
Hib Eris committed
988
     if (catDict.isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
989
       names = catDict.dictLookup("Names");
Hib Eris's avatar
Hib Eris committed
990
     } else {
991
       error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
Albert Astals Cid's avatar
Albert Astals Cid committed
992
       names.setToNull();
Hib Eris's avatar
Hib Eris committed
993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
     }
  }

  return &names;
}

NameTree *Catalog::getDestNameTree()
{
  if (!destNameTree) {

    destNameTree = new NameTree();

    if (getNames()->isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
1006
       Object obj = getNames()->dictLookup("Dests");
Hib Eris's avatar
Hib Eris committed
1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
       destNameTree->init(xref, &obj);
    }

  }

  return destNameTree;
}

NameTree *Catalog::getEmbeddedFileNameTree()
{
  if (!embeddedFileNameTree) {

    embeddedFileNameTree = new NameTree();

    if (getNames()->isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
1022
       Object obj = getNames()->dictLookup("EmbeddedFiles");
Hib Eris's avatar
Hib Eris committed
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
       embeddedFileNameTree->init(xref, &obj);
    }

  }

  return embeddedFileNameTree;
}

NameTree *Catalog::getJSNameTree()
{
  if (!jsNameTree) {

    jsNameTree = new NameTree();

    if (getNames()->isDict()) {
Albert Astals Cid's avatar
Albert Astals Cid committed
1038
       Object obj = getNames()->dictLookup("JavaScript");
Hib Eris's avatar
Hib Eris committed
1039 1040 1041 1042 1043 1044 1045 1046
       jsNameTree->init(xref, &obj);
    }

  }

  return jsNameTree;
}

1047
LinkAction* Catalog::getAdditionalAction(DocumentAdditionalActionsType type) {
Albert Astals Cid's avatar
Albert Astals Cid committed
1048 1049 1050
  LinkAction *linkAction = nullptr;
  Object additionalActionsObject = additionalActions.fetch(doc->getXRef());
  if (additionalActionsObject.isDict()) {
1051 1052 1053 1054
    const char *key = (type == actionCloseDocument ?       "WC" :
                       type == actionSaveDocumentStart ?   "WS" :
                       type == actionSaveDocumentFinish ?  "DS" :
                       type == actionPrintDocumentStart ?  "WP" :
1055
                       type == actionPrintDocumentFinish ? "DP" : nullptr);