Coverage Report

Created: 2026-08-31 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/QPDFObjectHandle.cc
Line
Count
Source
1
#include <qpdf/QPDFObjectHandle_private.hh>
2
3
#include <qpdf/JSON_writer.hh>
4
#include <qpdf/Pipeline_private.hh>
5
#include <qpdf/Pl_Buffer.hh>
6
#include <qpdf/Pl_QPDFTokenizer.hh>
7
#include <qpdf/QIntC.hh>
8
#include <qpdf/QPDF.hh>
9
#include <qpdf/QPDFExc.hh>
10
#include <qpdf/QPDFLogger.hh>
11
#include <qpdf/QPDFMatrix.hh>
12
#include <qpdf/QPDFObject_private.hh>
13
#include <qpdf/QPDFPageObjectHelper.hh>
14
#include <qpdf/QPDFParser.hh>
15
#include <qpdf/QTC.hh>
16
#include <qpdf/Util.hh>
17
18
#include <array>
19
#include <cctype>
20
#include <climits>
21
#include <cstdlib>
22
#include <cstring>
23
#include <stdexcept>
24
25
using namespace std::literals;
26
using namespace qpdf;
27
28
using Parser = impl::Parser;
29
30
const Null Null::temp_;
31
32
BaseHandle::
33
operator QPDFObjGen() const
34
68.1k
{
35
68.1k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
68.1k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
22.1k
    supports_retry(supports_retry)
47
22.1k
{
48
22.1k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
22.1k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
22.1k
}
54
55
void
56
QPDFObjectHandle::StreamDataProvider::provideStreamData(QPDFObjGen const& og, Pipeline* pipeline)
57
0
{
58
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline);
59
0
}
60
61
bool
62
QPDFObjectHandle::StreamDataProvider::provideStreamData(
63
    QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
64
0
{
65
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline, suppress_warnings, will_retry);
66
0
}
67
68
void
69
QPDFObjectHandle::StreamDataProvider::provideStreamData(
70
    int objid, int generation, Pipeline* pipeline)
71
0
{
72
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
73
0
}
74
75
bool
76
QPDFObjectHandle::StreamDataProvider::provideStreamData(
77
    int objid, int generation, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
78
0
{
79
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
80
0
    return false;
81
0
}
82
83
bool
84
QPDFObjectHandle::StreamDataProvider::supportsRetry()
85
0
{
86
0
    return supports_retry;
87
0
}
88
89
namespace
90
{
91
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
92
    {
93
      public:
94
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
95
0
            containing_page(containing_page),
96
0
            old_contents(old_contents)
97
0
        {
98
0
        }
99
0
        ~CoalesceProvider() override = default;
100
        void provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override;
101
102
      private:
103
        QPDFObjectHandle containing_page;
104
        QPDFObjectHandle old_contents;
105
    };
106
} // namespace
107
108
void
109
CoalesceProvider::provideStreamData(QPDFObjGen const&, Pipeline* p)
110
0
{
111
0
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
112
0
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
113
0
    std::string all_description;
114
0
    old_contents.pipeContentStreams(p, description, all_description);
115
0
}
116
117
void
118
QPDFObjectHandle::TokenFilter::handleEOF()
119
0
{
120
0
}
121
122
void
123
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
124
0
{
125
0
    pipeline = p;
126
0
}
127
128
void
129
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
130
0
{
131
0
    if (!pipeline) {
132
0
        return;
133
0
    }
134
0
    if (len) {
135
0
        pipeline->write(data, len);
136
0
    }
137
0
}
138
139
void
140
QPDFObjectHandle::TokenFilter::write(std::string const& str)
141
0
{
142
0
    write(str.c_str(), str.length());
143
0
}
144
145
void
146
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
147
0
{
148
0
    std::string const& value = token.getRawValue();
149
0
    write(value.c_str(), value.length());
150
0
}
151
152
void
153
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle)
154
0
{
155
0
    throw std::logic_error("You must override one of the handleObject methods in ParserCallbacks");
156
0
}
157
158
void
159
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle oh, size_t, size_t)
160
0
{
161
    // This version of handleObject was added in qpdf 9. If the developer did not override it, fall
162
    // back to the older interface.
163
0
    handleObject(oh);
164
0
}
165
166
void
167
QPDFObjectHandle::ParserCallbacks::contentSize(size_t)
168
0
{
169
    // Ignore by default; overriding this is optional.
170
0
}
171
172
void
173
QPDFObjectHandle::ParserCallbacks::terminateParsing()
174
0
{
175
0
    throw TerminateParsing();
176
0
}
177
178
std::pair<bool, bool>
179
Name::analyzeJSONEncoding(const std::string& name)
180
0
{
181
0
    int tail = 0;       // Number of continuation characters expected.
182
0
    bool tail2 = false; // Potential overlong 3 octet utf-8.
183
0
    bool tail3 = false; // potential overlong 4 octet
184
0
    bool needs_escaping = false;
185
0
    for (auto const& it: name) {
186
0
        auto c = static_cast<unsigned char>(it);
187
0
        if (tail) {
188
0
            if ((c & 0xc0) != 0x80) {
189
0
                return {false, false};
190
0
            }
191
0
            if (tail2) {
192
0
                if ((c & 0xe0) == 0x80) {
193
0
                    return {false, false};
194
0
                }
195
0
                tail2 = false;
196
0
            } else if (tail3) {
197
0
                if ((c & 0xf0) == 0x80) {
198
0
                    return {false, false};
199
0
                }
200
0
                tail3 = false;
201
0
            }
202
0
            tail--;
203
0
        } else if (c < 0x80) {
204
0
            if (!needs_escaping) {
205
0
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
206
0
            }
207
0
        } else if ((c & 0xe0) == 0xc0) {
208
0
            if ((c & 0xfe) == 0xc0) {
209
0
                return {false, false};
210
0
            }
211
0
            tail = 1;
212
0
        } else if ((c & 0xf0) == 0xe0) {
213
0
            tail2 = (c == 0xe0);
214
0
            tail = 2;
215
0
        } else if ((c & 0xf8) == 0xf0) {
216
0
            tail3 = (c == 0xf0);
217
0
            tail = 3;
218
0
        } else {
219
0
            return {false, false};
220
0
        }
221
0
    }
222
0
    return {tail == 0, !needs_escaping};
223
0
}
224
225
std::string
226
Name::normalize(std::string const& name)
227
1.37M
{
228
1.37M
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
1.37M
    std::string result;
232
1.37M
    result += name.at(0);
233
40.1M
    for (size_t i = 1; i < name.length(); ++i) {
234
38.7M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
38.7M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
32.2k
            result += "#";
239
38.7M
        } else if (
240
38.7M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
22.5M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
22.5M
            result += util::hex_encode_char(ch);
243
22.5M
        } else {
244
16.2M
            result += ch;
245
16.2M
        }
246
38.7M
    }
247
1.37M
    return result;
248
1.37M
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
115k
{
253
115k
    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
5.78k
    case ::ot_null:
260
5.78k
        return QPDFObject::create<QPDF_Null>();
261
397
    case ::ot_boolean:
262
397
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
3.67k
    case ::ot_integer:
264
3.67k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
3.78k
    case ::ot_real:
266
3.78k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
1.29k
    case ::ot_string:
268
1.29k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
6.84k
    case ::ot_name:
270
6.84k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
1.10k
    case ::ot_array:
272
1.10k
        {
273
1.10k
            auto const& a = std::get<QPDF_Array>(obj->value);
274
1.10k
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
1.10k
            } else {
277
1.10k
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
1.10k
                if (a.sp) {
279
25
                    QPDF_Array result;
280
25
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
25
                    result.sp->size = a.sp->size;
282
1.36k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
1.36k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
1.36k
                    }
285
25
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
1.07k
                } else {
287
1.07k
                    std::vector<QPDFObjectHandle> result;
288
1.07k
                    result.reserve(a.elements.size());
289
6.02k
                    for (auto const& element: a.elements) {
290
6.02k
                        result.emplace_back(
291
6.02k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
6.02k
                    }
293
1.07k
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
1.07k
                }
295
1.10k
            }
296
1.10k
        }
297
92.3k
    case ::ot_dictionary:
298
92.3k
        {
299
92.3k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
92.3k
            if (shallow) {
301
91.6k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
91.6k
            } else {
303
713
                std::map<std::string, QPDFObjectHandle> new_items;
304
2.81k
                for (auto const& [key, val]: d.items) {
305
2.81k
                    new_items[key] = val.indirect() ? val : val.copy();
306
2.81k
                }
307
713
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
713
            }
309
92.3k
        }
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
115k
    }
327
0
    return {}; // unreachable
328
115k
}
329
330
// This method determines structural equivalence up to a given depth.
331
// The default depth is 10.
332
//
333
// Nomenclature note: ISO 32000-2 Annex J uses the term "equal" for this
334
// strict recursive comparison (J.4.1). We use "equivalent_to" here to
335
// implement Annex J's "equality", distinguishing it from C++ shallow
336
// pointer equality.
337
//
338
// Implementation notes:
339
//
340
// (1) We deviate from Annex J by comparing raw streams only, without
341
// decoding.
342
//
343
// (2) Loop detection is expensive and is avoided. If either object has
344
// a cycle in its forward orbit, this implementation will return false.
345
346
bool
347
BaseHandle::equivalent_to(BaseHandle const& other, int depth) const
348
0
{
349
    // A. Identity, size & limit checks
350
0
    if (obj == other.obj) {
351
0
        return true;
352
0
    }
353
0
    if (depth < 0) {
354
0
        return false;
355
0
    }
356
0
    size_t size1 = size();
357
0
    size_t size2 = other.size();
358
0
    if (size1 != size2) {
359
0
        return false;
360
0
    }
361
    // B. Structural comparison
362
0
    qpdf_object_type_e t1 = resolved_type_code();
363
0
    qpdf_object_type_e t2 = other.resolved_type_code();
364
0
    if (t1 == ::ot_reference) {
365
0
        return referenced_object().equivalent_to(other, depth - 1);
366
0
    }
367
0
    if (t2 == ::ot_reference) {
368
0
        return equivalent_to(other.referenced_object(), depth - 1);
369
0
    }
370
0
    if (t1 != t2) {
371
0
        if ((t1 == ::ot_integer || t1 == ::ot_real) && (t2 == ::ot_integer || t2 == ::ot_real)) {
372
            // Numeric equivalence per Annex J
373
0
            return oh().getNumericValue() == other.oh().getNumericValue();
374
0
        }
375
        // normalize uninitialized and null
376
0
        return (t1 == ::ot_uninitialized && t2 == ::ot_null) ||
377
0
            (t2 == ::ot_uninitialized && t1 == ::ot_null);
378
0
    }
379
0
    switch (t1) {
380
0
    case ::ot_uninitialized:
381
0
    case ::ot_null:
382
0
        return true;
383
0
    case ::ot_boolean:
384
0
        return std::get<QPDF_Bool>(obj->value).val == std::get<QPDF_Bool>(other.obj->value).val;
385
0
    case ::ot_string:
386
0
        return std::get<QPDF_String>(obj->value).val == std::get<QPDF_String>(other.obj->value).val;
387
0
    case ::ot_name:
388
0
        return std::get<QPDF_Name>(obj->value).name == std::get<QPDF_Name>(other.obj->value).name;
389
0
    case ::ot_array:
390
0
        {
391
0
            auto const& a1 = std::get<QPDF_Array>(obj->value);
392
0
            auto const& a2 = std::get<QPDF_Array>(other.obj->value);
393
            // sizes size1, size2 were calculated above and checked to be equal
394
0
            if (!a1.sp && !a2.sp) {
395
0
                for (size_t i = 0; i < size1; ++i) {
396
0
                    if (!a1.elements[i].equivalent_to(a2.elements[i], depth - 1)) {
397
0
                        return false;
398
0
                    }
399
0
                }
400
0
                return true;
401
0
            }
402
            // at least one array is sparse
403
0
            auto get_item = [](QPDF_Array const& arr, size_t idx) -> BaseHandle const& {
404
0
                if (arr.sp) {
405
0
                    auto it = arr.sp->elements.find(idx);
406
0
                    if (it == arr.sp->elements.end()) {
407
0
                        static QPDFObjectHandle null_oh = Null();
408
0
                        return null_oh;
409
0
                    }
410
0
                    return it->second;
411
0
                }
412
0
                return arr.elements[idx];
413
0
            };
414
0
            for (size_t i = 0; i < size1; ++i) {
415
0
                if (!get_item(a1, i).equivalent_to(get_item(a2, i), depth - 1)) {
416
0
                    return false;
417
0
                }
418
0
            }
419
0
            return true;
420
0
        }
421
0
    case ::ot_dictionary:
422
0
        {
423
0
            auto const& map1 = std::get<QPDF_Dictionary>(obj->value).items;
424
0
            auto const& map2 = std::get<QPDF_Dictionary>(other.obj->value).items;
425
0
            auto it2 = map2.begin();
426
0
            auto end2 = map2.end();
427
0
            for (auto const& [key1, value1]: map1) {
428
0
                if (value1.null()) {
429
0
                    continue;
430
0
                }
431
0
                while (it2 != end2 && it2->second.null()) {
432
0
                    ++it2;
433
0
                }
434
0
                if (it2 == end2 || key1 != it2->first ||
435
0
                    !value1.equivalent_to(it2->second, depth - 1)) {
436
0
                    return false;
437
0
                }
438
0
                ++it2;
439
0
            }
440
0
            while (it2 != end2 && it2->second.null()) {
441
0
                ++it2;
442
0
            }
443
0
            return it2 == end2;
444
0
        }
445
0
    case ::ot_stream:
446
0
        {
447
0
            auto const& s1 = std::get<QPDF_Stream>(obj->value);
448
0
            auto const& s2 = std::get<QPDF_Stream>(other.obj->value);
449
0
            if (!s1.m->stream_dict.equivalent_to(s2.m->stream_dict, depth - 1)) {
450
0
                return false;
451
0
            }
452
0
            return s1.m->stream_data->view() == s2.m->stream_data->view();
453
0
        }
454
0
    case ::ot_operator:
455
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_operator");
456
0
    case ::ot_inlineimage:
457
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_inlineimage");
458
0
    case ::ot_integer:
459
0
        return std::get<QPDF_Integer>(obj->value).val ==
460
0
            std::get<QPDF_Integer>(other.obj->value).val;
461
0
    case ::ot_real:
462
0
        return oh().getNumericValue() == other.oh().getNumericValue();
463
0
    case ::ot_unresolved: // cannot determine equivalence so return false
464
0
    case ::ot_reference:  // handled above
465
0
    case ::ot_destroyed:  // should not happen
466
0
    case ::ot_reserved:   // should not happen
467
0
        return false;
468
0
    }
469
0
    return false; // unreachable
470
0
}
471
472
std::string
473
BaseHandle::unparse() const
474
1.50M
{
475
1.50M
    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
263k
    case ::ot_null:
483
263k
        return "null";
484
14.2k
    case ::ot_boolean:
485
14.2k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
539k
    case ::ot_integer:
487
539k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
149k
    case ::ot_real:
489
149k
        return std::get<QPDF_Real>(obj->value).val;
490
19.1k
    case ::ot_string:
491
19.1k
        return std::get<QPDF_String>(obj->value).unparse(false);
492
517k
    case ::ot_name:
493
517k
        return Name::normalize(std::get<QPDF_Name>(obj->value).name);
494
0
    case ::ot_array:
495
0
        {
496
0
            auto const& a = std::get<QPDF_Array>(obj->value);
497
0
            std::string result = "[ ";
498
0
            if (a.sp) {
499
0
                size_t next = 0;
500
0
                for (auto& [key, value]: a.sp->elements) {
501
0
                    for (size_t j = next; j < key; ++j) {
502
0
                        result += "null ";
503
0
                    }
504
0
                    result += value.unparse() + " ";
505
0
                    next = key + 1;
506
0
                }
507
0
                for (size_t j = next; j < a.sp->size; ++j) {
508
0
                    result += "null ";
509
0
                }
510
0
            } else {
511
0
                for (auto const& item: a.elements) {
512
0
                    result += item.unparse() + " ";
513
0
                }
514
0
            }
515
0
            result += "]";
516
0
            return result;
517
0
        }
518
0
    case ::ot_dictionary:
519
0
        {
520
0
            auto const& items = std::get<QPDF_Dictionary>(obj->value).items;
521
0
            std::string result = "<< ";
522
0
            for (auto& iter: items) {
523
0
                if (!iter.second.null()) {
524
0
                    result += Name::normalize(iter.first) + " " + iter.second.unparse() + " ";
525
0
                }
526
0
            }
527
0
            result += ">>";
528
0
            return result;
529
0
        }
530
0
    case ::ot_stream:
531
0
        return obj->og.unparse(' ') + " R";
532
0
    case ::ot_operator:
533
0
        return std::get<QPDF_Operator>(obj->value).val;
534
0
    case ::ot_inlineimage:
535
0
        return std::get<QPDF_InlineImage>(obj->value).val;
536
0
    case ::ot_unresolved:
537
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object");
538
0
        return ""; // does not return
539
0
    case ::ot_destroyed:
540
0
        throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF");
541
0
        return ""; // does not return
542
0
    case ::ot_reference:
543
0
        return obj->og.unparse(' ') + " R";
544
1.50M
    }
545
0
    return {}; // unreachable
546
1.50M
}
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
2.56M
{
693
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
694
2.56M
    if (only_direct && indirect()) {
695
248k
        return;
696
248k
    }
697
698
2.31M
    switch (raw_type_code()) {
699
118k
    case ::ot_array:
700
118k
        {
701
118k
            auto& a = std::get<QPDF_Array>(obj->value);
702
118k
            if (a.sp) {
703
35.3k
                for (auto& item: a.sp->elements) {
704
35.3k
                    item.second.disconnect();
705
35.3k
                }
706
118k
            } else {
707
1.03M
                for (auto& oh: a.elements) {
708
1.03M
                    oh.disconnect();
709
1.03M
                }
710
118k
            }
711
118k
        }
712
118k
        break;
713
212k
    case ::ot_dictionary:
714
823k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
715
823k
            iter.second.disconnect();
716
823k
        }
717
212k
        break;
718
41.4k
    case ::ot_stream:
719
41.4k
        {
720
41.4k
            auto& s = std::get<QPDF_Stream>(obj->value);
721
41.4k
            s.m->stream_provider = nullptr;
722
41.4k
            s.m->stream_dict.disconnect();
723
41.4k
        }
724
41.4k
        break;
725
0
    case ::ot_uninitialized:
726
0
        return;
727
1.94M
    default:
728
1.94M
        break;
729
2.31M
    }
730
2.31M
    obj->qpdf = nullptr;
731
2.31M
    obj->og = QPDFObjGen();
732
2.31M
}
733
734
bool
735
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
736
1.03k
{
737
1.03k
    return obj == rhs.obj;
738
1.03k
}
739
740
qpdf_object_type_e
741
QPDFObjectHandle::getTypeCode() const
742
1.99M
{
743
1.99M
    return type_code();
744
1.99M
}
745
746
char const*
747
BaseHandle::type_name() const
748
10.4k
{
749
10.4k
    static constexpr std::array<char const*, 16> tn{
750
10.4k
        "uninitialized",
751
10.4k
        "reserved",
752
10.4k
        "null",
753
10.4k
        "boolean",
754
10.4k
        "integer",
755
10.4k
        "real",
756
10.4k
        "string",
757
10.4k
        "name",
758
10.4k
        "array",
759
10.4k
        "dictionary",
760
10.4k
        "stream",
761
10.4k
        "operator",
762
10.4k
        "inline-image",
763
10.4k
        "unresolved",
764
10.4k
        "destroyed",
765
10.4k
        "reference"};
766
10.4k
    return tn[type_code()];
767
10.4k
}
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
26.9k
{
784
26.9k
    return type_code() == ::ot_boolean;
785
26.9k
}
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
20.5k
{
798
20.5k
    return type_code() == ::ot_null;
799
20.5k
}
800
801
bool
802
QPDFObjectHandle::isInteger() const
803
154k
{
804
154k
    return type_code() == ::ot_integer;
805
154k
}
806
807
bool
808
QPDFObjectHandle::isReal() const
809
21.3k
{
810
21.3k
    return type_code() == ::ot_real;
811
21.3k
}
812
813
bool
814
QPDFObjectHandle::isNumber() const
815
31.6k
{
816
31.6k
    return (isInteger() || isReal());
817
31.6k
}
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
76.0k
{
846
76.0k
    return type_code() == ::ot_name;
847
76.0k
}
848
849
bool
850
QPDFObjectHandle::isString() const
851
14.9k
{
852
14.9k
    return type_code() == ::ot_string;
853
14.9k
}
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
4.09M
{
870
4.09M
    return type_code() == ::ot_array;
871
4.09M
}
872
873
bool
874
QPDFObjectHandle::isDictionary() const
875
10.2M
{
876
10.2M
    return type_code() == ::ot_dictionary;
877
10.2M
}
878
879
bool
880
QPDFObjectHandle::isStream() const
881
4.77M
{
882
4.77M
    return type_code() == ::ot_stream;
883
4.77M
}
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
2.89k
{
894
2.89k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
895
2.89k
}
896
897
bool
898
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
899
78.0k
{
900
78.0k
    return Name(*this) == name;
901
78.0k
}
902
903
bool
904
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
905
4.87M
{
906
4.87M
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
907
112k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
908
4.87M
}
909
910
bool
911
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
912
99.5k
{
913
99.5k
    if (auto stream = as_stream()) {
914
35.4k
        return stream && (type.empty() || stream.Type() == type) &&
915
12.9k
            (subtype.empty() || stream.Subtype() == subtype);
916
35.4k
    }
917
64.1k
    return false;
918
99.5k
}
919
920
// Bool accessors
921
922
bool
923
QPDFObjectHandle::getBoolValue() const
924
15
{
925
15
    if (auto boolean = as<QPDF_Bool>()) {
926
15
        return boolean->val;
927
15
    } else {
928
0
        typeWarning("boolean", "returning false");
929
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
930
0
        return false;
931
0
    }
932
15
}
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
2.33k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
948
2.33k
{
949
2.33k
}
950
951
QPDFObjectHandle
952
QPDFObjectHandle::newInteger(long long value)
953
0
{
954
0
    return {QPDFObject::create<QPDF_Integer>(value)};
955
0
}
956
957
int64_t
958
Integer::value() const
959
158k
{
960
158k
    auto* i = as<QPDF_Integer>();
961
158k
    if (!i) {
962
889
        throw invalid_error("Integer");
963
889
    }
964
157k
    return i->val;
965
158k
}
966
967
long long
968
QPDFObjectHandle::getIntValue() const
969
10.2k
{
970
10.2k
    if (auto const integer = Integer(*this)) {
971
10.2k
        return integer;
972
10.2k
    } else {
973
0
        typeWarning("integer", "returning 0");
974
0
        return 0;
975
0
    }
976
10.2k
}
977
978
bool
979
QPDFObjectHandle::getValueAsInt(long long& value) const
980
6.67k
{
981
6.67k
    if (auto const integer = Integer(*this)) {
982
6.58k
        value = integer;
983
6.58k
        return true;
984
6.58k
    }
985
91
    return false;
986
6.67k
}
987
988
int
989
QPDFObjectHandle::getIntValueAsInt() const
990
39.2k
{
991
39.2k
    try {
992
39.2k
        return Integer(*this).value<int>();
993
39.2k
    } catch (std::invalid_argument&) {
994
30
        typeWarning("integer", "returning 0");
995
30
        return 0;
996
30
    }
997
39.2k
}
998
999
bool
1000
QPDFObjectHandle::getValueAsInt(int& value) const
1001
1.80k
{
1002
1.80k
    if (!isInteger()) {
1003
49
        return false;
1004
49
    }
1005
1.76k
    value = getIntValueAsInt();
1006
1.76k
    return true;
1007
1.80k
}
1008
1009
unsigned long long
1010
QPDFObjectHandle::getUIntValue() const
1011
36.4k
{
1012
36.4k
    try {
1013
36.4k
        return Integer(*this).value<unsigned long long>();
1014
36.4k
    } catch (std::invalid_argument&) {
1015
449
        typeWarning("integer", "returning 0");
1016
449
        return 0;
1017
449
    }
1018
36.4k
}
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
7.68k
{
1033
7.68k
    try {
1034
7.68k
        return Integer(*this).value<unsigned int>();
1035
7.68k
    } catch (std::invalid_argument&) {
1036
410
        typeWarning("integer", "returning 0");
1037
410
        return 0;
1038
410
    }
1039
7.68k
}
1040
1041
bool
1042
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1043
2.28k
{
1044
2.28k
    if (!isInteger()) {
1045
472
        return false;
1046
472
    }
1047
1.81k
    value = getUIntValueAsUInt();
1048
1.81k
    return true;
1049
2.28k
}
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
0
{
1078
0
    return {QPDFObject::create<QPDF_Name>(name)};
1079
0
}
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
18.2k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1088
18.2k
{
1089
18.2k
}
1090
1091
std::string const&
1092
Name::value() const
1093
405k
{
1094
405k
    auto* n = as<QPDF_Name>();
1095
405k
    if (!n) {
1096
0
        throw invalid_error("Name");
1097
0
    }
1098
405k
    return n->name;
1099
405k
}
1100
1101
std::string
1102
QPDFObjectHandle::getName() const
1103
15.8k
{
1104
15.8k
    if (auto* name = as<QPDF_Name>()) {
1105
15.8k
        return name->name;
1106
15.8k
    }
1107
0
    typeWarning("name", "returning dummy name");
1108
0
    return "/QPDFFakeName";
1109
15.8k
}
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
23
{
1126
23
    return {QPDFObject::create<QPDF_String>(str)};
1127
23
}
1128
1129
QPDFObjectHandle
1130
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1131
0
{
1132
0
    return {String::utf16(utf8_str).obj_sp()};
1133
0
}
1134
1135
String::String(std::string const& str) :
1136
0
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1137
0
{
1138
0
}
1139
1140
String::String(std::string&& str) :
1141
0
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1142
0
{
1143
0
}
1144
1145
String
1146
String::utf16(std::string const& utf8_str)
1147
0
{
1148
0
    std::string result;
1149
0
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1150
0
        return String(result);
1151
0
    }
1152
0
    return String(QUtil::utf8_to_utf16(utf8_str));
1153
0
}
1154
1155
std::string const&
1156
String::value() const
1157
39.7k
{
1158
39.7k
    auto* s = as<QPDF_String>();
1159
39.7k
    if (!s) {
1160
0
        throw invalid_error("String");
1161
0
    }
1162
39.7k
    return s->val;
1163
39.7k
}
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
36.9k
{
1186
36.9k
    try {
1187
36.9k
        return String(obj).value();
1188
36.9k
    } catch (std::invalid_argument&) {
1189
0
        typeWarning("string", "returning empty string");
1190
0
        return {};
1191
0
    }
1192
36.9k
}
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
29.6k
{
1290
29.6k
    if (isNameAndEquals(value)) {
1291
114
        return true;
1292
29.5k
    } else if (isArray()) {
1293
34.5k
        for (auto& item: getArrayAsVector()) {
1294
34.5k
            if (item.isNameAndEquals(value)) {
1295
541
                return true;
1296
541
            }
1297
34.5k
        }
1298
13.6k
    }
1299
28.9k
    return false;
1300
29.6k
}
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
22
{
1587
22
    if (isIndirect()) {
1588
22
        return getObjGen().unparse(' ') + " R";
1589
22
    } else {
1590
0
        return unparseResolved();
1591
0
    }
1592
22
}
1593
1594
std::string
1595
QPDFObjectHandle::unparseResolved() const
1596
1.50M
{
1597
1.50M
    if (!obj) {
1598
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1599
0
    }
1600
1.50M
    return BaseHandle::unparse();
1601
1.50M
}
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
14.6k
{
1663
14.6k
    return parse(nullptr, object_str, object_description);
1664
14.6k
}
1665
1666
QPDFObjectHandle
1667
QPDFObjectHandle::parse(
1668
    QPDF* context, std::string const& object_str, std::string const& object_description)
1669
14.6k
{
1670
14.6k
    auto input = is::OffsetBuffer("parsed object", object_str);
1671
14.6k
    auto result = Parser::parse(input, object_description, context);
1672
14.6k
    size_t offset = QIntC::to_size(input.tell());
1673
14.6k
    while (offset < object_str.length()) {
1674
0
        if (!isspace(object_str.at(offset))) {
1675
0
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1676
0
            throw QPDFExc(
1677
0
                qpdf_e_damaged_pdf,
1678
0
                "parsed object",
1679
0
                object_description,
1680
0
                input.getLastOffset(),
1681
0
                "trailing data found parsing object from string");
1682
0
        }
1683
0
        ++offset;
1684
0
    }
1685
14.6k
    return result;
1686
14.6k
}
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
0
{
1890
0
    return {QPDFObject::create<QPDF_Bool>(value)};
1891
0
}
1892
1893
QPDFObjectHandle
1894
QPDFObjectHandle::newNull()
1895
1.97k
{
1896
1.97k
    return {QPDFObject::create<QPDF_Null>()};
1897
1.97k
}
1898
1899
QPDFObjectHandle
1900
QPDFObjectHandle::newReal(std::string const& value)
1901
0
{
1902
0
    return {QPDFObject::create<QPDF_Real>(value)};
1903
0
}
1904
1905
QPDFObjectHandle
1906
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1907
29.6k
{
1908
29.6k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1909
29.6k
}
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
0
{
1926
0
    return newArray(std::vector<QPDFObjectHandle>());
1927
0
}
1928
1929
QPDFObjectHandle
1930
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1931
7.41k
{
1932
7.41k
    return {QPDFObject::create<QPDF_Array>(items)};
1933
7.41k
}
1934
1935
QPDFObjectHandle
1936
QPDFObjectHandle::newArray(Rectangle const& rect)
1937
7.41k
{
1938
7.41k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1939
7.41k
}
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
29.2k
{
1986
29.2k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1987
29.2k
}
1988
1989
QPDFObjectHandle
1990
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1991
29.2k
{
1992
29.2k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1993
29.2k
}
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
76.8k
{
2037
76.8k
    return obj ? obj->getDescription() : ""s;
2038
76.8k
}
2039
2040
void
2041
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
2042
0
{
2043
0
    if (obj) {
2044
0
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
2045
0
        obj->setDescription(owning_qpdf, descr);
2046
0
    }
2047
0
}
2048
2049
bool
2050
QPDFObjectHandle::hasObjectDescription() const
2051
47.9k
{
2052
47.9k
    return obj && obj->hasDescription();
2053
47.9k
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::shallowCopy()
2057
232
{
2058
232
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
232
    return {copy()};
2062
232
}
2063
2064
QPDFObjectHandle
2065
QPDFObjectHandle::unsafeShallowCopy()
2066
91.6k
{
2067
91.6k
    if (!obj) {
2068
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2069
0
    }
2070
91.6k
    return {copy(true)};
2071
91.6k
}
2072
2073
void
2074
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2075
23.4k
{
2076
23.4k
    static uint32_t constexpr max_nesting = 500;
2077
23.4k
    if (++level > max_nesting + 1) {
2078
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2079
0
    }
2080
23.4k
    assertInitialized();
2081
2082
23.4k
    auto cur_og = getObjGen();
2083
23.4k
    if (!visited.add(cur_og)) {
2084
17
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2085
17
    }
2086
2087
23.4k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2088
14.9k
        obj = copy(true);
2089
14.9k
    } else if (auto a = as_array(strict)) {
2090
5.52k
        std::vector<QPDFObjectHandle> items;
2091
16.3k
        for (auto const& item: a) {
2092
16.3k
            items.emplace_back(item);
2093
16.3k
            items.back().makeDirect(level, visited, stop_at_streams);
2094
16.3k
        }
2095
5.52k
        obj = QPDFObject::create<QPDF_Array>(items);
2096
5.52k
    } else if (isDictionary()) {
2097
2.94k
        std::map<std::string, QPDFObjectHandle> items;
2098
8.90k
        for (auto const& [key, value]: as_dictionary(strict)) {
2099
8.90k
            if (!value.null()) {
2100
6.93k
                items.insert({key, value});
2101
6.93k
                items[key].makeDirect(level, visited, stop_at_streams);
2102
6.93k
            }
2103
8.90k
        }
2104
2.94k
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2105
2.94k
    } else if (isStream()) {
2106
2
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2107
2
        if (!stop_at_streams) {
2108
2
            throw std::runtime_error("attempt to make a stream into a direct object");
2109
2
        }
2110
2
    } 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
23.4k
    visited.erase(cur_og);
2118
23.4k
}
2119
2120
void
2121
QPDFObjectHandle::makeDirect(bool allow_streams)
2122
169
{
2123
169
    QPDFObjGen::set visited;
2124
169
    makeDirect(0, visited, allow_streams);
2125
169
}
2126
2127
void
2128
QPDFObjectHandle::assertInitialized() const
2129
23.4k
{
2130
23.4k
    if (!obj) {
2131
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2132
0
    }
2133
23.4k
}
2134
2135
std::invalid_argument
2136
BaseHandle::invalid_error(std::string const& method) const
2137
889
{
2138
889
    return std::invalid_argument(method + " operation attempted on invalid object");
2139
889
}
2140
std::runtime_error
2141
BaseHandle::type_error(char const* expected_type) const
2142
151
{
2143
151
    return std::runtime_error(
2144
151
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2145
151
}
2146
2147
QPDFExc
2148
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2149
10.2k
{
2150
10.2k
    return {
2151
10.2k
        qpdf_e_object,
2152
10.2k
        "",
2153
10.2k
        description(),
2154
10.2k
        0,
2155
10.2k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2156
10.2k
            message};
2157
10.2k
}
2158
2159
void
2160
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2161
10.2k
{
2162
10.2k
    if (!obj) {
2163
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2164
0
    }
2165
10.2k
    warn(type_error(expected_type, message));
2166
10.2k
}
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
16
{
2177
16
    warn({qpdf_e_object, "", description(), 0, warning});
2178
16
}
2179
2180
void
2181
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2182
151
{
2183
151
    if (!istype) {
2184
151
        throw type_error(type_name);
2185
151
    }
2186
151
}
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
76.8k
{
2339
76.8k
    if (!qpdf()) {
2340
468
        throw std::move(e);
2341
468
    }
2342
76.4k
    qpdf()->warn(std::move(e));
2343
76.4k
}
2344
2345
void
2346
BaseHandle::warn(std::string const& warning) const
2347
75.3k
{
2348
75.3k
    if (qpdf()) {
2349
66.5k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2350
66.5k
    } else {
2351
8.76k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2352
8.76k
    }
2353
75.3k
}
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
5.25M
{
2523
5.25M
    return obj ? obj->getObjGen() : QPDFObjGen();
2524
5.25M
}
2525
2526
int
2527
QPDFObjectHandle::getObjectID() const
2528
2.03M
{
2529
2.03M
    return getObjGen().getObj();
2530
2.03M
}
2531
2532
int
2533
QPDFObjectHandle::getGeneration() const
2534
0
{
2535
0
    return getObjGen().getGen();
2536
0
}
2537
2538
bool
2539
QPDFObjectHandle::isIndirect() const
2540
24.8k
{
2541
24.8k
    return getObjectID() != 0;
2542
24.8k
}
2543
2544
// Indirect object accessors
2545
QPDF*
2546
QPDFObjectHandle::getOwningQPDF() const
2547
176k
{
2548
176k
    return obj ? obj->getQPDF() : nullptr;
2549
176k
}
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
23
{
2563
23
    if (obj) {
2564
23
        obj->setParsedOffset(offset);
2565
23
    }
2566
23
}
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
}