Coverage Report

Created: 2025-07-18 07:03

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