Coverage Report

Created: 2025-07-12 06:25

/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
0
{
34
0
    return obj ? obj->getObjGen() : QPDFObjGen();
35
0
}
36
37
namespace
38
{
39
    class TerminateParsing
40
    {
41
    };
42
} // namespace
43
44
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
45
8.45k
    supports_retry(supports_retry)
46
8.45k
{
47
8.45k
}
48
49
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
50
8.45k
{
51
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
52
8.45k
}
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
0
{
227
0
    if (name.empty()) {
228
0
        return name;
229
0
    }
230
0
    std::string result;
231
0
    result += name.at(0);
232
0
    for (size_t i = 1; i < name.length(); ++i) {
233
0
        char ch = name.at(i);
234
        // Don't use locale/ctype here; follow PDF spec guidelines.
235
0
        if (ch == '\0') {
236
            // QPDFTokenizer embeds a null character to encode an invalid #.
237
0
            result += "#";
238
0
        } else if (
239
0
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
240
0
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
241
0
            result += util::hex_encode_char(ch);
242
0
        } else {
243
0
            result += ch;
244
0
        }
245
0
    }
246
0
    return result;
247
0
}
248
249
std::shared_ptr<QPDFObject>
250
BaseHandle::copy(bool shallow) const
251
0
{
252
0
    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
0
    case ::ot_null:
259
0
        return QPDFObject::create<QPDF_Null>();
260
0
    case ::ot_boolean:
261
0
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
262
0
    case ::ot_integer:
263
0
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
264
0
    case ::ot_real:
265
0
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
266
0
    case ::ot_string:
267
0
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
268
0
    case ::ot_name:
269
0
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
270
0
    case ::ot_array:
271
0
        {
272
0
            auto const& a = std::get<QPDF_Array>(obj->value);
273
0
            if (shallow) {
274
0
                return QPDFObject::create<QPDF_Array>(a);
275
0
            } else {
276
0
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
277
0
                if (a.sp) {
278
0
                    QPDF_Array result;
279
0
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
280
0
                    result.sp->size = a.sp->size;
281
0
                    for (auto const& [idx, oh]: a.sp->elements) {
282
0
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
283
0
                    }
284
0
                    return QPDFObject::create<QPDF_Array>(std::move(result));
285
0
                } else {
286
0
                    std::vector<QPDFObjectHandle> result;
287
0
                    result.reserve(a.elements.size());
288
0
                    for (auto const& element: a.elements) {
289
0
                        result.emplace_back(
290
0
                            element ? (element.indirect() ? element : element.copy()) : element);
291
0
                    }
292
0
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
293
0
                }
294
0
            }
295
0
        }
296
0
    case ::ot_dictionary:
297
0
        {
298
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
299
0
            if (shallow) {
300
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
301
0
            } else {
302
0
                std::map<std::string, QPDFObjectHandle> new_items;
303
0
                for (auto const& [key, val]: d.items) {
304
0
                    new_items[key] = val.indirect() ? val : val.copy();
305
0
                }
306
0
                return QPDFObject::create<QPDF_Dictionary>(new_items);
307
0
            }
308
0
        }
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
0
    }
326
0
    return {}; // unreachable
327
0
}
328
329
std::string
330
BaseHandle::unparse() const
331
0
{
332
0
    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
0
    case ::ot_null:
340
0
        return "null";
341
0
    case ::ot_boolean:
342
0
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
343
0
    case ::ot_integer:
344
0
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
345
0
    case ::ot_real:
346
0
        return std::get<QPDF_Real>(obj->value).val;
347
0
    case ::ot_string:
348
0
        return std::get<QPDF_String>(obj->value).unparse(false);
349
0
    case ::ot_name:
350
0
        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
0
    }
403
0
    return {}; // unreachable
404
0
}
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.34M
{
543
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
544
2.34M
    if (only_direct && indirect()) {
545
426k
        return;
546
426k
    }
547
548
1.91M
    switch (raw_type_code()) {
549
8.27k
    case ::ot_array:
550
8.27k
        {
551
8.27k
            auto& a = std::get<QPDF_Array>(obj->value);
552
8.27k
            if (a.sp) {
553
0
                for (auto& item: a.sp->elements) {
554
0
                    item.second.disconnect();
555
0
                }
556
8.27k
            } else {
557
2.26M
                for (auto& oh: a.elements) {
558
2.26M
                    oh.disconnect();
559
2.26M
                }
560
8.27k
            }
561
8.27k
        }
562
8.27k
        break;
563
10.4k
    case ::ot_dictionary:
564
64.1k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
565
64.1k
            iter.second.disconnect();
566
64.1k
        }
567
10.4k
        break;
568
3.59k
    case ::ot_stream:
569
3.59k
        {
570
3.59k
            auto& s = std::get<QPDF_Stream>(obj->value);
571
3.59k
            s.m->stream_provider = nullptr;
572
3.59k
            s.m->stream_dict.disconnect();
573
3.59k
        }
574
3.59k
        break;
575
0
    case ::ot_uninitialized:
576
0
        return;
577
1.89M
    default:
578
1.89M
        break;
579
1.91M
    }
580
1.91M
    obj->qpdf = nullptr;
581
1.91M
    obj->og = QPDFObjGen();
582
1.91M
}
583
584
std::string
585
QPDFObject::getStringValue() const
586
3.92k
{
587
3.92k
    switch (getResolvedTypeCode()) {
588
0
    case ::ot_real:
589
0
        return std::get<QPDF_Real>(value).val;
590
0
    case ::ot_string:
591
0
        return std::get<QPDF_String>(value).val;
592
3.92k
    case ::ot_name:
593
3.92k
        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
3.92k
    }
603
0
    return ""; // unreachable
604
3.92k
}
605
606
bool
607
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
608
0
{
609
0
    return obj == rhs.obj;
610
0
}
611
612
qpdf_object_type_e
613
QPDFObjectHandle::getTypeCode() const
614
0
{
615
0
    return obj ? obj->getResolvedTypeCode() : ::ot_uninitialized;
616
0
}
617
618
char const*
619
QPDFObjectHandle::getTypeName() const
620
0
{
621
0
    static constexpr std::array<char const*, 16> tn{
622
0
        "uninitialized",
623
0
        "reserved",
624
0
        "null",
625
0
        "boolean",
626
0
        "integer",
627
0
        "real",
628
0
        "string",
629
0
        "name",
630
0
        "array",
631
0
        "dictionary",
632
0
        "stream",
633
0
        "operator",
634
0
        "inline-image",
635
0
        "unresolved",
636
0
        "destroyed",
637
0
        "reference"};
638
0
    return obj ? tn[getTypeCode()] : "uninitialized";
639
0
}
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
0
{
650
0
    return type_code() == ::ot_boolean;
651
0
}
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
145k
{
664
145k
    return type_code() == ::ot_null;
665
145k
}
666
667
bool
668
QPDFObjectHandle::isInteger() const
669
7.26k
{
670
7.26k
    return type_code() == ::ot_integer;
671
7.26k
}
672
673
bool
674
QPDFObjectHandle::isReal() const
675
0
{
676
0
    return type_code() == ::ot_real;
677
0
}
678
679
bool
680
QPDFObjectHandle::isNumber() const
681
0
{
682
0
    return (isInteger() || isReal());
683
0
}
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
3.92k
{
712
3.92k
    return type_code() == ::ot_name;
713
3.92k
}
714
715
bool
716
QPDFObjectHandle::isString() const
717
0
{
718
0
    return type_code() == ::ot_string;
719
0
}
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
0
{
736
0
    return type_code() == ::ot_array;
737
0
}
738
739
bool
740
QPDFObjectHandle::isDictionary() const
741
165k
{
742
165k
    return type_code() == ::ot_dictionary;
743
165k
}
744
745
bool
746
QPDFObjectHandle::isStream() const
747
185k
{
748
185k
    return type_code() == ::ot_stream;
749
185k
}
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
0
{
760
0
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
761
0
}
762
763
bool
764
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
765
0
{
766
0
    return isName() && (getName() == name);
767
0
}
768
769
bool
770
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
771
0
{
772
0
    return isDictionary() && (type.empty() || getKey("/Type").isNameAndEquals(type)) &&
773
0
        (subtype.empty() || getKey("/Subtype").isNameAndEquals(subtype));
774
0
}
775
776
bool
777
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
778
0
{
779
0
    return isStream() && getDict().isDictionaryOfType(type, subtype);
780
0
}
781
782
// Bool accessors
783
784
bool
785
QPDFObjectHandle::getBoolValue() const
786
0
{
787
0
    if (auto boolean = as<QPDF_Bool>()) {
788
0
        return boolean->val;
789
0
    } else {
790
0
        typeWarning("boolean", "returning false");
791
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
792
0
        return false;
793
0
    }
794
0
}
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
7.26k
{
811
7.26k
    if (auto integer = as<QPDF_Integer>()) {
812
7.26k
        return integer->val;
813
7.26k
    } else {
814
0
        typeWarning("integer", "returning 0");
815
0
        QTC::TC("qpdf", "QPDFObjectHandle integer returning 0");
816
0
        return 0;
817
0
    }
818
7.26k
}
819
820
bool
821
QPDFObjectHandle::getValueAsInt(long long& value) const
822
0
{
823
0
    if (auto integer = as<QPDF_Integer>()) {
824
0
        value = integer->val;
825
0
        return true;
826
0
    }
827
0
    return false;
828
0
}
829
830
int
831
QPDFObjectHandle::getIntValueAsInt() const
832
7.26k
{
833
7.26k
    int result = 0;
834
7.26k
    long long v = getIntValue();
835
7.26k
    if (v < INT_MIN) {
836
0
        QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MIN");
837
0
        warnIfPossible("requested value of integer is too small; returning INT_MIN");
838
0
        result = INT_MIN;
839
7.26k
    } else if (v > INT_MAX) {
840
0
        QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MAX");
841
0
        warnIfPossible("requested value of integer is too big; returning INT_MAX");
842
0
        result = INT_MAX;
843
7.26k
    } else {
844
7.26k
        result = static_cast<int>(v);
845
7.26k
    }
846
7.26k
    return result;
847
7.26k
}
848
849
bool
850
QPDFObjectHandle::getValueAsInt(int& value) const
851
0
{
852
0
    if (!isInteger()) {
853
0
        return false;
854
0
    }
855
0
    value = getIntValueAsInt();
856
0
    return true;
857
0
}
858
859
unsigned long long
860
QPDFObjectHandle::getUIntValue() const
861
0
{
862
0
    long long v = getIntValue();
863
0
    if (v < 0) {
864
0
        QTC::TC("qpdf", "QPDFObjectHandle uint returning 0");
865
0
        warnIfPossible("unsigned value request for negative number; returning 0");
866
0
        return 0;
867
0
    } else {
868
0
        return static_cast<unsigned long long>(v);
869
0
    }
870
0
}
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
0
{
885
0
    long long v = getIntValue();
886
0
    if (v < 0) {
887
0
        QTC::TC("qpdf", "QPDFObjectHandle uint uint returning 0");
888
0
        warnIfPossible("unsigned integer value request for negative number; returning 0");
889
0
        return 0;
890
0
    } else if (v > UINT_MAX) {
891
0
        QTC::TC("qpdf", "QPDFObjectHandle uint returning UINT_MAX");
892
0
        warnIfPossible("requested value of unsigned integer is too big; returning UINT_MAX");
893
0
        return UINT_MAX;
894
0
    } else {
895
0
        return static_cast<unsigned int>(v);
896
0
    }
897
0
}
898
899
bool
900
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
901
0
{
902
0
    if (!isInteger()) {
903
0
        return false;
904
0
    }
905
0
    value = getUIntValueAsUInt();
906
0
    return true;
907
0
}
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
3.92k
{
938
3.92k
    if (isName()) {
939
3.92k
        return obj->getStringValue();
940
3.92k
    } 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
3.92k
}
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
0
{
962
0
    if (isString()) {
963
0
        return obj->getStringValue();
964
0
    } else {
965
0
        typeWarning("string", "returning empty string");
966
0
        QTC::TC("qpdf", "QPDFObjectHandle string returning empty string");
967
0
        return "";
968
0
    }
969
0
}
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
0
{
1054
0
    return *this;
1055
0
}
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
0
{
1070
0
    if (isNameAndEquals(value)) {
1071
0
        return true;
1072
0
    } else if (isArray()) {
1073
0
        for (auto& item: getArrayAsVector()) {
1074
0
            if (item.isNameAndEquals(value)) {
1075
0
                return true;
1076
0
            }
1077
0
        }
1078
0
    }
1079
0
    return false;
1080
0
}
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
0
{
1372
0
    if (isIndirect()) {
1373
0
        return getObjGen().unparse(' ') + " R";
1374
0
    } else {
1375
0
        return unparseResolved();
1376
0
    }
1377
0
}
1378
1379
std::string
1380
QPDFObjectHandle::unparseResolved() const
1381
0
{
1382
0
    if (!obj) {
1383
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1384
0
    }
1385
0
    return BaseHandle::unparse();
1386
0
}
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
11.3k
{
1448
11.3k
    return parse(nullptr, object_str, object_description);
1449
11.3k
}
1450
1451
QPDFObjectHandle
1452
QPDFObjectHandle::parse(
1453
    QPDF* context, std::string const& object_str, std::string const& object_description)
1454
11.3k
{
1455
    // BufferInputSource does not modify the input, but Buffer either requires a string& or copies
1456
    // the string.
1457
11.3k
    Buffer buf(const_cast<std::string&>(object_str));
1458
11.3k
    auto input = BufferInputSource("parsed object", &buf);
1459
11.3k
    auto result = QPDFParser::parse(input, object_description, context);
1460
11.3k
    size_t offset = QIntC::to_size(input.tell());
1461
886k
    while (offset < object_str.length()) {
1462
875k
        if (!isspace(object_str.at(offset))) {
1463
89
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1464
89
            throw QPDFExc(
1465
89
                qpdf_e_damaged_pdf,
1466
89
                "parsed object",
1467
89
                object_description,
1468
89
                input.getLastOffset(),
1469
89
                "trailing data found parsing object from string");
1470
89
        }
1471
875k
        ++offset;
1472
875k
    }
1473
11.2k
    return result;
1474
11.3k
}
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
937
{
1650
937
    return obj ? obj->getParsedOffset() : -1;
1651
937
}
1652
1653
QPDFObjectHandle
1654
QPDFObjectHandle::newBool(bool value)
1655
948
{
1656
948
    return {QPDFObject::create<QPDF_Bool>(value)};
1657
948
}
1658
1659
QPDFObjectHandle
1660
QPDFObjectHandle::newNull()
1661
30.1k
{
1662
30.1k
    return {QPDFObject::create<QPDF_Null>()};
1663
30.1k
}
1664
1665
QPDFObjectHandle
1666
QPDFObjectHandle::newInteger(long long value)
1667
1.92M
{
1668
1.92M
    return {QPDFObject::create<QPDF_Integer>(value)};
1669
1.92M
}
1670
1671
QPDFObjectHandle
1672
QPDFObjectHandle::newReal(std::string const& value)
1673
4.02k
{
1674
4.02k
    return {QPDFObject::create<QPDF_Real>(value)};
1675
4.02k
}
1676
1677
QPDFObjectHandle
1678
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1679
0
{
1680
0
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1681
0
}
1682
1683
QPDFObjectHandle
1684
QPDFObjectHandle::newName(std::string const& name)
1685
16.2k
{
1686
16.2k
    return {QPDFObject::create<QPDF_Name>(name)};
1687
16.2k
}
1688
1689
QPDFObjectHandle
1690
QPDFObjectHandle::newString(std::string const& str)
1691
1.19k
{
1692
1.19k
    return {QPDFObject::create<QPDF_String>(str)};
1693
1.19k
}
1694
1695
QPDFObjectHandle
1696
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1697
6.54k
{
1698
6.54k
    return {QPDF_String::create_utf16(utf8_str)};
1699
6.54k
}
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
13.2k
{
1716
13.2k
    return newArray(std::vector<QPDFObjectHandle>());
1717
13.2k
}
1718
1719
QPDFObjectHandle
1720
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1721
13.2k
{
1722
13.2k
    return {QPDFObject::create<QPDF_Array>(items)};
1723
13.2k
}
1724
1725
QPDFObjectHandle
1726
QPDFObjectHandle::newArray(Rectangle const& rect)
1727
0
{
1728
0
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1729
0
}
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
29.8k
{
1776
29.8k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1777
29.8k
}
1778
1779
QPDFObjectHandle
1780
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1781
29.8k
{
1782
29.8k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1783
29.8k
}
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
5.50k
{
1827
5.50k
    if (obj) {
1828
5.50k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
1829
5.50k
        obj->setDescription(owning_qpdf, descr);
1830
5.50k
    }
1831
5.50k
}
1832
1833
bool
1834
QPDFObjectHandle::hasObjectDescription() const
1835
2.49M
{
1836
2.49M
    return obj && obj->hasDescription();
1837
2.49M
}
1838
1839
QPDFObjectHandle
1840
QPDFObjectHandle::shallowCopy()
1841
0
{
1842
0
    if (!obj) {
1843
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1844
0
    }
1845
0
    return {copy()};
1846
0
}
1847
1848
QPDFObjectHandle
1849
QPDFObjectHandle::unsafeShallowCopy()
1850
0
{
1851
0
    if (!obj) {
1852
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1853
0
    }
1854
0
    return {copy(true)};
1855
0
}
1856
1857
void
1858
QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
1859
0
{
1860
0
    assertInitialized();
1861
1862
0
    auto cur_og = getObjGen();
1863
0
    if (!visited.add(cur_og)) {
1864
0
        QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
1865
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
1866
0
    }
1867
1868
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
1869
0
        obj = copy(true);
1870
0
    } else if (auto a = as_array(strict)) {
1871
0
        std::vector<QPDFObjectHandle> items;
1872
0
        for (auto const& item: a) {
1873
0
            items.emplace_back(item);
1874
0
            items.back().makeDirect(visited, stop_at_streams);
1875
0
        }
1876
0
        obj = QPDFObject::create<QPDF_Array>(items);
1877
0
    } else if (isDictionary()) {
1878
0
        std::map<std::string, QPDFObjectHandle> items;
1879
0
        for (auto const& [key, value]: as_dictionary(strict)) {
1880
0
            if (!value.null()) {
1881
0
                items.insert({key, value});
1882
0
                items[key].makeDirect(visited, stop_at_streams);
1883
0
            }
1884
0
        }
1885
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
1886
0
    } else if (isStream()) {
1887
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
1888
0
        if (!stop_at_streams) {
1889
0
            throw std::runtime_error("attempt to make a stream into a direct object");
1890
0
        }
1891
0
    } 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
0
    visited.erase(cur_og);
1899
0
}
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
0
{
1922
0
    QPDFObjGen::set visited;
1923
0
    makeDirect(visited, allow_streams);
1924
0
}
1925
1926
void
1927
QPDFObjectHandle::assertInitialized() const
1928
0
{
1929
0
    if (!obj) {
1930
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1931
0
    }
1932
0
}
1933
1934
void
1935
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& warning) const
1936
0
{
1937
0
    QPDF* context = nullptr;
1938
0
    std::string description;
1939
    // Type checks above guarantee that the object has been dereferenced. Nevertheless, dereference
1940
    // throws exceptions in the test suite
1941
0
    if (!obj) {
1942
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1943
0
    }
1944
0
    obj->getDescription(context, description);
1945
    // Null context handled by warn
1946
0
    warn(
1947
0
        context,
1948
0
        QPDFExc(
1949
0
            qpdf_e_object,
1950
0
            "",
1951
0
            description,
1952
0
            0,
1953
0
            std::string("operation for ") + expected_type + " attempted on object of type " +
1954
0
                QPDFObjectHandle(*this).getTypeName() + ": " + warning));
1955
0
}
1956
1957
void
1958
QPDFObjectHandle::warnIfPossible(std::string const& warning) const
1959
0
{
1960
0
    QPDF* context = nullptr;
1961
0
    std::string description;
1962
0
    if (obj && obj->getDescription(context, description)) {
1963
0
        warn(context, QPDFExc(qpdf_e_damaged_pdf, "", description, 0, warning));
1964
0
    } else {
1965
0
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
1966
0
    }
1967
0
}
1968
1969
void
1970
QPDFObjectHandle::objectWarning(std::string const& warning) const
1971
0
{
1972
0
    QPDF* context = nullptr;
1973
0
    std::string description;
1974
    // Type checks above guarantee that the object is initialized.
1975
0
    obj->getDescription(context, description);
1976
    // Null context handled by warn
1977
0
    warn(context, QPDFExc(qpdf_e_object, "", description, 0, warning));
1978
0
}
1979
1980
void
1981
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
1982
0
{
1983
0
    if (!istype) {
1984
0
        throw std::runtime_error(
1985
0
            std::string("operation for ") + type_name + " attempted on object of type " +
1986
0
            QPDFObjectHandle(*this).getTypeName());
1987
0
    }
1988
0
}
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
0
{
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
0
    if (qpdf) {
2136
0
        qpdf->warn(e);
2137
0
    } else {
2138
0
        throw e;
2139
0
    }
2140
0
}
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
0
    oh(oh)
2229
0
{
2230
0
}
2231
2232
QPDFObjectHandle::QPDFArrayItems::iterator&
2233
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2234
0
{
2235
0
    if (!m->is_end) {
2236
0
        ++m->item_number;
2237
0
        updateIValue();
2238
0
    }
2239
0
    return *this;
2240
0
}
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
0
{
2255
0
    updateIValue();
2256
0
    return ivalue;
2257
0
}
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
0
{
2269
0
    return (m->item_number == other.m->item_number);
2270
0
}
2271
2272
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2273
0
    m(new Members(oh, for_begin))
2274
0
{
2275
0
    updateIValue();
2276
0
}
2277
2278
void
2279
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2280
0
{
2281
0
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2282
0
    if (m->is_end) {
2283
0
        ivalue = QPDFObjectHandle();
2284
0
    } else {
2285
0
        ivalue = m->oh.getArrayItem(m->item_number);
2286
0
    }
2287
0
}
2288
2289
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2290
0
    oh(oh)
2291
0
{
2292
0
    item_number = for_begin ? 0 : oh.getArrayNItems();
2293
0
}
2294
2295
QPDFObjectHandle::QPDFArrayItems::iterator
2296
QPDFObjectHandle::QPDFArrayItems::begin()
2297
0
{
2298
0
    return {oh, true};
2299
0
}
2300
2301
QPDFObjectHandle::QPDFArrayItems::iterator
2302
QPDFObjectHandle::QPDFArrayItems::end()
2303
0
{
2304
0
    return {oh, false};
2305
0
}
2306
2307
QPDFObjGen
2308
QPDFObjectHandle::getObjGen() const
2309
141k
{
2310
141k
    return obj ? obj->getObjGen() : QPDFObjGen();
2311
141k
}
2312
2313
int
2314
QPDFObjectHandle::getObjectID() const
2315
98.9k
{
2316
98.9k
    return getObjGen().getObj();
2317
98.9k
}
2318
2319
int
2320
QPDFObjectHandle::getGeneration() const
2321
0
{
2322
0
    return getObjGen().getGen();
2323
0
}
2324
2325
bool
2326
QPDFObjectHandle::isIndirect() const
2327
98.9k
{
2328
98.9k
    return getObjectID() != 0;
2329
98.9k
}
2330
2331
// Indirect object accessors
2332
QPDF*
2333
QPDFObjectHandle::getOwningQPDF() const
2334
290k
{
2335
290k
    return obj ? obj->getQPDF() : nullptr;
2336
290k
}
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
0
{
2350
0
    if (obj) {
2351
0
        obj->setParsedOffset(offset);
2352
0
    }
2353
0
}
2354
2355
QPDFObjectHandle
2356
operator""_qpdf(char const* v, size_t len)
2357
0
{
2358
0
    return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal");
2359
0
}