Coverage Report

Created: 2025-10-10 06:17

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
238k
{
31
238k
    return obj ? obj->getObjGen() : QPDFObjGen();
32
238k
}
33
34
namespace
35
{
36
    class TerminateParsing
37
    {
38
    };
39
} // namespace
40
41
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
42
26.3k
    supports_retry(supports_retry)
43
26.3k
{
44
26.3k
}
45
46
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
47
26.3k
{
48
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
49
26.3k
}
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
2.91k
{
82
2.91k
    return supports_retry;
83
2.91k
}
84
85
namespace
86
{
87
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
88
    {
89
      public:
90
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
91
2.91k
            containing_page(containing_page),
92
2.91k
            old_contents(old_contents)
93
2.91k
        {
94
2.91k
        }
95
2.91k
        ~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
2.91k
{
107
2.91k
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
108
2.91k
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
109
2.91k
    std::string all_description;
110
2.91k
    old_contents.pipeContentStreams(p, description, all_description);
111
2.91k
}
112
113
void
114
QPDFObjectHandle::TokenFilter::handleEOF()
115
4.59k
{
116
4.59k
}
117
118
void
119
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
120
9.18k
{
121
9.18k
    pipeline = p;
122
9.18k
}
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
387k
{
177
387k
    int tail = 0;       // Number of continuation characters expected.
178
387k
    bool tail2 = false; // Potential overlong 3 octet utf-8.
179
387k
    bool tail3 = false; // potential overlong 4 octet
180
387k
    bool needs_escaping = false;
181
2.96M
    for (auto const& it: name) {
182
2.96M
        auto c = static_cast<unsigned char>(it);
183
2.96M
        if (tail) {
184
11.0k
            if ((c & 0xc0) != 0x80) {
185
4.99k
                return {false, false};
186
4.99k
            }
187
6.09k
            if (tail2) {
188
420
                if ((c & 0xe0) == 0x80) {
189
116
                    return {false, false};
190
116
                }
191
304
                tail2 = false;
192
5.67k
            } else if (tail3) {
193
804
                if ((c & 0xf0) == 0x80) {
194
388
                    return {false, false};
195
388
                }
196
416
                tail3 = false;
197
416
            }
198
5.59k
            tail--;
199
2.95M
        } else if (c < 0x80) {
200
2.93M
            if (!needs_escaping) {
201
2.60M
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
202
2.60M
            }
203
2.93M
        } else if ((c & 0xe0) == 0xc0) {
204
5.09k
            if ((c & 0xfe) == 0xc0) {
205
631
                return {false, false};
206
631
            }
207
4.45k
            tail = 1;
208
20.7k
        } else if ((c & 0xf0) == 0xe0) {
209
1.75k
            tail2 = (c == 0xe0);
210
1.75k
            tail = 2;
211
18.9k
        } else if ((c & 0xf8) == 0xf0) {
212
2.30k
            tail3 = (c == 0xf0);
213
2.30k
            tail = 3;
214
16.6k
        } else {
215
16.6k
            return {false, false};
216
16.6k
        }
217
2.96M
    }
218
364k
    return {tail == 0, !needs_escaping};
219
387k
}
220
221
std::string
222
Name::normalize(std::string const& name)
223
89.3k
{
224
89.3k
    if (name.empty()) {
225
0
        return name;
226
0
    }
227
89.3k
    std::string result;
228
89.3k
    result += name.at(0);
229
46.3M
    for (size_t i = 1; i < name.length(); ++i) {
230
46.2M
        char ch = name.at(i);
231
        // Don't use locale/ctype here; follow PDF spec guidelines.
232
46.2M
        if (ch == '\0') {
233
            // QPDFTokenizer embeds a null character to encode an invalid #.
234
8.36k
            result += "#";
235
46.2M
        } else if (
236
46.2M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
237
45.5M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
238
45.5M
            result += util::hex_encode_char(ch);
239
45.5M
        } else {
240
662k
            result += ch;
241
662k
        }
242
46.2M
    }
243
89.3k
    return result;
244
89.3k
}
245
246
std::shared_ptr<QPDFObject>
247
BaseHandle::copy(bool shallow) const
248
471k
{
249
471k
    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
76.0k
    case ::ot_null:
256
76.0k
        return QPDFObject::create<QPDF_Null>();
257
2.18k
    case ::ot_boolean:
258
2.18k
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
259
48.1k
    case ::ot_integer:
260
48.1k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
261
22.6k
    case ::ot_real:
262
22.6k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
263
49.2k
    case ::ot_string:
264
49.2k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
265
210k
    case ::ot_name:
266
210k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
267
30.4k
    case ::ot_array:
268
30.4k
        {
269
30.4k
            auto const& a = std::get<QPDF_Array>(obj->value);
270
30.4k
            if (shallow) {
271
0
                return QPDFObject::create<QPDF_Array>(a);
272
30.4k
            } else {
273
30.4k
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
274
30.4k
                if (a.sp) {
275
301
                    QPDF_Array result;
276
301
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
277
301
                    result.sp->size = a.sp->size;
278
3.17k
                    for (auto const& [idx, oh]: a.sp->elements) {
279
3.17k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
280
3.17k
                    }
281
301
                    return QPDFObject::create<QPDF_Array>(std::move(result));
282
30.1k
                } else {
283
30.1k
                    std::vector<QPDFObjectHandle> result;
284
30.1k
                    result.reserve(a.elements.size());
285
310k
                    for (auto const& element: a.elements) {
286
310k
                        result.emplace_back(
287
310k
                            element ? (element.indirect() ? element : element.copy()) : element);
288
310k
                    }
289
30.1k
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
290
30.1k
                }
291
30.4k
            }
292
30.4k
        }
293
32.0k
    case ::ot_dictionary:
294
32.0k
        {
295
32.0k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
296
32.0k
            if (shallow) {
297
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
298
32.0k
            } else {
299
32.0k
                std::map<std::string, QPDFObjectHandle> new_items;
300
230k
                for (auto const& [key, val]: d.items) {
301
230k
                    new_items[key] = val.indirect() ? val : val.copy();
302
230k
                }
303
32.0k
                return QPDFObject::create<QPDF_Dictionary>(new_items);
304
32.0k
            }
305
32.0k
        }
306
6
    case ::ot_stream:
307
6
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
308
6
        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
471k
    }
323
0
    return {}; // unreachable
324
471k
}
325
326
std::string
327
BaseHandle::unparse() const
328
133k
{
329
133k
    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
9.88k
    case ::ot_null:
337
9.88k
        return "null";
338
286
    case ::ot_boolean:
339
286
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
340
21.6k
    case ::ot_integer:
341
21.6k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
342
1.64k
    case ::ot_real:
343
1.64k
        return std::get<QPDF_Real>(obj->value).val;
344
34.3k
    case ::ot_string:
345
34.3k
        return std::get<QPDF_String>(obj->value).unparse(false);
346
65.9k
    case ::ot_name:
347
65.9k
        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
133k
    }
399
0
    return {}; // unreachable
400
133k
}
401
402
void
403
BaseHandle::write_json(int json_version, JSON::Writer& p) const
404
460k
{
405
460k
    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
13.7k
    case ::ot_null:
411
13.7k
    case ::ot_operator:
412
13.7k
    case ::ot_inlineimage:
413
13.7k
        p << "null";
414
13.7k
        break;
415
7.39k
    case ::ot_boolean:
416
7.39k
        p << std::get<QPDF_Bool>(obj->value).val;
417
7.39k
        break;
418
66.1k
    case ::ot_integer:
419
66.1k
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
420
66.1k
        break;
421
34.7k
    case ::ot_real:
422
34.7k
        {
423
34.7k
            auto const& val = std::get<QPDF_Real>(obj->value).val;
424
34.7k
            if (val.empty()) {
425
                // Can't really happen...
426
0
                p << "0";
427
34.7k
            } else if (val.at(0) == '.') {
428
952
                p << "0" << val;
429
33.7k
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
430
1.71k
                p << "-0." << val.substr(2);
431
32.0k
            } else {
432
32.0k
                p << val;
433
32.0k
            }
434
34.7k
            if (val.back() == '.') {
435
733
                p << "0";
436
733
            }
437
34.7k
        }
438
34.7k
        break;
439
31.9k
    case ::ot_string:
440
31.9k
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
441
31.9k
        break;
442
204k
    case ::ot_name:
443
204k
        {
444
204k
            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
204k
            if (json_version == 1) {
448
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
449
204k
            } else {
450
204k
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
451
191k
                    if (res.second) {
452
186k
                        p << "\"" << n.name << "\"";
453
186k
                    } else {
454
4.54k
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
455
4.54k
                    }
456
191k
                } else {
457
13.0k
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
458
13.0k
                }
459
204k
            }
460
204k
        }
461
204k
        break;
462
46.0k
    case ::ot_array:
463
46.0k
        {
464
46.0k
            auto const& a = std::get<QPDF_Array>(obj->value);
465
46.0k
            p.writeStart('[');
466
46.0k
            if (a.sp) {
467
358
                size_t next = 0;
468
5.13k
                for (auto& [key, value]: a.sp->elements) {
469
32.3k
                    for (size_t j = next; j < key; ++j) {
470
27.2k
                        p.writeNext() << "null";
471
27.2k
                    }
472
5.13k
                    p.writeNext();
473
5.13k
                    auto item_og = value.getObj()->getObjGen();
474
5.13k
                    if (item_og.isIndirect()) {
475
472
                        p << "\"" << item_og.unparse(' ') << " R\"";
476
4.66k
                    } else {
477
4.66k
                        value.write_json(json_version, p);
478
4.66k
                    }
479
5.13k
                    next = key + 1;
480
5.13k
                }
481
22.1k
                for (size_t j = next; j < a.sp->size; ++j) {
482
21.8k
                    p.writeNext() << "null";
483
21.8k
                }
484
45.6k
            } else {
485
306k
                for (auto const& item: a.elements) {
486
306k
                    p.writeNext();
487
306k
                    auto item_og = item.id_gen();
488
306k
                    if (item_og.isIndirect()) {
489
12.5k
                        p << "\"" << item_og.unparse(' ') << " R\"";
490
293k
                    } else {
491
293k
                        item.write_json(json_version, p);
492
293k
                    }
493
306k
                }
494
45.6k
            }
495
46.0k
            p.writeEnd(']');
496
46.0k
        }
497
46.0k
        break;
498
56.6k
    case ::ot_dictionary:
499
56.6k
        {
500
56.6k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
501
56.6k
            p.writeStart('{');
502
236k
            for (auto& iter: d.items) {
503
236k
                if (!iter.second.null()) {
504
183k
                    p.writeNext();
505
183k
                    if (json_version == 1) {
506
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
507
0
                          << "\": ";
508
183k
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
509
172k
                        if (res.second) {
510
169k
                            p << "\"" << iter.first << "\": ";
511
169k
                        } else {
512
2.87k
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
513
2.87k
                        }
514
172k
                    } else {
515
10.3k
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
516
10.3k
                          << "\": ";
517
10.3k
                    }
518
183k
                    iter.second.writeJSON(json_version, p);
519
183k
                }
520
236k
            }
521
56.6k
            p.writeEnd('}');
522
56.6k
        }
523
56.6k
        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
460k
    }
533
460k
}
534
535
void
536
BaseHandle::disconnect(bool only_direct)
537
2.74M
{
538
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
539
2.74M
    if (only_direct && indirect()) {
540
201k
        return;
541
201k
    }
542
543
2.54M
    switch (raw_type_code()) {
544
153k
    case ::ot_array:
545
153k
        {
546
153k
            auto& a = std::get<QPDF_Array>(obj->value);
547
153k
            if (a.sp) {
548
45.2k
                for (auto& item: a.sp->elements) {
549
45.2k
                    item.second.disconnect();
550
45.2k
                }
551
152k
            } else {
552
1.18M
                for (auto& oh: a.elements) {
553
1.18M
                    oh.disconnect();
554
1.18M
                }
555
152k
            }
556
153k
        }
557
153k
        break;
558
247k
    case ::ot_dictionary:
559
1.05M
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
560
1.05M
            iter.second.disconnect();
561
1.05M
        }
562
247k
        break;
563
33.7k
    case ::ot_stream:
564
33.7k
        {
565
33.7k
            auto& s = std::get<QPDF_Stream>(obj->value);
566
33.7k
            s.m->stream_provider = nullptr;
567
33.7k
            s.m->stream_dict.disconnect();
568
33.7k
        }
569
33.7k
        break;
570
0
    case ::ot_uninitialized:
571
0
        return;
572
2.10M
    default:
573
2.10M
        break;
574
2.54M
    }
575
2.54M
    obj->qpdf = nullptr;
576
2.54M
    obj->og = QPDFObjGen();
577
2.54M
}
578
579
std::string
580
QPDFObject::getStringValue() const
581
390k
{
582
390k
    switch (getResolvedTypeCode()) {
583
57.5k
    case ::ot_real:
584
57.5k
        return std::get<QPDF_Real>(value).val;
585
3.33k
    case ::ot_string:
586
3.33k
        return std::get<QPDF_String>(value).val;
587
21.4k
    case ::ot_name:
588
21.4k
        return std::get<QPDF_Name>(value).name;
589
308k
    case ::ot_operator:
590
308k
        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
390k
    }
598
0
    return ""; // unreachable
599
390k
}
600
601
bool
602
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
603
13
{
604
13
    return obj == rhs.obj;
605
13
}
606
607
qpdf_object_type_e
608
QPDFObjectHandle::getTypeCode() const
609
0
{
610
0
    return type_code();
611
0
}
612
613
char const*
614
BaseHandle::type_name() const
615
23.5k
{
616
23.5k
    static constexpr std::array<char const*, 16> tn{
617
23.5k
        "uninitialized",
618
23.5k
        "reserved",
619
23.5k
        "null",
620
23.5k
        "boolean",
621
23.5k
        "integer",
622
23.5k
        "real",
623
23.5k
        "string",
624
23.5k
        "name",
625
23.5k
        "array",
626
23.5k
        "dictionary",
627
23.5k
        "stream",
628
23.5k
        "operator",
629
23.5k
        "inline-image",
630
23.5k
        "unresolved",
631
23.5k
        "destroyed",
632
23.5k
        "reference"};
633
23.5k
    return tn[type_code()];
634
23.5k
}
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
153k
{
651
153k
    return type_code() == ::ot_boolean;
652
153k
}
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
73.2k
{
665
73.2k
    return type_code() == ::ot_null;
666
73.2k
}
667
668
bool
669
QPDFObjectHandle::isInteger() const
670
496k
{
671
496k
    return type_code() == ::ot_integer;
672
496k
}
673
674
bool
675
QPDFObjectHandle::isReal() const
676
281k
{
677
281k
    return type_code() == ::ot_real;
678
281k
}
679
680
bool
681
QPDFObjectHandle::isNumber() const
682
186k
{
683
186k
    return (isInteger() || isReal());
684
186k
}
685
686
double
687
QPDFObjectHandle::getNumericValue() const
688
66.8k
{
689
66.8k
    if (isInteger()) {
690
9.24k
        return static_cast<double>(getIntValue());
691
57.5k
    } else if (isReal()) {
692
57.5k
        return atof(getRealValue().c_str());
693
57.5k
    } else {
694
0
        typeWarning("number", "returning 0");
695
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
696
0
        return 0;
697
0
    }
698
66.8k
}
699
700
bool
701
QPDFObjectHandle::getValueAsNumber(double& value) const
702
67.0k
{
703
67.0k
    if (!isNumber()) {
704
244
        return false;
705
244
    }
706
66.8k
    value = getNumericValue();
707
66.8k
    return true;
708
67.0k
}
709
710
bool
711
QPDFObjectHandle::isName() const
712
155k
{
713
155k
    return type_code() == ::ot_name;
714
155k
}
715
716
bool
717
QPDFObjectHandle::isString() const
718
110k
{
719
110k
    return type_code() == ::ot_string;
720
110k
}
721
722
bool
723
QPDFObjectHandle::isOperator() const
724
1.02M
{
725
1.02M
    return type_code() == ::ot_operator;
726
1.02M
}
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
94.9k
{
737
94.9k
    return type_code() == ::ot_array;
738
94.9k
}
739
740
bool
741
QPDFObjectHandle::isDictionary() const
742
1.72M
{
743
1.72M
    return type_code() == ::ot_dictionary;
744
1.72M
}
745
746
bool
747
QPDFObjectHandle::isStream() const
748
305k
{
749
305k
    return type_code() == ::ot_stream;
750
305k
}
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
142k
{
761
142k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
762
142k
}
763
764
bool
765
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
766
3.48k
{
767
3.48k
    return Name(*this) == name;
768
3.48k
}
769
770
bool
771
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
772
609k
{
773
609k
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
774
107k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
775
609k
}
776
777
bool
778
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
779
184k
{
780
184k
    return isStream() && getDict().isDictionaryOfType(type, subtype);
781
184k
}
782
783
// Bool accessors
784
785
bool
786
QPDFObjectHandle::getBoolValue() const
787
2.13k
{
788
2.13k
    if (auto boolean = as<QPDF_Bool>()) {
789
2.13k
        return boolean->val;
790
2.13k
    } else {
791
0
        typeWarning("boolean", "returning false");
792
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
793
0
        return false;
794
0
    }
795
2.13k
}
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
18.2k
{
817
18.2k
    return {QPDFObject::create<QPDF_Integer>(value)};
818
18.2k
}
819
820
int64_t
821
Integer::value() const
822
142k
{
823
142k
    auto* i = as<QPDF_Integer>();
824
142k
    if (!i) {
825
987
        throw invalid_error("Integer");
826
987
    }
827
141k
    return i->val;
828
142k
}
829
830
long long
831
QPDFObjectHandle::getIntValue() const
832
46.4k
{
833
46.4k
    if (auto const integer = Integer(*this)) {
834
46.4k
        return integer;
835
46.4k
    } else {
836
0
        typeWarning("integer", "returning 0");
837
0
        return 0;
838
0
    }
839
46.4k
}
840
841
bool
842
QPDFObjectHandle::getValueAsInt(long long& value) const
843
6.93k
{
844
6.93k
    if (auto const integer = Integer(*this)) {
845
6.86k
        value = integer;
846
6.86k
        return true;
847
6.86k
    }
848
71
    return false;
849
6.93k
}
850
851
int
852
QPDFObjectHandle::getIntValueAsInt() const
853
41.0k
{
854
41.0k
    try {
855
41.0k
        return Integer(*this);
856
41.0k
    } catch (std::underflow_error&) {
857
61
        warn("requested value of integer is too small; returning INT_MIN");
858
61
        return INT_MIN;
859
160
    } catch (std::overflow_error&) {
860
160
        warn("requested value of integer is too big; returning INT_MAX");
861
160
        return INT_MAX;
862
160
    } catch (std::invalid_argument&) {
863
11
        typeWarning("integer", "returning 0");
864
11
        return 0;
865
11
    }
866
41.0k
}
867
868
bool
869
QPDFObjectHandle::getValueAsInt(int& value) const
870
1.65k
{
871
1.65k
    if (!isInteger()) {
872
45
        return false;
873
45
    }
874
1.60k
    value = getIntValueAsInt();
875
1.60k
    return true;
876
1.65k
}
877
878
unsigned long long
879
QPDFObjectHandle::getUIntValue() const
880
22.9k
{
881
22.9k
    try {
882
22.9k
        return Integer(*this);
883
22.9k
    } catch (std::underflow_error&) {
884
146
        warn("unsigned value request for negative number; returning 0");
885
146
        return 0;
886
622
    } catch (std::invalid_argument&) {
887
622
        typeWarning("integer", "returning 0");
888
622
        return 0;
889
622
    }
890
22.9k
}
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.57k
{
905
7.57k
    try {
906
7.57k
        return Integer(*this);
907
7.57k
    } catch (std::underflow_error&) {
908
113
        warn("unsigned integer value request for negative number; returning 0");
909
113
        return 0;
910
113
    } catch (std::overflow_error&) {
911
90
        warn("requested value of unsigned integer is too big; returning UINT_MAX");
912
90
        return UINT_MAX;
913
354
    } catch (std::invalid_argument&) {
914
354
        typeWarning("integer", "returning 0");
915
354
        return 0;
916
354
    }
917
7.57k
}
918
919
bool
920
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
921
1.82k
{
922
1.82k
    if (!isInteger()) {
923
167
        return false;
924
167
    }
925
1.65k
    value = getUIntValueAsUInt();
926
1.65k
    return true;
927
1.82k
}
928
929
// Real accessors
930
931
std::string
932
QPDFObjectHandle::getRealValue() const
933
57.5k
{
934
57.5k
    if (isReal()) {
935
57.5k
        return obj->getStringValue();
936
57.5k
    } 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
57.5k
}
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
5.76k
{
958
5.76k
    return {QPDFObject::create<QPDF_Name>(name)};
959
5.76k
}
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
11.7k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
968
11.7k
{
969
11.7k
}
970
971
std::string const&
972
Name::value() const
973
189k
{
974
189k
    auto* n = as<QPDF_Name>();
975
189k
    if (!n) {
976
0
        throw invalid_error("Name");
977
0
    }
978
189k
    return n->name;
979
189k
}
980
981
std::string
982
QPDFObjectHandle::getName() const
983
22.9k
{
984
22.9k
    if (isName()) {
985
21.3k
        return obj->getStringValue();
986
21.3k
    } else {
987
1.68k
        typeWarning("name", "returning dummy name");
988
1.68k
        return "/QPDFFakeName";
989
1.68k
    }
990
22.9k
}
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
3.33k
{
1007
3.33k
    if (isString()) {
1008
3.33k
        return obj->getStringValue();
1009
3.33k
    } else {
1010
0
        typeWarning("string", "returning empty string");
1011
0
        QTC::TC("qpdf", "QPDFObjectHandle string returning empty string");
1012
0
        return "";
1013
0
    }
1014
3.33k
}
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
375k
{
1029
375k
    if (auto str = as<QPDF_String>()) {
1030
375k
        return str->getUTF8Val();
1031
375k
    } else {
1032
0
        typeWarning("string", "returning empty string");
1033
0
        QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8");
1034
0
        return "";
1035
0
    }
1036
375k
}
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
308k
{
1053
308k
    if (isOperator()) {
1054
308k
        return obj->getStringValue();
1055
308k
    } 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
308k
}
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.1k
{
1099
10.1k
    return *this;
1100
10.1k
}
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
585
{
1115
585
    if (isNameAndEquals(value)) {
1116
44
        return true;
1117
541
    } else if (isArray()) {
1118
2.78k
        for (auto& item: getArrayAsVector()) {
1119
2.78k
            if (item.isNameAndEquals(value)) {
1120
2
                return true;
1121
2
            }
1122
2.78k
        }
1123
63
    }
1124
539
    return false;
1125
585
}
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
14.2k
{
1143
14.2k
    if (!(isDictionary() && other.isDictionary())) {
1144
3.90k
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1145
3.90k
        return;
1146
3.90k
    }
1147
1148
10.3k
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1149
10.3k
                              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
27.2k
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1160
27.2k
        auto other_val = value1;
1161
27.2k
        if (hasKey(rtype)) {
1162
15.1k
            QPDFObjectHandle this_val = getKey(rtype);
1163
15.1k
            if (this_val.isDictionary() && other_val.isDictionary()) {
1164
8.65k
                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
2.08k
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1169
2.08k
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1170
2.08k
                }
1171
8.65k
                std::map<QPDFObjGen, std::string> og_to_name;
1172
8.65k
                std::set<std::string> rnames;
1173
8.65k
                int min_suffix = 1;
1174
8.65k
                bool initialized_maps = false;
1175
83.8k
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1176
83.8k
                    QPDFObjectHandle rval = value2;
1177
83.8k
                    if (!this_val.hasKey(key)) {
1178
16.1k
                        if (!rval.isIndirect()) {
1179
11.7k
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1180
11.7k
                            rval = rval.shallowCopy();
1181
11.7k
                        }
1182
16.1k
                        this_val.replaceKey(key, rval);
1183
67.7k
                    } 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
83.8k
                }
1205
8.65k
            } else if (this_val.isArray() && other_val.isArray()) {
1206
2.35k
                std::set<std::string> scalars;
1207
73.7k
                for (auto this_item: this_val.aitems()) {
1208
73.7k
                    if (this_item.isScalar()) {
1209
72.8k
                        scalars.insert(this_item.unparse());
1210
72.8k
                    }
1211
73.7k
                }
1212
67.4k
                for (auto other_item: other_val.aitems()) {
1213
67.4k
                    if (other_item.isScalar()) {
1214
65.3k
                        if (!scalars.contains(other_item.unparse())) {
1215
26.7k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1216
26.7k
                            this_val.appendItem(other_item);
1217
38.5k
                        } else {
1218
38.5k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1219
38.5k
                        }
1220
65.3k
                    }
1221
67.4k
                }
1222
2.35k
            }
1223
15.1k
        } else {
1224
12.0k
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1225
12.0k
            replaceKey(rtype, other_val.shallowCopy());
1226
12.0k
        }
1227
27.2k
    }
1228
10.3k
}
1229
1230
std::set<std::string>
1231
QPDFObjectHandle::getResourceNames() const
1232
7.58k
{
1233
    // Return second-level dictionary keys
1234
7.58k
    std::set<std::string> result;
1235
27.7k
    for (auto const& item: as_dictionary(strict)) {
1236
184k
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1237
184k
            if (!val2.null()) {
1238
144k
                result.insert(key2);
1239
144k
            }
1240
184k
        }
1241
27.7k
    }
1242
7.58k
    return result;
1243
7.58k
}
1244
1245
std::string
1246
QPDFObjectHandle::getUniqueResourceName(
1247
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1248
7.58k
{
1249
7.58k
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1250
7.58k
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1251
7.59k
    while (min_suffix <= max_suffix) {
1252
7.59k
        std::string candidate = prefix + std::to_string(min_suffix);
1253
7.59k
        if (!names.contains(candidate)) {
1254
7.58k
            return candidate;
1255
7.58k
        }
1256
        // Increment after return; min_suffix should be the value
1257
        // used, not the next value.
1258
11
        ++min_suffix;
1259
11
    }
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
2
    throw std::logic_error(
1264
2
        "unable to find unconflicting name in QPDFObjectHandle::getUniqueResourceName");
1265
7.58k
}
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
28.0k
{
1281
28.0k
    all_description = description;
1282
28.0k
    std::vector<QPDFObjectHandle> result;
1283
28.0k
    if (auto array = as_array(strict)) {
1284
5.40k
        int n_items = static_cast<int>(array.size());
1285
21.0k
        for (int i = 0; i < n_items; ++i) {
1286
15.6k
            QPDFObjectHandle item = array[i];
1287
15.6k
            if (item.isStream()) {
1288
9.85k
                result.emplace_back(item);
1289
9.85k
            } else {
1290
5.75k
                item.warn(
1291
5.75k
                    {qpdf_e_damaged_pdf,
1292
5.75k
                     "",
1293
5.75k
                     description + ": item index " + std::to_string(i) + " (from 0)",
1294
5.75k
                     0,
1295
5.75k
                     "ignoring non-stream in an array of streams"});
1296
5.75k
            }
1297
15.6k
        }
1298
22.6k
    } else if (isStream()) {
1299
6.29k
        result.emplace_back(*this);
1300
16.3k
    } else if (!null()) {
1301
811
        warn(
1302
811
            {qpdf_e_damaged_pdf,
1303
811
             "",
1304
811
             description,
1305
811
             0,
1306
811
             " object is supposed to be a stream or an array of streams but is neither"});
1307
811
    }
1308
1309
28.0k
    bool first = true;
1310
28.0k
    for (auto const& item: result) {
1311
16.1k
        if (first) {
1312
11.3k
            first = false;
1313
11.3k
        } else {
1314
4.78k
            all_description += ",";
1315
4.78k
        }
1316
16.1k
        all_description += " stream " + item.getObjGen().unparse(' ');
1317
16.1k
    }
1318
1319
28.0k
    return result;
1320
28.0k
}
1321
1322
std::vector<QPDFObjectHandle>
1323
QPDFObjectHandle::getPageContents()
1324
4.90k
{
1325
4.90k
    std::string description = "page object " + getObjGen().unparse(' ');
1326
4.90k
    std::string all_description;
1327
4.90k
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1328
4.90k
}
1329
1330
void
1331
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1332
4.90k
{
1333
4.90k
    new_contents.assertStream();
1334
1335
4.90k
    std::vector<QPDFObjectHandle> content_streams;
1336
4.90k
    if (first) {
1337
2.45k
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1338
2.45k
        content_streams.push_back(new_contents);
1339
2.45k
    }
1340
4.90k
    for (auto const& iter: getPageContents()) {
1341
3.75k
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1342
3.75k
        content_streams.push_back(iter);
1343
3.75k
    }
1344
4.90k
    if (!first) {
1345
2.44k
        content_streams.push_back(new_contents);
1346
2.44k
    }
1347
1348
4.90k
    replaceKey("/Contents", newArray(content_streams));
1349
4.90k
}
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
20.4k
{
1387
20.4k
    QPDFObjectHandle contents = getKey("/Contents");
1388
20.4k
    if (contents.isStream()) {
1389
3.22k
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1390
3.22k
        return;
1391
17.2k
    } 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
14.1k
        return;
1395
14.1k
    }
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
3.09k
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1401
1402
3.09k
    QPDFObjectHandle new_contents = newStream(&qpdf);
1403
3.09k
    replaceKey("/Contents", new_contents);
1404
1405
3.09k
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1406
3.09k
    new_contents.replaceStreamData(provider, newNull(), newNull());
1407
3.09k
}
1408
1409
std::string
1410
QPDFObjectHandle::unparse() const
1411
138k
{
1412
138k
    if (isIndirect()) {
1413
4.44k
        return getObjGen().unparse(' ') + " R";
1414
133k
    } else {
1415
133k
        return unparseResolved();
1416
133k
    }
1417
138k
}
1418
1419
std::string
1420
QPDFObjectHandle::unparseResolved() const
1421
133k
{
1422
133k
    if (!obj) {
1423
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1424
0
    }
1425
133k
    return BaseHandle::unparse();
1426
133k
}
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
16.1k
{
1441
16.1k
    if ((!dereference_indirect) && isIndirect()) {
1442
0
        return JSON::makeString(unparse());
1443
16.1k
    } else if (!obj) {
1444
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1445
16.1k
    } else {
1446
16.1k
        Pl_Buffer p{"json"};
1447
16.1k
        JSON::Writer jw{&p, 0};
1448
16.1k
        writeJSON(json_version, jw, dereference_indirect);
1449
16.1k
        p.finish();
1450
16.1k
        return JSON::parse(p.getString());
1451
16.1k
    }
1452
16.1k
}
1453
1454
void
1455
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1456
199k
{
1457
199k
    if (!dereference_indirect && isIndirect()) {
1458
36.7k
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1459
162k
    } else if (!obj) {
1460
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1461
162k
    } else {
1462
162k
        write_json(json_version, p);
1463
162k
    }
1464
199k
}
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
19.8k
{
1488
19.8k
    return parse(nullptr, object_str, object_description);
1489
19.8k
}
1490
1491
QPDFObjectHandle
1492
QPDFObjectHandle::parse(
1493
    QPDF* context, std::string const& object_str, std::string const& object_description)
1494
19.8k
{
1495
19.8k
    auto input = is::OffsetBuffer("parsed object", object_str);
1496
19.8k
    auto result = QPDFParser::parse(input, object_description, context);
1497
19.8k
    size_t offset = QIntC::to_size(input.tell());
1498
19.8k
    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
19.8k
    return result;
1511
19.8k
}
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
23.1k
{
1525
23.1k
    bool need_newline = false;
1526
23.1k
    std::string buffer;
1527
23.1k
    pl::String buf(buffer);
1528
23.1k
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1529
12.2k
        if (need_newline) {
1530
783
            buf.writeCStr("\n");
1531
783
        }
1532
12.2k
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1533
887
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1534
887
            throw QPDFExc(
1535
887
                qpdf_e_damaged_pdf,
1536
887
                "content stream",
1537
887
                "content stream object " + stream.getObjGen().unparse(' '),
1538
887
                0,
1539
887
                "errors while decoding content stream");
1540
887
        }
1541
11.3k
        need_newline = buffer.empty() || buffer.back() != '\n';
1542
11.3k
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1543
11.3k
        p->writeString(buffer);
1544
11.3k
        buffer.clear();
1545
11.3k
    }
1546
22.2k
    p->finish();
1547
22.2k
}
1548
1549
void
1550
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1551
20.2k
{
1552
20.2k
    std::string description = "page object " + getObjGen().unparse(' ');
1553
20.2k
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1554
20.2k
}
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
20.2k
{
1589
20.2k
    std::string stream_data;
1590
20.2k
    pl::String buf(stream_data);
1591
20.2k
    std::string all_description;
1592
20.2k
    pipeContentStreams(&buf, description, all_description);
1593
20.2k
    if (callbacks) {
1594
0
        callbacks->contentSize(stream_data.size());
1595
0
    }
1596
20.2k
    try {
1597
20.2k
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1598
20.2k
    } catch (TerminateParsing&) {
1599
0
        return;
1600
0
    }
1601
18.4k
    if (callbacks) {
1602
0
        callbacks->handleEOF();
1603
0
    }
1604
18.4k
}
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
18.7k
{
1613
18.7k
    size_t stream_length = stream_data.size();
1614
18.7k
    auto input = is::OffsetBuffer(description, stream_data);
1615
18.7k
    Tokenizer tokenizer;
1616
18.7k
    tokenizer.allowEOF();
1617
18.7k
    auto sp_description = QPDFParser::make_description(description, "content");
1618
739k
    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
724k
        tokenizer.nextToken(input, "content", true);
1623
724k
        qpdf_offset_t offset = input.getLastOffset();
1624
724k
        input.seek(offset, SEEK_SET);
1625
724k
        auto obj = QPDFParser::parse_content(input, sp_description, tokenizer, context);
1626
724k
        if (!obj) {
1627
            // EOF
1628
3.77k
            break;
1629
3.77k
        }
1630
720k
        size_t length = QIntC::to_size(input.tell() - offset);
1631
720k
        if (callbacks) {
1632
0
            callbacks->handleObject(obj, QIntC::to_size(offset), length);
1633
0
        }
1634
720k
        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
1.95k
            char ch;
1638
1.95k
            input.read(&ch, 1);
1639
1.95k
            tokenizer.expectInlineImage(input);
1640
1.95k
            tokenizer.nextToken(input, description);
1641
1.95k
            offset = input.getLastOffset();
1642
1.95k
            length = QIntC::to_size(input.tell() - offset);
1643
1.95k
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1644
66
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1645
66
                warn(
1646
66
                    context,
1647
66
                    {qpdf_e_damaged_pdf,
1648
66
                     description,
1649
66
                     "stream data",
1650
66
                     input.tell(),
1651
66
                     "EOF found while reading inline image"});
1652
1.89k
            } else {
1653
1.89k
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1654
1.89k
                if (callbacks) {
1655
0
                    callbacks->handleObject(
1656
0
                        QPDFObjectHandle::newInlineImage(tokenizer.getValue()),
1657
0
                        QIntC::to_size(offset),
1658
0
                        length);
1659
0
                }
1660
1.89k
            }
1661
1.95k
        }
1662
720k
    }
1663
18.7k
}
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
4.58k
{
1675
4.58k
    return as_stream(error).addTokenFilter(filter);
1676
4.58k
}
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
124k
{
1705
124k
    return {QPDFObject::create<QPDF_Null>()};
1706
124k
}
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
39.4k
{
1717
39.4k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1718
39.4k
}
1719
1720
QPDFObjectHandle
1721
QPDFObjectHandle::newString(std::string const& str)
1722
21
{
1723
21
    return {QPDFObject::create<QPDF_String>(str)};
1724
21
}
1725
1726
QPDFObjectHandle
1727
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1728
780
{
1729
780
    return {QPDF_String::create_utf16(utf8_str)};
1730
780
}
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
13
{
1747
13
    return newArray(std::vector<QPDFObjectHandle>());
1748
13
}
1749
1750
QPDFObjectHandle
1751
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1752
19.4k
{
1753
19.4k
    return {QPDFObject::create<QPDF_Array>(items)};
1754
19.4k
}
1755
1756
QPDFObjectHandle
1757
QPDFObjectHandle::newArray(Rectangle const& rect)
1758
9.85k
{
1759
9.85k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1760
9.85k
}
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
2.94k
{
1789
2.94k
    return newArray(rect);
1790
2.94k
}
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
15.1k
{
1807
15.1k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1808
15.1k
}
1809
1810
QPDFObjectHandle
1811
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1812
15.1k
{
1813
15.1k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1814
15.1k
}
1815
1816
QPDFObjectHandle
1817
QPDFObjectHandle::newStream(QPDF* qpdf)
1818
2.99k
{
1819
2.99k
    if (qpdf == nullptr) {
1820
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1821
0
    }
1822
2.99k
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1823
2.99k
    return qpdf->newStream();
1824
2.99k
}
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
2.94k
{
1839
2.94k
    if (qpdf == nullptr) {
1840
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1841
0
    }
1842
2.94k
    QTC::TC("qpdf", "QPDFObjectHandle newStream with string");
1843
2.94k
    return qpdf->newStream(data);
1844
2.94k
}
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
143k
{
1858
143k
    return obj ? obj->getDescription() : ""s;
1859
143k
}
1860
1861
void
1862
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
1863
13.6k
{
1864
13.6k
    if (obj) {
1865
13.6k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
1866
13.6k
        obj->setDescription(owning_qpdf, descr);
1867
13.6k
    }
1868
13.6k
}
1869
1870
bool
1871
QPDFObjectHandle::hasObjectDescription() const
1872
43.2k
{
1873
43.2k
    return obj && obj->hasDescription();
1874
43.2k
}
1875
1876
QPDFObjectHandle
1877
QPDFObjectHandle::shallowCopy()
1878
35.7k
{
1879
35.7k
    if (!obj) {
1880
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1881
0
    }
1882
35.7k
    return {copy()};
1883
35.7k
}
1884
1885
QPDFObjectHandle
1886
QPDFObjectHandle::unsafeShallowCopy()
1887
0
{
1888
0
    if (!obj) {
1889
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1890
0
    }
1891
0
    return {copy(true)};
1892
0
}
1893
1894
void
1895
QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
1896
0
{
1897
0
    assertInitialized();
1898
1899
0
    auto cur_og = getObjGen();
1900
0
    if (!visited.add(cur_og)) {
1901
0
        QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
1902
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
1903
0
    }
1904
1905
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
1906
0
        obj = copy(true);
1907
0
    } else if (auto a = as_array(strict)) {
1908
0
        std::vector<QPDFObjectHandle> items;
1909
0
        for (auto const& item: a) {
1910
0
            items.emplace_back(item);
1911
0
            items.back().makeDirect(visited, stop_at_streams);
1912
0
        }
1913
0
        obj = QPDFObject::create<QPDF_Array>(items);
1914
0
    } else if (isDictionary()) {
1915
0
        std::map<std::string, QPDFObjectHandle> items;
1916
0
        for (auto const& [key, value]: as_dictionary(strict)) {
1917
0
            if (!value.null()) {
1918
0
                items.insert({key, value});
1919
0
                items[key].makeDirect(visited, stop_at_streams);
1920
0
            }
1921
0
        }
1922
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
1923
0
    } else if (isStream()) {
1924
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
1925
0
        if (!stop_at_streams) {
1926
0
            throw std::runtime_error("attempt to make a stream into a direct object");
1927
0
        }
1928
0
    } 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
0
    visited.erase(cur_og);
1936
0
}
1937
1938
void
1939
QPDFObjectHandle::makeDirect(bool allow_streams)
1940
0
{
1941
0
    QPDFObjGen::set visited;
1942
0
    makeDirect(visited, allow_streams);
1943
0
}
1944
1945
void
1946
QPDFObjectHandle::assertInitialized() const
1947
0
{
1948
0
    if (!obj) {
1949
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1950
0
    }
1951
0
}
1952
1953
std::invalid_argument
1954
BaseHandle::invalid_error(std::string const& method) const
1955
987
{
1956
987
    return std::invalid_argument(method + " operation attempted on invalid object");
1957
987
}
1958
std::runtime_error
1959
BaseHandle::type_error(char const* expected_type) const
1960
0
{
1961
0
    return std::runtime_error(
1962
0
        "operation for "s + expected_type + " attempted on object of type " + type_name());
1963
0
}
1964
1965
QPDFExc
1966
BaseHandle::type_error(char const* expected_type, std::string const& message) const
1967
23.5k
{
1968
23.5k
    return {
1969
23.5k
        qpdf_e_object,
1970
23.5k
        "",
1971
23.5k
        description(),
1972
23.5k
        0,
1973
23.5k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
1974
23.5k
            message};
1975
23.5k
}
1976
1977
void
1978
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
1979
23.5k
{
1980
23.5k
    if (!obj) {
1981
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1982
0
    }
1983
23.5k
    warn(type_error(expected_type, message));
1984
23.5k
}
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
0
{
1995
0
    warn({qpdf_e_object, "", description(), 0, warning});
1996
0
}
1997
1998
void
1999
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2000
4.90k
{
2001
4.90k
    if (!istype) {
2002
0
        throw type_error(type_name);
2003
0
    }
2004
4.90k
}
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
4.90k
{
2069
4.90k
    assertType("stream", isStream());
2070
4.90k
}
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
77.8k
{
2124
77.8k
    return isStreamOfType("", "/Form");
2125
77.8k
}
2126
2127
bool
2128
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2129
11.3k
{
2130
11.3k
    return (
2131
11.3k
        isStreamOfType("", "/Image") &&
2132
1.21k
        ((!exclude_imagemask) ||
2133
1.21k
         (!(getDict().getKey("/ImageMask").isBool() &&
2134
66
            getDict().getKey("/ImageMask").getBoolValue()))));
2135
11.3k
}
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
66
{
2148
66
    if (!qpdf) {
2149
0
        throw std::move(e);
2150
0
    }
2151
66
    qpdf->warn(std::move(e));
2152
66
}
2153
2154
void
2155
BaseHandle::warn(QPDFExc&& e) const
2156
150k
{
2157
150k
    if (!qpdf()) {
2158
511
        throw std::move(e);
2159
511
    }
2160
149k
    qpdf()->warn(std::move(e));
2161
149k
}
2162
2163
void
2164
BaseHandle::warn(std::string const& warning) const
2165
181k
{
2166
181k
    if (qpdf()) {
2167
120k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2168
120k
    } else {
2169
61.1k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2170
61.1k
    }
2171
181k
}
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.1k
    oh(oh)
2260
10.1k
{
2261
10.1k
}
2262
2263
QPDFObjectHandle::QPDFArrayItems::iterator&
2264
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2265
208k
{
2266
208k
    if (!m->is_end) {
2267
208k
        ++m->item_number;
2268
208k
        updateIValue();
2269
208k
    }
2270
208k
    return *this;
2271
208k
}
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
208k
{
2286
208k
    updateIValue();
2287
208k
    return ivalue;
2288
208k
}
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
218k
{
2300
218k
    return (m->item_number == other.m->item_number);
2301
218k
}
2302
2303
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2304
20.3k
    m(new Members(oh, for_begin))
2305
20.3k
{
2306
20.3k
    updateIValue();
2307
20.3k
}
2308
2309
void
2310
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2311
436k
{
2312
436k
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2313
436k
    if (m->is_end) {
2314
20.0k
        ivalue = QPDFObjectHandle();
2315
416k
    } else {
2316
416k
        ivalue = m->oh.getArrayItem(m->item_number);
2317
416k
    }
2318
436k
}
2319
2320
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2321
20.3k
    oh(oh)
2322
20.3k
{
2323
20.3k
    item_number = for_begin ? 0 : oh.getArrayNItems();
2324
20.3k
}
2325
2326
QPDFObjectHandle::QPDFArrayItems::iterator
2327
QPDFObjectHandle::QPDFArrayItems::begin()
2328
10.1k
{
2329
10.1k
    return {oh, true};
2330
10.1k
}
2331
2332
QPDFObjectHandle::QPDFArrayItems::iterator
2333
QPDFObjectHandle::QPDFArrayItems::end()
2334
10.1k
{
2335
10.1k
    return {oh, false};
2336
10.1k
}
2337
2338
QPDFObjGen
2339
QPDFObjectHandle::getObjGen() const
2340
590k
{
2341
590k
    return obj ? obj->getObjGen() : QPDFObjGen();
2342
590k
}
2343
2344
int
2345
QPDFObjectHandle::getObjectID() const
2346
412k
{
2347
412k
    return getObjGen().getObj();
2348
412k
}
2349
2350
int
2351
QPDFObjectHandle::getGeneration() const
2352
0
{
2353
0
    return getObjGen().getGen();
2354
0
}
2355
2356
bool
2357
QPDFObjectHandle::isIndirect() const
2358
404k
{
2359
404k
    return getObjectID() != 0;
2360
404k
}
2361
2362
// Indirect object accessors
2363
QPDF*
2364
QPDFObjectHandle::getOwningQPDF() const
2365
351k
{
2366
351k
    return obj ? obj->getQPDF() : nullptr;
2367
351k
}
2368
2369
QPDF&
2370
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2371
2.99k
{
2372
2.99k
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2373
2.99k
        return *result;
2374
2.99k
    }
2375
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2376
2.99k
}
2377
2378
void
2379
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2380
21
{
2381
21
    if (obj) {
2382
21
        obj->setParsedOffset(offset);
2383
21
    }
2384
21
}
2385
2386
QPDFObjectHandle
2387
operator""_qpdf(char const* v, size_t len)
2388
19.8k
{
2389
19.8k
    return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal");
2390
19.8k
}