Coverage Report

Created: 2026-03-12 06:57

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
0
{
35
0
    return obj ? obj->getObjGen() : QPDFObjGen();
36
0
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
14.4k
    supports_retry(supports_retry)
47
14.4k
{
48
14.4k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
14.4k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
14.4k
}
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 referenced_object().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
1.46M
{
542
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
543
1.46M
    if (only_direct && indirect()) {
544
370k
        return;
545
370k
    }
546
547
1.09M
    switch (raw_type_code()) {
548
8.95k
    case ::ot_array:
549
8.95k
        {
550
8.95k
            auto& a = std::get<QPDF_Array>(obj->value);
551
8.95k
            if (a.sp) {
552
0
                for (auto& item: a.sp->elements) {
553
0
                    item.second.disconnect();
554
0
                }
555
8.95k
            } else {
556
1.42M
                for (auto& oh: a.elements) {
557
1.42M
                    oh.disconnect();
558
1.42M
                }
559
8.95k
            }
560
8.95k
        }
561
8.95k
        break;
562
6.91k
    case ::ot_dictionary:
563
29.5k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
564
29.5k
            iter.second.disconnect();
565
29.5k
        }
566
6.91k
        break;
567
2.80k
    case ::ot_stream:
568
2.80k
        {
569
2.80k
            auto& s = std::get<QPDF_Stream>(obj->value);
570
2.80k
            s.m->stream_provider = nullptr;
571
2.80k
            s.m->stream_dict.disconnect();
572
2.80k
        }
573
2.80k
        break;
574
0
    case ::ot_uninitialized:
575
0
        return;
576
1.07M
    default:
577
1.07M
        break;
578
1.09M
    }
579
1.09M
    obj->qpdf = nullptr;
580
1.09M
    obj->og = QPDFObjGen();
581
1.09M
}
582
583
std::string
584
QPDFObject::getStringValue() const
585
3.78k
{
586
3.78k
    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
3.78k
    case ::ot_name:
592
3.78k
        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
3.78k
    }
602
0
    return ""; // unreachable
603
3.78k
}
604
605
bool
606
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
607
0
{
608
0
    return obj == rhs.obj;
609
0
}
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
0
{
620
0
    static constexpr std::array<char const*, 16> tn{
621
0
        "uninitialized",
622
0
        "reserved",
623
0
        "null",
624
0
        "boolean",
625
0
        "integer",
626
0
        "real",
627
0
        "string",
628
0
        "name",
629
0
        "array",
630
0
        "dictionary",
631
0
        "stream",
632
0
        "operator",
633
0
        "inline-image",
634
0
        "unresolved",
635
0
        "destroyed",
636
0
        "reference"};
637
0
    return tn[type_code()];
638
0
}
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
0
{
655
0
    return type_code() == ::ot_boolean;
656
0
}
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
0
{
669
0
    return type_code() == ::ot_null;
670
0
}
671
672
bool
673
QPDFObjectHandle::isInteger() const
674
7.64k
{
675
7.64k
    return type_code() == ::ot_integer;
676
7.64k
}
677
678
bool
679
QPDFObjectHandle::isReal() const
680
0
{
681
0
    return type_code() == ::ot_real;
682
0
}
683
684
bool
685
QPDFObjectHandle::isNumber() const
686
0
{
687
0
    return (isInteger() || isReal());
688
0
}
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
3.78k
{
717
3.78k
    return type_code() == ::ot_name;
718
3.78k
}
719
720
bool
721
QPDFObjectHandle::isString() const
722
0
{
723
0
    return type_code() == ::ot_string;
724
0
}
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
0
{
741
0
    return type_code() == ::ot_array;
742
0
}
743
744
bool
745
QPDFObjectHandle::isDictionary() const
746
137k
{
747
137k
    return type_code() == ::ot_dictionary;
748
137k
}
749
750
bool
751
QPDFObjectHandle::isStream() const
752
140k
{
753
140k
    return type_code() == ::ot_stream;
754
140k
}
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
0
{
765
0
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
766
0
}
767
768
bool
769
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
770
0
{
771
0
    return Name(*this) == name;
772
0
}
773
774
bool
775
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
776
0
{
777
0
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
778
0
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
779
0
}
780
781
bool
782
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
783
0
{
784
0
    return isStream() && getDict().isDictionaryOfType(type, subtype);
785
0
}
786
787
// Bool accessors
788
789
bool
790
QPDFObjectHandle::getBoolValue() const
791
0
{
792
0
    if (auto boolean = as<QPDF_Bool>()) {
793
0
        return boolean->val;
794
0
    } else {
795
0
        typeWarning("boolean", "returning false");
796
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
797
0
        return false;
798
0
    }
799
0
}
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
0
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
815
0
{
816
0
}
817
818
QPDFObjectHandle
819
QPDFObjectHandle::newInteger(long long value)
820
1.09M
{
821
1.09M
    return {QPDFObject::create<QPDF_Integer>(value)};
822
1.09M
}
823
824
int64_t
825
Integer::value() const
826
7.64k
{
827
7.64k
    auto* i = as<QPDF_Integer>();
828
7.64k
    if (!i) {
829
0
        throw invalid_error("Integer");
830
0
    }
831
7.64k
    return i->val;
832
7.64k
}
833
834
long long
835
QPDFObjectHandle::getIntValue() const
836
0
{
837
0
    if (auto const integer = Integer(*this)) {
838
0
        return integer;
839
0
    } else {
840
0
        typeWarning("integer", "returning 0");
841
0
        return 0;
842
0
    }
843
0
}
844
845
bool
846
QPDFObjectHandle::getValueAsInt(long long& value) const
847
0
{
848
0
    if (auto const integer = Integer(*this)) {
849
0
        value = integer;
850
0
        return true;
851
0
    }
852
0
    return false;
853
0
}
854
855
int
856
QPDFObjectHandle::getIntValueAsInt() const
857
7.64k
{
858
7.64k
    try {
859
7.64k
        return Integer(*this).value<int>();
860
7.64k
    } catch (std::invalid_argument&) {
861
0
        typeWarning("integer", "returning 0");
862
0
        return 0;
863
0
    }
864
7.64k
}
865
866
bool
867
QPDFObjectHandle::getValueAsInt(int& value) const
868
0
{
869
0
    if (!isInteger()) {
870
0
        return false;
871
0
    }
872
0
    value = getIntValueAsInt();
873
0
    return true;
874
0
}
875
876
unsigned long long
877
QPDFObjectHandle::getUIntValue() const
878
0
{
879
0
    try {
880
0
        return Integer(*this).value<unsigned long long>();
881
0
    } catch (std::invalid_argument&) {
882
0
        typeWarning("integer", "returning 0");
883
0
        return 0;
884
0
    }
885
0
}
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
0
{
900
0
    try {
901
0
        return Integer(*this).value<unsigned int>();
902
0
    } catch (std::invalid_argument&) {
903
0
        typeWarning("integer", "returning 0");
904
0
        return 0;
905
0
    }
906
0
}
907
908
bool
909
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
910
0
{
911
0
    if (!isInteger()) {
912
0
        return false;
913
0
    }
914
0
    value = getUIntValueAsUInt();
915
0
    return true;
916
0
}
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
11.9k
{
947
11.9k
    return {QPDFObject::create<QPDF_Name>(name)};
948
11.9k
}
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
0
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
957
0
{
958
0
}
959
960
std::string const&
961
Name::value() const
962
0
{
963
0
    auto* n = as<QPDF_Name>();
964
0
    if (!n) {
965
0
        throw invalid_error("Name");
966
0
    }
967
0
    return n->name;
968
0
}
969
970
std::string
971
QPDFObjectHandle::getName() const
972
3.78k
{
973
3.78k
    if (isName()) {
974
3.78k
        return obj->getStringValue();
975
3.78k
    } else {
976
0
        typeWarning("name", "returning dummy name");
977
0
        return "/QPDFFakeName";
978
0
    }
979
3.78k
}
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
1.10k
{
996
1.10k
    return {QPDFObject::create<QPDF_String>(str)};
997
1.10k
}
998
999
QPDFObjectHandle
1000
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1001
5.29k
{
1002
5.29k
    return {String::utf16(utf8_str).obj_sp()};
1003
5.29k
}
1004
1005
String::String(std::string const& str) :
1006
1.10k
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1007
1.10k
{
1008
1.10k
}
1009
1010
String::String(std::string&& str) :
1011
4.18k
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1012
4.18k
{
1013
4.18k
}
1014
1015
String
1016
String::utf16(std::string const& utf8_str)
1017
5.29k
{
1018
5.29k
    std::string result;
1019
5.29k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1020
1.10k
        return String(result);
1021
1.10k
    }
1022
4.18k
    return String(QUtil::utf8_to_utf16(utf8_str));
1023
5.29k
}
1024
1025
std::string const&
1026
String::value() const
1027
0
{
1028
0
    auto* s = as<QPDF_String>();
1029
0
    if (!s) {
1030
0
        throw invalid_error("String");
1031
0
    }
1032
0
    return s->val;
1033
0
}
1034
1035
std::string
1036
String::utf8_value() const
1037
0
{
1038
0
    auto* s = as<QPDF_String>();
1039
0
    if (!s) {
1040
0
        throw invalid_error("String");
1041
0
    }
1042
0
    if (util::is_utf16(s->val)) {
1043
0
        return QUtil::utf16_to_utf8(s->val);
1044
0
    }
1045
0
    if (util::is_explicit_utf8(s->val)) {
1046
        // PDF 2.0 allows UTF-8 strings when explicitly prefixed with the three-byte representation
1047
        // of U+FEFF.
1048
0
        return s->val.substr(3);
1049
0
    }
1050
0
    return QUtil::pdf_doc_to_utf8(s->val);
1051
0
}
1052
1053
std::string
1054
QPDFObjectHandle::getStringValue() const
1055
0
{
1056
0
    try {
1057
0
        return String(obj).value();
1058
0
    } catch (std::invalid_argument&) {
1059
0
        typeWarning("string", "returning empty string");
1060
0
        return {};
1061
0
    }
1062
0
}
1063
1064
bool
1065
QPDFObjectHandle::getValueAsString(std::string& value) const
1066
0
{
1067
0
    try {
1068
0
        value = String(obj).value();
1069
0
        return true;
1070
0
    } catch (std::invalid_argument&) {
1071
0
        return false;
1072
0
    }
1073
0
}
1074
1075
std::string
1076
QPDFObjectHandle::getUTF8Value() const
1077
0
{
1078
0
    try {
1079
0
        return String(obj).utf8_value();
1080
0
    } catch (std::invalid_argument&) {
1081
0
        typeWarning("string", "returning empty string");
1082
0
        return {};
1083
0
    }
1084
0
}
1085
1086
bool
1087
QPDFObjectHandle::getValueAsUTF8(std::string& value) const
1088
0
{
1089
0
    try {
1090
0
        value = String(obj).utf8_value();
1091
0
        return true;
1092
0
    } catch (std::invalid_argument&) {
1093
0
        return false;
1094
0
    }
1095
0
}
1096
1097
// Operator and Inline Image accessors
1098
1099
std::string
1100
QPDFObjectHandle::getOperatorValue() const
1101
0
{
1102
0
    if (isOperator()) {
1103
0
        return obj->getStringValue();
1104
0
    } else {
1105
0
        typeWarning("operator", "returning fake value");
1106
0
        QTC::TC("qpdf", "QPDFObjectHandle operator returning fake value");
1107
0
        return "QPDFFAKE";
1108
0
    }
1109
0
}
1110
1111
bool
1112
QPDFObjectHandle::getValueAsOperator(std::string& value) const
1113
0
{
1114
0
    if (!isOperator()) {
1115
0
        return false;
1116
0
    }
1117
0
    value = obj->getStringValue();
1118
0
    return true;
1119
0
}
1120
1121
std::string
1122
QPDFObjectHandle::getInlineImageValue() const
1123
0
{
1124
0
    if (isInlineImage()) {
1125
0
        return obj->getStringValue();
1126
0
    } else {
1127
0
        typeWarning("inlineimage", "returning empty data");
1128
0
        QTC::TC("qpdf", "QPDFObjectHandle inlineimage returning empty data");
1129
0
        return "";
1130
0
    }
1131
0
}
1132
1133
bool
1134
QPDFObjectHandle::getValueAsInlineImage(std::string& value) const
1135
0
{
1136
0
    if (!isInlineImage()) {
1137
0
        return false;
1138
0
    }
1139
0
    value = obj->getStringValue();
1140
0
    return true;
1141
0
}
1142
1143
// Array accessors and mutators are in QPDF_Array.cc
1144
1145
QPDFObjectHandle::QPDFArrayItems
1146
QPDFObjectHandle::aitems()
1147
0
{
1148
0
    return *this;
1149
0
}
1150
1151
// Dictionary accessors are in QPDF_Dictionary.cc
1152
1153
QPDFObjectHandle::QPDFDictItems
1154
QPDFObjectHandle::ditems()
1155
0
{
1156
0
    return {*this};
1157
0
}
1158
1159
// Array and Name accessors
1160
1161
bool
1162
QPDFObjectHandle::isOrHasName(std::string const& value) const
1163
0
{
1164
0
    if (isNameAndEquals(value)) {
1165
0
        return true;
1166
0
    } 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
0
    return false;
1174
0
}
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
10.3k
{
1537
10.3k
    return parse(nullptr, object_str, object_description);
1538
10.3k
}
1539
1540
QPDFObjectHandle
1541
QPDFObjectHandle::parse(
1542
    QPDF* context, std::string const& object_str, std::string const& object_description)
1543
10.3k
{
1544
10.3k
    auto input = is::OffsetBuffer("parsed object", object_str);
1545
10.3k
    auto result = Parser::parse(input, object_description, context);
1546
10.3k
    size_t offset = QIntC::to_size(input.tell());
1547
1.05M
    while (offset < object_str.length()) {
1548
1.04M
        if (!isspace(object_str.at(offset))) {
1549
52
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1550
52
            throw QPDFExc(
1551
52
                qpdf_e_damaged_pdf,
1552
52
                "parsed object",
1553
52
                object_description,
1554
52
                input.getLastOffset(),
1555
52
                "trailing data found parsing object from string");
1556
52
        }
1557
1.04M
        ++offset;
1558
1.04M
    }
1559
10.3k
    return result;
1560
10.3k
}
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
999
{
1748
999
    return {QPDFObject::create<QPDF_Bool>(value)};
1749
999
}
1750
1751
QPDFObjectHandle
1752
QPDFObjectHandle::newNull()
1753
30.8k
{
1754
30.8k
    return {QPDFObject::create<QPDF_Null>()};
1755
30.8k
}
1756
1757
QPDFObjectHandle
1758
QPDFObjectHandle::newReal(std::string const& value)
1759
5.35k
{
1760
5.35k
    return {QPDFObject::create<QPDF_Real>(value)};
1761
5.35k
}
1762
1763
QPDFObjectHandle
1764
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1765
0
{
1766
0
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1767
0
}
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
11.5k
{
1784
11.5k
    return newArray(std::vector<QPDFObjectHandle>());
1785
11.5k
}
1786
1787
QPDFObjectHandle
1788
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1789
11.5k
{
1790
11.5k
    return {QPDFObject::create<QPDF_Array>(items)};
1791
11.5k
}
1792
1793
QPDFObjectHandle
1794
QPDFObjectHandle::newArray(Rectangle const& rect)
1795
0
{
1796
0
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1797
0
}
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
24.5k
{
1844
24.5k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1845
24.5k
}
1846
1847
QPDFObjectHandle
1848
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1849
24.5k
{
1850
24.5k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1851
24.5k
}
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
0
{
1895
0
    return obj ? obj->getDescription() : ""s;
1896
0
}
1897
1898
void
1899
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
1900
4.25k
{
1901
4.25k
    if (obj) {
1902
4.25k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
1903
4.25k
        obj->setDescription(owning_qpdf, descr);
1904
4.25k
    }
1905
4.25k
}
1906
1907
bool
1908
QPDFObjectHandle::hasObjectDescription() const
1909
1.75M
{
1910
1.75M
    return obj && obj->hasDescription();
1911
1.75M
}
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
0
{
1993
0
    return std::invalid_argument(method + " operation attempted on invalid object");
1994
0
}
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
0
{
2005
0
    return {
2006
0
        qpdf_e_object,
2007
0
        "",
2008
0
        description(),
2009
0
        0,
2010
0
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2011
0
            message};
2012
0
}
2013
2014
void
2015
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2016
0
{
2017
0
    if (!obj) {
2018
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2019
0
    }
2020
0
    warn(type_error(expected_type, message));
2021
0
}
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
0
{
2194
0
    if (!qpdf()) {
2195
0
        throw std::move(e);
2196
0
    }
2197
0
    qpdf()->warn(std::move(e));
2198
0
}
2199
2200
void
2201
BaseHandle::warn(std::string const& warning) const
2202
0
{
2203
0
    if (qpdf()) {
2204
0
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2205
0
    } else {
2206
0
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2207
0
    }
2208
0
}
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
70.8k
{
2378
70.8k
    return obj ? obj->getObjGen() : QPDFObjGen();
2379
70.8k
}
2380
2381
int
2382
QPDFObjectHandle::getObjectID() const
2383
37.6k
{
2384
37.6k
    return getObjGen().getObj();
2385
37.6k
}
2386
2387
int
2388
QPDFObjectHandle::getGeneration() const
2389
0
{
2390
0
    return getObjGen().getGen();
2391
0
}
2392
2393
bool
2394
QPDFObjectHandle::isIndirect() const
2395
37.6k
{
2396
37.6k
    return getObjectID() != 0;
2397
37.6k
}
2398
2399
// Indirect object accessors
2400
QPDF*
2401
QPDFObjectHandle::getOwningQPDF() const
2402
216k
{
2403
216k
    return obj ? obj->getQPDF() : nullptr;
2404
216k
}
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
0
{
2418
0
    if (obj) {
2419
0
        obj->setParsedOffset(offset);
2420
0
    }
2421
0
}
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
}