Coverage Report

Created: 2026-06-15 06:18

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
127k
{
35
127k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
127k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
17.1k
    supports_retry(supports_retry)
47
17.1k
{
48
17.1k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
17.1k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
17.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
61.5k
{
253
61.5k
    switch (resolved_type_code()) {
254
0
    case ::ot_uninitialized:
255
0
        throw std::logic_error("QPDFObjectHandle: attempting to copy an uninitialized object");
256
0
        return {}; // does not return
257
0
    case ::ot_reserved:
258
0
        return QPDFObject::create<QPDF_Reserved>();
259
193
    case ::ot_null:
260
193
        return QPDFObject::create<QPDF_Null>();
261
35
    case ::ot_boolean:
262
35
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
1.11k
    case ::ot_integer:
264
1.11k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
395
    case ::ot_real:
266
395
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
1.02k
    case ::ot_string:
268
1.02k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
58.0k
    case ::ot_name:
270
58.0k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
231
    case ::ot_array:
272
231
        {
273
231
            auto const& a = std::get<QPDF_Array>(obj->value);
274
231
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
231
            } else {
277
231
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
231
                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
231
                } else {
287
231
                    std::vector<QPDFObjectHandle> result;
288
231
                    result.reserve(a.elements.size());
289
60.3k
                    for (auto const& element: a.elements) {
290
60.3k
                        result.emplace_back(
291
60.3k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
60.3k
                    }
293
231
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
231
                }
295
231
            }
296
231
        }
297
517
    case ::ot_dictionary:
298
517
        {
299
517
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
517
            if (shallow) {
301
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
517
            } else {
303
517
                std::map<std::string, QPDFObjectHandle> new_items;
304
2.38k
                for (auto const& [key, val]: d.items) {
305
2.38k
                    new_items[key] = val.indirect() ? val : val.copy();
306
2.38k
                }
307
517
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
517
            }
309
517
        }
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
61.5k
    }
327
0
    return {}; // unreachable
328
61.5k
}
329
330
// This method determines structural equivalence up to a given depth.
331
// The default depth is 10.
332
//
333
// Nomenclature note: ISO 32000-2 Annex J uses the term "equal" for this
334
// strict recursive comparison (J.4.1). We use "equivalent_to" here to
335
// implement Annex J's "equality", distinguishing it from C++ shallow
336
// pointer equality.
337
//
338
// Implementation notes:
339
//
340
// (1) We deviate from Annex J by comparing raw streams only, without
341
// decoding.
342
//
343
// (2) Loop detection is expensive and is avoided. If either object has
344
// a cycle in its forward orbit, this implementation will return false.
345
346
bool
347
BaseHandle::equivalent_to(BaseHandle const& other, int depth) const
348
0
{
349
    // A. Identity, size & limit checks
350
0
    if (obj == other.obj) {
351
0
        return true;
352
0
    }
353
0
    if (depth < 0) {
354
0
        return false;
355
0
    }
356
0
    size_t size1 = size();
357
0
    size_t size2 = other.size();
358
0
    if (size1 != size2) {
359
0
        return false;
360
0
    }
361
    // B. Structural comparison
362
0
    qpdf_object_type_e t1 = resolved_type_code();
363
0
    qpdf_object_type_e t2 = other.resolved_type_code();
364
0
    if (t1 == ::ot_reference) {
365
0
        return referenced_object().equivalent_to(other, depth - 1);
366
0
    }
367
0
    if (t2 == ::ot_reference) {
368
0
        return equivalent_to(other.referenced_object(), depth - 1);
369
0
    }
370
0
    if (t1 != t2) {
371
0
        if ((t1 == ::ot_integer || t1 == ::ot_real) && (t2 == ::ot_integer || t2 == ::ot_real)) {
372
            // Numeric equivalence per Annex J
373
0
            return oh().getNumericValue() == other.oh().getNumericValue();
374
0
        }
375
        // normalize uninitialized and null
376
0
        return (t1 == ::ot_uninitialized && t2 == ::ot_null) ||
377
0
            (t2 == ::ot_uninitialized && t1 == ::ot_null);
378
0
    }
379
0
    switch (t1) {
380
0
    case ::ot_uninitialized:
381
0
    case ::ot_null:
382
0
        return true;
383
0
    case ::ot_boolean:
384
0
        return std::get<QPDF_Bool>(obj->value).val == std::get<QPDF_Bool>(other.obj->value).val;
385
0
    case ::ot_string:
386
0
        return std::get<QPDF_String>(obj->value).val == std::get<QPDF_String>(other.obj->value).val;
387
0
    case ::ot_name:
388
0
        return std::get<QPDF_Name>(obj->value).name == std::get<QPDF_Name>(other.obj->value).name;
389
0
    case ::ot_array:
390
0
        {
391
0
            auto const& a1 = std::get<QPDF_Array>(obj->value);
392
0
            auto const& a2 = std::get<QPDF_Array>(other.obj->value);
393
            // sizes size1, size2 were calculated above and checked to be equal
394
0
            if (!a1.sp && !a2.sp) {
395
0
                for (size_t i = 0; i < size1; ++i) {
396
0
                    if (!a1.elements[i].equivalent_to(a2.elements[i], depth - 1)) {
397
0
                        return false;
398
0
                    }
399
0
                }
400
0
                return true;
401
0
            }
402
            // at least one array is sparse
403
0
            auto get_item = [](QPDF_Array const& arr, size_t idx) -> BaseHandle const& {
404
0
                if (arr.sp) {
405
0
                    auto it = arr.sp->elements.find(idx);
406
0
                    if (it == arr.sp->elements.end()) {
407
0
                        static QPDFObjectHandle null_oh = Null();
408
0
                        return null_oh;
409
0
                    }
410
0
                    return it->second;
411
0
                }
412
0
                return arr.elements[idx];
413
0
            };
414
0
            for (size_t i = 0; i < size1; ++i) {
415
0
                if (!get_item(a1, i).equivalent_to(get_item(a2, i), depth - 1)) {
416
0
                    return false;
417
0
                }
418
0
            }
419
0
            return true;
420
0
        }
421
0
    case ::ot_dictionary:
422
0
        {
423
0
            auto const& map1 = std::get<QPDF_Dictionary>(obj->value).items;
424
0
            auto const& map2 = std::get<QPDF_Dictionary>(other.obj->value).items;
425
0
            auto it2 = map2.begin();
426
0
            auto end2 = map2.end();
427
0
            for (auto const& [key1, value1]: map1) {
428
0
                if (value1.null()) {
429
0
                    continue;
430
0
                }
431
0
                while (it2 != end2 && it2->second.null()) {
432
0
                    ++it2;
433
0
                }
434
0
                if (it2 == end2 || key1 != it2->first ||
435
0
                    !value1.equivalent_to(it2->second, depth - 1)) {
436
0
                    return false;
437
0
                }
438
0
                ++it2;
439
0
            }
440
0
            while (it2 != end2 && it2->second.null()) {
441
0
                ++it2;
442
0
            }
443
0
            return it2 == end2;
444
0
        }
445
0
    case ::ot_stream:
446
0
        {
447
0
            auto const& s1 = std::get<QPDF_Stream>(obj->value);
448
0
            auto const& s2 = std::get<QPDF_Stream>(other.obj->value);
449
0
            if (!s1.m->stream_dict.equivalent_to(s2.m->stream_dict, depth - 1)) {
450
0
                return false;
451
0
            }
452
0
            return s1.m->stream_data->view() == s2.m->stream_data->view();
453
0
        }
454
0
    case ::ot_operator:
455
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_operator");
456
0
    case ::ot_inlineimage:
457
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_inlineimage");
458
0
    case ::ot_integer:
459
0
        return std::get<QPDF_Integer>(obj->value).val ==
460
0
            std::get<QPDF_Integer>(other.obj->value).val;
461
0
    case ::ot_real:
462
0
        return oh().getNumericValue() == other.oh().getNumericValue();
463
0
    case ::ot_unresolved: // cannot determine equivalence so return false
464
0
    case ::ot_reference:  // handled above
465
0
    case ::ot_destroyed:  // should not happen
466
0
    case ::ot_reserved:   // should not happen
467
0
        return false;
468
0
    }
469
0
    return false; // unreachable
470
0
}
471
472
std::string
473
BaseHandle::unparse() const
474
0
{
475
0
    switch (resolved_type_code()) {
476
0
    case ::ot_uninitialized:
477
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object");
478
0
        return ""; // does not return
479
0
    case ::ot_reserved:
480
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
481
0
        return ""; // does not return
482
0
    case ::ot_null:
483
0
        return "null";
484
0
    case ::ot_boolean:
485
0
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
0
    case ::ot_integer:
487
0
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
0
    case ::ot_real:
489
0
        return std::get<QPDF_Real>(obj->value).val;
490
0
    case ::ot_string:
491
0
        return std::get<QPDF_String>(obj->value).unparse(false);
492
0
    case ::ot_name:
493
0
        return Name::normalize(std::get<QPDF_Name>(obj->value).name);
494
0
    case ::ot_array:
495
0
        {
496
0
            auto const& a = std::get<QPDF_Array>(obj->value);
497
0
            std::string result = "[ ";
498
0
            if (a.sp) {
499
0
                size_t next = 0;
500
0
                for (auto& [key, value]: a.sp->elements) {
501
0
                    for (size_t j = next; j < key; ++j) {
502
0
                        result += "null ";
503
0
                    }
504
0
                    result += value.unparse() + " ";
505
0
                    next = key + 1;
506
0
                }
507
0
                for (size_t j = next; j < a.sp->size; ++j) {
508
0
                    result += "null ";
509
0
                }
510
0
            } else {
511
0
                for (auto const& item: a.elements) {
512
0
                    result += item.unparse() + " ";
513
0
                }
514
0
            }
515
0
            result += "]";
516
0
            return result;
517
0
        }
518
0
    case ::ot_dictionary:
519
0
        {
520
0
            auto const& items = std::get<QPDF_Dictionary>(obj->value).items;
521
0
            std::string result = "<< ";
522
0
            for (auto& iter: items) {
523
0
                if (!iter.second.null()) {
524
0
                    result += Name::normalize(iter.first) + " " + iter.second.unparse() + " ";
525
0
                }
526
0
            }
527
0
            result += ">>";
528
0
            return result;
529
0
        }
530
0
    case ::ot_stream:
531
0
        return obj->og.unparse(' ') + " R";
532
0
    case ::ot_operator:
533
0
        return std::get<QPDF_Operator>(obj->value).val;
534
0
    case ::ot_inlineimage:
535
0
        return std::get<QPDF_InlineImage>(obj->value).val;
536
0
    case ::ot_unresolved:
537
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object");
538
0
        return ""; // does not return
539
0
    case ::ot_destroyed:
540
0
        throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF");
541
0
        return ""; // does not return
542
0
    case ::ot_reference:
543
0
        return obj->og.unparse(' ') + " R";
544
0
    }
545
0
    return {}; // unreachable
546
0
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
0
{
551
0
    switch (resolved_type_code()) {
552
0
    case ::ot_uninitialized:
553
0
        throw std::logic_error(
554
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
555
0
        break; // unreachable
556
0
    case ::ot_null:
557
0
    case ::ot_operator:
558
0
    case ::ot_inlineimage:
559
0
        p << "null";
560
0
        break;
561
0
    case ::ot_boolean:
562
0
        p << std::get<QPDF_Bool>(obj->value).val;
563
0
        break;
564
0
    case ::ot_integer:
565
0
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
0
        break;
567
0
    case ::ot_real:
568
0
        {
569
0
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
0
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
0
            } else if (val.at(0) == '.') {
574
0
                p << "0" << val;
575
0
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
0
                p << "-0." << val.substr(2);
577
0
            } else {
578
0
                p << val;
579
0
            }
580
0
            if (val.back() == '.') {
581
0
                p << "0";
582
0
            }
583
0
        }
584
0
        break;
585
0
    case ::ot_string:
586
0
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
587
0
        break;
588
0
    case ::ot_name:
589
0
        {
590
0
            auto const& n = std::get<QPDF_Name>(obj->value);
591
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
592
            // updating this method make sure QPDF_Dictionary is also update.
593
0
            if (json_version == 1) {
594
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
595
0
            } else {
596
0
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
597
0
                    if (res.second) {
598
0
                        p << "\"" << n.name << "\"";
599
0
                    } else {
600
0
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
601
0
                    }
602
0
                } else {
603
0
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
0
                }
605
0
            }
606
0
        }
607
0
        break;
608
0
    case ::ot_array:
609
0
        {
610
0
            auto const& a = std::get<QPDF_Array>(obj->value);
611
0
            p.writeStart('[');
612
0
            if (a.sp) {
613
0
                size_t next = 0;
614
0
                for (auto& [key, value]: a.sp->elements) {
615
0
                    for (size_t j = next; j < key; ++j) {
616
0
                        p.writeNext() << "null";
617
0
                    }
618
0
                    p.writeNext();
619
0
                    auto item_og = value.id_gen();
620
0
                    if (item_og.isIndirect()) {
621
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
622
0
                    } else {
623
0
                        value.write_json(json_version, p);
624
0
                    }
625
0
                    next = key + 1;
626
0
                }
627
0
                for (size_t j = next; j < a.sp->size; ++j) {
628
0
                    p.writeNext() << "null";
629
0
                }
630
0
            } else {
631
0
                for (auto const& item: a.elements) {
632
0
                    p.writeNext();
633
0
                    auto item_og = item.id_gen();
634
0
                    if (item_og.isIndirect()) {
635
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
636
0
                    } else {
637
0
                        item.write_json(json_version, p);
638
0
                    }
639
0
                }
640
0
            }
641
0
            p.writeEnd(']');
642
0
        }
643
0
        break;
644
0
    case ::ot_dictionary:
645
0
        {
646
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
647
0
            p.writeStart('{');
648
0
            for (auto& iter: d.items) {
649
0
                if (!iter.second.null()) {
650
0
                    p.writeNext();
651
0
                    if (json_version == 1) {
652
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
653
0
                          << "\": ";
654
0
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
655
0
                        if (res.second) {
656
0
                            p << "\"" << iter.first << "\": ";
657
0
                        } else {
658
0
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
659
0
                        }
660
0
                    } else {
661
0
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
0
                          << "\": ";
663
0
                    }
664
0
                    iter.second.writeJSON(json_version, p);
665
0
                }
666
0
            }
667
0
            p.writeEnd('}');
668
0
        }
669
0
        break;
670
0
    case ::ot_stream:
671
0
        std::get<QPDF_Stream>(obj->value).m->stream_dict.writeJSON(json_version, p);
672
0
        break;
673
0
    case ::ot_reference:
674
0
        p << "\"" << obj->og.unparse(' ') << " R\"";
675
0
        break;
676
0
    default:
677
0
        throw std::logic_error("attempted to write an unsuitable object as JSON");
678
0
    }
679
0
}
680
681
void
682
BaseHandle::disconnect(bool only_direct)
683
4.03M
{
684
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
685
4.03M
    if (only_direct && indirect()) {
686
72.8k
        return;
687
72.8k
    }
688
689
3.96M
    switch (raw_type_code()) {
690
34.8k
    case ::ot_array:
691
34.8k
        {
692
34.8k
            auto& a = std::get<QPDF_Array>(obj->value);
693
34.8k
            if (a.sp) {
694
2.02M
                for (auto& item: a.sp->elements) {
695
2.02M
                    item.second.disconnect();
696
2.02M
                }
697
34.6k
            } else {
698
1.52M
                for (auto& oh: a.elements) {
699
1.52M
                    oh.disconnect();
700
1.52M
                }
701
34.6k
            }
702
34.8k
        }
703
34.8k
        break;
704
71.7k
    case ::ot_dictionary:
705
244k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
706
244k
            iter.second.disconnect();
707
244k
        }
708
71.7k
        break;
709
13.7k
    case ::ot_stream:
710
13.7k
        {
711
13.7k
            auto& s = std::get<QPDF_Stream>(obj->value);
712
13.7k
            s.m->stream_provider = nullptr;
713
13.7k
            s.m->stream_dict.disconnect();
714
13.7k
        }
715
13.7k
        break;
716
0
    case ::ot_uninitialized:
717
0
        return;
718
3.84M
    default:
719
3.84M
        break;
720
3.96M
    }
721
3.96M
    obj->qpdf = nullptr;
722
3.96M
    obj->og = QPDFObjGen();
723
3.96M
}
724
725
bool
726
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
727
159
{
728
159
    return obj == rhs.obj;
729
159
}
730
731
qpdf_object_type_e
732
QPDFObjectHandle::getTypeCode() const
733
0
{
734
0
    return type_code();
735
0
}
736
737
char const*
738
BaseHandle::type_name() const
739
4.56k
{
740
4.56k
    static constexpr std::array<char const*, 16> tn{
741
4.56k
        "uninitialized",
742
4.56k
        "reserved",
743
4.56k
        "null",
744
4.56k
        "boolean",
745
4.56k
        "integer",
746
4.56k
        "real",
747
4.56k
        "string",
748
4.56k
        "name",
749
4.56k
        "array",
750
4.56k
        "dictionary",
751
4.56k
        "stream",
752
4.56k
        "operator",
753
4.56k
        "inline-image",
754
4.56k
        "unresolved",
755
4.56k
        "destroyed",
756
4.56k
        "reference"};
757
4.56k
    return tn[type_code()];
758
4.56k
}
759
760
char const*
761
QPDFObjectHandle::getTypeName() const
762
0
{
763
0
    return type_name();
764
0
}
765
766
bool
767
QPDFObjectHandle::isDestroyed() const
768
0
{
769
0
    return type_code() == ::ot_destroyed;
770
0
}
771
772
bool
773
QPDFObjectHandle::isBool() const
774
3.88k
{
775
3.88k
    return type_code() == ::ot_boolean;
776
3.88k
}
777
778
bool
779
QPDFObjectHandle::isDirectNull() const
780
0
{
781
    // Don't call dereference() -- this is a const method, and we know
782
    // objid == 0, so there's nothing to resolve.
783
0
    return !indirect() && raw_type_code() == ::ot_null;
784
0
}
785
786
bool
787
QPDFObjectHandle::isNull() const
788
1.96k
{
789
1.96k
    return type_code() == ::ot_null;
790
1.96k
}
791
792
bool
793
QPDFObjectHandle::isInteger() const
794
103k
{
795
103k
    return type_code() == ::ot_integer;
796
103k
}
797
798
bool
799
QPDFObjectHandle::isReal() const
800
14.1k
{
801
14.1k
    return type_code() == ::ot_real;
802
14.1k
}
803
804
bool
805
QPDFObjectHandle::isNumber() const
806
18.7k
{
807
18.7k
    return (isInteger() || isReal());
808
18.7k
}
809
810
double
811
QPDFObjectHandle::getNumericValue() const
812
0
{
813
0
    if (isInteger()) {
814
0
        return static_cast<double>(getIntValue());
815
0
    } else if (isReal()) {
816
0
        return atof(getRealValue().c_str());
817
0
    } else {
818
0
        typeWarning("number", "returning 0");
819
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
820
0
        return 0;
821
0
    }
822
0
}
823
824
bool
825
QPDFObjectHandle::getValueAsNumber(double& value) const
826
0
{
827
0
    if (!isNumber()) {
828
0
        return false;
829
0
    }
830
0
    value = getNumericValue();
831
0
    return true;
832
0
}
833
834
bool
835
QPDFObjectHandle::isName() const
836
15.0k
{
837
15.0k
    return type_code() == ::ot_name;
838
15.0k
}
839
840
bool
841
QPDFObjectHandle::isString() const
842
9.70k
{
843
9.70k
    return type_code() == ::ot_string;
844
9.70k
}
845
846
bool
847
QPDFObjectHandle::isOperator() const
848
0
{
849
0
    return type_code() == ::ot_operator;
850
0
}
851
852
bool
853
QPDFObjectHandle::isInlineImage() const
854
0
{
855
0
    return type_code() == ::ot_inlineimage;
856
0
}
857
858
bool
859
QPDFObjectHandle::isArray() const
860
120k
{
861
120k
    return type_code() == ::ot_array;
862
120k
}
863
864
bool
865
QPDFObjectHandle::isDictionary() const
866
380k
{
867
380k
    return type_code() == ::ot_dictionary;
868
380k
}
869
870
bool
871
QPDFObjectHandle::isStream() const
872
0
{
873
0
    return type_code() == ::ot_stream;
874
0
}
875
876
bool
877
QPDFObjectHandle::isReserved() const
878
0
{
879
0
    return type_code() == ::ot_reserved;
880
0
}
881
882
bool
883
QPDFObjectHandle::isScalar() const
884
3.05k
{
885
3.05k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
886
3.05k
}
887
888
bool
889
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
890
543
{
891
543
    return Name(*this) == name;
892
543
}
893
894
bool
895
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
896
129k
{
897
129k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
898
9.09k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
899
129k
}
900
901
bool
902
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
903
46.7k
{
904
46.7k
    if (auto stream = as_stream()) {
905
18.6k
        return stream && (type.empty() || stream.Type() == type) &&
906
13.8k
            (subtype.empty() || stream.Subtype() == subtype);
907
18.6k
    }
908
28.1k
    return false;
909
46.7k
}
910
911
// Bool accessors
912
913
bool
914
QPDFObjectHandle::getBoolValue() const
915
1
{
916
1
    if (auto boolean = as<QPDF_Bool>()) {
917
1
        return boolean->val;
918
1
    } else {
919
0
        typeWarning("boolean", "returning false");
920
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
921
0
        return false;
922
0
    }
923
1
}
924
925
bool
926
QPDFObjectHandle::getValueAsBool(bool& value) const
927
0
{
928
0
    if (auto boolean = as<QPDF_Bool>()) {
929
0
        value = boolean->val;
930
0
        return true;
931
0
    }
932
0
    return false;
933
0
}
934
935
// Integer methods
936
937
Integer::Integer(long long value) :
938
1.51k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
939
1.51k
{
940
1.51k
}
941
942
QPDFObjectHandle
943
QPDFObjectHandle::newInteger(long long value)
944
0
{
945
0
    return {QPDFObject::create<QPDF_Integer>(value)};
946
0
}
947
948
int64_t
949
Integer::value() const
950
87.9k
{
951
87.9k
    auto* i = as<QPDF_Integer>();
952
87.9k
    if (!i) {
953
1.28k
        throw invalid_error("Integer");
954
1.28k
    }
955
86.6k
    return i->val;
956
87.9k
}
957
958
long long
959
QPDFObjectHandle::getIntValue() const
960
18.9k
{
961
18.9k
    if (auto const integer = Integer(*this)) {
962
18.9k
        return integer;
963
18.9k
    } else {
964
0
        typeWarning("integer", "returning 0");
965
0
        return 0;
966
0
    }
967
18.9k
}
968
969
bool
970
QPDFObjectHandle::getValueAsInt(long long& value) const
971
7.38k
{
972
7.38k
    if (auto const integer = Integer(*this)) {
973
7.34k
        value = integer;
974
7.34k
        return true;
975
7.34k
    }
976
42
    return false;
977
7.38k
}
978
979
int
980
QPDFObjectHandle::getIntValueAsInt() const
981
42.5k
{
982
42.5k
    try {
983
42.5k
        return Integer(*this).value<int>();
984
42.5k
    } catch (std::invalid_argument&) {
985
17
        typeWarning("integer", "returning 0");
986
17
        return 0;
987
17
    }
988
42.5k
}
989
990
bool
991
QPDFObjectHandle::getValueAsInt(int& value) const
992
2.08k
{
993
2.08k
    if (!isInteger()) {
994
36
        return false;
995
36
    }
996
2.04k
    value = getIntValueAsInt();
997
2.04k
    return true;
998
2.08k
}
999
1000
unsigned long long
1001
QPDFObjectHandle::getUIntValue() const
1002
11.0k
{
1003
11.0k
    try {
1004
11.0k
        return Integer(*this).value<unsigned long long>();
1005
11.0k
    } catch (std::invalid_argument&) {
1006
931
        typeWarning("integer", "returning 0");
1007
931
        return 0;
1008
931
    }
1009
11.0k
}
1010
1011
bool
1012
QPDFObjectHandle::getValueAsUInt(unsigned long long& value) const
1013
0
{
1014
0
    if (!isInteger()) {
1015
0
        return false;
1016
0
    }
1017
0
    value = getUIntValue();
1018
0
    return true;
1019
0
}
1020
1021
unsigned int
1022
QPDFObjectHandle::getUIntValueAsUInt() const
1023
8.18k
{
1024
8.18k
    try {
1025
8.18k
        return Integer(*this).value<unsigned int>();
1026
8.18k
    } catch (std::invalid_argument&) {
1027
341
        typeWarning("integer", "returning 0");
1028
341
        return 0;
1029
341
    }
1030
8.18k
}
1031
1032
bool
1033
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1034
2.29k
{
1035
2.29k
    if (!isInteger()) {
1036
208
        return false;
1037
208
    }
1038
2.08k
    value = getUIntValueAsUInt();
1039
2.08k
    return true;
1040
2.29k
}
1041
1042
// Real accessors
1043
1044
std::string
1045
QPDFObjectHandle::getRealValue() const
1046
0
{
1047
0
    if (auto* real = as<QPDF_Real>()) {
1048
0
        return real->val;
1049
0
    }
1050
0
    typeWarning("real", "returning 0.0");
1051
0
    return "0.0";
1052
0
}
1053
1054
bool
1055
QPDFObjectHandle::getValueAsReal(std::string& value) const
1056
0
{
1057
0
    if (auto* real = as<QPDF_Real>()) {
1058
0
        value = real->val;
1059
0
        return true;
1060
0
    }
1061
0
    return false;
1062
0
}
1063
1064
// Name methods
1065
1066
QPDFObjectHandle
1067
QPDFObjectHandle::newName(std::string const& name)
1068
0
{
1069
0
    return {QPDFObject::create<QPDF_Name>(name)};
1070
0
}
1071
1072
Name::Name(std::string const& name) :
1073
0
    BaseHandle(QPDFObject::create<QPDF_Name>(name))
1074
0
{
1075
0
}
1076
1077
Name::Name(std::string&& name) :
1078
9.90k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1079
9.90k
{
1080
9.90k
}
1081
1082
std::string const&
1083
Name::value() const
1084
58.1k
{
1085
58.1k
    auto* n = as<QPDF_Name>();
1086
58.1k
    if (!n) {
1087
0
        throw invalid_error("Name");
1088
0
    }
1089
58.1k
    return n->name;
1090
58.1k
}
1091
1092
std::string
1093
QPDFObjectHandle::getName() const
1094
5.02k
{
1095
5.02k
    if (auto* name = as<QPDF_Name>()) {
1096
5.02k
        return name->name;
1097
5.02k
    }
1098
0
    typeWarning("name", "returning dummy name");
1099
0
    return "/QPDFFakeName";
1100
5.02k
}
1101
1102
bool
1103
QPDFObjectHandle::getValueAsName(std::string& value) const
1104
0
{
1105
0
    if (auto* name = as<QPDF_Name>()) {
1106
0
        value = name->name;
1107
0
        return true;
1108
0
    }
1109
0
    return false;
1110
0
}
1111
1112
// String methods
1113
1114
QPDFObjectHandle
1115
QPDFObjectHandle::newString(std::string const& str)
1116
21
{
1117
21
    return {QPDFObject::create<QPDF_String>(str)};
1118
21
}
1119
1120
QPDFObjectHandle
1121
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1122
2.34k
{
1123
2.34k
    return {String::utf16(utf8_str).obj_sp()};
1124
2.34k
}
1125
1126
String::String(std::string const& str) :
1127
1.82k
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1128
1.82k
{
1129
1.82k
}
1130
1131
String::String(std::string&& str) :
1132
518
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1133
518
{
1134
518
}
1135
1136
String
1137
String::utf16(std::string const& utf8_str)
1138
2.34k
{
1139
2.34k
    std::string result;
1140
2.34k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1141
1.82k
        return String(result);
1142
1.82k
    }
1143
518
    return String(QUtil::utf8_to_utf16(utf8_str));
1144
2.34k
}
1145
1146
std::string const&
1147
String::value() const
1148
3.19k
{
1149
3.19k
    auto* s = as<QPDF_String>();
1150
3.19k
    if (!s) {
1151
0
        throw invalid_error("String");
1152
0
    }
1153
3.19k
    return s->val;
1154
3.19k
}
1155
1156
std::string
1157
String::utf8_value() const
1158
591k
{
1159
591k
    auto* s = as<QPDF_String>();
1160
591k
    if (!s) {
1161
0
        throw invalid_error("String");
1162
0
    }
1163
591k
    if (util::is_utf16(s->val)) {
1164
34.3k
        return QUtil::utf16_to_utf8(s->val);
1165
34.3k
    }
1166
556k
    if (util::is_explicit_utf8(s->val)) {
1167
        // PDF 2.0 allows UTF-8 strings when explicitly prefixed with the three-byte representation
1168
        // of U+FEFF.
1169
2.78k
        return s->val.substr(3);
1170
2.78k
    }
1171
553k
    return QUtil::pdf_doc_to_utf8(s->val);
1172
556k
}
1173
1174
std::string
1175
QPDFObjectHandle::getStringValue() const
1176
3.19k
{
1177
3.19k
    try {
1178
3.19k
        return String(obj).value();
1179
3.19k
    } catch (std::invalid_argument&) {
1180
0
        typeWarning("string", "returning empty string");
1181
0
        return {};
1182
0
    }
1183
3.19k
}
1184
1185
bool
1186
QPDFObjectHandle::getValueAsString(std::string& value) const
1187
0
{
1188
0
    try {
1189
0
        value = String(obj).value();
1190
0
        return true;
1191
0
    } catch (std::invalid_argument&) {
1192
0
        return false;
1193
0
    }
1194
0
}
1195
1196
std::string
1197
QPDFObjectHandle::getUTF8Value() const
1198
591k
{
1199
591k
    try {
1200
591k
        return String(obj).utf8_value();
1201
591k
    } catch (std::invalid_argument&) {
1202
0
        typeWarning("string", "returning empty string");
1203
0
        return {};
1204
0
    }
1205
591k
}
1206
1207
bool
1208
QPDFObjectHandle::getValueAsUTF8(std::string& value) const
1209
0
{
1210
0
    try {
1211
0
        value = String(obj).utf8_value();
1212
0
        return true;
1213
0
    } catch (std::invalid_argument&) {
1214
0
        return false;
1215
0
    }
1216
0
}
1217
1218
// Operator and Inline Image accessors
1219
1220
std::string
1221
QPDFObjectHandle::getOperatorValue() const
1222
0
{
1223
0
    if (auto* op = as<QPDF_Operator>()) {
1224
0
        return op->val;
1225
0
    }
1226
0
    typeWarning("operator", "returning fake value");
1227
0
    return "QPDFFAKE";
1228
0
}
1229
1230
bool
1231
QPDFObjectHandle::getValueAsOperator(std::string& value) const
1232
0
{
1233
0
    if (auto* op = as<QPDF_Operator>()) {
1234
0
        value = op->val;
1235
0
        return true;
1236
0
    }
1237
0
    return false;
1238
0
}
1239
1240
std::string
1241
QPDFObjectHandle::getInlineImageValue() const
1242
0
{
1243
0
    if (auto* ii = as<QPDF_InlineImage>()) {
1244
0
        return ii->val;
1245
0
    }
1246
0
    typeWarning("inlineimage", "returning empty data");
1247
0
    return {};
1248
0
}
1249
1250
bool
1251
QPDFObjectHandle::getValueAsInlineImage(std::string& value) const
1252
0
{
1253
0
    if (auto* ii = as<QPDF_InlineImage>()) {
1254
0
        value = ii->val;
1255
0
        return true;
1256
0
    }
1257
0
    return false;
1258
0
}
1259
1260
// Array accessors and mutators are in QPDF_Array.cc
1261
1262
QPDFObjectHandle::QPDFArrayItems
1263
QPDFObjectHandle::aitems()
1264
0
{
1265
0
    return *this;
1266
0
}
1267
1268
// Dictionary accessors are in QPDF_Dictionary.cc
1269
1270
QPDFObjectHandle::QPDFDictItems
1271
QPDFObjectHandle::ditems()
1272
0
{
1273
0
    return {*this};
1274
0
}
1275
1276
// Array and Name accessors
1277
1278
bool
1279
QPDFObjectHandle::isOrHasName(std::string const& value) const
1280
359
{
1281
359
    if (isNameAndEquals(value)) {
1282
0
        return true;
1283
359
    } else if (isArray()) {
1284
74
        for (auto& item: getArrayAsVector()) {
1285
74
            if (item.isNameAndEquals(value)) {
1286
29
                return true;
1287
29
            }
1288
74
        }
1289
45
    }
1290
330
    return false;
1291
359
}
1292
1293
void
1294
QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf)
1295
0
{
1296
0
    for (auto const& i1: as_dictionary()) {
1297
0
        for (auto& i2: i1.second.as_dictionary()) {
1298
0
            if (!i2.second.null() && !i2.second.isIndirect()) {
1299
0
                i2.second = owning_qpdf.makeIndirectObject(i2.second);
1300
0
            }
1301
0
        }
1302
0
    }
1303
0
}
1304
1305
void
1306
QPDFObjectHandle::mergeResources(
1307
    QPDFObjectHandle other, std::map<std::string, std::map<std::string, std::string>>* conflicts)
1308
0
{
1309
0
    if (!(isDictionary() && other.isDictionary())) {
1310
0
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1311
0
        return;
1312
0
    }
1313
1314
0
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1315
0
                              std::map<QPDFObjGen, std::string>& og_to_name) {
1316
0
        for (auto const& [key, value]: dict.as_dictionary()) {
1317
0
            if (!value.null() && value.isIndirect()) {
1318
0
                og_to_name.insert_or_assign(value.getObjGen(), key);
1319
0
            }
1320
0
        }
1321
0
    };
1322
1323
    // This algorithm is described in comments in QPDFObjectHandle.hh
1324
    // above the declaration of mergeResources.
1325
0
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1326
0
        auto other_val = value1;
1327
0
        if (hasKey(rtype)) {
1328
0
            QPDFObjectHandle this_val = getKey(rtype);
1329
0
            if (this_val.isDictionary() && other_val.isDictionary()) {
1330
0
                if (this_val.isIndirect()) {
1331
                    // Do this even if there are no keys. Various places in the code call
1332
                    // mergeResources with resource dictionaries that contain empty subdictionaries
1333
                    // just to get this shallow copy functionality.
1334
0
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1335
0
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1336
0
                }
1337
0
                std::map<QPDFObjGen, std::string> og_to_name;
1338
0
                std::set<std::string> rnames;
1339
0
                int min_suffix = 1;
1340
0
                bool initialized_maps = false;
1341
0
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1342
0
                    QPDFObjectHandle rval = value2;
1343
0
                    if (!this_val.hasKey(key)) {
1344
0
                        if (!rval.isIndirect()) {
1345
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1346
0
                            rval = rval.shallowCopy();
1347
0
                        }
1348
0
                        this_val.replaceKey(key, rval);
1349
0
                    } else if (conflicts) {
1350
0
                        if (!initialized_maps) {
1351
0
                            make_og_to_name(this_val, og_to_name);
1352
0
                            rnames = this_val.getResourceNames();
1353
0
                            initialized_maps = true;
1354
0
                        }
1355
0
                        auto rval_og = rval.getObjGen();
1356
0
                        if (rval.isIndirect() && og_to_name.contains(rval_og)) {
1357
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge reuse");
1358
0
                            auto new_key = og_to_name[rval_og];
1359
0
                            if (new_key != key) {
1360
0
                                (*conflicts)[rtype][key] = new_key;
1361
0
                            }
1362
0
                        } else {
1363
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge generate");
1364
0
                            std::string new_key =
1365
0
                                getUniqueResourceName(key + "_", min_suffix, &rnames);
1366
0
                            (*conflicts)[rtype][key] = new_key;
1367
0
                            this_val.replaceKey(new_key, rval);
1368
0
                        }
1369
0
                    }
1370
0
                }
1371
0
            } else if (this_val.isArray() && other_val.isArray()) {
1372
0
                std::set<std::string> scalars;
1373
0
                for (auto this_item: this_val.aitems()) {
1374
0
                    if (this_item.isScalar()) {
1375
0
                        scalars.insert(this_item.unparse());
1376
0
                    }
1377
0
                }
1378
0
                for (auto other_item: other_val.aitems()) {
1379
0
                    if (other_item.isScalar()) {
1380
0
                        if (!scalars.contains(other_item.unparse())) {
1381
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1382
0
                            this_val.appendItem(other_item);
1383
0
                        } else {
1384
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1385
0
                        }
1386
0
                    }
1387
0
                }
1388
0
            }
1389
0
        } else {
1390
0
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1391
0
            replaceKey(rtype, other_val.shallowCopy());
1392
0
        }
1393
0
    }
1394
0
}
1395
1396
std::set<std::string>
1397
QPDFObjectHandle::getResourceNames() const
1398
0
{
1399
    // Return second-level dictionary keys
1400
0
    std::set<std::string> result;
1401
0
    for (auto const& item: as_dictionary(strict)) {
1402
0
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1403
0
            if (!val2.null()) {
1404
0
                result.insert(key2);
1405
0
            }
1406
0
        }
1407
0
    }
1408
0
    return result;
1409
0
}
1410
1411
std::string
1412
QPDFObjectHandle::getUniqueResourceName(
1413
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1414
0
{
1415
0
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1416
0
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1417
0
    while (min_suffix <= max_suffix) {
1418
0
        std::string candidate = prefix + std::to_string(min_suffix);
1419
0
        if (!names.contains(candidate)) {
1420
0
            return candidate;
1421
0
        }
1422
        // Increment after return; min_suffix should be the value
1423
        // used, not the next value.
1424
0
        ++min_suffix;
1425
0
    }
1426
    // This could only happen if there is a coding error.
1427
    // The number of candidates we test is more than the
1428
    // number of keys we're checking against.
1429
0
    throw std::logic_error(
1430
0
        "unable to find unconflicting name in QPDFObjectHandle::getUniqueResourceName");
1431
0
}
1432
1433
// Dictionary mutators are in QPDF_Dictionary.cc
1434
1435
// Stream accessors are in QPDF_Stream.cc
1436
1437
std::map<std::string, QPDFObjectHandle>
1438
QPDFObjectHandle::getPageImages()
1439
0
{
1440
0
    return QPDFPageObjectHelper(*this).getImages();
1441
0
}
1442
1443
std::vector<QPDFObjectHandle>
1444
QPDFObjectHandle::arrayOrStreamToStreamArray(
1445
    std::string const& description, std::string& all_description)
1446
0
{
1447
0
    all_description = description;
1448
0
    std::vector<QPDFObjectHandle> result;
1449
0
    if (auto array = as_array(strict)) {
1450
0
        int n_items = static_cast<int>(array.size());
1451
0
        for (int i = 0; i < n_items; ++i) {
1452
0
            QPDFObjectHandle item = array[i];
1453
0
            if (item.isStream()) {
1454
0
                result.emplace_back(item);
1455
0
            } else {
1456
0
                item.warn(
1457
0
                    {qpdf_e_damaged_pdf,
1458
0
                     "",
1459
0
                     description + ": item index " + std::to_string(i) + " (from 0)",
1460
0
                     0,
1461
0
                     "ignoring non-stream in an array of streams"});
1462
0
            }
1463
0
        }
1464
0
    } else if (isStream()) {
1465
0
        result.emplace_back(*this);
1466
0
    } else if (!null()) {
1467
0
        warn(
1468
0
            {qpdf_e_damaged_pdf,
1469
0
             "",
1470
0
             description,
1471
0
             0,
1472
0
             " object is supposed to be a stream or an array of streams but is neither"});
1473
0
    }
1474
1475
0
    bool first = true;
1476
0
    for (auto const& item: result) {
1477
0
        if (first) {
1478
0
            first = false;
1479
0
        } else {
1480
0
            all_description += ",";
1481
0
        }
1482
0
        all_description += " stream " + item.getObjGen().unparse(' ');
1483
0
    }
1484
1485
0
    return result;
1486
0
}
1487
1488
std::vector<QPDFObjectHandle>
1489
QPDFObjectHandle::getPageContents()
1490
0
{
1491
0
    std::string description = "page object " + getObjGen().unparse(' ');
1492
0
    std::string all_description;
1493
0
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1494
0
}
1495
1496
void
1497
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1498
0
{
1499
0
    new_contents.assertStream();
1500
1501
0
    std::vector<QPDFObjectHandle> content_streams;
1502
0
    if (first) {
1503
0
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1504
0
        content_streams.push_back(new_contents);
1505
0
    }
1506
0
    for (auto const& iter: getPageContents()) {
1507
0
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1508
0
        content_streams.push_back(iter);
1509
0
    }
1510
0
    if (!first) {
1511
0
        content_streams.push_back(new_contents);
1512
0
    }
1513
1514
0
    replaceKey("/Contents", newArray(content_streams));
1515
0
}
1516
1517
void
1518
QPDFObjectHandle::rotatePage(int angle, bool relative)
1519
0
{
1520
0
    if ((angle % 90) != 0) {
1521
0
        throw std::runtime_error(
1522
0
            "QPDF::rotatePage called with an angle that is not a multiple of 90");
1523
0
    }
1524
0
    int new_angle = angle;
1525
0
    if (relative) {
1526
0
        int old_angle = 0;
1527
0
        QPDFObjectHandle cur_obj = *this;
1528
0
        QPDFObjGen::set visited;
1529
0
        while (visited.add(cur_obj)) {
1530
            // Don't get stuck in an infinite loop
1531
0
            if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) {
1532
0
                break;
1533
0
            } else if (cur_obj.getKey("/Parent").isDictionary()) {
1534
0
                cur_obj = cur_obj.getKey("/Parent");
1535
0
            } else {
1536
0
                break;
1537
0
            }
1538
0
        }
1539
0
        QTC::TC("qpdf", "QPDFObjectHandle found old angle", visited.size() > 1 ? 0 : 1);
1540
0
        if ((old_angle % 90) != 0) {
1541
0
            old_angle = 0;
1542
0
        }
1543
0
        new_angle += old_angle;
1544
0
    }
1545
0
    new_angle = (new_angle + 360) % 360;
1546
    // Make this explicit even with new_angle == 0 since /Rotate can be inherited.
1547
0
    replaceKey("/Rotate", Integer(new_angle));
1548
0
}
1549
1550
void
1551
QPDFObjectHandle::coalesceContentStreams()
1552
0
{
1553
0
    QPDFObjectHandle contents = getKey("/Contents");
1554
0
    if (contents.isStream()) {
1555
0
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1556
0
        return;
1557
0
    } else if (!contents.isArray()) {
1558
        // /Contents is optional for pages, and some very damaged files may have pages that are
1559
        // invalid in other ways.
1560
0
        return;
1561
0
    }
1562
    // Should not be possible for a page object to not have an owning PDF unless it was manually
1563
    // constructed in some incorrect way. However, it can happen in a PDF file whose page structure
1564
    // is direct, which is against spec but still possible to hand construct, as in fuzz issue
1565
    // 27393.
1566
0
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1567
1568
0
    QPDFObjectHandle new_contents = newStream(&qpdf);
1569
0
    replaceKey("/Contents", new_contents);
1570
1571
0
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1572
0
    new_contents.replaceStreamData(provider, newNull(), newNull());
1573
0
}
1574
1575
std::string
1576
QPDFObjectHandle::unparse() const
1577
0
{
1578
0
    if (isIndirect()) {
1579
0
        return getObjGen().unparse(' ') + " R";
1580
0
    } else {
1581
0
        return unparseResolved();
1582
0
    }
1583
0
}
1584
1585
std::string
1586
QPDFObjectHandle::unparseResolved() const
1587
0
{
1588
0
    if (!obj) {
1589
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1590
0
    }
1591
0
    return BaseHandle::unparse();
1592
0
}
1593
1594
std::string
1595
QPDFObjectHandle::unparseBinary() const
1596
0
{
1597
0
    if (auto str = as<QPDF_String>()) {
1598
0
        return str->unparse(true);
1599
0
    } else {
1600
0
        return unparse();
1601
0
    }
1602
0
}
1603
1604
JSON
1605
QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) const
1606
0
{
1607
0
    if ((!dereference_indirect) && isIndirect()) {
1608
0
        return JSON::makeString(unparse());
1609
0
    } else if (!obj) {
1610
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1611
0
    } else {
1612
0
        Pl_Buffer p{"json"};
1613
0
        JSON::Writer jw{&p, 0};
1614
0
        writeJSON(json_version, jw, dereference_indirect);
1615
0
        p.finish();
1616
0
        return JSON::parse(p.getString());
1617
0
    }
1618
0
}
1619
1620
void
1621
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1622
0
{
1623
0
    if (!dereference_indirect && isIndirect()) {
1624
0
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1625
0
    } else if (!obj) {
1626
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1627
0
    } else {
1628
0
        write_json(json_version, p);
1629
0
    }
1630
0
}
1631
1632
void
1633
QPDFObjectHandle::writeJSON(
1634
    int json_version, Pipeline* p, bool dereference_indirect, size_t depth) const
1635
0
{
1636
0
    JSON::Writer jw{p, depth};
1637
0
    writeJSON(json_version, jw, dereference_indirect);
1638
0
}
1639
1640
QPDFObjectHandle
1641
QPDFObjectHandle::wrapInArray()
1642
0
{
1643
0
    if (isArray()) {
1644
0
        return *this;
1645
0
    }
1646
0
    QPDFObjectHandle result = QPDFObjectHandle::newArray();
1647
0
    result.appendItem(*this);
1648
0
    return result;
1649
0
}
1650
1651
QPDFObjectHandle
1652
QPDFObjectHandle::parse(std::string const& object_str, std::string const& object_description)
1653
0
{
1654
0
    return parse(nullptr, object_str, object_description);
1655
0
}
1656
1657
QPDFObjectHandle
1658
QPDFObjectHandle::parse(
1659
    QPDF* context, std::string const& object_str, std::string const& object_description)
1660
0
{
1661
0
    auto input = is::OffsetBuffer("parsed object", object_str);
1662
0
    auto result = Parser::parse(input, object_description, context);
1663
0
    size_t offset = QIntC::to_size(input.tell());
1664
0
    while (offset < object_str.length()) {
1665
0
        if (!isspace(object_str.at(offset))) {
1666
0
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1667
0
            throw QPDFExc(
1668
0
                qpdf_e_damaged_pdf,
1669
0
                "parsed object",
1670
0
                object_description,
1671
0
                input.getLastOffset(),
1672
0
                "trailing data found parsing object from string");
1673
0
        }
1674
0
        ++offset;
1675
0
    }
1676
0
    return result;
1677
0
}
1678
1679
void
1680
QPDFObjectHandle::pipePageContents(Pipeline* p)
1681
0
{
1682
0
    std::string description = "page object " + getObjGen().unparse(' ');
1683
0
    std::string all_description;
1684
0
    getKey("/Contents").pipeContentStreams(p, description, all_description);
1685
0
}
1686
1687
void
1688
QPDFObjectHandle::pipeContentStreams(
1689
    Pipeline* p, std::string const& description, std::string& all_description)
1690
0
{
1691
0
    bool need_newline = false;
1692
0
    std::string buffer;
1693
0
    pl::String buf(buffer);
1694
0
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1695
0
        if (need_newline) {
1696
0
            buf.writeCStr("\n");
1697
0
        }
1698
0
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1699
0
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1700
0
            throw QPDFExc(
1701
0
                qpdf_e_damaged_pdf,
1702
0
                "content stream",
1703
0
                "content stream object " + stream.getObjGen().unparse(' '),
1704
0
                0,
1705
0
                "errors while decoding content stream");
1706
0
        }
1707
0
        need_newline = buffer.empty() || buffer.back() != '\n';
1708
0
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1709
0
        p->writeString(buffer);
1710
0
        buffer.clear();
1711
0
    }
1712
0
    p->finish();
1713
0
}
1714
1715
void
1716
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1717
0
{
1718
0
    std::string description = "page object " + getObjGen().unparse(' ');
1719
0
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1720
0
}
1721
1722
void
1723
QPDFObjectHandle::parseAsContents(ParserCallbacks* callbacks)
1724
0
{
1725
0
    std::string description = "object " + getObjGen().unparse(' ');
1726
0
    parseContentStream_internal(description, callbacks);
1727
0
}
1728
1729
void
1730
QPDFObjectHandle::filterPageContents(TokenFilter* filter, Pipeline* next)
1731
0
{
1732
0
    auto description = "token filter for page object " + getObjGen().unparse(' ');
1733
0
    Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
1734
0
    pipePageContents(&token_pipeline);
1735
0
}
1736
1737
void
1738
QPDFObjectHandle::filterAsContents(TokenFilter* filter, Pipeline* next)
1739
0
{
1740
0
    auto description = "token filter for object " + getObjGen().unparse(' ');
1741
0
    Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
1742
0
    pipeStreamData(&token_pipeline, 0, qpdf_dl_specialized);
1743
0
}
1744
1745
void
1746
QPDFObjectHandle::parseContentStream(QPDFObjectHandle stream_or_array, ParserCallbacks* callbacks)
1747
0
{
1748
0
    stream_or_array.parseContentStream_internal("content stream objects", callbacks);
1749
0
}
1750
1751
void
1752
QPDFObjectHandle::parseContentStream_internal(
1753
    std::string const& description, ParserCallbacks* callbacks)
1754
0
{
1755
0
    std::string stream_data;
1756
0
    pl::String buf(stream_data);
1757
0
    std::string all_description;
1758
0
    pipeContentStreams(&buf, description, all_description);
1759
0
    if (callbacks) {
1760
0
        callbacks->contentSize(stream_data.size());
1761
0
    }
1762
0
    try {
1763
0
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1764
0
    } catch (TerminateParsing&) {
1765
0
        return;
1766
0
    }
1767
0
    if (callbacks) {
1768
0
        callbacks->handleEOF();
1769
0
    }
1770
0
}
1771
1772
void
1773
QPDFObjectHandle::parseContentStream_data(
1774
    std::string_view stream_data,
1775
    std::string const& description,
1776
    ParserCallbacks* callbacks,
1777
    QPDF* context)
1778
0
{
1779
0
    size_t stream_length = stream_data.size();
1780
0
    auto input = is::OffsetBuffer(description, stream_data);
1781
0
    Tokenizer tokenizer;
1782
0
    tokenizer.allowEOF();
1783
0
    auto max_bad_count = Limits::parser_max_errors();
1784
0
    auto sp_description = Parser::make_description(description, "content");
1785
0
    while (QIntC::to_size(input.tell()) < stream_length) {
1786
        // Read a token and seek to the beginning. The offset we get from this process is the
1787
        // beginning of the next non-ignorable (space, comment) token. This way, the offset and
1788
        // don't including ignorable content.
1789
0
        tokenizer.nextToken(input, "content", true);
1790
0
        qpdf_offset_t offset = input.getLastOffset();
1791
0
        input.seek(offset, SEEK_SET);
1792
0
        auto [obj, empty] = Parser::parse_content(input, sp_description, tokenizer, context);
1793
0
        if (empty) {
1794
            // EOF
1795
0
            return;
1796
0
        }
1797
0
        if (!obj) {
1798
0
            if (max_bad_count && --max_bad_count == 0) {
1799
0
                Limits::error();
1800
0
                warn(
1801
0
                    context,
1802
0
                    {qpdf_e_damaged_pdf,
1803
0
                     description,
1804
0
                     "stream data",
1805
0
                     input.tell(),
1806
0
                     "limits error(parser-max-errors): too many errors during content stream "
1807
0
                     "parsing"});
1808
0
                return;
1809
0
            }
1810
0
            obj = newNull();
1811
0
        }
1812
0
        size_t length = QIntC::to_size(input.tell() - offset);
1813
0
        if (callbacks) {
1814
0
            callbacks->handleObject(obj, QIntC::to_size(offset), length);
1815
0
        }
1816
0
        if (obj.isOperator() && obj.getOperatorValue() == "ID") {
1817
            // Discard next character; it is the space after ID that terminated the token.  Read
1818
            // until end of inline image.
1819
0
            char ch;
1820
0
            input.read(&ch, 1);
1821
0
            tokenizer.expectInlineImage(input);
1822
0
            tokenizer.nextToken(input, description);
1823
0
            offset = input.getLastOffset();
1824
0
            length = QIntC::to_size(input.tell() - offset);
1825
0
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1826
0
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1827
0
                warn(
1828
0
                    context,
1829
0
                    {qpdf_e_damaged_pdf,
1830
0
                     description,
1831
0
                     "stream data",
1832
0
                     input.tell(),
1833
0
                     "EOF found while reading inline image"});
1834
0
            } else {
1835
0
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1836
0
                if (callbacks) {
1837
0
                    callbacks->handleObject(
1838
0
                        QPDFObjectHandle::newInlineImage(tokenizer.getValue()),
1839
0
                        QIntC::to_size(offset),
1840
0
                        length);
1841
0
                }
1842
0
            }
1843
0
        }
1844
0
    }
1845
0
}
1846
1847
void
1848
QPDFObjectHandle::addContentTokenFilter(std::shared_ptr<TokenFilter> filter)
1849
0
{
1850
0
    coalesceContentStreams();
1851
0
    getKey("/Contents").addTokenFilter(filter);
1852
0
}
1853
1854
void
1855
QPDFObjectHandle::addTokenFilter(std::shared_ptr<TokenFilter> filter)
1856
0
{
1857
0
    return as_stream(error).addTokenFilter(filter);
1858
0
}
1859
1860
QPDFObjectHandle
1861
QPDFObjectHandle::parse(
1862
    std::shared_ptr<InputSource> input,
1863
    std::string const& object_description,
1864
    QPDFTokenizer& tokenizer,
1865
    bool& empty,
1866
    StringDecrypter* decrypter,
1867
    QPDF* context)
1868
0
{
1869
0
    return Parser::parse(*input, object_description, tokenizer, empty, decrypter, context);
1870
0
}
1871
1872
qpdf_offset_t
1873
QPDFObjectHandle::getParsedOffset() const
1874
0
{
1875
0
    return offset();
1876
0
}
1877
1878
QPDFObjectHandle
1879
QPDFObjectHandle::newBool(bool value)
1880
0
{
1881
0
    return {QPDFObject::create<QPDF_Bool>(value)};
1882
0
}
1883
1884
QPDFObjectHandle
1885
QPDFObjectHandle::newNull()
1886
17.9k
{
1887
17.9k
    return {QPDFObject::create<QPDF_Null>()};
1888
17.9k
}
1889
1890
QPDFObjectHandle
1891
QPDFObjectHandle::newReal(std::string const& value)
1892
0
{
1893
0
    return {QPDFObject::create<QPDF_Real>(value)};
1894
0
}
1895
1896
QPDFObjectHandle
1897
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1898
14.3k
{
1899
14.3k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1900
14.3k
}
1901
1902
QPDFObjectHandle
1903
QPDFObjectHandle::newOperator(std::string const& value)
1904
0
{
1905
0
    return {QPDFObject::create<QPDF_Operator>(value)};
1906
0
}
1907
1908
QPDFObjectHandle
1909
QPDFObjectHandle::newInlineImage(std::string const& value)
1910
0
{
1911
0
    return {QPDFObject::create<QPDF_InlineImage>(value)};
1912
0
}
1913
1914
QPDFObjectHandle
1915
QPDFObjectHandle::newArray()
1916
0
{
1917
0
    return newArray(std::vector<QPDFObjectHandle>());
1918
0
}
1919
1920
QPDFObjectHandle
1921
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1922
3.59k
{
1923
3.59k
    return {QPDFObject::create<QPDF_Array>(items)};
1924
3.59k
}
1925
1926
QPDFObjectHandle
1927
QPDFObjectHandle::newArray(Rectangle const& rect)
1928
3.59k
{
1929
3.59k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1930
3.59k
}
1931
1932
QPDFObjectHandle
1933
QPDFObjectHandle::newArray(Matrix const& matrix)
1934
0
{
1935
0
    return newArray(
1936
0
        {newReal(matrix.a),
1937
0
         newReal(matrix.b),
1938
0
         newReal(matrix.c),
1939
0
         newReal(matrix.d),
1940
0
         newReal(matrix.e),
1941
0
         newReal(matrix.f)});
1942
0
}
1943
1944
QPDFObjectHandle
1945
QPDFObjectHandle::newArray(QPDFMatrix const& matrix)
1946
0
{
1947
0
    return newArray(
1948
0
        {newReal(matrix.a),
1949
0
         newReal(matrix.b),
1950
0
         newReal(matrix.c),
1951
0
         newReal(matrix.d),
1952
0
         newReal(matrix.e),
1953
0
         newReal(matrix.f)});
1954
0
}
1955
1956
QPDFObjectHandle
1957
QPDFObjectHandle::newFromRectangle(Rectangle const& rect)
1958
0
{
1959
0
    return newArray(rect);
1960
0
}
1961
1962
QPDFObjectHandle
1963
QPDFObjectHandle::newFromMatrix(Matrix const& m)
1964
0
{
1965
0
    return newArray(m);
1966
0
}
1967
1968
QPDFObjectHandle
1969
QPDFObjectHandle::newFromMatrix(QPDFMatrix const& m)
1970
0
{
1971
0
    return newArray(m);
1972
0
}
1973
1974
QPDFObjectHandle
1975
QPDFObjectHandle::newDictionary()
1976
4.76k
{
1977
4.76k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1978
4.76k
}
1979
1980
QPDFObjectHandle
1981
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1982
4.76k
{
1983
4.76k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1984
4.76k
}
1985
1986
QPDFObjectHandle
1987
QPDFObjectHandle::newStream(QPDF* qpdf)
1988
0
{
1989
0
    if (qpdf == nullptr) {
1990
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1991
0
    }
1992
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1993
0
    return qpdf->newStream();
1994
0
}
1995
1996
QPDFObjectHandle
1997
QPDFObjectHandle::newStream(QPDF* qpdf, std::shared_ptr<Buffer> data)
1998
0
{
1999
0
    if (qpdf == nullptr) {
2000
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
2001
0
    }
2002
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with data");
2003
0
    return qpdf->newStream(data);
2004
0
}
2005
2006
QPDFObjectHandle
2007
QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data)
2008
0
{
2009
0
    if (qpdf == nullptr) {
2010
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
2011
0
    }
2012
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with string");
2013
0
    return qpdf->newStream(data);
2014
0
}
2015
2016
QPDFObjectHandle
2017
QPDFObjectHandle::newReserved(QPDF* qpdf)
2018
0
{
2019
0
    if (qpdf == nullptr) {
2020
0
        throw std::runtime_error("attempt to create reserved object in null qpdf object");
2021
0
    }
2022
0
    return qpdf->newReserved();
2023
0
}
2024
2025
std::string
2026
BaseHandle::description() const
2027
46.8k
{
2028
46.8k
    return obj ? obj->getDescription() : ""s;
2029
46.8k
}
2030
2031
void
2032
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
2033
0
{
2034
0
    if (obj) {
2035
0
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
2036
0
        obj->setDescription(owning_qpdf, descr);
2037
0
    }
2038
0
}
2039
2040
bool
2041
QPDFObjectHandle::hasObjectDescription() const
2042
20.4k
{
2043
20.4k
    return obj && obj->hasDescription();
2044
20.4k
}
2045
2046
QPDFObjectHandle
2047
QPDFObjectHandle::shallowCopy()
2048
146
{
2049
146
    if (!obj) {
2050
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2051
0
    }
2052
146
    return {copy()};
2053
146
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::unsafeShallowCopy()
2057
0
{
2058
0
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
0
    return {copy(true)};
2062
0
}
2063
2064
void
2065
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2066
0
{
2067
0
    static uint32_t constexpr max_nesting = 500;
2068
0
    if (++level > max_nesting + 1) {
2069
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2070
0
    }
2071
0
    assertInitialized();
2072
2073
0
    auto cur_og = getObjGen();
2074
0
    if (!visited.add(cur_og)) {
2075
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2076
0
    }
2077
2078
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2079
0
        obj = copy(true);
2080
0
    } else if (auto a = as_array(strict)) {
2081
0
        std::vector<QPDFObjectHandle> items;
2082
0
        for (auto const& item: a) {
2083
0
            items.emplace_back(item);
2084
0
            items.back().makeDirect(level, visited, stop_at_streams);
2085
0
        }
2086
0
        obj = QPDFObject::create<QPDF_Array>(items);
2087
0
    } else if (isDictionary()) {
2088
0
        std::map<std::string, QPDFObjectHandle> items;
2089
0
        for (auto const& [key, value]: as_dictionary(strict)) {
2090
0
            if (!value.null()) {
2091
0
                items.insert({key, value});
2092
0
                items[key].makeDirect(level, visited, stop_at_streams);
2093
0
            }
2094
0
        }
2095
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2096
0
    } else if (isStream()) {
2097
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2098
0
        if (!stop_at_streams) {
2099
0
            throw std::runtime_error("attempt to make a stream into a direct object");
2100
0
        }
2101
0
    } else if (isReserved()) {
2102
0
        throw std::logic_error(
2103
0
            "QPDFObjectHandle: attempting to make a reserved object handle direct");
2104
0
    } else {
2105
0
        throw std::logic_error("QPDFObjectHandle::makeDirectInternal: unknown object type");
2106
0
    }
2107
2108
0
    visited.erase(cur_og);
2109
0
}
2110
2111
void
2112
QPDFObjectHandle::makeDirect(bool allow_streams)
2113
0
{
2114
0
    QPDFObjGen::set visited;
2115
0
    makeDirect(0, visited, allow_streams);
2116
0
}
2117
2118
void
2119
QPDFObjectHandle::assertInitialized() const
2120
0
{
2121
0
    if (!obj) {
2122
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2123
0
    }
2124
0
}
2125
2126
std::invalid_argument
2127
BaseHandle::invalid_error(std::string const& method) const
2128
1.28k
{
2129
1.28k
    return std::invalid_argument(method + " operation attempted on invalid object");
2130
1.28k
}
2131
std::runtime_error
2132
BaseHandle::type_error(char const* expected_type) const
2133
0
{
2134
0
    return std::runtime_error(
2135
0
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2136
0
}
2137
2138
QPDFExc
2139
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2140
4.56k
{
2141
4.56k
    return {
2142
4.56k
        qpdf_e_object,
2143
4.56k
        "",
2144
4.56k
        description(),
2145
4.56k
        0,
2146
4.56k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2147
4.56k
            message};
2148
4.56k
}
2149
2150
void
2151
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2152
4.56k
{
2153
4.56k
    if (!obj) {
2154
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2155
0
    }
2156
4.56k
    warn(type_error(expected_type, message));
2157
4.56k
}
2158
2159
void
2160
QPDFObjectHandle::warnIfPossible(std::string const& warning) const
2161
0
{
2162
0
    warn(warning);
2163
0
}
2164
2165
void
2166
QPDFObjectHandle::objectWarning(std::string const& warning) const
2167
0
{
2168
0
    warn({qpdf_e_object, "", description(), 0, warning});
2169
0
}
2170
2171
void
2172
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2173
0
{
2174
0
    if (!istype) {
2175
0
        throw type_error(type_name);
2176
0
    }
2177
0
}
2178
2179
void
2180
QPDFObjectHandle::assertNull() const
2181
0
{
2182
0
    assertType("null", isNull());
2183
0
}
2184
2185
void
2186
QPDFObjectHandle::assertBool() const
2187
0
{
2188
0
    assertType("boolean", isBool());
2189
0
}
2190
2191
void
2192
QPDFObjectHandle::assertInteger() const
2193
0
{
2194
0
    assertType("integer", isInteger());
2195
0
}
2196
2197
void
2198
QPDFObjectHandle::assertReal() const
2199
0
{
2200
0
    assertType("real", isReal());
2201
0
}
2202
2203
void
2204
QPDFObjectHandle::assertName() const
2205
0
{
2206
0
    assertType("name", isName());
2207
0
}
2208
2209
void
2210
QPDFObjectHandle::assertString() const
2211
0
{
2212
0
    assertType("string", isString());
2213
0
}
2214
2215
void
2216
QPDFObjectHandle::assertOperator() const
2217
0
{
2218
0
    assertType("operator", isOperator());
2219
0
}
2220
2221
void
2222
QPDFObjectHandle::assertInlineImage() const
2223
0
{
2224
0
    assertType("inlineimage", isInlineImage());
2225
0
}
2226
2227
void
2228
QPDFObjectHandle::assertArray() const
2229
0
{
2230
0
    assertType("array", isArray());
2231
0
}
2232
2233
void
2234
QPDFObjectHandle::assertDictionary() const
2235
0
{
2236
0
    assertType("dictionary", isDictionary());
2237
0
}
2238
2239
void
2240
QPDFObjectHandle::assertStream() const
2241
0
{
2242
0
    assertType("stream", isStream());
2243
0
}
2244
2245
void
2246
QPDFObjectHandle::assertReserved() const
2247
0
{
2248
0
    assertType("reserved", isReserved());
2249
0
}
2250
2251
void
2252
QPDFObjectHandle::assertIndirect() const
2253
0
{
2254
0
    if (!isIndirect()) {
2255
0
        throw std::logic_error("operation for indirect object attempted on direct object");
2256
0
    }
2257
0
}
2258
2259
void
2260
QPDFObjectHandle::assertScalar() const
2261
0
{
2262
0
    assertType("scalar", isScalar());
2263
0
}
2264
2265
void
2266
QPDFObjectHandle::assertNumber() const
2267
0
{
2268
0
    assertType("number", isNumber());
2269
0
}
2270
2271
bool
2272
QPDFObjectHandle::isPageObject() const
2273
0
{
2274
    // See comments in QPDFObjectHandle.hh.
2275
0
    if (!qpdf()) {
2276
0
        return false;
2277
0
    }
2278
    // getAllPages repairs /Type when traversing the page tree.
2279
0
    (void)qpdf()->doc().pages().all();
2280
0
    return isDictionaryOfType("/Page");
2281
0
}
2282
2283
bool
2284
QPDFObjectHandle::isPagesObject() const
2285
0
{
2286
0
    if (!qpdf()) {
2287
0
        return false;
2288
0
    }
2289
    // getAllPages repairs /Type when traversing the page tree.
2290
0
    (void)qpdf()->doc().pages().all();
2291
0
    return isDictionaryOfType("/Pages");
2292
0
}
2293
2294
bool
2295
QPDFObjectHandle::isFormXObject() const
2296
0
{
2297
0
    return isStreamOfType("", "/Form");
2298
0
}
2299
2300
bool
2301
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2302
0
{
2303
0
    return (
2304
0
        isStreamOfType("", "/Image") &&
2305
0
        ((!exclude_imagemask) ||
2306
0
         (!(getDict().getKey("/ImageMask").isBool() &&
2307
0
            getDict().getKey("/ImageMask").getBoolValue()))));
2308
0
}
2309
2310
void
2311
QPDFObjectHandle::assertPageObject() const
2312
0
{
2313
0
    if (!isPageObject()) {
2314
0
        throw std::runtime_error("page operation called on non-Page object");
2315
0
    }
2316
0
}
2317
2318
void
2319
BaseHandle::warn(QPDF* qpdf, QPDFExc&& e)
2320
0
{
2321
0
    if (!qpdf) {
2322
0
        throw std::move(e);
2323
0
    }
2324
0
    qpdf->warn(std::move(e));
2325
0
}
2326
2327
void
2328
BaseHandle::warn(QPDFExc&& e) const
2329
46.8k
{
2330
46.8k
    if (!qpdf()) {
2331
217
        throw std::move(e);
2332
217
    }
2333
46.6k
    qpdf()->warn(std::move(e));
2334
46.6k
}
2335
2336
void
2337
BaseHandle::warn(std::string const& warning) const
2338
50.5k
{
2339
50.5k
    if (qpdf()) {
2340
42.2k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2341
42.2k
    } else {
2342
8.30k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2343
8.30k
    }
2344
50.5k
}
2345
2346
QPDFObjectHandle::QPDFDictItems::QPDFDictItems(QPDFObjectHandle const& oh) :
2347
0
    oh(oh)
2348
0
{
2349
0
}
2350
2351
QPDFObjectHandle::QPDFDictItems::iterator&
2352
QPDFObjectHandle::QPDFDictItems::iterator::operator++()
2353
0
{
2354
0
    ++m->iter;
2355
0
    updateIValue();
2356
0
    return *this;
2357
0
}
2358
2359
QPDFObjectHandle::QPDFDictItems::iterator&
2360
QPDFObjectHandle::QPDFDictItems::iterator::operator--()
2361
0
{
2362
0
    --m->iter;
2363
0
    updateIValue();
2364
0
    return *this;
2365
0
}
2366
2367
QPDFObjectHandle::QPDFDictItems::iterator::reference
2368
QPDFObjectHandle::QPDFDictItems::iterator::operator*()
2369
0
{
2370
0
    updateIValue();
2371
0
    return ivalue;
2372
0
}
2373
2374
QPDFObjectHandle::QPDFDictItems::iterator::pointer
2375
QPDFObjectHandle::QPDFDictItems::iterator::operator->()
2376
0
{
2377
0
    updateIValue();
2378
0
    return &ivalue;
2379
0
}
2380
2381
bool
2382
QPDFObjectHandle::QPDFDictItems::iterator::operator==(iterator const& other) const
2383
0
{
2384
0
    if (m->is_end && other.m->is_end) {
2385
0
        return true;
2386
0
    }
2387
0
    if (m->is_end || other.m->is_end) {
2388
0
        return false;
2389
0
    }
2390
0
    return (ivalue.first == other.ivalue.first);
2391
0
}
2392
2393
QPDFObjectHandle::QPDFDictItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2394
0
    m(new Members(oh, for_begin))
2395
0
{
2396
0
    updateIValue();
2397
0
}
2398
2399
void
2400
QPDFObjectHandle::QPDFDictItems::iterator::updateIValue()
2401
0
{
2402
0
    m->is_end = (m->iter == m->keys.end());
2403
0
    if (m->is_end) {
2404
0
        ivalue.first = "";
2405
0
        ivalue.second = QPDFObjectHandle();
2406
0
    } else {
2407
0
        ivalue.first = *(m->iter);
2408
0
        ivalue.second = m->oh.getKey(ivalue.first);
2409
0
    }
2410
0
}
2411
2412
QPDFObjectHandle::QPDFDictItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2413
0
    oh(oh)
2414
0
{
2415
0
    keys = oh.getKeys();
2416
0
    iter = for_begin ? keys.begin() : keys.end();
2417
0
}
2418
2419
QPDFObjectHandle::QPDFDictItems::iterator
2420
QPDFObjectHandle::QPDFDictItems::begin()
2421
0
{
2422
0
    return {oh, true};
2423
0
}
2424
2425
QPDFObjectHandle::QPDFDictItems::iterator
2426
QPDFObjectHandle::QPDFDictItems::end()
2427
0
{
2428
0
    return {oh, false};
2429
0
}
2430
2431
QPDFObjectHandle::QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle const& oh) :
2432
0
    oh(oh)
2433
0
{
2434
0
}
2435
2436
QPDFObjectHandle::QPDFArrayItems::iterator&
2437
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2438
0
{
2439
0
    if (!m->is_end) {
2440
0
        ++m->item_number;
2441
0
        updateIValue();
2442
0
    }
2443
0
    return *this;
2444
0
}
2445
2446
QPDFObjectHandle::QPDFArrayItems::iterator&
2447
QPDFObjectHandle::QPDFArrayItems::iterator::operator--()
2448
0
{
2449
0
    if (m->item_number > 0) {
2450
0
        --m->item_number;
2451
0
        updateIValue();
2452
0
    }
2453
0
    return *this;
2454
0
}
2455
2456
QPDFObjectHandle::QPDFArrayItems::iterator::reference
2457
QPDFObjectHandle::QPDFArrayItems::iterator::operator*()
2458
0
{
2459
0
    updateIValue();
2460
0
    return ivalue;
2461
0
}
2462
2463
QPDFObjectHandle::QPDFArrayItems::iterator::pointer
2464
QPDFObjectHandle::QPDFArrayItems::iterator::operator->()
2465
0
{
2466
0
    updateIValue();
2467
0
    return &ivalue;
2468
0
}
2469
2470
bool
2471
QPDFObjectHandle::QPDFArrayItems::iterator::operator==(iterator const& other) const
2472
0
{
2473
0
    return (m->item_number == other.m->item_number);
2474
0
}
2475
2476
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2477
0
    m(new Members(oh, for_begin))
2478
0
{
2479
0
    updateIValue();
2480
0
}
2481
2482
void
2483
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2484
0
{
2485
0
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2486
0
    if (m->is_end) {
2487
0
        ivalue = QPDFObjectHandle();
2488
0
    } else {
2489
0
        ivalue = m->oh.getArrayItem(m->item_number);
2490
0
    }
2491
0
}
2492
2493
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2494
0
    oh(oh)
2495
0
{
2496
0
    item_number = for_begin ? 0 : oh.getArrayNItems();
2497
0
}
2498
2499
QPDFObjectHandle::QPDFArrayItems::iterator
2500
QPDFObjectHandle::QPDFArrayItems::begin()
2501
0
{
2502
0
    return {oh, true};
2503
0
}
2504
2505
QPDFObjectHandle::QPDFArrayItems::iterator
2506
QPDFObjectHandle::QPDFArrayItems::end()
2507
0
{
2508
0
    return {oh, false};
2509
0
}
2510
2511
QPDFObjGen
2512
QPDFObjectHandle::getObjGen() const
2513
32.4k
{
2514
32.4k
    return obj ? obj->getObjGen() : QPDFObjGen();
2515
32.4k
}
2516
2517
int
2518
QPDFObjectHandle::getObjectID() const
2519
22.3k
{
2520
22.3k
    return getObjGen().getObj();
2521
22.3k
}
2522
2523
int
2524
QPDFObjectHandle::getGeneration() const
2525
0
{
2526
0
    return getObjGen().getGen();
2527
0
}
2528
2529
bool
2530
QPDFObjectHandle::isIndirect() const
2531
17.8k
{
2532
17.8k
    return getObjectID() != 0;
2533
17.8k
}
2534
2535
// Indirect object accessors
2536
QPDF*
2537
QPDFObjectHandle::getOwningQPDF() const
2538
51.2k
{
2539
51.2k
    return obj ? obj->getQPDF() : nullptr;
2540
51.2k
}
2541
2542
QPDF&
2543
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2544
0
{
2545
0
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2546
0
        return *result;
2547
0
    }
2548
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2549
0
}
2550
2551
void
2552
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2553
21
{
2554
21
    if (obj) {
2555
21
        obj->setParsedOffset(offset);
2556
21
    }
2557
21
}
2558
2559
QPDFObjectHandle
2560
operator""_qpdf(char const* v, size_t len)
2561
0
{
2562
0
    return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal");
2563
0
}