Coverage Report

Created: 2025-07-11 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
37.0k
{
34
37.0k
    return obj ? obj->getObjGen() : QPDFObjGen();
35
37.0k
}
36
37
namespace
38
{
39
    class TerminateParsing
40
    {
41
    };
42
} // namespace
43
44
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
45
0
    supports_retry(supports_retry)
46
0
{
47
0
}
48
49
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
50
0
{
51
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
52
0
}
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
0
{
85
0
    return supports_retry;
86
0
}
87
88
namespace
89
{
90
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
91
    {
92
      public:
93
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
94
0
            containing_page(containing_page),
95
0
            old_contents(old_contents)
96
0
        {
97
0
        }
98
0
        ~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
0
{
110
0
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
111
0
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
112
0
    std::string all_description;
113
0
    old_contents.pipeContentStreams(p, description, all_description);
114
0
}
115
116
void
117
QPDFObjectHandle::TokenFilter::handleEOF()
118
0
{
119
0
}
120
121
void
122
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
123
0
{
124
0
    pipeline = p;
125
0
}
126
127
void
128
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
129
0
{
130
0
    if (!pipeline) {
131
0
        return;
132
0
    }
133
0
    if (len) {
134
0
        pipeline->write(data, len);
135
0
    }
136
0
}
137
138
void
139
QPDFObjectHandle::TokenFilter::write(std::string const& str)
140
0
{
141
0
    write(str.c_str(), str.length());
142
0
}
143
144
void
145
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
146
0
{
147
0
    std::string const& value = token.getRawValue();
148
0
    write(value.c_str(), value.length());
149
0
}
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
0
{
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
0
    handleObject(oh);
163
0
}
164
165
void
166
QPDFObjectHandle::ParserCallbacks::contentSize(size_t)
167
0
{
168
    // Ignore by default; overriding this is optional.
169
0
}
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
0
{
180
0
    int tail = 0;       // Number of continuation characters expected.
181
0
    bool tail2 = false; // Potential overlong 3 octet utf-8.
182
0
    bool tail3 = false; // potential overlong 4 octet
183
0
    bool needs_escaping = false;
184
0
    for (auto const& it: name) {
185
0
        auto c = static_cast<unsigned char>(it);
186
0
        if (tail) {
187
0
            if ((c & 0xc0) != 0x80) {
188
0
                return {false, false};
189
0
            }
190
0
            if (tail2) {
191
0
                if ((c & 0xe0) == 0x80) {
192
0
                    return {false, false};
193
0
                }
194
0
                tail2 = false;
195
0
            } else if (tail3) {
196
0
                if ((c & 0xf0) == 0x80) {
197
0
                    return {false, false};
198
0
                }
199
0
                tail3 = false;
200
0
            }
201
0
            tail--;
202
0
        } else if (c < 0x80) {
203
0
            if (!needs_escaping) {
204
0
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
205
0
            }
206
0
        } else if ((c & 0xe0) == 0xc0) {
207
0
            if ((c & 0xfe) == 0xc0) {
208
0
                return {false, false};
209
0
            }
210
0
            tail = 1;
211
0
        } else if ((c & 0xf0) == 0xe0) {
212
0
            tail2 = (c == 0xe0);
213
0
            tail = 2;
214
0
        } else if ((c & 0xf8) == 0xf0) {
215
0
            tail3 = (c == 0xf0);
216
0
            tail = 3;
217
0
        } else {
218
0
            return {false, false};
219
0
        }
220
0
    }
221
0
    return {tail == 0, !needs_escaping};
222
0
}
223
224
std::string
225
Name::normalize(std::string const& name)
226
1.49M
{
227
1.49M
    if (name.empty()) {
228
0
        return name;
229
0
    }
230
1.49M
    std::string result;
231
1.49M
    result += name.at(0);
232
31.0M
    for (size_t i = 1; i < name.length(); ++i) {
233
29.5M
        char ch = name.at(i);
234
        // Don't use locale/ctype here; follow PDF spec guidelines.
235
29.5M
        if (ch == '\0') {
236
            // QPDFTokenizer embeds a null character to encode an invalid #.
237
47.3k
            result += "#";
238
29.5M
        } else if (
239
29.5M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
240
29.5M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
241
17.4M
            result += util::hex_encode_char(ch);
242
17.4M
        } else {
243
12.1M
            result += ch;
244
12.1M
        }
245
29.5M
    }
246
1.49M
    return result;
247
1.49M
}
248
249
std::shared_ptr<QPDFObject>
250
BaseHandle::copy(bool shallow) const
251
102k
{
252
102k
    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
2.38k
    case ::ot_null:
259
2.38k
        return QPDFObject::create<QPDF_Null>();
260
35
    case ::ot_boolean:
261
35
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
262
1.57k
    case ::ot_integer:
263
1.57k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
264
731
    case ::ot_real:
265
731
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
266
1.31k
    case ::ot_string:
267
1.31k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
268
3.51k
    case ::ot_name:
269
3.51k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
270
115
    case ::ot_array:
271
115
        {
272
115
            auto const& a = std::get<QPDF_Array>(obj->value);
273
115
            if (shallow) {
274
0
                return QPDFObject::create<QPDF_Array>(a);
275
115
            } else {
276
115
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
277
115
                if (a.sp) {
278
17
                    QPDF_Array result;
279
17
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
280
17
                    result.sp->size = a.sp->size;
281
961
                    for (auto const& [idx, oh]: a.sp->elements) {
282
961
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
283
961
                    }
284
17
                    return QPDFObject::create<QPDF_Array>(std::move(result));
285
98
                } else {
286
98
                    std::vector<QPDFObjectHandle> result;
287
98
                    result.reserve(a.elements.size());
288
2.75k
                    for (auto const& element: a.elements) {
289
2.75k
                        result.emplace_back(
290
2.75k
                            element ? (element.indirect() ? element : element.copy()) : element);
291
2.75k
                    }
292
98
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
293
98
                }
294
115
            }
295
115
        }
296
93.0k
    case ::ot_dictionary:
297
93.0k
        {
298
93.0k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
299
93.0k
            if (shallow) {
300
92.4k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
301
92.4k
            } else {
302
636
                std::map<std::string, QPDFObjectHandle> new_items;
303
2.52k
                for (auto const& [key, val]: d.items) {
304
2.52k
                    new_items[key] = val.indirect() ? val : val.copy();
305
2.52k
                }
306
636
                return QPDFObject::create<QPDF_Dictionary>(new_items);
307
636
            }
308
93.0k
        }
309
0
    case ::ot_stream:
310
0
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
311
0
        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
102k
    }
326
0
    return {}; // unreachable
327
102k
}
328
329
std::string
330
BaseHandle::unparse() const
331
1.44M
{
332
1.44M
    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
242k
    case ::ot_null:
340
242k
        return "null";
341
9.44k
    case ::ot_boolean:
342
9.44k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
343
490k
    case ::ot_integer:
344
490k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
345
152k
    case ::ot_real:
346
152k
        return std::get<QPDF_Real>(obj->value).val;
347
23.4k
    case ::ot_string:
348
23.4k
        return std::get<QPDF_String>(obj->value).unparse(false);
349
532k
    case ::ot_name:
350
532k
        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
1.44M
    }
403
0
    return {}; // unreachable
404
1.44M
}
405
406
void
407
BaseHandle::write_json(int json_version, JSON::Writer& p) const
408
0
{
409
0
    switch (resolved_type_code()) {
410
0
    case ::ot_uninitialized:
411
0
        throw std::logic_error(
412
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
413
0
        break; // unreachable
414
0
    case ::ot_null:
415
0
    case ::ot_operator:
416
0
    case ::ot_inlineimage:
417
0
        p << "null";
418
0
        break;
419
0
    case ::ot_boolean:
420
0
        p << std::get<QPDF_Bool>(obj->value).val;
421
0
        break;
422
0
    case ::ot_integer:
423
0
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
424
0
        break;
425
0
    case ::ot_real:
426
0
        {
427
0
            auto const& val = std::get<QPDF_Real>(obj->value).val;
428
0
            if (val.empty()) {
429
                // Can't really happen...
430
0
                p << "0";
431
0
            } else if (val.at(0) == '.') {
432
0
                p << "0" << val;
433
0
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
434
0
                p << "-0." << val.substr(2);
435
0
            } else {
436
0
                p << val;
437
0
            }
438
0
            if (val.back() == '.') {
439
0
                p << "0";
440
0
            }
441
0
        }
442
0
        break;
443
0
    case ::ot_string:
444
0
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
445
0
        break;
446
0
    case ::ot_name:
447
0
        {
448
0
            auto const& n = std::get<QPDF_Name>(obj->value);
449
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
450
            // updating this method make sure QPDF_Dictionary is also update.
451
0
            if (json_version == 1) {
452
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
453
0
            } else {
454
0
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
455
0
                    if (res.second) {
456
0
                        p << "\"" << n.name << "\"";
457
0
                    } else {
458
0
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
459
0
                    }
460
0
                } else {
461
0
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
462
0
                }
463
0
            }
464
0
        }
465
0
        break;
466
0
    case ::ot_array:
467
0
        {
468
0
            auto const& a = std::get<QPDF_Array>(obj->value);
469
0
            p.writeStart('[');
470
0
            if (a.sp) {
471
0
                int next = 0;
472
0
                for (auto& item: a.sp->elements) {
473
0
                    int key = item.first;
474
0
                    for (int j = next; j < key; ++j) {
475
0
                        p.writeNext() << "null";
476
0
                    }
477
0
                    p.writeNext();
478
0
                    auto item_og = item.second.getObj()->getObjGen();
479
0
                    if (item_og.isIndirect()) {
480
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
481
0
                    } else {
482
0
                        item.second.write_json(json_version, p);
483
0
                    }
484
0
                    next = ++key;
485
0
                }
486
0
                for (int j = next; j < a.sp->size; ++j) {
487
0
                    p.writeNext() << "null";
488
0
                }
489
0
            } else {
490
0
                for (auto const& item: a.elements) {
491
0
                    p.writeNext();
492
0
                    auto item_og = item.getObj()->getObjGen();
493
0
                    if (item_og.isIndirect()) {
494
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
495
0
                    } else {
496
0
                        item.write_json(json_version, p);
497
0
                    }
498
0
                }
499
0
            }
500
0
            p.writeEnd(']');
501
0
        }
502
0
        break;
503
0
    case ::ot_dictionary:
504
0
        {
505
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
506
0
            p.writeStart('{');
507
0
            for (auto& iter: d.items) {
508
0
                if (!iter.second.null()) {
509
0
                    p.writeNext();
510
0
                    if (json_version == 1) {
511
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
512
0
                          << "\": ";
513
0
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
514
0
                        if (res.second) {
515
0
                            p << "\"" << iter.first << "\": ";
516
0
                        } else {
517
0
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
518
0
                        }
519
0
                    } else {
520
0
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
521
0
                          << "\": ";
522
0
                    }
523
0
                    iter.second.writeJSON(json_version, p);
524
0
                }
525
0
            }
526
0
            p.writeEnd('}');
527
0
        }
528
0
        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
0
    }
538
0
}
539
540
void
541
BaseHandle::disconnect(bool only_direct)
542
2.26M
{
543
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
544
2.26M
    if (only_direct && indirect()) {
545
266k
        return;
546
266k
    }
547
548
2.00M
    switch (raw_type_code()) {
549
114k
    case ::ot_array:
550
114k
        {
551
114k
            auto& a = std::get<QPDF_Array>(obj->value);
552
114k
            if (a.sp) {
553
22.3k
                for (auto& item: a.sp->elements) {
554
22.3k
                    item.second.disconnect();
555
22.3k
                }
556
114k
            } else {
557
750k
                for (auto& oh: a.elements) {
558
750k
                    oh.disconnect();
559
750k
                }
560
114k
            }
561
114k
        }
562
114k
        break;
563
221k
    case ::ot_dictionary:
564
892k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
565
892k
            iter.second.disconnect();
566
892k
        }
567
221k
        break;
568
43.4k
    case ::ot_stream:
569
43.4k
        {
570
43.4k
            auto& s = std::get<QPDF_Stream>(obj->value);
571
43.4k
            s.m->stream_provider = nullptr;
572
43.4k
            s.m->stream_dict.disconnect();
573
43.4k
        }
574
43.4k
        break;
575
0
    case ::ot_uninitialized:
576
0
        return;
577
1.62M
    default:
578
1.62M
        break;
579
2.00M
    }
580
2.00M
    obj->qpdf = nullptr;
581
2.00M
    obj->og = QPDFObjGen();
582
2.00M
}
583
584
std::string
585
QPDFObject::getStringValue() const
586
459k
{
587
459k
    switch (getResolvedTypeCode()) {
588
0
    case ::ot_real:
589
0
        return std::get<QPDF_Real>(value).val;
590
45.8k
    case ::ot_string:
591
45.8k
        return std::get<QPDF_String>(value).val;
592
413k
    case ::ot_name:
593
413k
        return std::get<QPDF_Name>(value).name;
594
0
    case ::ot_operator:
595
0
        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
459k
    }
603
0
    return ""; // unreachable
604
459k
}
605
606
bool
607
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
608
1.39k
{
609
1.39k
    return obj == rhs.obj;
610
1.39k
}
611
612
qpdf_object_type_e
613
QPDFObjectHandle::getTypeCode() const
614
1.97M
{
615
1.97M
    return obj ? obj->getResolvedTypeCode() : ::ot_uninitialized;
616
1.97M
}
617
618
char const*
619
QPDFObjectHandle::getTypeName() const
620
6.52k
{
621
6.52k
    static constexpr std::array<char const*, 16> tn{
622
6.52k
        "uninitialized",
623
6.52k
        "reserved",
624
6.52k
        "null",
625
6.52k
        "boolean",
626
6.52k
        "integer",
627
6.52k
        "real",
628
6.52k
        "string",
629
6.52k
        "name",
630
6.52k
        "array",
631
6.52k
        "dictionary",
632
6.52k
        "stream",
633
6.52k
        "operator",
634
6.52k
        "inline-image",
635
6.52k
        "unresolved",
636
6.52k
        "destroyed",
637
6.52k
        "reference"};
638
6.52k
    return obj ? tn[getTypeCode()] : "uninitialized";
639
6.52k
}
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
8.58k
{
650
8.58k
    return type_code() == ::ot_boolean;
651
8.58k
}
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
260k
{
664
260k
    return type_code() == ::ot_null;
665
260k
}
666
667
bool
668
QPDFObjectHandle::isInteger() const
669
211k
{
670
211k
    return type_code() == ::ot_integer;
671
211k
}
672
673
bool
674
QPDFObjectHandle::isReal() const
675
11.0k
{
676
11.0k
    return type_code() == ::ot_real;
677
11.0k
}
678
679
bool
680
QPDFObjectHandle::isNumber() const
681
38.8k
{
682
38.8k
    return (isInteger() || isReal());
683
38.8k
}
684
685
double
686
QPDFObjectHandle::getNumericValue() const
687
0
{
688
0
    if (isInteger()) {
689
0
        return static_cast<double>(getIntValue());
690
0
    } else if (isReal()) {
691
0
        return atof(getRealValue().c_str());
692
0
    } else {
693
0
        typeWarning("number", "returning 0");
694
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
695
0
        return 0;
696
0
    }
697
0
}
698
699
bool
700
QPDFObjectHandle::getValueAsNumber(double& value) const
701
0
{
702
0
    if (!isNumber()) {
703
0
        return false;
704
0
    }
705
0
    value = getNumericValue();
706
0
    return true;
707
0
}
708
709
bool
710
QPDFObjectHandle::isName() const
711
1.38M
{
712
1.38M
    return type_code() == ::ot_name;
713
1.38M
}
714
715
bool
716
QPDFObjectHandle::isString() const
717
51.2k
{
718
51.2k
    return type_code() == ::ot_string;
719
51.2k
}
720
721
bool
722
QPDFObjectHandle::isOperator() const
723
0
{
724
0
    return type_code() == ::ot_operator;
725
0
}
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
2.28M
{
736
2.28M
    return type_code() == ::ot_array;
737
2.28M
}
738
739
bool
740
QPDFObjectHandle::isDictionary() const
741
6.09M
{
742
6.09M
    return type_code() == ::ot_dictionary;
743
6.09M
}
744
745
bool
746
QPDFObjectHandle::isStream() const
747
2.92M
{
748
2.92M
    return type_code() == ::ot_stream;
749
2.92M
}
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
1.57k
{
760
1.57k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
761
1.57k
}
762
763
bool
764
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
765
817k
{
766
817k
    return isName() && (getName() == name);
767
817k
}
768
769
bool
770
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
771
3.02M
{
772
3.02M
    return isDictionary() && (type.empty() || getKey("/Type").isNameAndEquals(type)) &&
773
3.02M
        (subtype.empty() || getKey("/Subtype").isNameAndEquals(subtype));
774
3.02M
}
775
776
bool
777
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
778
93.8k
{
779
93.8k
    return isStream() && getDict().isDictionaryOfType(type, subtype);
780
93.8k
}
781
782
// Bool accessors
783
784
bool
785
QPDFObjectHandle::getBoolValue() const
786
39
{
787
39
    if (auto boolean = as<QPDF_Bool>()) {
788
39
        return boolean->val;
789
39
    } else {
790
0
        typeWarning("boolean", "returning false");
791
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
792
0
        return false;
793
0
    }
794
39
}
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
154k
{
811
154k
    if (auto integer = as<QPDF_Integer>()) {
812
153k
        return integer->val;
813
153k
    } else {
814
680
        typeWarning("integer", "returning 0");
815
680
        QTC::TC("qpdf", "QPDFObjectHandle integer returning 0");
816
680
        return 0;
817
680
    }
818
154k
}
819
820
bool
821
QPDFObjectHandle::getValueAsInt(long long& value) const
822
6.38k
{
823
6.38k
    if (auto integer = as<QPDF_Integer>()) {
824
6.30k
        value = integer->val;
825
6.30k
        return true;
826
6.30k
    }
827
88
    return false;
828
6.38k
}
829
830
int
831
QPDFObjectHandle::getIntValueAsInt() const
832
38.0k
{
833
38.0k
    int result = 0;
834
38.0k
    long long v = getIntValue();
835
38.0k
    if (v < INT_MIN) {
836
61
        QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MIN");
837
61
        warnIfPossible("requested value of integer is too small; returning INT_MIN");
838
61
        result = INT_MIN;
839
37.9k
    } else if (v > INT_MAX) {
840
28
        QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MAX");
841
28
        warnIfPossible("requested value of integer is too big; returning INT_MAX");
842
28
        result = INT_MAX;
843
37.9k
    } else {
844
37.9k
        result = static_cast<int>(v);
845
37.9k
    }
846
38.0k
    return result;
847
38.0k
}
848
849
bool
850
QPDFObjectHandle::getValueAsInt(int& value) const
851
2.13k
{
852
2.13k
    if (!isInteger()) {
853
34
        return false;
854
34
    }
855
2.10k
    value = getIntValueAsInt();
856
2.10k
    return true;
857
2.13k
}
858
859
unsigned long long
860
QPDFObjectHandle::getUIntValue() const
861
39.8k
{
862
39.8k
    long long v = getIntValue();
863
39.8k
    if (v < 0) {
864
61
        QTC::TC("qpdf", "QPDFObjectHandle uint returning 0");
865
61
        warnIfPossible("unsigned value request for negative number; returning 0");
866
61
        return 0;
867
39.8k
    } else {
868
39.8k
        return static_cast<unsigned long long>(v);
869
39.8k
    }
870
39.8k
}
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
7.71k
{
885
7.71k
    long long v = getIntValue();
886
7.71k
    if (v < 0) {
887
86
        QTC::TC("qpdf", "QPDFObjectHandle uint uint returning 0");
888
86
        warnIfPossible("unsigned integer value request for negative number; returning 0");
889
86
        return 0;
890
7.63k
    } else if (v > UINT_MAX) {
891
54
        QTC::TC("qpdf", "QPDFObjectHandle uint returning UINT_MAX");
892
54
        warnIfPossible("requested value of unsigned integer is too big; returning UINT_MAX");
893
54
        return UINT_MAX;
894
7.57k
    } else {
895
7.57k
        return static_cast<unsigned int>(v);
896
7.57k
    }
897
7.71k
}
898
899
bool
900
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
901
2.30k
{
902
2.30k
    if (!isInteger()) {
903
165
        return false;
904
165
    }
905
2.13k
    value = getUIntValueAsUInt();
906
2.13k
    return true;
907
2.30k
}
908
909
// Real accessors
910
911
std::string
912
QPDFObjectHandle::getRealValue() const
913
0
{
914
0
    if (isReal()) {
915
0
        return obj->getStringValue();
916
0
    } 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
0
}
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
413k
{
938
413k
    if (isName()) {
939
413k
        return obj->getStringValue();
940
413k
    } else {
941
0
        typeWarning("name", "returning dummy name");
942
0
        QTC::TC("qpdf", "QPDFObjectHandle name returning dummy name");
943
0
        return "/QPDFFakeName";
944
0
    }
945
413k
}
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
45.9k
{
962
45.9k
    if (isString()) {
963
45.8k
        return obj->getStringValue();
964
45.8k
    } else {
965
101
        typeWarning("string", "returning empty string");
966
101
        QTC::TC("qpdf", "QPDFObjectHandle string returning empty string");
967
101
        return "";
968
101
    }
969
45.9k
}
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
0
{
984
0
    if (auto str = as<QPDF_String>()) {
985
0
        return str->getUTF8Val();
986
0
    } else {
987
0
        typeWarning("string", "returning empty string");
988
0
        QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8");
989
0
        return "";
990
0
    }
991
0
}
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
0
{
1008
0
    if (isOperator()) {
1009
0
        return obj->getStringValue();
1010
0
    } 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
0
}
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
9.73k
{
1054
9.73k
    return *this;
1055
9.73k
}
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
30.0k
{
1070
30.0k
    if (isNameAndEquals(value)) {
1071
43
        return true;
1072
29.9k
    } else if (isArray()) {
1073
34.8k
        for (auto& item: getArrayAsVector()) {
1074
34.8k
            if (item.isNameAndEquals(value)) {
1075
298
                return true;
1076
298
            }
1077
34.8k
        }
1078
11.8k
    }
1079
29.6k
    return false;
1080
30.0k
}
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
0
{
1098
0
    if (!(isDictionary() && other.isDictionary())) {
1099
0
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1100
0
        return;
1101
0
    }
1102
1103
0
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1104
0
                              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
0
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1115
0
        auto other_val = value1;
1116
0
        if (hasKey(rtype)) {
1117
0
            QPDFObjectHandle this_val = getKey(rtype);
1118
0
            if (this_val.isDictionary() && other_val.isDictionary()) {
1119
0
                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
0
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1124
0
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1125
0
                }
1126
0
                std::map<QPDFObjGen, std::string> og_to_name;
1127
0
                std::set<std::string> rnames;
1128
0
                int min_suffix = 1;
1129
0
                bool initialized_maps = false;
1130
0
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1131
0
                    QPDFObjectHandle rval = value2;
1132
0
                    if (!this_val.hasKey(key)) {
1133
0
                        if (!rval.isIndirect()) {
1134
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1135
0
                            rval = rval.shallowCopy();
1136
0
                        }
1137
0
                        this_val.replaceKey(key, rval);
1138
0
                    } 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
0
                }
1160
0
            } else if (this_val.isArray() && other_val.isArray()) {
1161
0
                std::set<std::string> scalars;
1162
0
                for (auto this_item: this_val.aitems()) {
1163
0
                    if (this_item.isScalar()) {
1164
0
                        scalars.insert(this_item.unparse());
1165
0
                    }
1166
0
                }
1167
0
                for (auto other_item: other_val.aitems()) {
1168
0
                    if (other_item.isScalar()) {
1169
0
                        if (!scalars.contains(other_item.unparse())) {
1170
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1171
0
                            this_val.appendItem(other_item);
1172
0
                        } else {
1173
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1174
0
                        }
1175
0
                    }
1176
0
                }
1177
0
            }
1178
0
        } else {
1179
0
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1180
0
            replaceKey(rtype, other_val.shallowCopy());
1181
0
        }
1182
0
    }
1183
0
}
1184
1185
std::set<std::string>
1186
QPDFObjectHandle::getResourceNames() const
1187
0
{
1188
    // Return second-level dictionary keys
1189
0
    std::set<std::string> result;
1190
0
    for (auto const& item: as_dictionary(strict)) {
1191
0
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1192
0
            if (!val2.null()) {
1193
0
                result.insert(key2);
1194
0
            }
1195
0
        }
1196
0
    }
1197
0
    return result;
1198
0
}
1199
1200
std::string
1201
QPDFObjectHandle::getUniqueResourceName(
1202
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1203
0
{
1204
0
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1205
0
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1206
0
    while (min_suffix <= max_suffix) {
1207
0
        std::string candidate = prefix + std::to_string(min_suffix);
1208
0
        if (!names.contains(candidate)) {
1209
0
            return candidate;
1210
0
        }
1211
        // Increment after return; min_suffix should be the value
1212
        // used, not the next value.
1213
0
        ++min_suffix;
1214
0
    }
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
0
    throw std::logic_error(
1219
0
        "unable to find unconflicting name in QPDFObjectHandle::getUniqueResourceName");
1220
0
}
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
0
{
1236
0
    all_description = description;
1237
0
    std::vector<QPDFObjectHandle> result;
1238
0
    if (auto array = as_array(strict)) {
1239
0
        int n_items = array.size();
1240
0
        for (int i = 0; i < n_items; ++i) {
1241
0
            QPDFObjectHandle item = array.at(i).second;
1242
0
            if (item.isStream()) {
1243
0
                result.emplace_back(item);
1244
0
            } else {
1245
0
                QTC::TC("qpdf", "QPDFObjectHandle non-stream in stream array");
1246
0
                warn(
1247
0
                    item.getOwningQPDF(),
1248
0
                    QPDFExc(
1249
0
                        qpdf_e_damaged_pdf,
1250
0
                        "",
1251
0
                        description + ": item index " + std::to_string(i) + " (from 0)",
1252
0
                        0,
1253
0
                        "ignoring non-stream in an array of streams"));
1254
0
            }
1255
0
        }
1256
0
    } else if (isStream()) {
1257
0
        result.emplace_back(*this);
1258
0
    } else if (!isNull()) {
1259
0
        warn(
1260
0
            getOwningQPDF(),
1261
0
            QPDFExc(
1262
0
                qpdf_e_damaged_pdf,
1263
0
                "",
1264
0
                description,
1265
0
                0,
1266
0
                " object is supposed to be a stream or an array of streams but is neither"));
1267
0
    }
1268
1269
0
    bool first = true;
1270
0
    for (auto const& item: result) {
1271
0
        if (first) {
1272
0
            first = false;
1273
0
        } else {
1274
0
            all_description += ",";
1275
0
        }
1276
0
        all_description += " stream " + item.getObjGen().unparse(' ');
1277
0
    }
1278
1279
0
    return result;
1280
0
}
1281
1282
std::vector<QPDFObjectHandle>
1283
QPDFObjectHandle::getPageContents()
1284
0
{
1285
0
    std::string description = "page object " + getObjGen().unparse(' ');
1286
0
    std::string all_description;
1287
0
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1288
0
}
1289
1290
void
1291
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1292
0
{
1293
0
    new_contents.assertStream();
1294
1295
0
    std::vector<QPDFObjectHandle> content_streams;
1296
0
    if (first) {
1297
0
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1298
0
        content_streams.push_back(new_contents);
1299
0
    }
1300
0
    for (auto const& iter: getPageContents()) {
1301
0
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1302
0
        content_streams.push_back(iter);
1303
0
    }
1304
0
    if (!first) {
1305
0
        content_streams.push_back(new_contents);
1306
0
    }
1307
1308
0
    replaceKey("/Contents", newArray(content_streams));
1309
0
}
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
0
{
1347
0
    QPDFObjectHandle contents = getKey("/Contents");
1348
0
    if (contents.isStream()) {
1349
0
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1350
0
        return;
1351
0
    } 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
0
        return;
1355
0
    }
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
0
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1361
1362
0
    QPDFObjectHandle new_contents = newStream(&qpdf);
1363
0
    replaceKey("/Contents", new_contents);
1364
1365
0
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1366
0
    new_contents.replaceStreamData(provider, newNull(), newNull());
1367
0
}
1368
1369
std::string
1370
QPDFObjectHandle::unparse() const
1371
11
{
1372
11
    if (isIndirect()) {
1373
11
        return getObjGen().unparse(' ') + " R";
1374
11
    } else {
1375
0
        return unparseResolved();
1376
0
    }
1377
11
}
1378
1379
std::string
1380
QPDFObjectHandle::unparseResolved() const
1381
1.44M
{
1382
1.44M
    if (!obj) {
1383
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1384
0
    }
1385
1.44M
    return BaseHandle::unparse();
1386
1.44M
}
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
0
{
1401
0
    if ((!dereference_indirect) && isIndirect()) {
1402
0
        return JSON::makeString(unparse());
1403
0
    } else if (!obj) {
1404
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1405
0
    } else {
1406
0
        Pl_Buffer p{"json"};
1407
0
        JSON::Writer jw{&p, 0};
1408
0
        writeJSON(json_version, jw, dereference_indirect);
1409
0
        p.finish();
1410
0
        return JSON::parse(p.getString());
1411
0
    }
1412
0
}
1413
1414
void
1415
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1416
0
{
1417
0
    if (!dereference_indirect && isIndirect()) {
1418
0
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1419
0
    } else if (!obj) {
1420
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1421
0
    } else {
1422
0
        write_json(json_version, p);
1423
0
    }
1424
0
}
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
25.4k
{
1448
25.4k
    return parse(nullptr, object_str, object_description);
1449
25.4k
}
1450
1451
QPDFObjectHandle
1452
QPDFObjectHandle::parse(
1453
    QPDF* context, std::string const& object_str, std::string const& object_description)
1454
25.4k
{
1455
    // BufferInputSource does not modify the input, but Buffer either requires a string& or copies
1456
    // the string.
1457
25.4k
    Buffer buf(const_cast<std::string&>(object_str));
1458
25.4k
    auto input = BufferInputSource("parsed object", &buf);
1459
25.4k
    auto result = QPDFParser::parse(input, object_description, context);
1460
25.4k
    size_t offset = QIntC::to_size(input.tell());
1461
25.4k
    while (offset < object_str.length()) {
1462
0
        if (!isspace(object_str.at(offset))) {
1463
0
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1464
0
            throw QPDFExc(
1465
0
                qpdf_e_damaged_pdf,
1466
0
                "parsed object",
1467
0
                object_description,
1468
0
                input.getLastOffset(),
1469
0
                "trailing data found parsing object from string");
1470
0
        }
1471
0
        ++offset;
1472
0
    }
1473
25.4k
    return result;
1474
25.4k
}
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
0
{
1488
0
    bool need_newline = false;
1489
0
    std::string buffer;
1490
0
    pl::String buf(buffer);
1491
0
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1492
0
        if (need_newline) {
1493
0
            buf.writeCStr("\n");
1494
0
        }
1495
0
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1496
0
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1497
0
            throw QPDFExc(
1498
0
                qpdf_e_damaged_pdf,
1499
0
                "content stream",
1500
0
                "content stream object " + stream.getObjGen().unparse(' '),
1501
0
                0,
1502
0
                "errors while decoding content stream");
1503
0
        }
1504
0
        need_newline = buffer.empty() || buffer.back() != '\n';
1505
0
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1506
0
        p->writeString(buffer);
1507
0
        buffer.clear();
1508
0
    }
1509
0
    p->finish();
1510
0
}
1511
1512
void
1513
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1514
0
{
1515
0
    std::string description = "page object " + getObjGen().unparse(' ');
1516
0
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1517
0
}
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
0
{
1552
0
    Pl_Buffer buf("concatenated stream data buffer");
1553
0
    std::string all_description;
1554
0
    pipeContentStreams(&buf, description, all_description);
1555
0
    auto stream_data = buf.getBufferSharedPointer();
1556
0
    callbacks->contentSize(stream_data->getSize());
1557
0
    try {
1558
0
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1559
0
    } catch (TerminateParsing&) {
1560
0
        return;
1561
0
    }
1562
0
    callbacks->handleEOF();
1563
0
}
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
0
{
1572
0
    size_t stream_length = stream_data->getSize();
1573
0
    auto input = BufferInputSource(description, stream_data.get());
1574
0
    Tokenizer tokenizer;
1575
0
    tokenizer.allowEOF();
1576
0
    auto sp_description = QPDFParser::make_description(description, "content");
1577
0
    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
0
        tokenizer.nextToken(input, "content", true);
1582
0
        qpdf_offset_t offset = input.getLastOffset();
1583
0
        input.seek(offset, SEEK_SET);
1584
0
        auto obj = QPDFParser::parse_content(input, sp_description, tokenizer, context);
1585
0
        if (!obj) {
1586
            // EOF
1587
0
            break;
1588
0
        }
1589
0
        size_t length = QIntC::to_size(input.tell() - offset);
1590
1591
0
        callbacks->handleObject(obj, QIntC::to_size(offset), length);
1592
0
        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
0
            char ch;
1596
0
            input.read(&ch, 1);
1597
0
            tokenizer.expectInlineImage(input);
1598
0
            tokenizer.nextToken(input, description);
1599
0
            offset = input.getLastOffset();
1600
0
            length = QIntC::to_size(input.tell() - offset);
1601
0
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1602
0
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1603
0
                warn(
1604
0
                    context,
1605
0
                    QPDFExc(
1606
0
                        qpdf_e_damaged_pdf,
1607
0
                        description,
1608
0
                        "stream data",
1609
0
                        input.tell(),
1610
0
                        "EOF found while reading inline image"));
1611
0
            } else {
1612
0
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1613
0
                callbacks->handleObject(
1614
0
                    QPDFObjectHandle::newInlineImage(tokenizer.getValue()),
1615
0
                    QIntC::to_size(offset),
1616
0
                    length);
1617
0
            }
1618
0
        }
1619
0
    }
1620
0
}
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
0
{
1632
0
    return as_stream(error).addTokenFilter(filter);
1633
0
}
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
0
{
1650
0
    return obj ? obj->getParsedOffset() : -1;
1651
0
}
1652
1653
QPDFObjectHandle
1654
QPDFObjectHandle::newBool(bool value)
1655
0
{
1656
0
    return {QPDFObject::create<QPDF_Bool>(value)};
1657
0
}
1658
1659
QPDFObjectHandle
1660
QPDFObjectHandle::newNull()
1661
2.68k
{
1662
2.68k
    return {QPDFObject::create<QPDF_Null>()};
1663
2.68k
}
1664
1665
QPDFObjectHandle
1666
QPDFObjectHandle::newInteger(long long value)
1667
1.91k
{
1668
1.91k
    return {QPDFObject::create<QPDF_Integer>(value)};
1669
1.91k
}
1670
1671
QPDFObjectHandle
1672
QPDFObjectHandle::newReal(std::string const& value)
1673
0
{
1674
0
    return {QPDFObject::create<QPDF_Real>(value)};
1675
0
}
1676
1677
QPDFObjectHandle
1678
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1679
26.6k
{
1680
26.6k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1681
26.6k
}
1682
1683
QPDFObjectHandle
1684
QPDFObjectHandle::newName(std::string const& name)
1685
0
{
1686
0
    return {QPDFObject::create<QPDF_Name>(name)};
1687
0
}
1688
1689
QPDFObjectHandle
1690
QPDFObjectHandle::newString(std::string const& str)
1691
2
{
1692
2
    return {QPDFObject::create<QPDF_String>(str)};
1693
2
}
1694
1695
QPDFObjectHandle
1696
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1697
0
{
1698
0
    return {QPDF_String::create_utf16(utf8_str)};
1699
0
}
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
0
{
1710
0
    return {QPDFObject::create<QPDF_InlineImage>(value)};
1711
0
}
1712
1713
QPDFObjectHandle
1714
QPDFObjectHandle::newArray()
1715
0
{
1716
0
    return newArray(std::vector<QPDFObjectHandle>());
1717
0
}
1718
1719
QPDFObjectHandle
1720
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1721
9.19k
{
1722
9.19k
    return {QPDFObject::create<QPDF_Array>(items)};
1723
9.19k
}
1724
1725
QPDFObjectHandle
1726
QPDFObjectHandle::newArray(Rectangle const& rect)
1727
6.66k
{
1728
6.66k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1729
6.66k
}
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
0
{
1758
0
    return newArray(rect);
1759
0
}
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
19.2k
{
1776
19.2k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1777
19.2k
}
1778
1779
QPDFObjectHandle
1780
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1781
19.2k
{
1782
19.2k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1783
19.2k
}
1784
1785
QPDFObjectHandle
1786
QPDFObjectHandle::newStream(QPDF* qpdf)
1787
0
{
1788
0
    if (qpdf == nullptr) {
1789
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1790
0
    }
1791
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1792
0
    return qpdf->newStream();
1793
0
}
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
0
{
1808
0
    if (qpdf == nullptr) {
1809
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1810
0
    }
1811
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with string");
1812
0
    return qpdf->newStream(data);
1813
0
}
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
0
{
1827
0
    if (obj) {
1828
0
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
1829
0
        obj->setDescription(owning_qpdf, descr);
1830
0
    }
1831
0
}
1832
1833
bool
1834
QPDFObjectHandle::hasObjectDescription() const
1835
49.8k
{
1836
49.8k
    return obj && obj->hasDescription();
1837
49.8k
}
1838
1839
QPDFObjectHandle
1840
QPDFObjectHandle::shallowCopy()
1841
119
{
1842
119
    if (!obj) {
1843
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1844
0
    }
1845
119
    return {copy()};
1846
119
}
1847
1848
QPDFObjectHandle
1849
QPDFObjectHandle::unsafeShallowCopy()
1850
92.4k
{
1851
92.4k
    if (!obj) {
1852
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1853
0
    }
1854
92.4k
    return {copy(true)};
1855
92.4k
}
1856
1857
void
1858
QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
1859
6.58k
{
1860
6.58k
    assertInitialized();
1861
1862
6.58k
    auto cur_og = getObjGen();
1863
6.58k
    if (!visited.add(cur_og)) {
1864
23
        QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
1865
23
        throw std::runtime_error("loop detected while converting object from indirect to direct");
1866
23
    }
1867
1868
6.55k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
1869
5.21k
        obj = copy(true);
1870
5.21k
    } else if (auto a = as_array(strict)) {
1871
870
        std::vector<QPDFObjectHandle> items;
1872
4.69k
        for (auto const& item: a) {
1873
4.69k
            items.emplace_back(item);
1874
4.69k
            items.back().makeDirect(visited, stop_at_streams);
1875
4.69k
        }
1876
870
        obj = QPDFObject::create<QPDF_Array>(items);
1877
870
    } else if (isDictionary()) {
1878
478
        std::map<std::string, QPDFObjectHandle> items;
1879
2.05k
        for (auto const& [key, value]: as_dictionary(strict)) {
1880
2.05k
            if (!value.null()) {
1881
1.75k
                items.insert({key, value});
1882
1.75k
                items[key].makeDirect(visited, stop_at_streams);
1883
1.75k
            }
1884
2.05k
        }
1885
478
        obj = QPDFObject::create<QPDF_Dictionary>(items);
1886
478
    } else if (isStream()) {
1887
1
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
1888
1
        if (!stop_at_streams) {
1889
1
            throw std::runtime_error("attempt to make a stream into a direct object");
1890
1
        }
1891
1
    } 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
6.55k
    visited.erase(cur_og);
1899
6.55k
}
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
128
{
1922
128
    QPDFObjGen::set visited;
1923
128
    makeDirect(visited, allow_streams);
1924
128
}
1925
1926
void
1927
QPDFObjectHandle::assertInitialized() const
1928
6.58k
{
1929
6.58k
    if (!obj) {
1930
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1931
0
    }
1932
6.58k
}
1933
1934
void
1935
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& warning) const
1936
6.40k
{
1937
6.40k
    QPDF* context = nullptr;
1938
6.40k
    std::string description;
1939
    // Type checks above guarantee that the object has been dereferenced. Nevertheless, dereference
1940
    // throws exceptions in the test suite
1941
6.40k
    if (!obj) {
1942
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1943
0
    }
1944
6.40k
    obj->getDescription(context, description);
1945
    // Null context handled by warn
1946
6.40k
    warn(
1947
6.40k
        context,
1948
6.40k
        QPDFExc(
1949
6.40k
            qpdf_e_object,
1950
6.40k
            "",
1951
6.40k
            description,
1952
6.40k
            0,
1953
6.40k
            std::string("operation for ") + expected_type + " attempted on object of type " +
1954
6.40k
                QPDFObjectHandle(*this).getTypeName() + ": " + warning));
1955
6.40k
}
1956
1957
void
1958
QPDFObjectHandle::warnIfPossible(std::string const& warning) const
1959
57.0k
{
1960
57.0k
    QPDF* context = nullptr;
1961
57.0k
    std::string description;
1962
57.0k
    if (obj && obj->getDescription(context, description)) {
1963
41.8k
        warn(context, QPDFExc(qpdf_e_damaged_pdf, "", description, 0, warning));
1964
41.8k
    } else {
1965
15.1k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
1966
15.1k
    }
1967
57.0k
}
1968
1969
void
1970
QPDFObjectHandle::objectWarning(std::string const& warning) const
1971
5
{
1972
5
    QPDF* context = nullptr;
1973
5
    std::string description;
1974
    // Type checks above guarantee that the object is initialized.
1975
5
    obj->getDescription(context, description);
1976
    // Null context handled by warn
1977
5
    warn(context, QPDFExc(qpdf_e_object, "", description, 0, warning));
1978
5
}
1979
1980
void
1981
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
1982
126
{
1983
126
    if (!istype) {
1984
126
        throw std::runtime_error(
1985
126
            std::string("operation for ") + type_name + " attempted on object of type " +
1986
126
            QPDFObjectHandle(*this).getTypeName());
1987
126
    }
1988
126
}
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
0
{
2053
0
    assertType("stream", isStream());
2054
0
}
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
0
{
2108
0
    return isStreamOfType("", "/Form");
2109
0
}
2110
2111
bool
2112
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2113
0
{
2114
0
    return (
2115
0
        isStreamOfType("", "/Image") &&
2116
0
        ((!exclude_imagemask) ||
2117
0
         (!(getDict().getKey("/ImageMask").isBool() &&
2118
0
            getDict().getKey("/ImageMask").getBoolValue()))));
2119
0
}
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
48.2k
{
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
48.2k
    if (qpdf) {
2136
47.9k
        qpdf->warn(e);
2137
47.9k
    } else {
2138
313
        throw e;
2139
313
    }
2140
48.2k
}
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
9.73k
    oh(oh)
2229
9.73k
{
2230
9.73k
}
2231
2232
QPDFObjectHandle::QPDFArrayItems::iterator&
2233
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2234
47.1k
{
2235
47.1k
    if (!m->is_end) {
2236
47.1k
        ++m->item_number;
2237
47.1k
        updateIValue();
2238
47.1k
    }
2239
47.1k
    return *this;
2240
47.1k
}
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
47.3k
{
2255
47.3k
    updateIValue();
2256
47.3k
    return ivalue;
2257
47.3k
}
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
56.8k
{
2269
56.8k
    return (m->item_number == other.m->item_number);
2270
56.8k
}
2271
2272
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2273
19.4k
    m(new Members(oh, for_begin))
2274
19.4k
{
2275
19.4k
    updateIValue();
2276
19.4k
}
2277
2278
void
2279
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2280
113k
{
2281
113k
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2282
113k
    if (m->is_end) {
2283
19.3k
        ivalue = QPDFObjectHandle();
2284
94.6k
    } else {
2285
94.6k
        ivalue = m->oh.getArrayItem(m->item_number);
2286
94.6k
    }
2287
113k
}
2288
2289
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2290
19.4k
    oh(oh)
2291
19.4k
{
2292
19.4k
    item_number = for_begin ? 0 : oh.getArrayNItems();
2293
19.4k
}
2294
2295
QPDFObjectHandle::QPDFArrayItems::iterator
2296
QPDFObjectHandle::QPDFArrayItems::begin()
2297
9.73k
{
2298
9.73k
    return {oh, true};
2299
9.73k
}
2300
2301
QPDFObjectHandle::QPDFArrayItems::iterator
2302
QPDFObjectHandle::QPDFArrayItems::end()
2303
9.73k
{
2304
9.73k
    return {oh, false};
2305
9.73k
}
2306
2307
QPDFObjGen
2308
QPDFObjectHandle::getObjGen() const
2309
9.97M
{
2310
9.97M
    return obj ? obj->getObjGen() : QPDFObjGen();
2311
9.97M
}
2312
2313
int
2314
QPDFObjectHandle::getObjectID() const
2315
6.56M
{
2316
6.56M
    return getObjGen().getObj();
2317
6.56M
}
2318
2319
int
2320
QPDFObjectHandle::getGeneration() const
2321
0
{
2322
0
    return getObjGen().getGen();
2323
0
}
2324
2325
bool
2326
QPDFObjectHandle::isIndirect() const
2327
4.57M
{
2328
4.57M
    return getObjectID() != 0;
2329
4.57M
}
2330
2331
// Indirect object accessors
2332
QPDF*
2333
QPDFObjectHandle::getOwningQPDF() const
2334
269k
{
2335
269k
    return obj ? obj->getQPDF() : nullptr;
2336
269k
}
2337
2338
QPDF&
2339
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2340
0
{
2341
0
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2342
0
        return *result;
2343
0
    }
2344
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2345
0
}
2346
2347
void
2348
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2349
2
{
2350
2
    if (obj) {
2351
2
        obj->setParsedOffset(offset);
2352
2
    }
2353
2
}
2354
2355
QPDFObjectHandle
2356
operator""_qpdf(char const* v, size_t len)
2357
10.9k
{
2358
10.9k
    return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal");
2359
10.9k
}