Coverage Report

Created: 2025-07-14 06:14

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