Coverage Report

Created: 2026-07-16 06:57

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