Coverage Report

Created: 2026-09-14 06:15

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