Coverage Report

Created: 2026-01-25 06:27

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