Coverage Report

Created: 2026-01-25 06:25

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