Coverage Report

Created: 2025-12-14 06:36

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