Coverage Report

Created: 2025-08-03 06:19

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