Coverage Report

Created: 2025-08-29 06:54

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