Coverage Report

Created: 2026-01-25 06:26

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
94.3k
{
35
94.3k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
94.3k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
25.7k
    supports_retry(supports_retry)
47
25.7k
{
48
25.7k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
25.7k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
25.7k
}
54
55
void
56
QPDFObjectHandle::StreamDataProvider::provideStreamData(QPDFObjGen const& og, Pipeline* pipeline)
57
0
{
58
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline);
59
0
}
60
61
bool
62
QPDFObjectHandle::StreamDataProvider::provideStreamData(
63
    QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
64
0
{
65
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline, suppress_warnings, will_retry);
66
0
}
67
68
void
69
QPDFObjectHandle::StreamDataProvider::provideStreamData(
70
    int objid, int generation, Pipeline* pipeline)
71
0
{
72
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
73
0
}
74
75
bool
76
QPDFObjectHandle::StreamDataProvider::provideStreamData(
77
    int objid, int generation, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
78
0
{
79
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
80
0
    return false;
81
0
}
82
83
bool
84
QPDFObjectHandle::StreamDataProvider::supportsRetry()
85
0
{
86
0
    return supports_retry;
87
0
}
88
89
namespace
90
{
91
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
92
    {
93
      public:
94
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
95
0
            containing_page(containing_page),
96
0
            old_contents(old_contents)
97
0
        {
98
0
        }
99
0
        ~CoalesceProvider() override = default;
100
        void provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override;
101
102
      private:
103
        QPDFObjectHandle containing_page;
104
        QPDFObjectHandle old_contents;
105
    };
106
} // namespace
107
108
void
109
CoalesceProvider::provideStreamData(QPDFObjGen const&, Pipeline* p)
110
0
{
111
0
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
112
0
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
113
0
    std::string all_description;
114
0
    old_contents.pipeContentStreams(p, description, all_description);
115
0
}
116
117
void
118
QPDFObjectHandle::TokenFilter::handleEOF()
119
2.39k
{
120
2.39k
}
121
122
void
123
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
124
5.12k
{
125
5.12k
    pipeline = p;
126
5.12k
}
127
128
void
129
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
130
58.7M
{
131
58.7M
    if (!pipeline) {
132
0
        return;
133
0
    }
134
58.7M
    if (len) {
135
58.7M
        pipeline->write(data, len);
136
58.7M
    }
137
58.7M
}
138
139
void
140
QPDFObjectHandle::TokenFilter::write(std::string const& str)
141
4.76M
{
142
4.76M
    write(str.c_str(), str.length());
143
4.76M
}
144
145
void
146
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
147
27.7M
{
148
27.7M
    std::string const& value = token.getRawValue();
149
27.7M
    write(value.c_str(), value.length());
150
27.7M
}
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
13.6M
{
228
13.6M
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
13.6M
    std::string result;
232
13.6M
    result += name.at(0);
233
44.7M
    for (size_t i = 1; i < name.length(); ++i) {
234
31.0M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
31.0M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
191k
            result += "#";
239
30.8M
        } else if (
240
30.8M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
15.8M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
15.8M
            result += util::hex_encode_char(ch);
243
15.8M
        } else {
244
15.0M
            result += ch;
245
15.0M
        }
246
31.0M
    }
247
13.6M
    return result;
248
13.6M
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
84.0k
{
253
84.0k
    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
13.0k
    case ::ot_null:
260
13.0k
        return QPDFObject::create<QPDF_Null>();
261
237
    case ::ot_boolean:
262
237
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
5.70k
    case ::ot_integer:
264
5.70k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
5.57k
    case ::ot_real:
266
5.57k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
482
    case ::ot_string:
268
482
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
14.0k
    case ::ot_name:
270
14.0k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
623
    case ::ot_array:
272
623
        {
273
623
            auto const& a = std::get<QPDF_Array>(obj->value);
274
623
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
623
            } else {
277
623
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
623
                if (a.sp) {
279
28
                    QPDF_Array result;
280
28
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
28
                    result.sp->size = a.sp->size;
282
1.36k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
1.36k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
1.36k
                    }
285
28
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
595
                } else {
287
595
                    std::vector<QPDFObjectHandle> result;
288
595
                    result.reserve(a.elements.size());
289
3.62k
                    for (auto const& element: a.elements) {
290
3.62k
                        result.emplace_back(
291
3.62k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
3.62k
                    }
293
595
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
595
                }
295
623
            }
296
623
        }
297
44.4k
    case ::ot_dictionary:
298
44.4k
        {
299
44.4k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
44.4k
            if (shallow) {
301
43.6k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
43.6k
            } else {
303
783
                std::map<std::string, QPDFObjectHandle> new_items;
304
3.15k
                for (auto const& [key, val]: d.items) {
305
3.15k
                    new_items[key] = val.indirect() ? val : val.copy();
306
3.15k
                }
307
783
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
783
            }
309
44.4k
        }
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
84.0k
    }
327
0
    return {}; // unreachable
328
84.0k
}
329
330
std::string
331
BaseHandle::unparse() const
332
13.2M
{
333
13.2M
    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
174k
    case ::ot_null:
341
174k
        return "null";
342
4.83k
    case ::ot_boolean:
343
4.83k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
344
201k
    case ::ot_integer:
345
201k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
346
65.2k
    case ::ot_real:
347
65.2k
        return std::get<QPDF_Real>(obj->value).val;
348
122k
    case ::ot_string:
349
122k
        return std::get<QPDF_String>(obj->value).unparse(false);
350
12.6M
    case ::ot_name:
351
12.6M
        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
13.2M
    }
403
0
    return {}; // unreachable
404
13.2M
}
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
14.2M
{
542
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
543
14.2M
    if (only_direct && indirect()) {
544
146k
        return;
545
146k
    }
546
547
14.0M
    switch (raw_type_code()) {
548
87.6k
    case ::ot_array:
549
87.6k
        {
550
87.6k
            auto& a = std::get<QPDF_Array>(obj->value);
551
87.6k
            if (a.sp) {
552
9.87M
                for (auto& item: a.sp->elements) {
553
9.87M
                    item.second.disconnect();
554
9.87M
                }
555
86.9k
            } else {
556
3.24M
                for (auto& oh: a.elements) {
557
3.24M
                    oh.disconnect();
558
3.24M
                }
559
86.9k
            }
560
87.6k
        }
561
87.6k
        break;
562
161k
    case ::ot_dictionary:
563
657k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
564
657k
            iter.second.disconnect();
565
657k
        }
566
161k
        break;
567
39.5k
    case ::ot_stream:
568
39.5k
        {
569
39.5k
            auto& s = std::get<QPDF_Stream>(obj->value);
570
39.5k
            s.m->stream_provider = nullptr;
571
39.5k
            s.m->stream_dict.disconnect();
572
39.5k
        }
573
39.5k
        break;
574
0
    case ::ot_uninitialized:
575
0
        return;
576
13.7M
    default:
577
13.7M
        break;
578
14.0M
    }
579
14.0M
    obj->qpdf = nullptr;
580
14.0M
    obj->og = QPDFObjGen();
581
14.0M
}
582
583
std::string
584
QPDFObject::getStringValue() const
585
20.5k
{
586
20.5k
    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
20.5k
    case ::ot_name:
592
20.5k
        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
20.5k
    }
602
0
    return ""; // unreachable
603
20.5k
}
604
605
bool
606
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
607
259
{
608
259
    return obj == rhs.obj;
609
259
}
610
611
qpdf_object_type_e
612
QPDFObjectHandle::getTypeCode() const
613
13.3M
{
614
13.3M
    return type_code();
615
13.3M
}
616
617
char const*
618
BaseHandle::type_name() const
619
5.33k
{
620
5.33k
    static constexpr std::array<char const*, 16> tn{
621
5.33k
        "uninitialized",
622
5.33k
        "reserved",
623
5.33k
        "null",
624
5.33k
        "boolean",
625
5.33k
        "integer",
626
5.33k
        "real",
627
5.33k
        "string",
628
5.33k
        "name",
629
5.33k
        "array",
630
5.33k
        "dictionary",
631
5.33k
        "stream",
632
5.33k
        "operator",
633
5.33k
        "inline-image",
634
5.33k
        "unresolved",
635
5.33k
        "destroyed",
636
5.33k
        "reference"};
637
5.33k
    return tn[type_code()];
638
5.33k
}
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
45.7k
{
655
45.7k
    return type_code() == ::ot_boolean;
656
45.7k
}
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
29.0k
{
669
29.0k
    return type_code() == ::ot_null;
670
29.0k
}
671
672
bool
673
QPDFObjectHandle::isInteger() const
674
177k
{
675
177k
    return type_code() == ::ot_integer;
676
177k
}
677
678
bool
679
QPDFObjectHandle::isReal() const
680
22.6k
{
681
22.6k
    return type_code() == ::ot_real;
682
22.6k
}
683
684
bool
685
QPDFObjectHandle::isNumber() const
686
25.8k
{
687
25.8k
    return (isInteger() || isReal());
688
25.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
87.6k
{
717
87.6k
    return type_code() == ::ot_name;
718
87.6k
}
719
720
bool
721
QPDFObjectHandle::isString() const
722
18.5k
{
723
18.5k
    return type_code() == ::ot_string;
724
18.5k
}
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
34.5k
{
741
34.5k
    return type_code() == ::ot_array;
742
34.5k
}
743
744
bool
745
QPDFObjectHandle::isDictionary() const
746
13.8M
{
747
13.8M
    return type_code() == ::ot_dictionary;
748
13.8M
}
749
750
bool
751
QPDFObjectHandle::isStream() const
752
13.9M
{
753
13.9M
    return type_code() == ::ot_stream;
754
13.9M
}
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.11k
{
765
1.11k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
766
1.11k
}
767
768
bool
769
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
770
59.9k
{
771
59.9k
    return Name(*this) == name;
772
59.9k
}
773
774
bool
775
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
776
407k
{
777
407k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
778
35.1k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
779
407k
}
780
781
bool
782
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
783
715k
{
784
715k
    return isStream() && getDict().isDictionaryOfType(type, subtype);
785
715k
}
786
787
// Bool accessors
788
789
bool
790
QPDFObjectHandle::getBoolValue() const
791
1
{
792
1
    if (auto boolean = as<QPDF_Bool>()) {
793
1
        return boolean->val;
794
1
    } else {
795
0
        typeWarning("boolean", "returning false");
796
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
797
0
        return false;
798
0
    }
799
1
}
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.23k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
815
1.23k
{
816
1.23k
}
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
118k
{
827
118k
    auto* i = as<QPDF_Integer>();
828
118k
    if (!i) {
829
1.50k
        throw invalid_error("Integer");
830
1.50k
    }
831
117k
    return i->val;
832
118k
}
833
834
long long
835
QPDFObjectHandle::getIntValue() const
836
14.4k
{
837
14.4k
    if (auto const integer = Integer(*this)) {
838
14.4k
        return integer;
839
14.4k
    } else {
840
0
        typeWarning("integer", "returning 0");
841
0
        return 0;
842
0
    }
843
14.4k
}
844
845
bool
846
QPDFObjectHandle::getValueAsInt(long long& value) const
847
7.68k
{
848
7.68k
    if (auto const integer = Integer(*this)) {
849
7.62k
        value = integer;
850
7.62k
        return true;
851
7.62k
    }
852
58
    return false;
853
7.68k
}
854
855
int
856
QPDFObjectHandle::getIntValueAsInt() const
857
44.0k
{
858
44.0k
    try {
859
44.0k
        return Integer(*this).value<int>();
860
44.0k
    } catch (std::invalid_argument&) {
861
15
        typeWarning("integer", "returning 0");
862
15
        return 0;
863
15
    }
864
44.0k
}
865
866
bool
867
QPDFObjectHandle::getValueAsInt(int& value) const
868
1.80k
{
869
1.80k
    if (!isInteger()) {
870
77
        return false;
871
77
    }
872
1.72k
    value = getIntValueAsInt();
873
1.72k
    return true;
874
1.80k
}
875
876
unsigned long long
877
QPDFObjectHandle::getUIntValue() const
878
29.1k
{
879
29.1k
    try {
880
29.1k
        return Integer(*this).value<unsigned long long>();
881
29.1k
    } catch (std::invalid_argument&) {
882
1.10k
        typeWarning("integer", "returning 0");
883
1.10k
        return 0;
884
1.10k
    }
885
29.1k
}
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
7.97k
{
900
7.97k
    try {
901
7.97k
        return Integer(*this).value<unsigned int>();
902
7.97k
    } catch (std::invalid_argument&) {
903
387
        typeWarning("integer", "returning 0");
904
387
        return 0;
905
387
    }
906
7.97k
}
907
908
bool
909
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
910
2.06k
{
911
2.06k
    if (!isInteger()) {
912
253
        return false;
913
253
    }
914
1.81k
    value = getUIntValueAsUInt();
915
1.81k
    return true;
916
2.06k
}
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
21.3k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
957
21.3k
{
958
21.3k
}
959
960
std::string const&
961
Name::value() const
962
174k
{
963
174k
    auto* n = as<QPDF_Name>();
964
174k
    if (!n) {
965
0
        throw invalid_error("Name");
966
0
    }
967
174k
    return n->name;
968
174k
}
969
970
std::string
971
QPDFObjectHandle::getName() const
972
20.2k
{
973
20.2k
    if (isName()) {
974
20.2k
        return obj->getStringValue();
975
20.2k
    } else {
976
0
        typeWarning("name", "returning dummy name");
977
0
        return "/QPDFFakeName";
978
0
    }
979
20.2k
}
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
104k
{
996
104k
    return {QPDFObject::create<QPDF_String>(str)};
997
104k
}
998
999
QPDFObjectHandle
1000
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1001
0
{
1002
0
    return {String::utf16(utf8_str).obj_sp()};
1003
0
}
1004
1005
String::String(std::string const& str) :
1006
0
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1007
0
{
1008
0
}
1009
1010
String::String(std::string&& str) :
1011
0
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1012
0
{
1013
0
}
1014
1015
String
1016
String::utf16(std::string const& utf8_str)
1017
0
{
1018
0
    std::string result;
1019
0
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1020
0
        return String(result);
1021
0
    }
1022
0
    return String(QUtil::utf8_to_utf16(utf8_str));
1023
0
}
1024
1025
std::string const&
1026
String::value() const
1027
6.18k
{
1028
6.18k
    auto* s = as<QPDF_String>();
1029
6.18k
    if (!s) {
1030
163
        throw invalid_error("String");
1031
163
    }
1032
6.02k
    return s->val;
1033
6.18k
}
1034
1035
std::string
1036
String::utf8_value() const
1037
0
{
1038
0
    auto* s = as<QPDF_String>();
1039
0
    if (!s) {
1040
0
        throw invalid_error("String");
1041
0
    }
1042
0
    if (util::is_utf16(s->val)) {
1043
0
        return QUtil::utf16_to_utf8(s->val);
1044
0
    }
1045
0
    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
0
        return s->val.substr(3);
1049
0
    }
1050
0
    return QUtil::pdf_doc_to_utf8(s->val);
1051
0
}
1052
1053
std::string
1054
QPDFObjectHandle::getStringValue() const
1055
6.18k
{
1056
6.18k
    try {
1057
6.18k
        return String(obj).value();
1058
6.18k
    } catch (std::invalid_argument&) {
1059
163
        typeWarning("string", "returning empty string");
1060
163
        return {};
1061
163
    }
1062
6.18k
}
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
0
{
1078
0
    try {
1079
0
        return String(obj).utf8_value();
1080
0
    } catch (std::invalid_argument&) {
1081
0
        typeWarning("string", "returning empty string");
1082
0
        return {};
1083
0
    }
1084
0
}
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
15.0k
{
1164
15.0k
    if (isNameAndEquals(value)) {
1165
158
        return true;
1166
14.8k
    } else if (isArray()) {
1167
30.8k
        for (auto& item: getArrayAsVector()) {
1168
30.8k
            if (item.isNameAndEquals(value)) {
1169
327
                return true;
1170
327
            }
1171
30.8k
        }
1172
4.57k
    }
1173
14.5k
    return false;
1174
15.0k
}
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
104k
{
1461
104k
    if (isIndirect()) {
1462
17
        return getObjGen().unparse(' ') + " R";
1463
104k
    } else {
1464
104k
        return unparseResolved();
1465
104k
    }
1466
104k
}
1467
1468
std::string
1469
QPDFObjectHandle::unparseResolved() const
1470
13.2M
{
1471
13.2M
    if (!obj) {
1472
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1473
0
    }
1474
13.2M
    return BaseHandle::unparse();
1475
13.2M
}
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
25
{
1537
25
    return parse(nullptr, object_str, object_description);
1538
25
}
1539
1540
QPDFObjectHandle
1541
QPDFObjectHandle::parse(
1542
    QPDF* context, std::string const& object_str, std::string const& object_description)
1543
25
{
1544
25
    auto input = is::OffsetBuffer("parsed object", object_str);
1545
25
    auto result = Parser::parse(input, object_description, context);
1546
25
    size_t offset = QIntC::to_size(input.tell());
1547
25
    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
25
    return result;
1560
25
}
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
8.06k
{
1754
8.06k
    return {QPDFObject::create<QPDF_Null>()};
1755
8.06k
}
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
30.9k
{
1766
30.9k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1767
30.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
7.73k
{
1790
7.73k
    return {QPDFObject::create<QPDF_Array>(items)};
1791
7.73k
}
1792
1793
QPDFObjectHandle
1794
QPDFObjectHandle::newArray(Rectangle const& rect)
1795
7.73k
{
1796
7.73k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1797
7.73k
}
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
15.7k
{
1844
15.7k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1845
15.7k
}
1846
1847
QPDFObjectHandle
1848
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1849
15.7k
{
1850
15.7k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1851
15.7k
}
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
58.1k
{
1895
58.1k
    return obj ? obj->getDescription() : ""s;
1896
58.1k
}
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
46.4k
{
1910
46.4k
    return obj && obj->hasDescription();
1911
46.4k
}
1912
1913
QPDFObjectHandle
1914
QPDFObjectHandle::shallowCopy()
1915
160
{
1916
160
    if (!obj) {
1917
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1918
0
    }
1919
160
    return {copy()};
1920
160
}
1921
1922
QPDFObjectHandle
1923
QPDFObjectHandle::unsafeShallowCopy()
1924
43.6k
{
1925
43.6k
    if (!obj) {
1926
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1927
0
    }
1928
43.6k
    return {copy(true)};
1929
43.6k
}
1930
1931
void
1932
QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
1933
43.8k
{
1934
43.8k
    assertInitialized();
1935
1936
43.8k
    auto cur_og = getObjGen();
1937
43.8k
    if (!visited.add(cur_og)) {
1938
20
        QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
1939
20
        throw std::runtime_error("loop detected while converting object from indirect to direct");
1940
20
    }
1941
1942
43.8k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
1943
33.4k
        obj = copy(true);
1944
33.4k
    } else if (auto a = as_array(strict)) {
1945
3.99k
        std::vector<QPDFObjectHandle> items;
1946
29.4k
        for (auto const& item: a) {
1947
29.4k
            items.emplace_back(item);
1948
29.4k
            items.back().makeDirect(visited, stop_at_streams);
1949
29.4k
        }
1950
3.99k
        obj = QPDFObject::create<QPDF_Array>(items);
1951
6.37k
    } else if (isDictionary()) {
1952
6.37k
        std::map<std::string, QPDFObjectHandle> items;
1953
18.1k
        for (auto const& [key, value]: as_dictionary(strict)) {
1954
18.1k
            if (!value.null()) {
1955
14.1k
                items.insert({key, value});
1956
14.1k
                items[key].makeDirect(visited, stop_at_streams);
1957
14.1k
            }
1958
18.1k
        }
1959
6.37k
        obj = QPDFObject::create<QPDF_Dictionary>(items);
1960
6.37k
    } else if (isStream()) {
1961
2
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
1962
2
        if (!stop_at_streams) {
1963
2
            throw std::runtime_error("attempt to make a stream into a direct object");
1964
2
        }
1965
2
    } 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
43.7k
    visited.erase(cur_og);
1973
43.7k
}
1974
1975
void
1976
QPDFObjectHandle::makeDirect(bool allow_streams)
1977
209
{
1978
209
    QPDFObjGen::set visited;
1979
209
    makeDirect(visited, allow_streams);
1980
209
}
1981
1982
void
1983
QPDFObjectHandle::assertInitialized() const
1984
43.8k
{
1985
43.8k
    if (!obj) {
1986
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1987
0
    }
1988
43.8k
}
1989
1990
std::invalid_argument
1991
BaseHandle::invalid_error(std::string const& method) const
1992
1.67k
{
1993
1.67k
    return std::invalid_argument(method + " operation attempted on invalid object");
1994
1.67k
}
1995
std::runtime_error
1996
BaseHandle::type_error(char const* expected_type) const
1997
52
{
1998
52
    return std::runtime_error(
1999
52
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2000
52
}
2001
2002
QPDFExc
2003
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2004
5.28k
{
2005
5.28k
    return {
2006
5.28k
        qpdf_e_object,
2007
5.28k
        "",
2008
5.28k
        description(),
2009
5.28k
        0,
2010
5.28k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2011
5.28k
            message};
2012
5.28k
}
2013
2014
void
2015
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2016
5.28k
{
2017
5.28k
    if (!obj) {
2018
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2019
0
    }
2020
5.28k
    warn(type_error(expected_type, message));
2021
5.28k
}
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
29
{
2032
29
    warn({qpdf_e_object, "", description(), 0, warning});
2033
29
}
2034
2035
void
2036
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2037
52
{
2038
52
    if (!istype) {
2039
52
        throw type_error(type_name);
2040
52
    }
2041
52
}
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
58.1k
{
2194
58.1k
    if (!qpdf()) {
2195
388
        throw std::move(e);
2196
388
    }
2197
57.7k
    qpdf()->warn(std::move(e));
2198
57.7k
}
2199
2200
void
2201
BaseHandle::warn(std::string const& warning) const
2202
58.7k
{
2203
58.7k
    if (qpdf()) {
2204
52.8k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2205
52.8k
    } else {
2206
5.92k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2207
5.92k
    }
2208
58.7k
}
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
27.5M
{
2378
27.5M
    return obj ? obj->getObjGen() : QPDFObjGen();
2379
27.5M
}
2380
2381
int
2382
QPDFObjectHandle::getObjectID() const
2383
13.2M
{
2384
13.2M
    return getObjGen().getObj();
2385
13.2M
}
2386
2387
int
2388
QPDFObjectHandle::getGeneration() const
2389
0
{
2390
0
    return getObjGen().getGen();
2391
0
}
2392
2393
bool
2394
QPDFObjectHandle::isIndirect() const
2395
126k
{
2396
126k
    return getObjectID() != 0;
2397
126k
}
2398
2399
// Indirect object accessors
2400
QPDF*
2401
QPDFObjectHandle::getOwningQPDF() const
2402
101k
{
2403
101k
    return obj ? obj->getQPDF() : nullptr;
2404
101k
}
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
30
{
2418
30
    if (obj) {
2419
30
        obj->setParsedOffset(offset);
2420
30
    }
2421
30
}
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
}