Coverage Report

Created: 2026-01-09 06:29

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