Coverage Report

Created: 2025-12-05 06:57

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
51.8k
{
33
51.8k
    return obj ? obj->getObjGen() : QPDFObjGen();
34
51.8k
}
35
36
namespace
37
{
38
    class TerminateParsing
39
    {
40
    };
41
} // namespace
42
43
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
44
19.8k
    supports_retry(supports_retry)
45
19.8k
{
46
19.8k
}
47
48
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
49
19.8k
{
50
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
51
19.8k
}
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.40M
{
226
1.40M
    if (name.empty()) {
227
0
        return name;
228
0
    }
229
1.40M
    std::string result;
230
1.40M
    result += name.at(0);
231
43.6M
    for (size_t i = 1; i < name.length(); ++i) {
232
42.2M
        char ch = name.at(i);
233
        // Don't use locale/ctype here; follow PDF spec guidelines.
234
42.2M
        if (ch == '\0') {
235
            // QPDFTokenizer embeds a null character to encode an invalid #.
236
60.2k
            result += "#";
237
42.1M
        } else if (
238
42.1M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
239
25.9M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
240
25.9M
            result += util::hex_encode_char(ch);
241
25.9M
        } else {
242
16.1M
            result += ch;
243
16.1M
        }
244
42.2M
    }
245
1.40M
    return result;
246
1.40M
}
247
248
std::shared_ptr<QPDFObject>
249
BaseHandle::copy(bool shallow) const
250
90.5k
{
251
90.5k
    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
3.48k
    case ::ot_null:
258
3.48k
        return QPDFObject::create<QPDF_Null>();
259
104
    case ::ot_boolean:
260
104
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
261
2.65k
    case ::ot_integer:
262
2.65k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
263
1.62k
    case ::ot_real:
264
1.62k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
265
938
    case ::ot_string:
266
938
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
267
2.64k
    case ::ot_name:
268
2.64k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
269
158
    case ::ot_array:
270
158
        {
271
158
            auto const& a = std::get<QPDF_Array>(obj->value);
272
158
            if (shallow) {
273
0
                return QPDFObject::create<QPDF_Array>(a);
274
158
            } else {
275
158
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
276
158
                if (a.sp) {
277
15
                    QPDF_Array result;
278
15
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
279
15
                    result.sp->size = a.sp->size;
280
463
                    for (auto const& [idx, oh]: a.sp->elements) {
281
463
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
282
463
                    }
283
15
                    return QPDFObject::create<QPDF_Array>(std::move(result));
284
143
                } else {
285
143
                    std::vector<QPDFObjectHandle> result;
286
143
                    result.reserve(a.elements.size());
287
1.21k
                    for (auto const& element: a.elements) {
288
1.21k
                        result.emplace_back(
289
1.21k
                            element ? (element.indirect() ? element : element.copy()) : element);
290
1.21k
                    }
291
143
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
292
143
                }
293
158
            }
294
158
        }
295
78.8k
    case ::ot_dictionary:
296
78.8k
        {
297
78.8k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
298
78.8k
            if (shallow) {
299
78.3k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
300
78.3k
            } else {
301
580
                std::map<std::string, QPDFObjectHandle> new_items;
302
2.58k
                for (auto const& [key, val]: d.items) {
303
2.58k
                    new_items[key] = val.indirect() ? val : val.copy();
304
2.58k
                }
305
580
                return QPDFObject::create<QPDF_Dictionary>(new_items);
306
580
            }
307
78.8k
        }
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
90.5k
    }
325
0
    return {}; // unreachable
326
90.5k
}
327
328
std::string
329
BaseHandle::unparse() const
330
1.42M
{
331
1.42M
    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
259k
    case ::ot_null:
339
259k
        return "null";
340
14.4k
    case ::ot_boolean:
341
14.4k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
342
376k
    case ::ot_integer:
343
376k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
344
142k
    case ::ot_real:
345
142k
        return std::get<QPDF_Real>(obj->value).val;
346
22.1k
    case ::ot_string:
347
22.1k
        return std::get<QPDF_String>(obj->value).unparse(false);
348
605k
    case ::ot_name:
349
605k
        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
1.42M
    }
401
0
    return {}; // unreachable
402
1.42M
}
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
1.89M
{
540
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
541
1.89M
    if (only_direct && indirect()) {
542
217k
        return;
543
217k
    }
544
545
1.67M
    switch (raw_type_code()) {
546
96.1k
    case ::ot_array:
547
96.1k
        {
548
96.1k
            auto& a = std::get<QPDF_Array>(obj->value);
549
96.1k
            if (a.sp) {
550
43.7k
                for (auto& item: a.sp->elements) {
551
43.7k
                    item.second.disconnect();
552
43.7k
                }
553
95.9k
            } else {
554
640k
                for (auto& oh: a.elements) {
555
640k
                    oh.disconnect();
556
640k
                }
557
95.9k
            }
558
96.1k
        }
559
96.1k
        break;
560
188k
    case ::ot_dictionary:
561
709k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
562
709k
            iter.second.disconnect();
563
709k
        }
564
188k
        break;
565
34.5k
    case ::ot_stream:
566
34.5k
        {
567
34.5k
            auto& s = std::get<QPDF_Stream>(obj->value);
568
34.5k
            s.m->stream_provider = nullptr;
569
34.5k
            s.m->stream_dict.disconnect();
570
34.5k
        }
571
34.5k
        break;
572
0
    case ::ot_uninitialized:
573
0
        return;
574
1.35M
    default:
575
1.35M
        break;
576
1.67M
    }
577
1.67M
    obj->qpdf = nullptr;
578
1.67M
    obj->og = QPDFObjGen();
579
1.67M
}
580
581
std::string
582
QPDFObject::getStringValue() const
583
13.6k
{
584
13.6k
    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
13.6k
    case ::ot_name:
590
13.6k
        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
13.6k
    }
600
0
    return ""; // unreachable
601
13.6k
}
602
603
bool
604
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
605
998
{
606
998
    return obj == rhs.obj;
607
998
}
608
609
qpdf_object_type_e
610
QPDFObjectHandle::getTypeCode() const
611
1.86M
{
612
1.86M
    return type_code();
613
1.86M
}
614
615
char const*
616
BaseHandle::type_name() const
617
7.89k
{
618
7.89k
    static constexpr std::array<char const*, 16> tn{
619
7.89k
        "uninitialized",
620
7.89k
        "reserved",
621
7.89k
        "null",
622
7.89k
        "boolean",
623
7.89k
        "integer",
624
7.89k
        "real",
625
7.89k
        "string",
626
7.89k
        "name",
627
7.89k
        "array",
628
7.89k
        "dictionary",
629
7.89k
        "stream",
630
7.89k
        "operator",
631
7.89k
        "inline-image",
632
7.89k
        "unresolved",
633
7.89k
        "destroyed",
634
7.89k
        "reference"};
635
7.89k
    return tn[type_code()];
636
7.89k
}
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
17.0k
{
653
17.0k
    return type_code() == ::ot_boolean;
654
17.0k
}
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
12.6k
{
667
12.6k
    return type_code() == ::ot_null;
668
12.6k
}
669
670
bool
671
QPDFObjectHandle::isInteger() const
672
126k
{
673
126k
    return type_code() == ::ot_integer;
674
126k
}
675
676
bool
677
QPDFObjectHandle::isReal() const
678
12.7k
{
679
12.7k
    return type_code() == ::ot_real;
680
12.7k
}
681
682
bool
683
QPDFObjectHandle::isNumber() const
684
24.9k
{
685
24.9k
    return (isInteger() || isReal());
686
24.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
70.6k
{
715
70.6k
    return type_code() == ::ot_name;
716
70.6k
}
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
3.94M
{
739
3.94M
    return type_code() == ::ot_array;
740
3.94M
}
741
742
bool
743
QPDFObjectHandle::isDictionary() const
744
9.59M
{
745
9.59M
    return type_code() == ::ot_dictionary;
746
9.59M
}
747
748
bool
749
QPDFObjectHandle::isStream() const
750
4.60M
{
751
4.60M
    return type_code() == ::ot_stream;
752
4.60M
}
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
1.78k
{
763
1.78k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
764
1.78k
}
765
766
bool
767
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
768
69.1k
{
769
69.1k
    return Name(*this) == name;
770
69.1k
}
771
772
bool
773
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
774
4.65M
{
775
4.65M
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
776
118k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
777
4.65M
}
778
779
bool
780
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
781
84.1k
{
782
84.1k
    return isStream() && getDict().isDictionaryOfType(type, subtype);
783
84.1k
}
784
785
// Bool accessors
786
787
bool
788
QPDFObjectHandle::getBoolValue() const
789
13
{
790
13
    if (auto boolean = as<QPDF_Bool>()) {
791
13
        return boolean->val;
792
13
    } else {
793
0
        typeWarning("boolean", "returning false");
794
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
795
0
        return false;
796
0
    }
797
13
}
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.49k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
813
1.49k
{
814
1.49k
}
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
127k
{
825
127k
    auto* i = as<QPDF_Integer>();
826
127k
    if (!i) {
827
709
        throw invalid_error("Integer");
828
709
    }
829
126k
    return i->val;
830
127k
}
831
832
long long
833
QPDFObjectHandle::getIntValue() const
834
6.27k
{
835
6.27k
    if (auto const integer = Integer(*this)) {
836
6.27k
        return integer;
837
6.27k
    } else {
838
0
        typeWarning("integer", "returning 0");
839
0
        return 0;
840
0
    }
841
6.27k
}
842
843
bool
844
QPDFObjectHandle::getValueAsInt(long long& value) const
845
5.56k
{
846
5.56k
    if (auto const integer = Integer(*this)) {
847
5.48k
        value = integer;
848
5.48k
        return true;
849
5.48k
    }
850
84
    return false;
851
5.56k
}
852
853
int
854
QPDFObjectHandle::getIntValueAsInt() const
855
32.2k
{
856
32.2k
    try {
857
32.2k
        return Integer(*this).value<int>();
858
32.2k
    } catch (std::invalid_argument&) {
859
23
        typeWarning("integer", "returning 0");
860
23
        return 0;
861
23
    }
862
32.2k
}
863
864
bool
865
QPDFObjectHandle::getValueAsInt(int& value) const
866
1.79k
{
867
1.79k
    if (!isInteger()) {
868
28
        return false;
869
28
    }
870
1.76k
    value = getIntValueAsInt();
871
1.76k
    return true;
872
1.79k
}
873
874
unsigned long long
875
QPDFObjectHandle::getUIntValue() const
876
30.5k
{
877
30.5k
    try {
878
30.5k
        return Integer(*this).value<unsigned long long>();
879
30.5k
    } catch (std::invalid_argument&) {
880
370
        typeWarning("integer", "returning 0");
881
370
        return 0;
882
370
    }
883
30.5k
}
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
6.66k
{
898
6.66k
    try {
899
6.66k
        return Integer(*this).value<unsigned int>();
900
6.66k
    } catch (std::invalid_argument&) {
901
316
        typeWarning("integer", "returning 0");
902
316
        return 0;
903
316
    }
904
6.66k
}
905
906
bool
907
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
908
1.96k
{
909
1.96k
    if (!isInteger()) {
910
157
        return false;
911
157
    }
912
1.80k
    value = getUIntValueAsUInt();
913
1.80k
    return true;
914
1.96k
}
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
13.7k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
955
13.7k
{
956
13.7k
}
957
958
std::string const&
959
Name::value() const
960
366k
{
961
366k
    auto* n = as<QPDF_Name>();
962
366k
    if (!n) {
963
0
        throw invalid_error("Name");
964
0
    }
965
366k
    return n->name;
966
366k
}
967
968
std::string
969
QPDFObjectHandle::getName() const
970
13.4k
{
971
13.4k
    if (isName()) {
972
13.4k
        return obj->getStringValue();
973
13.4k
    } else {
974
0
        typeWarning("name", "returning dummy name");
975
0
        return "/QPDFFakeName";
976
0
    }
977
13.4k
}
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
22
{
994
22
    return {QPDFObject::create<QPDF_String>(str)};
995
22
}
996
997
QPDFObjectHandle
998
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
999
0
{
1000
0
    return {String::utf16(utf8_str).obj_sp()};
1001
0
}
1002
1003
String::String(std::string const& str) :
1004
0
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1005
0
{
1006
0
}
1007
1008
String::String(std::string&& str) :
1009
0
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1010
0
{
1011
0
}
1012
1013
String
1014
String::utf16(std::string const& utf8_str)
1015
0
{
1016
0
    std::string result;
1017
0
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1018
0
        return String(result);
1019
0
    }
1020
0
    return String(QUtil::utf8_to_utf16(utf8_str));
1021
0
}
1022
1023
std::string const&
1024
String::value() const
1025
35.4k
{
1026
35.4k
    auto* s = as<QPDF_String>();
1027
35.4k
    if (!s) {
1028
120
        throw invalid_error("String");
1029
120
    }
1030
35.3k
    return s->val;
1031
35.4k
}
1032
1033
std::string
1034
String::utf8_value() const
1035
0
{
1036
0
    auto* s = as<QPDF_String>();
1037
0
    if (!s) {
1038
0
        throw invalid_error("String");
1039
0
    }
1040
0
    if (util::is_utf16(s->val)) {
1041
0
        return QUtil::utf16_to_utf8(s->val);
1042
0
    }
1043
0
    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
0
        return s->val.substr(3);
1047
0
    }
1048
0
    return QUtil::pdf_doc_to_utf8(s->val);
1049
0
}
1050
1051
std::string
1052
QPDFObjectHandle::getStringValue() const
1053
35.4k
{
1054
35.4k
    try {
1055
35.4k
        return String(obj).value();
1056
35.4k
    } catch (std::invalid_argument&) {
1057
120
        typeWarning("string", "returning empty string");
1058
120
        return {};
1059
120
    }
1060
35.4k
}
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
0
{
1076
0
    try {
1077
0
        return String(obj).utf8_value();
1078
0
    } catch (std::invalid_argument&) {
1079
0
        typeWarning("string", "returning empty string");
1080
0
        return {};
1081
0
    }
1082
0
}
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
25.0k
{
1162
25.0k
    if (isNameAndEquals(value)) {
1163
74
        return true;
1164
24.9k
    } else if (isArray()) {
1165
29.3k
        for (auto& item: getArrayAsVector()) {
1166
29.3k
            if (item.isNameAndEquals(value)) {
1167
475
                return true;
1168
475
            }
1169
29.3k
        }
1170
9.42k
    }
1171
24.4k
    return false;
1172
25.0k
}
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
16
{
1459
16
    if (isIndirect()) {
1460
16
        return getObjGen().unparse(' ') + " R";
1461
16
    } else {
1462
0
        return unparseResolved();
1463
0
    }
1464
16
}
1465
1466
std::string
1467
QPDFObjectHandle::unparseResolved() const
1468
1.42M
{
1469
1.42M
    if (!obj) {
1470
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1471
0
    }
1472
1.42M
    return BaseHandle::unparse();
1473
1.42M
}
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
13.4k
{
1535
13.4k
    return parse(nullptr, object_str, object_description);
1536
13.4k
}
1537
1538
QPDFObjectHandle
1539
QPDFObjectHandle::parse(
1540
    QPDF* context, std::string const& object_str, std::string const& object_description)
1541
13.4k
{
1542
13.4k
    auto input = is::OffsetBuffer("parsed object", object_str);
1543
13.4k
    auto result = QPDFParser::parse(input, object_description, context);
1544
13.4k
    size_t offset = QIntC::to_size(input.tell());
1545
13.4k
    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
13.4k
    return result;
1558
13.4k
}
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
1.26k
{
1752
1.26k
    return {QPDFObject::create<QPDF_Null>()};
1753
1.26k
}
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
27.7k
{
1764
27.7k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1765
27.7k
}
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
6.92k
{
1788
6.92k
    return {QPDFObject::create<QPDF_Array>(items)};
1789
6.92k
}
1790
1791
QPDFObjectHandle
1792
QPDFObjectHandle::newArray(Rectangle const& rect)
1793
6.92k
{
1794
6.92k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1795
6.92k
}
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
25.0k
{
1842
25.0k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1843
25.0k
}
1844
1845
QPDFObjectHandle
1846
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1847
25.0k
{
1848
25.0k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1849
25.0k
}
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
55.8k
{
1893
55.8k
    return obj ? obj->getDescription() : ""s;
1894
55.8k
}
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
39.9k
{
1908
39.9k
    return obj && obj->hasDescription();
1909
39.9k
}
1910
1911
QPDFObjectHandle
1912
QPDFObjectHandle::shallowCopy()
1913
127
{
1914
127
    if (!obj) {
1915
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1916
0
    }
1917
127
    return {copy()};
1918
127
}
1919
1920
QPDFObjectHandle
1921
QPDFObjectHandle::unsafeShallowCopy()
1922
78.3k
{
1923
78.3k
    if (!obj) {
1924
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1925
0
    }
1926
78.3k
    return {copy(true)};
1927
78.3k
}
1928
1929
void
1930
QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
1931
14.7k
{
1932
14.7k
    assertInitialized();
1933
1934
14.7k
    auto cur_og = getObjGen();
1935
14.7k
    if (!visited.add(cur_og)) {
1936
25
        QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
1937
25
        throw std::runtime_error("loop detected while converting object from indirect to direct");
1938
25
    }
1939
1940
14.7k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
1941
9.09k
        obj = copy(true);
1942
9.09k
    } else if (auto a = as_array(strict)) {
1943
4.25k
        std::vector<QPDFObjectHandle> items;
1944
11.5k
        for (auto const& item: a) {
1945
11.5k
            items.emplace_back(item);
1946
11.5k
            items.back().makeDirect(visited, stop_at_streams);
1947
11.5k
        }
1948
4.25k
        obj = QPDFObject::create<QPDF_Array>(items);
1949
4.25k
    } else if (isDictionary()) {
1950
1.38k
        std::map<std::string, QPDFObjectHandle> items;
1951
4.25k
        for (auto const& [key, value]: as_dictionary(strict)) {
1952
4.25k
            if (!value.null()) {
1953
3.10k
                items.insert({key, value});
1954
3.10k
                items[key].makeDirect(visited, stop_at_streams);
1955
3.10k
            }
1956
4.25k
        }
1957
1.38k
        obj = QPDFObject::create<QPDF_Dictionary>(items);
1958
1.38k
    } else if (isStream()) {
1959
1
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
1960
1
        if (!stop_at_streams) {
1961
1
            throw std::runtime_error("attempt to make a stream into a direct object");
1962
1
        }
1963
1
    } 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
14.7k
    visited.erase(cur_og);
1971
14.7k
}
1972
1973
void
1974
QPDFObjectHandle::makeDirect(bool allow_streams)
1975
153
{
1976
153
    QPDFObjGen::set visited;
1977
153
    makeDirect(visited, allow_streams);
1978
153
}
1979
1980
void
1981
QPDFObjectHandle::assertInitialized() const
1982
14.7k
{
1983
14.7k
    if (!obj) {
1984
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1985
0
    }
1986
14.7k
}
1987
1988
std::invalid_argument
1989
BaseHandle::invalid_error(std::string const& method) const
1990
829
{
1991
829
    return std::invalid_argument(method + " operation attempted on invalid object");
1992
829
}
1993
std::runtime_error
1994
BaseHandle::type_error(char const* expected_type) const
1995
114
{
1996
114
    return std::runtime_error(
1997
114
        "operation for "s + expected_type + " attempted on object of type " + type_name());
1998
114
}
1999
2000
QPDFExc
2001
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2002
7.78k
{
2003
7.78k
    return {
2004
7.78k
        qpdf_e_object,
2005
7.78k
        "",
2006
7.78k
        description(),
2007
7.78k
        0,
2008
7.78k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2009
7.78k
            message};
2010
7.78k
}
2011
2012
void
2013
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2014
7.78k
{
2015
7.78k
    if (!obj) {
2016
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2017
0
    }
2018
7.78k
    warn(type_error(expected_type, message));
2019
7.78k
}
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
20
{
2030
20
    warn({qpdf_e_object, "", description(), 0, warning});
2031
20
}
2032
2033
void
2034
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2035
114
{
2036
114
    if (!istype) {
2037
114
        throw type_error(type_name);
2038
114
    }
2039
114
}
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
55.8k
{
2192
55.8k
    if (!qpdf()) {
2193
342
        throw std::move(e);
2194
342
    }
2195
55.4k
    qpdf()->warn(std::move(e));
2196
55.4k
}
2197
2198
void
2199
BaseHandle::warn(std::string const& warning) const
2200
53.6k
{
2201
53.6k
    if (qpdf()) {
2202
48.0k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2203
48.0k
    } else {
2204
5.62k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2205
5.62k
    }
2206
53.6k
}
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
4.77M
{
2376
4.77M
    return obj ? obj->getObjGen() : QPDFObjGen();
2377
4.77M
}
2378
2379
int
2380
QPDFObjectHandle::getObjectID() const
2381
1.76M
{
2382
1.76M
    return getObjGen().getObj();
2383
1.76M
}
2384
2385
int
2386
QPDFObjectHandle::getGeneration() const
2387
0
{
2388
0
    return getObjGen().getGen();
2389
0
}
2390
2391
bool
2392
QPDFObjectHandle::isIndirect() const
2393
22.1k
{
2394
22.1k
    return getObjectID() != 0;
2395
22.1k
}
2396
2397
// Indirect object accessors
2398
QPDF*
2399
QPDFObjectHandle::getOwningQPDF() const
2400
143k
{
2401
143k
    return obj ? obj->getQPDF() : nullptr;
2402
143k
}
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
22
{
2416
22
    if (obj) {
2417
22
        obj->setParsedOffset(offset);
2418
22
    }
2419
22
}
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
}