Coverage Report

Created: 2025-06-22 06:27

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