Coverage Report

Created: 2025-08-26 07:13

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