Coverage Report

Created: 2026-10-17 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/QPDFObjectHandle.cc
Line
Count
Source
1
#include <qpdf/QPDFObjectHandle_private.hh>
2
3
#include <qpdf/JSON_writer.hh>
4
#include <qpdf/Pipeline_private.hh>
5
#include <qpdf/Pl_Buffer.hh>
6
#include <qpdf/Pl_QPDFTokenizer.hh>
7
#include <qpdf/QIntC.hh>
8
#include <qpdf/QPDF.hh>
9
#include <qpdf/QPDFExc.hh>
10
#include <qpdf/QPDFLogger.hh>
11
#include <qpdf/QPDFMatrix.hh>
12
#include <qpdf/QPDFObject_private.hh>
13
#include <qpdf/QPDFPageObjectHelper.hh>
14
#include <qpdf/QPDFParser.hh>
15
#include <qpdf/QTC.hh>
16
#include <qpdf/Util.hh>
17
18
#include <array>
19
#include <cctype>
20
#include <climits>
21
#include <cstdlib>
22
#include <cstring>
23
#include <stdexcept>
24
25
using namespace std::literals;
26
using namespace qpdf;
27
28
using Parser = impl::Parser;
29
30
const Null Null::temp_;
31
32
BaseHandle::
33
operator QPDFObjGen() const
34
110k
{
35
110k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
110k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
26.7k
    supports_retry(supports_retry)
47
26.7k
{
48
26.7k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
26.7k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
26.7k
}
54
55
void
56
QPDFObjectHandle::StreamDataProvider::provideStreamData(QPDFObjGen const& og, Pipeline* pipeline)
57
0
{
58
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline);
59
0
}
60
61
bool
62
QPDFObjectHandle::StreamDataProvider::provideStreamData(
63
    QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
64
0
{
65
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline, suppress_warnings, will_retry);
66
0
}
67
68
void
69
QPDFObjectHandle::StreamDataProvider::provideStreamData(
70
    int objid, int generation, Pipeline* pipeline)
71
0
{
72
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
73
0
}
74
75
bool
76
QPDFObjectHandle::StreamDataProvider::provideStreamData(
77
    int objid, int generation, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
78
0
{
79
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
80
0
    return false;
81
0
}
82
83
bool
84
QPDFObjectHandle::StreamDataProvider::supportsRetry()
85
0
{
86
0
    return supports_retry;
87
0
}
88
89
namespace
90
{
91
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
92
    {
93
      public:
94
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
95
0
            containing_page(containing_page),
96
0
            old_contents(old_contents)
97
0
        {
98
0
        }
99
0
        ~CoalesceProvider() override = default;
100
        void provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override;
101
102
      private:
103
        QPDFObjectHandle containing_page;
104
        QPDFObjectHandle old_contents;
105
    };
106
} // namespace
107
108
void
109
CoalesceProvider::provideStreamData(QPDFObjGen const&, Pipeline* p)
110
0
{
111
0
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
112
0
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
113
0
    std::string all_description;
114
0
    old_contents.pipeContentStreams(p, description, all_description);
115
0
}
116
117
void
118
QPDFObjectHandle::TokenFilter::handleEOF()
119
0
{
120
0
}
121
122
void
123
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
124
0
{
125
0
    pipeline = p;
126
0
}
127
128
void
129
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
130
0
{
131
0
    if (!pipeline) {
132
0
        return;
133
0
    }
134
0
    if (len) {
135
0
        pipeline->write(data, len);
136
0
    }
137
0
}
138
139
void
140
QPDFObjectHandle::TokenFilter::write(std::string const& str)
141
0
{
142
0
    write(str.c_str(), str.length());
143
0
}
144
145
void
146
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
147
0
{
148
0
    std::string const& value = token.getRawValue();
149
0
    write(value.c_str(), value.length());
150
0
}
151
152
void
153
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle)
154
0
{
155
0
    throw std::logic_error("You must override one of the handleObject methods in ParserCallbacks");
156
0
}
157
158
void
159
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle oh, size_t, size_t)
160
0
{
161
    // This version of handleObject was added in qpdf 9. If the developer did not override it, fall
162
    // back to the older interface.
163
0
    handleObject(oh);
164
0
}
165
166
void
167
QPDFObjectHandle::ParserCallbacks::contentSize(size_t)
168
0
{
169
    // Ignore by default; overriding this is optional.
170
0
}
171
172
void
173
QPDFObjectHandle::ParserCallbacks::terminateParsing()
174
0
{
175
0
    throw TerminateParsing();
176
0
}
177
178
std::pair<bool, bool>
179
Name::analyzeJSONEncoding(const std::string& name)
180
0
{
181
0
    int tail = 0;       // Number of continuation characters expected.
182
0
    bool tail2 = false; // Potential overlong 3 octet utf-8.
183
0
    bool tail3 = false; // potential overlong 4 octet
184
0
    bool needs_escaping = false;
185
0
    for (auto const& it: name) {
186
0
        auto c = static_cast<unsigned char>(it);
187
0
        if (tail) {
188
0
            if ((c & 0xc0) != 0x80) {
189
0
                return {false, false};
190
0
            }
191
0
            if (tail2) {
192
0
                if ((c & 0xe0) == 0x80) {
193
0
                    return {false, false};
194
0
                }
195
0
                tail2 = false;
196
0
            } else if (tail3) {
197
0
                if ((c & 0xf0) == 0x80) {
198
0
                    return {false, false};
199
0
                }
200
0
                tail3 = false;
201
0
            }
202
0
            tail--;
203
0
        } else if (c < 0x80) {
204
0
            if (!needs_escaping) {
205
0
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
206
0
            }
207
0
        } else if ((c & 0xe0) == 0xc0) {
208
0
            if ((c & 0xfe) == 0xc0) {
209
0
                return {false, false};
210
0
            }
211
0
            tail = 1;
212
0
        } else if ((c & 0xf0) == 0xe0) {
213
0
            tail2 = (c == 0xe0);
214
0
            tail = 2;
215
0
        } else if ((c & 0xf8) == 0xf0) {
216
0
            tail3 = (c == 0xf0);
217
0
            tail = 3;
218
0
        } else {
219
0
            return {false, false};
220
0
        }
221
0
    }
222
0
    return {tail == 0, !needs_escaping};
223
0
}
224
225
std::string
226
Name::normalize(std::string const& name)
227
1.18M
{
228
1.18M
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
1.18M
    std::string result;
232
1.18M
    result += name.at(0);
233
52.5M
    for (size_t i = 1; i < name.length(); ++i) {
234
51.3M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
51.3M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
36.9k
            result += "#";
239
51.3M
        } else if (
240
51.3M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
45.4M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
32.7M
            result += util::hex_encode_char(ch);
243
32.7M
        } else {
244
18.5M
            result += ch;
245
18.5M
        }
246
51.3M
    }
247
1.18M
    return result;
248
1.18M
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
152k
{
253
152k
    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
12.8k
    case ::ot_null:
260
12.8k
        return QPDFObject::create<QPDF_Null>();
261
224
    case ::ot_boolean:
262
224
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
12.5k
    case ::ot_integer:
264
12.5k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
2.49k
    case ::ot_real:
266
2.49k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
3.13k
    case ::ot_string:
268
3.13k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
35.7k
    case ::ot_name:
270
35.7k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
1.90k
    case ::ot_array:
272
1.90k
        {
273
1.90k
            auto const& a = std::get<QPDF_Array>(obj->value);
274
1.90k
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
1.90k
            } else {
277
1.90k
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
1.90k
                if (a.sp) {
279
24
                    QPDF_Array result;
280
24
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
24
                    result.sp->size = a.sp->size;
282
846
                    for (auto const& [idx, oh]: a.sp->elements) {
283
846
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
846
                    }
285
24
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
1.87k
                } else {
287
1.87k
                    std::vector<QPDFObjectHandle> result;
288
1.87k
                    result.reserve(a.elements.size());
289
32.6k
                    for (auto const& element: a.elements) {
290
32.6k
                        result.emplace_back(
291
32.6k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
32.6k
                    }
293
1.87k
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
1.87k
                }
295
1.90k
            }
296
1.90k
        }
297
83.3k
    case ::ot_dictionary:
298
83.3k
        {
299
83.3k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
83.3k
            if (shallow) {
301
81.8k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
81.8k
            } else {
303
1.43k
                std::map<std::string, QPDFObjectHandle> new_items;
304
6.38k
                for (auto const& [key, val]: d.items) {
305
6.38k
                    new_items[key] = val.indirect() ? val : val.copy();
306
6.38k
                }
307
1.43k
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
1.43k
            }
309
83.3k
        }
310
0
    case ::ot_stream:
311
0
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
312
0
        throw std::runtime_error("stream objects cannot be cloned");
313
0
        return {}; // does not return
314
0
    case ::ot_operator:
315
0
        return QPDFObject::create<QPDF_Operator>(std::get<QPDF_Operator>(obj->value).val);
316
0
    case ::ot_inlineimage:
317
0
        return QPDFObject::create<QPDF_InlineImage>(std::get<QPDF_InlineImage>(obj->value).val);
318
0
    case ::ot_unresolved:
319
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
320
0
        return {}; // does not return
321
0
    case ::ot_destroyed:
322
0
        throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF");
323
0
        return {}; // does not return
324
0
    case ::ot_reference:
325
0
        return referenced_object().obj_sp();
326
152k
    }
327
0
    return {}; // unreachable
328
152k
}
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
2.14M
{
475
2.14M
    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
223k
    case ::ot_null:
483
223k
        return "null";
484
9.68k
    case ::ot_boolean:
485
9.68k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
1.05M
    case ::ot_integer:
487
1.05M
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
108k
    case ::ot_real:
489
108k
        return std::get<QPDF_Real>(obj->value).val;
490
271k
    case ::ot_string:
491
271k
        return std::get<QPDF_String>(obj->value).unparse(false);
492
475k
    case ::ot_name:
493
475k
        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
2.14M
    }
545
0
    return {}; // unreachable
546
2.14M
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
0
{
551
0
    switch (resolved_type_code()) {
552
0
    case ::ot_uninitialized:
553
0
        throw std::logic_error(
554
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
555
0
        break; // unreachable
556
0
    case ::ot_null:
557
0
    case ::ot_operator:
558
0
    case ::ot_inlineimage:
559
0
        p << "null";
560
0
        break;
561
0
    case ::ot_boolean:
562
0
        p << std::get<QPDF_Bool>(obj->value).val;
563
0
        break;
564
0
    case ::ot_integer:
565
0
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
0
        break;
567
0
    case ::ot_real:
568
0
        {
569
0
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
0
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
0
            } else if (val.at(0) == '.') {
574
0
                p << "0" << val;
575
0
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
0
                p << "-0." << val.substr(2);
577
0
            } else if (val.at(0) == '0') {
578
0
                auto i = val.find_first_not_of('0');
579
0
                if (i == std::string::npos) {
580
0
                    p << "0";
581
0
                } else if (val.at(i) == '.') {
582
0
                    p << "0" << val.substr(i);
583
0
                } else {
584
0
                    p << val.substr(i);
585
0
                }
586
0
            } else {
587
0
                p << val;
588
0
            }
589
0
            if (val.back() == '.') {
590
0
                p << "0";
591
0
            }
592
0
        }
593
0
        break;
594
0
    case ::ot_string:
595
0
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
596
0
        break;
597
0
    case ::ot_name:
598
0
        {
599
0
            auto const& n = std::get<QPDF_Name>(obj->value);
600
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
601
            // updating this method make sure QPDF_Dictionary is also update.
602
0
            if (json_version == 1) {
603
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
0
            } else {
605
0
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
606
0
                    if (res.second) {
607
0
                        p << "\"" << n.name << "\"";
608
0
                    } else {
609
0
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
610
0
                    }
611
0
                } else {
612
0
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
613
0
                }
614
0
            }
615
0
        }
616
0
        break;
617
0
    case ::ot_array:
618
0
        {
619
0
            auto const& a = std::get<QPDF_Array>(obj->value);
620
0
            p.writeStart('[');
621
0
            if (a.sp) {
622
0
                size_t next = 0;
623
0
                for (auto& [key, value]: a.sp->elements) {
624
0
                    for (size_t j = next; j < key; ++j) {
625
0
                        p.writeNext() << "null";
626
0
                    }
627
0
                    p.writeNext();
628
0
                    auto item_og = value.id_gen();
629
0
                    if (item_og.isIndirect()) {
630
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
631
0
                    } else {
632
0
                        value.write_json(json_version, p);
633
0
                    }
634
0
                    next = key + 1;
635
0
                }
636
0
                for (size_t j = next; j < a.sp->size; ++j) {
637
0
                    p.writeNext() << "null";
638
0
                }
639
0
            } else {
640
0
                for (auto const& item: a.elements) {
641
0
                    p.writeNext();
642
0
                    auto item_og = item.id_gen();
643
0
                    if (item_og.isIndirect()) {
644
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
645
0
                    } else {
646
0
                        item.write_json(json_version, p);
647
0
                    }
648
0
                }
649
0
            }
650
0
            p.writeEnd(']');
651
0
        }
652
0
        break;
653
0
    case ::ot_dictionary:
654
0
        {
655
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
656
0
            p.writeStart('{');
657
0
            for (auto& iter: d.items) {
658
0
                if (!iter.second.null()) {
659
0
                    p.writeNext();
660
0
                    if (json_version == 1) {
661
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
0
                          << "\": ";
663
0
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
664
0
                        if (res.second) {
665
0
                            p << "\"" << iter.first << "\": ";
666
0
                        } else {
667
0
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
668
0
                        }
669
0
                    } else {
670
0
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
671
0
                          << "\": ";
672
0
                    }
673
0
                    iter.second.writeJSON(json_version, p);
674
0
                }
675
0
            }
676
0
            p.writeEnd('}');
677
0
        }
678
0
        break;
679
0
    case ::ot_stream:
680
0
        std::get<QPDF_Stream>(obj->value).m->stream_dict.writeJSON(json_version, p);
681
0
        break;
682
0
    case ::ot_reference:
683
0
        p << "\"" << obj->og.unparse(' ') << " R\"";
684
0
        break;
685
0
    default:
686
0
        throw std::logic_error("attempted to write an unsuitable object as JSON");
687
0
    }
688
0
}
689
690
void
691
BaseHandle::disconnect(bool only_direct)
692
2.60M
{
693
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
694
2.60M
    if (only_direct && indirect()) {
695
235k
        return;
696
235k
    }
697
698
2.37M
    switch (raw_type_code()) {
699
103k
    case ::ot_array:
700
103k
        {
701
103k
            auto& a = std::get<QPDF_Array>(obj->value);
702
103k
            if (a.sp) {
703
26.4k
                for (auto& item: a.sp->elements) {
704
26.4k
                    item.second.disconnect();
705
26.4k
                }
706
102k
            } else {
707
1.24M
                for (auto& oh: a.elements) {
708
1.24M
                    oh.disconnect();
709
1.24M
                }
710
102k
            }
711
103k
        }
712
103k
        break;
713
198k
    case ::ot_dictionary:
714
741k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
715
741k
            iter.second.disconnect();
716
741k
        }
717
198k
        break;
718
40.2k
    case ::ot_stream:
719
40.2k
        {
720
40.2k
            auto& s = std::get<QPDF_Stream>(obj->value);
721
40.2k
            s.m->stream_provider = nullptr;
722
40.2k
            s.m->stream_dict.disconnect();
723
40.2k
        }
724
40.2k
        break;
725
0
    case ::ot_uninitialized:
726
0
        return;
727
2.03M
    default:
728
2.03M
        break;
729
2.37M
    }
730
2.37M
    obj->qpdf = nullptr;
731
2.37M
    obj->og = QPDFObjGen();
732
2.37M
}
733
734
bool
735
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
736
923
{
737
923
    return obj == rhs.obj;
738
923
}
739
740
qpdf_object_type_e
741
QPDFObjectHandle::getTypeCode() const
742
2.54M
{
743
2.54M
    return type_code();
744
2.54M
}
745
746
char const*
747
BaseHandle::type_name() const
748
13.4k
{
749
13.4k
    static constexpr std::array<char const*, 16> tn{
750
13.4k
        "uninitialized",
751
13.4k
        "reserved",
752
13.4k
        "null",
753
13.4k
        "boolean",
754
13.4k
        "integer",
755
13.4k
        "real",
756
13.4k
        "string",
757
13.4k
        "name",
758
13.4k
        "array",
759
13.4k
        "dictionary",
760
13.4k
        "stream",
761
13.4k
        "operator",
762
13.4k
        "inline-image",
763
13.4k
        "unresolved",
764
13.4k
        "destroyed",
765
13.4k
        "reference"};
766
13.4k
    return tn[type_code()];
767
13.4k
}
768
769
char const*
770
QPDFObjectHandle::getTypeName() const
771
0
{
772
0
    return type_name();
773
0
}
774
775
bool
776
QPDFObjectHandle::isDestroyed() const
777
0
{
778
0
    return type_code() == ::ot_destroyed;
779
0
}
780
781
bool
782
QPDFObjectHandle::isBool() const
783
46.9k
{
784
46.9k
    return type_code() == ::ot_boolean;
785
46.9k
}
786
787
bool
788
QPDFObjectHandle::isDirectNull() const
789
0
{
790
    // Don't call dereference() -- this is a const method, and we know
791
    // objid == 0, so there's nothing to resolve.
792
0
    return !indirect() && raw_type_code() == ::ot_null;
793
0
}
794
795
bool
796
QPDFObjectHandle::isNull() const
797
25.3k
{
798
25.3k
    return type_code() == ::ot_null;
799
25.3k
}
800
801
bool
802
QPDFObjectHandle::isInteger() const
803
176k
{
804
176k
    return type_code() == ::ot_integer;
805
176k
}
806
807
bool
808
QPDFObjectHandle::isReal() const
809
23.1k
{
810
23.1k
    return type_code() == ::ot_real;
811
23.1k
}
812
813
bool
814
QPDFObjectHandle::isNumber() const
815
27.2k
{
816
27.2k
    return (isInteger() || isReal());
817
27.2k
}
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
95.7k
{
846
95.7k
    return type_code() == ::ot_name;
847
95.7k
}
848
849
bool
850
QPDFObjectHandle::isString() const
851
17.7k
{
852
17.7k
    return type_code() == ::ot_string;
853
17.7k
}
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
3.46M
{
870
3.46M
    return type_code() == ::ot_array;
871
3.46M
}
872
873
bool
874
QPDFObjectHandle::isDictionary() const
875
2.70M
{
876
2.70M
    return type_code() == ::ot_dictionary;
877
2.70M
}
878
879
bool
880
QPDFObjectHandle::isStream() const
881
2.01M
{
882
2.01M
    return type_code() == ::ot_stream;
883
2.01M
}
884
885
bool
886
QPDFObjectHandle::isReserved() const
887
0
{
888
0
    return type_code() == ::ot_reserved;
889
0
}
890
891
bool
892
QPDFObjectHandle::isScalar() const
893
2.72k
{
894
2.72k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
895
2.72k
}
896
897
bool
898
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
899
57.1k
{
900
57.1k
    return Name(*this) == name;
901
57.1k
}
902
903
bool
904
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
905
668k
{
906
668k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
907
26.3k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
908
668k
}
909
910
bool
911
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
912
104k
{
913
104k
    if (auto stream = as_stream()) {
914
40.7k
        return stream && (type.empty() || stream.Type() == type) &&
915
14.6k
            (subtype.empty() || stream.Subtype() == subtype);
916
40.7k
    }
917
63.9k
    return false;
918
104k
}
919
920
// Bool accessors
921
922
bool
923
QPDFObjectHandle::getBoolValue() const
924
3
{
925
3
    if (auto boolean = as<QPDF_Bool>()) {
926
3
        return boolean->val;
927
3
    } else {
928
0
        typeWarning("boolean", "returning false");
929
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
930
0
        return false;
931
0
    }
932
3
}
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.83k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
948
1.83k
{
949
1.83k
}
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
152k
{
960
152k
    auto* i = as<QPDF_Integer>();
961
152k
    if (!i) {
962
1.37k
        throw invalid_error("Integer");
963
1.37k
    }
964
151k
    return i->val;
965
152k
}
966
967
long long
968
QPDFObjectHandle::getIntValue() const
969
11.1k
{
970
11.1k
    if (auto const integer = Integer(*this)) {
971
11.1k
        return integer;
972
11.1k
    } else {
973
0
        typeWarning("integer", "returning 0");
974
0
        return 0;
975
0
    }
976
11.1k
}
977
978
bool
979
QPDFObjectHandle::getValueAsInt(long long& value) const
980
7.55k
{
981
7.55k
    if (auto const integer = Integer(*this)) {
982
7.42k
        value = integer;
983
7.42k
        return true;
984
7.42k
    }
985
131
    return false;
986
7.55k
}
987
988
int
989
QPDFObjectHandle::getIntValueAsInt() const
990
45.5k
{
991
45.5k
    try {
992
45.5k
        return Integer(*this).value<int>();
993
45.5k
    } catch (std::invalid_argument&) {
994
14
        typeWarning("integer", "returning 0");
995
14
        return 0;
996
14
    }
997
45.5k
}
998
999
bool
1000
QPDFObjectHandle::getValueAsInt(int& value) const
1001
1.90k
{
1002
1.90k
    if (!isInteger()) {
1003
27
        return false;
1004
27
    }
1005
1.87k
    value = getIntValueAsInt();
1006
1.87k
    return true;
1007
1.90k
}
1008
1009
unsigned long long
1010
QPDFObjectHandle::getUIntValue() const
1011
31.1k
{
1012
31.1k
    try {
1013
31.1k
        return Integer(*this).value<unsigned long long>();
1014
31.1k
    } catch (std::invalid_argument&) {
1015
740
        typeWarning("integer", "returning 0");
1016
740
        return 0;
1017
740
    }
1018
31.1k
}
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
8.52k
{
1033
8.52k
    try {
1034
8.52k
        return Integer(*this).value<unsigned int>();
1035
8.52k
    } catch (std::invalid_argument&) {
1036
616
        typeWarning("integer", "returning 0");
1037
616
        return 0;
1038
616
    }
1039
8.52k
}
1040
1041
bool
1042
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1043
2.21k
{
1044
2.21k
    if (!isInteger()) {
1045
307
        return false;
1046
307
    }
1047
1.90k
    value = getUIntValueAsUInt();
1048
1.90k
    return true;
1049
2.21k
}
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
23.4k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1088
23.4k
{
1089
23.4k
}
1090
1091
std::string const&
1092
Name::value() const
1093
365k
{
1094
365k
    auto* n = as<QPDF_Name>();
1095
365k
    if (!n) {
1096
0
        throw invalid_error("Name");
1097
0
    }
1098
365k
    return n->name;
1099
365k
}
1100
1101
std::string
1102
QPDFObjectHandle::getName() const
1103
17.5k
{
1104
17.5k
    if (auto* name = as<QPDF_Name>()) {
1105
17.5k
        return name->name;
1106
17.5k
    }
1107
0
    typeWarning("name", "returning dummy name");
1108
0
    return "/QPDFFakeName";
1109
17.5k
}
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
2
{
1126
2
    return {QPDFObject::create<QPDF_String>(str)};
1127
2
}
1128
1129
QPDFObjectHandle
1130
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1131
0
{
1132
0
    return {String::utf16(utf8_str).obj_sp()};
1133
0
}
1134
1135
String::String(std::string const& str) :
1136
0
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1137
0
{
1138
0
}
1139
1140
String::String(std::string&& str) :
1141
0
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1142
0
{
1143
0
}
1144
1145
String
1146
String::utf16(std::string const& utf8_str)
1147
0
{
1148
0
    std::string result;
1149
0
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1150
0
        return String(result);
1151
0
    }
1152
0
    return String(QUtil::utf8_to_utf16(utf8_str));
1153
0
}
1154
1155
std::string const&
1156
String::value() const
1157
4.95k
{
1158
4.95k
    auto* s = as<QPDF_String>();
1159
4.95k
    if (!s) {
1160
0
        throw invalid_error("String");
1161
0
    }
1162
4.95k
    return s->val;
1163
4.95k
}
1164
1165
std::string
1166
String::utf8_value() const
1167
0
{
1168
0
    auto* s = as<QPDF_String>();
1169
0
    if (!s) {
1170
0
        throw invalid_error("String");
1171
0
    }
1172
0
    if (util::is_utf16(s->val)) {
1173
0
        return QUtil::utf16_to_utf8(s->val);
1174
0
    }
1175
0
    if (util::is_explicit_utf8(s->val)) {
1176
        // PDF 2.0 allows UTF-8 strings when explicitly prefixed with the three-byte representation
1177
        // of U+FEFF.
1178
0
        return s->val.substr(3);
1179
0
    }
1180
0
    return QUtil::pdf_doc_to_utf8(s->val);
1181
0
}
1182
1183
std::string
1184
QPDFObjectHandle::getStringValue() const
1185
4.22k
{
1186
4.22k
    try {
1187
4.22k
        return String(obj).value();
1188
4.22k
    } catch (std::invalid_argument&) {
1189
0
        typeWarning("string", "returning empty string");
1190
0
        return {};
1191
0
    }
1192
4.22k
}
1193
1194
bool
1195
QPDFObjectHandle::getValueAsString(std::string& value) const
1196
0
{
1197
0
    try {
1198
0
        value = String(obj).value();
1199
0
        return true;
1200
0
    } catch (std::invalid_argument&) {
1201
0
        return false;
1202
0
    }
1203
0
}
1204
1205
std::string
1206
QPDFObjectHandle::getUTF8Value() const
1207
0
{
1208
0
    try {
1209
0
        return String(obj).utf8_value();
1210
0
    } catch (std::invalid_argument&) {
1211
0
        typeWarning("string", "returning empty string");
1212
0
        return {};
1213
0
    }
1214
0
}
1215
1216
bool
1217
QPDFObjectHandle::getValueAsUTF8(std::string& value) const
1218
0
{
1219
0
    try {
1220
0
        value = String(obj).utf8_value();
1221
0
        return true;
1222
0
    } catch (std::invalid_argument&) {
1223
0
        return false;
1224
0
    }
1225
0
}
1226
1227
// Operator and Inline Image accessors
1228
1229
std::string
1230
QPDFObjectHandle::getOperatorValue() const
1231
0
{
1232
0
    if (auto* op = as<QPDF_Operator>()) {
1233
0
        return op->val;
1234
0
    }
1235
0
    typeWarning("operator", "returning fake value");
1236
0
    return "QPDFFAKE";
1237
0
}
1238
1239
bool
1240
QPDFObjectHandle::getValueAsOperator(std::string& value) const
1241
0
{
1242
0
    if (auto* op = as<QPDF_Operator>()) {
1243
0
        value = op->val;
1244
0
        return true;
1245
0
    }
1246
0
    return false;
1247
0
}
1248
1249
std::string
1250
QPDFObjectHandle::getInlineImageValue() const
1251
0
{
1252
0
    if (auto* ii = as<QPDF_InlineImage>()) {
1253
0
        return ii->val;
1254
0
    }
1255
0
    typeWarning("inlineimage", "returning empty data");
1256
0
    return {};
1257
0
}
1258
1259
bool
1260
QPDFObjectHandle::getValueAsInlineImage(std::string& value) const
1261
0
{
1262
0
    if (auto* ii = as<QPDF_InlineImage>()) {
1263
0
        value = ii->val;
1264
0
        return true;
1265
0
    }
1266
0
    return false;
1267
0
}
1268
1269
// Array accessors and mutators are in QPDF_Array.cc
1270
1271
QPDFObjectHandle::QPDFArrayItems
1272
QPDFObjectHandle::aitems()
1273
0
{
1274
0
    return *this;
1275
0
}
1276
1277
// Dictionary accessors are in QPDF_Dictionary.cc
1278
1279
QPDFObjectHandle::QPDFDictItems
1280
QPDFObjectHandle::ditems()
1281
0
{
1282
0
    return {*this};
1283
0
}
1284
1285
// Array and Name accessors
1286
1287
bool
1288
QPDFObjectHandle::isOrHasName(std::string const& value) const
1289
24.1k
{
1290
24.1k
    if (isNameAndEquals(value)) {
1291
18
        return true;
1292
24.1k
    } else if (isArray()) {
1293
23.2k
        for (auto& item: getArrayAsVector()) {
1294
23.2k
            if (item.isNameAndEquals(value)) {
1295
842
                return true;
1296
842
            }
1297
23.2k
        }
1298
8.71k
    }
1299
23.3k
    return false;
1300
24.1k
}
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
20
{
1587
20
    if (isIndirect()) {
1588
20
        return getObjGen().unparse(' ') + " R";
1589
20
    } else {
1590
0
        return unparseResolved();
1591
0
    }
1592
20
}
1593
1594
std::string
1595
QPDFObjectHandle::unparseResolved() const
1596
2.14M
{
1597
2.14M
    if (!obj) {
1598
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1599
0
    }
1600
2.14M
    return BaseHandle::unparse();
1601
2.14M
}
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
58
{
1663
58
    return parse(nullptr, object_str, object_description);
1664
58
}
1665
1666
QPDFObjectHandle
1667
QPDFObjectHandle::parse(
1668
    QPDF* context, std::string const& object_str, std::string const& object_description)
1669
58
{
1670
58
    auto input = is::OffsetBuffer("parsed object", object_str);
1671
58
    auto result = Parser::parse(input, object_description, context);
1672
58
    size_t offset = QIntC::to_size(input.tell());
1673
58
    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
58
    return result;
1686
58
}
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
3.28k
{
1896
3.28k
    return {QPDFObject::create<QPDF_Null>()};
1897
3.28k
}
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
39.6k
{
1908
39.6k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1909
39.6k
}
1910
1911
QPDFObjectHandle
1912
QPDFObjectHandle::newOperator(std::string const& value)
1913
0
{
1914
0
    return {QPDFObject::create<QPDF_Operator>(value)};
1915
0
}
1916
1917
QPDFObjectHandle
1918
QPDFObjectHandle::newInlineImage(std::string const& value)
1919
0
{
1920
0
    return {QPDFObject::create<QPDF_InlineImage>(value)};
1921
0
}
1922
1923
QPDFObjectHandle
1924
QPDFObjectHandle::newArray()
1925
0
{
1926
0
    return newArray(std::vector<QPDFObjectHandle>());
1927
0
}
1928
1929
QPDFObjectHandle
1930
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1931
9.90k
{
1932
9.90k
    return {QPDFObject::create<QPDF_Array>(items)};
1933
9.90k
}
1934
1935
QPDFObjectHandle
1936
QPDFObjectHandle::newArray(Rectangle const& rect)
1937
9.90k
{
1938
9.90k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1939
9.90k
}
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
18.0k
{
1986
18.0k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1987
18.0k
}
1988
1989
QPDFObjectHandle
1990
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1991
18.0k
{
1992
18.0k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1993
18.0k
}
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
87.4k
{
2037
87.4k
    return obj ? obj->getDescription() : ""s;
2038
87.4k
}
2039
2040
void
2041
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
2042
0
{
2043
0
    if (obj) {
2044
0
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
2045
0
        obj->setDescription(owning_qpdf, descr);
2046
0
    }
2047
0
}
2048
2049
bool
2050
QPDFObjectHandle::hasObjectDescription() const
2051
47.6k
{
2052
47.6k
    return obj && obj->hasDescription();
2053
47.6k
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::shallowCopy()
2057
719
{
2058
719
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
719
    return {copy()};
2062
719
}
2063
2064
QPDFObjectHandle
2065
QPDFObjectHandle::unsafeShallowCopy()
2066
81.8k
{
2067
81.8k
    if (!obj) {
2068
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2069
0
    }
2070
81.8k
    return {copy(true)};
2071
81.8k
}
2072
2073
void
2074
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2075
43.4k
{
2076
43.4k
    static uint32_t constexpr max_nesting = 500;
2077
43.4k
    if (++level > max_nesting + 1) {
2078
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2079
0
    }
2080
43.4k
    assertInitialized();
2081
2082
43.4k
    auto cur_og = getObjGen();
2083
43.4k
    if (!visited.add(cur_og)) {
2084
40
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2085
40
    }
2086
2087
43.4k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2088
33.1k
        obj = copy(true);
2089
33.1k
    } else if (auto a = as_array(strict)) {
2090
4.63k
        std::vector<QPDFObjectHandle> items;
2091
27.1k
        for (auto const& item: a) {
2092
27.1k
            items.emplace_back(item);
2093
27.1k
            items.back().makeDirect(level, visited, stop_at_streams);
2094
27.1k
        }
2095
4.63k
        obj = QPDFObject::create<QPDF_Array>(items);
2096
5.60k
    } else if (isDictionary()) {
2097
5.60k
        std::map<std::string, QPDFObjectHandle> items;
2098
21.3k
        for (auto const& [key, value]: as_dictionary(strict)) {
2099
21.3k
            if (!value.null()) {
2100
16.1k
                items.insert({key, value});
2101
16.1k
                items[key].makeDirect(level, visited, stop_at_streams);
2102
16.1k
            }
2103
21.3k
        }
2104
5.60k
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2105
5.60k
    } else if (isStream()) {
2106
1
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2107
1
        if (!stop_at_streams) {
2108
1
            throw std::runtime_error("attempt to make a stream into a direct object");
2109
1
        }
2110
1
    } 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
43.4k
    visited.erase(cur_og);
2118
43.4k
}
2119
2120
void
2121
QPDFObjectHandle::makeDirect(bool allow_streams)
2122
190
{
2123
190
    QPDFObjGen::set visited;
2124
190
    makeDirect(0, visited, allow_streams);
2125
190
}
2126
2127
void
2128
QPDFObjectHandle::assertInitialized() const
2129
43.4k
{
2130
43.4k
    if (!obj) {
2131
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2132
0
    }
2133
43.4k
}
2134
2135
std::invalid_argument
2136
BaseHandle::invalid_error(std::string const& method) const
2137
1.37k
{
2138
1.37k
    return std::invalid_argument(method + " operation attempted on invalid object");
2139
1.37k
}
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
13.4k
{
2150
13.4k
    return {
2151
13.4k
        qpdf_e_object,
2152
13.4k
        "",
2153
13.4k
        description(),
2154
13.4k
        0,
2155
13.4k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2156
13.4k
            message};
2157
13.4k
}
2158
2159
void
2160
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2161
13.4k
{
2162
13.4k
    if (!obj) {
2163
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2164
0
    }
2165
13.4k
    warn(type_error(expected_type, message));
2166
13.4k
}
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
23
{
2177
23
    warn({qpdf_e_object, "", description(), 0, warning});
2178
23
}
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
87.4k
{
2339
87.4k
    if (!qpdf()) {
2340
658
        throw std::move(e);
2341
658
    }
2342
86.8k
    qpdf()->warn(std::move(e));
2343
86.8k
}
2344
2345
void
2346
BaseHandle::warn(std::string const& warning) const
2347
82.1k
{
2348
82.1k
    if (qpdf()) {
2349
74.0k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2350
74.0k
    } else {
2351
8.17k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2352
8.17k
    }
2353
82.1k
}
2354
2355
QPDFObjectHandle::QPDFDictItems::QPDFDictItems(QPDFObjectHandle const& oh) :
2356
0
    oh(oh)
2357
0
{
2358
0
}
2359
2360
QPDFObjectHandle::QPDFDictItems::iterator&
2361
QPDFObjectHandle::QPDFDictItems::iterator::operator++()
2362
0
{
2363
0
    ++m->iter;
2364
0
    updateIValue();
2365
0
    return *this;
2366
0
}
2367
2368
QPDFObjectHandle::QPDFDictItems::iterator&
2369
QPDFObjectHandle::QPDFDictItems::iterator::operator--()
2370
0
{
2371
0
    --m->iter;
2372
0
    updateIValue();
2373
0
    return *this;
2374
0
}
2375
2376
QPDFObjectHandle::QPDFDictItems::iterator::reference
2377
QPDFObjectHandle::QPDFDictItems::iterator::operator*()
2378
0
{
2379
0
    updateIValue();
2380
0
    return ivalue;
2381
0
}
2382
2383
QPDFObjectHandle::QPDFDictItems::iterator::pointer
2384
QPDFObjectHandle::QPDFDictItems::iterator::operator->()
2385
0
{
2386
0
    updateIValue();
2387
0
    return &ivalue;
2388
0
}
2389
2390
bool
2391
QPDFObjectHandle::QPDFDictItems::iterator::operator==(iterator const& other) const
2392
0
{
2393
0
    if (m->is_end && other.m->is_end) {
2394
0
        return true;
2395
0
    }
2396
0
    if (m->is_end || other.m->is_end) {
2397
0
        return false;
2398
0
    }
2399
0
    return (ivalue.first == other.ivalue.first);
2400
0
}
2401
2402
QPDFObjectHandle::QPDFDictItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2403
0
    m(new Members(oh, for_begin))
2404
0
{
2405
0
    updateIValue();
2406
0
}
2407
2408
void
2409
QPDFObjectHandle::QPDFDictItems::iterator::updateIValue()
2410
0
{
2411
0
    m->is_end = (m->iter == m->keys.end());
2412
0
    if (m->is_end) {
2413
0
        ivalue.first = "";
2414
0
        ivalue.second = QPDFObjectHandle();
2415
0
    } else {
2416
0
        ivalue.first = *(m->iter);
2417
0
        ivalue.second = m->oh.getKey(ivalue.first);
2418
0
    }
2419
0
}
2420
2421
QPDFObjectHandle::QPDFDictItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2422
0
    oh(oh)
2423
0
{
2424
0
    keys = oh.getKeys();
2425
0
    iter = for_begin ? keys.begin() : keys.end();
2426
0
}
2427
2428
QPDFObjectHandle::QPDFDictItems::iterator
2429
QPDFObjectHandle::QPDFDictItems::begin()
2430
0
{
2431
0
    return {oh, true};
2432
0
}
2433
2434
QPDFObjectHandle::QPDFDictItems::iterator
2435
QPDFObjectHandle::QPDFDictItems::end()
2436
0
{
2437
0
    return {oh, false};
2438
0
}
2439
2440
QPDFObjectHandle::QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle const& oh) :
2441
0
    oh(oh)
2442
0
{
2443
0
}
2444
2445
QPDFObjectHandle::QPDFArrayItems::iterator&
2446
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2447
0
{
2448
0
    if (!m->is_end) {
2449
0
        ++m->item_number;
2450
0
        updateIValue();
2451
0
    }
2452
0
    return *this;
2453
0
}
2454
2455
QPDFObjectHandle::QPDFArrayItems::iterator&
2456
QPDFObjectHandle::QPDFArrayItems::iterator::operator--()
2457
0
{
2458
0
    if (m->item_number > 0) {
2459
0
        --m->item_number;
2460
0
        updateIValue();
2461
0
    }
2462
0
    return *this;
2463
0
}
2464
2465
QPDFObjectHandle::QPDFArrayItems::iterator::reference
2466
QPDFObjectHandle::QPDFArrayItems::iterator::operator*()
2467
0
{
2468
0
    updateIValue();
2469
0
    return ivalue;
2470
0
}
2471
2472
QPDFObjectHandle::QPDFArrayItems::iterator::pointer
2473
QPDFObjectHandle::QPDFArrayItems::iterator::operator->()
2474
0
{
2475
0
    updateIValue();
2476
0
    return &ivalue;
2477
0
}
2478
2479
bool
2480
QPDFObjectHandle::QPDFArrayItems::iterator::operator==(iterator const& other) const
2481
0
{
2482
0
    return (m->item_number == other.m->item_number);
2483
0
}
2484
2485
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2486
0
    m(new Members(oh, for_begin))
2487
0
{
2488
0
    updateIValue();
2489
0
}
2490
2491
void
2492
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2493
0
{
2494
0
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2495
0
    if (m->is_end) {
2496
0
        ivalue = QPDFObjectHandle();
2497
0
    } else {
2498
0
        ivalue = m->oh.getArrayItem(m->item_number);
2499
0
    }
2500
0
}
2501
2502
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2503
0
    oh(oh)
2504
0
{
2505
0
    item_number = for_begin ? 0 : oh.getArrayNItems();
2506
0
}
2507
2508
QPDFObjectHandle::QPDFArrayItems::iterator
2509
QPDFObjectHandle::QPDFArrayItems::begin()
2510
0
{
2511
0
    return {oh, true};
2512
0
}
2513
2514
QPDFObjectHandle::QPDFArrayItems::iterator
2515
QPDFObjectHandle::QPDFArrayItems::end()
2516
0
{
2517
0
    return {oh, false};
2518
0
}
2519
2520
QPDFObjGen
2521
QPDFObjectHandle::getObjGen() const
2522
5.88M
{
2523
5.88M
    return obj ? obj->getObjGen() : QPDFObjGen();
2524
5.88M
}
2525
2526
int
2527
QPDFObjectHandle::getObjectID() const
2528
2.52M
{
2529
2.52M
    return getObjGen().getObj();
2530
2.52M
}
2531
2532
int
2533
QPDFObjectHandle::getGeneration() const
2534
0
{
2535
0
    return getObjGen().getGen();
2536
0
}
2537
2538
bool
2539
QPDFObjectHandle::isIndirect() const
2540
41.3k
{
2541
41.3k
    return getObjectID() != 0;
2542
41.3k
}
2543
2544
// Indirect object accessors
2545
QPDF*
2546
QPDFObjectHandle::getOwningQPDF() const
2547
147k
{
2548
147k
    return obj ? obj->getQPDF() : nullptr;
2549
147k
}
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
2
{
2563
2
    if (obj) {
2564
2
        obj->setParsedOffset(offset);
2565
2
    }
2566
2
}
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
}