Coverage Report

Created: 2025-11-09 06:16

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
0
{
33
0
    return obj ? obj->getObjGen() : QPDFObjGen();
34
0
}
35
36
namespace
37
{
38
    class TerminateParsing
39
    {
40
    };
41
} // namespace
42
43
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
44
13.1k
    supports_retry(supports_retry)
45
13.1k
{
46
13.1k
}
47
48
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
49
13.1k
{
50
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
51
13.1k
}
52
53
void
54
QPDFObjectHandle::StreamDataProvider::provideStreamData(QPDFObjGen const& og, Pipeline* pipeline)
55
0
{
56
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline);
57
0
}
58
59
bool
60
QPDFObjectHandle::StreamDataProvider::provideStreamData(
61
    QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
62
0
{
63
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline, suppress_warnings, will_retry);
64
0
}
65
66
void
67
QPDFObjectHandle::StreamDataProvider::provideStreamData(
68
    int objid, int generation, Pipeline* pipeline)
69
0
{
70
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
71
0
}
72
73
bool
74
QPDFObjectHandle::StreamDataProvider::provideStreamData(
75
    int objid, int generation, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
76
0
{
77
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
78
0
    return false;
79
0
}
80
81
bool
82
QPDFObjectHandle::StreamDataProvider::supportsRetry()
83
0
{
84
0
    return supports_retry;
85
0
}
86
87
namespace
88
{
89
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
90
    {
91
      public:
92
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
93
0
            containing_page(containing_page),
94
0
            old_contents(old_contents)
95
0
        {
96
0
        }
97
0
        ~CoalesceProvider() override = default;
98
        void provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override;
99
100
      private:
101
        QPDFObjectHandle containing_page;
102
        QPDFObjectHandle old_contents;
103
    };
104
} // namespace
105
106
void
107
CoalesceProvider::provideStreamData(QPDFObjGen const&, Pipeline* p)
108
0
{
109
0
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
110
0
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
111
0
    std::string all_description;
112
0
    old_contents.pipeContentStreams(p, description, all_description);
113
0
}
114
115
void
116
QPDFObjectHandle::TokenFilter::handleEOF()
117
0
{
118
0
}
119
120
void
121
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
122
0
{
123
0
    pipeline = p;
124
0
}
125
126
void
127
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
128
0
{
129
0
    if (!pipeline) {
130
0
        return;
131
0
    }
132
0
    if (len) {
133
0
        pipeline->write(data, len);
134
0
    }
135
0
}
136
137
void
138
QPDFObjectHandle::TokenFilter::write(std::string const& str)
139
0
{
140
0
    write(str.c_str(), str.length());
141
0
}
142
143
void
144
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
145
0
{
146
0
    std::string const& value = token.getRawValue();
147
0
    write(value.c_str(), value.length());
148
0
}
149
150
void
151
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle)
152
0
{
153
0
    throw std::logic_error("You must override one of the handleObject methods in ParserCallbacks");
154
0
}
155
156
void
157
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle oh, size_t, size_t)
158
0
{
159
    // This version of handleObject was added in qpdf 9. If the developer did not override it, fall
160
    // back to the older interface.
161
0
    handleObject(oh);
162
0
}
163
164
void
165
QPDFObjectHandle::ParserCallbacks::contentSize(size_t)
166
0
{
167
    // Ignore by default; overriding this is optional.
168
0
}
169
170
void
171
QPDFObjectHandle::ParserCallbacks::terminateParsing()
172
0
{
173
0
    throw TerminateParsing();
174
0
}
175
176
std::pair<bool, bool>
177
Name::analyzeJSONEncoding(const std::string& name)
178
0
{
179
0
    int tail = 0;       // Number of continuation characters expected.
180
0
    bool tail2 = false; // Potential overlong 3 octet utf-8.
181
0
    bool tail3 = false; // potential overlong 4 octet
182
0
    bool needs_escaping = false;
183
0
    for (auto const& it: name) {
184
0
        auto c = static_cast<unsigned char>(it);
185
0
        if (tail) {
186
0
            if ((c & 0xc0) != 0x80) {
187
0
                return {false, false};
188
0
            }
189
0
            if (tail2) {
190
0
                if ((c & 0xe0) == 0x80) {
191
0
                    return {false, false};
192
0
                }
193
0
                tail2 = false;
194
0
            } else if (tail3) {
195
0
                if ((c & 0xf0) == 0x80) {
196
0
                    return {false, false};
197
0
                }
198
0
                tail3 = false;
199
0
            }
200
0
            tail--;
201
0
        } else if (c < 0x80) {
202
0
            if (!needs_escaping) {
203
0
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
204
0
            }
205
0
        } else if ((c & 0xe0) == 0xc0) {
206
0
            if ((c & 0xfe) == 0xc0) {
207
0
                return {false, false};
208
0
            }
209
0
            tail = 1;
210
0
        } else if ((c & 0xf0) == 0xe0) {
211
0
            tail2 = (c == 0xe0);
212
0
            tail = 2;
213
0
        } else if ((c & 0xf8) == 0xf0) {
214
0
            tail3 = (c == 0xf0);
215
0
            tail = 3;
216
0
        } else {
217
0
            return {false, false};
218
0
        }
219
0
    }
220
0
    return {tail == 0, !needs_escaping};
221
0
}
222
223
std::string
224
Name::normalize(std::string const& name)
225
0
{
226
0
    if (name.empty()) {
227
0
        return name;
228
0
    }
229
0
    std::string result;
230
0
    result += name.at(0);
231
0
    for (size_t i = 1; i < name.length(); ++i) {
232
0
        char ch = name.at(i);
233
        // Don't use locale/ctype here; follow PDF spec guidelines.
234
0
        if (ch == '\0') {
235
            // QPDFTokenizer embeds a null character to encode an invalid #.
236
0
            result += "#";
237
0
        } else if (
238
0
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
239
0
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
240
0
            result += util::hex_encode_char(ch);
241
0
        } else {
242
0
            result += ch;
243
0
        }
244
0
    }
245
0
    return result;
246
0
}
247
248
std::shared_ptr<QPDFObject>
249
BaseHandle::copy(bool shallow) const
250
0
{
251
0
    switch (resolved_type_code()) {
252
0
    case ::ot_uninitialized:
253
0
        throw std::logic_error("QPDFObjectHandle: attempting to copy an uninitialized object");
254
0
        return {}; // does not return
255
0
    case ::ot_reserved:
256
0
        return QPDFObject::create<QPDF_Reserved>();
257
0
    case ::ot_null:
258
0
        return QPDFObject::create<QPDF_Null>();
259
0
    case ::ot_boolean:
260
0
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
261
0
    case ::ot_integer:
262
0
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
263
0
    case ::ot_real:
264
0
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
265
0
    case ::ot_string:
266
0
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
267
0
    case ::ot_name:
268
0
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
269
0
    case ::ot_array:
270
0
        {
271
0
            auto const& a = std::get<QPDF_Array>(obj->value);
272
0
            if (shallow) {
273
0
                return QPDFObject::create<QPDF_Array>(a);
274
0
            } else {
275
0
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
276
0
                if (a.sp) {
277
0
                    QPDF_Array result;
278
0
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
279
0
                    result.sp->size = a.sp->size;
280
0
                    for (auto const& [idx, oh]: a.sp->elements) {
281
0
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
282
0
                    }
283
0
                    return QPDFObject::create<QPDF_Array>(std::move(result));
284
0
                } else {
285
0
                    std::vector<QPDFObjectHandle> result;
286
0
                    result.reserve(a.elements.size());
287
0
                    for (auto const& element: a.elements) {
288
0
                        result.emplace_back(
289
0
                            element ? (element.indirect() ? element : element.copy()) : element);
290
0
                    }
291
0
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
292
0
                }
293
0
            }
294
0
        }
295
0
    case ::ot_dictionary:
296
0
        {
297
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
298
0
            if (shallow) {
299
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
300
0
            } else {
301
0
                std::map<std::string, QPDFObjectHandle> new_items;
302
0
                for (auto const& [key, val]: d.items) {
303
0
                    new_items[key] = val.indirect() ? val : val.copy();
304
0
                }
305
0
                return QPDFObject::create<QPDF_Dictionary>(new_items);
306
0
            }
307
0
        }
308
0
    case ::ot_stream:
309
0
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
310
0
        throw std::runtime_error("stream objects cannot be cloned");
311
0
        return {}; // does not return
312
0
    case ::ot_operator:
313
0
        return QPDFObject::create<QPDF_Operator>(std::get<QPDF_Operator>(obj->value).val);
314
0
    case ::ot_inlineimage:
315
0
        return QPDFObject::create<QPDF_InlineImage>(std::get<QPDF_InlineImage>(obj->value).val);
316
0
    case ::ot_unresolved:
317
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
318
0
        return {}; // does not return
319
0
    case ::ot_destroyed:
320
0
        throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF");
321
0
        return {}; // does not return
322
0
    case ::ot_reference:
323
0
        return obj->qpdf->getObject(obj->og).obj_sp();
324
0
    }
325
0
    return {}; // unreachable
326
0
}
327
328
std::string
329
BaseHandle::unparse() const
330
0
{
331
0
    switch (resolved_type_code()) {
332
0
    case ::ot_uninitialized:
333
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object");
334
0
        return ""; // does not return
335
0
    case ::ot_reserved:
336
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
337
0
        return ""; // does not return
338
0
    case ::ot_null:
339
0
        return "null";
340
0
    case ::ot_boolean:
341
0
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
342
0
    case ::ot_integer:
343
0
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
344
0
    case ::ot_real:
345
0
        return std::get<QPDF_Real>(obj->value).val;
346
0
    case ::ot_string:
347
0
        return std::get<QPDF_String>(obj->value).unparse(false);
348
0
    case ::ot_name:
349
0
        return Name::normalize(std::get<QPDF_Name>(obj->value).name);
350
0
    case ::ot_array:
351
0
        {
352
0
            auto const& a = std::get<QPDF_Array>(obj->value);
353
0
            std::string result = "[ ";
354
0
            if (a.sp) {
355
0
                size_t next = 0;
356
0
                for (auto& [key, value]: a.sp->elements) {
357
0
                    for (size_t j = next; j < key; ++j) {
358
0
                        result += "null ";
359
0
                    }
360
0
                    result += value.unparse() + " ";
361
0
                    next = key + 1;
362
0
                }
363
0
                for (size_t j = next; j < a.sp->size; ++j) {
364
0
                    result += "null ";
365
0
                }
366
0
            } else {
367
0
                for (auto const& item: a.elements) {
368
0
                    result += item.unparse() + " ";
369
0
                }
370
0
            }
371
0
            result += "]";
372
0
            return result;
373
0
        }
374
0
    case ::ot_dictionary:
375
0
        {
376
0
            auto const& items = std::get<QPDF_Dictionary>(obj->value).items;
377
0
            std::string result = "<< ";
378
0
            for (auto& iter: items) {
379
0
                if (!iter.second.null()) {
380
0
                    result += Name::normalize(iter.first) + " " + iter.second.unparse() + " ";
381
0
                }
382
0
            }
383
0
            result += ">>";
384
0
            return result;
385
0
        }
386
0
    case ::ot_stream:
387
0
        return obj->og.unparse(' ') + " R";
388
0
    case ::ot_operator:
389
0
        return std::get<QPDF_Operator>(obj->value).val;
390
0
    case ::ot_inlineimage:
391
0
        return std::get<QPDF_InlineImage>(obj->value).val;
392
0
    case ::ot_unresolved:
393
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object");
394
0
        return ""; // does not return
395
0
    case ::ot_destroyed:
396
0
        throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF");
397
0
        return ""; // does not return
398
0
    case ::ot_reference:
399
0
        return obj->og.unparse(' ') + " R";
400
0
    }
401
0
    return {}; // unreachable
402
0
}
403
404
void
405
BaseHandle::write_json(int json_version, JSON::Writer& p) const
406
0
{
407
0
    switch (resolved_type_code()) {
408
0
    case ::ot_uninitialized:
409
0
        throw std::logic_error(
410
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
411
0
        break; // unreachable
412
0
    case ::ot_null:
413
0
    case ::ot_operator:
414
0
    case ::ot_inlineimage:
415
0
        p << "null";
416
0
        break;
417
0
    case ::ot_boolean:
418
0
        p << std::get<QPDF_Bool>(obj->value).val;
419
0
        break;
420
0
    case ::ot_integer:
421
0
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
422
0
        break;
423
0
    case ::ot_real:
424
0
        {
425
0
            auto const& val = std::get<QPDF_Real>(obj->value).val;
426
0
            if (val.empty()) {
427
                // Can't really happen...
428
0
                p << "0";
429
0
            } else if (val.at(0) == '.') {
430
0
                p << "0" << val;
431
0
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
432
0
                p << "-0." << val.substr(2);
433
0
            } else {
434
0
                p << val;
435
0
            }
436
0
            if (val.back() == '.') {
437
0
                p << "0";
438
0
            }
439
0
        }
440
0
        break;
441
0
    case ::ot_string:
442
0
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
443
0
        break;
444
0
    case ::ot_name:
445
0
        {
446
0
            auto const& n = std::get<QPDF_Name>(obj->value);
447
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
448
            // updating this method make sure QPDF_Dictionary is also update.
449
0
            if (json_version == 1) {
450
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
451
0
            } else {
452
0
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
453
0
                    if (res.second) {
454
0
                        p << "\"" << n.name << "\"";
455
0
                    } else {
456
0
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
457
0
                    }
458
0
                } else {
459
0
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
460
0
                }
461
0
            }
462
0
        }
463
0
        break;
464
0
    case ::ot_array:
465
0
        {
466
0
            auto const& a = std::get<QPDF_Array>(obj->value);
467
0
            p.writeStart('[');
468
0
            if (a.sp) {
469
0
                size_t next = 0;
470
0
                for (auto& [key, value]: a.sp->elements) {
471
0
                    for (size_t j = next; j < key; ++j) {
472
0
                        p.writeNext() << "null";
473
0
                    }
474
0
                    p.writeNext();
475
0
                    auto item_og = value.id_gen();
476
0
                    if (item_og.isIndirect()) {
477
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
478
0
                    } else {
479
0
                        value.write_json(json_version, p);
480
0
                    }
481
0
                    next = key + 1;
482
0
                }
483
0
                for (size_t j = next; j < a.sp->size; ++j) {
484
0
                    p.writeNext() << "null";
485
0
                }
486
0
            } else {
487
0
                for (auto const& item: a.elements) {
488
0
                    p.writeNext();
489
0
                    auto item_og = item.id_gen();
490
0
                    if (item_og.isIndirect()) {
491
0
                        p << "\"" << item_og.unparse(' ') << " R\"";
492
0
                    } else {
493
0
                        item.write_json(json_version, p);
494
0
                    }
495
0
                }
496
0
            }
497
0
            p.writeEnd(']');
498
0
        }
499
0
        break;
500
0
    case ::ot_dictionary:
501
0
        {
502
0
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
503
0
            p.writeStart('{');
504
0
            for (auto& iter: d.items) {
505
0
                if (!iter.second.null()) {
506
0
                    p.writeNext();
507
0
                    if (json_version == 1) {
508
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
509
0
                          << "\": ";
510
0
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
511
0
                        if (res.second) {
512
0
                            p << "\"" << iter.first << "\": ";
513
0
                        } else {
514
0
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
515
0
                        }
516
0
                    } else {
517
0
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
518
0
                          << "\": ";
519
0
                    }
520
0
                    iter.second.writeJSON(json_version, p);
521
0
                }
522
0
            }
523
0
            p.writeEnd('}');
524
0
        }
525
0
        break;
526
0
    case ::ot_stream:
527
0
        std::get<QPDF_Stream>(obj->value).m->stream_dict.writeJSON(json_version, p);
528
0
        break;
529
0
    case ::ot_reference:
530
0
        p << "\"" << obj->og.unparse(' ') << " R\"";
531
0
        break;
532
0
    default:
533
0
        throw std::logic_error("attempted to write an unsuitable object as JSON");
534
0
    }
535
0
}
536
537
void
538
BaseHandle::disconnect(bool only_direct)
539
870k
{
540
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
541
870k
    if (only_direct && indirect()) {
542
142k
        return;
543
142k
    }
544
545
727k
    switch (raw_type_code()) {
546
11.3k
    case ::ot_array:
547
11.3k
        {
548
11.3k
            auto& a = std::get<QPDF_Array>(obj->value);
549
11.3k
            if (a.sp) {
550
0
                for (auto& item: a.sp->elements) {
551
0
                    item.second.disconnect();
552
0
                }
553
11.3k
            } else {
554
781k
                for (auto& oh: a.elements) {
555
781k
                    oh.disconnect();
556
781k
                }
557
11.3k
            }
558
11.3k
        }
559
11.3k
        break;
560
15.7k
    case ::ot_dictionary:
561
73.1k
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
562
73.1k
            iter.second.disconnect();
563
73.1k
        }
564
15.7k
        break;
565
2.68k
    case ::ot_stream:
566
2.68k
        {
567
2.68k
            auto& s = std::get<QPDF_Stream>(obj->value);
568
2.68k
            s.m->stream_provider = nullptr;
569
2.68k
            s.m->stream_dict.disconnect();
570
2.68k
        }
571
2.68k
        break;
572
0
    case ::ot_uninitialized:
573
0
        return;
574
697k
    default:
575
697k
        break;
576
727k
    }
577
727k
    obj->qpdf = nullptr;
578
727k
    obj->og = QPDFObjGen();
579
727k
}
580
581
std::string
582
QPDFObject::getStringValue() const
583
2.53k
{
584
2.53k
    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
2.53k
    case ::ot_name:
590
2.53k
        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
2.53k
    }
600
0
    return ""; // unreachable
601
2.53k
}
602
603
bool
604
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
605
0
{
606
0
    return obj == rhs.obj;
607
0
}
608
609
qpdf_object_type_e
610
QPDFObjectHandle::getTypeCode() const
611
0
{
612
0
    return type_code();
613
0
}
614
615
char const*
616
BaseHandle::type_name() const
617
0
{
618
0
    static constexpr std::array<char const*, 16> tn{
619
0
        "uninitialized",
620
0
        "reserved",
621
0
        "null",
622
0
        "boolean",
623
0
        "integer",
624
0
        "real",
625
0
        "string",
626
0
        "name",
627
0
        "array",
628
0
        "dictionary",
629
0
        "stream",
630
0
        "operator",
631
0
        "inline-image",
632
0
        "unresolved",
633
0
        "destroyed",
634
0
        "reference"};
635
0
    return tn[type_code()];
636
0
}
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
0
{
653
0
    return type_code() == ::ot_boolean;
654
0
}
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
0
{
667
0
    return type_code() == ::ot_null;
668
0
}
669
670
bool
671
QPDFObjectHandle::isInteger() const
672
7.80k
{
673
7.80k
    return type_code() == ::ot_integer;
674
7.80k
}
675
676
bool
677
QPDFObjectHandle::isReal() const
678
0
{
679
0
    return type_code() == ::ot_real;
680
0
}
681
682
bool
683
QPDFObjectHandle::isNumber() const
684
0
{
685
0
    return (isInteger() || isReal());
686
0
}
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
2.53k
{
715
2.53k
    return type_code() == ::ot_name;
716
2.53k
}
717
718
bool
719
QPDFObjectHandle::isString() const
720
0
{
721
0
    return type_code() == ::ot_string;
722
0
}
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
0
{
739
0
    return type_code() == ::ot_array;
740
0
}
741
742
bool
743
QPDFObjectHandle::isDictionary() const
744
132k
{
745
132k
    return type_code() == ::ot_dictionary;
746
132k
}
747
748
bool
749
QPDFObjectHandle::isStream() const
750
132k
{
751
132k
    return type_code() == ::ot_stream;
752
132k
}
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
0
{
763
0
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
764
0
}
765
766
bool
767
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
768
0
{
769
0
    return Name(*this) == name;
770
0
}
771
772
bool
773
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
774
0
{
775
0
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
776
0
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
777
0
}
778
779
bool
780
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
781
0
{
782
0
    return isStream() && getDict().isDictionaryOfType(type, subtype);
783
0
}
784
785
// Bool accessors
786
787
bool
788
QPDFObjectHandle::getBoolValue() const
789
0
{
790
0
    if (auto boolean = as<QPDF_Bool>()) {
791
0
        return boolean->val;
792
0
    } else {
793
0
        typeWarning("boolean", "returning false");
794
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
795
0
        return false;
796
0
    }
797
0
}
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
0
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
813
0
{
814
0
}
815
816
QPDFObjectHandle
817
QPDFObjectHandle::newInteger(long long value)
818
607k
{
819
607k
    return {QPDFObject::create<QPDF_Integer>(value)};
820
607k
}
821
822
int64_t
823
Integer::value() const
824
7.80k
{
825
7.80k
    auto* i = as<QPDF_Integer>();
826
7.80k
    if (!i) {
827
0
        throw invalid_error("Integer");
828
0
    }
829
7.80k
    return i->val;
830
7.80k
}
831
832
long long
833
QPDFObjectHandle::getIntValue() const
834
0
{
835
0
    if (auto const integer = Integer(*this)) {
836
0
        return integer;
837
0
    } else {
838
0
        typeWarning("integer", "returning 0");
839
0
        return 0;
840
0
    }
841
0
}
842
843
bool
844
QPDFObjectHandle::getValueAsInt(long long& value) const
845
0
{
846
0
    if (auto const integer = Integer(*this)) {
847
0
        value = integer;
848
0
        return true;
849
0
    }
850
0
    return false;
851
0
}
852
853
int
854
QPDFObjectHandle::getIntValueAsInt() const
855
7.80k
{
856
7.80k
    try {
857
7.80k
        return Integer(*this).value<int>();
858
7.80k
    } catch (std::invalid_argument&) {
859
0
        typeWarning("integer", "returning 0");
860
0
        return 0;
861
0
    }
862
7.80k
}
863
864
bool
865
QPDFObjectHandle::getValueAsInt(int& value) const
866
0
{
867
0
    if (!isInteger()) {
868
0
        return false;
869
0
    }
870
0
    value = getIntValueAsInt();
871
0
    return true;
872
0
}
873
874
unsigned long long
875
QPDFObjectHandle::getUIntValue() const
876
0
{
877
0
    try {
878
0
        return Integer(*this).value<unsigned long long>();
879
0
    } catch (std::invalid_argument&) {
880
0
        typeWarning("integer", "returning 0");
881
0
        return 0;
882
0
    }
883
0
}
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
0
{
898
0
    try {
899
0
        return Integer(*this).value<unsigned int>();
900
0
    } catch (std::invalid_argument&) {
901
0
        typeWarning("integer", "returning 0");
902
0
        return 0;
903
0
    }
904
0
}
905
906
bool
907
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
908
0
{
909
0
    if (!isInteger()) {
910
0
        return false;
911
0
    }
912
0
    value = getUIntValueAsUInt();
913
0
    return true;
914
0
}
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
14.1k
{
945
14.1k
    return {QPDFObject::create<QPDF_Name>(name)};
946
14.1k
}
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
0
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
955
0
{
956
0
}
957
958
std::string const&
959
Name::value() const
960
0
{
961
0
    auto* n = as<QPDF_Name>();
962
0
    if (!n) {
963
0
        throw invalid_error("Name");
964
0
    }
965
0
    return n->name;
966
0
}
967
968
std::string
969
QPDFObjectHandle::getName() const
970
2.53k
{
971
2.53k
    if (isName()) {
972
2.53k
        return obj->getStringValue();
973
2.53k
    } else {
974
0
        typeWarning("name", "returning dummy name");
975
0
        return "/QPDFFakeName";
976
0
    }
977
2.53k
}
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
1.10k
{
994
1.10k
    return {QPDFObject::create<QPDF_String>(str)};
995
1.10k
}
996
997
QPDFObjectHandle
998
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
999
5.46k
{
1000
5.46k
    return {String::utf16(utf8_str).obj_sp()};
1001
5.46k
}
1002
1003
String::String(std::string const& str) :
1004
1.58k
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1005
1.58k
{
1006
1.58k
}
1007
1008
String::String(std::string&& str) :
1009
3.88k
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1010
3.88k
{
1011
3.88k
}
1012
1013
String
1014
String::utf16(std::string const& utf8_str)
1015
5.46k
{
1016
5.46k
    std::string result;
1017
5.46k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1018
1.58k
        return String(result);
1019
1.58k
    }
1020
3.88k
    return String(QUtil::utf8_to_utf16(utf8_str));
1021
5.46k
}
1022
1023
std::string const&
1024
String::value() const
1025
0
{
1026
0
    auto* s = as<QPDF_String>();
1027
0
    if (!s) {
1028
0
        throw invalid_error("String");
1029
0
    }
1030
0
    return s->val;
1031
0
}
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
0
{
1054
0
    try {
1055
0
        return String(obj).value();
1056
0
    } catch (std::invalid_argument&) {
1057
0
        typeWarning("string", "returning empty string");
1058
0
        return {};
1059
0
    }
1060
0
}
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
0
{
1162
0
    if (isNameAndEquals(value)) {
1163
0
        return true;
1164
0
    } else if (isArray()) {
1165
0
        for (auto& item: getArrayAsVector()) {
1166
0
            if (item.isNameAndEquals(value)) {
1167
0
                return true;
1168
0
            }
1169
0
        }
1170
0
    }
1171
0
    return false;
1172
0
}
1173
1174
void
1175
QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf)
1176
0
{
1177
0
    for (auto const& i1: as_dictionary()) {
1178
0
        for (auto& i2: i1.second.as_dictionary()) {
1179
0
            if (!i2.second.null() && !i2.second.isIndirect()) {
1180
0
                i2.second = owning_qpdf.makeIndirectObject(i2.second);
1181
0
            }
1182
0
        }
1183
0
    }
1184
0
}
1185
1186
void
1187
QPDFObjectHandle::mergeResources(
1188
    QPDFObjectHandle other, std::map<std::string, std::map<std::string, std::string>>* conflicts)
1189
0
{
1190
0
    if (!(isDictionary() && other.isDictionary())) {
1191
0
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1192
0
        return;
1193
0
    }
1194
1195
0
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1196
0
                              std::map<QPDFObjGen, std::string>& og_to_name) {
1197
0
        for (auto const& [key, value]: dict.as_dictionary()) {
1198
0
            if (!value.null() && value.isIndirect()) {
1199
0
                og_to_name.insert_or_assign(value.getObjGen(), key);
1200
0
            }
1201
0
        }
1202
0
    };
1203
1204
    // This algorithm is described in comments in QPDFObjectHandle.hh
1205
    // above the declaration of mergeResources.
1206
0
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1207
0
        auto other_val = value1;
1208
0
        if (hasKey(rtype)) {
1209
0
            QPDFObjectHandle this_val = getKey(rtype);
1210
0
            if (this_val.isDictionary() && other_val.isDictionary()) {
1211
0
                if (this_val.isIndirect()) {
1212
                    // Do this even if there are no keys. Various places in the code call
1213
                    // mergeResources with resource dictionaries that contain empty subdictionaries
1214
                    // just to get this shallow copy functionality.
1215
0
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1216
0
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1217
0
                }
1218
0
                std::map<QPDFObjGen, std::string> og_to_name;
1219
0
                std::set<std::string> rnames;
1220
0
                int min_suffix = 1;
1221
0
                bool initialized_maps = false;
1222
0
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1223
0
                    QPDFObjectHandle rval = value2;
1224
0
                    if (!this_val.hasKey(key)) {
1225
0
                        if (!rval.isIndirect()) {
1226
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1227
0
                            rval = rval.shallowCopy();
1228
0
                        }
1229
0
                        this_val.replaceKey(key, rval);
1230
0
                    } else if (conflicts) {
1231
0
                        if (!initialized_maps) {
1232
0
                            make_og_to_name(this_val, og_to_name);
1233
0
                            rnames = this_val.getResourceNames();
1234
0
                            initialized_maps = true;
1235
0
                        }
1236
0
                        auto rval_og = rval.getObjGen();
1237
0
                        if (rval.isIndirect() && og_to_name.contains(rval_og)) {
1238
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge reuse");
1239
0
                            auto new_key = og_to_name[rval_og];
1240
0
                            if (new_key != key) {
1241
0
                                (*conflicts)[rtype][key] = new_key;
1242
0
                            }
1243
0
                        } else {
1244
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge generate");
1245
0
                            std::string new_key =
1246
0
                                getUniqueResourceName(key + "_", min_suffix, &rnames);
1247
0
                            (*conflicts)[rtype][key] = new_key;
1248
0
                            this_val.replaceKey(new_key, rval);
1249
0
                        }
1250
0
                    }
1251
0
                }
1252
0
            } else if (this_val.isArray() && other_val.isArray()) {
1253
0
                std::set<std::string> scalars;
1254
0
                for (auto this_item: this_val.aitems()) {
1255
0
                    if (this_item.isScalar()) {
1256
0
                        scalars.insert(this_item.unparse());
1257
0
                    }
1258
0
                }
1259
0
                for (auto other_item: other_val.aitems()) {
1260
0
                    if (other_item.isScalar()) {
1261
0
                        if (!scalars.contains(other_item.unparse())) {
1262
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1263
0
                            this_val.appendItem(other_item);
1264
0
                        } else {
1265
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1266
0
                        }
1267
0
                    }
1268
0
                }
1269
0
            }
1270
0
        } else {
1271
0
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1272
0
            replaceKey(rtype, other_val.shallowCopy());
1273
0
        }
1274
0
    }
1275
0
}
1276
1277
std::set<std::string>
1278
QPDFObjectHandle::getResourceNames() const
1279
0
{
1280
    // Return second-level dictionary keys
1281
0
    std::set<std::string> result;
1282
0
    for (auto const& item: as_dictionary(strict)) {
1283
0
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1284
0
            if (!val2.null()) {
1285
0
                result.insert(key2);
1286
0
            }
1287
0
        }
1288
0
    }
1289
0
    return result;
1290
0
}
1291
1292
std::string
1293
QPDFObjectHandle::getUniqueResourceName(
1294
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1295
0
{
1296
0
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1297
0
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1298
0
    while (min_suffix <= max_suffix) {
1299
0
        std::string candidate = prefix + std::to_string(min_suffix);
1300
0
        if (!names.contains(candidate)) {
1301
0
            return candidate;
1302
0
        }
1303
        // Increment after return; min_suffix should be the value
1304
        // used, not the next value.
1305
0
        ++min_suffix;
1306
0
    }
1307
    // This could only happen if there is a coding error.
1308
    // The number of candidates we test is more than the
1309
    // number of keys we're checking against.
1310
0
    throw std::logic_error(
1311
0
        "unable to find unconflicting name in QPDFObjectHandle::getUniqueResourceName");
1312
0
}
1313
1314
// Dictionary mutators are in QPDF_Dictionary.cc
1315
1316
// Stream accessors are in QPDF_Stream.cc
1317
1318
std::map<std::string, QPDFObjectHandle>
1319
QPDFObjectHandle::getPageImages()
1320
0
{
1321
0
    return QPDFPageObjectHelper(*this).getImages();
1322
0
}
1323
1324
std::vector<QPDFObjectHandle>
1325
QPDFObjectHandle::arrayOrStreamToStreamArray(
1326
    std::string const& description, std::string& all_description)
1327
0
{
1328
0
    all_description = description;
1329
0
    std::vector<QPDFObjectHandle> result;
1330
0
    if (auto array = as_array(strict)) {
1331
0
        int n_items = static_cast<int>(array.size());
1332
0
        for (int i = 0; i < n_items; ++i) {
1333
0
            QPDFObjectHandle item = array[i];
1334
0
            if (item.isStream()) {
1335
0
                result.emplace_back(item);
1336
0
            } else {
1337
0
                item.warn(
1338
0
                    {qpdf_e_damaged_pdf,
1339
0
                     "",
1340
0
                     description + ": item index " + std::to_string(i) + " (from 0)",
1341
0
                     0,
1342
0
                     "ignoring non-stream in an array of streams"});
1343
0
            }
1344
0
        }
1345
0
    } else if (isStream()) {
1346
0
        result.emplace_back(*this);
1347
0
    } else if (!null()) {
1348
0
        warn(
1349
0
            {qpdf_e_damaged_pdf,
1350
0
             "",
1351
0
             description,
1352
0
             0,
1353
0
             " object is supposed to be a stream or an array of streams but is neither"});
1354
0
    }
1355
1356
0
    bool first = true;
1357
0
    for (auto const& item: result) {
1358
0
        if (first) {
1359
0
            first = false;
1360
0
        } else {
1361
0
            all_description += ",";
1362
0
        }
1363
0
        all_description += " stream " + item.getObjGen().unparse(' ');
1364
0
    }
1365
1366
0
    return result;
1367
0
}
1368
1369
std::vector<QPDFObjectHandle>
1370
QPDFObjectHandle::getPageContents()
1371
0
{
1372
0
    std::string description = "page object " + getObjGen().unparse(' ');
1373
0
    std::string all_description;
1374
0
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1375
0
}
1376
1377
void
1378
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1379
0
{
1380
0
    new_contents.assertStream();
1381
1382
0
    std::vector<QPDFObjectHandle> content_streams;
1383
0
    if (first) {
1384
0
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1385
0
        content_streams.push_back(new_contents);
1386
0
    }
1387
0
    for (auto const& iter: getPageContents()) {
1388
0
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1389
0
        content_streams.push_back(iter);
1390
0
    }
1391
0
    if (!first) {
1392
0
        content_streams.push_back(new_contents);
1393
0
    }
1394
1395
0
    replaceKey("/Contents", newArray(content_streams));
1396
0
}
1397
1398
void
1399
QPDFObjectHandle::rotatePage(int angle, bool relative)
1400
0
{
1401
0
    if ((angle % 90) != 0) {
1402
0
        throw std::runtime_error(
1403
0
            "QPDF::rotatePage called with an angle that is not a multiple of 90");
1404
0
    }
1405
0
    int new_angle = angle;
1406
0
    if (relative) {
1407
0
        int old_angle = 0;
1408
0
        QPDFObjectHandle cur_obj = *this;
1409
0
        QPDFObjGen::set visited;
1410
0
        while (visited.add(cur_obj)) {
1411
            // Don't get stuck in an infinite loop
1412
0
            if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) {
1413
0
                break;
1414
0
            } else if (cur_obj.getKey("/Parent").isDictionary()) {
1415
0
                cur_obj = cur_obj.getKey("/Parent");
1416
0
            } else {
1417
0
                break;
1418
0
            }
1419
0
        }
1420
0
        QTC::TC("qpdf", "QPDFObjectHandle found old angle", visited.size() > 1 ? 0 : 1);
1421
0
        if ((old_angle % 90) != 0) {
1422
0
            old_angle = 0;
1423
0
        }
1424
0
        new_angle += old_angle;
1425
0
    }
1426
0
    new_angle = (new_angle + 360) % 360;
1427
    // Make this explicit even with new_angle == 0 since /Rotate can be inherited.
1428
0
    replaceKey("/Rotate", Integer(new_angle));
1429
0
}
1430
1431
void
1432
QPDFObjectHandle::coalesceContentStreams()
1433
0
{
1434
0
    QPDFObjectHandle contents = getKey("/Contents");
1435
0
    if (contents.isStream()) {
1436
0
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1437
0
        return;
1438
0
    } else if (!contents.isArray()) {
1439
        // /Contents is optional for pages, and some very damaged files may have pages that are
1440
        // invalid in other ways.
1441
0
        return;
1442
0
    }
1443
    // Should not be possible for a page object to not have an owning PDF unless it was manually
1444
    // constructed in some incorrect way. However, it can happen in a PDF file whose page structure
1445
    // is direct, which is against spec but still possible to hand construct, as in fuzz issue
1446
    // 27393.
1447
0
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1448
1449
0
    QPDFObjectHandle new_contents = newStream(&qpdf);
1450
0
    replaceKey("/Contents", new_contents);
1451
1452
0
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1453
0
    new_contents.replaceStreamData(provider, newNull(), newNull());
1454
0
}
1455
1456
std::string
1457
QPDFObjectHandle::unparse() const
1458
0
{
1459
0
    if (isIndirect()) {
1460
0
        return getObjGen().unparse(' ') + " R";
1461
0
    } else {
1462
0
        return unparseResolved();
1463
0
    }
1464
0
}
1465
1466
std::string
1467
QPDFObjectHandle::unparseResolved() const
1468
0
{
1469
0
    if (!obj) {
1470
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1471
0
    }
1472
0
    return BaseHandle::unparse();
1473
0
}
1474
1475
std::string
1476
QPDFObjectHandle::unparseBinary() const
1477
0
{
1478
0
    if (auto str = as<QPDF_String>()) {
1479
0
        return str->unparse(true);
1480
0
    } else {
1481
0
        return unparse();
1482
0
    }
1483
0
}
1484
1485
JSON
1486
QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) const
1487
0
{
1488
0
    if ((!dereference_indirect) && isIndirect()) {
1489
0
        return JSON::makeString(unparse());
1490
0
    } else if (!obj) {
1491
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1492
0
    } else {
1493
0
        Pl_Buffer p{"json"};
1494
0
        JSON::Writer jw{&p, 0};
1495
0
        writeJSON(json_version, jw, dereference_indirect);
1496
0
        p.finish();
1497
0
        return JSON::parse(p.getString());
1498
0
    }
1499
0
}
1500
1501
void
1502
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1503
0
{
1504
0
    if (!dereference_indirect && isIndirect()) {
1505
0
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1506
0
    } else if (!obj) {
1507
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1508
0
    } else {
1509
0
        write_json(json_version, p);
1510
0
    }
1511
0
}
1512
1513
void
1514
QPDFObjectHandle::writeJSON(
1515
    int json_version, Pipeline* p, bool dereference_indirect, size_t depth) const
1516
0
{
1517
0
    JSON::Writer jw{p, depth};
1518
0
    writeJSON(json_version, jw, dereference_indirect);
1519
0
}
1520
1521
QPDFObjectHandle
1522
QPDFObjectHandle::wrapInArray()
1523
0
{
1524
0
    if (isArray()) {
1525
0
        return *this;
1526
0
    }
1527
0
    QPDFObjectHandle result = QPDFObjectHandle::newArray();
1528
0
    result.appendItem(*this);
1529
0
    return result;
1530
0
}
1531
1532
QPDFObjectHandle
1533
QPDFObjectHandle::parse(std::string const& object_str, std::string const& object_description)
1534
11.4k
{
1535
11.4k
    return parse(nullptr, object_str, object_description);
1536
11.4k
}
1537
1538
QPDFObjectHandle
1539
QPDFObjectHandle::parse(
1540
    QPDF* context, std::string const& object_str, std::string const& object_description)
1541
11.4k
{
1542
11.4k
    auto input = is::OffsetBuffer("parsed object", object_str);
1543
11.4k
    auto result = QPDFParser::parse(input, object_description, context);
1544
11.4k
    size_t offset = QIntC::to_size(input.tell());
1545
40.8k
    while (offset < object_str.length()) {
1546
29.5k
        if (!isspace(object_str.at(offset))) {
1547
73
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1548
73
            throw QPDFExc(
1549
73
                qpdf_e_damaged_pdf,
1550
73
                "parsed object",
1551
73
                object_description,
1552
73
                input.getLastOffset(),
1553
73
                "trailing data found parsing object from string");
1554
73
        }
1555
29.4k
        ++offset;
1556
29.4k
    }
1557
11.3k
    return result;
1558
11.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
926
{
1746
926
    return {QPDFObject::create<QPDF_Bool>(value)};
1747
926
}
1748
1749
QPDFObjectHandle
1750
QPDFObjectHandle::newNull()
1751
32.1k
{
1752
32.1k
    return {QPDFObject::create<QPDF_Null>()};
1753
32.1k
}
1754
1755
QPDFObjectHandle
1756
QPDFObjectHandle::newReal(std::string const& value)
1757
5.06k
{
1758
5.06k
    return {QPDFObject::create<QPDF_Real>(value)};
1759
5.06k
}
1760
1761
QPDFObjectHandle
1762
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1763
0
{
1764
0
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1765
0
}
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
12.1k
{
1782
12.1k
    return newArray(std::vector<QPDFObjectHandle>());
1783
12.1k
}
1784
1785
QPDFObjectHandle
1786
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1787
12.1k
{
1788
12.1k
    return {QPDFObject::create<QPDF_Array>(items)};
1789
12.1k
}
1790
1791
QPDFObjectHandle
1792
QPDFObjectHandle::newArray(Rectangle const& rect)
1793
0
{
1794
0
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1795
0
}
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
21.8k
{
1842
21.8k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1843
21.8k
}
1844
1845
QPDFObjectHandle
1846
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1847
21.8k
{
1848
21.8k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1849
21.8k
}
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
0
{
1893
0
    return obj ? obj->getDescription() : ""s;
1894
0
}
1895
1896
void
1897
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
1898
4.11k
{
1899
4.11k
    if (obj) {
1900
4.11k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
1901
4.11k
        obj->setDescription(owning_qpdf, descr);
1902
4.11k
    }
1903
4.11k
}
1904
1905
bool
1906
QPDFObjectHandle::hasObjectDescription() const
1907
976k
{
1908
976k
    return obj && obj->hasDescription();
1909
976k
}
1910
1911
QPDFObjectHandle
1912
QPDFObjectHandle::shallowCopy()
1913
0
{
1914
0
    if (!obj) {
1915
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1916
0
    }
1917
0
    return {copy()};
1918
0
}
1919
1920
QPDFObjectHandle
1921
QPDFObjectHandle::unsafeShallowCopy()
1922
0
{
1923
0
    if (!obj) {
1924
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1925
0
    }
1926
0
    return {copy(true)};
1927
0
}
1928
1929
void
1930
QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
1931
0
{
1932
0
    assertInitialized();
1933
1934
0
    auto cur_og = getObjGen();
1935
0
    if (!visited.add(cur_og)) {
1936
0
        QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
1937
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
1938
0
    }
1939
1940
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
1941
0
        obj = copy(true);
1942
0
    } else if (auto a = as_array(strict)) {
1943
0
        std::vector<QPDFObjectHandle> items;
1944
0
        for (auto const& item: a) {
1945
0
            items.emplace_back(item);
1946
0
            items.back().makeDirect(visited, stop_at_streams);
1947
0
        }
1948
0
        obj = QPDFObject::create<QPDF_Array>(items);
1949
0
    } else if (isDictionary()) {
1950
0
        std::map<std::string, QPDFObjectHandle> items;
1951
0
        for (auto const& [key, value]: as_dictionary(strict)) {
1952
0
            if (!value.null()) {
1953
0
                items.insert({key, value});
1954
0
                items[key].makeDirect(visited, stop_at_streams);
1955
0
            }
1956
0
        }
1957
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
1958
0
    } else if (isStream()) {
1959
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
1960
0
        if (!stop_at_streams) {
1961
0
            throw std::runtime_error("attempt to make a stream into a direct object");
1962
0
        }
1963
0
    } else if (isReserved()) {
1964
0
        throw std::logic_error(
1965
0
            "QPDFObjectHandle: attempting to make a reserved object handle direct");
1966
0
    } else {
1967
0
        throw std::logic_error("QPDFObjectHandle::makeDirectInternal: unknown object type");
1968
0
    }
1969
1970
0
    visited.erase(cur_og);
1971
0
}
1972
1973
void
1974
QPDFObjectHandle::makeDirect(bool allow_streams)
1975
0
{
1976
0
    QPDFObjGen::set visited;
1977
0
    makeDirect(visited, allow_streams);
1978
0
}
1979
1980
void
1981
QPDFObjectHandle::assertInitialized() const
1982
0
{
1983
0
    if (!obj) {
1984
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1985
0
    }
1986
0
}
1987
1988
std::invalid_argument
1989
BaseHandle::invalid_error(std::string const& method) const
1990
0
{
1991
0
    return std::invalid_argument(method + " operation attempted on invalid object");
1992
0
}
1993
std::runtime_error
1994
BaseHandle::type_error(char const* expected_type) const
1995
0
{
1996
0
    return std::runtime_error(
1997
0
        "operation for "s + expected_type + " attempted on object of type " + type_name());
1998
0
}
1999
2000
QPDFExc
2001
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2002
0
{
2003
0
    return {
2004
0
        qpdf_e_object,
2005
0
        "",
2006
0
        description(),
2007
0
        0,
2008
0
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2009
0
            message};
2010
0
}
2011
2012
void
2013
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2014
0
{
2015
0
    if (!obj) {
2016
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2017
0
    }
2018
0
    warn(type_error(expected_type, message));
2019
0
}
2020
2021
void
2022
QPDFObjectHandle::warnIfPossible(std::string const& warning) const
2023
0
{
2024
0
    warn(warning);
2025
0
}
2026
2027
void
2028
QPDFObjectHandle::objectWarning(std::string const& warning) const
2029
0
{
2030
0
    warn({qpdf_e_object, "", description(), 0, warning});
2031
0
}
2032
2033
void
2034
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2035
0
{
2036
0
    if (!istype) {
2037
0
        throw type_error(type_name);
2038
0
    }
2039
0
}
2040
2041
void
2042
QPDFObjectHandle::assertNull() const
2043
0
{
2044
0
    assertType("null", isNull());
2045
0
}
2046
2047
void
2048
QPDFObjectHandle::assertBool() const
2049
0
{
2050
0
    assertType("boolean", isBool());
2051
0
}
2052
2053
void
2054
QPDFObjectHandle::assertInteger() const
2055
0
{
2056
0
    assertType("integer", isInteger());
2057
0
}
2058
2059
void
2060
QPDFObjectHandle::assertReal() const
2061
0
{
2062
0
    assertType("real", isReal());
2063
0
}
2064
2065
void
2066
QPDFObjectHandle::assertName() const
2067
0
{
2068
0
    assertType("name", isName());
2069
0
}
2070
2071
void
2072
QPDFObjectHandle::assertString() const
2073
0
{
2074
0
    assertType("string", isString());
2075
0
}
2076
2077
void
2078
QPDFObjectHandle::assertOperator() const
2079
0
{
2080
0
    assertType("operator", isOperator());
2081
0
}
2082
2083
void
2084
QPDFObjectHandle::assertInlineImage() const
2085
0
{
2086
0
    assertType("inlineimage", isInlineImage());
2087
0
}
2088
2089
void
2090
QPDFObjectHandle::assertArray() const
2091
0
{
2092
0
    assertType("array", isArray());
2093
0
}
2094
2095
void
2096
QPDFObjectHandle::assertDictionary() const
2097
0
{
2098
0
    assertType("dictionary", isDictionary());
2099
0
}
2100
2101
void
2102
QPDFObjectHandle::assertStream() const
2103
0
{
2104
0
    assertType("stream", isStream());
2105
0
}
2106
2107
void
2108
QPDFObjectHandle::assertReserved() const
2109
0
{
2110
0
    assertType("reserved", isReserved());
2111
0
}
2112
2113
void
2114
QPDFObjectHandle::assertIndirect() const
2115
0
{
2116
0
    if (!isIndirect()) {
2117
0
        throw std::logic_error("operation for indirect object attempted on direct object");
2118
0
    }
2119
0
}
2120
2121
void
2122
QPDFObjectHandle::assertScalar() const
2123
0
{
2124
0
    assertType("scalar", isScalar());
2125
0
}
2126
2127
void
2128
QPDFObjectHandle::assertNumber() const
2129
0
{
2130
0
    assertType("number", isNumber());
2131
0
}
2132
2133
bool
2134
QPDFObjectHandle::isPageObject() const
2135
0
{
2136
    // See comments in QPDFObjectHandle.hh.
2137
0
    if (!qpdf()) {
2138
0
        return false;
2139
0
    }
2140
    // getAllPages repairs /Type when traversing the page tree.
2141
0
    (void)qpdf()->doc().pages().all();
2142
0
    return isDictionaryOfType("/Page");
2143
0
}
2144
2145
bool
2146
QPDFObjectHandle::isPagesObject() const
2147
0
{
2148
0
    if (!qpdf()) {
2149
0
        return false;
2150
0
    }
2151
    // getAllPages repairs /Type when traversing the page tree.
2152
0
    (void)qpdf()->doc().pages().all();
2153
0
    return isDictionaryOfType("/Pages");
2154
0
}
2155
2156
bool
2157
QPDFObjectHandle::isFormXObject() const
2158
0
{
2159
0
    return isStreamOfType("", "/Form");
2160
0
}
2161
2162
bool
2163
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2164
0
{
2165
0
    return (
2166
0
        isStreamOfType("", "/Image") &&
2167
0
        ((!exclude_imagemask) ||
2168
0
         (!(getDict().getKey("/ImageMask").isBool() &&
2169
0
            getDict().getKey("/ImageMask").getBoolValue()))));
2170
0
}
2171
2172
void
2173
QPDFObjectHandle::assertPageObject() const
2174
0
{
2175
0
    if (!isPageObject()) {
2176
0
        throw std::runtime_error("page operation called on non-Page object");
2177
0
    }
2178
0
}
2179
2180
void
2181
BaseHandle::warn(QPDF* qpdf, QPDFExc&& e)
2182
0
{
2183
0
    if (!qpdf) {
2184
0
        throw std::move(e);
2185
0
    }
2186
0
    qpdf->warn(std::move(e));
2187
0
}
2188
2189
void
2190
BaseHandle::warn(QPDFExc&& e) const
2191
0
{
2192
0
    if (!qpdf()) {
2193
0
        throw std::move(e);
2194
0
    }
2195
0
    qpdf()->warn(std::move(e));
2196
0
}
2197
2198
void
2199
BaseHandle::warn(std::string const& warning) const
2200
0
{
2201
0
    if (qpdf()) {
2202
0
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2203
0
    } else {
2204
0
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2205
0
    }
2206
0
}
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
76.3k
{
2376
76.3k
    return obj ? obj->getObjGen() : QPDFObjGen();
2377
76.3k
}
2378
2379
int
2380
QPDFObjectHandle::getObjectID() const
2381
41.1k
{
2382
41.1k
    return getObjGen().getObj();
2383
41.1k
}
2384
2385
int
2386
QPDFObjectHandle::getGeneration() const
2387
0
{
2388
0
    return getObjGen().getGen();
2389
0
}
2390
2391
bool
2392
QPDFObjectHandle::isIndirect() const
2393
41.1k
{
2394
41.1k
    return getObjectID() != 0;
2395
41.1k
}
2396
2397
// Indirect object accessors
2398
QPDF*
2399
QPDFObjectHandle::getOwningQPDF() const
2400
211k
{
2401
211k
    return obj ? obj->getQPDF() : nullptr;
2402
211k
}
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
0
{
2416
0
    if (obj) {
2417
0
        obj->setParsedOffset(offset);
2418
0
    }
2419
0
}
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
}