Coverage Report

Created: 2026-04-09 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/xpdf-4.06/xpdf/OptionalContent.cc
Line
Count
Source
1
//========================================================================
2
//
3
// OptionalContent.cc
4
//
5
// Copyright 2008-2013 Glyph & Cog, LLC
6
//
7
//========================================================================
8
9
#include <aconf.h>
10
11
#include "gmempp.h"
12
#include "GString.h"
13
#include "GList.h"
14
#include "Error.h"
15
#include "Object.h"
16
#include "PDFDoc.h"
17
#include "TextString.h"
18
#include "OptionalContent.h"
19
20
//------------------------------------------------------------------------
21
22
10.1k
#define ocPolicyAllOn  1
23
7.43k
#define ocPolicyAnyOn  2
24
0
#define ocPolicyAnyOff 3
25
10.1k
#define ocPolicyAllOff 4
26
27
//------------------------------------------------------------------------
28
29
// Max depth of nested visibility expressions.  This is used to catch
30
// infinite loops in the visibility expression object structure.
31
1.13k
#define visibilityExprRecursionLimit 50
32
33
// Max depth of nested display nodes.  This is used to catch infinite
34
// loops in the "Order" object structure.
35
1.59M
#define displayNodeRecursionLimit 50
36
37
//------------------------------------------------------------------------
38
39
19.1k
OptionalContent::OptionalContent(PDFDoc *doc) {
40
19.1k
  Object *ocProps;
41
19.1k
  Object ocgList, defView, uad, obj1, obj2, obj3, obj4;
42
19.1k
  Ref ref1;
43
19.1k
  OptionalContentGroup *ocg;
44
19.1k
  int i, j;
45
46
19.1k
  xref = doc->getXRef();
47
19.1k
  ocgs = new GList();
48
19.1k
  display = NULL;
49
50
19.1k
  if ((ocProps = doc->getCatalog()->getOCProperties())->isDict()) {
51
895
    if (ocProps->dictLookup("OCGs", &ocgList)->isArray()) {
52
53
      //----- read the OCG list
54
48.0k
      for (i = 0; i < ocgList.arrayGetLength(); ++i) {
55
47.2k
  if (ocgList.arrayGetNF(i, &obj1)->isRef()) {
56
11.8k
    ref1 = obj1.getRef();
57
11.8k
    obj1.fetch(xref, &obj2);
58
11.8k
    if ((ocg = OptionalContentGroup::parse(&ref1, &obj2))) {
59
6.44k
      ocgs->append(ocg);
60
6.44k
    }
61
11.8k
    obj2.free();
62
11.8k
  }
63
47.2k
  obj1.free();
64
47.2k
      }
65
66
      //----- read the default viewing OCCD
67
836
      if (ocProps->dictLookup("D", &defView)->isDict()) {
68
69
  //----- read the usage app dicts
70
664
  if (defView.dictLookup("AS", &obj1)->isArray()) {
71
3.11k
    for (i = 0; i < obj1.arrayGetLength(); ++i) {
72
3.05k
      if (obj1.arrayGet(i, &uad)->isDict()) {
73
252
        if (uad.dictLookup("Event", &obj2)->isName("View")) {
74
41
    if (uad.dictLookup("OCGs", &obj3)->isArray()) {
75
303
      for (j = 0; j < obj3.arrayGetLength(); ++j) {
76
265
        if (obj3.arrayGetNF(j, &obj4)->isRef()) {
77
81
          ref1 = obj4.getRef();
78
81
          if ((ocg = findOCG(&ref1))) {
79
35
      ocg->setInViewUsageAppDict();
80
35
          }
81
81
        }
82
265
        obj4.free();
83
265
      }
84
38
    }
85
41
    obj3.free();
86
41
        }
87
252
        obj2.free();
88
252
      }
89
3.05k
      uad.free();
90
3.05k
    }
91
67
  }
92
664
  obj1.free();
93
94
  //----- initial state from OCCD
95
664
  if (defView.dictLookup("OFF", &obj1)->isArray()) {
96
30.0k
    for (i = 0; i < obj1.arrayGetLength(); ++i) {
97
29.8k
      if (obj1.arrayGetNF(i, &obj2)->isRef()) {
98
23.5k
        ref1 = obj2.getRef();
99
23.5k
        if ((ocg = findOCG(&ref1))) {
100
9.54k
    ocg->setState(gFalse);
101
13.9k
        } else {
102
13.9k
    error(errSyntaxError, -1,
103
13.9k
          "Invalid OCG reference in OFF array in default viewing OCCD");
104
13.9k
        }
105
23.5k
      }
106
29.8k
      obj2.free();
107
29.8k
    }
108
184
  }
109
664
  obj1.free();
110
111
  //----- initial state from OCG usage dict
112
5.13k
  for (i = 0; i < ocgs->getLength(); ++i) {
113
4.46k
    ocg = (OptionalContentGroup *)ocgs->get(i);
114
4.46k
    if (ocg->getInViewUsageAppDict() &&
115
33
        ocg->getViewState() != ocUsageUnset) {
116
3
      ocg->setState(ocg->getViewState() == ocUsageOn);
117
3
    }
118
4.46k
  }
119
120
  //----- display order
121
664
  if (defView.dictLookup("Order", &obj1)->isArray()) {
122
527
    display = OCDisplayNode::parse(&obj1, this, xref);
123
527
  }
124
664
  obj1.free();
125
126
664
      } else {
127
172
  error(errSyntaxError, -1, "Missing or invalid default viewing OCCD");
128
172
      }
129
836
      defView.free();
130
131
836
    }
132
895
    ocgList.free();
133
895
  }
134
135
19.1k
  if (!display) {
136
18.6k
    display = new OCDisplayNode();
137
18.6k
  }
138
19.1k
}
139
140
19.1k
OptionalContent::~OptionalContent() {
141
19.1k
  deleteGList(ocgs, OptionalContentGroup);
142
19.1k
  delete display;
143
19.1k
}
144
145
0
int OptionalContent::getNumOCGs() {
146
0
  return ocgs->getLength();
147
0
}
148
149
0
OptionalContentGroup *OptionalContent::getOCG(int idx) {
150
0
  return (OptionalContentGroup *)ocgs->get(idx);
151
0
}
152
153
121k
OptionalContentGroup *OptionalContent::findOCG(Ref *ref) {
154
121k
  OptionalContentGroup *ocg;
155
121k
  int i;
156
157
1.23M
  for (i = 0; i < ocgs->getLength(); ++i) {
158
1.13M
    ocg = (OptionalContentGroup *)ocgs->get(i);
159
1.13M
    if (ocg->matches(ref)) {
160
20.1k
      return ocg;
161
20.1k
    }
162
1.13M
  }
163
100k
  return NULL;
164
121k
}
165
166
96.8k
GBool OptionalContent::evalOCObject(Object *obj, GBool *visible) {
167
96.8k
  OptionalContentGroup *ocg;
168
96.8k
  int policy;
169
96.8k
  Ref ref;
170
96.8k
  Object obj2, obj3, obj4, obj5;
171
96.8k
  GBool gotOCG;
172
96.8k
  int i;
173
174
96.8k
  if (obj->isNull()) {
175
86.4k
    return gFalse;
176
86.4k
  }
177
10.3k
  if (obj->isRef()) {
178
9.92k
    ref = obj->getRef();
179
9.92k
    if ((ocg = findOCG(&ref))) {
180
73
      *visible = ocg->getState();
181
73
      return gTrue;
182
73
    }
183
9.92k
  }
184
10.2k
  obj->fetch(xref, &obj2);
185
10.2k
  if (!obj2.isDict("OCMD")) {
186
5.07k
    obj2.free();
187
5.07k
    return gFalse;
188
5.07k
  }
189
5.16k
  if (obj2.dictLookup("VE", &obj3)->isArray()) {
190
96
    *visible = evalOCVisibilityExpr(&obj3, 0);
191
96
    obj3.free();
192
5.06k
  } else {
193
5.06k
    obj3.free();
194
5.06k
    policy = ocPolicyAnyOn;
195
5.06k
    if (obj2.dictLookup("P", &obj3)->isName()) {
196
0
      if (obj3.isName("AllOn")) {
197
0
  policy = ocPolicyAllOn;
198
0
      } else if (obj3.isName("AnyOn")) {
199
0
  policy = ocPolicyAnyOn;
200
0
      } else if (obj3.isName("AnyOff")) {
201
0
  policy = ocPolicyAnyOff;
202
0
      } else if (obj3.isName("AllOff")) {
203
0
  policy = ocPolicyAllOff;
204
0
      }
205
0
    }
206
5.06k
    obj3.free();
207
5.06k
    obj2.dictLookupNF("OCGs", &obj3);
208
5.06k
    ocg = NULL;
209
5.06k
    if (obj3.isRef()) {
210
39
      ref = obj3.getRef();
211
39
      ocg = findOCG(&ref);
212
39
    }
213
5.06k
    if (ocg) {
214
12
      *visible = (policy == ocPolicyAllOn || policy == ocPolicyAnyOn) ?
215
12
             ocg->getState() : !ocg->getState();
216
5.05k
    } else {
217
5.05k
      *visible = policy == ocPolicyAllOn || policy == ocPolicyAllOff;
218
5.05k
      if (!obj3.fetch(xref, &obj4)->isArray()) {
219
1.26k
  obj4.free();
220
1.26k
  obj3.free();
221
1.26k
  obj2.free();
222
1.26k
  return gFalse;
223
1.26k
      }
224
3.78k
      gotOCG = gFalse;
225
21.4k
      for (i = 0; i < obj4.arrayGetLength(); ++i) {
226
17.6k
  obj4.arrayGetNF(i, &obj5);
227
17.6k
  if (obj5.isRef()) {
228
4.33k
    ref = obj5.getRef();
229
4.33k
    if ((ocg = findOCG(&ref))) {
230
2.36k
      gotOCG = gTrue;
231
2.36k
      switch (policy) {
232
0
      case ocPolicyAllOn:
233
0
        *visible = *visible && ocg->getState();
234
0
        break;
235
2.36k
      case ocPolicyAnyOn:
236
2.36k
        *visible = *visible || ocg->getState();
237
2.36k
        break;
238
0
      case ocPolicyAnyOff:
239
0
        *visible = *visible || !ocg->getState();
240
0
        break;
241
0
      case ocPolicyAllOff:
242
0
        *visible = *visible && !ocg->getState();
243
0
        break;
244
2.36k
      }
245
2.36k
    }
246
4.33k
  }
247
17.6k
  obj5.free();
248
17.6k
      }
249
3.78k
      if (!gotOCG) {
250
  // if the "OCGs" array is "empty or contains references only
251
  // to null or deleted objects", this OCMD doesn't have any
252
  // effect
253
1.63k
  obj4.free();
254
1.63k
  obj3.free();
255
1.63k
  obj2.free();
256
1.63k
  return gFalse;
257
1.63k
      }
258
2.15k
      obj4.free();
259
2.15k
    }
260
2.16k
    obj3.free();
261
2.16k
  }
262
2.26k
  obj2.free();
263
2.26k
  return gTrue;
264
5.16k
}
265
266
1.13k
GBool OptionalContent::evalOCVisibilityExpr(Object *expr, int recursion) {
267
1.13k
  OptionalContentGroup *ocg;
268
1.13k
  Object expr2, op, obj;
269
1.13k
  Ref ref;
270
1.13k
  GBool ret;
271
1.13k
  int i;
272
273
1.13k
  if (recursion > visibilityExprRecursionLimit) {
274
0
    error(errSyntaxError, -1,
275
0
    "Loop detected in optional content visibility expression");
276
0
    return gTrue;
277
0
  }
278
1.13k
  if (expr->isRef()) {
279
124
    ref = expr->getRef();
280
124
    if ((ocg = findOCG(&ref))) {
281
52
      return ocg->getState();
282
52
    }
283
124
  }
284
1.08k
  expr->fetch(xref, &expr2);
285
1.08k
  if (!expr2.isArray() || expr2.arrayGetLength() < 1) {
286
933
    error(errSyntaxError, -1,
287
933
    "Invalid optional content visibility expression");
288
933
    expr2.free();
289
933
    return gTrue;
290
933
  }
291
147
  expr2.arrayGet(0, &op);
292
147
  if (op.isName("Not")) {
293
26
    if (expr2.arrayGetLength() == 2) {
294
20
      expr2.arrayGetNF(1, &obj);
295
20
      ret = !evalOCVisibilityExpr(&obj, recursion + 1);
296
20
      obj.free();
297
20
    } else {
298
6
      error(errSyntaxError, -1,
299
6
      "Invalid optional content visibility expression");
300
6
      ret = gTrue;
301
6
    }
302
121
  } else if (op.isName("And")) {
303
67
    ret = gTrue;
304
1.07k
    for (i = 1; i < expr2.arrayGetLength() && ret; ++i) {
305
1.00k
      expr2.arrayGetNF(i, &obj);
306
1.00k
      ret = evalOCVisibilityExpr(&obj, recursion + 1);
307
1.00k
      obj.free();
308
1.00k
    }
309
67
  } else if (op.isName("Or")) {
310
9
    ret = gFalse;
311
18
    for (i = 1; i < expr2.arrayGetLength() && !ret; ++i) {
312
9
      expr2.arrayGetNF(i, &obj);
313
9
      ret = evalOCVisibilityExpr(&obj, recursion + 1);
314
9
      obj.free();
315
9
    }
316
45
  } else {
317
45
    error(errSyntaxError, -1,
318
45
    "Invalid optional content visibility expression");
319
45
    ret = gTrue;
320
45
  }
321
147
  op.free();
322
147
  expr2.free();
323
147
  return ret;
324
1.08k
}
325
326
//------------------------------------------------------------------------
327
328
11.8k
OptionalContentGroup *OptionalContentGroup::parse(Ref *refA, Object *obj) {
329
11.8k
  TextString *nameA;
330
11.8k
  Object obj1, obj2, obj3;
331
11.8k
  OCUsageState viewStateA, printStateA;
332
333
11.8k
  if (!obj->isDict()) {
334
4.04k
    return NULL;
335
4.04k
  }
336
7.78k
  if (!obj->dictLookup("Name", &obj1)->isString()) {
337
1.34k
    error(errSyntaxError, -1, "Missing or invalid Name in OCG");
338
1.34k
    obj1.free();
339
1.34k
    return NULL;
340
1.34k
  }
341
6.44k
  nameA = new TextString(obj1.getString());
342
6.44k
  obj1.free();
343
344
6.44k
  viewStateA = printStateA = ocUsageUnset;
345
6.44k
  if (obj->dictLookup("Usage", &obj1)->isDict()) {
346
156
    if (obj1.dictLookup("View", &obj2)->isDict()) {
347
48
      if (obj2.dictLookup("ViewState", &obj3)->isName()) {
348
22
  if (obj3.isName("ON")) {
349
16
    viewStateA = ocUsageOn;
350
16
  } else {
351
6
    viewStateA = ocUsageOff;
352
6
  }
353
22
      }
354
48
      obj3.free();
355
48
    }
356
156
    obj2.free();
357
156
    if (obj1.dictLookup("Print", &obj2)->isDict()) {
358
52
      if (obj2.dictLookup("PrintState", &obj3)->isName()) {
359
23
  if (obj3.isName("ON")) {
360
17
    printStateA = ocUsageOn;
361
17
  } else {
362
6
    printStateA = ocUsageOff;
363
6
  }
364
23
      }
365
52
      obj3.free();
366
52
    }
367
156
    obj2.free();
368
156
  }
369
6.44k
  obj1.free();
370
371
6.44k
  return new OptionalContentGroup(refA, nameA, viewStateA, printStateA);
372
7.78k
}
373
374
OptionalContentGroup::OptionalContentGroup(Ref *refA, TextString *nameA,
375
             OCUsageState viewStateA,
376
6.44k
             OCUsageState printStateA) {
377
6.44k
  ref = *refA;
378
6.44k
  name = nameA;
379
6.44k
  viewState = viewStateA;
380
6.44k
  printState = printStateA;
381
6.44k
  state = gTrue;
382
6.44k
  inViewUsageAppDict = gFalse;
383
6.44k
}
384
385
6.44k
OptionalContentGroup::~OptionalContentGroup() {
386
6.44k
  delete name;
387
6.44k
}
388
389
1.13M
GBool OptionalContentGroup::matches(Ref *refA) {
390
1.13M
  return refA->num == ref.num && refA->gen == ref.gen;
391
1.13M
}
392
393
0
Unicode *OptionalContentGroup::getName() {
394
0
  return name->getUnicode();
395
0
}
396
397
0
int OptionalContentGroup::getNameLength() {
398
0
  return name->getLength();
399
0
}
400
401
//------------------------------------------------------------------------
402
403
OCDisplayNode *OCDisplayNode::parse(Object *obj, OptionalContent *oc,
404
1.59M
            XRef *xref, int recursion) {
405
1.59M
  Object obj2, obj3;
406
1.59M
  Ref ref;
407
1.59M
  OptionalContentGroup *ocgA;
408
1.59M
  OCDisplayNode *node, *child;
409
1.59M
  int i;
410
411
1.59M
  if (recursion > displayNodeRecursionLimit) {
412
200k
    error(errSyntaxError, -1, "Loop detected in optional content order");
413
200k
    return NULL;
414
200k
  }
415
1.38M
  if (obj->isRef()) {
416
83.1k
    ref = obj->getRef();
417
83.1k
    if ((ocgA = oc->findOCG(&ref))) {
418
8.11k
      return new OCDisplayNode(ocgA);
419
8.11k
    }
420
83.1k
  }
421
1.38M
  obj->fetch(xref, &obj2);
422
1.38M
  if (!obj2.isArray()) {
423
1.16M
    obj2.free();
424
1.16M
    return NULL;
425
1.16M
  }
426
213k
  i = 0;
427
213k
  if (obj2.arrayGetLength() >= 1) {
428
211k
    if (obj2.arrayGet(0, &obj3)->isString()) {
429
11.6k
      node = new OCDisplayNode(obj3.getString());
430
11.6k
      i = 1;
431
200k
    } else {
432
200k
      node = new OCDisplayNode();
433
200k
    }
434
211k
    obj3.free();
435
211k
  } else {
436
1.88k
    node = new OCDisplayNode();
437
1.88k
  }
438
1.80M
  for (; i < obj2.arrayGetLength(); ++i) {
439
1.59M
    obj2.arrayGetNF(i, &obj3);
440
1.59M
    if ((child = OCDisplayNode::parse(&obj3, oc, xref, recursion + 1))) {
441
221k
      if (!child->ocg && !child->name && node->getNumChildren() > 0) {
442
0
  if (child->getNumChildren() > 0) {
443
0
    node->getChild(node->getNumChildren() - 1)->
444
0
              addChildren(child->takeChildren());
445
0
  }
446
0
  delete child;
447
221k
      } else {
448
221k
  node->addChild(child);
449
221k
      }
450
221k
    }
451
1.59M
    obj3.free();
452
1.59M
  }
453
213k
  obj2.free();
454
213k
  return node;
455
1.38M
}
456
457
220k
OCDisplayNode::OCDisplayNode() {
458
220k
  name = new TextString();
459
220k
  ocg = NULL;
460
220k
  parent = NULL;
461
220k
  children = NULL;
462
220k
}
463
464
11.6k
OCDisplayNode::OCDisplayNode(GString *nameA) {
465
11.6k
  name = new TextString(nameA);
466
11.6k
  ocg = NULL;
467
11.6k
  children = NULL;
468
11.6k
}
469
470
8.11k
OCDisplayNode::OCDisplayNode(OptionalContentGroup *ocgA) {
471
8.11k
  name = new TextString(ocgA->name);
472
8.11k
  ocg = ocgA;
473
8.11k
  children = NULL;
474
8.11k
}
475
476
221k
void OCDisplayNode::addChild(OCDisplayNode *child) {
477
221k
  if (!children) {
478
147k
    children = new GList();
479
147k
  }
480
221k
  children->append(child);
481
221k
  child->parent = this;
482
221k
}
483
484
0
void OCDisplayNode::addChildren(GList *childrenA) {
485
0
  int i;
486
487
0
  if (!children) {
488
0
    children = new GList();
489
0
  }
490
0
  children->append(childrenA);
491
0
  for (i = 0; i < childrenA->getLength(); ++i) {
492
0
    ((OCDisplayNode *)childrenA->get(i))->parent = this;
493
0
  }
494
0
  delete childrenA;
495
0
}
496
497
0
GList *OCDisplayNode::takeChildren() {
498
0
  GList *childrenA;
499
0
  int i;
500
501
0
  childrenA = children;
502
0
  children = NULL;
503
0
  for (i = 0; i < childrenA->getLength(); ++i) {
504
0
    ((OCDisplayNode *)childrenA->get(i))->parent = NULL;
505
0
  }
506
0
  return childrenA;
507
0
}
508
509
240k
OCDisplayNode::~OCDisplayNode() {
510
240k
  delete name;
511
240k
  if (children) {
512
147k
    deleteGList(children, OCDisplayNode);
513
147k
  }
514
240k
}
515
516
0
Unicode *OCDisplayNode::getName() {
517
0
  return name->getUnicode();
518
0
}
519
520
0
int OCDisplayNode::getNameLength() {
521
0
  return name->getLength();
522
0
}
523
524
0
int OCDisplayNode::getNumChildren() {
525
0
  if (!children) {
526
0
    return 0;
527
0
  }
528
0
  return children->getLength();
529
0
}
530
531
0
OCDisplayNode *OCDisplayNode::getChild(int idx) {
532
0
  return (OCDisplayNode *)children->get(idx);
533
0
}