Coverage Report

Created: 2025-08-03 06:18

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