Coverage Report

Created: 2026-08-22 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/xpdf-4.06/xpdf/Catalog.cc
Line
Count
Source
1
//========================================================================
2
//
3
// Catalog.cc
4
//
5
// Copyright 1996-2013 Glyph & Cog, LLC
6
//
7
//========================================================================
8
9
#include <aconf.h>
10
11
#include <string.h>
12
#include <stddef.h>
13
#include <limits.h>
14
#include "gmem.h"
15
#include "gmempp.h"
16
#include "gfile.h"
17
#include "GList.h"
18
#include "Object.h"
19
#include "CharTypes.h"
20
#include "PDFDoc.h"
21
#include "XRef.h"
22
#include "Array.h"
23
#include "Dict.h"
24
#include "Page.h"
25
#include "Error.h"
26
#include "Link.h"
27
#include "AcroForm.h"
28
#include "TextString.h"
29
#include "Catalog.h"
30
31
//------------------------------------------------------------------------
32
// PageTreeNode
33
//------------------------------------------------------------------------
34
35
class PageTreeNode {
36
public:
37
38
  PageTreeNode(Ref refA, int countA, PageTreeNode *parentA);
39
  ~PageTreeNode();
40
41
  Ref ref;
42
  int count;
43
  PageTreeNode *parent;
44
  GList *kids;      // [PageTreeNode]
45
  PageAttrs *attrs;
46
};
47
48
12.5k
PageTreeNode::PageTreeNode(Ref refA, int countA, PageTreeNode *parentA) {
49
12.5k
  ref = refA;
50
12.5k
  count = countA;
51
12.5k
  parent = parentA;
52
12.5k
  kids = NULL;
53
12.5k
  attrs = NULL;
54
12.5k
}
55
56
12.5k
PageTreeNode::~PageTreeNode() {
57
12.5k
  delete attrs;
58
12.5k
  if (kids) {
59
2.44k
    deleteGList(kids, PageTreeNode);
60
2.44k
  }
61
12.5k
}
62
63
//------------------------------------------------------------------------
64
// EmbeddedFile
65
//------------------------------------------------------------------------
66
67
class EmbeddedFile {
68
public:
69
70
  EmbeddedFile(TextString *nameA, Object *streamRefA);
71
  ~EmbeddedFile();
72
73
  TextString *name;
74
  Object streamRef;
75
};
76
77
315
EmbeddedFile::EmbeddedFile(TextString *nameA, Object *streamRefA) {
78
315
  name = nameA;
79
315
  streamRefA->copy(&streamRef);
80
315
}
81
82
315
EmbeddedFile::~EmbeddedFile() {
83
315
  delete name;
84
315
  streamRef.free();
85
315
}
86
87
//------------------------------------------------------------------------
88
// PageLabelNode
89
//------------------------------------------------------------------------
90
91
class PageLabelNode {
92
public:
93
94
  PageLabelNode(int firstPageA, Dict *dict);
95
  ~PageLabelNode();
96
97
  int firstPage;    // first page number covered by this node
98
  int lastPage;     // last page number covered by this node
99
  TextString *prefix;   // label prefix (may be empty)
100
  int start;      // value of the numeric portion of this
101
        //   label for the first page in the range
102
  char style;     // page label style
103
};
104
105
1.34k
PageLabelNode::PageLabelNode(int firstPageA, Dict *dict) {
106
1.34k
  Object prefixObj, styleObj, startObj;
107
108
  // convert page index to page number
109
1.34k
  firstPage = firstPageA + 1;
110
111
  // lastPage will be filled in later
112
1.34k
  lastPage = -1;
113
114
1.34k
  if (dict->lookup("P", &prefixObj)->isString()) {
115
325
    prefix = new TextString(prefixObj.getString());
116
1.01k
  } else {
117
1.01k
    prefix = new TextString();
118
1.01k
  }
119
1.34k
  prefixObj.free();
120
121
1.34k
  style = '\0';
122
1.34k
  if (dict->lookup("S", &styleObj)->isName()) {
123
514
    if (strlen(styleObj.getName()) == 1) {
124
220
      style = styleObj.getName()[0];
125
220
    }
126
514
  }
127
1.34k
  styleObj.free();
128
129
1.34k
  start = 1;
130
1.34k
  if (dict->lookup("St", &startObj)->isInt()) {
131
53
    start = startObj.getInt();
132
53
  }
133
1.34k
  startObj.free();
134
1.34k
}
135
136
1.34k
PageLabelNode::~PageLabelNode() {
137
1.34k
  delete prefix;
138
1.34k
}
139
140
//------------------------------------------------------------------------
141
// Catalog
142
//------------------------------------------------------------------------
143
144
3.42k
Catalog::Catalog(PDFDoc *docA) {
145
3.42k
  Object catDict;
146
3.42k
  Object obj, obj2;
147
148
3.42k
  ok = gTrue;
149
3.42k
  doc = docA;
150
3.42k
  xref = doc->getXRef();
151
3.42k
  pageTree = NULL;
152
3.42k
  pages = NULL;
153
3.42k
  pageRefs = NULL;
154
3.42k
  numPages = 0;
155
3.42k
  baseURI = NULL;
156
3.42k
  form = NULL;
157
3.42k
  embeddedFiles = NULL;
158
3.42k
  pageLabels = NULL;
159
3.42k
#if MULTITHREADED
160
3.42k
  gInitMutex(&pageMutex);
161
3.42k
#endif
162
163
3.42k
  xref->getCatalog(&catDict);
164
3.42k
  if (!catDict.isDict()) {
165
201
    error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})",
166
201
    catDict.getTypeName());
167
201
    goto err1;
168
201
  }
169
170
  // read page tree
171
3.21k
  if (!readPageTree(&catDict)) {
172
224
    goto err1;
173
224
  }
174
175
  // read named destination dictionary
176
2.99k
  catDict.dictLookup("Dests", &dests);
177
178
  // read root of named destination tree
179
2.99k
  if (catDict.dictLookup("Names", &obj)->isDict())
180
226
    obj.dictLookup("Dests", &nameTree);
181
2.76k
  else
182
2.76k
    nameTree.initNull();
183
2.99k
  obj.free();
184
185
  // read base URI
186
2.99k
  if (catDict.dictLookup("URI", &obj)->isDict()) {
187
0
    if (obj.dictLookup("Base", &obj2)->isString()) {
188
0
      baseURI = obj2.getString()->copy();
189
0
    }
190
0
    obj2.free();
191
0
  }
192
2.99k
  obj.free();
193
2.99k
  if (!baseURI || baseURI->getLength() == 0) {
194
2.99k
    if (baseURI) {
195
0
      delete baseURI;
196
0
    }
197
2.99k
    if (doc->getFileName()) {
198
0
      baseURI = makePathAbsolute(grabPath(doc->getFileName()->getCString()));
199
0
      if (baseURI->getChar(0) == '/') {
200
0
  baseURI->insert(0, "file://localhost");
201
0
      } else {
202
0
  baseURI->insert(0, "file://localhost/");
203
0
      }
204
2.99k
    } else {
205
2.99k
      baseURI = new GString("file://localhost/");
206
2.99k
    }
207
2.99k
  }
208
209
  // get the metadata stream
210
2.99k
  catDict.dictLookup("Metadata", &metadata);
211
212
  // get the structure tree root
213
2.99k
  catDict.dictLookup("StructTreeRoot", &structTreeRoot);
214
215
  // get the outline dictionary
216
2.99k
  catDict.dictLookup("Outlines", &outline);
217
218
  // get the AcroForm dictionary
219
2.99k
  catDict.dictLookup("AcroForm", &acroForm);
220
221
  // get the NeedsRendering flag
222
  // NB: AcroForm::load() uses this value
223
2.99k
  needsRendering = catDict.dictLookup("NeedsRendering", &obj)->isBool() &&
224
7
                   obj.getBool();
225
2.99k
  obj.free();
226
227
  // create the Form
228
  // (if acroForm is a null object, this will still create an AcroForm
229
  // if there are unattached Widget-type annots)
230
2.99k
  form = AcroForm::load(doc, this, &acroForm);
231
232
  // get the OCProperties dictionary
233
2.99k
  catDict.dictLookup("OCProperties", &ocProperties);
234
235
  // get the list of embedded files
236
2.99k
  readEmbeddedFileList(catDict.getDict());
237
238
  // get the ViewerPreferences object
239
2.99k
  catDict.dictLookupNF("ViewerPreferences", &viewerPrefs);
240
241
2.99k
  if (catDict.dictLookup("PageLabels", &obj)->isDict()) {
242
223
    readPageLabelTree(&obj);
243
223
  }
244
2.99k
  obj.free();
245
246
2.99k
  catDict.free();
247
2.99k
  return;
248
249
425
 err1:
250
425
  catDict.free();
251
425
  dests.initNull();
252
425
  nameTree.initNull();
253
425
  ok = gFalse;
254
425
}
255
256
3.41k
Catalog::~Catalog() {
257
3.41k
  int i;
258
259
3.41k
  if (pageTree) {
260
2.99k
    delete pageTree;
261
2.99k
  }
262
3.41k
  if (pages) {
263
194k
    for (i = 0; i < numPages; ++i) {
264
191k
      if (pages[i]) {
265
191k
  delete pages[i];
266
191k
      }
267
191k
    }
268
2.98k
    gfree(pages);
269
2.98k
    gfree(pageRefs);
270
2.98k
  }
271
3.41k
#if MULTITHREADED
272
3.41k
  gDestroyMutex(&pageMutex);
273
3.41k
#endif
274
3.41k
  dests.free();
275
3.41k
  nameTree.free();
276
3.41k
  if (baseURI) {
277
2.99k
    delete baseURI;
278
2.99k
  }
279
3.41k
  metadata.free();
280
3.41k
  structTreeRoot.free();
281
3.41k
  outline.free();
282
3.41k
  acroForm.free();
283
3.41k
  if (form) {
284
1.53k
    delete form;
285
1.53k
  }
286
3.41k
  ocProperties.free();
287
3.41k
  if (embeddedFiles) {
288
13
    deleteGList(embeddedFiles, EmbeddedFile);
289
13
  }
290
3.41k
  if (pageLabels) {
291
109
    deleteGList(pageLabels, PageLabelNode);
292
109
  }
293
3.41k
  viewerPrefs.free();
294
3.41k
}
295
296
382k
Page *Catalog::getPage(int i) {
297
382k
  Page *page;
298
299
382k
#if MULTITHREADED
300
382k
  gLockMutex(&pageMutex);
301
382k
#endif
302
382k
  if (!pages[i-1]) {
303
191k
    loadPage(i);
304
191k
  }
305
382k
  page = pages[i-1];
306
382k
#if MULTITHREADED
307
382k
  gUnlockMutex(&pageMutex);
308
382k
#endif
309
382k
  return page;
310
382k
}
311
312
0
Ref *Catalog::getPageRef(int i) {
313
0
  Ref *pageRef;
314
315
0
#if MULTITHREADED
316
0
  gLockMutex(&pageMutex);
317
0
#endif
318
0
  if (!pages[i-1]) {
319
0
    loadPage(i);
320
0
  }
321
0
  pageRef = &pageRefs[i-1];
322
0
#if MULTITHREADED
323
0
  gUnlockMutex(&pageMutex);
324
0
#endif
325
0
  return pageRef;
326
0
}
327
328
0
void Catalog::doneWithPage(int i) {
329
0
#if MULTITHREADED
330
0
  gLockMutex(&pageMutex);
331
0
#endif
332
0
  if (pages[i-1]) {
333
0
    delete pages[i-1];
334
0
    pages[i-1] = NULL;
335
0
  }
336
0
#if MULTITHREADED
337
0
  gUnlockMutex(&pageMutex);
338
0
#endif
339
0
}
340
341
0
GString *Catalog::readMetadata() {
342
0
  GString *s;
343
0
  Dict *dict;
344
0
  Object obj;
345
0
  char buf[4096];
346
0
  int n;
347
348
0
  if (!metadata.isStream()) {
349
0
    return NULL;
350
0
  }
351
0
  dict = metadata.streamGetDict();
352
0
  if (!dict->lookup("Subtype", &obj)->isName("XML")) {
353
0
    error(errSyntaxWarning, -1, "Unknown Metadata type: '{0:s}'",
354
0
    obj.isName() ? obj.getName() : "???");
355
0
  }
356
0
  obj.free();
357
0
  s = new GString();
358
0
  metadata.streamReset();
359
0
  while ((n = metadata.streamGetBlock(buf, sizeof(buf))) > 0) {
360
0
    s->append(buf, n);
361
0
  }
362
0
  metadata.streamClose();
363
0
  return s;
364
0
}
365
366
0
int Catalog::findPage(int num, int gen) {
367
0
  int i;
368
369
0
#if MULTITHREADED
370
0
  gLockMutex(&pageMutex);
371
0
#endif
372
0
  for (i = 0; i < numPages; ++i) {
373
0
    if (!pages[i]) {
374
0
      loadPage(i+1);
375
0
    }
376
0
    if (pageRefs[i].num == num && pageRefs[i].gen == gen) {
377
0
#if MULTITHREADED
378
0
      gUnlockMutex(&pageMutex);
379
0
#endif
380
0
      return i + 1;
381
0
    }
382
0
  }
383
0
#if MULTITHREADED
384
0
  gUnlockMutex(&pageMutex);
385
0
#endif
386
0
  return 0;
387
0
}
388
389
0
LinkDest *Catalog::findDest(GString *name) {
390
0
  LinkDest *dest;
391
0
  Object obj1, obj2;
392
0
  GBool found;
393
394
  // try named destination dictionary then name tree
395
0
  found = gFalse;
396
0
  if (dests.isDict()) {
397
0
    if (!dests.dictLookup(name->getCString(), &obj1)->isNull()) {
398
0
      found = gTrue;
399
0
    } else {
400
0
      obj1.free();
401
0
    }
402
0
  }
403
0
  if (!found && nameTree.isDict()) {
404
0
    char *touchedObjs = (char *)gmalloc(xref->getNumObjects());
405
0
    memset(touchedObjs, 0, xref->getNumObjects());
406
0
    if (!findDestInTree(&nameTree, &nameTree, name, &obj1, touchedObjs)
407
0
  ->isNull()) {
408
0
      found = gTrue;
409
0
    } else {
410
0
      obj1.free();
411
0
    }
412
0
    gfree(touchedObjs);
413
0
  }
414
0
  if (!found) {
415
0
    return NULL;
416
0
  }
417
418
  // construct LinkDest
419
0
  dest = NULL;
420
0
  if (obj1.isArray()) {
421
0
    dest = new LinkDest(obj1.getArray());
422
0
  } else if (obj1.isDict()) {
423
0
    if (obj1.dictLookup("D", &obj2)->isArray()) {
424
0
      dest = new LinkDest(obj2.getArray());
425
0
    } else {
426
0
      error(errSyntaxWarning, -1, "Bad named destination value");
427
0
    }
428
0
    obj2.free();
429
0
  } else {
430
0
    error(errSyntaxWarning, -1, "Bad named destination value");
431
0
  }
432
0
  obj1.free();
433
0
  if (dest && !dest->isOk()) {
434
0
    delete dest;
435
0
    dest = NULL;
436
0
  }
437
438
0
  return dest;
439
0
}
440
441
Object *Catalog::findDestInTree(Object *treeRef, Object *tree, GString *name,
442
0
        Object *obj, char *touchedObjs) {
443
0
  Object names, name1;
444
0
  Object kids, kidRef, kid, limits, low, high;
445
0
  GBool done, found;
446
0
  int cmp, i;
447
448
  // check for invalid reference
449
0
  if (treeRef->isRef() &&
450
0
      (treeRef->getRefNum() < 0 ||
451
0
       treeRef->getRefNum() >= xref->getNumObjects())) {
452
0
    obj->initNull();
453
0
    return obj;
454
0
  }
455
456
  // check for a destination tree loop
457
0
  if (treeRef->isRef()) {
458
0
    if (touchedObjs[treeRef->getRefNum()]) {
459
0
      error(errSyntaxError, -1, "Loop in destination name tree");
460
0
      obj->initNull();
461
0
      return obj;
462
0
    }
463
0
    touchedObjs[treeRef->getRefNum()] = 1;
464
0
  }
465
466
  // leaf node
467
0
  if (tree->dictLookup("Names", &names)->isArray()) {
468
0
    done = found = gFalse;
469
0
    for (i = 0; !done && i < names.arrayGetLength(); i += 2) {
470
0
      if (names.arrayGet(i, &name1)->isString()) {
471
0
  cmp = name->cmp(name1.getString());
472
0
  if (cmp == 0) {
473
0
    names.arrayGet(i+1, obj);
474
0
    found = gTrue;
475
0
    done = gTrue;
476
0
  } else if (cmp < 0) {
477
0
    done = gTrue;
478
0
  }
479
0
      }
480
0
      name1.free();
481
0
    }
482
0
    names.free();
483
0
    if (!found) {
484
0
      obj->initNull();
485
0
    }
486
0
    return obj;
487
0
  }
488
0
  names.free();
489
490
  // root or intermediate node
491
0
  done = gFalse;
492
0
  if (tree->dictLookup("Kids", &kids)->isArray()) {
493
0
    for (i = 0; !done && i < kids.arrayGetLength(); ++i) {
494
0
      kids.arrayGetNF(i, &kidRef);
495
0
      kids.arrayGet(i, &kid);
496
0
      if (kid.isDict()) {
497
0
  if (kid.dictLookup("Limits", &limits)->isArray()) {
498
0
    if (limits.arrayGet(0, &low)->isString() &&
499
0
        name->cmp(low.getString()) >= 0) {
500
0
      if (limits.arrayGet(1, &high)->isString() &&
501
0
    name->cmp(high.getString()) <= 0) {
502
0
        findDestInTree(&kidRef, &kid, name, obj, touchedObjs);
503
0
        done = gTrue;
504
0
      }
505
0
      high.free();
506
0
    }
507
0
    low.free();
508
0
  }
509
0
  limits.free();
510
0
      }
511
0
      kid.free();
512
0
      kidRef.free();
513
0
    }
514
0
  }
515
0
  kids.free();
516
517
  // name was outside of ranges of all kids
518
0
  if (!done) {
519
0
    obj->initNull();
520
0
  }
521
522
0
  return obj;
523
0
}
524
525
3.21k
GBool Catalog::readPageTree(Object *catDict) {
526
3.21k
  Object topPagesRef, topPagesObj, countObj;
527
3.21k
  int i;
528
529
3.21k
  if (!catDict->dictLookupNF("Pages", &topPagesRef)->isRef()) {
530
123
    error(errSyntaxError, -1, "Top-level pages reference is wrong type ({0:s})",
531
123
    topPagesRef.getTypeName());
532
123
    topPagesRef.free();
533
123
    return gFalse;
534
123
  }
535
3.09k
  if (!topPagesRef.fetch(xref, &topPagesObj)->isDict()) {
536
98
    error(errSyntaxError, -1, "Top-level pages object is wrong type ({0:s})",
537
98
    topPagesObj.getTypeName());
538
98
    topPagesObj.free();
539
98
    topPagesRef.free();
540
98
    return gFalse;
541
98
  }
542
2.99k
  if (topPagesObj.dictLookup("Count", &countObj)->isInt()) {
543
1.04k
    numPages = countObj.getInt();
544
1.04k
    if (numPages == 0 || numPages > 50000) {
545
      // 1. Acrobat apparently scans the page tree if it sees a zero
546
      //    count.
547
      // 2. Absurdly large page counts result in very slow loading,
548
      //    because other code tries to fetch pages 1 through n.
549
      // In both cases: ignore the given page count and scan the tree
550
      // instead.
551
197
      char *touchedObjs = (char *)gmalloc(xref->getNumObjects());
552
197
      memset(touchedObjs, 0, xref->getNumObjects());
553
197
      numPages = countPageTree(&topPagesRef, touchedObjs);
554
197
      gfree(touchedObjs);
555
197
    }
556
1.95k
  } else {
557
    // assume we got a Page node instead of a Pages node
558
1.95k
    numPages = 1;
559
1.95k
  }
560
2.99k
  countObj.free();
561
2.99k
  if (numPages < 0) {
562
3
    error(errSyntaxError, -1, "Invalid page count");
563
3
    topPagesObj.free();
564
3
    topPagesRef.free();
565
3
    numPages = 0;
566
3
    return gFalse;
567
3
  }
568
2.99k
  pageTree = new PageTreeNode(topPagesRef.getRef(), numPages, NULL);
569
2.99k
  topPagesObj.free();
570
2.99k
  topPagesRef.free();
571
2.99k
  pages = (Page **)greallocn(pages, numPages, sizeof(Page *));
572
2.99k
  pageRefs = (Ref *)greallocn(pageRefs, numPages, sizeof(Ref));
573
194k
  for (i = 0; i < numPages; ++i) {
574
191k
    pages[i] = NULL;
575
191k
    pageRefs[i].num = -1;
576
191k
    pageRefs[i].gen = -1;
577
191k
  }
578
2.99k
  return gTrue;
579
2.99k
}
580
581
115k
int Catalog::countPageTree(Object *pagesNodeRef, char *touchedObjs) {
582
  // check for invalid reference
583
115k
  if (pagesNodeRef->isRef() &&
584
17.1k
      (pagesNodeRef->getRefNum() < 0 ||
585
17.1k
       pagesNodeRef->getRefNum() >= xref->getNumObjects())) {
586
1.07k
    return 0;
587
1.07k
  }
588
589
  // check for a page tree loop; fetch the node object
590
114k
  Object pagesNode;
591
114k
  if (pagesNodeRef->isRef()) {
592
16.0k
    if (touchedObjs[pagesNodeRef->getRefNum()]) {
593
12.1k
      error(errSyntaxError, -1, "Loop in Pages tree");
594
12.1k
      return 0;
595
12.1k
    }
596
3.85k
    touchedObjs[pagesNodeRef->getRefNum()] = 1;
597
3.85k
    xref->fetch(pagesNodeRef->getRefNum(), pagesNodeRef->getRefGen(),
598
3.85k
    &pagesNode);
599
98.2k
  } else {
600
98.2k
    pagesNodeRef->copy(&pagesNode);
601
98.2k
  }
602
603
  // count the subtree
604
102k
  int n = 0;
605
102k
  if (pagesNode.isDict()) {
606
7.10k
    Object kidsRef, kids;
607
7.10k
    pagesNode.dictLookupNF("Kids", &kidsRef);
608
7.10k
    if (kidsRef.isRef() &&
609
126
  kidsRef.getRefNum() >= 0 &&
610
126
  kidsRef.getRefNum() < xref->getNumObjects()) {
611
115
      if (touchedObjs[kidsRef.getRefNum()]) {
612
99
  error(errSyntaxError, -1, "Loop in Pages tree");
613
99
  kidsRef.free();
614
99
  pagesNode.free();
615
99
  return 0;
616
99
      }
617
16
      touchedObjs[kidsRef.getRefNum()] = 1;
618
16
      xref->fetch(kidsRef.getRefNum(), kidsRef.getRefGen(), &kids);
619
6.99k
    } else {
620
6.99k
      kidsRef.copy(&kids);
621
6.99k
    }
622
7.00k
    kidsRef.free();
623
7.00k
    if (kids.isArray()) {
624
116k
      for (int i = 0; i < kids.arrayGetLength(); ++i) {
625
115k
  Object kid;
626
115k
  kids.arrayGetNF(i, &kid);
627
115k
  int n2 = countPageTree(&kid, touchedObjs);
628
115k
  if (n2 < INT_MAX - n) {
629
115k
    n += n2;
630
115k
  } else {
631
0
    error(errSyntaxError, -1, "Page tree contains too many pages");
632
0
    n = INT_MAX;
633
0
  }
634
115k
  kid.free();
635
115k
      }
636
5.73k
    } else {
637
5.73k
      n = 1;
638
5.73k
    }
639
7.00k
    kids.free();
640
7.00k
  }
641
642
102k
  pagesNode.free();
643
644
102k
  return n;
645
102k
}
646
647
191k
void Catalog::loadPage(int pg) {
648
191k
  loadPage2(pg, pg - 1, pageTree);
649
191k
}
650
651
203k
void Catalog::loadPage2(int pg, int relPg, PageTreeNode *node) {
652
203k
  Object pageRefObj, pageObj, kidsObj, kidRefObj, kidObj, countObj;
653
203k
  PageTreeNode *kidNode, *p;
654
203k
  PageAttrs *attrs;
655
203k
  int count, i;
656
657
203k
  if (relPg >= node->count) {
658
0
    error(errSyntaxError, -1, "Internal error in page tree");
659
0
    pages[pg-1] = new Page(doc, pg);
660
0
    return;
661
0
  }
662
663
  // if this node has not been filled in yet, it's either a leaf node
664
  // or an unread internal node
665
203k
  if (!node->kids) {
666
667
    // check for a loop in the page tree
668
189k
    for (p = node->parent; p; p = p->parent) {
669
11.1k
      if (node->ref.num == p->ref.num && node->ref.gen == p->ref.gen) {
670
2.25k
  error(errSyntaxError, -1, "Loop in Pages tree");
671
2.25k
  pages[pg-1] = new Page(doc, pg);
672
2.25k
  return;
673
2.25k
      }
674
11.1k
    }
675
676
    // fetch the Page/Pages object
677
178k
    pageRefObj.initRef(node->ref.num, node->ref.gen);
678
178k
    if (!pageRefObj.fetch(xref, &pageObj)->isDict()) {
679
0
      error(errSyntaxError, -1, "Page tree object is wrong type ({0:s})",
680
0
      pageObj.getTypeName());
681
0
      pageObj.free();
682
0
      pageRefObj.free();
683
0
      pages[pg-1] = new Page(doc, pg);
684
0
      return;
685
0
    }
686
687
    // merge the PageAttrs
688
178k
    attrs = new PageAttrs(node->parent ? node->parent->attrs
689
178k
                     : (PageAttrs *)NULL,
690
178k
        pageObj.getDict(), xref);
691
692
    // if "Kids" exists, it's an internal node
693
178k
    if (pageObj.dictLookup("Kids", &kidsObj)->isArray()) {
694
695
      // save the PageAttrs
696
2.44k
      node->attrs = attrs;
697
698
      // read the kids
699
2.44k
      node->kids = new GList();
700
302k
      for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
701
300k
  if (kidsObj.arrayGetNF(i, &kidRefObj)->isRef()) {
702
136k
    if (kidRefObj.fetch(xref, &kidObj)->isDict()) {
703
9.53k
      if (kidObj.dictLookup("Count", &countObj)->isInt()) {
704
703
        count = countObj.getInt();
705
8.83k
      } else {
706
8.83k
        count = 1;
707
8.83k
      }
708
9.53k
      countObj.free();
709
9.53k
      node->kids->append(new PageTreeNode(kidRefObj.getRef(), count,
710
9.53k
            node));
711
126k
    } else {
712
126k
      error(errSyntaxError, -1, "Page tree object is wrong type ({0:s})",
713
126k
      kidObj.getTypeName());
714
126k
    }
715
136k
    kidObj.free();
716
164k
  } else {
717
164k
    error(errSyntaxError, -1,
718
164k
    "Page tree reference is wrong type ({0:s})",
719
164k
    kidRefObj.getTypeName());
720
164k
  }
721
300k
  kidRefObj.free();
722
300k
      }
723
724
175k
    } else {
725
      
726
      // create the Page object
727
175k
      pageRefs[pg-1] = node->ref;
728
175k
      pages[pg-1] = new Page(doc, pg, pageObj.getDict(), attrs);
729
175k
      if (!pages[pg-1]->isOk()) {
730
1.09k
  delete pages[pg-1];
731
1.09k
  pages[pg-1] = new Page(doc, pg);
732
1.09k
      }
733
734
175k
    }
735
736
178k
    kidsObj.free();
737
178k
    pageObj.free();
738
178k
    pageRefObj.free();
739
178k
  }
740
741
  // recursively descend the tree
742
201k
  if (node->kids) {
743
1.03M
    for (i = 0; i < node->kids->getLength(); ++i) {
744
1.02M
      kidNode = (PageTreeNode *)node->kids->get(i);
745
1.02M
      if (relPg < kidNode->count) {
746
12.6k
  loadPage2(pg, relPg, kidNode);
747
12.6k
  break;
748
12.6k
      }
749
1.00M
      relPg -= kidNode->count;
750
1.00M
    }
751
752
    // this will only happen if the page tree is invalid
753
    // (i.e., parent count > sum of children counts)
754
26.0k
    if (i == node->kids->getLength()) {
755
13.3k
      error(errSyntaxError, -1, "Invalid page count in page tree");
756
13.3k
      pages[pg-1] = new Page(doc, pg);
757
13.3k
    }
758
26.0k
  }
759
201k
}
760
761
0
Object *Catalog::getDestOutputProfile(Object *destOutProf) {
762
0
  Object catDict, intents, intent, subtype;
763
0
  int i;
764
765
0
  if (!xref->getCatalog(&catDict)->isDict()) {
766
0
    goto err1;
767
0
  }
768
0
  if (!catDict.dictLookup("OutputIntents", &intents)->isArray()) {
769
0
    goto err2;
770
0
  }
771
0
  for (i = 0; i < intents.arrayGetLength(); ++i) {
772
0
    intents.arrayGet(i, &intent);
773
0
    if (!intent.isDict()) {
774
0
      intent.free();
775
0
      continue;
776
0
    }
777
0
    if (!intent.dictLookup("S", &subtype)->isName("GTS_PDFX")) {
778
0
      subtype.free();
779
0
      intent.free();
780
0
      continue;
781
0
    }
782
0
    subtype.free();
783
0
    if (!intent.dictLookup("DestOutputProfile", destOutProf)->isStream()) {
784
0
      destOutProf->free();
785
0
      intent.free();
786
0
      goto err2;
787
0
    }
788
0
    intent.free();
789
0
    intents.free();
790
0
    catDict.free();
791
0
    return destOutProf;
792
0
  }
793
794
0
 err2:
795
0
  intents.free();
796
0
 err1:
797
0
  catDict.free();
798
0
  return NULL;
799
0
}
800
801
2.99k
void Catalog::readEmbeddedFileList(Dict *catDict) {
802
2.99k
  Object obj1, obj2;
803
2.99k
  char *touchedObjs;
804
805
2.99k
  touchedObjs = (char *)gmalloc(xref->getNumObjects());
806
2.99k
  memset(touchedObjs, 0, xref->getNumObjects());
807
808
  // read the embedded file name tree
809
2.99k
  if (catDict->lookup("Names", &obj1)->isDict()) {
810
226
    obj1.dictLookupNF("EmbeddedFiles", &obj2);
811
226
    readEmbeddedFileTree(&obj2, touchedObjs);
812
226
    obj2.free();
813
226
  }
814
2.99k
  obj1.free();
815
816
  // look for file attachment annotations
817
2.99k
  readFileAttachmentAnnots(catDict->lookupNF("Pages", &obj1), touchedObjs);
818
2.99k
  obj1.free();
819
820
2.99k
  gfree(touchedObjs);
821
2.99k
}
822
823
62.9k
void Catalog::readEmbeddedFileTree(Object *nodeRef, char *touchedObjs) {
824
62.9k
  Object node, kidsObj, kidObj;
825
62.9k
  Object namesObj, nameObj, fileSpecObj;
826
62.9k
  int i;
827
828
  // check for an object loop
829
62.9k
  if (nodeRef->isRef()) {
830
12.3k
    if (nodeRef->getRefNum() < 0 ||
831
12.3k
  nodeRef->getRefNum() >= xref->getNumObjects() ||
832
11.3k
  touchedObjs[nodeRef->getRefNum()]) {
833
11.3k
      return;
834
11.3k
    }
835
918
    touchedObjs[nodeRef->getRefNum()] = 1;
836
918
    xref->fetch(nodeRef->getRefNum(), nodeRef->getRefGen(), &node);
837
50.6k
  } else {
838
50.6k
    nodeRef->copy(&node);
839
50.6k
  }
840
841
51.5k
  if (!node.isDict()) {
842
48.4k
    node.free();
843
48.4k
    return;
844
48.4k
  }
845
846
3.13k
  if (checkDictLookup(&node, "Kids", &kidsObj, touchedObjs)->isArray()) {
847
64.4k
    for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
848
62.7k
      kidsObj.arrayGetNF(i, &kidObj);
849
62.7k
      readEmbeddedFileTree(&kidObj, touchedObjs);
850
62.7k
      kidObj.free();
851
62.7k
    }
852
1.68k
  } else {
853
1.44k
    if (checkDictLookup(&node, "Names", &namesObj, touchedObjs)->isArray()) {
854
20.4k
      for (i = 0; i+1 < namesObj.arrayGetLength(); ++i) {
855
20.2k
  namesObj.arrayGet(i, &nameObj);
856
20.2k
  namesObj.arrayGet(i+1, &fileSpecObj);
857
20.2k
  readEmbeddedFile(&fileSpecObj, &nameObj);
858
20.2k
  nameObj.free();
859
20.2k
  fileSpecObj.free();
860
20.2k
      }
861
184
    }
862
1.44k
    namesObj.free();
863
1.44k
  }
864
3.13k
  kidsObj.free();
865
866
3.13k
  node.free();
867
3.13k
}
868
869
void Catalog::readFileAttachmentAnnots(Object *pageNodeRef,
870
181k
               char *touchedObjs) {
871
181k
  Object pageNode, kids, kid, annots, annot, subtype, fileSpec, contents;
872
181k
  int i;
873
874
  // check for an invalid object reference (e.g., in a damaged PDF file)
875
181k
  if (pageNodeRef->isRef() &&
876
24.4k
      (pageNodeRef->getRefNum() < 0 ||
877
24.4k
       pageNodeRef->getRefNum() >= xref->getNumObjects())) {
878
1.66k
    return;
879
1.66k
  }
880
881
  // check for a page tree loop
882
180k
  if (pageNodeRef->isRef()) {
883
22.8k
    if (touchedObjs[pageNodeRef->getRefNum()]) {
884
14.8k
      return;
885
14.8k
    }
886
7.94k
    touchedObjs[pageNodeRef->getRefNum()] = 1;
887
7.94k
    xref->fetch(pageNodeRef->getRefNum(), pageNodeRef->getRefGen(), &pageNode);
888
157k
  } else {
889
157k
    pageNodeRef->copy(&pageNode);
890
157k
  }
891
892
165k
  if (pageNode.isDict()) {
893
13.7k
    if (checkDictLookup(&pageNode, "Kids", &kids, touchedObjs)->isArray()) {
894
182k
      for (i = 0; i < kids.arrayGetLength(); ++i) {
895
178k
  readFileAttachmentAnnots(kids.arrayGetNF(i, &kid), touchedObjs);
896
178k
  kid.free();
897
178k
      }
898
10.1k
    } else {
899
10.1k
      if (checkDictLookup(&pageNode, "Annots",
900
10.1k
        &annots, touchedObjs)->isArray()) {
901
15.5k
  for (i = 0; i < annots.arrayGetLength(); ++i) {
902
15.3k
    if (checkArrayGet(&annots, i, &annot, touchedObjs)->isDict()) {
903
1.27k
      if (checkDictLookup(&annot, "Subtype", &subtype, touchedObjs)
904
1.27k
      ->isName("FileAttachment")) {
905
0
        if (checkDictLookup(&annot, "FS", &fileSpec, touchedObjs)) {
906
0
    readEmbeddedFile(&fileSpec,
907
0
         checkDictLookup(&annot, "Contents",
908
0
             &contents, touchedObjs));
909
0
    contents.free();
910
0
        }
911
0
        fileSpec.free();
912
0
      }
913
1.27k
      subtype.free();
914
1.27k
    }
915
15.3k
    annot.free();
916
15.3k
  }
917
272
      }
918
10.1k
      annots.free();
919
10.1k
    }
920
13.7k
    kids.free();
921
13.7k
  }
922
923
165k
  pageNode.free();
924
165k
}
925
926
20.2k
void Catalog::readEmbeddedFile(Object *fileSpec, Object *name1) {
927
20.2k
  Object name2, efObj, streamObj;
928
20.2k
  GString *s;
929
20.2k
  TextString *name;
930
931
20.2k
  if (fileSpec->isDict()) {
932
1.03k
    if (fileSpec->dictLookup("UF", &name2)->isString()) {
933
21
      name = new TextString(name2.getString());
934
1.01k
    } else {
935
1.01k
      name2.free();
936
1.01k
      if (fileSpec->dictLookup("F", &name2)->isString()) {
937
70
  name = new TextString(name2.getString());
938
944
      } else if (name1 && name1->isString()) {
939
23
  name = new TextString(name1->getString());
940
921
      } else {
941
921
  s = new GString("?");
942
921
  name = new TextString(s);
943
921
  delete s;
944
921
      }
945
1.01k
    }
946
1.03k
    name2.free();
947
1.03k
    if (fileSpec->dictLookup("EF", &efObj)->isDict()) {
948
553
      if (efObj.dictLookupNF("F", &streamObj)->isRef()) {
949
315
  if (!embeddedFiles) {
950
13
    embeddedFiles = new GList();
951
13
  }
952
315
  embeddedFiles->append(new EmbeddedFile(name, &streamObj));
953
315
      } else {
954
238
  delete name;
955
238
      }
956
553
      streamObj.free();
957
553
    } else {
958
482
      delete name;
959
482
    }
960
1.03k
    efObj.free();
961
1.03k
  }
962
20.2k
}
963
964
0
int Catalog::getNumEmbeddedFiles() {
965
0
  return embeddedFiles ? embeddedFiles->getLength() : 0;
966
0
}
967
968
0
Unicode *Catalog::getEmbeddedFileName(int idx) {
969
0
  return ((EmbeddedFile *)embeddedFiles->get(idx))->name->getUnicode();
970
0
}
971
972
0
int Catalog::getEmbeddedFileNameLength(int idx) {
973
0
  return ((EmbeddedFile *)embeddedFiles->get(idx))->name->getLength();
974
0
}
975
976
0
Object *Catalog::getEmbeddedFileStreamRef(int idx) {
977
0
  return &((EmbeddedFile *)embeddedFiles->get(idx))->streamRef;
978
0
}
979
980
0
Object *Catalog::getEmbeddedFileStreamObj(int idx, Object *strObj) {
981
0
  ((EmbeddedFile *)embeddedFiles->get(idx))->streamRef.fetch(xref, strObj);
982
0
  if (!strObj->isStream()) {
983
0
    strObj->free();
984
0
    return NULL;
985
0
  }
986
0
  return strObj;
987
0
}
988
989
223
void Catalog::readPageLabelTree(Object *root) {
990
223
  PageLabelNode *label0, *label1;
991
223
  char *touchedObjs;
992
223
  int i;
993
994
223
  touchedObjs = (char *)gmalloc(xref->getNumObjects());
995
223
  memset(touchedObjs, 0, xref->getNumObjects());
996
223
  pageLabels = new GList();
997
223
  readPageLabelTree2(root, touchedObjs);
998
223
  gfree(touchedObjs);
999
1000
223
  if (pageLabels->getLength() == 0) {
1001
114
    deleteGList(pageLabels, PageLabelNode);
1002
114
    pageLabels = NULL;
1003
114
    return;
1004
114
  }
1005
1006
  // set lastPage in each node
1007
109
  label0 = (PageLabelNode *)pageLabels->get(0);
1008
1.34k
  for (i = 1; i < pageLabels->getLength(); ++i) {
1009
1.23k
    label1 = (PageLabelNode *)pageLabels->get(i);
1010
1.23k
    label0->lastPage = label1->firstPage - 1;
1011
1.23k
    label0 = label1;
1012
1.23k
  }
1013
109
  label0->lastPage = numPages;
1014
109
}
1015
1016
50.3k
void Catalog::readPageLabelTree2(Object *nodeRef, char *touchedObjs) {
1017
50.3k
  Object node, nums, num, labelObj, kidsRef, kids, kid;
1018
50.3k
  int i;
1019
1020
  // check for an object loop
1021
50.3k
  if (nodeRef->isRef()) {
1022
2.25k
    if (nodeRef->getRefNum() < 0 ||
1023
2.25k
  nodeRef->getRefNum() >= xref->getNumObjects() ||
1024
2.03k
  touchedObjs[nodeRef->getRefNum()]) {
1025
2.03k
      return;
1026
2.03k
    }
1027
219
    touchedObjs[nodeRef->getRefNum()] = 1;
1028
219
    xref->fetch(nodeRef->getRefNum(), nodeRef->getRefGen(), &node);
1029
48.0k
  } else {
1030
48.0k
    nodeRef->copy(&node);
1031
48.0k
  }
1032
1033
48.3k
  if (!node.isDict()) {
1034
44.8k
    node.free();
1035
44.8k
    return;
1036
44.8k
  }
1037
1038
3.43k
  if (node.dictLookup("Nums", &nums)->isArray()) {
1039
6.63k
    for (i = 0; i < nums.arrayGetLength() - 1; i += 2) {
1040
6.48k
      if (nums.arrayGet(i, &num)->isInt()) {
1041
3.17k
  if (nums.arrayGet(i+1, &labelObj)->isDict()) {
1042
1.34k
    pageLabels->append(new PageLabelNode(num.getInt(),
1043
1.34k
                 labelObj.getDict()));
1044
1.34k
  }
1045
3.17k
  labelObj.free();
1046
3.17k
      }
1047
6.48k
      num.free();
1048
6.48k
    }
1049
145
  }
1050
3.43k
  nums.free();
1051
1052
  // check for an object loop in the Kids entry
1053
3.43k
  if (node.dictLookupNF("Kids", &kidsRef)->isRef()) {
1054
204
    if (kidsRef.getRefNum() < 0 ||
1055
204
  kidsRef.getRefNum() >= doc->getXRef()->getNumObjects() ||
1056
196
  touchedObjs[kidsRef.getRefNum()]) {
1057
196
      kidsRef.free();
1058
196
      node.free();
1059
196
      return;
1060
196
    }
1061
8
    touchedObjs[kidsRef.getRefNum()] = 1;
1062
8
    kidsRef.fetch(doc->getXRef(), &kids);
1063
3.22k
  } else {
1064
3.22k
    kidsRef.copy(&kids);
1065
3.22k
  }
1066
3.23k
  kidsRef.free();
1067
1068
3.23k
  if (kids.isArray()) {
1069
52.0k
    for (i = 0; i < kids.arrayGetLength(); ++i) {
1070
50.1k
      kids.arrayGetNF(i, &kid);
1071
50.1k
      readPageLabelTree2(&kid, touchedObjs);
1072
50.1k
      kid.free();
1073
50.1k
    }
1074
1.95k
  }
1075
3.23k
  kids.free();
1076
1077
3.23k
  node.free();
1078
3.23k
}
1079
1080
0
TextString *Catalog::getPageLabel(int pageNum) {
1081
0
  PageLabelNode *label;
1082
0
  TextString *ts;
1083
0
  int pageRangeNum;
1084
0
  GString *suffix;
1085
1086
0
  if (!pageLabels || !(label = findPageLabel(pageNum))) {
1087
0
    return NULL;
1088
0
  }
1089
1090
0
  ts = new TextString(label->prefix);
1091
1092
0
  pageRangeNum = label->start + (pageNum - label->firstPage);
1093
1094
0
  suffix = NULL;
1095
0
  if (label->style == 'D') {
1096
0
    suffix = GString::format("{0:d}", pageRangeNum);
1097
0
  } else if (label->style == 'R') {
1098
0
    suffix = makeRomanNumeral(pageRangeNum, gTrue);
1099
0
  } else if (label->style == 'r') {
1100
0
    suffix = makeRomanNumeral(pageRangeNum, gFalse);
1101
0
  } else if (label->style == 'A') {
1102
0
    suffix = makeLetterLabel(pageRangeNum, gTrue);
1103
0
  } else if (label->style == 'a') {
1104
0
    suffix = makeLetterLabel(pageRangeNum, gFalse);
1105
0
  }
1106
0
  if (suffix) {
1107
0
    ts->append(suffix);
1108
0
    delete suffix;
1109
0
  }
1110
1111
0
  return ts;
1112
0
}
1113
1114
0
PageLabelNode *Catalog::findPageLabel(int pageNum) {
1115
0
  PageLabelNode *label;
1116
0
  int i;
1117
1118
  //~ this could use a binary search
1119
0
  for (i = 0; i < pageLabels->getLength(); ++i) {
1120
0
    label = (PageLabelNode *)pageLabels->get(i);
1121
0
    if (pageNum >= label->firstPage && pageNum <= label->lastPage) {
1122
0
      return label;
1123
0
    }
1124
0
  }
1125
0
  return NULL;
1126
0
}
1127
1128
0
GString *Catalog::makeRomanNumeral(int num, GBool uppercase) {
1129
0
  GString *s;
1130
1131
0
  s = new GString();
1132
0
  while (num >= 1000) {
1133
0
    s->append(uppercase ? 'M' : 'm');
1134
0
    num -= 1000;
1135
0
  }
1136
0
  if (num >= 900) {
1137
0
    s->append(uppercase ? "CM" : "cm");
1138
0
    num -= 900;
1139
0
  } else if (num >= 500) {
1140
0
    s->append(uppercase ? 'D' : 'd');
1141
0
    num -= 500;
1142
0
  } else if (num >= 400) {
1143
0
    s->append(uppercase ? "CD" : "cd");
1144
0
    num -= 400;
1145
0
  }
1146
0
  while (num >= 100) {
1147
0
    s->append(uppercase ? 'C' : 'c');
1148
0
    num -= 100;
1149
0
  }
1150
0
  if (num >= 90) {
1151
0
    s->append(uppercase ? "XC" : "xc");
1152
0
    num -= 90;
1153
0
  } else if (num >= 50) {
1154
0
    s->append(uppercase ? 'L' : 'l');
1155
0
    num -= 50;
1156
0
  } else if (num >= 40) {
1157
0
    s->append(uppercase ? "XL" : "xl");
1158
0
    num -= 40;
1159
0
  }
1160
0
  while (num >= 10) {
1161
0
    s->append(uppercase ? 'X' : 'x');
1162
0
    num -= 10;
1163
0
  }
1164
0
  if (num >= 9) {
1165
0
    s->append(uppercase ? "IX" : "ix");
1166
0
    num -= 9;
1167
0
  } else if (num >= 5) {
1168
0
    s->append(uppercase ? 'V' : 'v');
1169
0
    num -= 5;
1170
0
  } else if (num >= 4) {
1171
0
    s->append(uppercase ? "IV" : "iv");
1172
0
    num -= 4;
1173
0
  }
1174
0
  while (num >= 1) {
1175
0
    s->append(uppercase ? 'I' : 'i');
1176
0
    num -= 1;
1177
0
  }
1178
0
  return s;
1179
0
}
1180
1181
0
GString *Catalog::makeLetterLabel(int num, GBool uppercase) {
1182
0
  GString *s;
1183
0
  int m, n, i;
1184
1185
0
  m = (num - 1) / 26 + 1;
1186
0
  n = (num - 1) % 26;
1187
0
  s = new GString();
1188
0
  for (i = 0; i < m; ++i) {
1189
0
    s->append((char)((uppercase ? 'A' : 'a') + n));
1190
0
  }
1191
0
  return s;
1192
0
}
1193
1194
0
int Catalog::getPageNumFromPageLabel(TextString *pageLabel) {
1195
0
  PageLabelNode *label;
1196
0
  int pageNum, prefixLength, i, n;
1197
1198
0
  if (!pageLabels) {
1199
0
    return -1;
1200
0
  }
1201
0
  for (i = 0; i < pageLabels->getLength(); ++i) {
1202
0
    label = (PageLabelNode *)pageLabels->get(i);
1203
0
    prefixLength = label->prefix->getLength();
1204
0
    if (pageLabel->getLength() < prefixLength ||
1205
0
  memcmp(pageLabel->getUnicode(), label->prefix->getUnicode(),
1206
0
         prefixLength * sizeof(Unicode))) {
1207
0
      continue;
1208
0
    }
1209
0
    if (label->style == '\0' && pageLabel->getLength() == prefixLength) {
1210
0
      return label->firstPage;
1211
0
    }
1212
0
    if (!convertPageLabelToInt(pageLabel, prefixLength, label->style, &n)) {
1213
0
      continue;
1214
0
    }
1215
0
    if (n < label->start) {
1216
0
      continue;
1217
0
    }
1218
0
    pageNum = label->firstPage + n - label->start;
1219
0
    if (pageNum <= label->lastPage) {
1220
0
      return pageNum;
1221
0
    }
1222
0
  }
1223
0
  return -1;
1224
0
}
1225
1226
// Attempts to convert pageLabel[prefixLength .. end] to an integer,
1227
// following the specified page label style.  If successful, sets *n
1228
// and returns true; else returns false.
1229
GBool Catalog::convertPageLabelToInt(TextString *pageLabel, int prefixLength,
1230
0
             char style, int *n) {
1231
0
  Unicode *u;
1232
0
  Unicode delta;
1233
0
  int len, i;
1234
1235
0
  len = pageLabel->getLength();
1236
0
  if (len <= prefixLength) {
1237
0
    return gFalse;
1238
0
  }
1239
0
  u = pageLabel->getUnicode();
1240
0
  if (style == 'D') {
1241
0
    *n = 0;
1242
0
    for (i = prefixLength; i < len; ++i) {
1243
0
      if (u[i] < (Unicode)'0' || u[i] > (Unicode)'9') {
1244
0
  return gFalse;
1245
0
      }
1246
0
      *n = *n * 10 + (u[i] - (Unicode)'0');
1247
0
    }
1248
0
    return gTrue;
1249
0
  } else if (style == 'R' || style == 'r') {
1250
0
    delta = style - 'R';
1251
0
    *n = 0;
1252
0
    i = prefixLength;
1253
0
    while (i < len && u[i] == (Unicode)'M' + delta) {
1254
0
      *n += 1000;
1255
0
      ++i;
1256
0
    }
1257
0
    if (i+1 < len && u[i] == (Unicode)'C' + delta &&
1258
0
  u[i+1] == (Unicode)'M' + delta) {
1259
0
      *n += 900;
1260
0
      i += 2;
1261
0
    } else if (i < len && u[i] == (Unicode)'D' + delta) {
1262
0
      *n += 500;
1263
0
      ++i;
1264
0
    } else if (i+1 < len && u[i] == (Unicode)'C' + delta &&
1265
0
         u[i+1] == (Unicode)'D' + delta) {
1266
0
      *n += 400;
1267
0
      i += 2;
1268
0
    }
1269
0
    while (i < len && u[i] == (Unicode)'C' + delta) {
1270
0
      *n += 100;
1271
0
      ++i;
1272
0
    }
1273
0
    if (i+1 < len && u[i] == (Unicode)'X' + delta &&
1274
0
  u[i+1] == (Unicode)'C' + delta) {
1275
0
      *n += 90;
1276
0
      i += 2;
1277
0
    } else if (i < len && u[i] == (Unicode)'L' + delta) {
1278
0
      *n += 50;
1279
0
      ++i;
1280
0
    } else if (i+1 < len && u[i] == (Unicode)'X' + delta &&
1281
0
         u[i+1] == (Unicode)'L' + delta) {
1282
0
      *n += 40;
1283
0
      i += 2;
1284
0
    }
1285
0
    while (i < len && u[i] == (Unicode)'X' + delta) {
1286
0
      *n += 10;
1287
0
      ++i;
1288
0
    }
1289
0
    if (i+1 < len && u[i] == (Unicode)'I' + delta &&
1290
0
  u[i+1] == (Unicode)'X' + delta) {
1291
0
      *n += 9;
1292
0
      i += 2;
1293
0
    } else if (i < len && u[i] == (Unicode)'V' + delta) {
1294
0
      *n += 5;
1295
0
      ++i;
1296
0
    } else if (i+1 < len && u[i] == (Unicode)'I' + delta &&
1297
0
         u[i+1] == (Unicode)'V' + delta) {
1298
0
      *n += 4;
1299
0
      i += 2;
1300
0
    }
1301
0
    while (i < len && u[i] == (Unicode)'I' + delta) {
1302
0
      *n += 1;
1303
0
      ++i;
1304
0
    }
1305
0
    return i == len;
1306
0
  } else if (style == 'A' || style == 'a') {
1307
0
    if (u[prefixLength] < (Unicode)style ||
1308
0
  u[prefixLength] > (Unicode)style + 25) {
1309
0
      return gFalse;
1310
0
    }
1311
0
    for (i = prefixLength + 1; i < len; ++i) {
1312
0
      if (u[i] != u[prefixLength]) {
1313
0
  return gFalse;
1314
0
      }
1315
0
    }
1316
0
    *n = (len - prefixLength - 1) * 26 + (u[prefixLength] - (Unicode)style) + 1;
1317
0
    return gTrue;
1318
0
  }
1319
0
  return gFalse;
1320
0
}
1321
1322
0
GBool Catalog::usesJavaScript() {
1323
0
  Object catDict;
1324
0
  if (!xref->getCatalog(&catDict)->isDict()) {
1325
0
    catDict.free();
1326
0
    return gFalse;
1327
0
  }
1328
1329
0
  GBool usesJS = gFalse;
1330
1331
  // check for Catalog.Names.JavaScript
1332
0
  Object namesObj;
1333
0
  if (catDict.dictLookup("Names", &namesObj)->isDict()) {
1334
0
    Object jsNamesObj;
1335
0
    namesObj.dictLookup("JavaScript", &jsNamesObj);
1336
0
    if (jsNamesObj.isDict()) {
1337
0
      usesJS = gTrue;
1338
0
    }
1339
0
    jsNamesObj.free();
1340
0
  }
1341
0
  namesObj.free();
1342
1343
  // look for JavaScript actionas in Page.AA
1344
0
  if (!usesJS) {
1345
0
    char *touchedObjs = (char *)gmalloc(xref->getNumObjects());
1346
0
    memset(touchedObjs, 0, xref->getNumObjects());
1347
0
    Object pagesObj;
1348
0
    usesJS = scanPageTreeForJavaScript(catDict.dictLookupNF("Pages", &pagesObj),
1349
0
               touchedObjs);
1350
0
    pagesObj.free();
1351
0
    gfree(touchedObjs);
1352
0
  }
1353
1354
0
  catDict.free();
1355
1356
0
  return usesJS;
1357
0
}
1358
1359
GBool Catalog::scanPageTreeForJavaScript(Object *pageNodeRef,
1360
0
           char *touchedObjs) {
1361
  // check for an invalid object reference (e.g., in a damaged PDF file)
1362
0
  if (pageNodeRef->isRef() &&
1363
0
      (pageNodeRef->getRefNum() < 0 ||
1364
0
       pageNodeRef->getRefNum() >= xref->getNumObjects())) {
1365
0
    return gFalse;
1366
0
  }
1367
1368
  // check for a page tree loop
1369
0
  Object pageNode;
1370
0
  if (pageNodeRef->isRef()) {
1371
0
    if (touchedObjs[pageNodeRef->getRefNum()]) {
1372
0
      return gFalse;
1373
0
    }
1374
0
    touchedObjs[pageNodeRef->getRefNum()] = 1;
1375
0
    xref->fetch(pageNodeRef->getRefNum(), pageNodeRef->getRefGen(), &pageNode);
1376
0
  } else {
1377
0
    pageNodeRef->copy(&pageNode);
1378
0
  }
1379
1380
  // scan the page tree node
1381
0
  GBool usesJS = gFalse;
1382
0
  if (pageNode.isDict()) {
1383
0
    Object kids;
1384
0
    if (checkDictLookup(&pageNode, "Kids", &kids, touchedObjs)->isArray()) {
1385
0
      for (int i = 0; i < kids.arrayGetLength() && !usesJS; ++i) {
1386
0
  Object kid;
1387
0
  if (scanPageTreeForJavaScript(kids.arrayGetNF(i, &kid), touchedObjs)) {
1388
0
    usesJS = gTrue;
1389
0
  }
1390
0
  kid.free();
1391
0
      }
1392
0
    } else {
1393
1394
      // scan Page.AA
1395
0
      Object pageAA;
1396
0
      if (checkDictLookup(&pageNode, "AA", &pageAA, touchedObjs)->isDict()) {
1397
0
  if (scanAAForJavaScript(&pageAA)) {
1398
0
    usesJS = gTrue;
1399
0
  }
1400
0
      }
1401
0
      pageAA.free();
1402
1403
      // scanPage.Annots
1404
0
      if (!usesJS) {
1405
0
  Object annots;
1406
0
  if (checkDictLookup(&pageNode, "Annots",
1407
0
          &annots, touchedObjs)->isArray()) {
1408
0
    for (int i = 0; i < annots.arrayGetLength() && !usesJS; ++i) {
1409
0
      Object annot;
1410
0
      if (checkArrayGet(&annots, i, &annot, touchedObjs)->isDict()) {
1411
0
        Object annotAA;
1412
0
        if (checkDictLookup(&annot, "AA", &annotAA,
1413
0
          touchedObjs)->isDict()) {
1414
0
    if (scanAAForJavaScript(&annotAA)) {
1415
0
      usesJS = gTrue;
1416
0
    }
1417
0
        }
1418
0
        annotAA.free();
1419
0
      }
1420
0
      annot.free();
1421
0
    }
1422
0
  }
1423
0
        annots.free();
1424
0
      }
1425
0
    }
1426
0
    kids.free();
1427
0
  }
1428
1429
0
  pageNode.free();
1430
1431
0
  return usesJS;
1432
0
}
1433
1434
0
GBool Catalog::scanAAForJavaScript(Object *aaObj) {
1435
0
  GBool usesJS = gFalse;
1436
0
  for (int i = 0; i < aaObj->dictGetLength() && !usesJS; ++i) {
1437
0
    Object action;
1438
0
    if (aaObj->dictGetVal(i, &action)->isDict()) {
1439
0
      Object js;
1440
0
      if (!action.dictLookupNF("JS", &js)->isNull()) {
1441
0
  usesJS = gTrue;
1442
0
      }
1443
0
      js.free();
1444
0
    }
1445
0
    action.free();
1446
0
  }
1447
0
  return usesJS;
1448
0
}
1449
1450
Object *Catalog::checkDictLookup(Object *dictObj, const char *key,
1451
29.7k
         Object *element, char *touchedObjs) {
1452
29.7k
  Object refObj;
1453
29.7k
  dictObj->dictLookupNF(key, &refObj);
1454
29.7k
  if (refObj.isRef()) {
1455
717
    int num = refObj.getRefNum();
1456
717
    if (num >= 0 && num < xref->getNumObjects() && !touchedObjs[num]) {
1457
242
      touchedObjs[num] = 1;
1458
242
      xref->fetch(num, refObj.getRefGen(), element);
1459
475
    } else {
1460
475
      element->initNull();
1461
475
    }
1462
717
    refObj.free();
1463
29.0k
  } else {
1464
29.0k
    *element = refObj;
1465
29.0k
  }
1466
29.7k
  return element;
1467
29.7k
}
1468
1469
Object *Catalog::checkArrayGet(Object *arrayObj, int i,
1470
15.3k
             Object *element, char *touchedObjs) {
1471
15.3k
  Object refObj;
1472
15.3k
  arrayObj->arrayGetNF(i, &refObj);
1473
15.3k
  if (refObj.isRef()) {
1474
6.80k
    int num = refObj.getRefNum();
1475
6.80k
    if (num >= 0 && num < xref->getNumObjects() && !touchedObjs[num]) {
1476
4.82k
      touchedObjs[num] = 1;
1477
4.82k
      xref->fetch(num, refObj.getRefGen(), element);
1478
4.82k
    } else {
1479
1.97k
      element->initNull();
1480
1.97k
    }
1481
6.80k
    refObj.free();
1482
8.51k
  } else {
1483
8.51k
    *element = refObj;
1484
8.51k
  }
1485
15.3k
  return element;
1486
15.3k
}