Coverage Report

Created: 2026-06-09 07:00

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