Coverage Report

Created: 2026-02-26 06:36

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