Coverage Report

Created: 2026-09-03 06:17

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
143k
{
35
143k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
143k
}
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
8.19k
{
253
8.19k
    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
406
    case ::ot_null:
260
406
        return QPDFObject::create<QPDF_Null>();
261
22
    case ::ot_boolean:
262
22
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
2.57k
    case ::ot_integer:
264
2.57k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
930
    case ::ot_real:
266
930
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
1.88k
    case ::ot_string:
268
1.88k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
664
    case ::ot_name:
270
664
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
598
    case ::ot_array:
272
598
        {
273
598
            auto const& a = std::get<QPDF_Array>(obj->value);
274
598
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
598
            } else {
277
598
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
598
                if (a.sp) {
279
0
                    QPDF_Array result;
280
0
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
0
                    result.sp->size = a.sp->size;
282
0
                    for (auto const& [idx, oh]: a.sp->elements) {
283
0
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
0
                    }
285
0
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
598
                } else {
287
598
                    std::vector<QPDFObjectHandle> result;
288
598
                    result.reserve(a.elements.size());
289
2.30k
                    for (auto const& element: a.elements) {
290
2.30k
                        result.emplace_back(
291
2.30k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
2.30k
                    }
293
598
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
598
                }
295
598
            }
296
598
        }
297
1.11k
    case ::ot_dictionary:
298
1.11k
        {
299
1.11k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
1.11k
            if (shallow) {
301
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
1.11k
            } else {
303
1.11k
                std::map<std::string, QPDFObjectHandle> new_items;
304
6.86k
                for (auto const& [key, val]: d.items) {
305
6.86k
                    new_items[key] = val.indirect() ? val : val.copy();
306
6.86k
                }
307
1.11k
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
1.11k
            }
309
1.11k
        }
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
8.19k
    }
327
0
    return {}; // unreachable
328
8.19k
}
329
330
// This method determines structural equivalence up to a given depth.
331
// The default depth is 10.
332
//
333
// Nomenclature note: ISO 32000-2 Annex J uses the term "equal" for this
334
// strict recursive comparison (J.4.1). We use "equivalent_to" here to
335
// implement Annex J's "equality", distinguishing it from C++ shallow
336
// pointer equality.
337
//
338
// Implementation notes:
339
//
340
// (1) We deviate from Annex J by comparing raw streams only, without
341
// decoding.
342
//
343
// (2) Loop detection is expensive and is avoided. If either object has
344
// a cycle in its forward orbit, this implementation will return false.
345
346
bool
347
BaseHandle::equivalent_to(BaseHandle const& other, int depth) const
348
0
{
349
    // A. Identity, size & limit checks
350
0
    if (obj == other.obj) {
351
0
        return true;
352
0
    }
353
0
    if (depth < 0) {
354
0
        return false;
355
0
    }
356
0
    size_t size1 = size();
357
0
    size_t size2 = other.size();
358
0
    if (size1 != size2) {
359
0
        return false;
360
0
    }
361
    // B. Structural comparison
362
0
    qpdf_object_type_e t1 = resolved_type_code();
363
0
    qpdf_object_type_e t2 = other.resolved_type_code();
364
0
    if (t1 == ::ot_reference) {
365
0
        return referenced_object().equivalent_to(other, depth - 1);
366
0
    }
367
0
    if (t2 == ::ot_reference) {
368
0
        return equivalent_to(other.referenced_object(), depth - 1);
369
0
    }
370
0
    if (t1 != t2) {
371
0
        if ((t1 == ::ot_integer || t1 == ::ot_real) && (t2 == ::ot_integer || t2 == ::ot_real)) {
372
            // Numeric equivalence per Annex J
373
0
            return oh().getNumericValue() == other.oh().getNumericValue();
374
0
        }
375
        // normalize uninitialized and null
376
0
        return (t1 == ::ot_uninitialized && t2 == ::ot_null) ||
377
0
            (t2 == ::ot_uninitialized && t1 == ::ot_null);
378
0
    }
379
0
    switch (t1) {
380
0
    case ::ot_uninitialized:
381
0
    case ::ot_null:
382
0
        return true;
383
0
    case ::ot_boolean:
384
0
        return std::get<QPDF_Bool>(obj->value).val == std::get<QPDF_Bool>(other.obj->value).val;
385
0
    case ::ot_string:
386
0
        return std::get<QPDF_String>(obj->value).val == std::get<QPDF_String>(other.obj->value).val;
387
0
    case ::ot_name:
388
0
        return std::get<QPDF_Name>(obj->value).name == std::get<QPDF_Name>(other.obj->value).name;
389
0
    case ::ot_array:
390
0
        {
391
0
            auto const& a1 = std::get<QPDF_Array>(obj->value);
392
0
            auto const& a2 = std::get<QPDF_Array>(other.obj->value);
393
            // sizes size1, size2 were calculated above and checked to be equal
394
0
            if (!a1.sp && !a2.sp) {
395
0
                for (size_t i = 0; i < size1; ++i) {
396
0
                    if (!a1.elements[i].equivalent_to(a2.elements[i], depth - 1)) {
397
0
                        return false;
398
0
                    }
399
0
                }
400
0
                return true;
401
0
            }
402
            // at least one array is sparse
403
0
            auto get_item = [](QPDF_Array const& arr, size_t idx) -> BaseHandle const& {
404
0
                if (arr.sp) {
405
0
                    auto it = arr.sp->elements.find(idx);
406
0
                    if (it == arr.sp->elements.end()) {
407
0
                        static QPDFObjectHandle null_oh = Null();
408
0
                        return null_oh;
409
0
                    }
410
0
                    return it->second;
411
0
                }
412
0
                return arr.elements[idx];
413
0
            };
414
0
            for (size_t i = 0; i < size1; ++i) {
415
0
                if (!get_item(a1, i).equivalent_to(get_item(a2, i), depth - 1)) {
416
0
                    return false;
417
0
                }
418
0
            }
419
0
            return true;
420
0
        }
421
0
    case ::ot_dictionary:
422
0
        {
423
0
            auto const& map1 = std::get<QPDF_Dictionary>(obj->value).items;
424
0
            auto const& map2 = std::get<QPDF_Dictionary>(other.obj->value).items;
425
0
            auto it2 = map2.begin();
426
0
            auto end2 = map2.end();
427
0
            for (auto const& [key1, value1]: map1) {
428
0
                if (value1.null()) {
429
0
                    continue;
430
0
                }
431
0
                while (it2 != end2 && it2->second.null()) {
432
0
                    ++it2;
433
0
                }
434
0
                if (it2 == end2 || key1 != it2->first ||
435
0
                    !value1.equivalent_to(it2->second, depth - 1)) {
436
0
                    return false;
437
0
                }
438
0
                ++it2;
439
0
            }
440
0
            while (it2 != end2 && it2->second.null()) {
441
0
                ++it2;
442
0
            }
443
0
            return it2 == end2;
444
0
        }
445
0
    case ::ot_stream:
446
0
        {
447
0
            auto const& s1 = std::get<QPDF_Stream>(obj->value);
448
0
            auto const& s2 = std::get<QPDF_Stream>(other.obj->value);
449
0
            if (!s1.m->stream_dict.equivalent_to(s2.m->stream_dict, depth - 1)) {
450
0
                return false;
451
0
            }
452
0
            return s1.m->stream_data->view() == s2.m->stream_data->view();
453
0
        }
454
0
    case ::ot_operator:
455
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_operator");
456
0
    case ::ot_inlineimage:
457
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_inlineimage");
458
0
    case ::ot_integer:
459
0
        return std::get<QPDF_Integer>(obj->value).val ==
460
0
            std::get<QPDF_Integer>(other.obj->value).val;
461
0
    case ::ot_real:
462
0
        return oh().getNumericValue() == other.oh().getNumericValue();
463
0
    case ::ot_unresolved: // cannot determine equivalence so return false
464
0
    case ::ot_reference:  // handled above
465
0
    case ::ot_destroyed:  // should not happen
466
0
    case ::ot_reserved:   // should not happen
467
0
        return false;
468
0
    }
469
0
    return false; // unreachable
470
0
}
471
472
std::string
473
BaseHandle::unparse() const
474
0
{
475
0
    switch (resolved_type_code()) {
476
0
    case ::ot_uninitialized:
477
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object");
478
0
        return ""; // does not return
479
0
    case ::ot_reserved:
480
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
481
0
        return ""; // does not return
482
0
    case ::ot_null:
483
0
        return "null";
484
0
    case ::ot_boolean:
485
0
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
0
    case ::ot_integer:
487
0
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
0
    case ::ot_real:
489
0
        return std::get<QPDF_Real>(obj->value).val;
490
0
    case ::ot_string:
491
0
        return std::get<QPDF_String>(obj->value).unparse(false);
492
0
    case ::ot_name:
493
0
        return Name::normalize(std::get<QPDF_Name>(obj->value).name);
494
0
    case ::ot_array:
495
0
        {
496
0
            auto const& a = std::get<QPDF_Array>(obj->value);
497
0
            std::string result = "[ ";
498
0
            if (a.sp) {
499
0
                size_t next = 0;
500
0
                for (auto& [key, value]: a.sp->elements) {
501
0
                    for (size_t j = next; j < key; ++j) {
502
0
                        result += "null ";
503
0
                    }
504
0
                    result += value.unparse() + " ";
505
0
                    next = key + 1;
506
0
                }
507
0
                for (size_t j = next; j < a.sp->size; ++j) {
508
0
                    result += "null ";
509
0
                }
510
0
            } else {
511
0
                for (auto const& item: a.elements) {
512
0
                    result += item.unparse() + " ";
513
0
                }
514
0
            }
515
0
            result += "]";
516
0
            return result;
517
0
        }
518
0
    case ::ot_dictionary:
519
0
        {
520
0
            auto const& items = std::get<QPDF_Dictionary>(obj->value).items;
521
0
            std::string result = "<< ";
522
0
            for (auto& iter: items) {
523
0
                if (!iter.second.null()) {
524
0
                    result += Name::normalize(iter.first) + " " + iter.second.unparse() + " ";
525
0
                }
526
0
            }
527
0
            result += ">>";
528
0
            return result;
529
0
        }
530
0
    case ::ot_stream:
531
0
        return obj->og.unparse(' ') + " R";
532
0
    case ::ot_operator:
533
0
        return std::get<QPDF_Operator>(obj->value).val;
534
0
    case ::ot_inlineimage:
535
0
        return std::get<QPDF_InlineImage>(obj->value).val;
536
0
    case ::ot_unresolved:
537
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object");
538
0
        return ""; // does not return
539
0
    case ::ot_destroyed:
540
0
        throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF");
541
0
        return ""; // does not return
542
0
    case ::ot_reference:
543
0
        return obj->og.unparse(' ') + " R";
544
0
    }
545
0
    return {}; // unreachable
546
0
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
0
{
551
0
    switch (resolved_type_code()) {
552
0
    case ::ot_uninitialized:
553
0
        throw std::logic_error(
554
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
555
0
        break; // unreachable
556
0
    case ::ot_null:
557
0
    case ::ot_operator:
558
0
    case ::ot_inlineimage:
559
0
        p << "null";
560
0
        break;
561
0
    case ::ot_boolean:
562
0
        p << std::get<QPDF_Bool>(obj->value).val;
563
0
        break;
564
0
    case ::ot_integer:
565
0
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
0
        break;
567
0
    case ::ot_real:
568
0
        {
569
0
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
0
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
0
            } else if (val.at(0) == '.') {
574
0
                p << "0" << val;
575
0
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
0
                p << "-0." << val.substr(2);
577
0
            } else if (val.at(0) == '0') {
578
0
                auto i = val.find_first_not_of('0');
579
0
                if (i == std::string::npos) {
580
0
                    p << "0";
581
0
                } else if (val.at(i) == '.') {
582
0
                    p << "0" << val.substr(i);
583
0
                } else {
584
0
                    p << val.substr(i);
585
0
                }
586
0
            } else {
587
0
                p << val;
588
0
            }
589
0
            if (val.back() == '.') {
590
0
                p << "0";
591
0
            }
592
0
        }
593
0
        break;
594
0
    case ::ot_string:
595
0
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
596
0
        break;
597
0
    case ::ot_name:
598
0
        {
599
0
            auto const& n = std::get<QPDF_Name>(obj->value);
600
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
601
            // updating this method make sure QPDF_Dictionary is also update.
602
0
            if (json_version == 1) {
603
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
0
            } else {
605
0
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
606
0
                    if (res.second) {
607
0
                        p << "\"" << n.name << "\"";
608
0
                    } else {
609
0
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
610
0
                    }
611
0
                } else {
612
0
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
613
0
                }
614
0
            }
615
0
        }
616
0
        break;
617
0
    case ::ot_array:
618
0
        {
619
0
            auto const& a = std::get<QPDF_Array>(obj->value);
620
0
            p.writeStart('[');
621
0
            if (a.sp) {
622
0
                size_t next = 0;
623
0
                for (auto& [key, value]: a.sp->elements) {
624
0
                    for (size_t j = next; j < key; ++j) {
625
0
                        p.writeNext() << "null";
626
0
                    }
627
0
                    p.writeNext();
628
0
                    auto item_og = value.id_gen();
629
0
                    if (item_og.isIndirect()) {
630
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
631
0
                    } else {
632
0
                        value.write_json(json_version, p);
633
0
                    }
634
0
                    next = key + 1;
635
0
                }
636
0
                for (size_t j = next; j < a.sp->size; ++j) {
637
0
                    p.writeNext() << "null";
638
0
                }
639
0
            } else {
640
0
                for (auto const& item: a.elements) {
641
0
                    p.writeNext();
642
0
                    auto item_og = item.id_gen();
643
0
                    if (item_og.isIndirect()) {
644
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
645
0
                    } else {
646
0
                        item.write_json(json_version, p);
647
0
                    }
648
0
                }
649
0
            }
650
0
            p.writeEnd(']');
651
0
        }
652
0
        break;
653
0
    case ::ot_dictionary:
654
0
        {
655
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
656
0
            p.writeStart('{');
657
0
            for (auto& iter: d.items) {
658
0
                if (!iter.second.null()) {
659
0
                    p.writeNext();
660
0
                    if (json_version == 1) {
661
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
0
                          << "\": ";
663
0
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
664
0
                        if (res.second) {
665
0
                            p << "\"" << iter.first << "\": ";
666
0
                        } else {
667
0
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
668
0
                        }
669
0
                    } else {
670
0
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
671
0
                          << "\": ";
672
0
                    }
673
0
                    iter.second.writeJSON(json_version, p);
674
0
                }
675
0
            }
676
0
            p.writeEnd('}');
677
0
        }
678
0
        break;
679
0
    case ::ot_stream:
680
0
        std::get<QPDF_Stream>(obj->value).m->stream_dict.writeJSON(json_version, p);
681
0
        break;
682
0
    case ::ot_reference:
683
0
        p << "\"" << obj->og.unparse(' ') << " R\"";
684
0
        break;
685
0
    default:
686
0
        throw std::logic_error("attempted to write an unsuitable object as JSON");
687
0
    }
688
0
}
689
690
void
691
BaseHandle::disconnect(bool only_direct)
692
1.10M
{
693
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
694
1.10M
    if (only_direct && indirect()) {
695
63.8k
        return;
696
63.8k
    }
697
698
1.04M
    switch (raw_type_code()) {
699
45.1k
    case ::ot_array:
700
45.1k
        {
701
45.1k
            auto& a = std::get<QPDF_Array>(obj->value);
702
45.1k
            if (a.sp) {
703
43.2k
                for (auto& item: a.sp->elements) {
704
43.2k
                    item.second.disconnect();
705
43.2k
                }
706
45.0k
            } else {
707
449k
                for (auto& oh: a.elements) {
708
449k
                    oh.disconnect();
709
449k
                }
710
45.0k
            }
711
45.1k
        }
712
45.1k
        break;
713
81.2k
    case ::ot_dictionary:
714
293k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
715
293k
            iter.second.disconnect();
716
293k
        }
717
81.2k
        break;
718
17.4k
    case ::ot_stream:
719
17.4k
        {
720
17.4k
            auto& s = std::get<QPDF_Stream>(obj->value);
721
17.4k
            s.m->stream_provider = nullptr;
722
17.4k
            s.m->stream_dict.disconnect();
723
17.4k
        }
724
17.4k
        break;
725
0
    case ::ot_uninitialized:
726
0
        return;
727
900k
    default:
728
900k
        break;
729
1.04M
    }
730
1.04M
    obj->qpdf = nullptr;
731
1.04M
    obj->og = QPDFObjGen();
732
1.04M
}
733
734
bool
735
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
736
275
{
737
275
    return obj == rhs.obj;
738
275
}
739
740
qpdf_object_type_e
741
QPDFObjectHandle::getTypeCode() const
742
0
{
743
0
    return type_code();
744
0
}
745
746
char const*
747
BaseHandle::type_name() const
748
4.38k
{
749
4.38k
    static constexpr std::array<char const*, 16> tn{
750
4.38k
        "uninitialized",
751
4.38k
        "reserved",
752
4.38k
        "null",
753
4.38k
        "boolean",
754
4.38k
        "integer",
755
4.38k
        "real",
756
4.38k
        "string",
757
4.38k
        "name",
758
4.38k
        "array",
759
4.38k
        "dictionary",
760
4.38k
        "stream",
761
4.38k
        "operator",
762
4.38k
        "inline-image",
763
4.38k
        "unresolved",
764
4.38k
        "destroyed",
765
4.38k
        "reference"};
766
4.38k
    return tn[type_code()];
767
4.38k
}
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
2.25k
{
784
2.25k
    return type_code() == ::ot_boolean;
785
2.25k
}
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
2.31k
{
798
2.31k
    return type_code() == ::ot_null;
799
2.31k
}
800
801
bool
802
QPDFObjectHandle::isInteger() const
803
113k
{
804
113k
    return type_code() == ::ot_integer;
805
113k
}
806
807
bool
808
QPDFObjectHandle::isReal() const
809
10.8k
{
810
10.8k
    return type_code() == ::ot_real;
811
10.8k
}
812
813
bool
814
QPDFObjectHandle::isNumber() const
815
18.6k
{
816
18.6k
    return (isInteger() || isReal());
817
18.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
13.6k
{
846
13.6k
    return type_code() == ::ot_name;
847
13.6k
}
848
849
bool
850
QPDFObjectHandle::isString() const
851
11.1k
{
852
11.1k
    return type_code() == ::ot_string;
853
11.1k
}
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
83.8k
{
870
83.8k
    return type_code() == ::ot_array;
871
83.8k
}
872
873
bool
874
QPDFObjectHandle::isDictionary() const
875
447k
{
876
447k
    return type_code() == ::ot_dictionary;
877
447k
}
878
879
bool
880
QPDFObjectHandle::isStream() const
881
0
{
882
0
    return type_code() == ::ot_stream;
883
0
}
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
1.24k
{
894
1.24k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
895
1.24k
}
896
897
bool
898
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
899
1.82k
{
900
1.82k
    return Name(*this) == name;
901
1.82k
}
902
903
bool
904
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
905
159k
{
906
159k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
907
9.99k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
908
159k
}
909
910
bool
911
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
912
57.1k
{
913
57.1k
    if (auto stream = as_stream()) {
914
23.1k
        return stream && (type.empty() || stream.Type() == type) &&
915
15.9k
            (subtype.empty() || stream.Subtype() == subtype);
916
23.1k
    }
917
34.0k
    return false;
918
57.1k
}
919
920
// Bool accessors
921
922
bool
923
QPDFObjectHandle::getBoolValue() const
924
1
{
925
1
    if (auto boolean = as<QPDF_Bool>()) {
926
1
        return boolean->val;
927
1
    } else {
928
0
        typeWarning("boolean", "returning false");
929
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
930
0
        return false;
931
0
    }
932
1
}
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
1.91k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
948
1.91k
{
949
1.91k
}
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
98.8k
{
960
98.8k
    auto* i = as<QPDF_Integer>();
961
98.8k
    if (!i) {
962
1.83k
        throw invalid_error("Integer");
963
1.83k
    }
964
97.0k
    return i->val;
965
98.8k
}
966
967
long long
968
QPDFObjectHandle::getIntValue() const
969
18.5k
{
970
18.5k
    if (auto const integer = Integer(*this)) {
971
18.5k
        return integer;
972
18.5k
    } else {
973
0
        typeWarning("integer", "returning 0");
974
0
        return 0;
975
0
    }
976
18.5k
}
977
978
bool
979
QPDFObjectHandle::getValueAsInt(long long& value) const
980
8.41k
{
981
8.41k
    if (auto const integer = Integer(*this)) {
982
8.33k
        value = integer;
983
8.33k
        return true;
984
8.33k
    }
985
83
    return false;
986
8.41k
}
987
988
int
989
QPDFObjectHandle::getIntValueAsInt() const
990
47.8k
{
991
47.8k
    try {
992
47.8k
        return Integer(*this).value<int>();
993
47.8k
    } catch (std::invalid_argument&) {
994
46
        typeWarning("integer", "returning 0");
995
46
        return 0;
996
46
    }
997
47.8k
}
998
999
bool
1000
QPDFObjectHandle::getValueAsInt(int& value) const
1001
2.58k
{
1002
2.58k
    if (!isInteger()) {
1003
49
        return false;
1004
49
    }
1005
2.53k
    value = getIntValueAsInt();
1006
2.53k
    return true;
1007
2.58k
}
1008
1009
unsigned long long
1010
QPDFObjectHandle::getUIntValue() const
1011
14.4k
{
1012
14.4k
    try {
1013
14.4k
        return Integer(*this).value<unsigned long long>();
1014
14.4k
    } catch (std::invalid_argument&) {
1015
1.14k
        typeWarning("integer", "returning 0");
1016
1.14k
        return 0;
1017
1.14k
    }
1018
14.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
9.70k
{
1033
9.70k
    try {
1034
9.70k
        return Integer(*this).value<unsigned int>();
1035
9.70k
    } catch (std::invalid_argument&) {
1036
641
        typeWarning("integer", "returning 0");
1037
641
        return 0;
1038
641
    }
1039
9.70k
}
1040
1041
bool
1042
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1043
2.90k
{
1044
2.90k
    if (!isInteger()) {
1045
306
        return false;
1046
306
    }
1047
2.60k
    value = getUIntValueAsUInt();
1048
2.60k
    return true;
1049
2.90k
}
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
11.5k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1088
11.5k
{
1089
11.5k
}
1090
1091
std::string const&
1092
Name::value() const
1093
70.1k
{
1094
70.1k
    auto* n = as<QPDF_Name>();
1095
70.1k
    if (!n) {
1096
0
        throw invalid_error("Name");
1097
0
    }
1098
70.1k
    return n->name;
1099
70.1k
}
1100
1101
std::string
1102
QPDFObjectHandle::getName() const
1103
6.07k
{
1104
6.07k
    if (auto* name = as<QPDF_Name>()) {
1105
6.07k
        return name->name;
1106
6.07k
    }
1107
0
    typeWarning("name", "returning dummy name");
1108
0
    return "/QPDFFakeName";
1109
6.07k
}
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
32
{
1126
32
    return {QPDFObject::create<QPDF_String>(str)};
1127
32
}
1128
1129
QPDFObjectHandle
1130
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1131
2.27k
{
1132
2.27k
    return {String::utf16(utf8_str).obj_sp()};
1133
2.27k
}
1134
1135
String::String(std::string const& str) :
1136
1.51k
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1137
1.51k
{
1138
1.51k
}
1139
1140
String::String(std::string&& str) :
1141
762
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1142
762
{
1143
762
}
1144
1145
String
1146
String::utf16(std::string const& utf8_str)
1147
2.27k
{
1148
2.27k
    std::string result;
1149
2.27k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1150
1.51k
        return String(result);
1151
1.51k
    }
1152
762
    return String(QUtil::utf8_to_utf16(utf8_str));
1153
2.27k
}
1154
1155
std::string const&
1156
String::value() const
1157
4.93k
{
1158
4.93k
    auto* s = as<QPDF_String>();
1159
4.93k
    if (!s) {
1160
0
        throw invalid_error("String");
1161
0
    }
1162
4.93k
    return s->val;
1163
4.93k
}
1164
1165
std::string
1166
String::utf8_value() const
1167
379k
{
1168
379k
    auto* s = as<QPDF_String>();
1169
379k
    if (!s) {
1170
0
        throw invalid_error("String");
1171
0
    }
1172
379k
    if (util::is_utf16(s->val)) {
1173
3.68k
        return QUtil::utf16_to_utf8(s->val);
1174
3.68k
    }
1175
375k
    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
4.29k
        return s->val.substr(3);
1179
4.29k
    }
1180
371k
    return QUtil::pdf_doc_to_utf8(s->val);
1181
375k
}
1182
1183
std::string
1184
QPDFObjectHandle::getStringValue() const
1185
4.93k
{
1186
4.93k
    try {
1187
4.93k
        return String(obj).value();
1188
4.93k
    } catch (std::invalid_argument&) {
1189
0
        typeWarning("string", "returning empty string");
1190
0
        return {};
1191
0
    }
1192
4.93k
}
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
379k
{
1208
379k
    try {
1209
379k
        return String(obj).utf8_value();
1210
379k
    } catch (std::invalid_argument&) {
1211
0
        typeWarning("string", "returning empty string");
1212
0
        return {};
1213
0
    }
1214
379k
}
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
457
{
1290
457
    if (isNameAndEquals(value)) {
1291
9
        return true;
1292
448
    } else if (isArray()) {
1293
1.29k
        for (auto& item: getArrayAsVector()) {
1294
1.29k
            if (item.isNameAndEquals(value)) {
1295
1
                return true;
1296
1
            }
1297
1.29k
        }
1298
44
    }
1299
447
    return false;
1300
457
}
1301
1302
void
1303
QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf)
1304
0
{
1305
0
    for (auto const& i1: as_dictionary()) {
1306
0
        for (auto& i2: i1.second.as_dictionary()) {
1307
0
            if (!i2.second.null() && !i2.second.isIndirect()) {
1308
0
                i2.second = owning_qpdf.makeIndirectObject(i2.second);
1309
0
            }
1310
0
        }
1311
0
    }
1312
0
}
1313
1314
void
1315
QPDFObjectHandle::mergeResources(
1316
    QPDFObjectHandle other, std::map<std::string, std::map<std::string, std::string>>* conflicts)
1317
0
{
1318
0
    if (!(isDictionary() && other.isDictionary())) {
1319
0
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1320
0
        return;
1321
0
    }
1322
1323
0
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1324
0
                              std::map<QPDFObjGen, std::string>& og_to_name) {
1325
0
        for (auto const& [key, value]: dict.as_dictionary()) {
1326
0
            if (!value.null() && value.isIndirect()) {
1327
0
                og_to_name.insert_or_assign(value.getObjGen(), key);
1328
0
            }
1329
0
        }
1330
0
    };
1331
1332
    // This algorithm is described in comments in QPDFObjectHandle.hh
1333
    // above the declaration of mergeResources.
1334
0
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1335
0
        auto other_val = value1;
1336
0
        if (hasKey(rtype)) {
1337
0
            QPDFObjectHandle this_val = getKey(rtype);
1338
0
            if (this_val.isDictionary() && other_val.isDictionary()) {
1339
0
                if (this_val.isIndirect()) {
1340
                    // Do this even if there are no keys. Various places in the code call
1341
                    // mergeResources with resource dictionaries that contain empty subdictionaries
1342
                    // just to get this shallow copy functionality.
1343
0
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1344
0
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1345
0
                }
1346
0
                std::map<QPDFObjGen, std::string> og_to_name;
1347
0
                std::set<std::string> rnames;
1348
0
                int min_suffix = 1;
1349
0
                bool initialized_maps = false;
1350
0
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1351
0
                    QPDFObjectHandle rval = value2;
1352
0
                    if (!this_val.hasKey(key)) {
1353
0
                        if (!rval.isIndirect()) {
1354
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1355
0
                            rval = rval.shallowCopy();
1356
0
                        }
1357
0
                        this_val.replaceKey(key, rval);
1358
0
                    } else if (conflicts) {
1359
0
                        if (!initialized_maps) {
1360
0
                            make_og_to_name(this_val, og_to_name);
1361
0
                            rnames = this_val.getResourceNames();
1362
0
                            initialized_maps = true;
1363
0
                        }
1364
0
                        auto rval_og = rval.getObjGen();
1365
0
                        if (rval.isIndirect() && og_to_name.contains(rval_og)) {
1366
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge reuse");
1367
0
                            auto new_key = og_to_name[rval_og];
1368
0
                            if (new_key != key) {
1369
0
                                (*conflicts)[rtype][key] = new_key;
1370
0
                            }
1371
0
                        } else {
1372
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge generate");
1373
0
                            std::string new_key =
1374
0
                                getUniqueResourceName(key + "_", min_suffix, &rnames);
1375
0
                            (*conflicts)[rtype][key] = new_key;
1376
0
                            this_val.replaceKey(new_key, rval);
1377
0
                        }
1378
0
                    }
1379
0
                }
1380
0
            } else if (this_val.isArray() && other_val.isArray()) {
1381
0
                std::set<std::string> scalars;
1382
0
                for (auto this_item: this_val.aitems()) {
1383
0
                    if (this_item.isScalar()) {
1384
0
                        scalars.insert(this_item.unparse());
1385
0
                    }
1386
0
                }
1387
0
                for (auto other_item: other_val.aitems()) {
1388
0
                    if (other_item.isScalar()) {
1389
0
                        if (!scalars.contains(other_item.unparse())) {
1390
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1391
0
                            this_val.appendItem(other_item);
1392
0
                        } else {
1393
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1394
0
                        }
1395
0
                    }
1396
0
                }
1397
0
            }
1398
0
        } else {
1399
0
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1400
0
            replaceKey(rtype, other_val.shallowCopy());
1401
0
        }
1402
0
    }
1403
0
}
1404
1405
std::set<std::string>
1406
QPDFObjectHandle::getResourceNames() const
1407
0
{
1408
    // Return second-level dictionary keys
1409
0
    std::set<std::string> result;
1410
0
    for (auto const& item: as_dictionary(strict)) {
1411
0
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1412
0
            if (!val2.null()) {
1413
0
                result.insert(key2);
1414
0
            }
1415
0
        }
1416
0
    }
1417
0
    return result;
1418
0
}
1419
1420
std::string
1421
QPDFObjectHandle::getUniqueResourceName(
1422
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1423
0
{
1424
0
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1425
0
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1426
0
    while (min_suffix <= max_suffix) {
1427
0
        std::string candidate = prefix + std::to_string(min_suffix);
1428
0
        if (!names.contains(candidate)) {
1429
0
            return candidate;
1430
0
        }
1431
        // Increment after return; min_suffix should be the value
1432
        // used, not the next value.
1433
0
        ++min_suffix;
1434
0
    }
1435
    // This could only happen if there is a coding error.
1436
    // The number of candidates we test is more than the
1437
    // number of keys we're checking against.
1438
0
    throw std::logic_error(
1439
0
        "unable to find unconflicting name in QPDFObjectHandle::getUniqueResourceName");
1440
0
}
1441
1442
// Dictionary mutators are in QPDF_Dictionary.cc
1443
1444
// Stream accessors are in QPDF_Stream.cc
1445
1446
std::map<std::string, QPDFObjectHandle>
1447
QPDFObjectHandle::getPageImages()
1448
0
{
1449
0
    return QPDFPageObjectHelper(*this).getImages();
1450
0
}
1451
1452
std::vector<QPDFObjectHandle>
1453
QPDFObjectHandle::arrayOrStreamToStreamArray(
1454
    std::string const& description, std::string& all_description)
1455
0
{
1456
0
    all_description = description;
1457
0
    std::vector<QPDFObjectHandle> result;
1458
0
    if (auto array = as_array(strict)) {
1459
0
        int n_items = static_cast<int>(array.size());
1460
0
        for (int i = 0; i < n_items; ++i) {
1461
0
            QPDFObjectHandle item = array[i];
1462
0
            if (item.isStream()) {
1463
0
                result.emplace_back(item);
1464
0
            } else {
1465
0
                item.warn(
1466
0
                    {qpdf_e_damaged_pdf,
1467
0
                     "",
1468
0
                     description + ": item index " + std::to_string(i) + " (from 0)",
1469
0
                     0,
1470
0
                     "ignoring non-stream in an array of streams"});
1471
0
            }
1472
0
        }
1473
0
    } else if (isStream()) {
1474
0
        result.emplace_back(*this);
1475
0
    } else if (!null()) {
1476
0
        warn(
1477
0
            {qpdf_e_damaged_pdf,
1478
0
             "",
1479
0
             description,
1480
0
             0,
1481
0
             " object is supposed to be a stream or an array of streams but is neither"});
1482
0
    }
1483
1484
0
    bool first = true;
1485
0
    for (auto const& item: result) {
1486
0
        if (first) {
1487
0
            first = false;
1488
0
        } else {
1489
0
            all_description += ",";
1490
0
        }
1491
0
        all_description += " stream " + item.getObjGen().unparse(' ');
1492
0
    }
1493
1494
0
    return result;
1495
0
}
1496
1497
std::vector<QPDFObjectHandle>
1498
QPDFObjectHandle::getPageContents()
1499
0
{
1500
0
    std::string description = "page object " + getObjGen().unparse(' ');
1501
0
    std::string all_description;
1502
0
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1503
0
}
1504
1505
void
1506
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1507
0
{
1508
0
    new_contents.assertStream();
1509
1510
0
    std::vector<QPDFObjectHandle> content_streams;
1511
0
    if (first) {
1512
0
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1513
0
        content_streams.push_back(new_contents);
1514
0
    }
1515
0
    for (auto const& iter: getPageContents()) {
1516
0
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1517
0
        content_streams.push_back(iter);
1518
0
    }
1519
0
    if (!first) {
1520
0
        content_streams.push_back(new_contents);
1521
0
    }
1522
1523
0
    replaceKey("/Contents", newArray(content_streams));
1524
0
}
1525
1526
void
1527
QPDFObjectHandle::rotatePage(int angle, bool relative)
1528
0
{
1529
0
    if ((angle % 90) != 0) {
1530
0
        throw std::runtime_error(
1531
0
            "QPDF::rotatePage called with an angle that is not a multiple of 90");
1532
0
    }
1533
0
    int new_angle = angle;
1534
0
    if (relative) {
1535
0
        int old_angle = 0;
1536
0
        QPDFObjectHandle cur_obj = *this;
1537
0
        QPDFObjGen::set visited;
1538
0
        while (visited.add(cur_obj)) {
1539
            // Don't get stuck in an infinite loop
1540
0
            if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) {
1541
0
                break;
1542
0
            } else if (cur_obj.getKey("/Parent").isDictionary()) {
1543
0
                cur_obj = cur_obj.getKey("/Parent");
1544
0
            } else {
1545
0
                break;
1546
0
            }
1547
0
        }
1548
0
        QTC::TC("qpdf", "QPDFObjectHandle found old angle", visited.size() > 1 ? 0 : 1);
1549
0
        if ((old_angle % 90) != 0) {
1550
0
            old_angle = 0;
1551
0
        }
1552
0
        new_angle += old_angle;
1553
0
    }
1554
0
    new_angle = (new_angle + 360) % 360;
1555
    // Make this explicit even with new_angle == 0 since /Rotate can be inherited.
1556
0
    replaceKey("/Rotate", Integer(new_angle));
1557
0
}
1558
1559
void
1560
QPDFObjectHandle::coalesceContentStreams()
1561
0
{
1562
0
    QPDFObjectHandle contents = getKey("/Contents");
1563
0
    if (contents.isStream()) {
1564
0
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1565
0
        return;
1566
0
    } else if (!contents.isArray()) {
1567
        // /Contents is optional for pages, and some very damaged files may have pages that are
1568
        // invalid in other ways.
1569
0
        return;
1570
0
    }
1571
    // Should not be possible for a page object to not have an owning PDF unless it was manually
1572
    // constructed in some incorrect way. However, it can happen in a PDF file whose page structure
1573
    // is direct, which is against spec but still possible to hand construct, as in fuzz issue
1574
    // 27393.
1575
0
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1576
1577
0
    QPDFObjectHandle new_contents = newStream(&qpdf);
1578
0
    replaceKey("/Contents", new_contents);
1579
1580
0
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1581
0
    new_contents.replaceStreamData(provider, newNull(), newNull());
1582
0
}
1583
1584
std::string
1585
QPDFObjectHandle::unparse() const
1586
0
{
1587
0
    if (isIndirect()) {
1588
0
        return getObjGen().unparse(' ') + " R";
1589
0
    } else {
1590
0
        return unparseResolved();
1591
0
    }
1592
0
}
1593
1594
std::string
1595
QPDFObjectHandle::unparseResolved() const
1596
0
{
1597
0
    if (!obj) {
1598
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1599
0
    }
1600
0
    return BaseHandle::unparse();
1601
0
}
1602
1603
std::string
1604
QPDFObjectHandle::unparseBinary() const
1605
0
{
1606
0
    if (auto str = as<QPDF_String>()) {
1607
0
        return str->unparse(true);
1608
0
    } else {
1609
0
        return unparse();
1610
0
    }
1611
0
}
1612
1613
JSON
1614
QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) const
1615
0
{
1616
0
    if ((!dereference_indirect) && isIndirect()) {
1617
0
        return JSON::makeString(unparse());
1618
0
    } else if (!obj) {
1619
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1620
0
    } else {
1621
0
        Pl_Buffer p{"json"};
1622
0
        JSON::Writer jw{&p, 0};
1623
0
        writeJSON(json_version, jw, dereference_indirect);
1624
0
        p.finish();
1625
0
        return JSON::parse(p.getString());
1626
0
    }
1627
0
}
1628
1629
void
1630
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1631
0
{
1632
0
    if (!dereference_indirect && isIndirect()) {
1633
0
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1634
0
    } else if (!obj) {
1635
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1636
0
    } else {
1637
0
        write_json(json_version, p);
1638
0
    }
1639
0
}
1640
1641
void
1642
QPDFObjectHandle::writeJSON(
1643
    int json_version, Pipeline* p, bool dereference_indirect, size_t depth) const
1644
0
{
1645
0
    JSON::Writer jw{p, depth};
1646
0
    writeJSON(json_version, jw, dereference_indirect);
1647
0
}
1648
1649
QPDFObjectHandle
1650
QPDFObjectHandle::wrapInArray()
1651
0
{
1652
0
    if (isArray()) {
1653
0
        return *this;
1654
0
    }
1655
0
    QPDFObjectHandle result = QPDFObjectHandle::newArray();
1656
0
    result.appendItem(*this);
1657
0
    return result;
1658
0
}
1659
1660
QPDFObjectHandle
1661
QPDFObjectHandle::parse(std::string const& object_str, std::string const& object_description)
1662
0
{
1663
0
    return parse(nullptr, object_str, object_description);
1664
0
}
1665
1666
QPDFObjectHandle
1667
QPDFObjectHandle::parse(
1668
    QPDF* context, std::string const& object_str, std::string const& object_description)
1669
0
{
1670
0
    auto input = is::OffsetBuffer("parsed object", object_str);
1671
0
    auto result = Parser::parse(input, object_description, context);
1672
0
    size_t offset = QIntC::to_size(input.tell());
1673
0
    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
0
    return result;
1686
0
}
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
20.6k
{
1896
20.6k
    return {QPDFObject::create<QPDF_Null>()};
1897
20.6k
}
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
16.2k
{
1908
16.2k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1909
16.2k
}
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
4.05k
{
1932
4.05k
    return {QPDFObject::create<QPDF_Array>(items)};
1933
4.05k
}
1934
1935
QPDFObjectHandle
1936
QPDFObjectHandle::newArray(Rectangle const& rect)
1937
4.05k
{
1938
4.05k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1939
4.05k
}
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
5.93k
{
1986
5.93k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1987
5.93k
}
1988
1989
QPDFObjectHandle
1990
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1991
5.93k
{
1992
5.93k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1993
5.93k
}
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
50.0k
{
2037
50.0k
    return obj ? obj->getDescription() : ""s;
2038
50.0k
}
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
25.4k
{
2052
25.4k
    return obj && obj->hasDescription();
2053
25.4k
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::shallowCopy()
2057
265
{
2058
265
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
265
    return {copy()};
2062
265
}
2063
2064
QPDFObjectHandle
2065
QPDFObjectHandle::unsafeShallowCopy()
2066
0
{
2067
0
    if (!obj) {
2068
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2069
0
    }
2070
0
    return {copy(true)};
2071
0
}
2072
2073
void
2074
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2075
0
{
2076
0
    static uint32_t constexpr max_nesting = 500;
2077
0
    if (++level > max_nesting + 1) {
2078
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2079
0
    }
2080
0
    assertInitialized();
2081
2082
0
    auto cur_og = getObjGen();
2083
0
    if (!visited.add(cur_og)) {
2084
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2085
0
    }
2086
2087
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2088
0
        obj = copy(true);
2089
0
    } else if (auto a = as_array(strict)) {
2090
0
        std::vector<QPDFObjectHandle> items;
2091
0
        for (auto const& item: a) {
2092
0
            items.emplace_back(item);
2093
0
            items.back().makeDirect(level, visited, stop_at_streams);
2094
0
        }
2095
0
        obj = QPDFObject::create<QPDF_Array>(items);
2096
0
    } else if (isDictionary()) {
2097
0
        std::map<std::string, QPDFObjectHandle> items;
2098
0
        for (auto const& [key, value]: as_dictionary(strict)) {
2099
0
            if (!value.null()) {
2100
0
                items.insert({key, value});
2101
0
                items[key].makeDirect(level, visited, stop_at_streams);
2102
0
            }
2103
0
        }
2104
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2105
0
    } else if (isStream()) {
2106
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2107
0
        if (!stop_at_streams) {
2108
0
            throw std::runtime_error("attempt to make a stream into a direct object");
2109
0
        }
2110
0
    } else if (isReserved()) {
2111
0
        throw std::logic_error(
2112
0
            "QPDFObjectHandle: attempting to make a reserved object handle direct");
2113
0
    } else {
2114
0
        throw std::logic_error("QPDFObjectHandle::makeDirectInternal: unknown object type");
2115
0
    }
2116
2117
0
    visited.erase(cur_og);
2118
0
}
2119
2120
void
2121
QPDFObjectHandle::makeDirect(bool allow_streams)
2122
0
{
2123
0
    QPDFObjGen::set visited;
2124
0
    makeDirect(0, visited, allow_streams);
2125
0
}
2126
2127
void
2128
QPDFObjectHandle::assertInitialized() const
2129
0
{
2130
0
    if (!obj) {
2131
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2132
0
    }
2133
0
}
2134
2135
std::invalid_argument
2136
BaseHandle::invalid_error(std::string const& method) const
2137
1.83k
{
2138
1.83k
    return std::invalid_argument(method + " operation attempted on invalid object");
2139
1.83k
}
2140
std::runtime_error
2141
BaseHandle::type_error(char const* expected_type) const
2142
0
{
2143
0
    return std::runtime_error(
2144
0
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2145
0
}
2146
2147
QPDFExc
2148
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2149
4.38k
{
2150
4.38k
    return {
2151
4.38k
        qpdf_e_object,
2152
4.38k
        "",
2153
4.38k
        description(),
2154
4.38k
        0,
2155
4.38k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2156
4.38k
            message};
2157
4.38k
}
2158
2159
void
2160
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2161
4.38k
{
2162
4.38k
    if (!obj) {
2163
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2164
0
    }
2165
4.38k
    warn(type_error(expected_type, message));
2166
4.38k
}
2167
2168
void
2169
QPDFObjectHandle::warnIfPossible(std::string const& warning) const
2170
0
{
2171
0
    warn(warning);
2172
0
}
2173
2174
void
2175
QPDFObjectHandle::objectWarning(std::string const& warning) const
2176
0
{
2177
0
    warn({qpdf_e_object, "", description(), 0, warning});
2178
0
}
2179
2180
void
2181
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2182
0
{
2183
0
    if (!istype) {
2184
0
        throw type_error(type_name);
2185
0
    }
2186
0
}
2187
2188
void
2189
QPDFObjectHandle::assertNull() const
2190
0
{
2191
0
    assertType("null", isNull());
2192
0
}
2193
2194
void
2195
QPDFObjectHandle::assertBool() const
2196
0
{
2197
0
    assertType("boolean", isBool());
2198
0
}
2199
2200
void
2201
QPDFObjectHandle::assertInteger() const
2202
0
{
2203
0
    assertType("integer", isInteger());
2204
0
}
2205
2206
void
2207
QPDFObjectHandle::assertReal() const
2208
0
{
2209
0
    assertType("real", isReal());
2210
0
}
2211
2212
void
2213
QPDFObjectHandle::assertName() const
2214
0
{
2215
0
    assertType("name", isName());
2216
0
}
2217
2218
void
2219
QPDFObjectHandle::assertString() const
2220
0
{
2221
0
    assertType("string", isString());
2222
0
}
2223
2224
void
2225
QPDFObjectHandle::assertOperator() const
2226
0
{
2227
0
    assertType("operator", isOperator());
2228
0
}
2229
2230
void
2231
QPDFObjectHandle::assertInlineImage() const
2232
0
{
2233
0
    assertType("inlineimage", isInlineImage());
2234
0
}
2235
2236
void
2237
QPDFObjectHandle::assertArray() const
2238
0
{
2239
0
    assertType("array", isArray());
2240
0
}
2241
2242
void
2243
QPDFObjectHandle::assertDictionary() const
2244
0
{
2245
0
    assertType("dictionary", isDictionary());
2246
0
}
2247
2248
void
2249
QPDFObjectHandle::assertStream() const
2250
0
{
2251
0
    assertType("stream", isStream());
2252
0
}
2253
2254
void
2255
QPDFObjectHandle::assertReserved() const
2256
0
{
2257
0
    assertType("reserved", isReserved());
2258
0
}
2259
2260
void
2261
QPDFObjectHandle::assertIndirect() const
2262
0
{
2263
0
    if (!isIndirect()) {
2264
0
        throw std::logic_error("operation for indirect object attempted on direct object");
2265
0
    }
2266
0
}
2267
2268
void
2269
QPDFObjectHandle::assertScalar() const
2270
0
{
2271
0
    assertType("scalar", isScalar());
2272
0
}
2273
2274
void
2275
QPDFObjectHandle::assertNumber() const
2276
0
{
2277
0
    assertType("number", isNumber());
2278
0
}
2279
2280
bool
2281
QPDFObjectHandle::isPageObject() const
2282
0
{
2283
    // See comments in QPDFObjectHandle.hh.
2284
0
    if (!qpdf()) {
2285
0
        return false;
2286
0
    }
2287
    // getAllPages repairs /Type when traversing the page tree.
2288
0
    (void)qpdf()->doc().pages().all();
2289
0
    return isDictionaryOfType("/Page");
2290
0
}
2291
2292
bool
2293
QPDFObjectHandle::isPagesObject() const
2294
0
{
2295
0
    if (!qpdf()) {
2296
0
        return false;
2297
0
    }
2298
    // getAllPages repairs /Type when traversing the page tree.
2299
0
    (void)qpdf()->doc().pages().all();
2300
0
    return isDictionaryOfType("/Pages");
2301
0
}
2302
2303
bool
2304
QPDFObjectHandle::isFormXObject() const
2305
0
{
2306
0
    return isStreamOfType("", "/Form");
2307
0
}
2308
2309
bool
2310
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2311
0
{
2312
0
    return (
2313
0
        isStreamOfType("", "/Image") &&
2314
0
        ((!exclude_imagemask) ||
2315
0
         (!(getDict().getKey("/ImageMask").isBool() &&
2316
0
            getDict().getKey("/ImageMask").getBoolValue()))));
2317
0
}
2318
2319
void
2320
QPDFObjectHandle::assertPageObject() const
2321
0
{
2322
0
    if (!isPageObject()) {
2323
0
        throw std::runtime_error("page operation called on non-Page object");
2324
0
    }
2325
0
}
2326
2327
void
2328
BaseHandle::warn(QPDF* qpdf, QPDFExc&& e)
2329
0
{
2330
0
    if (!qpdf) {
2331
0
        throw std::move(e);
2332
0
    }
2333
0
    qpdf->warn(std::move(e));
2334
0
}
2335
2336
void
2337
BaseHandle::warn(QPDFExc&& e) const
2338
50.0k
{
2339
50.0k
    if (!qpdf()) {
2340
172
        throw std::move(e);
2341
172
    }
2342
49.8k
    qpdf()->warn(std::move(e));
2343
49.8k
}
2344
2345
void
2346
BaseHandle::warn(std::string const& warning) const
2347
54.7k
{
2348
54.7k
    if (qpdf()) {
2349
45.6k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2350
45.6k
    } else {
2351
9.14k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2352
9.14k
    }
2353
54.7k
}
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
36.3k
{
2523
36.3k
    return obj ? obj->getObjGen() : QPDFObjGen();
2524
36.3k
}
2525
2526
int
2527
QPDFObjectHandle::getObjectID() const
2528
24.0k
{
2529
24.0k
    return getObjGen().getObj();
2530
24.0k
}
2531
2532
int
2533
QPDFObjectHandle::getGeneration() const
2534
0
{
2535
0
    return getObjGen().getGen();
2536
0
}
2537
2538
bool
2539
QPDFObjectHandle::isIndirect() const
2540
18.4k
{
2541
18.4k
    return getObjectID() != 0;
2542
18.4k
}
2543
2544
// Indirect object accessors
2545
QPDF*
2546
QPDFObjectHandle::getOwningQPDF() const
2547
59.7k
{
2548
59.7k
    return obj ? obj->getQPDF() : nullptr;
2549
59.7k
}
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
32
{
2563
32
    if (obj) {
2564
32
        obj->setParsedOffset(offset);
2565
32
    }
2566
32
}
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
}