Coverage Report

Created: 2025-10-10 06:21

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