Coverage Report

Created: 2026-07-16 06:55

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
334k
{
35
334k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
334k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
28.5k
    supports_retry(supports_retry)
47
28.5k
{
48
28.5k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
28.5k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
28.5k
}
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
4.42k
{
86
4.42k
    return supports_retry;
87
4.42k
}
88
89
namespace
90
{
91
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
92
    {
93
      public:
94
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
95
4.42k
            containing_page(containing_page),
96
4.42k
            old_contents(old_contents)
97
4.42k
        {
98
4.42k
        }
99
4.42k
        ~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
4.42k
{
111
4.42k
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
112
4.42k
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
113
4.42k
    std::string all_description;
114
4.42k
    old_contents.pipeContentStreams(p, description, all_description);
115
4.42k
}
116
117
void
118
QPDFObjectHandle::TokenFilter::handleEOF()
119
7.86k
{
120
7.86k
}
121
122
void
123
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
124
31.2k
{
125
31.2k
    pipeline = p;
126
31.2k
}
127
128
void
129
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
130
264k
{
131
264k
    if (!pipeline) {
132
0
        return;
133
0
    }
134
264k
    if (len) {
135
256k
        pipeline->write(data, len);
136
256k
    }
137
264k
}
138
139
void
140
QPDFObjectHandle::TokenFilter::write(std::string const& str)
141
55.3k
{
142
55.3k
    write(str.c_str(), str.length());
143
55.3k
}
144
145
void
146
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
147
209k
{
148
209k
    std::string const& value = token.getRawValue();
149
209k
    write(value.c_str(), value.length());
150
209k
}
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
522k
{
181
522k
    int tail = 0;       // Number of continuation characters expected.
182
522k
    bool tail2 = false; // Potential overlong 3 octet utf-8.
183
522k
    bool tail3 = false; // potential overlong 4 octet
184
522k
    bool needs_escaping = false;
185
4.36M
    for (auto const& it: name) {
186
4.36M
        auto c = static_cast<unsigned char>(it);
187
4.36M
        if (tail) {
188
10.7k
            if ((c & 0xc0) != 0x80) {
189
5.55k
                return {false, false};
190
5.55k
            }
191
5.23k
            if (tail2) {
192
524
                if ((c & 0xe0) == 0x80) {
193
220
                    return {false, false};
194
220
                }
195
304
                tail2 = false;
196
4.70k
            } else if (tail3) {
197
150
                if ((c & 0xf0) == 0x80) {
198
80
                    return {false, false};
199
80
                }
200
70
                tail3 = false;
201
70
            }
202
4.93k
            tail--;
203
4.35M
        } else if (c < 0x80) {
204
4.32M
            if (!needs_escaping) {
205
3.01M
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
206
3.01M
            }
207
4.32M
        } else if ((c & 0xe0) == 0xc0) {
208
4.96k
            if ((c & 0xfe) == 0xc0) {
209
145
                return {false, false};
210
145
            }
211
4.81k
            tail = 1;
212
19.7k
        } else if ((c & 0xf0) == 0xe0) {
213
3.70k
            tail2 = (c == 0xe0);
214
3.70k
            tail = 2;
215
16.0k
        } else if ((c & 0xf8) == 0xf0) {
216
1.37k
            tail3 = (c == 0xf0);
217
1.37k
            tail = 3;
218
14.6k
        } else {
219
14.6k
            return {false, false};
220
14.6k
        }
221
4.36M
    }
222
501k
    return {tail == 0, !needs_escaping};
223
522k
}
224
225
std::string
226
Name::normalize(std::string const& name)
227
76.7k
{
228
76.7k
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
76.7k
    std::string result;
232
76.7k
    result += name.at(0);
233
139M
    for (size_t i = 1; i < name.length(); ++i) {
234
139M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
139M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
4.64M
            result += "#";
239
134M
        } else if (
240
134M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
134M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
134M
            result += util::hex_encode_char(ch);
243
134M
        } else {
244
578k
            result += ch;
245
578k
        }
246
139M
    }
247
76.7k
    return result;
248
76.7k
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
663k
{
253
663k
    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
66.5k
    case ::ot_null:
260
66.5k
        return QPDFObject::create<QPDF_Null>();
261
1.10k
    case ::ot_boolean:
262
1.10k
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
118k
    case ::ot_integer:
264
118k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
50.0k
    case ::ot_real:
266
50.0k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
129k
    case ::ot_string:
268
129k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
215k
    case ::ot_name:
270
215k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
33.2k
    case ::ot_array:
272
33.2k
        {
273
33.2k
            auto const& a = std::get<QPDF_Array>(obj->value);
274
33.2k
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
33.2k
            } else {
277
33.2k
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
33.2k
                if (a.sp) {
279
475
                    QPDF_Array result;
280
475
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
475
                    result.sp->size = a.sp->size;
282
9.03k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
9.03k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
9.03k
                    }
285
475
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
32.8k
                } else {
287
32.8k
                    std::vector<QPDFObjectHandle> result;
288
32.8k
                    result.reserve(a.elements.size());
289
975k
                    for (auto const& element: a.elements) {
290
975k
                        result.emplace_back(
291
975k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
975k
                    }
293
32.8k
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
32.8k
                }
295
33.2k
            }
296
33.2k
        }
297
49.9k
    case ::ot_dictionary:
298
49.9k
        {
299
49.9k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
49.9k
            if (shallow) {
301
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
49.9k
            } else {
303
49.9k
                std::map<std::string, QPDFObjectHandle> new_items;
304
257k
                for (auto const& [key, val]: d.items) {
305
257k
                    new_items[key] = val.indirect() ? val : val.copy();
306
257k
                }
307
49.9k
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
49.9k
            }
309
49.9k
        }
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
663k
    }
327
0
    return {}; // unreachable
328
663k
}
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
136k
{
475
136k
    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
21.5k
    case ::ot_null:
483
21.5k
        return "null";
484
285
    case ::ot_boolean:
485
285
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
5.96k
    case ::ot_integer:
487
5.96k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
1.79k
    case ::ot_real:
489
1.79k
        return std::get<QPDF_Real>(obj->value).val;
490
52.8k
    case ::ot_string:
491
52.8k
        return std::get<QPDF_String>(obj->value).unparse(false);
492
54.3k
    case ::ot_name:
493
54.3k
        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
136k
    }
545
0
    return {}; // unreachable
546
136k
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
670k
{
551
670k
    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
16.6k
    case ::ot_null:
557
16.6k
    case ::ot_operator:
558
16.6k
    case ::ot_inlineimage:
559
16.6k
        p << "null";
560
16.6k
        break;
561
3.43k
    case ::ot_boolean:
562
3.43k
        p << std::get<QPDF_Bool>(obj->value).val;
563
3.43k
        break;
564
128k
    case ::ot_integer:
565
128k
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
128k
        break;
567
71.9k
    case ::ot_real:
568
71.9k
        {
569
71.9k
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
71.9k
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
71.9k
            } else if (val.at(0) == '.') {
574
970
                p << "0" << val;
575
70.9k
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
343
                p << "-0." << val.substr(2);
577
70.6k
            } else {
578
70.6k
                p << val;
579
70.6k
            }
580
71.9k
            if (val.back() == '.') {
581
1.69k
                p << "0";
582
1.69k
            }
583
71.9k
        }
584
71.9k
        break;
585
115k
    case ::ot_string:
586
115k
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
587
115k
        break;
588
205k
    case ::ot_name:
589
205k
        {
590
205k
            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
205k
            if (json_version == 1) {
594
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
595
205k
            } else {
596
205k
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
597
194k
                    if (res.second) {
598
190k
                        p << "\"" << n.name << "\"";
599
190k
                    } else {
600
3.74k
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
601
3.74k
                    }
602
194k
                } else {
603
10.7k
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
10.7k
                }
605
205k
            }
606
205k
        }
607
205k
        break;
608
51.1k
    case ::ot_array:
609
51.1k
        {
610
51.1k
            auto const& a = std::get<QPDF_Array>(obj->value);
611
51.1k
            p.writeStart('[');
612
51.1k
            if (a.sp) {
613
345
                size_t next = 0;
614
4.93k
                for (auto& [key, value]: a.sp->elements) {
615
35.2k
                    for (size_t j = next; j < key; ++j) {
616
30.2k
                        p.writeNext() << "null";
617
30.2k
                    }
618
4.93k
                    p.writeNext();
619
4.93k
                    auto item_og = value.id_gen();
620
4.93k
                    if (item_og.isIndirect()) {
621
1.14k
                        p << "\"" << item_og.unparse(' ') << " R\"";
622
3.79k
                    } else {
623
3.79k
                        value.write_json(json_version, p);
624
3.79k
                    }
625
4.93k
                    next = key + 1;
626
4.93k
                }
627
5.55k
                for (size_t j = next; j < a.sp->size; ++j) {
628
5.21k
                    p.writeNext() << "null";
629
5.21k
                }
630
50.8k
            } else {
631
1.14M
                for (auto const& item: a.elements) {
632
1.14M
                    p.writeNext();
633
1.14M
                    auto item_og = item.id_gen();
634
1.14M
                    if (item_og.isIndirect()) {
635
788k
                        p << "\"" << item_og.unparse(' ') << " R\"";
636
788k
                    } else {
637
359k
                        item.write_json(json_version, p);
638
359k
                    }
639
1.14M
                }
640
50.8k
            }
641
51.1k
            p.writeEnd(']');
642
51.1k
        }
643
51.1k
        break;
644
77.6k
    case ::ot_dictionary:
645
77.6k
        {
646
77.6k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
647
77.6k
            p.writeStart('{');
648
354k
            for (auto& iter: d.items) {
649
354k
                if (!iter.second.null()) {
650
317k
                    p.writeNext();
651
317k
                    if (json_version == 1) {
652
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
653
0
                          << "\": ";
654
317k
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
655
305k
                        if (res.second) {
656
302k
                            p << "\"" << iter.first << "\": ";
657
302k
                        } else {
658
3.82k
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
659
3.82k
                        }
660
305k
                    } else {
661
11.6k
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
11.6k
                          << "\": ";
663
11.6k
                    }
664
317k
                    iter.second.writeJSON(json_version, p);
665
317k
                }
666
354k
            }
667
77.6k
            p.writeEnd('}');
668
77.6k
        }
669
77.6k
        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
670k
    }
679
670k
}
680
681
void
682
BaseHandle::disconnect(bool only_direct)
683
3.38M
{
684
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
685
3.38M
    if (only_direct && indirect()) {
686
410k
        return;
687
410k
    }
688
689
2.97M
    switch (raw_type_code()) {
690
144k
    case ::ot_array:
691
144k
        {
692
144k
            auto& a = std::get<QPDF_Array>(obj->value);
693
144k
            if (a.sp) {
694
19.6k
                for (auto& item: a.sp->elements) {
695
19.6k
                    item.second.disconnect();
696
19.6k
                }
697
143k
            } else {
698
1.98M
                for (auto& oh: a.elements) {
699
1.98M
                    oh.disconnect();
700
1.98M
                }
701
143k
            }
702
144k
        }
703
144k
        break;
704
247k
    case ::ot_dictionary:
705
1.01M
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
706
1.01M
            iter.second.disconnect();
707
1.01M
        }
708
247k
        break;
709
36.3k
    case ::ot_stream:
710
36.3k
        {
711
36.3k
            auto& s = std::get<QPDF_Stream>(obj->value);
712
36.3k
            s.m->stream_provider = nullptr;
713
36.3k
            s.m->stream_dict.disconnect();
714
36.3k
        }
715
36.3k
        break;
716
0
    case ::ot_uninitialized:
717
0
        return;
718
2.54M
    default:
719
2.54M
        break;
720
2.97M
    }
721
2.97M
    obj->qpdf = nullptr;
722
2.97M
    obj->og = QPDFObjGen();
723
2.97M
}
724
725
bool
726
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
727
7.55k
{
728
7.55k
    return obj == rhs.obj;
729
7.55k
}
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
13.0k
{
740
13.0k
    static constexpr std::array<char const*, 16> tn{
741
13.0k
        "uninitialized",
742
13.0k
        "reserved",
743
13.0k
        "null",
744
13.0k
        "boolean",
745
13.0k
        "integer",
746
13.0k
        "real",
747
13.0k
        "string",
748
13.0k
        "name",
749
13.0k
        "array",
750
13.0k
        "dictionary",
751
13.0k
        "stream",
752
13.0k
        "operator",
753
13.0k
        "inline-image",
754
13.0k
        "unresolved",
755
13.0k
        "destroyed",
756
13.0k
        "reference"};
757
13.0k
    return tn[type_code()];
758
13.0k
}
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
139k
{
775
139k
    return type_code() == ::ot_boolean;
776
139k
}
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
84.8k
{
789
84.8k
    return type_code() == ::ot_null;
790
84.8k
}
791
792
bool
793
QPDFObjectHandle::isInteger() const
794
596k
{
795
596k
    return type_code() == ::ot_integer;
796
596k
}
797
798
bool
799
QPDFObjectHandle::isReal() const
800
320k
{
801
320k
    return type_code() == ::ot_real;
802
320k
}
803
804
bool
805
QPDFObjectHandle::isNumber() const
806
257k
{
807
257k
    return (isInteger() || isReal());
808
257k
}
809
810
double
811
QPDFObjectHandle::getNumericValue() const
812
94.9k
{
813
94.9k
    if (isInteger()) {
814
15.6k
        return static_cast<double>(getIntValue());
815
79.3k
    } else if (isReal()) {
816
79.3k
        return atof(getRealValue().c_str());
817
79.3k
    } else {
818
0
        typeWarning("number", "returning 0");
819
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
820
0
        return 0;
821
0
    }
822
94.9k
}
823
824
bool
825
QPDFObjectHandle::getValueAsNumber(double& value) const
826
95.1k
{
827
95.1k
    if (!isNumber()) {
828
162
        return false;
829
162
    }
830
94.9k
    value = getNumericValue();
831
94.9k
    return true;
832
95.1k
}
833
834
bool
835
QPDFObjectHandle::isName() const
836
140k
{
837
140k
    return type_code() == ::ot_name;
838
140k
}
839
840
bool
841
QPDFObjectHandle::isString() const
842
78.3k
{
843
78.3k
    return type_code() == ::ot_string;
844
78.3k
}
845
846
bool
847
QPDFObjectHandle::isOperator() const
848
13.7M
{
849
13.7M
    return type_code() == ::ot_operator;
850
13.7M
}
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
114k
{
861
114k
    return type_code() == ::ot_array;
862
114k
}
863
864
bool
865
QPDFObjectHandle::isDictionary() const
866
1.45M
{
867
1.45M
    return type_code() == ::ot_dictionary;
868
1.45M
}
869
870
bool
871
QPDFObjectHandle::isStream() const
872
176k
{
873
176k
    return type_code() == ::ot_stream;
874
176k
}
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
8.14k
{
891
8.14k
    return Name(*this) == name;
892
8.14k
}
893
894
bool
895
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
896
547k
{
897
547k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
898
105k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
899
547k
}
900
901
bool
902
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
903
189k
{
904
189k
    if (auto stream = as_stream()) {
905
31.7k
        return stream && (type.empty() || stream.Type() == type) &&
906
21.7k
            (subtype.empty() || stream.Subtype() == subtype);
907
31.7k
    }
908
157k
    return false;
909
189k
}
910
911
// Bool accessors
912
913
bool
914
QPDFObjectHandle::getBoolValue() const
915
2.73k
{
916
2.73k
    if (auto boolean = as<QPDF_Bool>()) {
917
2.73k
        return boolean->val;
918
2.73k
    } else {
919
0
        typeWarning("boolean", "returning false");
920
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
921
0
        return false;
922
0
    }
923
2.73k
}
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
25.3k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
939
25.3k
{
940
25.3k
}
941
942
QPDFObjectHandle
943
QPDFObjectHandle::newInteger(long long value)
944
3.74k
{
945
3.74k
    return {QPDFObject::create<QPDF_Integer>(value)};
946
3.74k
}
947
948
int64_t
949
Integer::value() const
950
160k
{
951
160k
    auto* i = as<QPDF_Integer>();
952
160k
    if (!i) {
953
1.10k
        throw invalid_error("Integer");
954
1.10k
    }
955
159k
    return i->val;
956
160k
}
957
958
long long
959
QPDFObjectHandle::getIntValue() const
960
57.4k
{
961
57.4k
    if (auto const integer = Integer(*this)) {
962
57.4k
        return integer;
963
57.4k
    } else {
964
0
        typeWarning("integer", "returning 0");
965
0
        return 0;
966
0
    }
967
57.4k
}
968
969
bool
970
QPDFObjectHandle::getValueAsInt(long long& value) const
971
7.86k
{
972
7.86k
    if (auto const integer = Integer(*this)) {
973
7.81k
        value = integer;
974
7.81k
        return true;
975
7.81k
    }
976
48
    return false;
977
7.86k
}
978
979
int
980
QPDFObjectHandle::getIntValueAsInt() const
981
46.6k
{
982
46.6k
    try {
983
46.6k
        return Integer(*this).value<int>();
984
46.6k
    } catch (std::invalid_argument&) {
985
8
        typeWarning("integer", "returning 0");
986
8
        return 0;
987
8
    }
988
46.6k
}
989
990
bool
991
QPDFObjectHandle::getValueAsInt(int& value) const
992
1.17k
{
993
1.17k
    if (!isInteger()) {
994
18
        return false;
995
18
    }
996
1.15k
    value = getIntValueAsInt();
997
1.15k
    return true;
998
1.17k
}
999
1000
unsigned long long
1001
QPDFObjectHandle::getUIntValue() const
1002
20.9k
{
1003
20.9k
    try {
1004
20.9k
        return Integer(*this).value<unsigned long long>();
1005
20.9k
    } catch (std::invalid_argument&) {
1006
746
        typeWarning("integer", "returning 0");
1007
746
        return 0;
1008
746
    }
1009
20.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.96k
{
1024
6.96k
    try {
1025
6.96k
        return Integer(*this).value<unsigned int>();
1026
6.96k
    } catch (std::invalid_argument&) {
1027
348
        typeWarning("integer", "returning 0");
1028
348
        return 0;
1029
348
    }
1030
6.96k
}
1031
1032
bool
1033
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1034
1.29k
{
1035
1.29k
    if (!isInteger()) {
1036
118
        return false;
1037
118
    }
1038
1.17k
    value = getUIntValueAsUInt();
1039
1.17k
    return true;
1040
1.29k
}
1041
1042
// Real accessors
1043
1044
std::string
1045
QPDFObjectHandle::getRealValue() const
1046
79.3k
{
1047
79.3k
    if (auto* real = as<QPDF_Real>()) {
1048
79.3k
        return real->val;
1049
79.3k
    }
1050
0
    typeWarning("real", "returning 0.0");
1051
0
    return "0.0";
1052
79.3k
}
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
6.41k
{
1069
6.41k
    return {QPDFObject::create<QPDF_Name>(name)};
1070
6.41k
}
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
36.9k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1079
36.9k
{
1080
36.9k
}
1081
1082
std::string const&
1083
Name::value() const
1084
267k
{
1085
267k
    auto* n = as<QPDF_Name>();
1086
267k
    if (!n) {
1087
0
        throw invalid_error("Name");
1088
0
    }
1089
267k
    return n->name;
1090
267k
}
1091
1092
std::string
1093
QPDFObjectHandle::getName() const
1094
28.8k
{
1095
28.8k
    if (auto* name = as<QPDF_Name>()) {
1096
26.7k
        return name->name;
1097
26.7k
    }
1098
2.06k
    typeWarning("name", "returning dummy name");
1099
2.06k
    return "/QPDFFakeName";
1100
28.8k
}
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
18.8k
{
1117
18.8k
    return {QPDFObject::create<QPDF_String>(str)};
1118
18.8k
}
1119
1120
QPDFObjectHandle
1121
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1122
1.27k
{
1123
1.27k
    return {String::utf16(utf8_str).obj_sp()};
1124
1.27k
}
1125
1126
String::String(std::string const& str) :
1127
771
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1128
771
{
1129
771
}
1130
1131
String::String(std::string&& str) :
1132
501
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1133
501
{
1134
501
}
1135
1136
String
1137
String::utf16(std::string const& utf8_str)
1138
1.27k
{
1139
1.27k
    std::string result;
1140
1.27k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1141
771
        return String(result);
1142
771
    }
1143
501
    return String(QUtil::utf8_to_utf16(utf8_str));
1144
1.27k
}
1145
1146
std::string const&
1147
String::value() const
1148
5.22k
{
1149
5.22k
    auto* s = as<QPDF_String>();
1150
5.22k
    if (!s) {
1151
0
        throw invalid_error("String");
1152
0
    }
1153
5.22k
    return s->val;
1154
5.22k
}
1155
1156
std::string
1157
String::utf8_value() const
1158
489k
{
1159
489k
    auto* s = as<QPDF_String>();
1160
489k
    if (!s) {
1161
0
        throw invalid_error("String");
1162
0
    }
1163
489k
    if (util::is_utf16(s->val)) {
1164
35.0k
        return QUtil::utf16_to_utf8(s->val);
1165
35.0k
    }
1166
453k
    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.77k
        return s->val.substr(3);
1170
1.77k
    }
1171
452k
    return QUtil::pdf_doc_to_utf8(s->val);
1172
453k
}
1173
1174
std::string
1175
QPDFObjectHandle::getStringValue() const
1176
5.22k
{
1177
5.22k
    try {
1178
5.22k
        return String(obj).value();
1179
5.22k
    } catch (std::invalid_argument&) {
1180
0
        typeWarning("string", "returning empty string");
1181
0
        return {};
1182
0
    }
1183
5.22k
}
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
447k
{
1199
447k
    try {
1200
447k
        return String(obj).utf8_value();
1201
447k
    } catch (std::invalid_argument&) {
1202
0
        typeWarning("string", "returning empty string");
1203
0
        return {};
1204
0
    }
1205
447k
}
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
13.5M
{
1223
13.5M
    if (auto* op = as<QPDF_Operator>()) {
1224
13.5M
        return op->val;
1225
13.5M
    }
1226
0
    typeWarning("operator", "returning fake value");
1227
0
    return "QPDFFAKE";
1228
13.5M
}
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
5.02k
{
1265
5.02k
    return *this;
1266
5.02k
}
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.85k
{
1281
3.85k
    if (isNameAndEquals(value)) {
1282
1.88k
        return true;
1283
1.96k
    } else if (isArray()) {
1284
4.23k
        for (auto& item: getArrayAsVector()) {
1285
4.23k
            if (item.isNameAndEquals(value)) {
1286
10
                return true;
1287
10
            }
1288
4.23k
        }
1289
648
    }
1290
1.95k
    return false;
1291
3.85k
}
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
18.4k
{
1309
18.4k
    if (!(isDictionary() && other.isDictionary())) {
1310
5.69k
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1311
5.69k
        return;
1312
5.69k
    }
1313
1314
12.7k
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1315
12.7k
                              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
28.6k
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1326
28.6k
        auto other_val = value1;
1327
28.6k
        if (hasKey(rtype)) {
1328
15.1k
            QPDFObjectHandle this_val = getKey(rtype);
1329
15.1k
            if (this_val.isDictionary() && other_val.isDictionary()) {
1330
8.95k
                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.55k
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1335
1.55k
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1336
1.55k
                }
1337
8.95k
                std::map<QPDFObjGen, std::string> og_to_name;
1338
8.95k
                std::set<std::string> rnames;
1339
8.95k
                int min_suffix = 1;
1340
8.95k
                bool initialized_maps = false;
1341
36.4k
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1342
36.4k
                    QPDFObjectHandle rval = value2;
1343
36.4k
                    if (!this_val.hasKey(key)) {
1344
22.9k
                        if (!rval.isIndirect()) {
1345
12.6k
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1346
12.6k
                            rval = rval.shallowCopy();
1347
12.6k
                        }
1348
22.9k
                        this_val.replaceKey(key, rval);
1349
22.9k
                    } 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
36.4k
                }
1371
8.95k
            } else if (this_val.isArray() && other_val.isArray()) {
1372
2.51k
                std::set<std::string> scalars;
1373
73.2k
                for (auto this_item: this_val.aitems()) {
1374
73.2k
                    if (this_item.isScalar()) {
1375
72.0k
                        scalars.insert(this_item.unparse());
1376
72.0k
                    }
1377
73.2k
                }
1378
50.0k
                for (auto other_item: other_val.aitems()) {
1379
50.0k
                    if (other_item.isScalar()) {
1380
48.1k
                        if (!scalars.contains(other_item.unparse())) {
1381
14.2k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1382
14.2k
                            this_val.appendItem(other_item);
1383
33.8k
                        } else {
1384
33.8k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1385
33.8k
                        }
1386
48.1k
                    }
1387
50.0k
                }
1388
2.51k
            }
1389
15.1k
        } else {
1390
13.5k
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1391
13.5k
            replaceKey(rtype, other_val.shallowCopy());
1392
13.5k
        }
1393
28.6k
    }
1394
12.7k
}
1395
1396
std::set<std::string>
1397
QPDFObjectHandle::getResourceNames() const
1398
10.1k
{
1399
    // Return second-level dictionary keys
1400
10.1k
    std::set<std::string> result;
1401
27.2k
    for (auto const& item: as_dictionary(strict)) {
1402
141k
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1403
141k
            if (!val2.null()) {
1404
108k
                result.insert(key2);
1405
108k
            }
1406
141k
        }
1407
27.2k
    }
1408
10.1k
    return result;
1409
10.1k
}
1410
1411
std::string
1412
QPDFObjectHandle::getUniqueResourceName(
1413
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1414
10.1k
{
1415
10.1k
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1416
10.1k
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1417
10.1k
    while (min_suffix <= max_suffix) {
1418
10.1k
        std::string candidate = prefix + std::to_string(min_suffix);
1419
10.1k
        if (!names.contains(candidate)) {
1420
10.1k
            return candidate;
1421
10.1k
        }
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
10.1k
}
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
32.5k
{
1447
32.5k
    all_description = description;
1448
32.5k
    std::vector<QPDFObjectHandle> result;
1449
32.5k
    if (auto array = as_array(strict)) {
1450
7.55k
        int n_items = static_cast<int>(array.size());
1451
65.4k
        for (int i = 0; i < n_items; ++i) {
1452
57.8k
            QPDFObjectHandle item = array[i];
1453
57.8k
            if (item.isStream()) {
1454
23.2k
                result.emplace_back(item);
1455
34.5k
            } else {
1456
34.5k
                item.warn(
1457
34.5k
                    {qpdf_e_damaged_pdf,
1458
34.5k
                     "",
1459
34.5k
                     description + ": item index " + std::to_string(i) + " (from 0)",
1460
34.5k
                     0,
1461
34.5k
                     "ignoring non-stream in an array of streams"});
1462
34.5k
            }
1463
57.8k
        }
1464
24.9k
    } else if (isStream()) {
1465
6.77k
        result.emplace_back(*this);
1466
18.1k
    } else if (!null()) {
1467
665
        warn(
1468
665
            {qpdf_e_damaged_pdf,
1469
665
             "",
1470
665
             description,
1471
665
             0,
1472
665
             " object is supposed to be a stream or an array of streams but is neither"});
1473
665
    }
1474
1475
32.5k
    bool first = true;
1476
32.5k
    for (auto const& item: result) {
1477
30.0k
        if (first) {
1478
13.3k
            first = false;
1479
16.7k
        } else {
1480
16.7k
            all_description += ",";
1481
16.7k
        }
1482
30.0k
        all_description += " stream " + item.getObjGen().unparse(' ');
1483
30.0k
    }
1484
1485
32.5k
    return result;
1486
32.5k
}
1487
1488
std::vector<QPDFObjectHandle>
1489
QPDFObjectHandle::getPageContents()
1490
6.18k
{
1491
6.18k
    std::string description = "page object " + getObjGen().unparse(' ');
1492
6.18k
    std::string all_description;
1493
6.18k
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1494
6.18k
}
1495
1496
void
1497
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1498
6.18k
{
1499
6.18k
    new_contents.assertStream();
1500
1501
6.18k
    std::vector<QPDFObjectHandle> content_streams;
1502
6.18k
    if (first) {
1503
3.09k
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1504
3.09k
        content_streams.push_back(new_contents);
1505
3.09k
    }
1506
6.18k
    for (auto const& iter: getPageContents()) {
1507
3.60k
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1508
3.60k
        content_streams.push_back(iter);
1509
3.60k
    }
1510
6.18k
    if (!first) {
1511
3.08k
        content_streams.push_back(new_contents);
1512
3.08k
    }
1513
1514
6.18k
    replaceKey("/Contents", newArray(content_streams));
1515
6.18k
}
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
21.9k
{
1553
21.9k
    QPDFObjectHandle contents = getKey("/Contents");
1554
21.9k
    if (contents.isStream()) {
1555
2.28k
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1556
2.28k
        return;
1557
19.6k
    } 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
15.1k
        return;
1561
15.1k
    }
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
4.42k
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1567
1568
4.42k
    QPDFObjectHandle new_contents = newStream(&qpdf);
1569
4.42k
    replaceKey("/Contents", new_contents);
1570
1571
4.42k
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1572
4.42k
    new_contents.replaceStreamData(provider, newNull(), newNull());
1573
4.42k
}
1574
1575
std::string
1576
QPDFObjectHandle::unparse() const
1577
139k
{
1578
139k
    if (isIndirect()) {
1579
2.31k
        return getObjGen().unparse(' ') + " R";
1580
136k
    } else {
1581
136k
        return unparseResolved();
1582
136k
    }
1583
139k
}
1584
1585
std::string
1586
QPDFObjectHandle::unparseResolved() const
1587
136k
{
1588
136k
    if (!obj) {
1589
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1590
0
    }
1591
136k
    return BaseHandle::unparse();
1592
136k
}
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
19.2k
{
1607
19.2k
    if ((!dereference_indirect) && isIndirect()) {
1608
0
        return JSON::makeString(unparse());
1609
19.2k
    } else if (!obj) {
1610
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1611
19.2k
    } else {
1612
19.2k
        Pl_Buffer p{"json"};
1613
19.2k
        JSON::Writer jw{&p, 0};
1614
19.2k
        writeJSON(json_version, jw, dereference_indirect);
1615
19.2k
        p.finish();
1616
19.2k
        return JSON::parse(p.getString());
1617
19.2k
    }
1618
19.2k
}
1619
1620
void
1621
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1622
336k
{
1623
336k
    if (!dereference_indirect && isIndirect()) {
1624
29.7k
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1625
306k
    } else if (!obj) {
1626
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1627
306k
    } else {
1628
306k
        write_json(json_version, p);
1629
306k
    }
1630
336k
}
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
26.3k
{
1691
26.3k
    bool need_newline = false;
1692
26.3k
    std::string buffer;
1693
26.3k
    pl::String buf(buffer);
1694
26.3k
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1695
25.8k
        if (need_newline) {
1696
7.73k
            buf.writeCStr("\n");
1697
7.73k
        }
1698
25.8k
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1699
788
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1700
788
            throw QPDFExc(
1701
788
                qpdf_e_damaged_pdf,
1702
788
                "content stream",
1703
788
                "content stream object " + stream.getObjGen().unparse(' '),
1704
788
                0,
1705
788
                "errors while decoding content stream");
1706
788
        }
1707
25.0k
        need_newline = buffer.empty() || buffer.back() != '\n';
1708
25.0k
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1709
25.0k
        p->writeString(buffer);
1710
25.0k
        buffer.clear();
1711
25.0k
    }
1712
25.5k
    p->finish();
1713
25.5k
}
1714
1715
void
1716
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1717
21.8k
{
1718
21.8k
    std::string description = "page object " + getObjGen().unparse(' ');
1719
21.8k
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1720
21.8k
}
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
21.8k
{
1755
21.8k
    std::string stream_data;
1756
21.8k
    pl::String buf(stream_data);
1757
21.8k
    std::string all_description;
1758
21.8k
    pipeContentStreams(&buf, description, all_description);
1759
21.8k
    if (callbacks) {
1760
0
        callbacks->contentSize(stream_data.size());
1761
0
    }
1762
21.8k
    try {
1763
21.8k
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1764
21.8k
    } catch (TerminateParsing&) {
1765
0
        return;
1766
0
    }
1767
20.3k
    if (callbacks) {
1768
0
        callbacks->handleEOF();
1769
0
    }
1770
20.3k
}
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
20.3k
{
1779
20.3k
    size_t stream_length = stream_data.size();
1780
20.3k
    auto input = is::OffsetBuffer(description, stream_data);
1781
20.3k
    Tokenizer tokenizer;
1782
20.3k
    tokenizer.allowEOF();
1783
20.3k
    auto max_bad_count = Limits::parser_max_errors();
1784
20.3k
    auto sp_description = Parser::make_description(description, "content");
1785
13.7M
    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
13.7M
        tokenizer.nextToken(input, "content", true);
1790
13.7M
        qpdf_offset_t offset = input.getLastOffset();
1791
13.7M
        input.seek(offset, SEEK_SET);
1792
13.7M
        auto [obj, empty] = Parser::parse_content(input, sp_description, tokenizer, context);
1793
13.7M
        if (empty) {
1794
            // EOF
1795
3.99k
            return;
1796
3.99k
        }
1797
13.7M
        if (!obj) {
1798
4.75k
            if (max_bad_count && --max_bad_count == 0) {
1799
183
                Limits::error();
1800
183
                warn(
1801
183
                    context,
1802
183
                    {qpdf_e_damaged_pdf,
1803
183
                     description,
1804
183
                     "stream data",
1805
183
                     input.tell(),
1806
183
                     "limits error(parser-max-errors): too many errors during content stream "
1807
183
                     "parsing"});
1808
183
                return;
1809
183
            }
1810
4.56k
            obj = newNull();
1811
4.56k
        }
1812
13.7M
        size_t length = QIntC::to_size(input.tell() - offset);
1813
13.7M
        if (callbacks) {
1814
0
            callbacks->handleObject(obj, QIntC::to_size(offset), length);
1815
0
        }
1816
13.7M
        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.88k
            char ch;
1820
1.88k
            input.read(&ch, 1);
1821
1.88k
            tokenizer.expectInlineImage(input);
1822
1.88k
            tokenizer.nextToken(input, description);
1823
1.88k
            offset = input.getLastOffset();
1824
1.88k
            length = QIntC::to_size(input.tell() - offset);
1825
1.88k
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1826
77
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1827
77
                warn(
1828
77
                    context,
1829
77
                    {qpdf_e_damaged_pdf,
1830
77
                     description,
1831
77
                     "stream data",
1832
77
                     input.tell(),
1833
77
                     "EOF found while reading inline image"});
1834
1.80k
            } else {
1835
1.80k
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1836
1.80k
                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.80k
            }
1843
1.88k
        }
1844
13.7M
    }
1845
20.3k
}
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
63.5k
{
1887
63.5k
    return {QPDFObject::create<QPDF_Null>()};
1888
63.5k
}
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
52.4k
{
1899
52.4k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1900
52.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
19.7k
{
1923
19.7k
    return {QPDFObject::create<QPDF_Array>(items)};
1924
19.7k
}
1925
1926
QPDFObjectHandle
1927
QPDFObjectHandle::newArray(Rectangle const& rect)
1928
13.1k
{
1929
13.1k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1930
13.1k
}
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.71k
{
1959
3.71k
    return newArray(rect);
1960
3.71k
}
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
15.8k
{
1977
15.8k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1978
15.8k
}
1979
1980
QPDFObjectHandle
1981
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1982
15.8k
{
1983
15.8k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1984
15.8k
}
1985
1986
QPDFObjectHandle
1987
QPDFObjectHandle::newStream(QPDF* qpdf)
1988
4.42k
{
1989
4.42k
    if (qpdf == nullptr) {
1990
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1991
0
    }
1992
4.42k
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1993
4.42k
    return qpdf->newStream();
1994
4.42k
}
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
166k
{
2028
166k
    return obj ? obj->getDescription() : ""s;
2029
166k
}
2030
2031
void
2032
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
2033
18.4k
{
2034
18.4k
    if (obj) {
2035
18.4k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
2036
18.4k
        obj->setDescription(owning_qpdf, descr);
2037
18.4k
    }
2038
18.4k
}
2039
2040
bool
2041
QPDFObjectHandle::hasObjectDescription() const
2042
46.5k
{
2043
46.5k
    return obj && obj->hasDescription();
2044
46.5k
}
2045
2046
QPDFObjectHandle
2047
QPDFObjectHandle::shallowCopy()
2048
42.5k
{
2049
42.5k
    if (!obj) {
2050
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2051
0
    }
2052
42.5k
    return {copy()};
2053
42.5k
}
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
1.10k
{
2129
1.10k
    return std::invalid_argument(method + " operation attempted on invalid object");
2130
1.10k
}
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
13.0k
{
2141
13.0k
    return {
2142
13.0k
        qpdf_e_object,
2143
13.0k
        "",
2144
13.0k
        description(),
2145
13.0k
        0,
2146
13.0k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2147
13.0k
            message};
2148
13.0k
}
2149
2150
void
2151
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2152
13.0k
{
2153
13.0k
    if (!obj) {
2154
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2155
0
    }
2156
13.0k
    warn(type_error(expected_type, message));
2157
13.0k
}
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
6.18k
{
2174
6.18k
    if (!istype) {
2175
0
        throw type_error(type_name);
2176
0
    }
2177
6.18k
}
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
6.18k
{
2242
6.18k
    assertType("stream", isStream());
2243
6.18k
}
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
84.4k
{
2297
84.4k
    return isStreamOfType("", "/Form");
2298
84.4k
}
2299
2300
bool
2301
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2302
11.1k
{
2303
11.1k
    return (
2304
11.1k
        isStreamOfType("", "/Image") &&
2305
823
        ((!exclude_imagemask) ||
2306
823
         (!(getDict().getKey("/ImageMask").isBool() &&
2307
66
            getDict().getKey("/ImageMask").getBoolValue()))));
2308
11.1k
}
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
260
{
2321
260
    if (!qpdf) {
2322
0
        throw std::move(e);
2323
0
    }
2324
260
    qpdf->warn(std::move(e));
2325
260
}
2326
2327
void
2328
BaseHandle::warn(QPDFExc&& e) const
2329
201k
{
2330
201k
    if (!qpdf()) {
2331
931
        throw std::move(e);
2332
931
    }
2333
200k
    qpdf()->warn(std::move(e));
2334
200k
}
2335
2336
void
2337
BaseHandle::warn(std::string const& warning) const
2338
173k
{
2339
173k
    if (qpdf()) {
2340
153k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2341
153k
    } else {
2342
20.4k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2343
20.4k
    }
2344
173k
}
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
5.02k
    oh(oh)
2433
5.02k
{
2434
5.02k
}
2435
2436
QPDFObjectHandle::QPDFArrayItems::iterator&
2437
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2438
123k
{
2439
123k
    if (!m->is_end) {
2440
123k
        ++m->item_number;
2441
123k
        updateIValue();
2442
123k
    }
2443
123k
    return *this;
2444
123k
}
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
123k
{
2459
123k
    updateIValue();
2460
123k
    return ivalue;
2461
123k
}
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
128k
{
2473
128k
    return (m->item_number == other.m->item_number);
2474
128k
}
2475
2476
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2477
10.0k
    m(new Members(oh, for_begin))
2478
10.0k
{
2479
10.0k
    updateIValue();
2480
10.0k
}
2481
2482
void
2483
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2484
256k
{
2485
256k
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2486
256k
    if (m->is_end) {
2487
10.0k
        ivalue = QPDFObjectHandle();
2488
246k
    } else {
2489
246k
        ivalue = m->oh.getArrayItem(m->item_number);
2490
246k
    }
2491
256k
}
2492
2493
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2494
10.0k
    oh(oh)
2495
10.0k
{
2496
10.0k
    item_number = for_begin ? 0 : oh.getArrayNItems();
2497
10.0k
}
2498
2499
QPDFObjectHandle::QPDFArrayItems::iterator
2500
QPDFObjectHandle::QPDFArrayItems::begin()
2501
5.02k
{
2502
5.02k
    return {oh, true};
2503
5.02k
}
2504
2505
QPDFObjectHandle::QPDFArrayItems::iterator
2506
QPDFObjectHandle::QPDFArrayItems::end()
2507
5.02k
{
2508
5.02k
    return {oh, false};
2509
5.02k
}
2510
2511
QPDFObjGen
2512
QPDFObjectHandle::getObjGen() const
2513
742k
{
2514
742k
    return obj ? obj->getObjGen() : QPDFObjGen();
2515
742k
}
2516
2517
int
2518
QPDFObjectHandle::getObjectID() const
2519
562k
{
2520
562k
    return getObjGen().getObj();
2521
562k
}
2522
2523
int
2524
QPDFObjectHandle::getGeneration() const
2525
0
{
2526
0
    return getObjGen().getGen();
2527
0
}
2528
2529
bool
2530
QPDFObjectHandle::isIndirect() const
2531
552k
{
2532
552k
    return getObjectID() != 0;
2533
552k
}
2534
2535
// Indirect object accessors
2536
QPDF*
2537
QPDFObjectHandle::getOwningQPDF() const
2538
366k
{
2539
366k
    return obj ? obj->getQPDF() : nullptr;
2540
366k
}
2541
2542
QPDF&
2543
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2544
4.42k
{
2545
4.42k
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2546
4.42k
        return *result;
2547
4.42k
    }
2548
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2549
4.42k
}
2550
2551
void
2552
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2553
15
{
2554
15
    if (obj) {
2555
15
        obj->setParsedOffset(offset);
2556
15
    }
2557
15
}
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
}