Coverage Report

Created: 2026-08-31 06:54

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
0
{
35
0
    return obj ? obj->getObjGen() : QPDFObjGen();
36
0
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
14.8k
    supports_retry(supports_retry)
47
14.8k
{
48
14.8k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
14.8k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
14.8k
}
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
0
{
228
0
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
0
    std::string result;
232
0
    result += name.at(0);
233
0
    for (size_t i = 1; i < name.length(); ++i) {
234
0
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
0
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
0
            result += "#";
239
0
        } else if (
240
0
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
0
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
0
            result += util::hex_encode_char(ch);
243
0
        } else {
244
0
            result += ch;
245
0
        }
246
0
    }
247
0
    return result;
248
0
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
0
{
253
0
    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
0
    case ::ot_null:
260
0
        return QPDFObject::create<QPDF_Null>();
261
0
    case ::ot_boolean:
262
0
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
0
    case ::ot_integer:
264
0
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
0
    case ::ot_real:
266
0
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
0
    case ::ot_string:
268
0
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
0
    case ::ot_name:
270
0
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
0
    case ::ot_array:
272
0
        {
273
0
            auto const& a = std::get<QPDF_Array>(obj->value);
274
0
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
0
            } else {
277
0
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
0
                if (a.sp) {
279
0
                    QPDF_Array result;
280
0
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
0
                    result.sp->size = a.sp->size;
282
0
                    for (auto const& [idx, oh]: a.sp->elements) {
283
0
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
0
                    }
285
0
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
0
                } else {
287
0
                    std::vector<QPDFObjectHandle> result;
288
0
                    result.reserve(a.elements.size());
289
0
                    for (auto const& element: a.elements) {
290
0
                        result.emplace_back(
291
0
                            element ? (element.indirect() ? element : element.copy()) : element);
292
0
                    }
293
0
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
0
                }
295
0
            }
296
0
        }
297
0
    case ::ot_dictionary:
298
0
        {
299
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
0
            if (shallow) {
301
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
0
            } else {
303
0
                std::map<std::string, QPDFObjectHandle> new_items;
304
0
                for (auto const& [key, val]: d.items) {
305
0
                    new_items[key] = val.indirect() ? val : val.copy();
306
0
                }
307
0
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
0
            }
309
0
        }
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
0
    }
327
0
    return {}; // unreachable
328
0
}
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
0
{
475
0
    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
0
    case ::ot_null:
483
0
        return "null";
484
0
    case ::ot_boolean:
485
0
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
0
    case ::ot_integer:
487
0
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
0
    case ::ot_real:
489
0
        return std::get<QPDF_Real>(obj->value).val;
490
0
    case ::ot_string:
491
0
        return std::get<QPDF_String>(obj->value).unparse(false);
492
0
    case ::ot_name:
493
0
        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
0
    }
545
0
    return {}; // unreachable
546
0
}
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 if (val.at(0) == '0') {
578
0
                auto i = val.find_first_not_of('0');
579
0
                if (i == std::string::npos) {
580
0
                    p << "0";
581
0
                } else if (val.at(i) == '.') {
582
0
                    p << "0" << val.substr(i);
583
0
                } else {
584
0
                    p << val.substr(i);
585
0
                }
586
0
            } else {
587
0
                p << val;
588
0
            }
589
0
            if (val.back() == '.') {
590
0
                p << "0";
591
0
            }
592
0
        }
593
0
        break;
594
0
    case ::ot_string:
595
0
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
596
0
        break;
597
0
    case ::ot_name:
598
0
        {
599
0
            auto const& n = std::get<QPDF_Name>(obj->value);
600
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
601
            // updating this method make sure QPDF_Dictionary is also update.
602
0
            if (json_version == 1) {
603
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
0
            } else {
605
0
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
606
0
                    if (res.second) {
607
0
                        p << "\"" << n.name << "\"";
608
0
                    } else {
609
0
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
610
0
                    }
611
0
                } else {
612
0
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
613
0
                }
614
0
            }
615
0
        }
616
0
        break;
617
0
    case ::ot_array:
618
0
        {
619
0
            auto const& a = std::get<QPDF_Array>(obj->value);
620
0
            p.writeStart('[');
621
0
            if (a.sp) {
622
0
                size_t next = 0;
623
0
                for (auto& [key, value]: a.sp->elements) {
624
0
                    for (size_t j = next; j < key; ++j) {
625
0
                        p.writeNext() << "null";
626
0
                    }
627
0
                    p.writeNext();
628
0
                    auto item_og = value.id_gen();
629
0
                    if (item_og.isIndirect()) {
630
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
631
0
                    } else {
632
0
                        value.write_json(json_version, p);
633
0
                    }
634
0
                    next = key + 1;
635
0
                }
636
0
                for (size_t j = next; j < a.sp->size; ++j) {
637
0
                    p.writeNext() << "null";
638
0
                }
639
0
            } else {
640
0
                for (auto const& item: a.elements) {
641
0
                    p.writeNext();
642
0
                    auto item_og = item.id_gen();
643
0
                    if (item_og.isIndirect()) {
644
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
645
0
                    } else {
646
0
                        item.write_json(json_version, p);
647
0
                    }
648
0
                }
649
0
            }
650
0
            p.writeEnd(']');
651
0
        }
652
0
        break;
653
0
    case ::ot_dictionary:
654
0
        {
655
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
656
0
            p.writeStart('{');
657
0
            for (auto& iter: d.items) {
658
0
                if (!iter.second.null()) {
659
0
                    p.writeNext();
660
0
                    if (json_version == 1) {
661
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
0
                          << "\": ";
663
0
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
664
0
                        if (res.second) {
665
0
                            p << "\"" << iter.first << "\": ";
666
0
                        } else {
667
0
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
668
0
                        }
669
0
                    } else {
670
0
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
671
0
                          << "\": ";
672
0
                    }
673
0
                    iter.second.writeJSON(json_version, p);
674
0
                }
675
0
            }
676
0
            p.writeEnd('}');
677
0
        }
678
0
        break;
679
0
    case ::ot_stream:
680
0
        std::get<QPDF_Stream>(obj->value).m->stream_dict.writeJSON(json_version, p);
681
0
        break;
682
0
    case ::ot_reference:
683
0
        p << "\"" << obj->og.unparse(' ') << " R\"";
684
0
        break;
685
0
    default:
686
0
        throw std::logic_error("attempted to write an unsuitable object as JSON");
687
0
    }
688
0
}
689
690
void
691
BaseHandle::disconnect(bool only_direct)
692
1.55M
{
693
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
694
1.55M
    if (only_direct && indirect()) {
695
276k
        return;
696
276k
    }
697
698
1.27M
    switch (raw_type_code()) {
699
11.1k
    case ::ot_array:
700
11.1k
        {
701
11.1k
            auto& a = std::get<QPDF_Array>(obj->value);
702
11.1k
            if (a.sp) {
703
0
                for (auto& item: a.sp->elements) {
704
0
                    item.second.disconnect();
705
0
                }
706
11.1k
            } else {
707
1.45M
                for (auto& oh: a.elements) {
708
1.45M
                    oh.disconnect();
709
1.45M
                }
710
11.1k
            }
711
11.1k
        }
712
11.1k
        break;
713
19.5k
    case ::ot_dictionary:
714
81.8k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
715
81.8k
            iter.second.disconnect();
716
81.8k
        }
717
19.5k
        break;
718
2.37k
    case ::ot_stream:
719
2.37k
        {
720
2.37k
            auto& s = std::get<QPDF_Stream>(obj->value);
721
2.37k
            s.m->stream_provider = nullptr;
722
2.37k
            s.m->stream_dict.disconnect();
723
2.37k
        }
724
2.37k
        break;
725
0
    case ::ot_uninitialized:
726
0
        return;
727
1.24M
    default:
728
1.24M
        break;
729
1.27M
    }
730
1.27M
    obj->qpdf = nullptr;
731
1.27M
    obj->og = QPDFObjGen();
732
1.27M
}
733
734
bool
735
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
736
0
{
737
0
    return obj == rhs.obj;
738
0
}
739
740
qpdf_object_type_e
741
QPDFObjectHandle::getTypeCode() const
742
0
{
743
0
    return type_code();
744
0
}
745
746
char const*
747
BaseHandle::type_name() const
748
0
{
749
0
    static constexpr std::array<char const*, 16> tn{
750
0
        "uninitialized",
751
0
        "reserved",
752
0
        "null",
753
0
        "boolean",
754
0
        "integer",
755
0
        "real",
756
0
        "string",
757
0
        "name",
758
0
        "array",
759
0
        "dictionary",
760
0
        "stream",
761
0
        "operator",
762
0
        "inline-image",
763
0
        "unresolved",
764
0
        "destroyed",
765
0
        "reference"};
766
0
    return tn[type_code()];
767
0
}
768
769
char const*
770
QPDFObjectHandle::getTypeName() const
771
0
{
772
0
    return type_name();
773
0
}
774
775
bool
776
QPDFObjectHandle::isDestroyed() const
777
0
{
778
0
    return type_code() == ::ot_destroyed;
779
0
}
780
781
bool
782
QPDFObjectHandle::isBool() const
783
0
{
784
0
    return type_code() == ::ot_boolean;
785
0
}
786
787
bool
788
QPDFObjectHandle::isDirectNull() const
789
0
{
790
    // Don't call dereference() -- this is a const method, and we know
791
    // objid == 0, so there's nothing to resolve.
792
0
    return !indirect() && raw_type_code() == ::ot_null;
793
0
}
794
795
bool
796
QPDFObjectHandle::isNull() const
797
0
{
798
0
    return type_code() == ::ot_null;
799
0
}
800
801
bool
802
QPDFObjectHandle::isInteger() const
803
7.85k
{
804
7.85k
    return type_code() == ::ot_integer;
805
7.85k
}
806
807
bool
808
QPDFObjectHandle::isReal() const
809
0
{
810
0
    return type_code() == ::ot_real;
811
0
}
812
813
bool
814
QPDFObjectHandle::isNumber() const
815
0
{
816
0
    return (isInteger() || isReal());
817
0
}
818
819
double
820
QPDFObjectHandle::getNumericValue() const
821
0
{
822
0
    if (isInteger()) {
823
0
        return static_cast<double>(getIntValue());
824
0
    } else if (isReal()) {
825
0
        return atof(getRealValue().c_str());
826
0
    } else {
827
0
        typeWarning("number", "returning 0");
828
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
829
0
        return 0;
830
0
    }
831
0
}
832
833
bool
834
QPDFObjectHandle::getValueAsNumber(double& value) const
835
0
{
836
0
    if (!isNumber()) {
837
0
        return false;
838
0
    }
839
0
    value = getNumericValue();
840
0
    return true;
841
0
}
842
843
bool
844
QPDFObjectHandle::isName() const
845
0
{
846
0
    return type_code() == ::ot_name;
847
0
}
848
849
bool
850
QPDFObjectHandle::isString() const
851
0
{
852
0
    return type_code() == ::ot_string;
853
0
}
854
855
bool
856
QPDFObjectHandle::isOperator() const
857
0
{
858
0
    return type_code() == ::ot_operator;
859
0
}
860
861
bool
862
QPDFObjectHandle::isInlineImage() const
863
0
{
864
0
    return type_code() == ::ot_inlineimage;
865
0
}
866
867
bool
868
QPDFObjectHandle::isArray() const
869
0
{
870
0
    return type_code() == ::ot_array;
871
0
}
872
873
bool
874
QPDFObjectHandle::isDictionary() const
875
143k
{
876
143k
    return type_code() == ::ot_dictionary;
877
143k
}
878
879
bool
880
QPDFObjectHandle::isStream() const
881
142k
{
882
142k
    return type_code() == ::ot_stream;
883
142k
}
884
885
bool
886
QPDFObjectHandle::isReserved() const
887
0
{
888
0
    return type_code() == ::ot_reserved;
889
0
}
890
891
bool
892
QPDFObjectHandle::isScalar() const
893
0
{
894
0
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
895
0
}
896
897
bool
898
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
899
0
{
900
0
    return Name(*this) == name;
901
0
}
902
903
bool
904
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
905
0
{
906
0
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
907
0
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
908
0
}
909
910
bool
911
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
912
0
{
913
0
    if (auto stream = as_stream()) {
914
0
        return stream && (type.empty() || stream.Type() == type) &&
915
0
            (subtype.empty() || stream.Subtype() == subtype);
916
0
    }
917
0
    return false;
918
0
}
919
920
// Bool accessors
921
922
bool
923
QPDFObjectHandle::getBoolValue() const
924
0
{
925
0
    if (auto boolean = as<QPDF_Bool>()) {
926
0
        return boolean->val;
927
0
    } else {
928
0
        typeWarning("boolean", "returning false");
929
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
930
0
        return false;
931
0
    }
932
0
}
933
934
bool
935
QPDFObjectHandle::getValueAsBool(bool& value) const
936
0
{
937
0
    if (auto boolean = as<QPDF_Bool>()) {
938
0
        value = boolean->val;
939
0
        return true;
940
0
    }
941
0
    return false;
942
0
}
943
944
// Integer methods
945
946
Integer::Integer(long long value) :
947
0
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
948
0
{
949
0
}
950
951
QPDFObjectHandle
952
QPDFObjectHandle::newInteger(long long value)
953
1.15M
{
954
1.15M
    return {QPDFObject::create<QPDF_Integer>(value)};
955
1.15M
}
956
957
int64_t
958
Integer::value() const
959
7.85k
{
960
7.85k
    auto* i = as<QPDF_Integer>();
961
7.85k
    if (!i) {
962
0
        throw invalid_error("Integer");
963
0
    }
964
7.85k
    return i->val;
965
7.85k
}
966
967
long long
968
QPDFObjectHandle::getIntValue() const
969
0
{
970
0
    if (auto const integer = Integer(*this)) {
971
0
        return integer;
972
0
    } else {
973
0
        typeWarning("integer", "returning 0");
974
0
        return 0;
975
0
    }
976
0
}
977
978
bool
979
QPDFObjectHandle::getValueAsInt(long long& value) const
980
0
{
981
0
    if (auto const integer = Integer(*this)) {
982
0
        value = integer;
983
0
        return true;
984
0
    }
985
0
    return false;
986
0
}
987
988
int
989
QPDFObjectHandle::getIntValueAsInt() const
990
7.85k
{
991
7.85k
    try {
992
7.85k
        return Integer(*this).value<int>();
993
7.85k
    } catch (std::invalid_argument&) {
994
0
        typeWarning("integer", "returning 0");
995
0
        return 0;
996
0
    }
997
7.85k
}
998
999
bool
1000
QPDFObjectHandle::getValueAsInt(int& value) const
1001
0
{
1002
0
    if (!isInteger()) {
1003
0
        return false;
1004
0
    }
1005
0
    value = getIntValueAsInt();
1006
0
    return true;
1007
0
}
1008
1009
unsigned long long
1010
QPDFObjectHandle::getUIntValue() const
1011
0
{
1012
0
    try {
1013
0
        return Integer(*this).value<unsigned long long>();
1014
0
    } catch (std::invalid_argument&) {
1015
0
        typeWarning("integer", "returning 0");
1016
0
        return 0;
1017
0
    }
1018
0
}
1019
1020
bool
1021
QPDFObjectHandle::getValueAsUInt(unsigned long long& value) const
1022
0
{
1023
0
    if (!isInteger()) {
1024
0
        return false;
1025
0
    }
1026
0
    value = getUIntValue();
1027
0
    return true;
1028
0
}
1029
1030
unsigned int
1031
QPDFObjectHandle::getUIntValueAsUInt() const
1032
0
{
1033
0
    try {
1034
0
        return Integer(*this).value<unsigned int>();
1035
0
    } catch (std::invalid_argument&) {
1036
0
        typeWarning("integer", "returning 0");
1037
0
        return 0;
1038
0
    }
1039
0
}
1040
1041
bool
1042
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1043
0
{
1044
0
    if (!isInteger()) {
1045
0
        return false;
1046
0
    }
1047
0
    value = getUIntValueAsUInt();
1048
0
    return true;
1049
0
}
1050
1051
// Real accessors
1052
1053
std::string
1054
QPDFObjectHandle::getRealValue() const
1055
0
{
1056
0
    if (auto* real = as<QPDF_Real>()) {
1057
0
        return real->val;
1058
0
    }
1059
0
    typeWarning("real", "returning 0.0");
1060
0
    return "0.0";
1061
0
}
1062
1063
bool
1064
QPDFObjectHandle::getValueAsReal(std::string& value) const
1065
0
{
1066
0
    if (auto* real = as<QPDF_Real>()) {
1067
0
        value = real->val;
1068
0
        return true;
1069
0
    }
1070
0
    return false;
1071
0
}
1072
1073
// Name methods
1074
1075
QPDFObjectHandle
1076
QPDFObjectHandle::newName(std::string const& name)
1077
13.8k
{
1078
13.8k
    return {QPDFObject::create<QPDF_Name>(name)};
1079
13.8k
}
1080
1081
Name::Name(std::string const& name) :
1082
0
    BaseHandle(QPDFObject::create<QPDF_Name>(name))
1083
0
{
1084
0
}
1085
1086
Name::Name(std::string&& name) :
1087
0
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1088
0
{
1089
0
}
1090
1091
std::string const&
1092
Name::value() const
1093
0
{
1094
0
    auto* n = as<QPDF_Name>();
1095
0
    if (!n) {
1096
0
        throw invalid_error("Name");
1097
0
    }
1098
0
    return n->name;
1099
0
}
1100
1101
std::string
1102
QPDFObjectHandle::getName() const
1103
2.14k
{
1104
2.14k
    if (auto* name = as<QPDF_Name>()) {
1105
2.14k
        return name->name;
1106
2.14k
    }
1107
0
    typeWarning("name", "returning dummy name");
1108
0
    return "/QPDFFakeName";
1109
2.14k
}
1110
1111
bool
1112
QPDFObjectHandle::getValueAsName(std::string& value) const
1113
0
{
1114
0
    if (auto* name = as<QPDF_Name>()) {
1115
0
        value = name->name;
1116
0
        return true;
1117
0
    }
1118
0
    return false;
1119
0
}
1120
1121
// String methods
1122
1123
QPDFObjectHandle
1124
QPDFObjectHandle::newString(std::string const& str)
1125
1.12k
{
1126
1.12k
    return {QPDFObject::create<QPDF_String>(str)};
1127
1.12k
}
1128
1129
QPDFObjectHandle
1130
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1131
4.99k
{
1132
4.99k
    return {String::utf16(utf8_str).obj_sp()};
1133
4.99k
}
1134
1135
String::String(std::string const& str) :
1136
1.24k
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1137
1.24k
{
1138
1.24k
}
1139
1140
String::String(std::string&& str) :
1141
3.75k
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1142
3.75k
{
1143
3.75k
}
1144
1145
String
1146
String::utf16(std::string const& utf8_str)
1147
4.99k
{
1148
4.99k
    std::string result;
1149
4.99k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1150
1.24k
        return String(result);
1151
1.24k
    }
1152
3.75k
    return String(QUtil::utf8_to_utf16(utf8_str));
1153
4.99k
}
1154
1155
std::string const&
1156
String::value() const
1157
0
{
1158
0
    auto* s = as<QPDF_String>();
1159
0
    if (!s) {
1160
0
        throw invalid_error("String");
1161
0
    }
1162
0
    return s->val;
1163
0
}
1164
1165
std::string
1166
String::utf8_value() const
1167
0
{
1168
0
    auto* s = as<QPDF_String>();
1169
0
    if (!s) {
1170
0
        throw invalid_error("String");
1171
0
    }
1172
0
    if (util::is_utf16(s->val)) {
1173
0
        return QUtil::utf16_to_utf8(s->val);
1174
0
    }
1175
0
    if (util::is_explicit_utf8(s->val)) {
1176
        // PDF 2.0 allows UTF-8 strings when explicitly prefixed with the three-byte representation
1177
        // of U+FEFF.
1178
0
        return s->val.substr(3);
1179
0
    }
1180
0
    return QUtil::pdf_doc_to_utf8(s->val);
1181
0
}
1182
1183
std::string
1184
QPDFObjectHandle::getStringValue() const
1185
0
{
1186
0
    try {
1187
0
        return String(obj).value();
1188
0
    } catch (std::invalid_argument&) {
1189
0
        typeWarning("string", "returning empty string");
1190
0
        return {};
1191
0
    }
1192
0
}
1193
1194
bool
1195
QPDFObjectHandle::getValueAsString(std::string& value) const
1196
0
{
1197
0
    try {
1198
0
        value = String(obj).value();
1199
0
        return true;
1200
0
    } catch (std::invalid_argument&) {
1201
0
        return false;
1202
0
    }
1203
0
}
1204
1205
std::string
1206
QPDFObjectHandle::getUTF8Value() const
1207
0
{
1208
0
    try {
1209
0
        return String(obj).utf8_value();
1210
0
    } catch (std::invalid_argument&) {
1211
0
        typeWarning("string", "returning empty string");
1212
0
        return {};
1213
0
    }
1214
0
}
1215
1216
bool
1217
QPDFObjectHandle::getValueAsUTF8(std::string& value) const
1218
0
{
1219
0
    try {
1220
0
        value = String(obj).utf8_value();
1221
0
        return true;
1222
0
    } catch (std::invalid_argument&) {
1223
0
        return false;
1224
0
    }
1225
0
}
1226
1227
// Operator and Inline Image accessors
1228
1229
std::string
1230
QPDFObjectHandle::getOperatorValue() const
1231
0
{
1232
0
    if (auto* op = as<QPDF_Operator>()) {
1233
0
        return op->val;
1234
0
    }
1235
0
    typeWarning("operator", "returning fake value");
1236
0
    return "QPDFFAKE";
1237
0
}
1238
1239
bool
1240
QPDFObjectHandle::getValueAsOperator(std::string& value) const
1241
0
{
1242
0
    if (auto* op = as<QPDF_Operator>()) {
1243
0
        value = op->val;
1244
0
        return true;
1245
0
    }
1246
0
    return false;
1247
0
}
1248
1249
std::string
1250
QPDFObjectHandle::getInlineImageValue() const
1251
0
{
1252
0
    if (auto* ii = as<QPDF_InlineImage>()) {
1253
0
        return ii->val;
1254
0
    }
1255
0
    typeWarning("inlineimage", "returning empty data");
1256
0
    return {};
1257
0
}
1258
1259
bool
1260
QPDFObjectHandle::getValueAsInlineImage(std::string& value) const
1261
0
{
1262
0
    if (auto* ii = as<QPDF_InlineImage>()) {
1263
0
        value = ii->val;
1264
0
        return true;
1265
0
    }
1266
0
    return false;
1267
0
}
1268
1269
// Array accessors and mutators are in QPDF_Array.cc
1270
1271
QPDFObjectHandle::QPDFArrayItems
1272
QPDFObjectHandle::aitems()
1273
0
{
1274
0
    return *this;
1275
0
}
1276
1277
// Dictionary accessors are in QPDF_Dictionary.cc
1278
1279
QPDFObjectHandle::QPDFDictItems
1280
QPDFObjectHandle::ditems()
1281
0
{
1282
0
    return {*this};
1283
0
}
1284
1285
// Array and Name accessors
1286
1287
bool
1288
QPDFObjectHandle::isOrHasName(std::string const& value) const
1289
0
{
1290
0
    if (isNameAndEquals(value)) {
1291
0
        return true;
1292
0
    } else if (isArray()) {
1293
0
        for (auto& item: getArrayAsVector()) {
1294
0
            if (item.isNameAndEquals(value)) {
1295
0
                return true;
1296
0
            }
1297
0
        }
1298
0
    }
1299
0
    return false;
1300
0
}
1301
1302
void
1303
QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf)
1304
0
{
1305
0
    for (auto const& i1: as_dictionary()) {
1306
0
        for (auto& i2: i1.second.as_dictionary()) {
1307
0
            if (!i2.second.null() && !i2.second.isIndirect()) {
1308
0
                i2.second = owning_qpdf.makeIndirectObject(i2.second);
1309
0
            }
1310
0
        }
1311
0
    }
1312
0
}
1313
1314
void
1315
QPDFObjectHandle::mergeResources(
1316
    QPDFObjectHandle other, std::map<std::string, std::map<std::string, std::string>>* conflicts)
1317
0
{
1318
0
    if (!(isDictionary() && other.isDictionary())) {
1319
0
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1320
0
        return;
1321
0
    }
1322
1323
0
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1324
0
                              std::map<QPDFObjGen, std::string>& og_to_name) {
1325
0
        for (auto const& [key, value]: dict.as_dictionary()) {
1326
0
            if (!value.null() && value.isIndirect()) {
1327
0
                og_to_name.insert_or_assign(value.getObjGen(), key);
1328
0
            }
1329
0
        }
1330
0
    };
1331
1332
    // This algorithm is described in comments in QPDFObjectHandle.hh
1333
    // above the declaration of mergeResources.
1334
0
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1335
0
        auto other_val = value1;
1336
0
        if (hasKey(rtype)) {
1337
0
            QPDFObjectHandle this_val = getKey(rtype);
1338
0
            if (this_val.isDictionary() && other_val.isDictionary()) {
1339
0
                if (this_val.isIndirect()) {
1340
                    // Do this even if there are no keys. Various places in the code call
1341
                    // mergeResources with resource dictionaries that contain empty subdictionaries
1342
                    // just to get this shallow copy functionality.
1343
0
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1344
0
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1345
0
                }
1346
0
                std::map<QPDFObjGen, std::string> og_to_name;
1347
0
                std::set<std::string> rnames;
1348
0
                int min_suffix = 1;
1349
0
                bool initialized_maps = false;
1350
0
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1351
0
                    QPDFObjectHandle rval = value2;
1352
0
                    if (!this_val.hasKey(key)) {
1353
0
                        if (!rval.isIndirect()) {
1354
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1355
0
                            rval = rval.shallowCopy();
1356
0
                        }
1357
0
                        this_val.replaceKey(key, rval);
1358
0
                    } else if (conflicts) {
1359
0
                        if (!initialized_maps) {
1360
0
                            make_og_to_name(this_val, og_to_name);
1361
0
                            rnames = this_val.getResourceNames();
1362
0
                            initialized_maps = true;
1363
0
                        }
1364
0
                        auto rval_og = rval.getObjGen();
1365
0
                        if (rval.isIndirect() && og_to_name.contains(rval_og)) {
1366
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge reuse");
1367
0
                            auto new_key = og_to_name[rval_og];
1368
0
                            if (new_key != key) {
1369
0
                                (*conflicts)[rtype][key] = new_key;
1370
0
                            }
1371
0
                        } else {
1372
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge generate");
1373
0
                            std::string new_key =
1374
0
                                getUniqueResourceName(key + "_", min_suffix, &rnames);
1375
0
                            (*conflicts)[rtype][key] = new_key;
1376
0
                            this_val.replaceKey(new_key, rval);
1377
0
                        }
1378
0
                    }
1379
0
                }
1380
0
            } else if (this_val.isArray() && other_val.isArray()) {
1381
0
                std::set<std::string> scalars;
1382
0
                for (auto this_item: this_val.aitems()) {
1383
0
                    if (this_item.isScalar()) {
1384
0
                        scalars.insert(this_item.unparse());
1385
0
                    }
1386
0
                }
1387
0
                for (auto other_item: other_val.aitems()) {
1388
0
                    if (other_item.isScalar()) {
1389
0
                        if (!scalars.contains(other_item.unparse())) {
1390
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1391
0
                            this_val.appendItem(other_item);
1392
0
                        } else {
1393
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1394
0
                        }
1395
0
                    }
1396
0
                }
1397
0
            }
1398
0
        } else {
1399
0
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1400
0
            replaceKey(rtype, other_val.shallowCopy());
1401
0
        }
1402
0
    }
1403
0
}
1404
1405
std::set<std::string>
1406
QPDFObjectHandle::getResourceNames() const
1407
0
{
1408
    // Return second-level dictionary keys
1409
0
    std::set<std::string> result;
1410
0
    for (auto const& item: as_dictionary(strict)) {
1411
0
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1412
0
            if (!val2.null()) {
1413
0
                result.insert(key2);
1414
0
            }
1415
0
        }
1416
0
    }
1417
0
    return result;
1418
0
}
1419
1420
std::string
1421
QPDFObjectHandle::getUniqueResourceName(
1422
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1423
0
{
1424
0
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1425
0
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1426
0
    while (min_suffix <= max_suffix) {
1427
0
        std::string candidate = prefix + std::to_string(min_suffix);
1428
0
        if (!names.contains(candidate)) {
1429
0
            return candidate;
1430
0
        }
1431
        // Increment after return; min_suffix should be the value
1432
        // used, not the next value.
1433
0
        ++min_suffix;
1434
0
    }
1435
    // This could only happen if there is a coding error.
1436
    // The number of candidates we test is more than the
1437
    // number of keys we're checking against.
1438
0
    throw std::logic_error(
1439
0
        "unable to find unconflicting name in QPDFObjectHandle::getUniqueResourceName");
1440
0
}
1441
1442
// Dictionary mutators are in QPDF_Dictionary.cc
1443
1444
// Stream accessors are in QPDF_Stream.cc
1445
1446
std::map<std::string, QPDFObjectHandle>
1447
QPDFObjectHandle::getPageImages()
1448
0
{
1449
0
    return QPDFPageObjectHelper(*this).getImages();
1450
0
}
1451
1452
std::vector<QPDFObjectHandle>
1453
QPDFObjectHandle::arrayOrStreamToStreamArray(
1454
    std::string const& description, std::string& all_description)
1455
0
{
1456
0
    all_description = description;
1457
0
    std::vector<QPDFObjectHandle> result;
1458
0
    if (auto array = as_array(strict)) {
1459
0
        int n_items = static_cast<int>(array.size());
1460
0
        for (int i = 0; i < n_items; ++i) {
1461
0
            QPDFObjectHandle item = array[i];
1462
0
            if (item.isStream()) {
1463
0
                result.emplace_back(item);
1464
0
            } else {
1465
0
                item.warn(
1466
0
                    {qpdf_e_damaged_pdf,
1467
0
                     "",
1468
0
                     description + ": item index " + std::to_string(i) + " (from 0)",
1469
0
                     0,
1470
0
                     "ignoring non-stream in an array of streams"});
1471
0
            }
1472
0
        }
1473
0
    } else if (isStream()) {
1474
0
        result.emplace_back(*this);
1475
0
    } else if (!null()) {
1476
0
        warn(
1477
0
            {qpdf_e_damaged_pdf,
1478
0
             "",
1479
0
             description,
1480
0
             0,
1481
0
             " object is supposed to be a stream or an array of streams but is neither"});
1482
0
    }
1483
1484
0
    bool first = true;
1485
0
    for (auto const& item: result) {
1486
0
        if (first) {
1487
0
            first = false;
1488
0
        } else {
1489
0
            all_description += ",";
1490
0
        }
1491
0
        all_description += " stream " + item.getObjGen().unparse(' ');
1492
0
    }
1493
1494
0
    return result;
1495
0
}
1496
1497
std::vector<QPDFObjectHandle>
1498
QPDFObjectHandle::getPageContents()
1499
0
{
1500
0
    std::string description = "page object " + getObjGen().unparse(' ');
1501
0
    std::string all_description;
1502
0
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1503
0
}
1504
1505
void
1506
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1507
0
{
1508
0
    new_contents.assertStream();
1509
1510
0
    std::vector<QPDFObjectHandle> content_streams;
1511
0
    if (first) {
1512
0
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1513
0
        content_streams.push_back(new_contents);
1514
0
    }
1515
0
    for (auto const& iter: getPageContents()) {
1516
0
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1517
0
        content_streams.push_back(iter);
1518
0
    }
1519
0
    if (!first) {
1520
0
        content_streams.push_back(new_contents);
1521
0
    }
1522
1523
0
    replaceKey("/Contents", newArray(content_streams));
1524
0
}
1525
1526
void
1527
QPDFObjectHandle::rotatePage(int angle, bool relative)
1528
0
{
1529
0
    if ((angle % 90) != 0) {
1530
0
        throw std::runtime_error(
1531
0
            "QPDF::rotatePage called with an angle that is not a multiple of 90");
1532
0
    }
1533
0
    int new_angle = angle;
1534
0
    if (relative) {
1535
0
        int old_angle = 0;
1536
0
        QPDFObjectHandle cur_obj = *this;
1537
0
        QPDFObjGen::set visited;
1538
0
        while (visited.add(cur_obj)) {
1539
            // Don't get stuck in an infinite loop
1540
0
            if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) {
1541
0
                break;
1542
0
            } else if (cur_obj.getKey("/Parent").isDictionary()) {
1543
0
                cur_obj = cur_obj.getKey("/Parent");
1544
0
            } else {
1545
0
                break;
1546
0
            }
1547
0
        }
1548
0
        QTC::TC("qpdf", "QPDFObjectHandle found old angle", visited.size() > 1 ? 0 : 1);
1549
0
        if ((old_angle % 90) != 0) {
1550
0
            old_angle = 0;
1551
0
        }
1552
0
        new_angle += old_angle;
1553
0
    }
1554
0
    new_angle = (new_angle + 360) % 360;
1555
    // Make this explicit even with new_angle == 0 since /Rotate can be inherited.
1556
0
    replaceKey("/Rotate", Integer(new_angle));
1557
0
}
1558
1559
void
1560
QPDFObjectHandle::coalesceContentStreams()
1561
0
{
1562
0
    QPDFObjectHandle contents = getKey("/Contents");
1563
0
    if (contents.isStream()) {
1564
0
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1565
0
        return;
1566
0
    } else if (!contents.isArray()) {
1567
        // /Contents is optional for pages, and some very damaged files may have pages that are
1568
        // invalid in other ways.
1569
0
        return;
1570
0
    }
1571
    // Should not be possible for a page object to not have an owning PDF unless it was manually
1572
    // constructed in some incorrect way. However, it can happen in a PDF file whose page structure
1573
    // is direct, which is against spec but still possible to hand construct, as in fuzz issue
1574
    // 27393.
1575
0
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1576
1577
0
    QPDFObjectHandle new_contents = newStream(&qpdf);
1578
0
    replaceKey("/Contents", new_contents);
1579
1580
0
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1581
0
    new_contents.replaceStreamData(provider, newNull(), newNull());
1582
0
}
1583
1584
std::string
1585
QPDFObjectHandle::unparse() const
1586
0
{
1587
0
    if (isIndirect()) {
1588
0
        return getObjGen().unparse(' ') + " R";
1589
0
    } else {
1590
0
        return unparseResolved();
1591
0
    }
1592
0
}
1593
1594
std::string
1595
QPDFObjectHandle::unparseResolved() const
1596
0
{
1597
0
    if (!obj) {
1598
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1599
0
    }
1600
0
    return BaseHandle::unparse();
1601
0
}
1602
1603
std::string
1604
QPDFObjectHandle::unparseBinary() const
1605
0
{
1606
0
    if (auto str = as<QPDF_String>()) {
1607
0
        return str->unparse(true);
1608
0
    } else {
1609
0
        return unparse();
1610
0
    }
1611
0
}
1612
1613
JSON
1614
QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) const
1615
0
{
1616
0
    if ((!dereference_indirect) && isIndirect()) {
1617
0
        return JSON::makeString(unparse());
1618
0
    } else if (!obj) {
1619
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1620
0
    } else {
1621
0
        Pl_Buffer p{"json"};
1622
0
        JSON::Writer jw{&p, 0};
1623
0
        writeJSON(json_version, jw, dereference_indirect);
1624
0
        p.finish();
1625
0
        return JSON::parse(p.getString());
1626
0
    }
1627
0
}
1628
1629
void
1630
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1631
0
{
1632
0
    if (!dereference_indirect && isIndirect()) {
1633
0
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1634
0
    } else if (!obj) {
1635
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1636
0
    } else {
1637
0
        write_json(json_version, p);
1638
0
    }
1639
0
}
1640
1641
void
1642
QPDFObjectHandle::writeJSON(
1643
    int json_version, Pipeline* p, bool dereference_indirect, size_t depth) const
1644
0
{
1645
0
    JSON::Writer jw{p, depth};
1646
0
    writeJSON(json_version, jw, dereference_indirect);
1647
0
}
1648
1649
QPDFObjectHandle
1650
QPDFObjectHandle::wrapInArray()
1651
0
{
1652
0
    if (isArray()) {
1653
0
        return *this;
1654
0
    }
1655
0
    QPDFObjectHandle result = QPDFObjectHandle::newArray();
1656
0
    result.appendItem(*this);
1657
0
    return result;
1658
0
}
1659
1660
QPDFObjectHandle
1661
QPDFObjectHandle::parse(std::string const& object_str, std::string const& object_description)
1662
11.0k
{
1663
11.0k
    return parse(nullptr, object_str, object_description);
1664
11.0k
}
1665
1666
QPDFObjectHandle
1667
QPDFObjectHandle::parse(
1668
    QPDF* context, std::string const& object_str, std::string const& object_description)
1669
11.0k
{
1670
11.0k
    auto input = is::OffsetBuffer("parsed object", object_str);
1671
11.0k
    auto result = Parser::parse(input, object_description, context);
1672
11.0k
    size_t offset = QIntC::to_size(input.tell());
1673
14.8k
    while (offset < object_str.length()) {
1674
3.90k
        if (!isspace(object_str.at(offset))) {
1675
76
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1676
76
            throw QPDFExc(
1677
76
                qpdf_e_damaged_pdf,
1678
76
                "parsed object",
1679
76
                object_description,
1680
76
                input.getLastOffset(),
1681
76
                "trailing data found parsing object from string");
1682
76
        }
1683
3.82k
        ++offset;
1684
3.82k
    }
1685
10.9k
    return result;
1686
11.0k
}
1687
1688
void
1689
QPDFObjectHandle::pipePageContents(Pipeline* p)
1690
0
{
1691
0
    std::string description = "page object " + getObjGen().unparse(' ');
1692
0
    std::string all_description;
1693
0
    getKey("/Contents").pipeContentStreams(p, description, all_description);
1694
0
}
1695
1696
void
1697
QPDFObjectHandle::pipeContentStreams(
1698
    Pipeline* p, std::string const& description, std::string& all_description)
1699
0
{
1700
0
    bool need_newline = false;
1701
0
    std::string buffer;
1702
0
    pl::String buf(buffer);
1703
0
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1704
0
        if (need_newline) {
1705
0
            buf.writeCStr("\n");
1706
0
        }
1707
0
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1708
0
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1709
0
            throw QPDFExc(
1710
0
                qpdf_e_damaged_pdf,
1711
0
                "content stream",
1712
0
                "content stream object " + stream.getObjGen().unparse(' '),
1713
0
                0,
1714
0
                "errors while decoding content stream");
1715
0
        }
1716
0
        need_newline = buffer.empty() || buffer.back() != '\n';
1717
0
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1718
0
        p->writeString(buffer);
1719
0
        buffer.clear();
1720
0
    }
1721
0
    p->finish();
1722
0
}
1723
1724
void
1725
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1726
0
{
1727
0
    std::string description = "page object " + getObjGen().unparse(' ');
1728
0
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1729
0
}
1730
1731
void
1732
QPDFObjectHandle::parseAsContents(ParserCallbacks* callbacks)
1733
0
{
1734
0
    std::string description = "object " + getObjGen().unparse(' ');
1735
0
    parseContentStream_internal(description, callbacks);
1736
0
}
1737
1738
void
1739
QPDFObjectHandle::filterPageContents(TokenFilter* filter, Pipeline* next)
1740
0
{
1741
0
    auto description = "token filter for page object " + getObjGen().unparse(' ');
1742
0
    Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
1743
0
    pipePageContents(&token_pipeline);
1744
0
}
1745
1746
void
1747
QPDFObjectHandle::filterAsContents(TokenFilter* filter, Pipeline* next)
1748
0
{
1749
0
    auto description = "token filter for object " + getObjGen().unparse(' ');
1750
0
    Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
1751
0
    pipeStreamData(&token_pipeline, 0, qpdf_dl_specialized);
1752
0
}
1753
1754
void
1755
QPDFObjectHandle::parseContentStream(QPDFObjectHandle stream_or_array, ParserCallbacks* callbacks)
1756
0
{
1757
0
    stream_or_array.parseContentStream_internal("content stream objects", callbacks);
1758
0
}
1759
1760
void
1761
QPDFObjectHandle::parseContentStream_internal(
1762
    std::string const& description, ParserCallbacks* callbacks)
1763
0
{
1764
0
    std::string stream_data;
1765
0
    pl::String buf(stream_data);
1766
0
    std::string all_description;
1767
0
    pipeContentStreams(&buf, description, all_description);
1768
0
    if (callbacks) {
1769
0
        callbacks->contentSize(stream_data.size());
1770
0
    }
1771
0
    try {
1772
0
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1773
0
    } catch (TerminateParsing&) {
1774
0
        return;
1775
0
    }
1776
0
    if (callbacks) {
1777
0
        callbacks->handleEOF();
1778
0
    }
1779
0
}
1780
1781
void
1782
QPDFObjectHandle::parseContentStream_data(
1783
    std::string_view stream_data,
1784
    std::string const& description,
1785
    ParserCallbacks* callbacks,
1786
    QPDF* context)
1787
0
{
1788
0
    size_t stream_length = stream_data.size();
1789
0
    auto input = is::OffsetBuffer(description, stream_data);
1790
0
    Tokenizer tokenizer;
1791
0
    tokenizer.allowEOF();
1792
0
    auto max_bad_count = Limits::parser_max_errors();
1793
0
    auto sp_description = Parser::make_description(description, "content");
1794
0
    while (QIntC::to_size(input.tell()) < stream_length) {
1795
        // Read a token and seek to the beginning. The offset we get from this process is the
1796
        // beginning of the next non-ignorable (space, comment) token. This way, the offset and
1797
        // don't including ignorable content.
1798
0
        tokenizer.nextToken(input, "content", true);
1799
0
        qpdf_offset_t offset = input.getLastOffset();
1800
0
        input.seek(offset, SEEK_SET);
1801
0
        auto [obj, empty] = Parser::parse_content(input, sp_description, tokenizer, context);
1802
0
        if (empty) {
1803
            // EOF
1804
0
            return;
1805
0
        }
1806
0
        if (!obj) {
1807
0
            if (max_bad_count && --max_bad_count == 0) {
1808
0
                Limits::error();
1809
0
                warn(
1810
0
                    context,
1811
0
                    {qpdf_e_damaged_pdf,
1812
0
                     description,
1813
0
                     "stream data",
1814
0
                     input.tell(),
1815
0
                     "limits error(parser-max-errors): too many errors during content stream "
1816
0
                     "parsing"});
1817
0
                return;
1818
0
            }
1819
0
            obj = newNull();
1820
0
        }
1821
0
        size_t length = QIntC::to_size(input.tell() - offset);
1822
0
        if (callbacks) {
1823
0
            callbacks->handleObject(obj, QIntC::to_size(offset), length);
1824
0
        }
1825
0
        if (obj.isOperator() && obj.getOperatorValue() == "ID") {
1826
            // Discard next character; it is the space after ID that terminated the token.  Read
1827
            // until end of inline image.
1828
0
            char ch;
1829
0
            input.read(&ch, 1);
1830
0
            tokenizer.expectInlineImage(input);
1831
0
            tokenizer.nextToken(input, description);
1832
0
            offset = input.getLastOffset();
1833
0
            length = QIntC::to_size(input.tell() - offset);
1834
0
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1835
0
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1836
0
                warn(
1837
0
                    context,
1838
0
                    {qpdf_e_damaged_pdf,
1839
0
                     description,
1840
0
                     "stream data",
1841
0
                     input.tell(),
1842
0
                     "EOF found while reading inline image"});
1843
0
            } else {
1844
0
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1845
0
                if (callbacks) {
1846
0
                    callbacks->handleObject(
1847
0
                        QPDFObjectHandle::newInlineImage(tokenizer.getValue()),
1848
0
                        QIntC::to_size(offset),
1849
0
                        length);
1850
0
                }
1851
0
            }
1852
0
        }
1853
0
    }
1854
0
}
1855
1856
void
1857
QPDFObjectHandle::addContentTokenFilter(std::shared_ptr<TokenFilter> filter)
1858
0
{
1859
0
    coalesceContentStreams();
1860
0
    getKey("/Contents").addTokenFilter(filter);
1861
0
}
1862
1863
void
1864
QPDFObjectHandle::addTokenFilter(std::shared_ptr<TokenFilter> filter)
1865
0
{
1866
0
    return as_stream(error).addTokenFilter(filter);
1867
0
}
1868
1869
QPDFObjectHandle
1870
QPDFObjectHandle::parse(
1871
    std::shared_ptr<InputSource> input,
1872
    std::string const& object_description,
1873
    QPDFTokenizer& tokenizer,
1874
    bool& empty,
1875
    StringDecrypter* decrypter,
1876
    QPDF* context)
1877
0
{
1878
0
    return Parser::parse(*input, object_description, tokenizer, empty, decrypter, context);
1879
0
}
1880
1881
qpdf_offset_t
1882
QPDFObjectHandle::getParsedOffset() const
1883
0
{
1884
0
    return offset();
1885
0
}
1886
1887
QPDFObjectHandle
1888
QPDFObjectHandle::newBool(bool value)
1889
706
{
1890
706
    return {QPDFObject::create<QPDF_Bool>(value)};
1891
706
}
1892
1893
QPDFObjectHandle
1894
QPDFObjectHandle::newNull()
1895
19.9k
{
1896
19.9k
    return {QPDFObject::create<QPDF_Null>()};
1897
19.9k
}
1898
1899
QPDFObjectHandle
1900
QPDFObjectHandle::newReal(std::string const& value)
1901
5.59k
{
1902
5.59k
    return {QPDFObject::create<QPDF_Real>(value)};
1903
5.59k
}
1904
1905
QPDFObjectHandle
1906
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1907
0
{
1908
0
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1909
0
}
1910
1911
QPDFObjectHandle
1912
QPDFObjectHandle::newOperator(std::string const& value)
1913
0
{
1914
0
    return {QPDFObject::create<QPDF_Operator>(value)};
1915
0
}
1916
1917
QPDFObjectHandle
1918
QPDFObjectHandle::newInlineImage(std::string const& value)
1919
0
{
1920
0
    return {QPDFObject::create<QPDF_InlineImage>(value)};
1921
0
}
1922
1923
QPDFObjectHandle
1924
QPDFObjectHandle::newArray()
1925
12.0k
{
1926
12.0k
    return newArray(std::vector<QPDFObjectHandle>());
1927
12.0k
}
1928
1929
QPDFObjectHandle
1930
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1931
12.0k
{
1932
12.0k
    return {QPDFObject::create<QPDF_Array>(items)};
1933
12.0k
}
1934
1935
QPDFObjectHandle
1936
QPDFObjectHandle::newArray(Rectangle const& rect)
1937
0
{
1938
0
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1939
0
}
1940
1941
QPDFObjectHandle
1942
QPDFObjectHandle::newArray(Matrix const& matrix)
1943
0
{
1944
0
    return newArray(
1945
0
        {newReal(matrix.a),
1946
0
         newReal(matrix.b),
1947
0
         newReal(matrix.c),
1948
0
         newReal(matrix.d),
1949
0
         newReal(matrix.e),
1950
0
         newReal(matrix.f)});
1951
0
}
1952
1953
QPDFObjectHandle
1954
QPDFObjectHandle::newArray(QPDFMatrix const& matrix)
1955
0
{
1956
0
    return newArray(
1957
0
        {newReal(matrix.a),
1958
0
         newReal(matrix.b),
1959
0
         newReal(matrix.c),
1960
0
         newReal(matrix.d),
1961
0
         newReal(matrix.e),
1962
0
         newReal(matrix.f)});
1963
0
}
1964
1965
QPDFObjectHandle
1966
QPDFObjectHandle::newFromRectangle(Rectangle const& rect)
1967
0
{
1968
0
    return newArray(rect);
1969
0
}
1970
1971
QPDFObjectHandle
1972
QPDFObjectHandle::newFromMatrix(Matrix const& m)
1973
0
{
1974
0
    return newArray(m);
1975
0
}
1976
1977
QPDFObjectHandle
1978
QPDFObjectHandle::newFromMatrix(QPDFMatrix const& m)
1979
0
{
1980
0
    return newArray(m);
1981
0
}
1982
1983
QPDFObjectHandle
1984
QPDFObjectHandle::newDictionary()
1985
30.8k
{
1986
30.8k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1987
30.8k
}
1988
1989
QPDFObjectHandle
1990
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1991
30.8k
{
1992
30.8k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1993
30.8k
}
1994
1995
QPDFObjectHandle
1996
QPDFObjectHandle::newStream(QPDF* qpdf)
1997
0
{
1998
0
    if (qpdf == nullptr) {
1999
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
2000
0
    }
2001
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
2002
0
    return qpdf->newStream();
2003
0
}
2004
2005
QPDFObjectHandle
2006
QPDFObjectHandle::newStream(QPDF* qpdf, std::shared_ptr<Buffer> data)
2007
0
{
2008
0
    if (qpdf == nullptr) {
2009
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
2010
0
    }
2011
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with data");
2012
0
    return qpdf->newStream(data);
2013
0
}
2014
2015
QPDFObjectHandle
2016
QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data)
2017
0
{
2018
0
    if (qpdf == nullptr) {
2019
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
2020
0
    }
2021
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with string");
2022
0
    return qpdf->newStream(data);
2023
0
}
2024
2025
QPDFObjectHandle
2026
QPDFObjectHandle::newReserved(QPDF* qpdf)
2027
0
{
2028
0
    if (qpdf == nullptr) {
2029
0
        throw std::runtime_error("attempt to create reserved object in null qpdf object");
2030
0
    }
2031
0
    return qpdf->newReserved();
2032
0
}
2033
2034
std::string
2035
BaseHandle::description() const
2036
0
{
2037
0
    return obj ? obj->getDescription() : ""s;
2038
0
}
2039
2040
void
2041
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
2042
3.41k
{
2043
3.41k
    if (obj) {
2044
3.41k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
2045
3.41k
        obj->setDescription(owning_qpdf, descr);
2046
3.41k
    }
2047
3.41k
}
2048
2049
bool
2050
QPDFObjectHandle::hasObjectDescription() const
2051
1.67M
{
2052
1.67M
    return obj && obj->hasDescription();
2053
1.67M
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::shallowCopy()
2057
0
{
2058
0
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
0
    return {copy()};
2062
0
}
2063
2064
QPDFObjectHandle
2065
QPDFObjectHandle::unsafeShallowCopy()
2066
0
{
2067
0
    if (!obj) {
2068
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2069
0
    }
2070
0
    return {copy(true)};
2071
0
}
2072
2073
void
2074
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2075
0
{
2076
0
    static uint32_t constexpr max_nesting = 500;
2077
0
    if (++level > max_nesting + 1) {
2078
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2079
0
    }
2080
0
    assertInitialized();
2081
2082
0
    auto cur_og = getObjGen();
2083
0
    if (!visited.add(cur_og)) {
2084
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2085
0
    }
2086
2087
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2088
0
        obj = copy(true);
2089
0
    } else if (auto a = as_array(strict)) {
2090
0
        std::vector<QPDFObjectHandle> items;
2091
0
        for (auto const& item: a) {
2092
0
            items.emplace_back(item);
2093
0
            items.back().makeDirect(level, visited, stop_at_streams);
2094
0
        }
2095
0
        obj = QPDFObject::create<QPDF_Array>(items);
2096
0
    } else if (isDictionary()) {
2097
0
        std::map<std::string, QPDFObjectHandle> items;
2098
0
        for (auto const& [key, value]: as_dictionary(strict)) {
2099
0
            if (!value.null()) {
2100
0
                items.insert({key, value});
2101
0
                items[key].makeDirect(level, visited, stop_at_streams);
2102
0
            }
2103
0
        }
2104
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2105
0
    } else if (isStream()) {
2106
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2107
0
        if (!stop_at_streams) {
2108
0
            throw std::runtime_error("attempt to make a stream into a direct object");
2109
0
        }
2110
0
    } else if (isReserved()) {
2111
0
        throw std::logic_error(
2112
0
            "QPDFObjectHandle: attempting to make a reserved object handle direct");
2113
0
    } else {
2114
0
        throw std::logic_error("QPDFObjectHandle::makeDirectInternal: unknown object type");
2115
0
    }
2116
2117
0
    visited.erase(cur_og);
2118
0
}
2119
2120
void
2121
QPDFObjectHandle::makeDirect(bool allow_streams)
2122
0
{
2123
0
    QPDFObjGen::set visited;
2124
0
    makeDirect(0, visited, allow_streams);
2125
0
}
2126
2127
void
2128
QPDFObjectHandle::assertInitialized() const
2129
0
{
2130
0
    if (!obj) {
2131
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2132
0
    }
2133
0
}
2134
2135
std::invalid_argument
2136
BaseHandle::invalid_error(std::string const& method) const
2137
0
{
2138
0
    return std::invalid_argument(method + " operation attempted on invalid object");
2139
0
}
2140
std::runtime_error
2141
BaseHandle::type_error(char const* expected_type) const
2142
0
{
2143
0
    return std::runtime_error(
2144
0
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2145
0
}
2146
2147
QPDFExc
2148
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2149
0
{
2150
0
    return {
2151
0
        qpdf_e_object,
2152
0
        "",
2153
0
        description(),
2154
0
        0,
2155
0
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2156
0
            message};
2157
0
}
2158
2159
void
2160
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2161
0
{
2162
0
    if (!obj) {
2163
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2164
0
    }
2165
0
    warn(type_error(expected_type, message));
2166
0
}
2167
2168
void
2169
QPDFObjectHandle::warnIfPossible(std::string const& warning) const
2170
0
{
2171
0
    warn(warning);
2172
0
}
2173
2174
void
2175
QPDFObjectHandle::objectWarning(std::string const& warning) const
2176
0
{
2177
0
    warn({qpdf_e_object, "", description(), 0, warning});
2178
0
}
2179
2180
void
2181
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2182
0
{
2183
0
    if (!istype) {
2184
0
        throw type_error(type_name);
2185
0
    }
2186
0
}
2187
2188
void
2189
QPDFObjectHandle::assertNull() const
2190
0
{
2191
0
    assertType("null", isNull());
2192
0
}
2193
2194
void
2195
QPDFObjectHandle::assertBool() const
2196
0
{
2197
0
    assertType("boolean", isBool());
2198
0
}
2199
2200
void
2201
QPDFObjectHandle::assertInteger() const
2202
0
{
2203
0
    assertType("integer", isInteger());
2204
0
}
2205
2206
void
2207
QPDFObjectHandle::assertReal() const
2208
0
{
2209
0
    assertType("real", isReal());
2210
0
}
2211
2212
void
2213
QPDFObjectHandle::assertName() const
2214
0
{
2215
0
    assertType("name", isName());
2216
0
}
2217
2218
void
2219
QPDFObjectHandle::assertString() const
2220
0
{
2221
0
    assertType("string", isString());
2222
0
}
2223
2224
void
2225
QPDFObjectHandle::assertOperator() const
2226
0
{
2227
0
    assertType("operator", isOperator());
2228
0
}
2229
2230
void
2231
QPDFObjectHandle::assertInlineImage() const
2232
0
{
2233
0
    assertType("inlineimage", isInlineImage());
2234
0
}
2235
2236
void
2237
QPDFObjectHandle::assertArray() const
2238
0
{
2239
0
    assertType("array", isArray());
2240
0
}
2241
2242
void
2243
QPDFObjectHandle::assertDictionary() const
2244
0
{
2245
0
    assertType("dictionary", isDictionary());
2246
0
}
2247
2248
void
2249
QPDFObjectHandle::assertStream() const
2250
0
{
2251
0
    assertType("stream", isStream());
2252
0
}
2253
2254
void
2255
QPDFObjectHandle::assertReserved() const
2256
0
{
2257
0
    assertType("reserved", isReserved());
2258
0
}
2259
2260
void
2261
QPDFObjectHandle::assertIndirect() const
2262
0
{
2263
0
    if (!isIndirect()) {
2264
0
        throw std::logic_error("operation for indirect object attempted on direct object");
2265
0
    }
2266
0
}
2267
2268
void
2269
QPDFObjectHandle::assertScalar() const
2270
0
{
2271
0
    assertType("scalar", isScalar());
2272
0
}
2273
2274
void
2275
QPDFObjectHandle::assertNumber() const
2276
0
{
2277
0
    assertType("number", isNumber());
2278
0
}
2279
2280
bool
2281
QPDFObjectHandle::isPageObject() const
2282
0
{
2283
    // See comments in QPDFObjectHandle.hh.
2284
0
    if (!qpdf()) {
2285
0
        return false;
2286
0
    }
2287
    // getAllPages repairs /Type when traversing the page tree.
2288
0
    (void)qpdf()->doc().pages().all();
2289
0
    return isDictionaryOfType("/Page");
2290
0
}
2291
2292
bool
2293
QPDFObjectHandle::isPagesObject() const
2294
0
{
2295
0
    if (!qpdf()) {
2296
0
        return false;
2297
0
    }
2298
    // getAllPages repairs /Type when traversing the page tree.
2299
0
    (void)qpdf()->doc().pages().all();
2300
0
    return isDictionaryOfType("/Pages");
2301
0
}
2302
2303
bool
2304
QPDFObjectHandle::isFormXObject() const
2305
0
{
2306
0
    return isStreamOfType("", "/Form");
2307
0
}
2308
2309
bool
2310
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2311
0
{
2312
0
    return (
2313
0
        isStreamOfType("", "/Image") &&
2314
0
        ((!exclude_imagemask) ||
2315
0
         (!(getDict().getKey("/ImageMask").isBool() &&
2316
0
            getDict().getKey("/ImageMask").getBoolValue()))));
2317
0
}
2318
2319
void
2320
QPDFObjectHandle::assertPageObject() const
2321
0
{
2322
0
    if (!isPageObject()) {
2323
0
        throw std::runtime_error("page operation called on non-Page object");
2324
0
    }
2325
0
}
2326
2327
void
2328
BaseHandle::warn(QPDF* qpdf, QPDFExc&& e)
2329
0
{
2330
0
    if (!qpdf) {
2331
0
        throw std::move(e);
2332
0
    }
2333
0
    qpdf->warn(std::move(e));
2334
0
}
2335
2336
void
2337
BaseHandle::warn(QPDFExc&& e) const
2338
0
{
2339
0
    if (!qpdf()) {
2340
0
        throw std::move(e);
2341
0
    }
2342
0
    qpdf()->warn(std::move(e));
2343
0
}
2344
2345
void
2346
BaseHandle::warn(std::string const& warning) const
2347
0
{
2348
0
    if (qpdf()) {
2349
0
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2350
0
    } else {
2351
0
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2352
0
    }
2353
0
}
2354
2355
QPDFObjectHandle::QPDFDictItems::QPDFDictItems(QPDFObjectHandle const& oh) :
2356
0
    oh(oh)
2357
0
{
2358
0
}
2359
2360
QPDFObjectHandle::QPDFDictItems::iterator&
2361
QPDFObjectHandle::QPDFDictItems::iterator::operator++()
2362
0
{
2363
0
    ++m->iter;
2364
0
    updateIValue();
2365
0
    return *this;
2366
0
}
2367
2368
QPDFObjectHandle::QPDFDictItems::iterator&
2369
QPDFObjectHandle::QPDFDictItems::iterator::operator--()
2370
0
{
2371
0
    --m->iter;
2372
0
    updateIValue();
2373
0
    return *this;
2374
0
}
2375
2376
QPDFObjectHandle::QPDFDictItems::iterator::reference
2377
QPDFObjectHandle::QPDFDictItems::iterator::operator*()
2378
0
{
2379
0
    updateIValue();
2380
0
    return ivalue;
2381
0
}
2382
2383
QPDFObjectHandle::QPDFDictItems::iterator::pointer
2384
QPDFObjectHandle::QPDFDictItems::iterator::operator->()
2385
0
{
2386
0
    updateIValue();
2387
0
    return &ivalue;
2388
0
}
2389
2390
bool
2391
QPDFObjectHandle::QPDFDictItems::iterator::operator==(iterator const& other) const
2392
0
{
2393
0
    if (m->is_end && other.m->is_end) {
2394
0
        return true;
2395
0
    }
2396
0
    if (m->is_end || other.m->is_end) {
2397
0
        return false;
2398
0
    }
2399
0
    return (ivalue.first == other.ivalue.first);
2400
0
}
2401
2402
QPDFObjectHandle::QPDFDictItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2403
0
    m(new Members(oh, for_begin))
2404
0
{
2405
0
    updateIValue();
2406
0
}
2407
2408
void
2409
QPDFObjectHandle::QPDFDictItems::iterator::updateIValue()
2410
0
{
2411
0
    m->is_end = (m->iter == m->keys.end());
2412
0
    if (m->is_end) {
2413
0
        ivalue.first = "";
2414
0
        ivalue.second = QPDFObjectHandle();
2415
0
    } else {
2416
0
        ivalue.first = *(m->iter);
2417
0
        ivalue.second = m->oh.getKey(ivalue.first);
2418
0
    }
2419
0
}
2420
2421
QPDFObjectHandle::QPDFDictItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2422
0
    oh(oh)
2423
0
{
2424
0
    keys = oh.getKeys();
2425
0
    iter = for_begin ? keys.begin() : keys.end();
2426
0
}
2427
2428
QPDFObjectHandle::QPDFDictItems::iterator
2429
QPDFObjectHandle::QPDFDictItems::begin()
2430
0
{
2431
0
    return {oh, true};
2432
0
}
2433
2434
QPDFObjectHandle::QPDFDictItems::iterator
2435
QPDFObjectHandle::QPDFDictItems::end()
2436
0
{
2437
0
    return {oh, false};
2438
0
}
2439
2440
QPDFObjectHandle::QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle const& oh) :
2441
0
    oh(oh)
2442
0
{
2443
0
}
2444
2445
QPDFObjectHandle::QPDFArrayItems::iterator&
2446
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2447
0
{
2448
0
    if (!m->is_end) {
2449
0
        ++m->item_number;
2450
0
        updateIValue();
2451
0
    }
2452
0
    return *this;
2453
0
}
2454
2455
QPDFObjectHandle::QPDFArrayItems::iterator&
2456
QPDFObjectHandle::QPDFArrayItems::iterator::operator--()
2457
0
{
2458
0
    if (m->item_number > 0) {
2459
0
        --m->item_number;
2460
0
        updateIValue();
2461
0
    }
2462
0
    return *this;
2463
0
}
2464
2465
QPDFObjectHandle::QPDFArrayItems::iterator::reference
2466
QPDFObjectHandle::QPDFArrayItems::iterator::operator*()
2467
0
{
2468
0
    updateIValue();
2469
0
    return ivalue;
2470
0
}
2471
2472
QPDFObjectHandle::QPDFArrayItems::iterator::pointer
2473
QPDFObjectHandle::QPDFArrayItems::iterator::operator->()
2474
0
{
2475
0
    updateIValue();
2476
0
    return &ivalue;
2477
0
}
2478
2479
bool
2480
QPDFObjectHandle::QPDFArrayItems::iterator::operator==(iterator const& other) const
2481
0
{
2482
0
    return (m->item_number == other.m->item_number);
2483
0
}
2484
2485
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2486
0
    m(new Members(oh, for_begin))
2487
0
{
2488
0
    updateIValue();
2489
0
}
2490
2491
void
2492
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2493
0
{
2494
0
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2495
0
    if (m->is_end) {
2496
0
        ivalue = QPDFObjectHandle();
2497
0
    } else {
2498
0
        ivalue = m->oh.getArrayItem(m->item_number);
2499
0
    }
2500
0
}
2501
2502
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2503
0
    oh(oh)
2504
0
{
2505
0
    item_number = for_begin ? 0 : oh.getArrayNItems();
2506
0
}
2507
2508
QPDFObjectHandle::QPDFArrayItems::iterator
2509
QPDFObjectHandle::QPDFArrayItems::begin()
2510
0
{
2511
0
    return {oh, true};
2512
0
}
2513
2514
QPDFObjectHandle::QPDFArrayItems::iterator
2515
QPDFObjectHandle::QPDFArrayItems::end()
2516
0
{
2517
0
    return {oh, false};
2518
0
}
2519
2520
QPDFObjGen
2521
QPDFObjectHandle::getObjGen() const
2522
69.1k
{
2523
69.1k
    return obj ? obj->getObjGen() : QPDFObjGen();
2524
69.1k
}
2525
2526
int
2527
QPDFObjectHandle::getObjectID() const
2528
38.4k
{
2529
38.4k
    return getObjGen().getObj();
2530
38.4k
}
2531
2532
int
2533
QPDFObjectHandle::getGeneration() const
2534
0
{
2535
0
    return getObjGen().getGen();
2536
0
}
2537
2538
bool
2539
QPDFObjectHandle::isIndirect() const
2540
38.4k
{
2541
38.4k
    return getObjectID() != 0;
2542
38.4k
}
2543
2544
// Indirect object accessors
2545
QPDF*
2546
QPDFObjectHandle::getOwningQPDF() const
2547
228k
{
2548
228k
    return obj ? obj->getQPDF() : nullptr;
2549
228k
}
2550
2551
QPDF&
2552
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2553
0
{
2554
0
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2555
0
        return *result;
2556
0
    }
2557
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2558
0
}
2559
2560
void
2561
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2562
0
{
2563
0
    if (obj) {
2564
0
        obj->setParsedOffset(offset);
2565
0
    }
2566
0
}
2567
2568
QPDFObjectHandle
2569
operator""_qpdf(char const* v, size_t len)
2570
0
{
2571
0
    return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal");
2572
0
}