Coverage Report

Created: 2026-06-09 06:59

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
39.8k
{
35
39.8k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
39.8k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
19.9k
    supports_retry(supports_retry)
47
19.9k
{
48
19.9k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
19.9k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
19.9k
}
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
395k
{
228
395k
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
395k
    std::string result;
232
395k
    result += name.at(0);
233
10.1M
    for (size_t i = 1; i < name.length(); ++i) {
234
9.75M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
9.75M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
3.96k
            result += "#";
239
9.74M
        } else if (
240
9.74M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
4.98M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
4.76M
            result += util::hex_encode_char(ch);
243
4.97M
        } else {
244
4.97M
            result += ch;
245
4.97M
        }
246
9.75M
    }
247
395k
    return result;
248
395k
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
75.0k
{
253
75.0k
    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
6.74k
    case ::ot_null:
260
6.74k
        return QPDFObject::create<QPDF_Null>();
261
683
    case ::ot_boolean:
262
683
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
5.73k
    case ::ot_integer:
264
5.73k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
2.49k
    case ::ot_real:
266
2.49k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
9.14k
    case ::ot_string:
268
9.14k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
9.72k
    case ::ot_name:
270
9.72k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
262
    case ::ot_array:
272
262
        {
273
262
            auto const& a = std::get<QPDF_Array>(obj->value);
274
262
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
262
            } else {
277
262
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
262
                if (a.sp) {
279
37
                    QPDF_Array result;
280
37
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
37
                    result.sp->size = a.sp->size;
282
2.47k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
2.47k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
2.47k
                    }
285
37
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
225
                } else {
287
225
                    std::vector<QPDFObjectHandle> result;
288
225
                    result.reserve(a.elements.size());
289
1.64k
                    for (auto const& element: a.elements) {
290
1.64k
                        result.emplace_back(
291
1.64k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
1.64k
                    }
293
225
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
225
                }
295
262
            }
296
262
        }
297
40.2k
    case ::ot_dictionary:
298
40.2k
        {
299
40.2k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
40.2k
            if (shallow) {
301
39.3k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
39.3k
            } else {
303
865
                std::map<std::string, QPDFObjectHandle> new_items;
304
2.75k
                for (auto const& [key, val]: d.items) {
305
2.75k
                    new_items[key] = val.indirect() ? val : val.copy();
306
2.75k
                }
307
865
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
865
            }
309
40.2k
        }
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
75.0k
    }
327
0
    return {}; // unreachable
328
75.0k
}
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
380k
{
475
380k
    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
33.0k
    case ::ot_null:
483
33.0k
        return "null";
484
1.88k
    case ::ot_boolean:
485
1.88k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
155k
    case ::ot_integer:
487
155k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
53.6k
    case ::ot_real:
489
53.6k
        return std::get<QPDF_Real>(obj->value).val;
490
12.1k
    case ::ot_string:
491
12.1k
        return std::get<QPDF_String>(obj->value).unparse(false);
492
124k
    case ::ot_name:
493
124k
        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
380k
    }
545
0
    return {}; // unreachable
546
380k
}
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
1.55M
{
684
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
685
1.55M
    if (only_direct && indirect()) {
686
140k
        return;
687
140k
    }
688
689
1.41M
    switch (raw_type_code()) {
690
92.6k
    case ::ot_array:
691
92.6k
        {
692
92.6k
            auto& a = std::get<QPDF_Array>(obj->value);
693
92.6k
            if (a.sp) {
694
36.9k
                for (auto& item: a.sp->elements) {
695
36.9k
                    item.second.disconnect();
696
36.9k
                }
697
92.4k
            } else {
698
567k
                for (auto& oh: a.elements) {
699
567k
                    oh.disconnect();
700
567k
                }
701
92.4k
            }
702
92.6k
        }
703
92.6k
        break;
704
151k
    case ::ot_dictionary:
705
593k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
706
593k
            iter.second.disconnect();
707
593k
        }
708
151k
        break;
709
39.9k
    case ::ot_stream:
710
39.9k
        {
711
39.9k
            auto& s = std::get<QPDF_Stream>(obj->value);
712
39.9k
            s.m->stream_provider = nullptr;
713
39.9k
            s.m->stream_dict.disconnect();
714
39.9k
        }
715
39.9k
        break;
716
0
    case ::ot_uninitialized:
717
0
        return;
718
1.13M
    default:
719
1.13M
        break;
720
1.41M
    }
721
1.41M
    obj->qpdf = nullptr;
722
1.41M
    obj->og = QPDFObjGen();
723
1.41M
}
724
725
bool
726
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
727
118
{
728
118
    return obj == rhs.obj;
729
118
}
730
731
qpdf_object_type_e
732
QPDFObjectHandle::getTypeCode() const
733
567k
{
734
567k
    return type_code();
735
567k
}
736
737
char const*
738
BaseHandle::type_name() const
739
5.94k
{
740
5.94k
    static constexpr std::array<char const*, 16> tn{
741
5.94k
        "uninitialized",
742
5.94k
        "reserved",
743
5.94k
        "null",
744
5.94k
        "boolean",
745
5.94k
        "integer",
746
5.94k
        "real",
747
5.94k
        "string",
748
5.94k
        "name",
749
5.94k
        "array",
750
5.94k
        "dictionary",
751
5.94k
        "stream",
752
5.94k
        "operator",
753
5.94k
        "inline-image",
754
5.94k
        "unresolved",
755
5.94k
        "destroyed",
756
5.94k
        "reference"};
757
5.94k
    return tn[type_code()];
758
5.94k
}
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
39.1k
{
775
39.1k
    return type_code() == ::ot_boolean;
776
39.1k
}
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
25.6k
{
789
25.6k
    return type_code() == ::ot_null;
790
25.6k
}
791
792
bool
793
QPDFObjectHandle::isInteger() const
794
159k
{
795
159k
    return type_code() == ::ot_integer;
796
159k
}
797
798
bool
799
QPDFObjectHandle::isReal() const
800
25.2k
{
801
25.2k
    return type_code() == ::ot_real;
802
25.2k
}
803
804
bool
805
QPDFObjectHandle::isNumber() const
806
18.6k
{
807
18.6k
    return (isInteger() || isReal());
808
18.6k
}
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
52.6k
{
837
52.6k
    return type_code() == ::ot_name;
838
52.6k
}
839
840
bool
841
QPDFObjectHandle::isString() const
842
21.7k
{
843
21.7k
    return type_code() == ::ot_string;
844
21.7k
}
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
25.8k
{
861
25.8k
    return type_code() == ::ot_array;
862
25.8k
}
863
864
bool
865
QPDFObjectHandle::isDictionary() const
866
752k
{
867
752k
    return type_code() == ::ot_dictionary;
868
752k
}
869
870
bool
871
QPDFObjectHandle::isStream() const
872
6
{
873
6
    return type_code() == ::ot_stream;
874
6
}
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
844
{
885
844
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
886
844
}
887
888
bool
889
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
890
40.4k
{
891
40.4k
    return Name(*this) == name;
892
40.4k
}
893
894
bool
895
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
896
264k
{
897
264k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
898
15.6k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
899
264k
}
900
901
bool
902
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
903
74.4k
{
904
74.4k
    if (auto stream = as_stream()) {
905
32.5k
        return stream && (type.empty() || stream.Type() == type) &&
906
11.6k
            (subtype.empty() || stream.Subtype() == subtype);
907
32.5k
    }
908
41.9k
    return false;
909
74.4k
}
910
911
// Bool accessors
912
913
bool
914
QPDFObjectHandle::getBoolValue() const
915
23
{
916
23
    if (auto boolean = as<QPDF_Bool>()) {
917
23
        return boolean->val;
918
23
    } else {
919
0
        typeWarning("boolean", "returning false");
920
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
921
0
        return false;
922
0
    }
923
23
}
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
3.71k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
939
3.71k
{
940
3.71k
}
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
126k
{
951
126k
    auto* i = as<QPDF_Integer>();
952
126k
    if (!i) {
953
997
        throw invalid_error("Integer");
954
997
    }
955
125k
    return i->val;
956
126k
}
957
958
long long
959
QPDFObjectHandle::getIntValue() const
960
24.7k
{
961
24.7k
    if (auto const integer = Integer(*this)) {
962
24.7k
        return integer;
963
24.7k
    } else {
964
0
        typeWarning("integer", "returning 0");
965
0
        return 0;
966
0
    }
967
24.7k
}
968
969
bool
970
QPDFObjectHandle::getValueAsInt(long long& value) const
971
6.12k
{
972
6.12k
    if (auto const integer = Integer(*this)) {
973
6.01k
        value = integer;
974
6.01k
        return true;
975
6.01k
    }
976
113
    return false;
977
6.12k
}
978
979
int
980
QPDFObjectHandle::getIntValueAsInt() const
981
35.1k
{
982
35.1k
    try {
983
35.1k
        return Integer(*this).value<int>();
984
35.1k
    } catch (std::invalid_argument&) {
985
15
        typeWarning("integer", "returning 0");
986
15
        return 0;
987
15
    }
988
35.1k
}
989
990
bool
991
QPDFObjectHandle::getValueAsInt(int& value) const
992
1.13k
{
993
1.13k
    if (!isInteger()) {
994
23
        return false;
995
23
    }
996
1.11k
    value = getIntValueAsInt();
997
1.11k
    return true;
998
1.13k
}
999
1000
unsigned long long
1001
QPDFObjectHandle::getUIntValue() const
1002
35.8k
{
1003
35.8k
    try {
1004
35.8k
        return Integer(*this).value<unsigned long long>();
1005
35.8k
    } catch (std::invalid_argument&) {
1006
578
        typeWarning("integer", "returning 0");
1007
578
        return 0;
1008
578
    }
1009
35.8k
}
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.29k
{
1024
6.29k
    try {
1025
6.29k
        return Integer(*this).value<unsigned int>();
1026
6.29k
    } catch (std::invalid_argument&) {
1027
404
        typeWarning("integer", "returning 0");
1028
404
        return 0;
1029
404
    }
1030
6.29k
}
1031
1032
bool
1033
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1034
1.29k
{
1035
1.29k
    if (!isInteger()) {
1036
144
        return false;
1037
144
    }
1038
1.14k
    value = getUIntValueAsUInt();
1039
1.14k
    return true;
1040
1.29k
}
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
14.1k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1079
14.1k
{
1080
14.1k
}
1081
1082
std::string const&
1083
Name::value() const
1084
139k
{
1085
139k
    auto* n = as<QPDF_Name>();
1086
139k
    if (!n) {
1087
0
        throw invalid_error("Name");
1088
0
    }
1089
139k
    return n->name;
1090
139k
}
1091
1092
std::string
1093
QPDFObjectHandle::getName() const
1094
8.66k
{
1095
8.66k
    if (auto* name = as<QPDF_Name>()) {
1096
8.66k
        return name->name;
1097
8.66k
    }
1098
0
    typeWarning("name", "returning dummy name");
1099
0
    return "/QPDFFakeName";
1100
8.66k
}
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
45
{
1117
45
    return {QPDFObject::create<QPDF_String>(str)};
1118
45
}
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
24.5k
{
1149
24.5k
    auto* s = as<QPDF_String>();
1150
24.5k
    if (!s) {
1151
0
        throw invalid_error("String");
1152
0
    }
1153
24.5k
    return s->val;
1154
24.5k
}
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
23.8k
{
1177
23.8k
    try {
1178
23.8k
        return String(obj).value();
1179
23.8k
    } catch (std::invalid_argument&) {
1180
0
        typeWarning("string", "returning empty string");
1181
0
        return {};
1182
0
    }
1183
23.8k
}
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
19.5k
{
1281
19.5k
    if (isNameAndEquals(value)) {
1282
79
        return true;
1283
19.4k
    } else if (isArray()) {
1284
16.9k
        for (auto& item: getArrayAsVector()) {
1285
16.9k
            if (item.isNameAndEquals(value)) {
1286
179
                return true;
1287
179
            }
1288
16.9k
        }
1289
9.41k
    }
1290
19.3k
    return false;
1291
19.5k
}
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
5
{
1578
5
    if (isIndirect()) {
1579
5
        return getObjGen().unparse(' ') + " R";
1580
5
    } else {
1581
0
        return unparseResolved();
1582
0
    }
1583
5
}
1584
1585
std::string
1586
QPDFObjectHandle::unparseResolved() const
1587
380k
{
1588
380k
    if (!obj) {
1589
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1590
0
    }
1591
380k
    return BaseHandle::unparse();
1592
380k
}
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
35
{
1654
35
    return parse(nullptr, object_str, object_description);
1655
35
}
1656
1657
QPDFObjectHandle
1658
QPDFObjectHandle::parse(
1659
    QPDF* context, std::string const& object_str, std::string const& object_description)
1660
35
{
1661
35
    auto input = is::OffsetBuffer("parsed object", object_str);
1662
35
    auto result = Parser::parse(input, object_description, context);
1663
35
    size_t offset = QIntC::to_size(input.tell());
1664
35
    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
35
    return result;
1677
35
}
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.48k
{
1887
2.48k
    return {QPDFObject::create<QPDF_Null>()};
1888
2.48k
}
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
26.3k
{
1899
26.3k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1900
26.3k
}
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.59k
{
1923
6.59k
    return {QPDFObject::create<QPDF_Array>(items)};
1924
6.59k
}
1925
1926
QPDFObjectHandle
1927
QPDFObjectHandle::newArray(Rectangle const& rect)
1928
6.59k
{
1929
6.59k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1930
6.59k
}
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
12.4k
{
1977
12.4k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1978
12.4k
}
1979
1980
QPDFObjectHandle
1981
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1982
12.4k
{
1983
12.4k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1984
12.4k
}
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
56.8k
{
2028
56.8k
    return obj ? obj->getDescription() : ""s;
2029
56.8k
}
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
45.9k
{
2043
45.9k
    return obj && obj->hasDescription();
2044
45.9k
}
2045
2046
QPDFObjectHandle
2047
QPDFObjectHandle::shallowCopy()
2048
118
{
2049
118
    if (!obj) {
2050
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2051
0
    }
2052
118
    return {copy()};
2053
118
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::unsafeShallowCopy()
2057
39.3k
{
2058
39.3k
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
39.3k
    return {copy(true)};
2062
39.3k
}
2063
2064
void
2065
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2066
37.5k
{
2067
37.5k
    static uint32_t constexpr max_nesting = 500;
2068
37.5k
    if (++level > max_nesting + 1) {
2069
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2070
0
    }
2071
37.5k
    assertInitialized();
2072
2073
37.5k
    auto cur_og = getObjGen();
2074
37.5k
    if (!visited.add(cur_og)) {
2075
28
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2076
28
    }
2077
2078
37.5k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2079
29.2k
        obj = copy(true);
2080
29.2k
    } else if (auto a = as_array(strict)) {
2081
2.46k
        std::vector<QPDFObjectHandle> items;
2082
26.9k
        for (auto const& item: a) {
2083
26.9k
            items.emplace_back(item);
2084
26.9k
            items.back().makeDirect(level, visited, stop_at_streams);
2085
26.9k
        }
2086
2.46k
        obj = QPDFObject::create<QPDF_Array>(items);
2087
5.81k
    } else if (isDictionary()) {
2088
5.80k
        std::map<std::string, QPDFObjectHandle> items;
2089
15.9k
        for (auto const& [key, value]: as_dictionary(strict)) {
2090
15.9k
            if (!value.null()) {
2091
10.4k
                items.insert({key, value});
2092
10.4k
                items[key].makeDirect(level, visited, stop_at_streams);
2093
10.4k
            }
2094
15.9k
        }
2095
5.80k
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2096
5.80k
    } else if (isStream()) {
2097
6
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2098
6
        if (!stop_at_streams) {
2099
6
            throw std::runtime_error("attempt to make a stream into a direct object");
2100
6
        }
2101
6
    } 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
37.5k
    visited.erase(cur_og);
2109
37.5k
}
2110
2111
void
2112
QPDFObjectHandle::makeDirect(bool allow_streams)
2113
212
{
2114
212
    QPDFObjGen::set visited;
2115
212
    makeDirect(0, visited, allow_streams);
2116
212
}
2117
2118
void
2119
QPDFObjectHandle::assertInitialized() const
2120
37.5k
{
2121
37.5k
    if (!obj) {
2122
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2123
0
    }
2124
37.5k
}
2125
2126
std::invalid_argument
2127
BaseHandle::invalid_error(std::string const& method) const
2128
997
{
2129
997
    return std::invalid_argument(method + " operation attempted on invalid object");
2130
997
}
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
5.94k
{
2141
5.94k
    return {
2142
5.94k
        qpdf_e_object,
2143
5.94k
        "",
2144
5.94k
        description(),
2145
5.94k
        0,
2146
5.94k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2147
5.94k
            message};
2148
5.94k
}
2149
2150
void
2151
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2152
5.94k
{
2153
5.94k
    if (!obj) {
2154
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2155
0
    }
2156
5.94k
    warn(type_error(expected_type, message));
2157
5.94k
}
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
3
{
2168
3
    warn({qpdf_e_object, "", description(), 0, warning});
2169
3
}
2170
2171
void
2172
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2173
0
{
2174
0
    if (!istype) {
2175
0
        throw type_error(type_name);
2176
0
    }
2177
0
}
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
56.8k
{
2330
56.8k
    if (!qpdf()) {
2331
202
        throw std::move(e);
2332
202
    }
2333
56.6k
    qpdf()->warn(std::move(e));
2334
56.6k
}
2335
2336
void
2337
BaseHandle::warn(std::string const& warning) const
2338
61.7k
{
2339
61.7k
    if (qpdf()) {
2340
50.8k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2341
50.8k
    } else {
2342
10.9k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2343
10.9k
    }
2344
61.7k
}
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
1.19M
{
2514
1.19M
    return obj ? obj->getObjGen() : QPDFObjGen();
2515
1.19M
}
2516
2517
int
2518
QPDFObjectHandle::getObjectID() const
2519
115k
{
2520
115k
    return getObjGen().getObj();
2521
115k
}
2522
2523
int
2524
QPDFObjectHandle::getGeneration() const
2525
0
{
2526
0
    return getObjGen().getGen();
2527
0
}
2528
2529
bool
2530
QPDFObjectHandle::isIndirect() const
2531
13.5k
{
2532
13.5k
    return getObjectID() != 0;
2533
13.5k
}
2534
2535
// Indirect object accessors
2536
QPDF*
2537
QPDFObjectHandle::getOwningQPDF() const
2538
93.8k
{
2539
93.8k
    return obj ? obj->getQPDF() : nullptr;
2540
93.8k
}
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
45
{
2554
45
    if (obj) {
2555
45
        obj->setParsedOffset(offset);
2556
45
    }
2557
45
}
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
}