Coverage Report

Created: 2025-08-03 06:15

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