Coverage Report

Created: 2025-12-14 06:35

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