Coverage Report

Created: 2026-05-30 06:15

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
110k
{
35
110k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
110k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
22.2k
    supports_retry(supports_retry)
47
22.2k
{
48
22.2k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
22.2k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
22.2k
}
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
2.31k
{
120
2.31k
}
121
122
void
123
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
124
4.95k
{
125
4.95k
    pipeline = p;
126
4.95k
}
127
128
void
129
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
130
4.26M
{
131
4.26M
    if (!pipeline) {
132
0
        return;
133
0
    }
134
4.26M
    if (len) {
135
4.26M
        pipeline->write(data, len);
136
4.26M
    }
137
4.26M
}
138
139
void
140
QPDFObjectHandle::TokenFilter::write(std::string const& str)
141
171k
{
142
171k
    write(str.c_str(), str.length());
143
171k
}
144
145
void
146
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
147
2.23M
{
148
2.23M
    std::string const& value = token.getRawValue();
149
2.23M
    write(value.c_str(), value.length());
150
2.23M
}
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
450k
{
228
450k
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
450k
    std::string result;
232
450k
    result += name.at(0);
233
7.25M
    for (size_t i = 1; i < name.length(); ++i) {
234
6.79M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
6.79M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
74.2k
            result += "#";
239
6.72M
        } else if (
240
6.72M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
4.51M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
4.51M
            result += util::hex_encode_char(ch);
243
4.51M
        } else {
244
2.20M
            result += ch;
245
2.20M
        }
246
6.79M
    }
247
450k
    return result;
248
450k
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
86.5k
{
253
86.5k
    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
14.3k
    case ::ot_null:
260
14.3k
        return QPDFObject::create<QPDF_Null>();
261
580
    case ::ot_boolean:
262
580
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
13.8k
    case ::ot_integer:
264
13.8k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
3.73k
    case ::ot_real:
266
3.73k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
2.57k
    case ::ot_string:
268
2.57k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
13.0k
    case ::ot_name:
270
13.0k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
278
    case ::ot_array:
272
278
        {
273
278
            auto const& a = std::get<QPDF_Array>(obj->value);
274
278
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
278
            } else {
277
278
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
278
                if (a.sp) {
279
33
                    QPDF_Array result;
280
33
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
33
                    result.sp->size = a.sp->size;
282
1.29k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
1.29k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
1.29k
                    }
285
33
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
245
                } else {
287
245
                    std::vector<QPDFObjectHandle> result;
288
245
                    result.reserve(a.elements.size());
289
4.10k
                    for (auto const& element: a.elements) {
290
4.10k
                        result.emplace_back(
291
4.10k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
4.10k
                    }
293
245
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
245
                }
295
278
            }
296
278
        }
297
38.1k
    case ::ot_dictionary:
298
38.1k
        {
299
38.1k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
38.1k
            if (shallow) {
301
37.4k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
37.4k
            } else {
303
622
                std::map<std::string, QPDFObjectHandle> new_items;
304
1.99k
                for (auto const& [key, val]: d.items) {
305
1.99k
                    new_items[key] = val.indirect() ? val : val.copy();
306
1.99k
                }
307
622
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
622
            }
309
38.1k
        }
310
0
    case ::ot_stream:
311
0
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
312
0
        throw std::runtime_error("stream objects cannot be cloned");
313
0
        return {}; // does not return
314
0
    case ::ot_operator:
315
0
        return QPDFObject::create<QPDF_Operator>(std::get<QPDF_Operator>(obj->value).val);
316
0
    case ::ot_inlineimage:
317
0
        return QPDFObject::create<QPDF_InlineImage>(std::get<QPDF_InlineImage>(obj->value).val);
318
0
    case ::ot_unresolved:
319
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
320
0
        return {}; // does not return
321
0
    case ::ot_destroyed:
322
0
        throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF");
323
0
        return {}; // does not return
324
0
    case ::ot_reference:
325
0
        return referenced_object().obj_sp();
326
86.5k
    }
327
0
    return {}; // unreachable
328
86.5k
}
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
479k
{
475
479k
    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
86.8k
    case ::ot_null:
483
86.8k
        return "null";
484
4.25k
    case ::ot_boolean:
485
4.25k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
154k
    case ::ot_integer:
487
154k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
45.3k
    case ::ot_real:
489
45.3k
        return std::get<QPDF_Real>(obj->value).val;
490
29.2k
    case ::ot_string:
491
29.2k
        return std::get<QPDF_String>(obj->value).unparse(false);
492
159k
    case ::ot_name:
493
159k
        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
479k
    }
545
0
    return {}; // unreachable
546
479k
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
0
{
551
0
    switch (resolved_type_code()) {
552
0
    case ::ot_uninitialized:
553
0
        throw std::logic_error(
554
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
555
0
        break; // unreachable
556
0
    case ::ot_null:
557
0
    case ::ot_operator:
558
0
    case ::ot_inlineimage:
559
0
        p << "null";
560
0
        break;
561
0
    case ::ot_boolean:
562
0
        p << std::get<QPDF_Bool>(obj->value).val;
563
0
        break;
564
0
    case ::ot_integer:
565
0
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
0
        break;
567
0
    case ::ot_real:
568
0
        {
569
0
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
0
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
0
            } else if (val.at(0) == '.') {
574
0
                p << "0" << val;
575
0
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
0
                p << "-0." << val.substr(2);
577
0
            } else {
578
0
                p << val;
579
0
            }
580
0
            if (val.back() == '.') {
581
0
                p << "0";
582
0
            }
583
0
        }
584
0
        break;
585
0
    case ::ot_string:
586
0
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
587
0
        break;
588
0
    case ::ot_name:
589
0
        {
590
0
            auto const& n = std::get<QPDF_Name>(obj->value);
591
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
592
            // updating this method make sure QPDF_Dictionary is also update.
593
0
            if (json_version == 1) {
594
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
595
0
            } else {
596
0
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
597
0
                    if (res.second) {
598
0
                        p << "\"" << n.name << "\"";
599
0
                    } else {
600
0
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
601
0
                    }
602
0
                } else {
603
0
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
0
                }
605
0
            }
606
0
        }
607
0
        break;
608
0
    case ::ot_array:
609
0
        {
610
0
            auto const& a = std::get<QPDF_Array>(obj->value);
611
0
            p.writeStart('[');
612
0
            if (a.sp) {
613
0
                size_t next = 0;
614
0
                for (auto& [key, value]: a.sp->elements) {
615
0
                    for (size_t j = next; j < key; ++j) {
616
0
                        p.writeNext() << "null";
617
0
                    }
618
0
                    p.writeNext();
619
0
                    auto item_og = value.id_gen();
620
0
                    if (item_og.isIndirect()) {
621
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
622
0
                    } else {
623
0
                        value.write_json(json_version, p);
624
0
                    }
625
0
                    next = key + 1;
626
0
                }
627
0
                for (size_t j = next; j < a.sp->size; ++j) {
628
0
                    p.writeNext() << "null";
629
0
                }
630
0
            } else {
631
0
                for (auto const& item: a.elements) {
632
0
                    p.writeNext();
633
0
                    auto item_og = item.id_gen();
634
0
                    if (item_og.isIndirect()) {
635
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
636
0
                    } else {
637
0
                        item.write_json(json_version, p);
638
0
                    }
639
0
                }
640
0
            }
641
0
            p.writeEnd(']');
642
0
        }
643
0
        break;
644
0
    case ::ot_dictionary:
645
0
        {
646
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
647
0
            p.writeStart('{');
648
0
            for (auto& iter: d.items) {
649
0
                if (!iter.second.null()) {
650
0
                    p.writeNext();
651
0
                    if (json_version == 1) {
652
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
653
0
                          << "\": ";
654
0
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
655
0
                        if (res.second) {
656
0
                            p << "\"" << iter.first << "\": ";
657
0
                        } else {
658
0
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
659
0
                        }
660
0
                    } else {
661
0
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
0
                          << "\": ";
663
0
                    }
664
0
                    iter.second.writeJSON(json_version, p);
665
0
                }
666
0
            }
667
0
            p.writeEnd('}');
668
0
        }
669
0
        break;
670
0
    case ::ot_stream:
671
0
        std::get<QPDF_Stream>(obj->value).m->stream_dict.writeJSON(json_version, p);
672
0
        break;
673
0
    case ::ot_reference:
674
0
        p << "\"" << obj->og.unparse(' ') << " R\"";
675
0
        break;
676
0
    default:
677
0
        throw std::logic_error("attempted to write an unsuitable object as JSON");
678
0
    }
679
0
}
680
681
void
682
BaseHandle::disconnect(bool only_direct)
683
2.48M
{
684
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
685
2.48M
    if (only_direct && indirect()) {
686
131k
        return;
687
131k
    }
688
689
2.35M
    switch (raw_type_code()) {
690
70.6k
    case ::ot_array:
691
70.6k
        {
692
70.6k
            auto& a = std::get<QPDF_Array>(obj->value);
693
70.6k
            if (a.sp) {
694
52.2k
                for (auto& item: a.sp->elements) {
695
52.2k
                    item.second.disconnect();
696
52.2k
                }
697
70.3k
            } else {
698
1.57M
                for (auto& oh: a.elements) {
699
1.57M
                    oh.disconnect();
700
1.57M
                }
701
70.3k
            }
702
70.6k
        }
703
70.6k
        break;
704
125k
    case ::ot_dictionary:
705
481k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
706
481k
            iter.second.disconnect();
707
481k
        }
708
125k
        break;
709
34.5k
    case ::ot_stream:
710
34.5k
        {
711
34.5k
            auto& s = std::get<QPDF_Stream>(obj->value);
712
34.5k
            s.m->stream_provider = nullptr;
713
34.5k
            s.m->stream_dict.disconnect();
714
34.5k
        }
715
34.5k
        break;
716
0
    case ::ot_uninitialized:
717
0
        return;
718
2.12M
    default:
719
2.12M
        break;
720
2.35M
    }
721
2.35M
    obj->qpdf = nullptr;
722
2.35M
    obj->og = QPDFObjGen();
723
2.35M
}
724
725
bool
726
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
727
183
{
728
183
    return obj == rhs.obj;
729
183
}
730
731
qpdf_object_type_e
732
QPDFObjectHandle::getTypeCode() const
733
611k
{
734
611k
    return type_code();
735
611k
}
736
737
char const*
738
BaseHandle::type_name() const
739
3.90k
{
740
3.90k
    static constexpr std::array<char const*, 16> tn{
741
3.90k
        "uninitialized",
742
3.90k
        "reserved",
743
3.90k
        "null",
744
3.90k
        "boolean",
745
3.90k
        "integer",
746
3.90k
        "real",
747
3.90k
        "string",
748
3.90k
        "name",
749
3.90k
        "array",
750
3.90k
        "dictionary",
751
3.90k
        "stream",
752
3.90k
        "operator",
753
3.90k
        "inline-image",
754
3.90k
        "unresolved",
755
3.90k
        "destroyed",
756
3.90k
        "reference"};
757
3.90k
    return tn[type_code()];
758
3.90k
}
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
55.0k
{
775
55.0k
    return type_code() == ::ot_boolean;
776
55.0k
}
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
31.4k
{
789
31.4k
    return type_code() == ::ot_null;
790
31.4k
}
791
792
bool
793
QPDFObjectHandle::isInteger() const
794
983k
{
795
983k
    return type_code() == ::ot_integer;
796
983k
}
797
798
bool
799
QPDFObjectHandle::isReal() const
800
26.5k
{
801
26.5k
    return type_code() == ::ot_real;
802
26.5k
}
803
804
bool
805
QPDFObjectHandle::isNumber() const
806
28.9k
{
807
28.9k
    return (isInteger() || isReal());
808
28.9k
}
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
63.9k
{
837
63.9k
    return type_code() == ::ot_name;
838
63.9k
}
839
840
bool
841
QPDFObjectHandle::isString() const
842
19.1k
{
843
19.1k
    return type_code() == ::ot_string;
844
19.1k
}
845
846
bool
847
QPDFObjectHandle::isOperator() const
848
0
{
849
0
    return type_code() == ::ot_operator;
850
0
}
851
852
bool
853
QPDFObjectHandle::isInlineImage() const
854
0
{
855
0
    return type_code() == ::ot_inlineimage;
856
0
}
857
858
bool
859
QPDFObjectHandle::isArray() const
860
36.5k
{
861
36.5k
    return type_code() == ::ot_array;
862
36.5k
}
863
864
bool
865
QPDFObjectHandle::isDictionary() const
866
1.45M
{
867
1.45M
    return type_code() == ::ot_dictionary;
868
1.45M
}
869
870
bool
871
QPDFObjectHandle::isStream() const
872
866k
{
873
866k
    return type_code() == ::ot_stream;
874
866k
}
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
1.05k
{
885
1.05k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
886
1.05k
}
887
888
bool
889
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
890
65.8k
{
891
65.8k
    return Name(*this) == name;
892
65.8k
}
893
894
bool
895
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
896
307k
{
897
307k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
898
17.9k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
899
307k
}
900
901
bool
902
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
903
265k
{
904
265k
    if (auto stream = as_stream()) {
905
66.5k
        return stream && (type.empty() || stream.Type() == type) &&
906
13.5k
            (subtype.empty() || stream.Subtype() == subtype);
907
66.5k
    }
908
198k
    return false;
909
265k
}
910
911
// Bool accessors
912
913
bool
914
QPDFObjectHandle::getBoolValue() const
915
2
{
916
2
    if (auto boolean = as<QPDF_Bool>()) {
917
2
        return boolean->val;
918
2
    } else {
919
0
        typeWarning("boolean", "returning false");
920
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
921
0
        return false;
922
0
    }
923
2
}
924
925
bool
926
QPDFObjectHandle::getValueAsBool(bool& value) const
927
0
{
928
0
    if (auto boolean = as<QPDF_Bool>()) {
929
0
        value = boolean->val;
930
0
        return true;
931
0
    }
932
0
    return false;
933
0
}
934
935
// Integer methods
936
937
Integer::Integer(long long value) :
938
2.36k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
939
2.36k
{
940
2.36k
}
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
911k
{
951
911k
    auto* i = as<QPDF_Integer>();
952
911k
    if (!i) {
953
1.07k
        throw invalid_error("Integer");
954
1.07k
    }
955
910k
    return i->val;
956
911k
}
957
958
long long
959
QPDFObjectHandle::getIntValue() const
960
818k
{
961
818k
    if (auto const integer = Integer(*this)) {
962
818k
        return integer;
963
818k
    } else {
964
0
        typeWarning("integer", "returning 0");
965
0
        return 0;
966
0
    }
967
818k
}
968
969
bool
970
QPDFObjectHandle::getValueAsInt(long long& value) const
971
6.90k
{
972
6.90k
    if (auto const integer = Integer(*this)) {
973
6.80k
        value = integer;
974
6.80k
        return true;
975
6.80k
    }
976
101
    return false;
977
6.90k
}
978
979
int
980
QPDFObjectHandle::getIntValueAsInt() const
981
38.9k
{
982
38.9k
    try {
983
38.9k
        return Integer(*this).value<int>();
984
38.9k
    } catch (std::invalid_argument&) {
985
17
        typeWarning("integer", "returning 0");
986
17
        return 0;
987
17
    }
988
38.9k
}
989
990
bool
991
QPDFObjectHandle::getValueAsInt(int& value) const
992
1.44k
{
993
1.44k
    if (!isInteger()) {
994
34
        return false;
995
34
    }
996
1.41k
    value = getIntValueAsInt();
997
1.41k
    return true;
998
1.44k
}
999
1000
unsigned long long
1001
QPDFObjectHandle::getUIntValue() const
1002
26.7k
{
1003
26.7k
    try {
1004
26.7k
        return Integer(*this).value<unsigned long long>();
1005
26.7k
    } catch (std::invalid_argument&) {
1006
662
        typeWarning("integer", "returning 0");
1007
662
        return 0;
1008
662
    }
1009
26.7k
}
1010
1011
bool
1012
QPDFObjectHandle::getValueAsUInt(unsigned long long& value) const
1013
0
{
1014
0
    if (!isInteger()) {
1015
0
        return false;
1016
0
    }
1017
0
    value = getUIntValue();
1018
0
    return true;
1019
0
}
1020
1021
unsigned int
1022
QPDFObjectHandle::getUIntValueAsUInt() const
1023
7.07k
{
1024
7.07k
    try {
1025
7.07k
        return Integer(*this).value<unsigned int>();
1026
7.07k
    } catch (std::invalid_argument&) {
1027
394
        typeWarning("integer", "returning 0");
1028
394
        return 0;
1029
394
    }
1030
7.07k
}
1031
1032
bool
1033
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1034
1.81k
{
1035
1.81k
    if (!isInteger()) {
1036
363
        return false;
1037
363
    }
1038
1.44k
    value = getUIntValueAsUInt();
1039
1.44k
    return true;
1040
1.81k
}
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
19.9k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1079
19.9k
{
1080
19.9k
}
1081
1082
std::string const&
1083
Name::value() const
1084
165k
{
1085
165k
    auto* n = as<QPDF_Name>();
1086
165k
    if (!n) {
1087
0
        throw invalid_error("Name");
1088
0
    }
1089
165k
    return n->name;
1090
165k
}
1091
1092
std::string
1093
QPDFObjectHandle::getName() const
1094
17.8k
{
1095
17.8k
    if (auto* name = as<QPDF_Name>()) {
1096
17.8k
        return name->name;
1097
17.8k
    }
1098
0
    typeWarning("name", "returning dummy name");
1099
0
    return "/QPDFFakeName";
1100
17.8k
}
1101
1102
bool
1103
QPDFObjectHandle::getValueAsName(std::string& value) const
1104
0
{
1105
0
    if (auto* name = as<QPDF_Name>()) {
1106
0
        value = name->name;
1107
0
        return true;
1108
0
    }
1109
0
    return false;
1110
0
}
1111
1112
// String methods
1113
1114
QPDFObjectHandle
1115
QPDFObjectHandle::newString(std::string const& str)
1116
17.7k
{
1117
17.7k
    return {QPDFObject::create<QPDF_String>(str)};
1118
17.7k
}
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
5.01k
{
1149
5.01k
    auto* s = as<QPDF_String>();
1150
5.01k
    if (!s) {
1151
0
        throw invalid_error("String");
1152
0
    }
1153
5.01k
    return s->val;
1154
5.01k
}
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
4.42k
{
1177
4.42k
    try {
1178
4.42k
        return String(obj).value();
1179
4.42k
    } catch (std::invalid_argument&) {
1180
0
        typeWarning("string", "returning empty string");
1181
0
        return {};
1182
0
    }
1183
4.42k
}
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
17.6k
{
1281
17.6k
    if (isNameAndEquals(value)) {
1282
96
        return true;
1283
17.5k
    } else if (isArray()) {
1284
33.0k
        for (auto& item: getArrayAsVector()) {
1285
33.0k
            if (item.isNameAndEquals(value)) {
1286
306
                return true;
1287
306
            }
1288
33.0k
        }
1289
5.46k
    }
1290
17.2k
    return false;
1291
17.6k
}
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
17.6k
{
1578
17.6k
    if (isIndirect()) {
1579
3
        return getObjGen().unparse(' ') + " R";
1580
17.6k
    } else {
1581
17.6k
        return unparseResolved();
1582
17.6k
    }
1583
17.6k
}
1584
1585
std::string
1586
QPDFObjectHandle::unparseResolved() const
1587
479k
{
1588
479k
    if (!obj) {
1589
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1590
0
    }
1591
479k
    return BaseHandle::unparse();
1592
479k
}
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
43
{
1654
43
    return parse(nullptr, object_str, object_description);
1655
43
}
1656
1657
QPDFObjectHandle
1658
QPDFObjectHandle::parse(
1659
    QPDF* context, std::string const& object_str, std::string const& object_description)
1660
43
{
1661
43
    auto input = is::OffsetBuffer("parsed object", object_str);
1662
43
    auto result = Parser::parse(input, object_description, context);
1663
43
    size_t offset = QIntC::to_size(input.tell());
1664
43
    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
43
    return result;
1677
43
}
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
7.99k
{
1887
7.99k
    return {QPDFObject::create<QPDF_Null>()};
1888
7.99k
}
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.58k
{
1923
6.58k
    return {QPDFObject::create<QPDF_Array>(items)};
1924
6.58k
}
1925
1926
QPDFObjectHandle
1927
QPDFObjectHandle::newArray(Rectangle const& rect)
1928
6.58k
{
1929
6.58k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1930
6.58k
}
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
14.0k
{
1977
14.0k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1978
14.0k
}
1979
1980
QPDFObjectHandle
1981
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1982
14.0k
{
1983
14.0k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1984
14.0k
}
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
59.1k
{
2028
59.1k
    return obj ? obj->getDescription() : ""s;
2029
59.1k
}
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
40.6k
{
2043
40.6k
    return obj && obj->hasDescription();
2044
40.6k
}
2045
2046
QPDFObjectHandle
2047
QPDFObjectHandle::shallowCopy()
2048
146
{
2049
146
    if (!obj) {
2050
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2051
0
    }
2052
146
    return {copy()};
2053
146
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::unsafeShallowCopy()
2057
37.4k
{
2058
37.4k
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
37.4k
    return {copy(true)};
2062
37.4k
}
2063
2064
void
2065
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2066
53.2k
{
2067
53.2k
    static uint32_t constexpr max_nesting = 500;
2068
53.2k
    if (++level > max_nesting + 1) {
2069
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2070
0
    }
2071
53.2k
    assertInitialized();
2072
2073
53.2k
    auto cur_og = getObjGen();
2074
53.2k
    if (!visited.add(cur_og)) {
2075
74
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2076
74
    }
2077
2078
53.1k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2079
42.3k
        obj = copy(true);
2080
42.3k
    } else if (auto a = as_array(strict)) {
2081
4.06k
        std::vector<QPDFObjectHandle> items;
2082
36.6k
        for (auto const& item: a) {
2083
36.6k
            items.emplace_back(item);
2084
36.6k
            items.back().makeDirect(level, visited, stop_at_streams);
2085
36.6k
        }
2086
4.06k
        obj = QPDFObject::create<QPDF_Array>(items);
2087
6.77k
    } else if (isDictionary()) {
2088
6.76k
        std::map<std::string, QPDFObjectHandle> items;
2089
19.8k
        for (auto const& [key, value]: as_dictionary(strict)) {
2090
19.8k
            if (!value.null()) {
2091
16.3k
                items.insert({key, value});
2092
16.3k
                items[key].makeDirect(level, visited, stop_at_streams);
2093
16.3k
            }
2094
19.8k
        }
2095
6.76k
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2096
6.76k
    } else if (isStream()) {
2097
3
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2098
3
        if (!stop_at_streams) {
2099
3
            throw std::runtime_error("attempt to make a stream into a direct object");
2100
3
        }
2101
3
    } 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
53.1k
    visited.erase(cur_og);
2109
53.1k
}
2110
2111
void
2112
QPDFObjectHandle::makeDirect(bool allow_streams)
2113
278
{
2114
278
    QPDFObjGen::set visited;
2115
278
    makeDirect(0, visited, allow_streams);
2116
278
}
2117
2118
void
2119
QPDFObjectHandle::assertInitialized() const
2120
53.2k
{
2121
53.2k
    if (!obj) {
2122
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2123
0
    }
2124
53.2k
}
2125
2126
std::invalid_argument
2127
BaseHandle::invalid_error(std::string const& method) const
2128
1.07k
{
2129
1.07k
    return std::invalid_argument(method + " operation attempted on invalid object");
2130
1.07k
}
2131
std::runtime_error
2132
BaseHandle::type_error(char const* expected_type) const
2133
31
{
2134
31
    return std::runtime_error(
2135
31
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2136
31
}
2137
2138
QPDFExc
2139
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2140
3.86k
{
2141
3.86k
    return {
2142
3.86k
        qpdf_e_object,
2143
3.86k
        "",
2144
3.86k
        description(),
2145
3.86k
        0,
2146
3.86k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2147
3.86k
            message};
2148
3.86k
}
2149
2150
void
2151
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2152
3.86k
{
2153
3.86k
    if (!obj) {
2154
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2155
0
    }
2156
3.86k
    warn(type_error(expected_type, message));
2157
3.86k
}
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
10
{
2168
10
    warn({qpdf_e_object, "", description(), 0, warning});
2169
10
}
2170
2171
void
2172
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2173
31
{
2174
31
    if (!istype) {
2175
31
        throw type_error(type_name);
2176
31
    }
2177
31
}
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
59.1k
{
2330
59.1k
    if (!qpdf()) {
2331
191
        throw std::move(e);
2332
191
    }
2333
58.9k
    qpdf()->warn(std::move(e));
2334
58.9k
}
2335
2336
void
2337
BaseHandle::warn(std::string const& warning) const
2338
62.3k
{
2339
62.3k
    if (qpdf()) {
2340
55.2k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2341
55.2k
    } else {
2342
7.15k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2343
7.15k
    }
2344
62.3k
}
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.96M
{
2514
1.96M
    return obj ? obj->getObjGen() : QPDFObjGen();
2515
1.96M
}
2516
2517
int
2518
QPDFObjectHandle::getObjectID() const
2519
815k
{
2520
815k
    return getObjGen().getObj();
2521
815k
}
2522
2523
int
2524
QPDFObjectHandle::getGeneration() const
2525
0
{
2526
0
    return getObjGen().getGen();
2527
0
}
2528
2529
bool
2530
QPDFObjectHandle::isIndirect() const
2531
38.6k
{
2532
38.6k
    return getObjectID() != 0;
2533
38.6k
}
2534
2535
// Indirect object accessors
2536
QPDF*
2537
QPDFObjectHandle::getOwningQPDF() const
2538
107k
{
2539
107k
    return obj ? obj->getQPDF() : nullptr;
2540
107k
}
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
43
{
2554
43
    if (obj) {
2555
43
        obj->setParsedOffset(offset);
2556
43
    }
2557
43
}
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
}