Coverage Report

Created: 2025-11-24 06:48

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
93.3k
{
33
93.3k
    return obj ? obj->getObjGen() : QPDFObjGen();
34
93.3k
}
35
36
namespace
37
{
38
    class TerminateParsing
39
    {
40
    };
41
} // namespace
42
43
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
44
24.3k
    supports_retry(supports_retry)
45
24.3k
{
46
24.3k
}
47
48
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
49
24.3k
{
50
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
51
24.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.37k
{
118
2.37k
}
119
120
void
121
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
122
5.16k
{
123
5.16k
    pipeline = p;
124
5.16k
}
125
126
void
127
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
128
57.4M
{
129
57.4M
    if (!pipeline) {
130
0
        return;
131
0
    }
132
57.4M
    if (len) {
133
57.4M
        pipeline->write(data, len);
134
57.4M
    }
135
57.4M
}
136
137
void
138
QPDFObjectHandle::TokenFilter::write(std::string const& str)
139
3.31M
{
140
3.31M
    write(str.c_str(), str.length());
141
3.31M
}
142
143
void
144
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
145
28.8M
{
146
28.8M
    std::string const& value = token.getRawValue();
147
28.8M
    write(value.c_str(), value.length());
148
28.8M
}
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
9.11M
{
226
9.11M
    if (name.empty()) {
227
0
        return name;
228
0
    }
229
9.11M
    std::string result;
230
9.11M
    result += name.at(0);
231
21.7M
    for (size_t i = 1; i < name.length(); ++i) {
232
12.6M
        char ch = name.at(i);
233
        // Don't use locale/ctype here; follow PDF spec guidelines.
234
12.6M
        if (ch == '\0') {
235
            // QPDFTokenizer embeds a null character to encode an invalid #.
236
96.2k
            result += "#";
237
12.5M
        } else if (
238
12.5M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
239
10.7M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
240
1.80M
            result += util::hex_encode_char(ch);
241
10.7M
        } else {
242
10.7M
            result += ch;
243
10.7M
        }
244
12.6M
    }
245
9.11M
    return result;
246
9.11M
}
247
248
std::shared_ptr<QPDFObject>
249
BaseHandle::copy(bool shallow) const
250
73.3k
{
251
73.3k
    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
10.1k
    case ::ot_null:
258
10.1k
        return QPDFObject::create<QPDF_Null>();
259
202
    case ::ot_boolean:
260
202
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
261
3.05k
    case ::ot_integer:
262
3.05k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
263
4.19k
    case ::ot_real:
264
4.19k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
265
383
    case ::ot_string:
266
383
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
267
10.2k
    case ::ot_name:
268
10.2k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
269
467
    case ::ot_array:
270
467
        {
271
467
            auto const& a = std::get<QPDF_Array>(obj->value);
272
467
            if (shallow) {
273
0
                return QPDFObject::create<QPDF_Array>(a);
274
467
            } else {
275
467
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
276
467
                if (a.sp) {
277
32
                    QPDF_Array result;
278
32
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
279
32
                    result.sp->size = a.sp->size;
280
4.68k
                    for (auto const& [idx, oh]: a.sp->elements) {
281
4.68k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
282
4.68k
                    }
283
32
                    return QPDFObject::create<QPDF_Array>(std::move(result));
284
435
                } else {
285
435
                    std::vector<QPDFObjectHandle> result;
286
435
                    result.reserve(a.elements.size());
287
2.28k
                    for (auto const& element: a.elements) {
288
2.28k
                        result.emplace_back(
289
2.28k
                            element ? (element.indirect() ? element : element.copy()) : element);
290
2.28k
                    }
291
435
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
292
435
                }
293
467
            }
294
467
        }
295
44.6k
    case ::ot_dictionary:
296
44.6k
        {
297
44.6k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
298
44.6k
            if (shallow) {
299
44.0k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
300
44.0k
            } else {
301
641
                std::map<std::string, QPDFObjectHandle> new_items;
302
2.79k
                for (auto const& [key, val]: d.items) {
303
2.79k
                    new_items[key] = val.indirect() ? val : val.copy();
304
2.79k
                }
305
641
                return QPDFObject::create<QPDF_Dictionary>(new_items);
306
641
            }
307
44.6k
        }
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
73.3k
    }
325
0
    return {}; // unreachable
326
73.3k
}
327
328
std::string
329
BaseHandle::unparse() const
330
8.78M
{
331
8.78M
    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
125k
    case ::ot_null:
339
125k
        return "null";
340
6.97k
    case ::ot_boolean:
341
6.97k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
342
206k
    case ::ot_integer:
343
206k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
344
65.1k
    case ::ot_real:
345
65.1k
        return std::get<QPDF_Real>(obj->value).val;
346
162k
    case ::ot_string:
347
162k
        return std::get<QPDF_String>(obj->value).unparse(false);
348
8.21M
    case ::ot_name:
349
8.21M
        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
8.78M
    }
401
0
    return {}; // unreachable
402
8.78M
}
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
9.71M
{
540
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
541
9.71M
    if (only_direct && indirect()) {
542
152k
        return;
543
152k
    }
544
545
9.56M
    switch (raw_type_code()) {
546
90.0k
    case ::ot_array:
547
90.0k
        {
548
90.0k
            auto& a = std::get<QPDF_Array>(obj->value);
549
90.0k
            if (a.sp) {
550
7.32M
                for (auto& item: a.sp->elements) {
551
7.32M
                    item.second.disconnect();
552
7.32M
                }
553
89.6k
            } else {
554
1.29M
                for (auto& oh: a.elements) {
555
1.29M
                    oh.disconnect();
556
1.29M
                }
557
89.6k
            }
558
90.0k
        }
559
90.0k
        break;
560
163k
    case ::ot_dictionary:
561
669k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
562
669k
            iter.second.disconnect();
563
669k
        }
564
163k
        break;
565
39.2k
    case ::ot_stream:
566
39.2k
        {
567
39.2k
            auto& s = std::get<QPDF_Stream>(obj->value);
568
39.2k
            s.m->stream_provider = nullptr;
569
39.2k
            s.m->stream_dict.disconnect();
570
39.2k
        }
571
39.2k
        break;
572
0
    case ::ot_uninitialized:
573
0
        return;
574
9.26M
    default:
575
9.26M
        break;
576
9.56M
    }
577
9.56M
    obj->qpdf = nullptr;
578
9.56M
    obj->og = QPDFObjGen();
579
9.56M
}
580
581
std::string
582
QPDFObject::getStringValue() const
583
21.8k
{
584
21.8k
    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.8k
    case ::ot_name:
590
21.8k
        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.8k
    }
600
0
    return ""; // unreachable
601
21.8k
}
602
603
bool
604
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
605
242
{
606
242
    return obj == rhs.obj;
607
242
}
608
609
qpdf_object_type_e
610
QPDFObjectHandle::getTypeCode() const
611
8.82M
{
612
8.82M
    return type_code();
613
8.82M
}
614
615
char const*
616
BaseHandle::type_name() const
617
5.04k
{
618
5.04k
    static constexpr std::array<char const*, 16> tn{
619
5.04k
        "uninitialized",
620
5.04k
        "reserved",
621
5.04k
        "null",
622
5.04k
        "boolean",
623
5.04k
        "integer",
624
5.04k
        "real",
625
5.04k
        "string",
626
5.04k
        "name",
627
5.04k
        "array",
628
5.04k
        "dictionary",
629
5.04k
        "stream",
630
5.04k
        "operator",
631
5.04k
        "inline-image",
632
5.04k
        "unresolved",
633
5.04k
        "destroyed",
634
5.04k
        "reference"};
635
5.04k
    return tn[type_code()];
636
5.04k
}
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
30.2k
{
653
30.2k
    return type_code() == ::ot_boolean;
654
30.2k
}
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
22.4k
{
667
22.4k
    return type_code() == ::ot_null;
668
22.4k
}
669
670
bool
671
QPDFObjectHandle::isInteger() const
672
146k
{
673
146k
    return type_code() == ::ot_integer;
674
146k
}
675
676
bool
677
QPDFObjectHandle::isReal() const
678
20.1k
{
679
20.1k
    return type_code() == ::ot_real;
680
20.1k
}
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
77.2k
{
715
77.2k
    return type_code() == ::ot_name;
716
77.2k
}
717
718
bool
719
QPDFObjectHandle::isString() const
720
16.2k
{
721
16.2k
    return type_code() == ::ot_string;
722
16.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
32.9k
{
739
32.9k
    return type_code() == ::ot_array;
740
32.9k
}
741
742
bool
743
QPDFObjectHandle::isDictionary() const
744
9.39M
{
745
9.39M
    return type_code() == ::ot_dictionary;
746
9.39M
}
747
748
bool
749
QPDFObjectHandle::isStream() const
750
10.1M
{
751
10.1M
    return type_code() == ::ot_stream;
752
10.1M
}
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.02k
{
763
1.02k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
764
1.02k
}
765
766
bool
767
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
768
33.3k
{
769
33.3k
    return Name(*this) == name;
770
33.3k
}
771
772
bool
773
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
774
402k
{
775
402k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
776
33.3k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
777
402k
}
778
779
bool
780
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
781
1.45M
{
782
1.45M
    return isStream() && getDict().isDictionaryOfType(type, subtype);
783
1.45M
}
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.16k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
813
1.16k
{
814
1.16k
}
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
110k
{
825
110k
    auto* i = as<QPDF_Integer>();
826
110k
    if (!i) {
827
1.39k
        throw invalid_error("Integer");
828
1.39k
    }
829
108k
    return i->val;
830
110k
}
831
832
long long
833
QPDFObjectHandle::getIntValue() const
834
9.05k
{
835
9.05k
    if (auto const integer = Integer(*this)) {
836
9.05k
        return integer;
837
9.05k
    } else {
838
0
        typeWarning("integer", "returning 0");
839
0
        return 0;
840
0
    }
841
9.05k
}
842
843
bool
844
QPDFObjectHandle::getValueAsInt(long long& value) const
845
6.87k
{
846
6.87k
    if (auto const integer = Integer(*this)) {
847
6.82k
        value = integer;
848
6.82k
        return true;
849
6.82k
    }
850
47
    return false;
851
6.87k
}
852
853
int
854
QPDFObjectHandle::getIntValueAsInt() const
855
39.5k
{
856
39.5k
    try {
857
39.5k
        return Integer(*this).value<int>();
858
39.5k
    } catch (std::invalid_argument&) {
859
11
        typeWarning("integer", "returning 0");
860
11
        return 0;
861
11
    }
862
39.5k
}
863
864
bool
865
QPDFObjectHandle::getValueAsInt(int& value) const
866
1.62k
{
867
1.62k
    if (!isInteger()) {
868
48
        return false;
869
48
    }
870
1.57k
    value = getIntValueAsInt();
871
1.57k
    return true;
872
1.62k
}
873
874
unsigned long long
875
QPDFObjectHandle::getUIntValue() const
876
30.2k
{
877
30.2k
    try {
878
30.2k
        return Integer(*this).value<unsigned long long>();
879
30.2k
    } catch (std::invalid_argument&) {
880
1.06k
        typeWarning("integer", "returning 0");
881
1.06k
        return 0;
882
1.06k
    }
883
30.2k
}
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.32k
{
898
7.32k
    try {
899
7.32k
        return Integer(*this).value<unsigned int>();
900
7.32k
    } catch (std::invalid_argument&) {
901
312
        typeWarning("integer", "returning 0");
902
312
        return 0;
903
312
    }
904
7.32k
}
905
906
bool
907
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
908
1.86k
{
909
1.86k
    if (!isInteger()) {
910
243
        return false;
911
243
    }
912
1.62k
    value = getUIntValueAsUInt();
913
1.62k
    return true;
914
1.86k
}
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
16.1k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
955
16.1k
{
956
16.1k
}
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
21.7k
{
971
21.7k
    if (isName()) {
972
21.7k
        return obj->getStringValue();
973
21.7k
    } else {
974
0
        typeWarning("name", "returning dummy name");
975
0
        return "/QPDFFakeName";
976
0
    }
977
21.7k
}
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
145k
{
994
145k
    return {QPDFObject::create<QPDF_String>(str)};
995
145k
}
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.10k
{
1026
6.10k
    auto* s = as<QPDF_String>();
1027
6.10k
    if (!s) {
1028
187
        throw invalid_error("String");
1029
187
    }
1030
5.91k
    return s->val;
1031
6.10k
}
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.10k
{
1054
6.10k
    try {
1055
6.10k
        return String(obj).value();
1056
6.10k
    } catch (std::invalid_argument&) {
1057
187
        typeWarning("string", "returning empty string");
1058
187
        return {};
1059
187
    }
1060
6.10k
}
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.6k
{
1162
14.6k
    if (isNameAndEquals(value)) {
1163
89
        return true;
1164
14.6k
    } else if (isArray()) {
1165
13.6k
        for (auto& item: getArrayAsVector()) {
1166
13.6k
            if (item.isNameAndEquals(value)) {
1167
275
                return true;
1168
275
            }
1169
13.6k
        }
1170
4.27k
    }
1171
14.3k
    return false;
1172
14.6k
}
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
145k
{
1459
145k
    if (isIndirect()) {
1460
13
        return getObjGen().unparse(' ') + " R";
1461
145k
    } else {
1462
145k
        return unparseResolved();
1463
145k
    }
1464
145k
}
1465
1466
std::string
1467
QPDFObjectHandle::unparseResolved() const
1468
8.78M
{
1469
8.78M
    if (!obj) {
1470
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1471
0
    }
1472
8.78M
    return BaseHandle::unparse();
1473
8.78M
}
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
51
{
1535
51
    return parse(nullptr, object_str, object_description);
1536
51
}
1537
1538
QPDFObjectHandle
1539
QPDFObjectHandle::parse(
1540
    QPDF* context, std::string const& object_str, std::string const& object_description)
1541
51
{
1542
51
    auto input = is::OffsetBuffer("parsed object", object_str);
1543
51
    auto result = QPDFParser::parse(input, object_description, context);
1544
51
    size_t offset = QIntC::to_size(input.tell());
1545
51
    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
51
    return result;
1558
51
}
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
6.38k
{
1752
6.38k
    return {QPDFObject::create<QPDF_Null>()};
1753
6.38k
}
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
27.9k
{
1764
27.9k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1765
27.9k
}
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
6.97k
{
1788
6.97k
    return {QPDFObject::create<QPDF_Array>(items)};
1789
6.97k
}
1790
1791
QPDFObjectHandle
1792
QPDFObjectHandle::newArray(Rectangle const& rect)
1793
6.97k
{
1794
6.97k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1795
6.97k
}
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
14.6k
{
1842
14.6k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1843
14.6k
}
1844
1845
QPDFObjectHandle
1846
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1847
14.6k
{
1848
14.6k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1849
14.6k
}
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
54.8k
{
1893
54.8k
    return obj ? obj->getDescription() : ""s;
1894
54.8k
}
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
45.6k
{
1908
45.6k
    return obj && obj->hasDescription();
1909
45.6k
}
1910
1911
QPDFObjectHandle
1912
QPDFObjectHandle::shallowCopy()
1913
134
{
1914
134
    if (!obj) {
1915
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1916
0
    }
1917
134
    return {copy()};
1918
134
}
1919
1920
QPDFObjectHandle
1921
QPDFObjectHandle::unsafeShallowCopy()
1922
44.0k
{
1923
44.0k
    if (!obj) {
1924
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1925
0
    }
1926
44.0k
    return {copy(true)};
1927
44.0k
}
1928
1929
void
1930
QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
1931
28.4k
{
1932
28.4k
    assertInitialized();
1933
1934
28.4k
    auto cur_og = getObjGen();
1935
28.4k
    if (!visited.add(cur_og)) {
1936
17
        QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
1937
17
        throw std::runtime_error("loop detected while converting object from indirect to direct");
1938
17
    }
1939
1940
28.4k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
1941
20.6k
        obj = copy(true);
1942
20.6k
    } else if (auto a = as_array(strict)) {
1943
3.32k
        std::vector<QPDFObjectHandle> items;
1944
18.8k
        for (auto const& item: a) {
1945
18.8k
            items.emplace_back(item);
1946
18.8k
            items.back().makeDirect(visited, stop_at_streams);
1947
18.8k
        }
1948
3.32k
        obj = QPDFObject::create<QPDF_Array>(items);
1949
4.51k
    } else if (isDictionary()) {
1950
4.51k
        std::map<std::string, QPDFObjectHandle> items;
1951
11.9k
        for (auto const& [key, value]: as_dictionary(strict)) {
1952
11.9k
            if (!value.null()) {
1953
9.45k
                items.insert({key, value});
1954
9.45k
                items[key].makeDirect(visited, stop_at_streams);
1955
9.45k
            }
1956
11.9k
        }
1957
4.51k
        obj = QPDFObject::create<QPDF_Dictionary>(items);
1958
4.51k
    } else if (isStream()) {
1959
3
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
1960
3
        if (!stop_at_streams) {
1961
3
            throw std::runtime_error("attempt to make a stream into a direct object");
1962
3
        }
1963
3
    } 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
28.4k
    visited.erase(cur_og);
1971
28.4k
}
1972
1973
void
1974
QPDFObjectHandle::makeDirect(bool allow_streams)
1975
198
{
1976
198
    QPDFObjGen::set visited;
1977
198
    makeDirect(visited, allow_streams);
1978
198
}
1979
1980
void
1981
QPDFObjectHandle::assertInitialized() const
1982
28.4k
{
1983
28.4k
    if (!obj) {
1984
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1985
0
    }
1986
28.4k
}
1987
1988
std::invalid_argument
1989
BaseHandle::invalid_error(std::string const& method) const
1990
1.57k
{
1991
1.57k
    return std::invalid_argument(method + " operation attempted on invalid object");
1992
1.57k
}
1993
std::runtime_error
1994
BaseHandle::type_error(char const* expected_type) const
1995
46
{
1996
46
    return std::runtime_error(
1997
46
        "operation for "s + expected_type + " attempted on object of type " + type_name());
1998
46
}
1999
2000
QPDFExc
2001
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2002
4.99k
{
2003
4.99k
    return {
2004
4.99k
        qpdf_e_object,
2005
4.99k
        "",
2006
4.99k
        description(),
2007
4.99k
        0,
2008
4.99k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2009
4.99k
            message};
2010
4.99k
}
2011
2012
void
2013
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2014
4.99k
{
2015
4.99k
    if (!obj) {
2016
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2017
0
    }
2018
4.99k
    warn(type_error(expected_type, message));
2019
4.99k
}
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
19
{
2030
19
    warn({qpdf_e_object, "", description(), 0, warning});
2031
19
}
2032
2033
void
2034
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2035
46
{
2036
46
    if (!istype) {
2037
46
        throw type_error(type_name);
2038
46
    }
2039
46
}
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
54.8k
{
2192
54.8k
    if (!qpdf()) {
2193
416
        throw std::move(e);
2194
416
    }
2195
54.4k
    qpdf()->warn(std::move(e));
2196
54.4k
}
2197
2198
void
2199
BaseHandle::warn(std::string const& warning) const
2200
61.8k
{
2201
61.8k
    if (qpdf()) {
2202
49.8k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2203
49.8k
    } else {
2204
12.0k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2205
12.0k
    }
2206
61.8k
}
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
19.2M
{
2376
19.2M
    return obj ? obj->getObjGen() : QPDFObjGen();
2377
19.2M
}
2378
2379
int
2380
QPDFObjectHandle::getObjectID() const
2381
8.80M
{
2382
8.80M
    return getObjGen().getObj();
2383
8.80M
}
2384
2385
int
2386
QPDFObjectHandle::getGeneration() const
2387
0
{
2388
0
    return getObjGen().getGen();
2389
0
}
2390
2391
bool
2392
QPDFObjectHandle::isIndirect() const
2393
167k
{
2394
167k
    return getObjectID() != 0;
2395
167k
}
2396
2397
// Indirect object accessors
2398
QPDF*
2399
QPDFObjectHandle::getOwningQPDF() const
2400
95.0k
{
2401
95.0k
    return obj ? obj->getQPDF() : nullptr;
2402
95.0k
}
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
10
{
2416
10
    if (obj) {
2417
10
        obj->setParsedOffset(offset);
2418
10
    }
2419
10
}
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
}