Coverage Report

Created: 2026-08-13 07:11

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
134k
{
35
134k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
134k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
20.1k
    supports_retry(supports_retry)
47
20.1k
{
48
20.1k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
20.1k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
20.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
0
{
228
0
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
0
    std::string result;
232
0
    result += name.at(0);
233
0
    for (size_t i = 1; i < name.length(); ++i) {
234
0
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
0
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
0
            result += "#";
239
0
        } else if (
240
0
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
0
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
0
            result += util::hex_encode_char(ch);
243
0
        } else {
244
0
            result += ch;
245
0
        }
246
0
    }
247
0
    return result;
248
0
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
87.9k
{
253
87.9k
    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
573
    case ::ot_null:
260
573
        return QPDFObject::create<QPDF_Null>();
261
163
    case ::ot_boolean:
262
163
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
1.12k
    case ::ot_integer:
264
1.12k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
492
    case ::ot_real:
266
492
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
1.74k
    case ::ot_string:
268
1.74k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
82.9k
    case ::ot_name:
270
82.9k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
320
    case ::ot_array:
272
320
        {
273
320
            auto const& a = std::get<QPDF_Array>(obj->value);
274
320
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
320
            } else {
277
320
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
320
                if (a.sp) {
279
23
                    QPDF_Array result;
280
23
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
23
                    result.sp->size = a.sp->size;
282
5.10k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
5.10k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
5.10k
                    }
285
23
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
297
                } else {
287
297
                    std::vector<QPDFObjectHandle> result;
288
297
                    result.reserve(a.elements.size());
289
82.1k
                    for (auto const& element: a.elements) {
290
82.1k
                        result.emplace_back(
291
82.1k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
82.1k
                    }
293
297
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
297
                }
295
320
            }
296
320
        }
297
548
    case ::ot_dictionary:
298
548
        {
299
548
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
548
            if (shallow) {
301
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
548
            } else {
303
548
                std::map<std::string, QPDFObjectHandle> new_items;
304
2.41k
                for (auto const& [key, val]: d.items) {
305
2.41k
                    new_items[key] = val.indirect() ? val : val.copy();
306
2.41k
                }
307
548
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
548
            }
309
548
        }
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
87.9k
    }
327
0
    return {}; // unreachable
328
87.9k
}
329
330
// This method determines structural equivalence up to a given depth.
331
// The default depth is 10.
332
//
333
// Nomenclature note: ISO 32000-2 Annex J uses the term "equal" for this
334
// strict recursive comparison (J.4.1). We use "equivalent_to" here to
335
// implement Annex J's "equality", distinguishing it from C++ shallow
336
// pointer equality.
337
//
338
// Implementation notes:
339
//
340
// (1) We deviate from Annex J by comparing raw streams only, without
341
// decoding.
342
//
343
// (2) Loop detection is expensive and is avoided. If either object has
344
// a cycle in its forward orbit, this implementation will return false.
345
346
bool
347
BaseHandle::equivalent_to(BaseHandle const& other, int depth) const
348
0
{
349
    // A. Identity, size & limit checks
350
0
    if (obj == other.obj) {
351
0
        return true;
352
0
    }
353
0
    if (depth < 0) {
354
0
        return false;
355
0
    }
356
0
    size_t size1 = size();
357
0
    size_t size2 = other.size();
358
0
    if (size1 != size2) {
359
0
        return false;
360
0
    }
361
    // B. Structural comparison
362
0
    qpdf_object_type_e t1 = resolved_type_code();
363
0
    qpdf_object_type_e t2 = other.resolved_type_code();
364
0
    if (t1 == ::ot_reference) {
365
0
        return referenced_object().equivalent_to(other, depth - 1);
366
0
    }
367
0
    if (t2 == ::ot_reference) {
368
0
        return equivalent_to(other.referenced_object(), depth - 1);
369
0
    }
370
0
    if (t1 != t2) {
371
0
        if ((t1 == ::ot_integer || t1 == ::ot_real) && (t2 == ::ot_integer || t2 == ::ot_real)) {
372
            // Numeric equivalence per Annex J
373
0
            return oh().getNumericValue() == other.oh().getNumericValue();
374
0
        }
375
        // normalize uninitialized and null
376
0
        return (t1 == ::ot_uninitialized && t2 == ::ot_null) ||
377
0
            (t2 == ::ot_uninitialized && t1 == ::ot_null);
378
0
    }
379
0
    switch (t1) {
380
0
    case ::ot_uninitialized:
381
0
    case ::ot_null:
382
0
        return true;
383
0
    case ::ot_boolean:
384
0
        return std::get<QPDF_Bool>(obj->value).val == std::get<QPDF_Bool>(other.obj->value).val;
385
0
    case ::ot_string:
386
0
        return std::get<QPDF_String>(obj->value).val == std::get<QPDF_String>(other.obj->value).val;
387
0
    case ::ot_name:
388
0
        return std::get<QPDF_Name>(obj->value).name == std::get<QPDF_Name>(other.obj->value).name;
389
0
    case ::ot_array:
390
0
        {
391
0
            auto const& a1 = std::get<QPDF_Array>(obj->value);
392
0
            auto const& a2 = std::get<QPDF_Array>(other.obj->value);
393
            // sizes size1, size2 were calculated above and checked to be equal
394
0
            if (!a1.sp && !a2.sp) {
395
0
                for (size_t i = 0; i < size1; ++i) {
396
0
                    if (!a1.elements[i].equivalent_to(a2.elements[i], depth - 1)) {
397
0
                        return false;
398
0
                    }
399
0
                }
400
0
                return true;
401
0
            }
402
            // at least one array is sparse
403
0
            auto get_item = [](QPDF_Array const& arr, size_t idx) -> BaseHandle const& {
404
0
                if (arr.sp) {
405
0
                    auto it = arr.sp->elements.find(idx);
406
0
                    if (it == arr.sp->elements.end()) {
407
0
                        static QPDFObjectHandle null_oh = Null();
408
0
                        return null_oh;
409
0
                    }
410
0
                    return it->second;
411
0
                }
412
0
                return arr.elements[idx];
413
0
            };
414
0
            for (size_t i = 0; i < size1; ++i) {
415
0
                if (!get_item(a1, i).equivalent_to(get_item(a2, i), depth - 1)) {
416
0
                    return false;
417
0
                }
418
0
            }
419
0
            return true;
420
0
        }
421
0
    case ::ot_dictionary:
422
0
        {
423
0
            auto const& map1 = std::get<QPDF_Dictionary>(obj->value).items;
424
0
            auto const& map2 = std::get<QPDF_Dictionary>(other.obj->value).items;
425
0
            auto it2 = map2.begin();
426
0
            auto end2 = map2.end();
427
0
            for (auto const& [key1, value1]: map1) {
428
0
                if (value1.null()) {
429
0
                    continue;
430
0
                }
431
0
                while (it2 != end2 && it2->second.null()) {
432
0
                    ++it2;
433
0
                }
434
0
                if (it2 == end2 || key1 != it2->first ||
435
0
                    !value1.equivalent_to(it2->second, depth - 1)) {
436
0
                    return false;
437
0
                }
438
0
                ++it2;
439
0
            }
440
0
            while (it2 != end2 && it2->second.null()) {
441
0
                ++it2;
442
0
            }
443
0
            return it2 == end2;
444
0
        }
445
0
    case ::ot_stream:
446
0
        {
447
0
            auto const& s1 = std::get<QPDF_Stream>(obj->value);
448
0
            auto const& s2 = std::get<QPDF_Stream>(other.obj->value);
449
0
            if (!s1.m->stream_dict.equivalent_to(s2.m->stream_dict, depth - 1)) {
450
0
                return false;
451
0
            }
452
0
            return s1.m->stream_data->view() == s2.m->stream_data->view();
453
0
        }
454
0
    case ::ot_operator:
455
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_operator");
456
0
    case ::ot_inlineimage:
457
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_inlineimage");
458
0
    case ::ot_integer:
459
0
        return std::get<QPDF_Integer>(obj->value).val ==
460
0
            std::get<QPDF_Integer>(other.obj->value).val;
461
0
    case ::ot_real:
462
0
        return oh().getNumericValue() == other.oh().getNumericValue();
463
0
    case ::ot_unresolved: // cannot determine equivalence so return false
464
0
    case ::ot_reference:  // handled above
465
0
    case ::ot_destroyed:  // should not happen
466
0
    case ::ot_reserved:   // should not happen
467
0
        return false;
468
0
    }
469
0
    return false; // unreachable
470
0
}
471
472
std::string
473
BaseHandle::unparse() const
474
0
{
475
0
    switch (resolved_type_code()) {
476
0
    case ::ot_uninitialized:
477
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object");
478
0
        return ""; // does not return
479
0
    case ::ot_reserved:
480
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
481
0
        return ""; // does not return
482
0
    case ::ot_null:
483
0
        return "null";
484
0
    case ::ot_boolean:
485
0
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
0
    case ::ot_integer:
487
0
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
0
    case ::ot_real:
489
0
        return std::get<QPDF_Real>(obj->value).val;
490
0
    case ::ot_string:
491
0
        return std::get<QPDF_String>(obj->value).unparse(false);
492
0
    case ::ot_name:
493
0
        return Name::normalize(std::get<QPDF_Name>(obj->value).name);
494
0
    case ::ot_array:
495
0
        {
496
0
            auto const& a = std::get<QPDF_Array>(obj->value);
497
0
            std::string result = "[ ";
498
0
            if (a.sp) {
499
0
                size_t next = 0;
500
0
                for (auto& [key, value]: a.sp->elements) {
501
0
                    for (size_t j = next; j < key; ++j) {
502
0
                        result += "null ";
503
0
                    }
504
0
                    result += value.unparse() + " ";
505
0
                    next = key + 1;
506
0
                }
507
0
                for (size_t j = next; j < a.sp->size; ++j) {
508
0
                    result += "null ";
509
0
                }
510
0
            } else {
511
0
                for (auto const& item: a.elements) {
512
0
                    result += item.unparse() + " ";
513
0
                }
514
0
            }
515
0
            result += "]";
516
0
            return result;
517
0
        }
518
0
    case ::ot_dictionary:
519
0
        {
520
0
            auto const& items = std::get<QPDF_Dictionary>(obj->value).items;
521
0
            std::string result = "<< ";
522
0
            for (auto& iter: items) {
523
0
                if (!iter.second.null()) {
524
0
                    result += Name::normalize(iter.first) + " " + iter.second.unparse() + " ";
525
0
                }
526
0
            }
527
0
            result += ">>";
528
0
            return result;
529
0
        }
530
0
    case ::ot_stream:
531
0
        return obj->og.unparse(' ') + " R";
532
0
    case ::ot_operator:
533
0
        return std::get<QPDF_Operator>(obj->value).val;
534
0
    case ::ot_inlineimage:
535
0
        return std::get<QPDF_InlineImage>(obj->value).val;
536
0
    case ::ot_unresolved:
537
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object");
538
0
        return ""; // does not return
539
0
    case ::ot_destroyed:
540
0
        throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF");
541
0
        return ""; // does not return
542
0
    case ::ot_reference:
543
0
        return obj->og.unparse(' ') + " R";
544
0
    }
545
0
    return {}; // unreachable
546
0
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
0
{
551
0
    switch (resolved_type_code()) {
552
0
    case ::ot_uninitialized:
553
0
        throw std::logic_error(
554
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
555
0
        break; // unreachable
556
0
    case ::ot_null:
557
0
    case ::ot_operator:
558
0
    case ::ot_inlineimage:
559
0
        p << "null";
560
0
        break;
561
0
    case ::ot_boolean:
562
0
        p << std::get<QPDF_Bool>(obj->value).val;
563
0
        break;
564
0
    case ::ot_integer:
565
0
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
0
        break;
567
0
    case ::ot_real:
568
0
        {
569
0
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
0
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
0
            } else if (val.at(0) == '.') {
574
0
                p << "0" << val;
575
0
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
0
                p << "-0." << val.substr(2);
577
0
            } else {
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
4.11M
{
684
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
685
4.11M
    if (only_direct && indirect()) {
686
91.3k
        return;
687
91.3k
    }
688
689
4.02M
    switch (raw_type_code()) {
690
42.4k
    case ::ot_array:
691
42.4k
        {
692
42.4k
            auto& a = std::get<QPDF_Array>(obj->value);
693
42.4k
            if (a.sp) {
694
1.99M
                for (auto& item: a.sp->elements) {
695
1.99M
                    item.second.disconnect();
696
1.99M
                }
697
42.2k
            } else {
698
1.54M
                for (auto& oh: a.elements) {
699
1.54M
                    oh.disconnect();
700
1.54M
                }
701
42.2k
            }
702
42.4k
        }
703
42.4k
        break;
704
81.4k
    case ::ot_dictionary:
705
281k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
706
281k
            iter.second.disconnect();
707
281k
        }
708
81.4k
        break;
709
16.9k
    case ::ot_stream:
710
16.9k
        {
711
16.9k
            auto& s = std::get<QPDF_Stream>(obj->value);
712
16.9k
            s.m->stream_provider = nullptr;
713
16.9k
            s.m->stream_dict.disconnect();
714
16.9k
        }
715
16.9k
        break;
716
0
    case ::ot_uninitialized:
717
0
        return;
718
3.88M
    default:
719
3.88M
        break;
720
4.02M
    }
721
4.02M
    obj->qpdf = nullptr;
722
4.02M
    obj->og = QPDFObjGen();
723
4.02M
}
724
725
bool
726
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
727
160
{
728
160
    return obj == rhs.obj;
729
160
}
730
731
qpdf_object_type_e
732
QPDFObjectHandle::getTypeCode() const
733
0
{
734
0
    return type_code();
735
0
}
736
737
char const*
738
BaseHandle::type_name() const
739
4.92k
{
740
4.92k
    static constexpr std::array<char const*, 16> tn{
741
4.92k
        "uninitialized",
742
4.92k
        "reserved",
743
4.92k
        "null",
744
4.92k
        "boolean",
745
4.92k
        "integer",
746
4.92k
        "real",
747
4.92k
        "string",
748
4.92k
        "name",
749
4.92k
        "array",
750
4.92k
        "dictionary",
751
4.92k
        "stream",
752
4.92k
        "operator",
753
4.92k
        "inline-image",
754
4.92k
        "unresolved",
755
4.92k
        "destroyed",
756
4.92k
        "reference"};
757
4.92k
    return tn[type_code()];
758
4.92k
}
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
4.46k
{
775
4.46k
    return type_code() == ::ot_boolean;
776
4.46k
}
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
2.29k
{
789
2.29k
    return type_code() == ::ot_null;
790
2.29k
}
791
792
bool
793
QPDFObjectHandle::isInteger() const
794
131k
{
795
131k
    return type_code() == ::ot_integer;
796
131k
}
797
798
bool
799
QPDFObjectHandle::isReal() const
800
15.0k
{
801
15.0k
    return type_code() == ::ot_real;
802
15.0k
}
803
804
bool
805
QPDFObjectHandle::isNumber() const
806
19.6k
{
807
19.6k
    return (isInteger() || isReal());
808
19.6k
}
809
810
double
811
QPDFObjectHandle::getNumericValue() const
812
0
{
813
0
    if (isInteger()) {
814
0
        return static_cast<double>(getIntValue());
815
0
    } else if (isReal()) {
816
0
        return atof(getRealValue().c_str());
817
0
    } else {
818
0
        typeWarning("number", "returning 0");
819
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
820
0
        return 0;
821
0
    }
822
0
}
823
824
bool
825
QPDFObjectHandle::getValueAsNumber(double& value) const
826
0
{
827
0
    if (!isNumber()) {
828
0
        return false;
829
0
    }
830
0
    value = getNumericValue();
831
0
    return true;
832
0
}
833
834
bool
835
QPDFObjectHandle::isName() const
836
17.1k
{
837
17.1k
    return type_code() == ::ot_name;
838
17.1k
}
839
840
bool
841
QPDFObjectHandle::isString() const
842
11.1k
{
843
11.1k
    return type_code() == ::ot_string;
844
11.1k
}
845
846
bool
847
QPDFObjectHandle::isOperator() const
848
0
{
849
0
    return type_code() == ::ot_operator;
850
0
}
851
852
bool
853
QPDFObjectHandle::isInlineImage() const
854
0
{
855
0
    return type_code() == ::ot_inlineimage;
856
0
}
857
858
bool
859
QPDFObjectHandle::isArray() const
860
143k
{
861
143k
    return type_code() == ::ot_array;
862
143k
}
863
864
bool
865
QPDFObjectHandle::isDictionary() const
866
434k
{
867
434k
    return type_code() == ::ot_dictionary;
868
434k
}
869
870
bool
871
QPDFObjectHandle::isStream() const
872
0
{
873
0
    return type_code() == ::ot_stream;
874
0
}
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
3.46k
{
885
3.46k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
886
3.46k
}
887
888
bool
889
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
890
1.40k
{
891
1.40k
    return Name(*this) == name;
892
1.40k
}
893
894
bool
895
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
896
146k
{
897
146k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
898
10.1k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
899
146k
}
900
901
bool
902
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
903
55.5k
{
904
55.5k
    if (auto stream = as_stream()) {
905
23.0k
        return stream && (type.empty() || stream.Type() == type) &&
906
16.2k
            (subtype.empty() || stream.Subtype() == subtype);
907
23.0k
    }
908
32.5k
    return false;
909
55.5k
}
910
911
// Bool accessors
912
913
bool
914
QPDFObjectHandle::getBoolValue() const
915
4
{
916
4
    if (auto boolean = as<QPDF_Bool>()) {
917
4
        return boolean->val;
918
4
    } else {
919
0
        typeWarning("boolean", "returning false");
920
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
921
0
        return false;
922
0
    }
923
4
}
924
925
bool
926
QPDFObjectHandle::getValueAsBool(bool& value) const
927
0
{
928
0
    if (auto boolean = as<QPDF_Bool>()) {
929
0
        value = boolean->val;
930
0
        return true;
931
0
    }
932
0
    return false;
933
0
}
934
935
// Integer methods
936
937
Integer::Integer(long long value) :
938
1.78k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
939
1.78k
{
940
1.78k
}
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
115k
{
951
115k
    auto* i = as<QPDF_Integer>();
952
115k
    if (!i) {
953
1.59k
        throw invalid_error("Integer");
954
1.59k
    }
955
114k
    return i->val;
956
115k
}
957
958
long long
959
QPDFObjectHandle::getIntValue() const
960
34.0k
{
961
34.0k
    if (auto const integer = Integer(*this)) {
962
34.0k
        return integer;
963
34.0k
    } else {
964
0
        typeWarning("integer", "returning 0");
965
0
        return 0;
966
0
    }
967
34.0k
}
968
969
bool
970
QPDFObjectHandle::getValueAsInt(long long& value) const
971
8.66k
{
972
8.66k
    if (auto const integer = Integer(*this)) {
973
8.62k
        value = integer;
974
8.62k
        return true;
975
8.62k
    }
976
40
    return false;
977
8.66k
}
978
979
int
980
QPDFObjectHandle::getIntValueAsInt() const
981
49.4k
{
982
49.4k
    try {
983
49.4k
        return Integer(*this).value<int>();
984
49.4k
    } catch (std::invalid_argument&) {
985
20
        typeWarning("integer", "returning 0");
986
20
        return 0;
987
20
    }
988
49.4k
}
989
990
bool
991
QPDFObjectHandle::getValueAsInt(int& value) const
992
2.23k
{
993
2.23k
    if (!isInteger()) {
994
46
        return false;
995
46
    }
996
2.18k
    value = getIntValueAsInt();
997
2.18k
    return true;
998
2.23k
}
999
1000
unsigned long long
1001
QPDFObjectHandle::getUIntValue() const
1002
14.0k
{
1003
14.0k
    try {
1004
14.0k
        return Integer(*this).value<unsigned long long>();
1005
14.0k
    } catch (std::invalid_argument&) {
1006
1.17k
        typeWarning("integer", "returning 0");
1007
1.17k
        return 0;
1008
1.17k
    }
1009
14.0k
}
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
9.37k
{
1024
9.37k
    try {
1025
9.37k
        return Integer(*this).value<unsigned int>();
1026
9.37k
    } catch (std::invalid_argument&) {
1027
401
        typeWarning("integer", "returning 0");
1028
401
        return 0;
1029
401
    }
1030
9.37k
}
1031
1032
bool
1033
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1034
2.50k
{
1035
2.50k
    if (!isInteger()) {
1036
259
        return false;
1037
259
    }
1038
2.24k
    value = getUIntValueAsUInt();
1039
2.24k
    return true;
1040
2.50k
}
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
11.6k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1079
11.6k
{
1080
11.6k
}
1081
1082
std::string const&
1083
Name::value() const
1084
67.3k
{
1085
67.3k
    auto* n = as<QPDF_Name>();
1086
67.3k
    if (!n) {
1087
0
        throw invalid_error("Name");
1088
0
    }
1089
67.3k
    return n->name;
1090
67.3k
}
1091
1092
std::string
1093
QPDFObjectHandle::getName() const
1094
5.65k
{
1095
5.65k
    if (auto* name = as<QPDF_Name>()) {
1096
5.65k
        return name->name;
1097
5.65k
    }
1098
0
    typeWarning("name", "returning dummy name");
1099
0
    return "/QPDFFakeName";
1100
5.65k
}
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
20
{
1117
20
    return {QPDFObject::create<QPDF_String>(str)};
1118
20
}
1119
1120
QPDFObjectHandle
1121
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1122
2.70k
{
1123
2.70k
    return {String::utf16(utf8_str).obj_sp()};
1124
2.70k
}
1125
1126
String::String(std::string const& str) :
1127
2.06k
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1128
2.06k
{
1129
2.06k
}
1130
1131
String::String(std::string&& str) :
1132
641
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1133
641
{
1134
641
}
1135
1136
String
1137
String::utf16(std::string const& utf8_str)
1138
2.70k
{
1139
2.70k
    std::string result;
1140
2.70k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1141
2.06k
        return String(result);
1142
2.06k
    }
1143
641
    return String(QUtil::utf8_to_utf16(utf8_str));
1144
2.70k
}
1145
1146
std::string const&
1147
String::value() const
1148
3.75k
{
1149
3.75k
    auto* s = as<QPDF_String>();
1150
3.75k
    if (!s) {
1151
0
        throw invalid_error("String");
1152
0
    }
1153
3.75k
    return s->val;
1154
3.75k
}
1155
1156
std::string
1157
String::utf8_value() const
1158
701k
{
1159
701k
    auto* s = as<QPDF_String>();
1160
701k
    if (!s) {
1161
0
        throw invalid_error("String");
1162
0
    }
1163
701k
    if (util::is_utf16(s->val)) {
1164
43.6k
        return QUtil::utf16_to_utf8(s->val);
1165
43.6k
    }
1166
658k
    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
4.20k
        return s->val.substr(3);
1170
4.20k
    }
1171
654k
    return QUtil::pdf_doc_to_utf8(s->val);
1172
658k
}
1173
1174
std::string
1175
QPDFObjectHandle::getStringValue() const
1176
3.75k
{
1177
3.75k
    try {
1178
3.75k
        return String(obj).value();
1179
3.75k
    } catch (std::invalid_argument&) {
1180
0
        typeWarning("string", "returning empty string");
1181
0
        return {};
1182
0
    }
1183
3.75k
}
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
701k
{
1199
701k
    try {
1200
701k
        return String(obj).utf8_value();
1201
701k
    } catch (std::invalid_argument&) {
1202
0
        typeWarning("string", "returning empty string");
1203
0
        return {};
1204
0
    }
1205
701k
}
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
405
{
1281
405
    if (isNameAndEquals(value)) {
1282
0
        return true;
1283
405
    } else if (isArray()) {
1284
869
        for (auto& item: getArrayAsVector()) {
1285
869
            if (item.isNameAndEquals(value)) {
1286
31
                return true;
1287
31
            }
1288
869
        }
1289
55
    }
1290
374
    return false;
1291
405
}
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
0
{
1578
0
    if (isIndirect()) {
1579
0
        return getObjGen().unparse(' ') + " R";
1580
0
    } else {
1581
0
        return unparseResolved();
1582
0
    }
1583
0
}
1584
1585
std::string
1586
QPDFObjectHandle::unparseResolved() const
1587
0
{
1588
0
    if (!obj) {
1589
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1590
0
    }
1591
0
    return BaseHandle::unparse();
1592
0
}
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
0
{
1654
0
    return parse(nullptr, object_str, object_description);
1655
0
}
1656
1657
QPDFObjectHandle
1658
QPDFObjectHandle::parse(
1659
    QPDF* context, std::string const& object_str, std::string const& object_description)
1660
0
{
1661
0
    auto input = is::OffsetBuffer("parsed object", object_str);
1662
0
    auto result = Parser::parse(input, object_description, context);
1663
0
    size_t offset = QIntC::to_size(input.tell());
1664
0
    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
0
    return result;
1677
0
}
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
19.8k
{
1887
19.8k
    return {QPDFObject::create<QPDF_Null>()};
1888
19.8k
}
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
17.4k
{
1899
17.4k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1900
17.4k
}
1901
1902
QPDFObjectHandle
1903
QPDFObjectHandle::newOperator(std::string const& value)
1904
0
{
1905
0
    return {QPDFObject::create<QPDF_Operator>(value)};
1906
0
}
1907
1908
QPDFObjectHandle
1909
QPDFObjectHandle::newInlineImage(std::string const& value)
1910
0
{
1911
0
    return {QPDFObject::create<QPDF_InlineImage>(value)};
1912
0
}
1913
1914
QPDFObjectHandle
1915
QPDFObjectHandle::newArray()
1916
0
{
1917
0
    return newArray(std::vector<QPDFObjectHandle>());
1918
0
}
1919
1920
QPDFObjectHandle
1921
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1922
4.37k
{
1923
4.37k
    return {QPDFObject::create<QPDF_Array>(items)};
1924
4.37k
}
1925
1926
QPDFObjectHandle
1927
QPDFObjectHandle::newArray(Rectangle const& rect)
1928
4.37k
{
1929
4.37k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1930
4.37k
}
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
5.87k
{
1977
5.87k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1978
5.87k
}
1979
1980
QPDFObjectHandle
1981
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1982
5.87k
{
1983
5.87k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1984
5.87k
}
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
53.6k
{
2028
53.6k
    return obj ? obj->getDescription() : ""s;
2029
53.6k
}
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
24.9k
{
2043
24.9k
    return obj && obj->hasDescription();
2044
24.9k
}
2045
2046
QPDFObjectHandle
2047
QPDFObjectHandle::shallowCopy()
2048
154
{
2049
154
    if (!obj) {
2050
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2051
0
    }
2052
154
    return {copy()};
2053
154
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::unsafeShallowCopy()
2057
0
{
2058
0
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
0
    return {copy(true)};
2062
0
}
2063
2064
void
2065
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2066
0
{
2067
0
    static uint32_t constexpr max_nesting = 500;
2068
0
    if (++level > max_nesting + 1) {
2069
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2070
0
    }
2071
0
    assertInitialized();
2072
2073
0
    auto cur_og = getObjGen();
2074
0
    if (!visited.add(cur_og)) {
2075
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2076
0
    }
2077
2078
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2079
0
        obj = copy(true);
2080
0
    } else if (auto a = as_array(strict)) {
2081
0
        std::vector<QPDFObjectHandle> items;
2082
0
        for (auto const& item: a) {
2083
0
            items.emplace_back(item);
2084
0
            items.back().makeDirect(level, visited, stop_at_streams);
2085
0
        }
2086
0
        obj = QPDFObject::create<QPDF_Array>(items);
2087
0
    } else if (isDictionary()) {
2088
0
        std::map<std::string, QPDFObjectHandle> items;
2089
0
        for (auto const& [key, value]: as_dictionary(strict)) {
2090
0
            if (!value.null()) {
2091
0
                items.insert({key, value});
2092
0
                items[key].makeDirect(level, visited, stop_at_streams);
2093
0
            }
2094
0
        }
2095
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2096
0
    } else if (isStream()) {
2097
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2098
0
        if (!stop_at_streams) {
2099
0
            throw std::runtime_error("attempt to make a stream into a direct object");
2100
0
        }
2101
0
    } 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
0
    visited.erase(cur_og);
2109
0
}
2110
2111
void
2112
QPDFObjectHandle::makeDirect(bool allow_streams)
2113
0
{
2114
0
    QPDFObjGen::set visited;
2115
0
    makeDirect(0, visited, allow_streams);
2116
0
}
2117
2118
void
2119
QPDFObjectHandle::assertInitialized() const
2120
0
{
2121
0
    if (!obj) {
2122
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2123
0
    }
2124
0
}
2125
2126
std::invalid_argument
2127
BaseHandle::invalid_error(std::string const& method) const
2128
1.59k
{
2129
1.59k
    return std::invalid_argument(method + " operation attempted on invalid object");
2130
1.59k
}
2131
std::runtime_error
2132
BaseHandle::type_error(char const* expected_type) const
2133
0
{
2134
0
    return std::runtime_error(
2135
0
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2136
0
}
2137
2138
QPDFExc
2139
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2140
4.92k
{
2141
4.92k
    return {
2142
4.92k
        qpdf_e_object,
2143
4.92k
        "",
2144
4.92k
        description(),
2145
4.92k
        0,
2146
4.92k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2147
4.92k
            message};
2148
4.92k
}
2149
2150
void
2151
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2152
4.92k
{
2153
4.92k
    if (!obj) {
2154
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2155
0
    }
2156
4.92k
    warn(type_error(expected_type, message));
2157
4.92k
}
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
0
{
2168
0
    warn({qpdf_e_object, "", description(), 0, warning});
2169
0
}
2170
2171
void
2172
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2173
0
{
2174
0
    if (!istype) {
2175
0
        throw type_error(type_name);
2176
0
    }
2177
0
}
2178
2179
void
2180
QPDFObjectHandle::assertNull() const
2181
0
{
2182
0
    assertType("null", isNull());
2183
0
}
2184
2185
void
2186
QPDFObjectHandle::assertBool() const
2187
0
{
2188
0
    assertType("boolean", isBool());
2189
0
}
2190
2191
void
2192
QPDFObjectHandle::assertInteger() const
2193
0
{
2194
0
    assertType("integer", isInteger());
2195
0
}
2196
2197
void
2198
QPDFObjectHandle::assertReal() const
2199
0
{
2200
0
    assertType("real", isReal());
2201
0
}
2202
2203
void
2204
QPDFObjectHandle::assertName() const
2205
0
{
2206
0
    assertType("name", isName());
2207
0
}
2208
2209
void
2210
QPDFObjectHandle::assertString() const
2211
0
{
2212
0
    assertType("string", isString());
2213
0
}
2214
2215
void
2216
QPDFObjectHandle::assertOperator() const
2217
0
{
2218
0
    assertType("operator", isOperator());
2219
0
}
2220
2221
void
2222
QPDFObjectHandle::assertInlineImage() const
2223
0
{
2224
0
    assertType("inlineimage", isInlineImage());
2225
0
}
2226
2227
void
2228
QPDFObjectHandle::assertArray() const
2229
0
{
2230
0
    assertType("array", isArray());
2231
0
}
2232
2233
void
2234
QPDFObjectHandle::assertDictionary() const
2235
0
{
2236
0
    assertType("dictionary", isDictionary());
2237
0
}
2238
2239
void
2240
QPDFObjectHandle::assertStream() const
2241
0
{
2242
0
    assertType("stream", isStream());
2243
0
}
2244
2245
void
2246
QPDFObjectHandle::assertReserved() const
2247
0
{
2248
0
    assertType("reserved", isReserved());
2249
0
}
2250
2251
void
2252
QPDFObjectHandle::assertIndirect() const
2253
0
{
2254
0
    if (!isIndirect()) {
2255
0
        throw std::logic_error("operation for indirect object attempted on direct object");
2256
0
    }
2257
0
}
2258
2259
void
2260
QPDFObjectHandle::assertScalar() const
2261
0
{
2262
0
    assertType("scalar", isScalar());
2263
0
}
2264
2265
void
2266
QPDFObjectHandle::assertNumber() const
2267
0
{
2268
0
    assertType("number", isNumber());
2269
0
}
2270
2271
bool
2272
QPDFObjectHandle::isPageObject() const
2273
0
{
2274
    // See comments in QPDFObjectHandle.hh.
2275
0
    if (!qpdf()) {
2276
0
        return false;
2277
0
    }
2278
    // getAllPages repairs /Type when traversing the page tree.
2279
0
    (void)qpdf()->doc().pages().all();
2280
0
    return isDictionaryOfType("/Page");
2281
0
}
2282
2283
bool
2284
QPDFObjectHandle::isPagesObject() const
2285
0
{
2286
0
    if (!qpdf()) {
2287
0
        return false;
2288
0
    }
2289
    // getAllPages repairs /Type when traversing the page tree.
2290
0
    (void)qpdf()->doc().pages().all();
2291
0
    return isDictionaryOfType("/Pages");
2292
0
}
2293
2294
bool
2295
QPDFObjectHandle::isFormXObject() const
2296
0
{
2297
0
    return isStreamOfType("", "/Form");
2298
0
}
2299
2300
bool
2301
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2302
0
{
2303
0
    return (
2304
0
        isStreamOfType("", "/Image") &&
2305
0
        ((!exclude_imagemask) ||
2306
0
         (!(getDict().getKey("/ImageMask").isBool() &&
2307
0
            getDict().getKey("/ImageMask").getBoolValue()))));
2308
0
}
2309
2310
void
2311
QPDFObjectHandle::assertPageObject() const
2312
0
{
2313
0
    if (!isPageObject()) {
2314
0
        throw std::runtime_error("page operation called on non-Page object");
2315
0
    }
2316
0
}
2317
2318
void
2319
BaseHandle::warn(QPDF* qpdf, QPDFExc&& e)
2320
0
{
2321
0
    if (!qpdf) {
2322
0
        throw std::move(e);
2323
0
    }
2324
0
    qpdf->warn(std::move(e));
2325
0
}
2326
2327
void
2328
BaseHandle::warn(QPDFExc&& e) const
2329
53.6k
{
2330
53.6k
    if (!qpdf()) {
2331
238
        throw std::move(e);
2332
238
    }
2333
53.4k
    qpdf()->warn(std::move(e));
2334
53.4k
}
2335
2336
void
2337
BaseHandle::warn(std::string const& warning) const
2338
57.9k
{
2339
57.9k
    if (qpdf()) {
2340
48.7k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2341
48.7k
    } else {
2342
9.15k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2343
9.15k
    }
2344
57.9k
}
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
36.5k
{
2514
36.5k
    return obj ? obj->getObjGen() : QPDFObjGen();
2515
36.5k
}
2516
2517
int
2518
QPDFObjectHandle::getObjectID() const
2519
25.1k
{
2520
25.1k
    return getObjGen().getObj();
2521
25.1k
}
2522
2523
int
2524
QPDFObjectHandle::getGeneration() const
2525
0
{
2526
0
    return getObjGen().getGen();
2527
0
}
2528
2529
bool
2530
QPDFObjectHandle::isIndirect() const
2531
19.6k
{
2532
19.6k
    return getObjectID() != 0;
2533
19.6k
}
2534
2535
// Indirect object accessors
2536
QPDF*
2537
QPDFObjectHandle::getOwningQPDF() const
2538
61.4k
{
2539
61.4k
    return obj ? obj->getQPDF() : nullptr;
2540
61.4k
}
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
20
{
2554
20
    if (obj) {
2555
20
        obj->setParsedOffset(offset);
2556
20
    }
2557
20
}
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
}