Coverage Report

Created: 2025-08-26 07:08

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