Coverage Report

Created: 2025-08-03 06:15

/src/qpdf/libqpdf/QPDFObjectHandle.cc
Line
Count
Source (jump to first uncovered line)
1
#include <qpdf/assert_debug.h>
2
3
#include <qpdf/QPDFObjectHandle_private.hh>
4
5
#include <qpdf/JSON_writer.hh>
6
#include <qpdf/Pipeline_private.hh>
7
#include <qpdf/Pl_Buffer.hh>
8
#include <qpdf/Pl_QPDFTokenizer.hh>
9
#include <qpdf/QIntC.hh>
10
#include <qpdf/QPDF.hh>
11
#include <qpdf/QPDFExc.hh>
12
#include <qpdf/QPDFLogger.hh>
13
#include <qpdf/QPDFMatrix.hh>
14
#include <qpdf/QPDFObject_private.hh>
15
#include <qpdf/QPDFPageObjectHelper.hh>
16
#include <qpdf/QPDFParser.hh>
17
#include <qpdf/QTC.hh>
18
#include <qpdf/Util.hh>
19
20
#include <array>
21
#include <cctype>
22
#include <climits>
23
#include <cstdlib>
24
#include <cstring>
25
#include <stdexcept>
26
27
using namespace std::literals;
28
using namespace qpdf;
29
30
BaseHandle::
31
operator QPDFObjGen() const
32
0
{
33
0
    return obj ? obj->getObjGen() : QPDFObjGen();
34
0
}
35
36
namespace
37
{
38
    class TerminateParsing
39
    {
40
    };
41
} // namespace
42
43
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
44
7.83k
    supports_retry(supports_retry)
45
7.83k
{
46
7.83k
}
47
48
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
49
7.83k
{
50
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
51
7.83k
}
52
53
void
54
QPDFObjectHandle::StreamDataProvider::provideStreamData(QPDFObjGen const& og, Pipeline* pipeline)
55
0
{
56
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline);
57
0
}
58
59
bool
60
QPDFObjectHandle::StreamDataProvider::provideStreamData(
61
    QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
62
0
{
63
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline, suppress_warnings, will_retry);
64
0
}
65
66
void
67
QPDFObjectHandle::StreamDataProvider::provideStreamData(
68
    int objid, int generation, Pipeline* pipeline)
69
0
{
70
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
71
0
}
72
73
bool
74
QPDFObjectHandle::StreamDataProvider::provideStreamData(
75
    int objid, int generation, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
76
0
{
77
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
78
0
    return false;
79
0
}
80
81
bool
82
QPDFObjectHandle::StreamDataProvider::supportsRetry()
83
0
{
84
0
    return supports_retry;
85
0
}
86
87
namespace
88
{
89
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
90
    {
91
      public:
92
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
93
0
            containing_page(containing_page),
94
0
            old_contents(old_contents)
95
0
        {
96
0
        }
97
0
        ~CoalesceProvider() override = default;
98
        void provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override;
99
100
      private:
101
        QPDFObjectHandle containing_page;
102
        QPDFObjectHandle old_contents;
103
    };
104
} // namespace
105
106
void
107
CoalesceProvider::provideStreamData(QPDFObjGen const&, Pipeline* p)
108
0
{
109
0
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
110
0
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
111
0
    std::string all_description;
112
0
    old_contents.pipeContentStreams(p, description, all_description);
113
0
}
114
115
void
116
QPDFObjectHandle::TokenFilter::handleEOF()
117
0
{
118
0
}
119
120
void
121
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
122
0
{
123
0
    pipeline = p;
124
0
}
125
126
void
127
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
128
0
{
129
0
    if (!pipeline) {
130
0
        return;
131
0
    }
132
0
    if (len) {
133
0
        pipeline->write(data, len);
134
0
    }
135
0
}
136
137
void
138
QPDFObjectHandle::TokenFilter::write(std::string const& str)
139
0
{
140
0
    write(str.c_str(), str.length());
141
0
}
142
143
void
144
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
145
0
{
146
0
    std::string const& value = token.getRawValue();
147
0
    write(value.c_str(), value.length());
148
0
}
149
150
void
151
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle)
152
0
{
153
0
    throw std::logic_error("You must override one of the handleObject methods in ParserCallbacks");
154
0
}
155
156
void
157
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle oh, size_t, size_t)
158
0
{
159
    // This version of handleObject was added in qpdf 9. If the developer did not override it, fall
160
    // back to the older interface.
161
0
    handleObject(oh);
162
0
}
163
164
void
165
QPDFObjectHandle::ParserCallbacks::contentSize(size_t)
166
0
{
167
    // Ignore by default; overriding this is optional.
168
0
}
169
170
void
171
QPDFObjectHandle::ParserCallbacks::terminateParsing()
172
0
{
173
0
    throw TerminateParsing();
174
0
}
175
176
std::pair<bool, bool>
177
Name::analyzeJSONEncoding(const std::string& name)
178
0
{
179
0
    int tail = 0;       // Number of continuation characters expected.
180
0
    bool tail2 = false; // Potential overlong 3 octet utf-8.
181
0
    bool tail3 = false; // potential overlong 4 octet
182
0
    bool needs_escaping = false;
183
0
    for (auto const& it: name) {
184
0
        auto c = static_cast<unsigned char>(it);
185
0
        if (tail) {
186
0
            if ((c & 0xc0) != 0x80) {
187
0
                return {false, false};
188
0
            }
189
0
            if (tail2) {
190
0
                if ((c & 0xe0) == 0x80) {
191
0
                    return {false, false};
192
0
                }
193
0
                tail2 = false;
194
0
            } else if (tail3) {
195
0
                if ((c & 0xf0) == 0x80) {
196
0
                    return {false, false};
197
0
                }
198
0
                tail3 = false;
199
0
            }
200
0
            tail--;
201
0
        } else if (c < 0x80) {
202
0
            if (!needs_escaping) {
203
0
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
204
0
            }
205
0
        } else if ((c & 0xe0) == 0xc0) {
206
0
            if ((c & 0xfe) == 0xc0) {
207
0
                return {false, false};
208
0
            }
209
0
            tail = 1;
210
0
        } else if ((c & 0xf0) == 0xe0) {
211
0
            tail2 = (c == 0xe0);
212
0
            tail = 2;
213
0
        } else if ((c & 0xf8) == 0xf0) {
214
0
            tail3 = (c == 0xf0);
215
0
            tail = 3;
216
0
        } else {
217
0
            return {false, false};
218
0
        }
219
0
    }
220
0
    return {tail == 0, !needs_escaping};
221
0
}
222
223
std::string
224
Name::normalize(std::string const& name)
225
0
{
226
0
    if (name.empty()) {
227
0
        return name;
228
0
    }
229
0
    std::string result;
230
0
    result += name.at(0);
231
0
    for (size_t i = 1; i < name.length(); ++i) {
232
0
        char ch = name.at(i);
233
        // Don't use locale/ctype here; follow PDF spec guidelines.
234
0
        if (ch == '\0') {
235
            // QPDFTokenizer embeds a null character to encode an invalid #.
236
0
            result += "#";
237
0
        } else if (
238
0
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
239
0
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
240
0
            result += util::hex_encode_char(ch);
241
0
        } else {
242
0
            result += ch;
243
0
        }
244
0
    }
245
0
    return result;
246
0
}
247
248
std::shared_ptr<QPDFObject>
249
BaseHandle::copy(bool shallow) const
250
0
{
251
0
    switch (resolved_type_code()) {
252
0
    case ::ot_uninitialized:
253
0
        throw std::logic_error("QPDFObjectHandle: attempting to copy an uninitialized object");
254
0
        return {}; // does not return
255
0
    case ::ot_reserved:
256
0
        return QPDFObject::create<QPDF_Reserved>();
257
0
    case ::ot_null:
258
0
        return QPDFObject::create<QPDF_Null>();
259
0
    case ::ot_boolean:
260
0
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
261
0
    case ::ot_integer:
262
0
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
263
0
    case ::ot_real:
264
0
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
265
0
    case ::ot_string:
266
0
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
267
0
    case ::ot_name:
268
0
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
269
0
    case ::ot_array:
270
0
        {
271
0
            auto const& a = std::get<QPDF_Array>(obj->value);
272
0
            if (shallow) {
273
0
                return QPDFObject::create<QPDF_Array>(a);
274
0
            } else {
275
0
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
276
0
                if (a.sp) {
277
0
                    QPDF_Array result;
278
0
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
279
0
                    result.sp->size = a.sp->size;
280
0
                    for (auto const& [idx, oh]: a.sp->elements) {
281
0
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
282
0
                    }
283
0
                    return QPDFObject::create<QPDF_Array>(std::move(result));
284
0
                } else {
285
0
                    std::vector<QPDFObjectHandle> result;
286
0
                    result.reserve(a.elements.size());
287
0
                    for (auto const& element: a.elements) {
288
0
                        result.emplace_back(
289
0
                            element ? (element.indirect() ? element : element.copy()) : element);
290
0
                    }
291
0
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
292
0
                }
293
0
            }
294
0
        }
295
0
    case ::ot_dictionary:
296
0
        {
297
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
298
0
            if (shallow) {
299
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
300
0
            } else {
301
0
                std::map<std::string, QPDFObjectHandle> new_items;
302
0
                for (auto const& [key, val]: d.items) {
303
0
                    new_items[key] = val.indirect() ? val : val.copy();
304
0
                }
305
0
                return QPDFObject::create<QPDF_Dictionary>(new_items);
306
0
            }
307
0
        }
308
0
    case ::ot_stream:
309
0
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
310
0
        throw std::runtime_error("stream objects cannot be cloned");
311
0
        return {}; // does not return
312
0
    case ::ot_operator:
313
0
        return QPDFObject::create<QPDF_Operator>(std::get<QPDF_Operator>(obj->value).val);
314
0
    case ::ot_inlineimage:
315
0
        return QPDFObject::create<QPDF_InlineImage>(std::get<QPDF_InlineImage>(obj->value).val);
316
0
    case ::ot_unresolved:
317
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
318
0
        return {}; // does not return
319
0
    case ::ot_destroyed:
320
0
        throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF");
321
0
        return {}; // does not return
322
0
    case ::ot_reference:
323
0
        return obj->qpdf->getObject(obj->og).getObj();
324
0
    }
325
0
    return {}; // unreachable
326
0
}
327
328
std::string
329
BaseHandle::unparse() const
330
0
{
331
0
    switch (resolved_type_code()) {
332
0
    case ::ot_uninitialized:
333
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object");
334
0
        return ""; // does not return
335
0
    case ::ot_reserved:
336
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
337
0
        return ""; // does not return
338
0
    case ::ot_null:
339
0
        return "null";
340
0
    case ::ot_boolean:
341
0
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
342
0
    case ::ot_integer:
343
0
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
344
0
    case ::ot_real:
345
0
        return std::get<QPDF_Real>(obj->value).val;
346
0
    case ::ot_string:
347
0
        return std::get<QPDF_String>(obj->value).unparse(false);
348
0
    case ::ot_name:
349
0
        return Name::normalize(std::get<QPDF_Name>(obj->value).name);
350
0
    case ::ot_array:
351
0
        {
352
0
            auto const& a = std::get<QPDF_Array>(obj->value);
353
0
            std::string result = "[ ";
354
0
            if (a.sp) {
355
0
                int next = 0;
356
0
                for (auto& item: a.sp->elements) {
357
0
                    int key = item.first;
358
0
                    for (int j = next; j < key; ++j) {
359
0
                        result += "null ";
360
0
                    }
361
0
                    result += item.second.unparse() + " ";
362
0
                    next = ++key;
363
0
                }
364
0
                for (int j = next; j < a.sp->size; ++j) {
365
0
                    result += "null ";
366
0
                }
367
0
            } else {
368
0
                for (auto const& item: a.elements) {
369
0
                    result += item.unparse() + " ";
370
0
                }
371
0
            }
372
0
            result += "]";
373
0
            return result;
374
0
        }
375
0
    case ::ot_dictionary:
376
0
        {
377
0
            auto const& items = std::get<QPDF_Dictionary>(obj->value).items;
378
0
            std::string result = "<< ";
379
0
            for (auto& iter: items) {
380
0
                if (!iter.second.null()) {
381
0
                    result += Name::normalize(iter.first) + " " + iter.second.unparse() + " ";
382
0
                }
383
0
            }
384
0
            result += ">>";
385
0
            return result;
386
0
        }
387
0
    case ::ot_stream:
388
0
        return obj->og.unparse(' ') + " R";
389
0
    case ::ot_operator:
390
0
        return std::get<QPDF_Operator>(obj->value).val;
391
0
    case ::ot_inlineimage:
392
0
        return std::get<QPDF_InlineImage>(obj->value).val;
393
0
    case ::ot_unresolved:
394
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object");
395
0
        return ""; // does not return
396
0
    case ::ot_destroyed:
397
0
        throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF");
398
0
        return ""; // does not return
399
0
    case ::ot_reference:
400
0
        return obj->og.unparse(' ') + " R";
401
0
    }
402
0
    return {}; // unreachable
403
0
}
404
405
void
406
BaseHandle::write_json(int json_version, JSON::Writer& p) const
407
0
{
408
0
    switch (resolved_type_code()) {
409
0
    case ::ot_uninitialized:
410
0
        throw std::logic_error(
411
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
412
0
        break; // unreachable
413
0
    case ::ot_null:
414
0
    case ::ot_operator:
415
0
    case ::ot_inlineimage:
416
0
        p << "null";
417
0
        break;
418
0
    case ::ot_boolean:
419
0
        p << std::get<QPDF_Bool>(obj->value).val;
420
0
        break;
421
0
    case ::ot_integer:
422
0
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
423
0
        break;
424
0
    case ::ot_real:
425
0
        {
426
0
            auto const& val = std::get<QPDF_Real>(obj->value).val;
427
0
            if (val.empty()) {
428
                // Can't really happen...
429
0
                p << "0";
430
0
            } else if (val.at(0) == '.') {
431
0
                p << "0" << val;
432
0
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
433
0
                p << "-0." << val.substr(2);
434
0
            } else {
435
0
                p << val;
436
0
            }
437
0
            if (val.back() == '.') {
438
0
                p << "0";
439
0
            }
440
0
        }
441
0
        break;
442
0
    case ::ot_string:
443
0
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
444
0
        break;
445
0
    case ::ot_name:
446
0
        {
447
0
            auto const& n = std::get<QPDF_Name>(obj->value);
448
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
449
            // updating this method make sure QPDF_Dictionary is also update.
450
0
            if (json_version == 1) {
451
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
452
0
            } else {
453
0
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
454
0
                    if (res.second) {
455
0
                        p << "\"" << n.name << "\"";
456
0
                    } else {
457
0
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
458
0
                    }
459
0
                } else {
460
0
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
461
0
                }
462
0
            }
463
0
        }
464
0
        break;
465
0
    case ::ot_array:
466
0
        {
467
0
            auto const& a = std::get<QPDF_Array>(obj->value);
468
0
            p.writeStart('[');
469
0
            if (a.sp) {
470
0
                int next = 0;
471
0
                for (auto& item: a.sp->elements) {
472
0
                    int key = item.first;
473
0
                    for (int j = next; j < key; ++j) {
474
0
                        p.writeNext() << "null";
475
0
                    }
476
0
                    p.writeNext();
477
0
                    auto item_og = item.second.getObj()->getObjGen();
478
0
                    if (item_og.isIndirect()) {
479
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
480
0
                    } else {
481
0
                        item.second.write_json(json_version, p);
482
0
                    }
483
0
                    next = ++key;
484
0
                }
485
0
                for (int j = next; j < a.sp->size; ++j) {
486
0
                    p.writeNext() << "null";
487
0
                }
488
0
            } else {
489
0
                for (auto const& item: a.elements) {
490
0
                    p.writeNext();
491
0
                    auto item_og = item.getObj()->getObjGen();
492
0
                    if (item_og.isIndirect()) {
493
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
494
0
                    } else {
495
0
                        item.write_json(json_version, p);
496
0
                    }
497
0
                }
498
0
            }
499
0
            p.writeEnd(']');
500
0
        }
501
0
        break;
502
0
    case ::ot_dictionary:
503
0
        {
504
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
505
0
            p.writeStart('{');
506
0
            for (auto& iter: d.items) {
507
0
                if (!iter.second.null()) {
508
0
                    p.writeNext();
509
0
                    if (json_version == 1) {
510
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
511
0
                          << "\": ";
512
0
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
513
0
                        if (res.second) {
514
0
                            p << "\"" << iter.first << "\": ";
515
0
                        } else {
516
0
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
517
0
                        }
518
0
                    } else {
519
0
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
520
0
                          << "\": ";
521
0
                    }
522
0
                    iter.second.writeJSON(json_version, p);
523
0
                }
524
0
            }
525
0
            p.writeEnd('}');
526
0
        }
527
0
        break;
528
0
    case ::ot_stream:
529
0
        std::get<QPDF_Stream>(obj->value).m->stream_dict.writeJSON(json_version, p);
530
0
        break;
531
0
    case ::ot_reference:
532
0
        p << "\"" << obj->og.unparse(' ') << " R\"";
533
0
        break;
534
0
    default:
535
0
        throw std::logic_error("attempted to write an unsuitable object as JSON");
536
0
    }
537
0
}
538
539
void
540
BaseHandle::disconnect(bool only_direct)
541
2.10M
{
542
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
543
2.10M
    if (only_direct && indirect()) {
544
614k
        return;
545
614k
    }
546
547
1.48M
    switch (raw_type_code()) {
548
9.22k
    case ::ot_array:
549
9.22k
        {
550
9.22k
            auto& a = std::get<QPDF_Array>(obj->value);
551
9.22k
            if (a.sp) {
552
0
                for (auto& item: a.sp->elements) {
553
0
                    item.second.disconnect();
554
0
                }
555
9.22k
            } else {
556
2.03M
                for (auto& oh: a.elements) {
557
2.03M
                    oh.disconnect();
558
2.03M
                }
559
9.22k
            }
560
9.22k
        }
561
9.22k
        break;
562
8.19k
    case ::ot_dictionary:
563
48.7k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
564
48.7k
            iter.second.disconnect();
565
48.7k
        }
566
8.19k
        break;
567
2.88k
    case ::ot_stream:
568
2.88k
        {
569
2.88k
            auto& s = std::get<QPDF_Stream>(obj->value);
570
2.88k
            s.m->stream_provider = nullptr;
571
2.88k
            s.m->stream_dict.disconnect();
572
2.88k
        }
573
2.88k
        break;
574
0
    case ::ot_uninitialized:
575
0
        return;
576
1.46M
    default:
577
1.46M
        break;
578
1.48M
    }
579
1.48M
    obj->qpdf = nullptr;
580
1.48M
    obj->og = QPDFObjGen();
581
1.48M
}
582
583
std::string
584
QPDFObject::getStringValue() const
585
3.85k
{
586
3.85k
    switch (getResolvedTypeCode()) {
587
0
    case ::ot_real:
588
0
        return std::get<QPDF_Real>(value).val;
589
0
    case ::ot_string:
590
0
        return std::get<QPDF_String>(value).val;
591
3.85k
    case ::ot_name:
592
3.85k
        return std::get<QPDF_Name>(value).name;
593
0
    case ::ot_operator:
594
0
        return std::get<QPDF_Operator>(value).val;
595
0
    case ::ot_inlineimage:
596
0
        return std::get<QPDF_InlineImage>(value).val;
597
0
    case ::ot_reference:
598
0
        return std::get<QPDF_Reference>(value).obj->getStringValue();
599
0
    default:
600
0
        throw std::logic_error("Internal error in QPDFObject::getStringValue");
601
3.85k
    }
602
0
    return ""; // unreachable
603
3.85k
}
604
605
bool
606
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
607
0
{
608
0
    return obj == rhs.obj;
609
0
}
610
611
qpdf_object_type_e
612
QPDFObjectHandle::getTypeCode() const
613
0
{
614
0
    return obj ? obj->getResolvedTypeCode() : ::ot_uninitialized;
615
0
}
616
617
char const*
618
QPDFObjectHandle::getTypeName() const
619
0
{
620
0
    static constexpr std::array<char const*, 16> tn{
621
0
        "uninitialized",
622
0
        "reserved",
623
0
        "null",
624
0
        "boolean",
625
0
        "integer",
626
0
        "real",
627
0
        "string",
628
0
        "name",
629
0
        "array",
630
0
        "dictionary",
631
0
        "stream",
632
0
        "operator",
633
0
        "inline-image",
634
0
        "unresolved",
635
0
        "destroyed",
636
0
        "reference"};
637
0
    return obj ? tn[getTypeCode()] : "uninitialized";
638
0
}
639
640
bool
641
QPDFObjectHandle::isDestroyed() const
642
0
{
643
0
    return type_code() == ::ot_destroyed;
644
0
}
645
646
bool
647
QPDFObjectHandle::isBool() const
648
0
{
649
0
    return type_code() == ::ot_boolean;
650
0
}
651
652
bool
653
QPDFObjectHandle::isDirectNull() const
654
0
{
655
    // Don't call dereference() -- this is a const method, and we know
656
    // objid == 0, so there's nothing to resolve.
657
0
    return !indirect() && raw_type_code() == ::ot_null;
658
0
}
659
660
bool
661
QPDFObjectHandle::isNull() const
662
134k
{
663
134k
    return type_code() == ::ot_null;
664
134k
}
665
666
bool
667
QPDFObjectHandle::isInteger() const
668
7.61k
{
669
7.61k
    return type_code() == ::ot_integer;
670
7.61k
}
671
672
bool
673
QPDFObjectHandle::isReal() const
674
0
{
675
0
    return type_code() == ::ot_real;
676
0
}
677
678
bool
679
QPDFObjectHandle::isNumber() const
680
0
{
681
0
    return (isInteger() || isReal());
682
0
}
683
684
double
685
QPDFObjectHandle::getNumericValue() const
686
0
{
687
0
    if (isInteger()) {
688
0
        return static_cast<double>(getIntValue());
689
0
    } else if (isReal()) {
690
0
        return atof(getRealValue().c_str());
691
0
    } else {
692
0
        typeWarning("number", "returning 0");
693
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
694
0
        return 0;
695
0
    }
696
0
}
697
698
bool
699
QPDFObjectHandle::getValueAsNumber(double& value) const
700
0
{
701
0
    if (!isNumber()) {
702
0
        return false;
703
0
    }
704
0
    value = getNumericValue();
705
0
    return true;
706
0
}
707
708
bool
709
QPDFObjectHandle::isName() const
710
3.85k
{
711
3.85k
    return type_code() == ::ot_name;
712
3.85k
}
713
714
bool
715
QPDFObjectHandle::isString() const
716
0
{
717
0
    return type_code() == ::ot_string;
718
0
}
719
720
bool
721
QPDFObjectHandle::isOperator() const
722
0
{
723
0
    return type_code() == ::ot_operator;
724
0
}
725
726
bool
727
QPDFObjectHandle::isInlineImage() const
728
0
{
729
0
    return type_code() == ::ot_inlineimage;
730
0
}
731
732
bool
733
QPDFObjectHandle::isArray() const
734
0
{
735
0
    return type_code() == ::ot_array;
736
0
}
737
738
bool
739
QPDFObjectHandle::isDictionary() const
740
153k
{
741
153k
    return type_code() == ::ot_dictionary;
742
153k
}
743
744
bool
745
QPDFObjectHandle::isStream() const
746
171k
{
747
171k
    return type_code() == ::ot_stream;
748
171k
}
749
750
bool
751
QPDFObjectHandle::isReserved() const
752
0
{
753
0
    return type_code() == ::ot_reserved;
754
0
}
755
756
bool
757
QPDFObjectHandle::isScalar() const
758
0
{
759
0
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
760
0
}
761
762
bool
763
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
764
0
{
765
0
    return isName() && (getName() == name);
766
0
}
767
768
bool
769
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
770
0
{
771
0
    return isDictionary() && (type.empty() || getKey("/Type").isNameAndEquals(type)) &&
772
0
        (subtype.empty() || getKey("/Subtype").isNameAndEquals(subtype));
773
0
}
774
775
bool
776
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
777
0
{
778
0
    return isStream() && getDict().isDictionaryOfType(type, subtype);
779
0
}
780
781
// Bool accessors
782
783
bool
784
QPDFObjectHandle::getBoolValue() const
785
0
{
786
0
    if (auto boolean = as<QPDF_Bool>()) {
787
0
        return boolean->val;
788
0
    } else {
789
0
        typeWarning("boolean", "returning false");
790
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
791
0
        return false;
792
0
    }
793
0
}
794
795
bool
796
QPDFObjectHandle::getValueAsBool(bool& value) const
797
0
{
798
0
    if (auto boolean = as<QPDF_Bool>()) {
799
0
        value = boolean->val;
800
0
        return true;
801
0
    }
802
0
    return false;
803
0
}
804
805
// Integer accessors
806
807
long long
808
QPDFObjectHandle::getIntValue() const
809
7.61k
{
810
7.61k
    if (auto integer = as<QPDF_Integer>()) {
811
7.61k
        return integer->val;
812
7.61k
    } else {
813
0
        typeWarning("integer", "returning 0");
814
0
        QTC::TC("qpdf", "QPDFObjectHandle integer returning 0");
815
0
        return 0;
816
0
    }
817
7.61k
}
818
819
bool
820
QPDFObjectHandle::getValueAsInt(long long& value) const
821
0
{
822
0
    if (auto integer = as<QPDF_Integer>()) {
823
0
        value = integer->val;
824
0
        return true;
825
0
    }
826
0
    return false;
827
0
}
828
829
int
830
QPDFObjectHandle::getIntValueAsInt() const
831
7.61k
{
832
7.61k
    int result = 0;
833
7.61k
    long long v = getIntValue();
834
7.61k
    if (v < INT_MIN) {
835
0
        QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MIN");
836
0
        warnIfPossible("requested value of integer is too small; returning INT_MIN");
837
0
        result = INT_MIN;
838
7.61k
    } else if (v > INT_MAX) {
839
0
        QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MAX");
840
0
        warnIfPossible("requested value of integer is too big; returning INT_MAX");
841
0
        result = INT_MAX;
842
7.61k
    } else {
843
7.61k
        result = static_cast<int>(v);
844
7.61k
    }
845
7.61k
    return result;
846
7.61k
}
847
848
bool
849
QPDFObjectHandle::getValueAsInt(int& value) const
850
0
{
851
0
    if (!isInteger()) {
852
0
        return false;
853
0
    }
854
0
    value = getIntValueAsInt();
855
0
    return true;
856
0
}
857
858
unsigned long long
859
QPDFObjectHandle::getUIntValue() const
860
0
{
861
0
    long long v = getIntValue();
862
0
    if (v < 0) {
863
0
        QTC::TC("qpdf", "QPDFObjectHandle uint returning 0");
864
0
        warnIfPossible("unsigned value request for negative number; returning 0");
865
0
        return 0;
866
0
    } else {
867
0
        return static_cast<unsigned long long>(v);
868
0
    }
869
0
}
870
871
bool
872
QPDFObjectHandle::getValueAsUInt(unsigned long long& value) const
873
0
{
874
0
    if (!isInteger()) {
875
0
        return false;
876
0
    }
877
0
    value = getUIntValue();
878
0
    return true;
879
0
}
880
881
unsigned int
882
QPDFObjectHandle::getUIntValueAsUInt() const
883
0
{
884
0
    long long v = getIntValue();
885
0
    if (v < 0) {
886
0
        QTC::TC("qpdf", "QPDFObjectHandle uint uint returning 0");
887
0
        warnIfPossible("unsigned integer value request for negative number; returning 0");
888
0
        return 0;
889
0
    } else if (v > UINT_MAX) {
890
0
        QTC::TC("qpdf", "QPDFObjectHandle uint returning UINT_MAX");
891
0
        warnIfPossible("requested value of unsigned integer is too big; returning UINT_MAX");
892
0
        return UINT_MAX;
893
0
    } else {
894
0
        return static_cast<unsigned int>(v);
895
0
    }
896
0
}
897
898
bool
899
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
900
0
{
901
0
    if (!isInteger()) {
902
0
        return false;
903
0
    }
904
0
    value = getUIntValueAsUInt();
905
0
    return true;
906
0
}
907
908
// Real accessors
909
910
std::string
911
QPDFObjectHandle::getRealValue() const
912
0
{
913
0
    if (isReal()) {
914
0
        return obj->getStringValue();
915
0
    } else {
916
0
        typeWarning("real", "returning 0.0");
917
0
        QTC::TC("qpdf", "QPDFObjectHandle real returning 0.0");
918
0
        return "0.0";
919
0
    }
920
0
}
921
922
bool
923
QPDFObjectHandle::getValueAsReal(std::string& value) const
924
0
{
925
0
    if (!isReal()) {
926
0
        return false;
927
0
    }
928
0
    value = obj->getStringValue();
929
0
    return true;
930
0
}
931
932
// Name accessors
933
934
std::string
935
QPDFObjectHandle::getName() const
936
3.85k
{
937
3.85k
    if (isName()) {
938
3.85k
        return obj->getStringValue();
939
3.85k
    } else {
940
0
        typeWarning("name", "returning dummy name");
941
0
        QTC::TC("qpdf", "QPDFObjectHandle name returning dummy name");
942
0
        return "/QPDFFakeName";
943
0
    }
944
3.85k
}
945
946
bool
947
QPDFObjectHandle::getValueAsName(std::string& value) const
948
0
{
949
0
    if (!isName()) {
950
0
        return false;
951
0
    }
952
0
    value = obj->getStringValue();
953
0
    return true;
954
0
}
955
956
// String accessors
957
958
std::string
959
QPDFObjectHandle::getStringValue() const
960
0
{
961
0
    if (isString()) {
962
0
        return obj->getStringValue();
963
0
    } else {
964
0
        typeWarning("string", "returning empty string");
965
0
        QTC::TC("qpdf", "QPDFObjectHandle string returning empty string");
966
0
        return "";
967
0
    }
968
0
}
969
970
bool
971
QPDFObjectHandle::getValueAsString(std::string& value) const
972
0
{
973
0
    if (!isString()) {
974
0
        return false;
975
0
    }
976
0
    value = obj->getStringValue();
977
0
    return true;
978
0
}
979
980
std::string
981
QPDFObjectHandle::getUTF8Value() const
982
0
{
983
0
    if (auto str = as<QPDF_String>()) {
984
0
        return str->getUTF8Val();
985
0
    } else {
986
0
        typeWarning("string", "returning empty string");
987
0
        QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8");
988
0
        return "";
989
0
    }
990
0
}
991
992
bool
993
QPDFObjectHandle::getValueAsUTF8(std::string& value) const
994
0
{
995
0
    if (auto str = as<QPDF_String>()) {
996
0
        value = str->getUTF8Val();
997
0
        return true;
998
0
    }
999
0
    return false;
1000
0
}
1001
1002
// Operator and Inline Image accessors
1003
1004
std::string
1005
QPDFObjectHandle::getOperatorValue() const
1006
0
{
1007
0
    if (isOperator()) {
1008
0
        return obj->getStringValue();
1009
0
    } else {
1010
0
        typeWarning("operator", "returning fake value");
1011
0
        QTC::TC("qpdf", "QPDFObjectHandle operator returning fake value");
1012
0
        return "QPDFFAKE";
1013
0
    }
1014
0
}
1015
1016
bool
1017
QPDFObjectHandle::getValueAsOperator(std::string& value) const
1018
0
{
1019
0
    if (!isOperator()) {
1020
0
        return false;
1021
0
    }
1022
0
    value = obj->getStringValue();
1023
0
    return true;
1024
0
}
1025
1026
std::string
1027
QPDFObjectHandle::getInlineImageValue() const
1028
0
{
1029
0
    if (isInlineImage()) {
1030
0
        return obj->getStringValue();
1031
0
    } else {
1032
0
        typeWarning("inlineimage", "returning empty data");
1033
0
        QTC::TC("qpdf", "QPDFObjectHandle inlineimage returning empty data");
1034
0
        return "";
1035
0
    }
1036
0
}
1037
1038
bool
1039
QPDFObjectHandle::getValueAsInlineImage(std::string& value) const
1040
0
{
1041
0
    if (!isInlineImage()) {
1042
0
        return false;
1043
0
    }
1044
0
    value = obj->getStringValue();
1045
0
    return true;
1046
0
}
1047
1048
// Array accessors and mutators are in QPDF_Array.cc
1049
1050
QPDFObjectHandle::QPDFArrayItems
1051
QPDFObjectHandle::aitems()
1052
0
{
1053
0
    return *this;
1054
0
}
1055
1056
// Dictionary accessors are in QPDF_Dictionary.cc
1057
1058
QPDFObjectHandle::QPDFDictItems
1059
QPDFObjectHandle::ditems()
1060
0
{
1061
0
    return {*this};
1062
0
}
1063
1064
// Array and Name accessors
1065
1066
bool
1067
QPDFObjectHandle::isOrHasName(std::string const& value) const
1068
0
{
1069
0
    if (isNameAndEquals(value)) {
1070
0
        return true;
1071
0
    } else if (isArray()) {
1072
0
        for (auto& item: getArrayAsVector()) {
1073
0
            if (item.isNameAndEquals(value)) {
1074
0
                return true;
1075
0
            }
1076
0
        }
1077
0
    }
1078
0
    return false;
1079
0
}
1080
1081
void
1082
QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf)
1083
0
{
1084
0
    for (auto const& i1: as_dictionary()) {
1085
0
        for (auto& i2: i1.second.as_dictionary()) {
1086
0
            if (!i2.second.null() && !i2.second.isIndirect()) {
1087
0
                i2.second = owning_qpdf.makeIndirectObject(i2.second);
1088
0
            }
1089
0
        }
1090
0
    }
1091
0
}
1092
1093
void
1094
QPDFObjectHandle::mergeResources(
1095
    QPDFObjectHandle other, std::map<std::string, std::map<std::string, std::string>>* conflicts)
1096
0
{
1097
0
    if (!(isDictionary() && other.isDictionary())) {
1098
0
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1099
0
        return;
1100
0
    }
1101
1102
0
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1103
0
                              std::map<QPDFObjGen, std::string>& og_to_name) {
1104
0
        for (auto const& [key, value]: dict.as_dictionary()) {
1105
0
            if (!value.null() && value.isIndirect()) {
1106
0
                og_to_name.insert_or_assign(value.getObjGen(), key);
1107
0
            }
1108
0
        }
1109
0
    };
1110
1111
    // This algorithm is described in comments in QPDFObjectHandle.hh
1112
    // above the declaration of mergeResources.
1113
0
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1114
0
        auto other_val = value1;
1115
0
        if (hasKey(rtype)) {
1116
0
            QPDFObjectHandle this_val = getKey(rtype);
1117
0
            if (this_val.isDictionary() && other_val.isDictionary()) {
1118
0
                if (this_val.isIndirect()) {
1119
                    // Do this even if there are no keys. Various places in the code call
1120
                    // mergeResources with resource dictionaries that contain empty subdictionaries
1121
                    // just to get this shallow copy functionality.
1122
0
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1123
0
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1124
0
                }
1125
0
                std::map<QPDFObjGen, std::string> og_to_name;
1126
0
                std::set<std::string> rnames;
1127
0
                int min_suffix = 1;
1128
0
                bool initialized_maps = false;
1129
0
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1130
0
                    QPDFObjectHandle rval = value2;
1131
0
                    if (!this_val.hasKey(key)) {
1132
0
                        if (!rval.isIndirect()) {
1133
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1134
0
                            rval = rval.shallowCopy();
1135
0
                        }
1136
0
                        this_val.replaceKey(key, rval);
1137
0
                    } else if (conflicts) {
1138
0
                        if (!initialized_maps) {
1139
0
                            make_og_to_name(this_val, og_to_name);
1140
0
                            rnames = this_val.getResourceNames();
1141
0
                            initialized_maps = true;
1142
0
                        }
1143
0
                        auto rval_og = rval.getObjGen();
1144
0
                        if (rval.isIndirect() && og_to_name.contains(rval_og)) {
1145
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge reuse");
1146
0
                            auto new_key = og_to_name[rval_og];
1147
0
                            if (new_key != key) {
1148
0
                                (*conflicts)[rtype][key] = new_key;
1149
0
                            }
1150
0
                        } else {
1151
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge generate");
1152
0
                            std::string new_key =
1153
0
                                getUniqueResourceName(key + "_", min_suffix, &rnames);
1154
0
                            (*conflicts)[rtype][key] = new_key;
1155
0
                            this_val.replaceKey(new_key, rval);
1156
0
                        }
1157
0
                    }
1158
0
                }
1159
0
            } else if (this_val.isArray() && other_val.isArray()) {
1160
0
                std::set<std::string> scalars;
1161
0
                for (auto this_item: this_val.aitems()) {
1162
0
                    if (this_item.isScalar()) {
1163
0
                        scalars.insert(this_item.unparse());
1164
0
                    }
1165
0
                }
1166
0
                for (auto other_item: other_val.aitems()) {
1167
0
                    if (other_item.isScalar()) {
1168
0
                        if (!scalars.contains(other_item.unparse())) {
1169
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1170
0
                            this_val.appendItem(other_item);
1171
0
                        } else {
1172
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1173
0
                        }
1174
0
                    }
1175
0
                }
1176
0
            }
1177
0
        } else {
1178
0
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1179
0
            replaceKey(rtype, other_val.shallowCopy());
1180
0
        }
1181
0
    }
1182
0
}
1183
1184
std::set<std::string>
1185
QPDFObjectHandle::getResourceNames() const
1186
0
{
1187
    // Return second-level dictionary keys
1188
0
    std::set<std::string> result;
1189
0
    for (auto const& item: as_dictionary(strict)) {
1190
0
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1191
0
            if (!val2.null()) {
1192
0
                result.insert(key2);
1193
0
            }
1194
0
        }
1195
0
    }
1196
0
    return result;
1197
0
}
1198
1199
std::string
1200
QPDFObjectHandle::getUniqueResourceName(
1201
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1202
0
{
1203
0
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1204
0
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1205
0
    while (min_suffix <= max_suffix) {
1206
0
        std::string candidate = prefix + std::to_string(min_suffix);
1207
0
        if (!names.contains(candidate)) {
1208
0
            return candidate;
1209
0
        }
1210
        // Increment after return; min_suffix should be the value
1211
        // used, not the next value.
1212
0
        ++min_suffix;
1213
0
    }
1214
    // This could only happen if there is a coding error.
1215
    // The number of candidates we test is more than the
1216
    // number of keys we're checking against.
1217
0
    throw std::logic_error(
1218
0
        "unable to find unconflicting name in QPDFObjectHandle::getUniqueResourceName");
1219
0
}
1220
1221
// Dictionary mutators are in QPDF_Dictionary.cc
1222
1223
// Stream accessors are in QPDF_Stream.cc
1224
1225
std::map<std::string, QPDFObjectHandle>
1226
QPDFObjectHandle::getPageImages()
1227
0
{
1228
0
    return QPDFPageObjectHelper(*this).getImages();
1229
0
}
1230
1231
std::vector<QPDFObjectHandle>
1232
QPDFObjectHandle::arrayOrStreamToStreamArray(
1233
    std::string const& description, std::string& all_description)
1234
0
{
1235
0
    all_description = description;
1236
0
    std::vector<QPDFObjectHandle> result;
1237
0
    if (auto array = as_array(strict)) {
1238
0
        int n_items = array.size();
1239
0
        for (int i = 0; i < n_items; ++i) {
1240
0
            QPDFObjectHandle item = array.at(i).second;
1241
0
            if (item.isStream()) {
1242
0
                result.emplace_back(item);
1243
0
            } else {
1244
0
                QTC::TC("qpdf", "QPDFObjectHandle non-stream in stream array");
1245
0
                warn(
1246
0
                    item.getOwningQPDF(),
1247
0
                    QPDFExc(
1248
0
                        qpdf_e_damaged_pdf,
1249
0
                        "",
1250
0
                        description + ": item index " + std::to_string(i) + " (from 0)",
1251
0
                        0,
1252
0
                        "ignoring non-stream in an array of streams"));
1253
0
            }
1254
0
        }
1255
0
    } else if (isStream()) {
1256
0
        result.emplace_back(*this);
1257
0
    } else if (!isNull()) {
1258
0
        warn(
1259
0
            getOwningQPDF(),
1260
0
            QPDFExc(
1261
0
                qpdf_e_damaged_pdf,
1262
0
                "",
1263
0
                description,
1264
0
                0,
1265
0
                " object is supposed to be a stream or an array of streams but is neither"));
1266
0
    }
1267
1268
0
    bool first = true;
1269
0
    for (auto const& item: result) {
1270
0
        if (first) {
1271
0
            first = false;
1272
0
        } else {
1273
0
            all_description += ",";
1274
0
        }
1275
0
        all_description += " stream " + item.getObjGen().unparse(' ');
1276
0
    }
1277
1278
0
    return result;
1279
0
}
1280
1281
std::vector<QPDFObjectHandle>
1282
QPDFObjectHandle::getPageContents()
1283
0
{
1284
0
    std::string description = "page object " + getObjGen().unparse(' ');
1285
0
    std::string all_description;
1286
0
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1287
0
}
1288
1289
void
1290
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1291
0
{
1292
0
    new_contents.assertStream();
1293
1294
0
    std::vector<QPDFObjectHandle> content_streams;
1295
0
    if (first) {
1296
0
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1297
0
        content_streams.push_back(new_contents);
1298
0
    }
1299
0
    for (auto const& iter: getPageContents()) {
1300
0
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1301
0
        content_streams.push_back(iter);
1302
0
    }
1303
0
    if (!first) {
1304
0
        content_streams.push_back(new_contents);
1305
0
    }
1306
1307
0
    replaceKey("/Contents", newArray(content_streams));
1308
0
}
1309
1310
void
1311
QPDFObjectHandle::rotatePage(int angle, bool relative)
1312
0
{
1313
0
    if ((angle % 90) != 0) {
1314
0
        throw std::runtime_error(
1315
0
            "QPDF::rotatePage called with an angle that is not a multiple of 90");
1316
0
    }
1317
0
    int new_angle = angle;
1318
0
    if (relative) {
1319
0
        int old_angle = 0;
1320
0
        QPDFObjectHandle cur_obj = *this;
1321
0
        QPDFObjGen::set visited;
1322
0
        while (visited.add(cur_obj)) {
1323
            // Don't get stuck in an infinite loop
1324
0
            if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) {
1325
0
                break;
1326
0
            } else if (cur_obj.getKey("/Parent").isDictionary()) {
1327
0
                cur_obj = cur_obj.getKey("/Parent");
1328
0
            } else {
1329
0
                break;
1330
0
            }
1331
0
        }
1332
0
        QTC::TC("qpdf", "QPDFObjectHandle found old angle", visited.size() > 1 ? 0 : 1);
1333
0
        if ((old_angle % 90) != 0) {
1334
0
            old_angle = 0;
1335
0
        }
1336
0
        new_angle += old_angle;
1337
0
    }
1338
0
    new_angle = (new_angle + 360) % 360;
1339
    // Make this explicit even with new_angle == 0 since /Rotate can be inherited.
1340
0
    replaceKey("/Rotate", QPDFObjectHandle::newInteger(new_angle));
1341
0
}
1342
1343
void
1344
QPDFObjectHandle::coalesceContentStreams()
1345
0
{
1346
0
    QPDFObjectHandle contents = getKey("/Contents");
1347
0
    if (contents.isStream()) {
1348
0
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1349
0
        return;
1350
0
    } else if (!contents.isArray()) {
1351
        // /Contents is optional for pages, and some very damaged files may have pages that are
1352
        // invalid in other ways.
1353
0
        return;
1354
0
    }
1355
    // Should not be possible for a page object to not have an owning PDF unless it was manually
1356
    // constructed in some incorrect way. However, it can happen in a PDF file whose page structure
1357
    // is direct, which is against spec but still possible to hand construct, as in fuzz issue
1358
    // 27393.
1359
0
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1360
1361
0
    QPDFObjectHandle new_contents = newStream(&qpdf);
1362
0
    replaceKey("/Contents", new_contents);
1363
1364
0
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1365
0
    new_contents.replaceStreamData(provider, newNull(), newNull());
1366
0
}
1367
1368
std::string
1369
QPDFObjectHandle::unparse() const
1370
0
{
1371
0
    if (isIndirect()) {
1372
0
        return getObjGen().unparse(' ') + " R";
1373
0
    } else {
1374
0
        return unparseResolved();
1375
0
    }
1376
0
}
1377
1378
std::string
1379
QPDFObjectHandle::unparseResolved() const
1380
0
{
1381
0
    if (!obj) {
1382
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1383
0
    }
1384
0
    return BaseHandle::unparse();
1385
0
}
1386
1387
std::string
1388
QPDFObjectHandle::unparseBinary() const
1389
0
{
1390
0
    if (auto str = as<QPDF_String>()) {
1391
0
        return str->unparse(true);
1392
0
    } else {
1393
0
        return unparse();
1394
0
    }
1395
0
}
1396
1397
JSON
1398
QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) const
1399
0
{
1400
0
    if ((!dereference_indirect) && isIndirect()) {
1401
0
        return JSON::makeString(unparse());
1402
0
    } else if (!obj) {
1403
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1404
0
    } else {
1405
0
        Pl_Buffer p{"json"};
1406
0
        JSON::Writer jw{&p, 0};
1407
0
        writeJSON(json_version, jw, dereference_indirect);
1408
0
        p.finish();
1409
0
        return JSON::parse(p.getString());
1410
0
    }
1411
0
}
1412
1413
void
1414
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1415
0
{
1416
0
    if (!dereference_indirect && isIndirect()) {
1417
0
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1418
0
    } else if (!obj) {
1419
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1420
0
    } else {
1421
0
        write_json(json_version, p);
1422
0
    }
1423
0
}
1424
1425
void
1426
QPDFObjectHandle::writeJSON(
1427
    int json_version, Pipeline* p, bool dereference_indirect, size_t depth) const
1428
0
{
1429
0
    JSON::Writer jw{p, depth};
1430
0
    writeJSON(json_version, jw, dereference_indirect);
1431
0
}
1432
1433
QPDFObjectHandle
1434
QPDFObjectHandle::wrapInArray()
1435
0
{
1436
0
    if (isArray()) {
1437
0
        return *this;
1438
0
    }
1439
0
    QPDFObjectHandle result = QPDFObjectHandle::newArray();
1440
0
    result.appendItem(*this);
1441
0
    return result;
1442
0
}
1443
1444
QPDFObjectHandle
1445
QPDFObjectHandle::parse(std::string const& object_str, std::string const& object_description)
1446
11.0k
{
1447
11.0k
    return parse(nullptr, object_str, object_description);
1448
11.0k
}
1449
1450
QPDFObjectHandle
1451
QPDFObjectHandle::parse(
1452
    QPDF* context, std::string const& object_str, std::string const& object_description)
1453
11.0k
{
1454
11.0k
    auto input = is::OffsetBuffer("parsed object", object_str);
1455
11.0k
    auto result = QPDFParser::parse(input, object_description, context);
1456
11.0k
    size_t offset = QIntC::to_size(input.tell());
1457
1.06M
    while (offset < object_str.length()) {
1458
1.05M
        if (!isspace(object_str.at(offset))) {
1459
94
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1460
94
            throw QPDFExc(
1461
94
                qpdf_e_damaged_pdf,
1462
94
                "parsed object",
1463
94
                object_description,
1464
94
                input.getLastOffset(),
1465
94
                "trailing data found parsing object from string");
1466
94
        }
1467
1.05M
        ++offset;
1468
1.05M
    }
1469
10.9k
    return result;
1470
11.0k
}
1471
1472
void
1473
QPDFObjectHandle::pipePageContents(Pipeline* p)
1474
0
{
1475
0
    std::string description = "page object " + getObjGen().unparse(' ');
1476
0
    std::string all_description;
1477
0
    getKey("/Contents").pipeContentStreams(p, description, all_description);
1478
0
}
1479
1480
void
1481
QPDFObjectHandle::pipeContentStreams(
1482
    Pipeline* p, std::string const& description, std::string& all_description)
1483
0
{
1484
0
    bool need_newline = false;
1485
0
    std::string buffer;
1486
0
    pl::String buf(buffer);
1487
0
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1488
0
        if (need_newline) {
1489
0
            buf.writeCStr("\n");
1490
0
        }
1491
0
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1492
0
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1493
0
            throw QPDFExc(
1494
0
                qpdf_e_damaged_pdf,
1495
0
                "content stream",
1496
0
                "content stream object " + stream.getObjGen().unparse(' '),
1497
0
                0,
1498
0
                "errors while decoding content stream");
1499
0
        }
1500
0
        need_newline = buffer.empty() || buffer.back() != '\n';
1501
0
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1502
0
        p->writeString(buffer);
1503
0
        buffer.clear();
1504
0
    }
1505
0
    p->finish();
1506
0
}
1507
1508
void
1509
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1510
0
{
1511
0
    std::string description = "page object " + getObjGen().unparse(' ');
1512
0
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1513
0
}
1514
1515
void
1516
QPDFObjectHandle::parseAsContents(ParserCallbacks* callbacks)
1517
0
{
1518
0
    std::string description = "object " + getObjGen().unparse(' ');
1519
0
    parseContentStream_internal(description, callbacks);
1520
0
}
1521
1522
void
1523
QPDFObjectHandle::filterPageContents(TokenFilter* filter, Pipeline* next)
1524
0
{
1525
0
    auto description = "token filter for page object " + getObjGen().unparse(' ');
1526
0
    Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
1527
0
    pipePageContents(&token_pipeline);
1528
0
}
1529
1530
void
1531
QPDFObjectHandle::filterAsContents(TokenFilter* filter, Pipeline* next)
1532
0
{
1533
0
    auto description = "token filter for object " + getObjGen().unparse(' ');
1534
0
    Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
1535
0
    pipeStreamData(&token_pipeline, 0, qpdf_dl_specialized);
1536
0
}
1537
1538
void
1539
QPDFObjectHandle::parseContentStream(QPDFObjectHandle stream_or_array, ParserCallbacks* callbacks)
1540
0
{
1541
0
    stream_or_array.parseContentStream_internal("content stream objects", callbacks);
1542
0
}
1543
1544
void
1545
QPDFObjectHandle::parseContentStream_internal(
1546
    std::string const& description, ParserCallbacks* callbacks)
1547
0
{
1548
0
    std::string stream_data;
1549
0
    pl::String buf(stream_data);
1550
0
    std::string all_description;
1551
0
    pipeContentStreams(&buf, description, all_description);
1552
0
    callbacks->contentSize(stream_data.size());
1553
0
    try {
1554
0
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1555
0
    } catch (TerminateParsing&) {
1556
0
        return;
1557
0
    }
1558
0
    callbacks->handleEOF();
1559
0
}
1560
1561
void
1562
QPDFObjectHandle::parseContentStream_data(
1563
    std::string_view stream_data,
1564
    std::string const& description,
1565
    ParserCallbacks* callbacks,
1566
    QPDF* context)
1567
0
{
1568
0
    size_t stream_length = stream_data.size();
1569
0
    auto input = is::OffsetBuffer(description, stream_data);
1570
0
    Tokenizer tokenizer;
1571
0
    tokenizer.allowEOF();
1572
0
    auto sp_description = QPDFParser::make_description(description, "content");
1573
0
    while (QIntC::to_size(input.tell()) < stream_length) {
1574
        // Read a token and seek to the beginning. The offset we get from this process is the
1575
        // beginning of the next non-ignorable (space, comment) token. This way, the offset and
1576
        // don't including ignorable content.
1577
0
        tokenizer.nextToken(input, "content", true);
1578
0
        qpdf_offset_t offset = input.getLastOffset();
1579
0
        input.seek(offset, SEEK_SET);
1580
0
        auto obj = QPDFParser::parse_content(input, sp_description, tokenizer, context);
1581
0
        if (!obj) {
1582
            // EOF
1583
0
            break;
1584
0
        }
1585
0
        size_t length = QIntC::to_size(input.tell() - offset);
1586
1587
0
        callbacks->handleObject(obj, QIntC::to_size(offset), length);
1588
0
        if (obj.isOperator() && (obj.getOperatorValue() == "ID")) {
1589
            // Discard next character; it is the space after ID that terminated the token.  Read
1590
            // until end of inline image.
1591
0
            char ch;
1592
0
            input.read(&ch, 1);
1593
0
            tokenizer.expectInlineImage(input);
1594
0
            tokenizer.nextToken(input, description);
1595
0
            offset = input.getLastOffset();
1596
0
            length = QIntC::to_size(input.tell() - offset);
1597
0
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1598
0
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1599
0
                warn(
1600
0
                    context,
1601
0
                    QPDFExc(
1602
0
                        qpdf_e_damaged_pdf,
1603
0
                        description,
1604
0
                        "stream data",
1605
0
                        input.tell(),
1606
0
                        "EOF found while reading inline image"));
1607
0
            } else {
1608
0
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1609
0
                callbacks->handleObject(
1610
0
                    QPDFObjectHandle::newInlineImage(tokenizer.getValue()),
1611
0
                    QIntC::to_size(offset),
1612
0
                    length);
1613
0
            }
1614
0
        }
1615
0
    }
1616
0
}
1617
1618
void
1619
QPDFObjectHandle::addContentTokenFilter(std::shared_ptr<TokenFilter> filter)
1620
0
{
1621
0
    coalesceContentStreams();
1622
0
    getKey("/Contents").addTokenFilter(filter);
1623
0
}
1624
1625
void
1626
QPDFObjectHandle::addTokenFilter(std::shared_ptr<TokenFilter> filter)
1627
0
{
1628
0
    return as_stream(error).addTokenFilter(filter);
1629
0
}
1630
1631
QPDFObjectHandle
1632
QPDFObjectHandle::parse(
1633
    std::shared_ptr<InputSource> input,
1634
    std::string const& object_description,
1635
    QPDFTokenizer& tokenizer,
1636
    bool& empty,
1637
    StringDecrypter* decrypter,
1638
    QPDF* context)
1639
0
{
1640
0
    return QPDFParser::parse(*input, object_description, tokenizer, empty, decrypter, context);
1641
0
}
1642
1643
qpdf_offset_t
1644
QPDFObjectHandle::getParsedOffset() const
1645
683
{
1646
683
    return obj ? obj->getParsedOffset() : -1;
1647
683
}
1648
1649
QPDFObjectHandle
1650
QPDFObjectHandle::newBool(bool value)
1651
1.04k
{
1652
1.04k
    return {QPDFObject::create<QPDF_Bool>(value)};
1653
1.04k
}
1654
1655
QPDFObjectHandle
1656
QPDFObjectHandle::newNull()
1657
33.4k
{
1658
33.4k
    return {QPDFObject::create<QPDF_Null>()};
1659
33.4k
}
1660
1661
QPDFObjectHandle
1662
QPDFObjectHandle::newInteger(long long value)
1663
1.47M
{
1664
1.47M
    return {QPDFObject::create<QPDF_Integer>(value)};
1665
1.47M
}
1666
1667
QPDFObjectHandle
1668
QPDFObjectHandle::newReal(std::string const& value)
1669
4.03k
{
1670
4.03k
    return {QPDFObject::create<QPDF_Real>(value)};
1671
4.03k
}
1672
1673
QPDFObjectHandle
1674
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1675
0
{
1676
0
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1677
0
}
1678
1679
QPDFObjectHandle
1680
QPDFObjectHandle::newName(std::string const& name)
1681
11.3k
{
1682
11.3k
    return {QPDFObject::create<QPDF_Name>(name)};
1683
11.3k
}
1684
1685
QPDFObjectHandle
1686
QPDFObjectHandle::newString(std::string const& str)
1687
1.34k
{
1688
1.34k
    return {QPDFObject::create<QPDF_String>(str)};
1689
1.34k
}
1690
1691
QPDFObjectHandle
1692
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1693
5.53k
{
1694
5.53k
    return {QPDF_String::create_utf16(utf8_str)};
1695
5.53k
}
1696
1697
QPDFObjectHandle
1698
QPDFObjectHandle::newOperator(std::string const& value)
1699
0
{
1700
0
    return {QPDFObject::create<QPDF_Operator>(value)};
1701
0
}
1702
1703
QPDFObjectHandle
1704
QPDFObjectHandle::newInlineImage(std::string const& value)
1705
0
{
1706
0
    return {QPDFObject::create<QPDF_InlineImage>(value)};
1707
0
}
1708
1709
QPDFObjectHandle
1710
QPDFObjectHandle::newArray()
1711
13.6k
{
1712
13.6k
    return newArray(std::vector<QPDFObjectHandle>());
1713
13.6k
}
1714
1715
QPDFObjectHandle
1716
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1717
13.6k
{
1718
13.6k
    return {QPDFObject::create<QPDF_Array>(items)};
1719
13.6k
}
1720
1721
QPDFObjectHandle
1722
QPDFObjectHandle::newArray(Rectangle const& rect)
1723
0
{
1724
0
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1725
0
}
1726
1727
QPDFObjectHandle
1728
QPDFObjectHandle::newArray(Matrix const& matrix)
1729
0
{
1730
0
    return newArray(
1731
0
        {newReal(matrix.a),
1732
0
         newReal(matrix.b),
1733
0
         newReal(matrix.c),
1734
0
         newReal(matrix.d),
1735
0
         newReal(matrix.e),
1736
0
         newReal(matrix.f)});
1737
0
}
1738
1739
QPDFObjectHandle
1740
QPDFObjectHandle::newArray(QPDFMatrix const& matrix)
1741
0
{
1742
0
    return newArray(
1743
0
        {newReal(matrix.a),
1744
0
         newReal(matrix.b),
1745
0
         newReal(matrix.c),
1746
0
         newReal(matrix.d),
1747
0
         newReal(matrix.e),
1748
0
         newReal(matrix.f)});
1749
0
}
1750
1751
QPDFObjectHandle
1752
QPDFObjectHandle::newFromRectangle(Rectangle const& rect)
1753
0
{
1754
0
    return newArray(rect);
1755
0
}
1756
1757
QPDFObjectHandle
1758
QPDFObjectHandle::newFromMatrix(Matrix const& m)
1759
0
{
1760
0
    return newArray(m);
1761
0
}
1762
1763
QPDFObjectHandle
1764
QPDFObjectHandle::newFromMatrix(QPDFMatrix const& m)
1765
0
{
1766
0
    return newArray(m);
1767
0
}
1768
1769
QPDFObjectHandle
1770
QPDFObjectHandle::newDictionary()
1771
26.8k
{
1772
26.8k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1773
26.8k
}
1774
1775
QPDFObjectHandle
1776
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1777
26.8k
{
1778
26.8k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1779
26.8k
}
1780
1781
QPDFObjectHandle
1782
QPDFObjectHandle::newStream(QPDF* qpdf)
1783
0
{
1784
0
    if (qpdf == nullptr) {
1785
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1786
0
    }
1787
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1788
0
    return qpdf->newStream();
1789
0
}
1790
1791
QPDFObjectHandle
1792
QPDFObjectHandle::newStream(QPDF* qpdf, std::shared_ptr<Buffer> data)
1793
0
{
1794
0
    if (qpdf == nullptr) {
1795
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1796
0
    }
1797
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with data");
1798
0
    return qpdf->newStream(data);
1799
0
}
1800
1801
QPDFObjectHandle
1802
QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data)
1803
0
{
1804
0
    if (qpdf == nullptr) {
1805
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1806
0
    }
1807
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with string");
1808
0
    return qpdf->newStream(data);
1809
0
}
1810
1811
QPDFObjectHandle
1812
QPDFObjectHandle::newReserved(QPDF* qpdf)
1813
0
{
1814
0
    if (qpdf == nullptr) {
1815
0
        throw std::runtime_error("attempt to create reserved object in null qpdf object");
1816
0
    }
1817
0
    return qpdf->newReserved();
1818
0
}
1819
1820
void
1821
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
1822
4.39k
{
1823
4.39k
    if (obj) {
1824
4.39k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
1825
4.39k
        obj->setDescription(owning_qpdf, descr);
1826
4.39k
    }
1827
4.39k
}
1828
1829
bool
1830
QPDFObjectHandle::hasObjectDescription() const
1831
2.29M
{
1832
2.29M
    return obj && obj->hasDescription();
1833
2.29M
}
1834
1835
QPDFObjectHandle
1836
QPDFObjectHandle::shallowCopy()
1837
0
{
1838
0
    if (!obj) {
1839
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1840
0
    }
1841
0
    return {copy()};
1842
0
}
1843
1844
QPDFObjectHandle
1845
QPDFObjectHandle::unsafeShallowCopy()
1846
0
{
1847
0
    if (!obj) {
1848
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1849
0
    }
1850
0
    return {copy(true)};
1851
0
}
1852
1853
void
1854
QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
1855
0
{
1856
0
    assertInitialized();
1857
1858
0
    auto cur_og = getObjGen();
1859
0
    if (!visited.add(cur_og)) {
1860
0
        QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
1861
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
1862
0
    }
1863
1864
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
1865
0
        obj = copy(true);
1866
0
    } else if (auto a = as_array(strict)) {
1867
0
        std::vector<QPDFObjectHandle> items;
1868
0
        for (auto const& item: a) {
1869
0
            items.emplace_back(item);
1870
0
            items.back().makeDirect(visited, stop_at_streams);
1871
0
        }
1872
0
        obj = QPDFObject::create<QPDF_Array>(items);
1873
0
    } else if (isDictionary()) {
1874
0
        std::map<std::string, QPDFObjectHandle> items;
1875
0
        for (auto const& [key, value]: as_dictionary(strict)) {
1876
0
            if (!value.null()) {
1877
0
                items.insert({key, value});
1878
0
                items[key].makeDirect(visited, stop_at_streams);
1879
0
            }
1880
0
        }
1881
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
1882
0
    } else if (isStream()) {
1883
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
1884
0
        if (!stop_at_streams) {
1885
0
            throw std::runtime_error("attempt to make a stream into a direct object");
1886
0
        }
1887
0
    } else if (isReserved()) {
1888
0
        throw std::logic_error(
1889
0
            "QPDFObjectHandle: attempting to make a reserved object handle direct");
1890
0
    } else {
1891
0
        throw std::logic_error("QPDFObjectHandle::makeDirectInternal: unknown object type");
1892
0
    }
1893
1894
0
    visited.erase(cur_og);
1895
0
}
1896
1897
QPDFObjectHandle
1898
QPDFObjectHandle::copyStream()
1899
0
{
1900
0
    assertStream();
1901
0
    QPDFObjectHandle result = newStream(getOwningQPDF());
1902
0
    QPDFObjectHandle dict = result.getDict();
1903
0
    QPDFObjectHandle old_dict = getDict();
1904
0
    for (auto& iter: QPDFDictItems(old_dict)) {
1905
0
        if (iter.second.isIndirect()) {
1906
0
            dict.replaceKey(iter.first, iter.second);
1907
0
        } else {
1908
0
            dict.replaceKey(iter.first, iter.second.shallowCopy());
1909
0
        }
1910
0
    }
1911
0
    QPDF::StreamCopier::copyStreamData(getOwningQPDF(), result, *this);
1912
0
    return result;
1913
0
}
1914
1915
void
1916
QPDFObjectHandle::makeDirect(bool allow_streams)
1917
0
{
1918
0
    QPDFObjGen::set visited;
1919
0
    makeDirect(visited, allow_streams);
1920
0
}
1921
1922
void
1923
QPDFObjectHandle::assertInitialized() const
1924
0
{
1925
0
    if (!obj) {
1926
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1927
0
    }
1928
0
}
1929
1930
void
1931
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& warning) const
1932
0
{
1933
0
    QPDF* context = nullptr;
1934
0
    std::string description;
1935
    // Type checks above guarantee that the object has been dereferenced. Nevertheless, dereference
1936
    // throws exceptions in the test suite
1937
0
    if (!obj) {
1938
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1939
0
    }
1940
0
    obj->getDescription(context, description);
1941
    // Null context handled by warn
1942
0
    warn(
1943
0
        context,
1944
0
        QPDFExc(
1945
0
            qpdf_e_object,
1946
0
            "",
1947
0
            description,
1948
0
            0,
1949
0
            std::string("operation for ") + expected_type + " attempted on object of type " +
1950
0
                QPDFObjectHandle(*this).getTypeName() + ": " + warning));
1951
0
}
1952
1953
void
1954
QPDFObjectHandle::warnIfPossible(std::string const& warning) const
1955
0
{
1956
0
    QPDF* context = nullptr;
1957
0
    std::string description;
1958
0
    if (obj && obj->getDescription(context, description)) {
1959
0
        warn(context, QPDFExc(qpdf_e_damaged_pdf, "", description, 0, warning));
1960
0
    } else {
1961
0
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
1962
0
    }
1963
0
}
1964
1965
void
1966
QPDFObjectHandle::objectWarning(std::string const& warning) const
1967
0
{
1968
0
    QPDF* context = nullptr;
1969
0
    std::string description;
1970
    // Type checks above guarantee that the object is initialized.
1971
0
    obj->getDescription(context, description);
1972
    // Null context handled by warn
1973
0
    warn(context, QPDFExc(qpdf_e_object, "", description, 0, warning));
1974
0
}
1975
1976
void
1977
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
1978
0
{
1979
0
    if (!istype) {
1980
0
        throw std::runtime_error(
1981
0
            std::string("operation for ") + type_name + " attempted on object of type " +
1982
0
            QPDFObjectHandle(*this).getTypeName());
1983
0
    }
1984
0
}
1985
1986
void
1987
QPDFObjectHandle::assertNull() const
1988
0
{
1989
0
    assertType("null", isNull());
1990
0
}
1991
1992
void
1993
QPDFObjectHandle::assertBool() const
1994
0
{
1995
0
    assertType("boolean", isBool());
1996
0
}
1997
1998
void
1999
QPDFObjectHandle::assertInteger() const
2000
0
{
2001
0
    assertType("integer", isInteger());
2002
0
}
2003
2004
void
2005
QPDFObjectHandle::assertReal() const
2006
0
{
2007
0
    assertType("real", isReal());
2008
0
}
2009
2010
void
2011
QPDFObjectHandle::assertName() const
2012
0
{
2013
0
    assertType("name", isName());
2014
0
}
2015
2016
void
2017
QPDFObjectHandle::assertString() const
2018
0
{
2019
0
    assertType("string", isString());
2020
0
}
2021
2022
void
2023
QPDFObjectHandle::assertOperator() const
2024
0
{
2025
0
    assertType("operator", isOperator());
2026
0
}
2027
2028
void
2029
QPDFObjectHandle::assertInlineImage() const
2030
0
{
2031
0
    assertType("inlineimage", isInlineImage());
2032
0
}
2033
2034
void
2035
QPDFObjectHandle::assertArray() const
2036
0
{
2037
0
    assertType("array", isArray());
2038
0
}
2039
2040
void
2041
QPDFObjectHandle::assertDictionary() const
2042
0
{
2043
0
    assertType("dictionary", isDictionary());
2044
0
}
2045
2046
void
2047
QPDFObjectHandle::assertStream() const
2048
0
{
2049
0
    assertType("stream", isStream());
2050
0
}
2051
2052
void
2053
QPDFObjectHandle::assertReserved() const
2054
0
{
2055
0
    assertType("reserved", isReserved());
2056
0
}
2057
2058
void
2059
QPDFObjectHandle::assertIndirect() const
2060
0
{
2061
0
    if (!isIndirect()) {
2062
0
        throw std::logic_error("operation for indirect object attempted on direct object");
2063
0
    }
2064
0
}
2065
2066
void
2067
QPDFObjectHandle::assertScalar() const
2068
0
{
2069
0
    assertType("scalar", isScalar());
2070
0
}
2071
2072
void
2073
QPDFObjectHandle::assertNumber() const
2074
0
{
2075
0
    assertType("number", isNumber());
2076
0
}
2077
2078
bool
2079
QPDFObjectHandle::isPageObject() const
2080
0
{
2081
    // See comments in QPDFObjectHandle.hh.
2082
0
    if (getOwningQPDF() == nullptr) {
2083
0
        return false;
2084
0
    }
2085
    // getAllPages repairs /Type when traversing the page tree.
2086
0
    getOwningQPDF()->getAllPages();
2087
0
    return isDictionaryOfType("/Page");
2088
0
}
2089
2090
bool
2091
QPDFObjectHandle::isPagesObject() const
2092
0
{
2093
0
    if (getOwningQPDF() == nullptr) {
2094
0
        return false;
2095
0
    }
2096
    // getAllPages repairs /Type when traversing the page tree.
2097
0
    getOwningQPDF()->getAllPages();
2098
0
    return isDictionaryOfType("/Pages");
2099
0
}
2100
2101
bool
2102
QPDFObjectHandle::isFormXObject() const
2103
0
{
2104
0
    return isStreamOfType("", "/Form");
2105
0
}
2106
2107
bool
2108
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2109
0
{
2110
0
    return (
2111
0
        isStreamOfType("", "/Image") &&
2112
0
        ((!exclude_imagemask) ||
2113
0
         (!(getDict().getKey("/ImageMask").isBool() &&
2114
0
            getDict().getKey("/ImageMask").getBoolValue()))));
2115
0
}
2116
2117
void
2118
QPDFObjectHandle::assertPageObject() const
2119
0
{
2120
0
    if (!isPageObject()) {
2121
0
        throw std::runtime_error("page operation called on non-Page object");
2122
0
    }
2123
0
}
2124
2125
void
2126
QPDFObjectHandle::warn(QPDF* qpdf, QPDFExc const& e)
2127
0
{
2128
    // If parsing on behalf of a QPDF object and want to give a warning, we can warn through the
2129
    // object. If parsing for some other reason, such as an explicit creation of an object from a
2130
    // string, then just throw the exception.
2131
0
    if (qpdf) {
2132
0
        qpdf->warn(e);
2133
0
    } else {
2134
0
        throw e;
2135
0
    }
2136
0
}
2137
2138
QPDFObjectHandle::QPDFDictItems::QPDFDictItems(QPDFObjectHandle const& oh) :
2139
0
    oh(oh)
2140
0
{
2141
0
}
2142
2143
QPDFObjectHandle::QPDFDictItems::iterator&
2144
QPDFObjectHandle::QPDFDictItems::iterator::operator++()
2145
0
{
2146
0
    ++m->iter;
2147
0
    updateIValue();
2148
0
    return *this;
2149
0
}
2150
2151
QPDFObjectHandle::QPDFDictItems::iterator&
2152
QPDFObjectHandle::QPDFDictItems::iterator::operator--()
2153
0
{
2154
0
    --m->iter;
2155
0
    updateIValue();
2156
0
    return *this;
2157
0
}
2158
2159
QPDFObjectHandle::QPDFDictItems::iterator::reference
2160
QPDFObjectHandle::QPDFDictItems::iterator::operator*()
2161
0
{
2162
0
    updateIValue();
2163
0
    return ivalue;
2164
0
}
2165
2166
QPDFObjectHandle::QPDFDictItems::iterator::pointer
2167
QPDFObjectHandle::QPDFDictItems::iterator::operator->()
2168
0
{
2169
0
    updateIValue();
2170
0
    return &ivalue;
2171
0
}
2172
2173
bool
2174
QPDFObjectHandle::QPDFDictItems::iterator::operator==(iterator const& other) const
2175
0
{
2176
0
    if (m->is_end && other.m->is_end) {
2177
0
        return true;
2178
0
    }
2179
0
    if (m->is_end || other.m->is_end) {
2180
0
        return false;
2181
0
    }
2182
0
    return (ivalue.first == other.ivalue.first);
2183
0
}
2184
2185
QPDFObjectHandle::QPDFDictItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2186
0
    m(new Members(oh, for_begin))
2187
0
{
2188
0
    updateIValue();
2189
0
}
2190
2191
void
2192
QPDFObjectHandle::QPDFDictItems::iterator::updateIValue()
2193
0
{
2194
0
    m->is_end = (m->iter == m->keys.end());
2195
0
    if (m->is_end) {
2196
0
        ivalue.first = "";
2197
0
        ivalue.second = QPDFObjectHandle();
2198
0
    } else {
2199
0
        ivalue.first = *(m->iter);
2200
0
        ivalue.second = m->oh.getKey(ivalue.first);
2201
0
    }
2202
0
}
2203
2204
QPDFObjectHandle::QPDFDictItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2205
0
    oh(oh)
2206
0
{
2207
0
    keys = oh.getKeys();
2208
0
    iter = for_begin ? keys.begin() : keys.end();
2209
0
}
2210
2211
QPDFObjectHandle::QPDFDictItems::iterator
2212
QPDFObjectHandle::QPDFDictItems::begin()
2213
0
{
2214
0
    return {oh, true};
2215
0
}
2216
2217
QPDFObjectHandle::QPDFDictItems::iterator
2218
QPDFObjectHandle::QPDFDictItems::end()
2219
0
{
2220
0
    return {oh, false};
2221
0
}
2222
2223
QPDFObjectHandle::QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle const& oh) :
2224
0
    oh(oh)
2225
0
{
2226
0
}
2227
2228
QPDFObjectHandle::QPDFArrayItems::iterator&
2229
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2230
0
{
2231
0
    if (!m->is_end) {
2232
0
        ++m->item_number;
2233
0
        updateIValue();
2234
0
    }
2235
0
    return *this;
2236
0
}
2237
2238
QPDFObjectHandle::QPDFArrayItems::iterator&
2239
QPDFObjectHandle::QPDFArrayItems::iterator::operator--()
2240
0
{
2241
0
    if (m->item_number > 0) {
2242
0
        --m->item_number;
2243
0
        updateIValue();
2244
0
    }
2245
0
    return *this;
2246
0
}
2247
2248
QPDFObjectHandle::QPDFArrayItems::iterator::reference
2249
QPDFObjectHandle::QPDFArrayItems::iterator::operator*()
2250
0
{
2251
0
    updateIValue();
2252
0
    return ivalue;
2253
0
}
2254
2255
QPDFObjectHandle::QPDFArrayItems::iterator::pointer
2256
QPDFObjectHandle::QPDFArrayItems::iterator::operator->()
2257
0
{
2258
0
    updateIValue();
2259
0
    return &ivalue;
2260
0
}
2261
2262
bool
2263
QPDFObjectHandle::QPDFArrayItems::iterator::operator==(iterator const& other) const
2264
0
{
2265
0
    return (m->item_number == other.m->item_number);
2266
0
}
2267
2268
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2269
0
    m(new Members(oh, for_begin))
2270
0
{
2271
0
    updateIValue();
2272
0
}
2273
2274
void
2275
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2276
0
{
2277
0
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2278
0
    if (m->is_end) {
2279
0
        ivalue = QPDFObjectHandle();
2280
0
    } else {
2281
0
        ivalue = m->oh.getArrayItem(m->item_number);
2282
0
    }
2283
0
}
2284
2285
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2286
0
    oh(oh)
2287
0
{
2288
0
    item_number = for_begin ? 0 : oh.getArrayNItems();
2289
0
}
2290
2291
QPDFObjectHandle::QPDFArrayItems::iterator
2292
QPDFObjectHandle::QPDFArrayItems::begin()
2293
0
{
2294
0
    return {oh, true};
2295
0
}
2296
2297
QPDFObjectHandle::QPDFArrayItems::iterator
2298
QPDFObjectHandle::QPDFArrayItems::end()
2299
0
{
2300
0
    return {oh, false};
2301
0
}
2302
2303
QPDFObjGen
2304
QPDFObjectHandle::getObjGen() const
2305
129k
{
2306
129k
    return obj ? obj->getObjGen() : QPDFObjGen();
2307
129k
}
2308
2309
int
2310
QPDFObjectHandle::getObjectID() const
2311
93.1k
{
2312
93.1k
    return getObjGen().getObj();
2313
93.1k
}
2314
2315
int
2316
QPDFObjectHandle::getGeneration() const
2317
0
{
2318
0
    return getObjGen().getGen();
2319
0
}
2320
2321
bool
2322
QPDFObjectHandle::isIndirect() const
2323
93.1k
{
2324
93.1k
    return getObjectID() != 0;
2325
93.1k
}
2326
2327
// Indirect object accessors
2328
QPDF*
2329
QPDFObjectHandle::getOwningQPDF() const
2330
268k
{
2331
268k
    return obj ? obj->getQPDF() : nullptr;
2332
268k
}
2333
2334
QPDF&
2335
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2336
0
{
2337
0
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2338
0
        return *result;
2339
0
    }
2340
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2341
0
}
2342
2343
void
2344
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2345
0
{
2346
0
    if (obj) {
2347
0
        obj->setParsedOffset(offset);
2348
0
    }
2349
0
}
2350
2351
QPDFObjectHandle
2352
operator""_qpdf(char const* v, size_t len)
2353
0
{
2354
0
    return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal");
2355
0
}