Coverage Report

Created: 2026-01-16 06:32

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