Coverage Report

Created: 2026-04-04 06:05

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
35.0k
PageTreeNode::PageTreeNode(Ref refA, int countA, PageTreeNode *parentA) {
49
35.0k
  ref = refA;
50
35.0k
  count = countA;
51
35.0k
  parent = parentA;
52
35.0k
  kids = NULL;
53
35.0k
  attrs = NULL;
54
35.0k
}
55
56
35.0k
PageTreeNode::~PageTreeNode() {
57
35.0k
  delete attrs;
58
35.0k
  if (kids) {
59
2.23k
    deleteGList(kids, PageTreeNode);
60
2.23k
  }
61
35.0k
}
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
118
EmbeddedFile::EmbeddedFile(TextString *nameA, Object *streamRefA) {
78
118
  name = nameA;
79
118
  streamRefA->copy(&streamRef);
80
118
}
81
82
118
EmbeddedFile::~EmbeddedFile() {
83
118
  delete name;
84
118
  streamRef.free();
85
118
}
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
718
PageLabelNode::PageLabelNode(int firstPageA, Dict *dict) {
106
718
  Object prefixObj, styleObj, startObj;
107
108
  // convert page index to page number
109
718
  firstPage = firstPageA + 1;
110
111
  // lastPage will be filled in later
112
718
  lastPage = -1;
113
114
718
  if (dict->lookup("P", &prefixObj)->isString()) {
115
368
    prefix = new TextString(prefixObj.getString());
116
368
  } else {
117
350
    prefix = new TextString();
118
350
  }
119
718
  prefixObj.free();
120
121
718
  style = '\0';
122
718
  if (dict->lookup("S", &styleObj)->isName()) {
123
118
    if (strlen(styleObj.getName()) == 1) {
124
67
      style = styleObj.getName()[0];
125
67
    }
126
118
  }
127
718
  styleObj.free();
128
129
718
  start = 1;
130
718
  if (dict->lookup("St", &startObj)->isInt()) {
131
43
    start = startObj.getInt();
132
43
  }
133
718
  startObj.free();
134
718
}
135
136
718
PageLabelNode::~PageLabelNode() {
137
718
  delete prefix;
138
718
}
139
140
//------------------------------------------------------------------------
141
// Catalog
142
//------------------------------------------------------------------------
143
144
3.10k
Catalog::Catalog(PDFDoc *docA) {
145
3.10k
  Object catDict;
146
3.10k
  Object obj, obj2;
147
148
3.10k
  ok = gTrue;
149
3.10k
  doc = docA;
150
3.10k
  xref = doc->getXRef();
151
3.10k
  pageTree = NULL;
152
3.10k
  pages = NULL;
153
3.10k
  pageRefs = NULL;
154
3.10k
  numPages = 0;
155
3.10k
  baseURI = NULL;
156
3.10k
  form = NULL;
157
3.10k
  embeddedFiles = NULL;
158
3.10k
  pageLabels = NULL;
159
3.10k
#if MULTITHREADED
160
3.10k
  gInitMutex(&pageMutex);
161
3.10k
#endif
162
163
3.10k
  xref->getCatalog(&catDict);
164
3.10k
  if (!catDict.isDict()) {
165
197
    error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})",
166
197
    catDict.getTypeName());
167
197
    goto err1;
168
197
  }
169
170
  // read page tree
171
2.90k
  if (!readPageTree(&catDict)) {
172
217
    goto err1;
173
217
  }
174
175
  // read named destination dictionary
176
2.69k
  catDict.dictLookup("Dests", &dests);
177
178
  // read root of named destination tree
179
2.69k
  if (catDict.dictLookup("Names", &obj)->isDict())
180
203
    obj.dictLookup("Dests", &nameTree);
181
2.48k
  else
182
2.48k
    nameTree.initNull();
183
2.69k
  obj.free();
184
185
  // read base URI
186
2.69k
  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.69k
  obj.free();
193
2.69k
  if (!baseURI || baseURI->getLength() == 0) {
194
2.69k
    if (baseURI) {
195
0
      delete baseURI;
196
0
    }
197
2.69k
    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.69k
    } else {
205
2.69k
      baseURI = new GString("file://localhost/");
206
2.69k
    }
207
2.69k
  }
208
209
  // get the metadata stream
210
2.69k
  catDict.dictLookup("Metadata", &metadata);
211
212
  // get the structure tree root
213
2.69k
  catDict.dictLookup("StructTreeRoot", &structTreeRoot);
214
215
  // get the outline dictionary
216
2.69k
  catDict.dictLookup("Outlines", &outline);
217
218
  // get the AcroForm dictionary
219
2.69k
  catDict.dictLookup("AcroForm", &acroForm);
220
221
  // get the NeedsRendering flag
222
  // NB: AcroForm::load() uses this value
223
2.69k
  needsRendering = catDict.dictLookup("NeedsRendering", &obj)->isBool() &&
224
12
                   obj.getBool();
225
2.69k
  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.69k
  form = AcroForm::load(doc, this, &acroForm);
231
232
  // get the OCProperties dictionary
233
2.69k
  catDict.dictLookup("OCProperties", &ocProperties);
234
235
  // get the list of embedded files
236
2.69k
  readEmbeddedFileList(catDict.getDict());
237
238
  // get the ViewerPreferences object
239
2.69k
  catDict.dictLookupNF("ViewerPreferences", &viewerPrefs);
240
241
2.69k
  if (catDict.dictLookup("PageLabels", &obj)->isDict()) {
242
201
    readPageLabelTree(&obj);
243
201
  }
244
2.69k
  obj.free();
245
246
2.69k
  catDict.free();
247
2.69k
  return;
248
249
414
 err1:
250
414
  catDict.free();
251
414
  dests.initNull();
252
414
  nameTree.initNull();
253
414
  ok = gFalse;
254
414
}
255
256
3.10k
Catalog::~Catalog() {
257
3.10k
  int i;
258
259
3.10k
  if (pageTree) {
260
2.69k
    delete pageTree;
261
2.69k
  }
262
3.10k
  if (pages) {
263
207k
    for (i = 0; i < numPages; ++i) {
264
204k
      if (pages[i]) {
265
204k
  delete pages[i];
266
204k
      }
267
204k
    }
268
2.67k
    gfree(pages);
269
2.67k
    gfree(pageRefs);
270
2.67k
  }
271
3.10k
#if MULTITHREADED
272
3.10k
  gDestroyMutex(&pageMutex);
273
3.10k
#endif
274
3.10k
  dests.free();
275
3.10k
  nameTree.free();
276
3.10k
  if (baseURI) {
277
2.69k
    delete baseURI;
278
2.69k
  }
279
3.10k
  metadata.free();
280
3.10k
  structTreeRoot.free();
281
3.10k
  outline.free();
282
3.10k
  acroForm.free();
283
3.10k
  if (form) {
284
1.31k
    delete form;
285
1.31k
  }
286
3.10k
  ocProperties.free();
287
3.10k
  if (embeddedFiles) {
288
10
    deleteGList(embeddedFiles, EmbeddedFile);
289
10
  }
290
3.10k
  if (pageLabels) {
291
93
    deleteGList(pageLabels, PageLabelNode);
292
93
  }
293
3.10k
  viewerPrefs.free();
294
3.10k
}
295
296
409k
Page *Catalog::getPage(int i) {
297
409k
  Page *page;
298
299
409k
#if MULTITHREADED
300
409k
  gLockMutex(&pageMutex);
301
409k
#endif
302
409k
  if (!pages[i-1]) {
303
204k
    loadPage(i);
304
204k
  }
305
409k
  page = pages[i-1];
306
409k
#if MULTITHREADED
307
409k
  gUnlockMutex(&pageMutex);
308
409k
#endif
309
409k
  return page;
310
409k
}
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
2.90k
GBool Catalog::readPageTree(Object *catDict) {
526
2.90k
  Object topPagesRef, topPagesObj, countObj;
527
2.90k
  int i;
528
529
2.90k
  if (!catDict->dictLookupNF("Pages", &topPagesRef)->isRef()) {
530
119
    error(errSyntaxError, -1, "Top-level pages reference is wrong type ({0:s})",
531
119
    topPagesRef.getTypeName());
532
119
    topPagesRef.free();
533
119
    return gFalse;
534
119
  }
535
2.79k
  if (!topPagesRef.fetch(xref, &topPagesObj)->isDict()) {
536
97
    error(errSyntaxError, -1, "Top-level pages object is wrong type ({0:s})",
537
97
    topPagesObj.getTypeName());
538
97
    topPagesObj.free();
539
97
    topPagesRef.free();
540
97
    return gFalse;
541
97
  }
542
2.69k
  if (topPagesObj.dictLookup("Count", &countObj)->isInt()) {
543
986
    numPages = countObj.getInt();
544
986
    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
191
      char *touchedObjs = (char *)gmalloc(xref->getNumObjects());
552
191
      memset(touchedObjs, 0, xref->getNumObjects());
553
191
      numPages = countPageTree(&topPagesRef, touchedObjs);
554
191
      gfree(touchedObjs);
555
191
    }
556
1.70k
  } else {
557
    // assume we got a Page node instead of a Pages node
558
1.70k
    numPages = 1;
559
1.70k
  }
560
2.69k
  countObj.free();
561
2.69k
  if (numPages < 0) {
562
1
    error(errSyntaxError, -1, "Invalid page count");
563
1
    topPagesObj.free();
564
1
    topPagesRef.free();
565
1
    numPages = 0;
566
1
    return gFalse;
567
1
  }
568
2.69k
  pageTree = new PageTreeNode(topPagesRef.getRef(), numPages, NULL);
569
2.69k
  topPagesObj.free();
570
2.69k
  topPagesRef.free();
571
2.69k
  pages = (Page **)greallocn(pages, numPages, sizeof(Page *));
572
2.69k
  pageRefs = (Ref *)greallocn(pageRefs, numPages, sizeof(Ref));
573
207k
  for (i = 0; i < numPages; ++i) {
574
204k
    pages[i] = NULL;
575
204k
    pageRefs[i].num = -1;
576
204k
    pageRefs[i].gen = -1;
577
204k
  }
578
2.69k
  return gTrue;
579
2.69k
}
580
581
115k
int Catalog::countPageTree(Object *pagesNodeRef, char *touchedObjs) {
582
  // check for invalid reference
583
115k
  if (pagesNodeRef->isRef() &&
584
19.4k
      (pagesNodeRef->getRefNum() < 0 ||
585
19.4k
       pagesNodeRef->getRefNum() >= xref->getNumObjects())) {
586
824
    return 0;
587
824
  }
588
589
  // check for a page tree loop; fetch the node object
590
114k
  Object pagesNode;
591
114k
  if (pagesNodeRef->isRef()) {
592
18.6k
    if (touchedObjs[pagesNodeRef->getRefNum()]) {
593
12.3k
      error(errSyntaxError, -1, "Loop in Pages tree");
594
12.3k
      return 0;
595
12.3k
    }
596
6.29k
    touchedObjs[pagesNodeRef->getRefNum()] = 1;
597
6.29k
    xref->fetch(pagesNodeRef->getRefNum(), pagesNodeRef->getRefGen(),
598
6.29k
    &pagesNode);
599
95.9k
  } else {
600
95.9k
    pagesNodeRef->copy(&pagesNode);
601
95.9k
  }
602
603
  // count the subtree
604
102k
  int n = 0;
605
102k
  if (pagesNode.isDict()) {
606
8.12k
    Object kidsRef, kids;
607
8.12k
    pagesNode.dictLookupNF("Kids", &kidsRef);
608
8.12k
    if (kidsRef.isRef() &&
609
46
  kidsRef.getRefNum() >= 0 &&
610
46
  kidsRef.getRefNum() < xref->getNumObjects()) {
611
43
      if (touchedObjs[kidsRef.getRefNum()]) {
612
21
  error(errSyntaxError, -1, "Loop in Pages tree");
613
21
  kidsRef.free();
614
21
  pagesNode.free();
615
21
  return 0;
616
21
      }
617
22
      touchedObjs[kidsRef.getRefNum()] = 1;
618
22
      xref->fetch(kidsRef.getRefNum(), kidsRef.getRefGen(), &kids);
619
8.07k
    } else {
620
8.07k
      kidsRef.copy(&kids);
621
8.07k
    }
622
8.10k
    kidsRef.free();
623
8.10k
    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
6.96k
    } else {
637
6.96k
      n = 1;
638
6.96k
    }
639
8.10k
    kids.free();
640
8.10k
  }
641
642
102k
  pagesNode.free();
643
644
102k
  return n;
645
102k
}
646
647
204k
void Catalog::loadPage(int pg) {
648
204k
  loadPage2(pg, pg - 1, pageTree);
649
204k
}
650
651
324k
void Catalog::loadPage2(int pg, int relPg, PageTreeNode *node) {
652
324k
  Object pageRefObj, pageObj, kidsObj, kidRefObj, kidObj, countObj;
653
324k
  PageTreeNode *kidNode, *p;
654
324k
  PageAttrs *attrs;
655
324k
  int count, i;
656
657
324k
  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
324k
  if (!node->kids) {
666
667
    // check for a loop in the page tree
668
308k
    for (p = node->parent; p; p = p->parent) {
669
118k
      if (node->ref.num == p->ref.num && node->ref.gen == p->ref.gen) {
670
1.98k
  error(errSyntaxError, -1, "Loop in Pages tree");
671
1.98k
  pages[pg-1] = new Page(doc, pg);
672
1.98k
  return;
673
1.98k
      }
674
118k
    }
675
676
    // fetch the Page/Pages object
677
190k
    pageRefObj.initRef(node->ref.num, node->ref.gen);
678
190k
    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
190k
    attrs = new PageAttrs(node->parent ? node->parent->attrs
689
190k
                     : (PageAttrs *)NULL,
690
190k
        pageObj.getDict(), xref);
691
692
    // if "Kids" exists, it's an internal node
693
190k
    if (pageObj.dictLookup("Kids", &kidsObj)->isArray()) {
694
695
      // save the PageAttrs
696
2.23k
      node->attrs = attrs;
697
698
      // read the kids
699
2.23k
      node->kids = new GList();
700
667k
      for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
701
665k
  if (kidsObj.arrayGetNF(i, &kidRefObj)->isRef()) {
702
155k
    if (kidRefObj.fetch(xref, &kidObj)->isDict()) {
703
32.3k
      if (kidObj.dictLookup("Count", &countObj)->isInt()) {
704
784
        count = countObj.getInt();
705
31.5k
      } else {
706
31.5k
        count = 1;
707
31.5k
      }
708
32.3k
      countObj.free();
709
32.3k
      node->kids->append(new PageTreeNode(kidRefObj.getRef(), count,
710
32.3k
            node));
711
122k
    } else {
712
122k
      error(errSyntaxError, -1, "Page tree object is wrong type ({0:s})",
713
122k
      kidObj.getTypeName());
714
122k
    }
715
155k
    kidObj.free();
716
510k
  } else {
717
510k
    error(errSyntaxError, -1,
718
510k
    "Page tree reference is wrong type ({0:s})",
719
510k
    kidRefObj.getTypeName());
720
510k
  }
721
665k
  kidRefObj.free();
722
665k
      }
723
724
188k
    } else {
725
      
726
      // create the Page object
727
188k
      pageRefs[pg-1] = node->ref;
728
188k
      pages[pg-1] = new Page(doc, pg, pageObj.getDict(), attrs);
729
188k
      if (!pages[pg-1]->isOk()) {
730
1.23k
  delete pages[pg-1];
731
1.23k
  pages[pg-1] = new Page(doc, pg);
732
1.23k
      }
733
734
188k
    }
735
736
190k
    kidsObj.free();
737
190k
    pageObj.free();
738
190k
    pageRefObj.free();
739
190k
  }
740
741
  // recursively descend the tree
742
322k
  if (node->kids) {
743
805k
    for (i = 0; i < node->kids->getLength(); ++i) {
744
791k
      kidNode = (PageTreeNode *)node->kids->get(i);
745
791k
      if (relPg < kidNode->count) {
746
119k
  loadPage2(pg, relPg, kidNode);
747
119k
  break;
748
119k
      }
749
671k
      relPg -= kidNode->count;
750
671k
    }
751
752
    // this will only happen if the page tree is invalid
753
    // (i.e., parent count > sum of children counts)
754
133k
    if (i == node->kids->getLength()) {
755
13.9k
      error(errSyntaxError, -1, "Invalid page count in page tree");
756
13.9k
      pages[pg-1] = new Page(doc, pg);
757
13.9k
    }
758
133k
  }
759
322k
}
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.69k
void Catalog::readEmbeddedFileList(Dict *catDict) {
802
2.69k
  Object obj1, obj2;
803
2.69k
  char *touchedObjs;
804
805
2.69k
  touchedObjs = (char *)gmalloc(xref->getNumObjects());
806
2.69k
  memset(touchedObjs, 0, xref->getNumObjects());
807
808
  // read the embedded file name tree
809
2.69k
  if (catDict->lookup("Names", &obj1)->isDict()) {
810
203
    obj1.dictLookupNF("EmbeddedFiles", &obj2);
811
203
    readEmbeddedFileTree(&obj2, touchedObjs);
812
203
    obj2.free();
813
203
  }
814
2.69k
  obj1.free();
815
816
  // look for file attachment annotations
817
2.69k
  readFileAttachmentAnnots(catDict->lookupNF("Pages", &obj1), touchedObjs);
818
2.69k
  obj1.free();
819
820
2.69k
  gfree(touchedObjs);
821
2.69k
}
822
823
27.0k
void Catalog::readEmbeddedFileTree(Object *nodeRef, char *touchedObjs) {
824
27.0k
  Object node, kidsObj, kidObj;
825
27.0k
  Object namesObj, nameObj, fileSpecObj;
826
27.0k
  int i;
827
828
  // check for an object loop
829
27.0k
  if (nodeRef->isRef()) {
830
3.66k
    if (nodeRef->getRefNum() < 0 ||
831
3.66k
  nodeRef->getRefNum() >= xref->getNumObjects() ||
832
3.08k
  touchedObjs[nodeRef->getRefNum()]) {
833
3.08k
      return;
834
3.08k
    }
835
584
    touchedObjs[nodeRef->getRefNum()] = 1;
836
584
    xref->fetch(nodeRef->getRefNum(), nodeRef->getRefGen(), &node);
837
23.3k
  } else {
838
23.3k
    nodeRef->copy(&node);
839
23.3k
  }
840
841
23.9k
  if (!node.isDict()) {
842
22.4k
    node.free();
843
22.4k
    return;
844
22.4k
  }
845
846
1.52k
  if (checkDictLookup(&node, "Kids", &kidsObj, touchedObjs)->isArray()) {
847
27.4k
    for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
848
26.8k
      kidsObj.arrayGetNF(i, &kidObj);
849
26.8k
      readEmbeddedFileTree(&kidObj, touchedObjs);
850
26.8k
      kidObj.free();
851
26.8k
    }
852
882
  } else {
853
882
    if (checkDictLookup(&node, "Names", &namesObj, touchedObjs)->isArray()) {
854
8.90k
      for (i = 0; i+1 < namesObj.arrayGetLength(); ++i) {
855
8.81k
  namesObj.arrayGet(i, &nameObj);
856
8.81k
  namesObj.arrayGet(i+1, &fileSpecObj);
857
8.81k
  readEmbeddedFile(&fileSpecObj, &nameObj);
858
8.81k
  nameObj.free();
859
8.81k
  fileSpecObj.free();
860
8.81k
      }
861
99
    }
862
882
    namesObj.free();
863
882
  }
864
1.52k
  kidsObj.free();
865
866
1.52k
  node.free();
867
1.52k
}
868
869
void Catalog::readFileAttachmentAnnots(Object *pageNodeRef,
870
174k
               char *touchedObjs) {
871
174k
  Object pageNode, kids, kid, annots, annot, subtype, fileSpec, contents;
872
174k
  int i;
873
874
  // check for an invalid object reference (e.g., in a damaged PDF file)
875
174k
  if (pageNodeRef->isRef() &&
876
26.2k
      (pageNodeRef->getRefNum() < 0 ||
877
26.2k
       pageNodeRef->getRefNum() >= xref->getNumObjects())) {
878
1.29k
    return;
879
1.29k
  }
880
881
  // check for a page tree loop
882
173k
  if (pageNodeRef->isRef()) {
883
24.9k
    if (touchedObjs[pageNodeRef->getRefNum()]) {
884
14.8k
      return;
885
14.8k
    }
886
10.0k
    touchedObjs[pageNodeRef->getRefNum()] = 1;
887
10.0k
    xref->fetch(pageNodeRef->getRefNum(), pageNodeRef->getRefGen(), &pageNode);
888
148k
  } else {
889
148k
    pageNodeRef->copy(&pageNode);
890
148k
  }
891
892
158k
  if (pageNode.isDict()) {
893
14.0k
    if (checkDictLookup(&pageNode, "Kids", &kids, touchedObjs)->isArray()) {
894
175k
      for (i = 0; i < kids.arrayGetLength(); ++i) {
895
172k
  readFileAttachmentAnnots(kids.arrayGetNF(i, &kid), touchedObjs);
896
172k
  kid.free();
897
172k
      }
898
10.8k
    } else {
899
10.8k
      if (checkDictLookup(&pageNode, "Annots",
900
10.8k
        &annots, touchedObjs)->isArray()) {
901
14.4k
  for (i = 0; i < annots.arrayGetLength(); ++i) {
902
14.1k
    if (checkArrayGet(&annots, i, &annot, touchedObjs)->isDict()) {
903
1.32k
      if (checkDictLookup(&annot, "Subtype", &subtype, touchedObjs)
904
1.32k
      ->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.32k
      subtype.free();
914
1.32k
    }
915
14.1k
    annot.free();
916
14.1k
  }
917
272
      }
918
10.8k
      annots.free();
919
10.8k
    }
920
14.0k
    kids.free();
921
14.0k
  }
922
923
158k
  pageNode.free();
924
158k
}
925
926
8.81k
void Catalog::readEmbeddedFile(Object *fileSpec, Object *name1) {
927
8.81k
  Object name2, efObj, streamObj;
928
8.81k
  GString *s;
929
8.81k
  TextString *name;
930
931
8.81k
  if (fileSpec->isDict()) {
932
411
    if (fileSpec->dictLookup("UF", &name2)->isString()) {
933
0
      name = new TextString(name2.getString());
934
411
    } else {
935
411
      name2.free();
936
411
      if (fileSpec->dictLookup("F", &name2)->isString()) {
937
88
  name = new TextString(name2.getString());
938
323
      } else if (name1 && name1->isString()) {
939
18
  name = new TextString(name1->getString());
940
305
      } else {
941
305
  s = new GString("?");
942
305
  name = new TextString(s);
943
305
  delete s;
944
305
      }
945
411
    }
946
411
    name2.free();
947
411
    if (fileSpec->dictLookup("EF", &efObj)->isDict()) {
948
206
      if (efObj.dictLookupNF("F", &streamObj)->isRef()) {
949
118
  if (!embeddedFiles) {
950
10
    embeddedFiles = new GList();
951
10
  }
952
118
  embeddedFiles->append(new EmbeddedFile(name, &streamObj));
953
118
      } else {
954
88
  delete name;
955
88
      }
956
206
      streamObj.free();
957
206
    } else {
958
205
      delete name;
959
205
    }
960
411
    efObj.free();
961
411
  }
962
8.81k
}
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
201
void Catalog::readPageLabelTree(Object *root) {
990
201
  PageLabelNode *label0, *label1;
991
201
  char *touchedObjs;
992
201
  int i;
993
994
201
  touchedObjs = (char *)gmalloc(xref->getNumObjects());
995
201
  memset(touchedObjs, 0, xref->getNumObjects());
996
201
  pageLabels = new GList();
997
201
  readPageLabelTree2(root, touchedObjs);
998
201
  gfree(touchedObjs);
999
1000
201
  if (pageLabels->getLength() == 0) {
1001
108
    deleteGList(pageLabels, PageLabelNode);
1002
108
    pageLabels = NULL;
1003
108
    return;
1004
108
  }
1005
1006
  // set lastPage in each node
1007
93
  label0 = (PageLabelNode *)pageLabels->get(0);
1008
718
  for (i = 1; i < pageLabels->getLength(); ++i) {
1009
625
    label1 = (PageLabelNode *)pageLabels->get(i);
1010
625
    label0->lastPage = label1->firstPage - 1;
1011
625
    label0 = label1;
1012
625
  }
1013
93
  label0->lastPage = numPages;
1014
93
}
1015
1016
38.6k
void Catalog::readPageLabelTree2(Object *nodeRef, char *touchedObjs) {
1017
38.6k
  Object node, nums, num, labelObj, kidsRef, kids, kid;
1018
38.6k
  int i;
1019
1020
  // check for an object loop
1021
38.6k
  if (nodeRef->isRef()) {
1022
2.05k
    if (nodeRef->getRefNum() < 0 ||
1023
2.05k
  nodeRef->getRefNum() >= xref->getNumObjects() ||
1024
1.84k
  touchedObjs[nodeRef->getRefNum()]) {
1025
1.84k
      return;
1026
1.84k
    }
1027
202
    touchedObjs[nodeRef->getRefNum()] = 1;
1028
202
    xref->fetch(nodeRef->getRefNum(), nodeRef->getRefGen(), &node);
1029
36.5k
  } else {
1030
36.5k
    nodeRef->copy(&node);
1031
36.5k
  }
1032
1033
36.8k
  if (!node.isDict()) {
1034
34.2k
    node.free();
1035
34.2k
    return;
1036
34.2k
  }
1037
1038
2.53k
  if (node.dictLookup("Nums", &nums)->isArray()) {
1039
5.30k
    for (i = 0; i < nums.arrayGetLength() - 1; i += 2) {
1040
5.17k
      if (nums.arrayGet(i, &num)->isInt()) {
1041
1.87k
  if (nums.arrayGet(i+1, &labelObj)->isDict()) {
1042
718
    pageLabels->append(new PageLabelNode(num.getInt(),
1043
718
                 labelObj.getDict()));
1044
718
  }
1045
1.87k
  labelObj.free();
1046
1.87k
      }
1047
5.17k
      num.free();
1048
5.17k
    }
1049
133
  }
1050
2.53k
  nums.free();
1051
1052
  // check for an object loop in the Kids entry
1053
2.53k
  if (node.dictLookupNF("Kids", &kidsRef)->isRef()) {
1054
132
    if (kidsRef.getRefNum() < 0 ||
1055
132
  kidsRef.getRefNum() >= doc->getXRef()->getNumObjects() ||
1056
125
  touchedObjs[kidsRef.getRefNum()]) {
1057
125
      kidsRef.free();
1058
125
      node.free();
1059
125
      return;
1060
125
    }
1061
7
    touchedObjs[kidsRef.getRefNum()] = 1;
1062
7
    kidsRef.fetch(doc->getXRef(), &kids);
1063
2.39k
  } else {
1064
2.39k
    kidsRef.copy(&kids);
1065
2.39k
  }
1066
2.40k
  kidsRef.free();
1067
1068
2.40k
  if (kids.isArray()) {
1069
39.8k
    for (i = 0; i < kids.arrayGetLength(); ++i) {
1070
38.4k
      kids.arrayGetNF(i, &kid);
1071
38.4k
      readPageLabelTree2(&kid, touchedObjs);
1072
38.4k
      kid.free();
1073
38.4k
    }
1074
1.37k
  }
1075
2.40k
  kids.free();
1076
1077
2.40k
  node.free();
1078
2.40k
}
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
28.6k
         Object *element, char *touchedObjs) {
1452
28.6k
  Object refObj;
1453
28.6k
  dictObj->dictLookupNF(key, &refObj);
1454
28.6k
  if (refObj.isRef()) {
1455
537
    int num = refObj.getRefNum();
1456
537
    if (num >= 0 && num < xref->getNumObjects() && !touchedObjs[num]) {
1457
247
      touchedObjs[num] = 1;
1458
247
      xref->fetch(num, refObj.getRefGen(), element);
1459
290
    } else {
1460
290
      element->initNull();
1461
290
    }
1462
537
    refObj.free();
1463
28.1k
  } else {
1464
28.1k
    *element = refObj;
1465
28.1k
  }
1466
28.6k
  return element;
1467
28.6k
}
1468
1469
Object *Catalog::checkArrayGet(Object *arrayObj, int i,
1470
14.1k
             Object *element, char *touchedObjs) {
1471
14.1k
  Object refObj;
1472
14.1k
  arrayObj->arrayGetNF(i, &refObj);
1473
14.1k
  if (refObj.isRef()) {
1474
6.83k
    int num = refObj.getRefNum();
1475
6.83k
    if (num >= 0 && num < xref->getNumObjects() && !touchedObjs[num]) {
1476
5.56k
      touchedObjs[num] = 1;
1477
5.56k
      xref->fetch(num, refObj.getRefGen(), element);
1478
5.56k
    } else {
1479
1.27k
      element->initNull();
1480
1.27k
    }
1481
6.83k
    refObj.free();
1482
7.34k
  } else {
1483
7.34k
    *element = refObj;
1484
7.34k
  }
1485
14.1k
  return element;
1486
14.1k
}