Coverage Report

Created: 2025-08-26 07:08

/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
367k
{
33
367k
    return obj ? obj->getObjGen() : QPDFObjGen();
34
367k
}
35
36
namespace
37
{
38
    class TerminateParsing
39
    {
40
    };
41
} // namespace
42
43
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
44
2.72k
    supports_retry(supports_retry)
45
2.72k
{
46
2.72k
}
47
48
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
49
2.72k
{
50
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
51
2.72k
}
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.72k
{
84
2.72k
    return supports_retry;
85
2.72k
}
86
87
namespace
88
{
89
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
90
    {
91
      public:
92
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
93
2.72k
            containing_page(containing_page),
94
2.72k
            old_contents(old_contents)
95
2.72k
        {
96
2.72k
        }
97
2.72k
        ~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.72k
{
109
2.72k
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
110
2.72k
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
111
2.72k
    std::string all_description;
112
2.72k
    old_contents.pipeContentStreams(p, description, all_description);
113
2.72k
}
114
115
void
116
QPDFObjectHandle::TokenFilter::handleEOF()
117
4.41k
{
118
4.41k
}
119
120
void
121
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
122
8.83k
{
123
8.83k
    pipeline = p;
124
8.83k
}
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
314k
{
179
314k
    int tail = 0;       // Number of continuation characters expected.
180
314k
    bool tail2 = false; // Potential overlong 3 octet utf-8.
181
314k
    bool tail3 = false; // potential overlong 4 octet
182
314k
    bool needs_escaping = false;
183
3.86M
    for (auto const& it: name) {
184
3.86M
        auto c = static_cast<unsigned char>(it);
185
3.86M
        if (tail) {
186
12.8k
            if ((c & 0xc0) != 0x80) {
187
6.79k
                return {false, false};
188
6.79k
            }
189
6.09k
            if (tail2) {
190
159
                if ((c & 0xe0) == 0x80) {
191
70
                    return {false, false};
192
70
                }
193
89
                tail2 = false;
194
5.93k
            } else if (tail3) {
195
853
                if ((c & 0xf0) == 0x80) {
196
359
                    return {false, false};
197
359
                }
198
494
                tail3 = false;
199
494
            }
200
5.66k
            tail--;
201
3.85M
        } else if (c < 0x80) {
202
3.82M
            if (!needs_escaping) {
203
2.89M
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
204
2.89M
            }
205
3.82M
        } else if ((c & 0xe0) == 0xc0) {
206
7.14k
            if ((c & 0xfe) == 0xc0) {
207
617
                return {false, false};
208
617
            }
209
6.52k
            tail = 1;
210
22.1k
        } else if ((c & 0xf0) == 0xe0) {
211
1.33k
            tail2 = (c == 0xe0);
212
1.33k
            tail = 2;
213
20.8k
        } else if ((c & 0xf8) == 0xf0) {
214
2.62k
            tail3 = (c == 0xf0);
215
2.62k
            tail = 3;
216
18.2k
        } else {
217
18.2k
            return {false, false};
218
18.2k
        }
219
3.86M
    }
220
288k
    return {tail == 0, !needs_escaping};
221
314k
}
222
223
std::string
224
Name::normalize(std::string const& name)
225
226k
{
226
226k
    if (name.empty()) {
227
0
        return name;
228
0
    }
229
226k
    std::string result;
230
226k
    result += name.at(0);
231
21.1M
    for (size_t i = 1; i < name.length(); ++i) {
232
20.9M
        char ch = name.at(i);
233
        // Don't use locale/ctype here; follow PDF spec guidelines.
234
20.9M
        if (ch == '\0') {
235
            // QPDFTokenizer embeds a null character to encode an invalid #.
236
21.5k
            result += "#";
237
20.9M
        } else if (
238
20.9M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
239
20.9M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
240
16.7M
            result += util::hex_encode_char(ch);
241
16.7M
        } else {
242
4.16M
            result += ch;
243
4.16M
        }
244
20.9M
    }
245
226k
    return result;
246
226k
}
247
248
std::shared_ptr<QPDFObject>
249
BaseHandle::copy(bool shallow) const
250
576k
{
251
576k
    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
81.2k
    case ::ot_null:
258
81.2k
        return QPDFObject::create<QPDF_Null>();
259
2.69k
    case ::ot_boolean:
260
2.69k
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
261
78.4k
    case ::ot_integer:
262
78.4k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
263
21.0k
    case ::ot_real:
264
21.0k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
265
83.1k
    case ::ot_string:
266
83.1k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
267
248k
    case ::ot_name:
268
248k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
269
24.5k
    case ::ot_array:
270
24.5k
        {
271
24.5k
            auto const& a = std::get<QPDF_Array>(obj->value);
272
24.5k
            if (shallow) {
273
0
                return QPDFObject::create<QPDF_Array>(a);
274
24.5k
            } else {
275
24.5k
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
276
24.5k
                if (a.sp) {
277
98
                    QPDF_Array result;
278
98
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
279
98
                    result.sp->size = a.sp->size;
280
66.9k
                    for (auto const& [idx, oh]: a.sp->elements) {
281
66.9k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
282
66.9k
                    }
283
98
                    return QPDFObject::create<QPDF_Array>(std::move(result));
284
24.4k
                } else {
285
24.4k
                    std::vector<QPDFObjectHandle> result;
286
24.4k
                    result.reserve(a.elements.size());
287
300k
                    for (auto const& element: a.elements) {
288
300k
                        result.emplace_back(
289
300k
                            element ? (element.indirect() ? element : element.copy()) : element);
290
300k
                    }
291
24.4k
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
292
24.4k
                }
293
24.5k
            }
294
24.5k
        }
295
36.7k
    case ::ot_dictionary:
296
36.7k
        {
297
36.7k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
298
36.7k
            if (shallow) {
299
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
300
36.7k
            } else {
301
36.7k
                std::map<std::string, QPDFObjectHandle> new_items;
302
283k
                for (auto const& [key, val]: d.items) {
303
283k
                    new_items[key] = val.indirect() ? val : val.copy();
304
283k
                }
305
36.7k
                return QPDFObject::create<QPDF_Dictionary>(new_items);
306
36.7k
            }
307
36.7k
        }
308
7
    case ::ot_stream:
309
7
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
310
7
        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
576k
    }
325
0
    return {}; // unreachable
326
576k
}
327
328
std::string
329
BaseHandle::unparse() const
330
360k
{
331
360k
    switch (resolved_type_code()) {
332
0
    case ::ot_uninitialized:
333
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object");
334
0
        return ""; // does not return
335
0
    case ::ot_reserved:
336
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
337
0
        return ""; // does not return
338
20.5k
    case ::ot_null:
339
20.5k
        return "null";
340
189
    case ::ot_boolean:
341
189
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
342
46.1k
    case ::ot_integer:
343
46.1k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
344
1.43k
    case ::ot_real:
345
1.43k
        return std::get<QPDF_Real>(obj->value).val;
346
92.0k
    case ::ot_string:
347
92.0k
        return std::get<QPDF_String>(obj->value).unparse(false);
348
200k
    case ::ot_name:
349
200k
        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
360k
    }
401
0
    return {}; // unreachable
402
360k
}
403
404
void
405
BaseHandle::write_json(int json_version, JSON::Writer& p) const
406
385k
{
407
385k
    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
12.1k
    case ::ot_null:
413
12.1k
    case ::ot_operator:
414
12.1k
    case ::ot_inlineimage:
415
12.1k
        p << "null";
416
12.1k
        break;
417
8.89k
    case ::ot_boolean:
418
8.89k
        p << std::get<QPDF_Bool>(obj->value).val;
419
8.89k
        break;
420
80.1k
    case ::ot_integer:
421
80.1k
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
422
80.1k
        break;
423
26.3k
    case ::ot_real:
424
26.3k
        {
425
26.3k
            auto const& val = std::get<QPDF_Real>(obj->value).val;
426
26.3k
            if (val.empty()) {
427
                // Can't really happen...
428
0
                p << "0";
429
26.3k
            } else if (val.at(0) == '.') {
430
341
                p << "0" << val;
431
26.0k
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
432
387
                p << "-0." << val.substr(2);
433
25.6k
            } else {
434
25.6k
                p << val;
435
25.6k
            }
436
26.3k
            if (val.back() == '.') {
437
660
                p << "0";
438
660
            }
439
26.3k
        }
440
26.3k
        break;
441
44.1k
    case ::ot_string:
442
44.1k
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
443
44.1k
        break;
444
124k
    case ::ot_name:
445
124k
        {
446
124k
            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
124k
            if (json_version == 1) {
450
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
451
124k
            } else {
452
124k
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
453
107k
                    if (res.second) {
454
100k
                        p << "\"" << n.name << "\"";
455
100k
                    } else {
456
7.13k
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
457
7.13k
                    }
458
107k
                } else {
459
17.2k
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
460
17.2k
                }
461
124k
            }
462
124k
        }
463
124k
        break;
464
32.1k
    case ::ot_array:
465
32.1k
        {
466
32.1k
            auto const& a = std::get<QPDF_Array>(obj->value);
467
32.1k
            p.writeStart('[');
468
32.1k
            if (a.sp) {
469
119
                size_t next = 0;
470
11.4k
                for (auto& [key, value]: a.sp->elements) {
471
27.9k
                    for (size_t j = next; j < key; ++j) {
472
16.4k
                        p.writeNext() << "null";
473
16.4k
                    }
474
11.4k
                    p.writeNext();
475
11.4k
                    auto item_og = value.getObj()->getObjGen();
476
11.4k
                    if (item_og.isIndirect()) {
477
240
                        p << "\"" << item_og.unparse(' ') << " R\"";
478
11.2k
                    } else {
479
11.2k
                        value.write_json(json_version, p);
480
11.2k
                    }
481
11.4k
                    next = key + 1;
482
11.4k
                }
483
2.53k
                for (size_t j = next; j < a.sp->size; ++j) {
484
2.41k
                    p.writeNext() << "null";
485
2.41k
                }
486
31.9k
            } else {
487
220k
                for (auto const& item: a.elements) {
488
220k
                    p.writeNext();
489
220k
                    auto item_og = item.id_gen();
490
220k
                    if (item_og.isIndirect()) {
491
12.4k
                        p << "\"" << item_og.unparse(' ') << " R\"";
492
208k
                    } else {
493
208k
                        item.write_json(json_version, p);
494
208k
                    }
495
220k
                }
496
31.9k
            }
497
32.1k
            p.writeEnd(']');
498
32.1k
        }
499
32.1k
        break;
500
57.1k
    case ::ot_dictionary:
501
57.1k
        {
502
57.1k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
503
57.1k
            p.writeStart('{');
504
242k
            for (auto& iter: d.items) {
505
242k
                if (!iter.second.null()) {
506
189k
                    p.writeNext();
507
189k
                    if (json_version == 1) {
508
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
509
0
                          << "\": ";
510
189k
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
511
180k
                        if (res.second) {
512
177k
                            p << "\"" << iter.first << "\": ";
513
177k
                        } else {
514
2.66k
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
515
2.66k
                        }
516
180k
                    } else {
517
9.50k
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
518
9.50k
                          << "\": ";
519
9.50k
                    }
520
189k
                    iter.second.writeJSON(json_version, p);
521
189k
                }
522
242k
            }
523
57.1k
            p.writeEnd('}');
524
57.1k
        }
525
57.1k
        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
385k
    }
535
385k
}
536
537
void
538
BaseHandle::disconnect(bool only_direct)
539
3.07M
{
540
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
541
3.07M
    if (only_direct && indirect()) {
542
219k
        return;
543
219k
    }
544
545
2.85M
    switch (raw_type_code()) {
546
149k
    case ::ot_array:
547
149k
        {
548
149k
            auto& a = std::get<QPDF_Array>(obj->value);
549
149k
            if (a.sp) {
550
127k
                for (auto& item: a.sp->elements) {
551
127k
                    item.second.disconnect();
552
127k
                }
553
149k
            } else {
554
1.22M
                for (auto& oh: a.elements) {
555
1.22M
                    oh.disconnect();
556
1.22M
                }
557
149k
            }
558
149k
        }
559
149k
        break;
560
272k
    case ::ot_dictionary:
561
1.19M
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
562
1.19M
            iter.second.disconnect();
563
1.19M
        }
564
272k
        break;
565
35.0k
    case ::ot_stream:
566
35.0k
        {
567
35.0k
            auto& s = std::get<QPDF_Stream>(obj->value);
568
35.0k
            s.m->stream_provider = nullptr;
569
35.0k
            s.m->stream_dict.disconnect();
570
35.0k
        }
571
35.0k
        break;
572
0
    case ::ot_uninitialized:
573
0
        return;
574
2.39M
    default:
575
2.39M
        break;
576
2.85M
    }
577
2.85M
    obj->qpdf = nullptr;
578
2.85M
    obj->og = QPDFObjGen();
579
2.85M
}
580
581
std::string
582
QPDFObject::getStringValue() const
583
590k
{
584
590k
    switch (getResolvedTypeCode()) {
585
54.8k
    case ::ot_real:
586
54.8k
        return std::get<QPDF_Real>(value).val;
587
3.31k
    case ::ot_string:
588
3.31k
        return std::get<QPDF_String>(value).val;
589
209k
    case ::ot_name:
590
209k
        return std::get<QPDF_Name>(value).name;
591
322k
    case ::ot_operator:
592
322k
        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
590k
    }
600
0
    return ""; // unreachable
601
590k
}
602
603
bool
604
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
605
1.33k
{
606
1.33k
    return obj == rhs.obj;
607
1.33k
}
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
25.5k
{
618
25.5k
    static constexpr std::array<char const*, 16> tn{
619
25.5k
        "uninitialized",
620
25.5k
        "reserved",
621
25.5k
        "null",
622
25.5k
        "boolean",
623
25.5k
        "integer",
624
25.5k
        "real",
625
25.5k
        "string",
626
25.5k
        "name",
627
25.5k
        "array",
628
25.5k
        "dictionary",
629
25.5k
        "stream",
630
25.5k
        "operator",
631
25.5k
        "inline-image",
632
25.5k
        "unresolved",
633
25.5k
        "destroyed",
634
25.5k
        "reference"};
635
25.5k
    return tn[type_code()];
636
25.5k
}
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
382k
{
653
382k
    return type_code() == ::ot_boolean;
654
382k
}
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
587k
{
667
587k
    return type_code() == ::ot_null;
668
587k
}
669
670
bool
671
QPDFObjectHandle::isInteger() const
672
1.65M
{
673
1.65M
    return type_code() == ::ot_integer;
674
1.65M
}
675
676
bool
677
QPDFObjectHandle::isReal() const
678
325k
{
679
325k
    return type_code() == ::ot_real;
680
325k
}
681
682
bool
683
QPDFObjectHandle::isNumber() const
684
170k
{
685
170k
    return (isInteger() || isReal());
686
170k
}
687
688
double
689
QPDFObjectHandle::getNumericValue() const
690
61.3k
{
691
61.3k
    if (isInteger()) {
692
6.46k
        return static_cast<double>(getIntValue());
693
54.8k
    } else if (isReal()) {
694
54.8k
        return atof(getRealValue().c_str());
695
54.8k
    } else {
696
0
        typeWarning("number", "returning 0");
697
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
698
0
        return 0;
699
0
    }
700
61.3k
}
701
702
bool
703
QPDFObjectHandle::getValueAsNumber(double& value) const
704
61.6k
{
705
61.6k
    if (!isNumber()) {
706
343
        return false;
707
343
    }
708
61.3k
    value = getNumericValue();
709
61.3k
    return true;
710
61.6k
}
711
712
bool
713
QPDFObjectHandle::isName() const
714
814k
{
715
814k
    return type_code() == ::ot_name;
716
814k
}
717
718
bool
719
QPDFObjectHandle::isString() const
720
307k
{
721
307k
    return type_code() == ::ot_string;
722
307k
}
723
724
bool
725
QPDFObjectHandle::isOperator() const
726
1.04M
{
727
1.04M
    return type_code() == ::ot_operator;
728
1.04M
}
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
45.1k
{
739
45.1k
    return type_code() == ::ot_array;
740
45.1k
}
741
742
bool
743
QPDFObjectHandle::isDictionary() const
744
1.84M
{
745
1.84M
    return type_code() == ::ot_dictionary;
746
1.84M
}
747
748
bool
749
QPDFObjectHandle::isStream() const
750
304k
{
751
304k
    return type_code() == ::ot_stream;
752
304k
}
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
369k
{
763
369k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
764
369k
}
765
766
bool
767
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
768
195k
{
769
195k
    return isName() && (getName() == name);
770
195k
}
771
772
bool
773
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
774
653k
{
775
653k
    return isDictionary() && (type.empty() || getKey("/Type").isNameAndEquals(type)) &&
776
653k
        (subtype.empty() || getKey("/Subtype").isNameAndEquals(subtype));
777
653k
}
778
779
bool
780
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
781
193k
{
782
193k
    return isStream() && getDict().isDictionaryOfType(type, subtype);
783
193k
}
784
785
// Bool accessors
786
787
bool
788
QPDFObjectHandle::getBoolValue() const
789
2.16k
{
790
2.16k
    if (auto boolean = as<QPDF_Bool>()) {
791
2.16k
        return boolean->val;
792
2.16k
    } else {
793
0
        typeWarning("boolean", "returning false");
794
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
795
0
        return false;
796
0
    }
797
2.16k
}
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
706k
{
814
706k
    if (auto integer = as<QPDF_Integer>()) {
815
706k
        return integer->val;
816
706k
    } else {
817
804
        typeWarning("integer", "returning 0");
818
804
        QTC::TC("qpdf", "QPDFObjectHandle integer returning 0");
819
804
        return 0;
820
804
    }
821
706k
}
822
823
bool
824
QPDFObjectHandle::getValueAsInt(long long& value) const
825
6.66k
{
826
6.66k
    if (auto integer = as<QPDF_Integer>()) {
827
6.58k
        value = integer->val;
828
6.58k
        return true;
829
6.58k
    }
830
78
    return false;
831
6.66k
}
832
833
int
834
QPDFObjectHandle::getIntValueAsInt() const
835
45.9k
{
836
45.9k
    long long v = getIntValue();
837
45.9k
    if (v < INT_MIN) {
838
45
        warn("requested value of integer is too small; returning INT_MIN");
839
45
        return INT_MIN;
840
45
    }
841
45.8k
    if (v > INT_MAX) {
842
367
        warn("requested value of integer is too big; returning INT_MAX");
843
367
        return INT_MAX;
844
367
    }
845
45.5k
    return static_cast<int>(v);
846
45.8k
}
847
848
bool
849
QPDFObjectHandle::getValueAsInt(int& value) const
850
1.72k
{
851
1.72k
    if (!isInteger()) {
852
48
        return false;
853
48
    }
854
1.68k
    value = getIntValueAsInt();
855
1.68k
    return true;
856
1.72k
}
857
858
unsigned long long
859
QPDFObjectHandle::getUIntValue() const
860
24.4k
{
861
24.4k
    long long v = getIntValue();
862
24.4k
    if (v < 0) {
863
158
        warn("unsigned value request for negative number; returning 0");
864
158
        return 0;
865
24.3k
    } else {
866
24.3k
        return static_cast<unsigned long long>(v);
867
24.3k
    }
868
24.4k
}
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.50k
{
883
7.50k
    long long v = getIntValue();
884
7.50k
    if (v < 0) {
885
99
        warn("unsigned integer value request for negative number; returning 0");
886
99
        return 0;
887
99
    }
888
7.40k
    if (v > UINT_MAX) {
889
84
        warn("requested value of unsigned integer is too big; returning UINT_MAX");
890
84
        return UINT_MAX;
891
84
    }
892
7.31k
    return static_cast<unsigned int>(v);
893
7.40k
}
894
895
bool
896
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
897
1.87k
{
898
1.87k
    if (!isInteger()) {
899
148
        return false;
900
148
    }
901
1.72k
    value = getUIntValueAsUInt();
902
1.72k
    return true;
903
1.87k
}
904
905
// Real accessors
906
907
std::string
908
QPDFObjectHandle::getRealValue() const
909
54.8k
{
910
54.8k
    if (isReal()) {
911
54.8k
        return obj->getStringValue();
912
54.8k
    } 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
54.8k
}
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
211k
{
934
211k
    if (isName()) {
935
209k
        return obj->getStringValue();
936
209k
    } else {
937
1.91k
        typeWarning("name", "returning dummy name");
938
1.91k
        QTC::TC("qpdf", "QPDFObjectHandle name returning dummy name");
939
1.91k
        return "/QPDFFakeName";
940
1.91k
    }
941
211k
}
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.31k
{
958
3.31k
    if (isString()) {
959
3.31k
        return obj->getStringValue();
960
3.31k
    } 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.31k
}
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
141k
{
980
141k
    if (auto str = as<QPDF_String>()) {
981
141k
        return str->getUTF8Val();
982
141k
    } else {
983
0
        typeWarning("string", "returning empty string");
984
0
        QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8");
985
0
        return "";
986
0
    }
987
141k
}
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
322k
{
1004
322k
    if (isOperator()) {
1005
322k
        return obj->getStringValue();
1006
322k
    } 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
322k
}
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
10.9k
{
1050
10.9k
    return *this;
1051
10.9k
}
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
450
{
1066
450
    if (isNameAndEquals(value)) {
1067
3
        return true;
1068
447
    } else if (isArray()) {
1069
200
        for (auto& item: getArrayAsVector()) {
1070
200
            if (item.isNameAndEquals(value)) {
1071
0
                return true;
1072
0
            }
1073
200
        }
1074
79
    }
1075
447
    return false;
1076
450
}
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
10.3k
{
1094
10.3k
    if (!(isDictionary() && other.isDictionary())) {
1095
2.32k
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1096
2.32k
        return;
1097
2.32k
    }
1098
1099
8.03k
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1100
8.03k
                              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
30.3k
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1111
30.3k
        auto other_val = value1;
1112
30.3k
        if (hasKey(rtype)) {
1113
16.3k
            QPDFObjectHandle this_val = getKey(rtype);
1114
16.3k
            if (this_val.isDictionary() && other_val.isDictionary()) {
1115
7.54k
                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
2.63k
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1120
2.63k
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1121
2.63k
                }
1122
7.54k
                std::map<QPDFObjGen, std::string> og_to_name;
1123
7.54k
                std::set<std::string> rnames;
1124
7.54k
                int min_suffix = 1;
1125
7.54k
                bool initialized_maps = false;
1126
91.0k
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1127
91.0k
                    QPDFObjectHandle rval = value2;
1128
91.0k
                    if (!this_val.hasKey(key)) {
1129
15.1k
                        if (!rval.isIndirect()) {
1130
9.89k
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1131
9.89k
                            rval = rval.shallowCopy();
1132
9.89k
                        }
1133
15.1k
                        this_val.replaceKey(key, rval);
1134
75.9k
                    } 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
91.0k
                }
1156
8.79k
            } else if (this_val.isArray() && other_val.isArray()) {
1157
3.15k
                std::set<std::string> scalars;
1158
154k
                for (auto this_item: this_val.aitems()) {
1159
154k
                    if (this_item.isScalar()) {
1160
153k
                        scalars.insert(this_item.unparse());
1161
153k
                    }
1162
154k
                }
1163
214k
                for (auto other_item: other_val.aitems()) {
1164
214k
                    if (other_item.isScalar()) {
1165
210k
                        if (!scalars.contains(other_item.unparse())) {
1166
58.8k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1167
58.8k
                            this_val.appendItem(other_item);
1168
151k
                        } else {
1169
151k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1170
151k
                        }
1171
210k
                    }
1172
214k
                }
1173
3.15k
            }
1174
16.3k
        } else {
1175
14.0k
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1176
14.0k
            replaceKey(rtype, other_val.shallowCopy());
1177
14.0k
        }
1178
30.3k
    }
1179
8.03k
}
1180
1181
std::set<std::string>
1182
QPDFObjectHandle::getResourceNames() const
1183
6.32k
{
1184
    // Return second-level dictionary keys
1185
6.32k
    std::set<std::string> result;
1186
31.6k
    for (auto const& item: as_dictionary(strict)) {
1187
181k
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1188
181k
            if (!val2.null()) {
1189
141k
                result.insert(key2);
1190
141k
            }
1191
181k
        }
1192
31.6k
    }
1193
6.32k
    return result;
1194
6.32k
}
1195
1196
std::string
1197
QPDFObjectHandle::getUniqueResourceName(
1198
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1199
6.32k
{
1200
6.32k
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1201
6.32k
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1202
6.33k
    while (min_suffix <= max_suffix) {
1203
6.33k
        std::string candidate = prefix + std::to_string(min_suffix);
1204
6.33k
        if (!names.contains(candidate)) {
1205
6.32k
            return candidate;
1206
6.32k
        }
1207
        // Increment after return; min_suffix should be the value
1208
        // used, not the next value.
1209
9
        ++min_suffix;
1210
9
    }
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
6.32k
}
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
28.0k
{
1232
28.0k
    all_description = description;
1233
28.0k
    std::vector<QPDFObjectHandle> result;
1234
28.0k
    if (auto array = as_array(strict)) {
1235
4.98k
        int n_items = static_cast<int>(array.size());
1236
15.3k
        for (int i = 0; i < n_items; ++i) {
1237
10.3k
            QPDFObjectHandle item = array[i];
1238
10.3k
            if (item.isStream()) {
1239
7.85k
                result.emplace_back(item);
1240
7.85k
            } else {
1241
2.46k
                item.warn(
1242
2.46k
                    {qpdf_e_damaged_pdf,
1243
2.46k
                     "",
1244
2.46k
                     description + ": item index " + std::to_string(i) + " (from 0)",
1245
2.46k
                     0,
1246
2.46k
                     "ignoring non-stream in an array of streams"});
1247
2.46k
            }
1248
10.3k
        }
1249
23.0k
    } else if (isStream()) {
1250
5.85k
        result.emplace_back(*this);
1251
17.2k
    } else if (!null()) {
1252
1.02k
        warn(
1253
1.02k
            {qpdf_e_damaged_pdf,
1254
1.02k
             "",
1255
1.02k
             description,
1256
1.02k
             0,
1257
1.02k
             " object is supposed to be a stream or an array of streams but is neither"});
1258
1.02k
    }
1259
1260
28.0k
    bool first = true;
1261
28.0k
    for (auto const& item: result) {
1262
13.6k
        if (first) {
1263
10.4k
            first = false;
1264
10.4k
        } else {
1265
3.24k
            all_description += ",";
1266
3.24k
        }
1267
13.6k
        all_description += " stream " + item.getObjGen().unparse(' ');
1268
13.6k
    }
1269
1270
28.0k
    return result;
1271
28.0k
}
1272
1273
std::vector<QPDFObjectHandle>
1274
QPDFObjectHandle::getPageContents()
1275
4.42k
{
1276
4.42k
    std::string description = "page object " + getObjGen().unparse(' ');
1277
4.42k
    std::string all_description;
1278
4.42k
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1279
4.42k
}
1280
1281
void
1282
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1283
4.42k
{
1284
4.42k
    new_contents.assertStream();
1285
1286
4.42k
    std::vector<QPDFObjectHandle> content_streams;
1287
4.42k
    if (first) {
1288
2.22k
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1289
2.22k
        content_streams.push_back(new_contents);
1290
2.22k
    }
1291
4.42k
    for (auto const& iter: getPageContents()) {
1292
2.74k
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1293
2.74k
        content_streams.push_back(iter);
1294
2.74k
    }
1295
4.42k
    if (!first) {
1296
2.20k
        content_streams.push_back(new_contents);
1297
2.20k
    }
1298
1299
4.42k
    replaceKey("/Contents", newArray(content_streams));
1300
4.42k
}
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
21.1k
{
1338
21.1k
    QPDFObjectHandle contents = getKey("/Contents");
1339
21.1k
    if (contents.isStream()) {
1340
2.97k
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1341
2.97k
        return;
1342
18.2k
    } 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
15.2k
        return;
1346
15.2k
    }
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.98k
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1352
1353
2.98k
    QPDFObjectHandle new_contents = newStream(&qpdf);
1354
2.98k
    replaceKey("/Contents", new_contents);
1355
1356
2.98k
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1357
2.98k
    new_contents.replaceStreamData(provider, newNull(), newNull());
1358
2.98k
}
1359
1360
std::string
1361
QPDFObjectHandle::unparse() const
1362
364k
{
1363
364k
    if (isIndirect()) {
1364
3.50k
        return getObjGen().unparse(' ') + " R";
1365
360k
    } else {
1366
360k
        return unparseResolved();
1367
360k
    }
1368
364k
}
1369
1370
std::string
1371
QPDFObjectHandle::unparseResolved() const
1372
360k
{
1373
360k
    if (!obj) {
1374
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1375
0
    }
1376
360k
    return BaseHandle::unparse();
1377
360k
}
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
16.0k
{
1392
16.0k
    if ((!dereference_indirect) && isIndirect()) {
1393
0
        return JSON::makeString(unparse());
1394
16.0k
    } else if (!obj) {
1395
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1396
16.0k
    } else {
1397
16.0k
        Pl_Buffer p{"json"};
1398
16.0k
        JSON::Writer jw{&p, 0};
1399
16.0k
        writeJSON(json_version, jw, dereference_indirect);
1400
16.0k
        p.finish();
1401
16.0k
        return JSON::parse(p.getString());
1402
16.0k
    }
1403
16.0k
}
1404
1405
void
1406
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1407
205k
{
1408
205k
    if (!dereference_indirect && isIndirect()) {
1409
39.2k
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1410
166k
    } else if (!obj) {
1411
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1412
166k
    } else {
1413
166k
        write_json(json_version, p);
1414
166k
    }
1415
205k
}
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
19.0k
{
1439
19.0k
    return parse(nullptr, object_str, object_description);
1440
19.0k
}
1441
1442
QPDFObjectHandle
1443
QPDFObjectHandle::parse(
1444
    QPDF* context, std::string const& object_str, std::string const& object_description)
1445
19.0k
{
1446
19.0k
    auto input = is::OffsetBuffer("parsed object", object_str);
1447
19.0k
    auto result = QPDFParser::parse(input, object_description, context);
1448
19.0k
    size_t offset = QIntC::to_size(input.tell());
1449
19.0k
    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
19.0k
    return result;
1462
19.0k
}
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
23.6k
{
1476
23.6k
    bool need_newline = false;
1477
23.6k
    std::string buffer;
1478
23.6k
    pl::String buf(buffer);
1479
23.6k
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1480
10.8k
        if (need_newline) {
1481
496
            buf.writeCStr("\n");
1482
496
        }
1483
10.8k
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1484
697
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1485
697
            throw QPDFExc(
1486
697
                qpdf_e_damaged_pdf,
1487
697
                "content stream",
1488
697
                "content stream object " + stream.getObjGen().unparse(' '),
1489
697
                0,
1490
697
                "errors while decoding content stream");
1491
697
        }
1492
10.1k
        need_newline = buffer.empty() || buffer.back() != '\n';
1493
10.1k
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1494
10.1k
        p->writeString(buffer);
1495
10.1k
        buffer.clear();
1496
10.1k
    }
1497
22.9k
    p->finish();
1498
22.9k
}
1499
1500
void
1501
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1502
20.9k
{
1503
20.9k
    std::string description = "page object " + getObjGen().unparse(' ');
1504
20.9k
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1505
20.9k
}
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
20.9k
{
1540
20.9k
    std::string stream_data;
1541
20.9k
    pl::String buf(stream_data);
1542
20.9k
    std::string all_description;
1543
20.9k
    pipeContentStreams(&buf, description, all_description);
1544
20.9k
    if (callbacks) {
1545
0
        callbacks->contentSize(stream_data.size());
1546
0
    }
1547
20.9k
    try {
1548
20.9k
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1549
20.9k
    } catch (TerminateParsing&) {
1550
0
        return;
1551
0
    }
1552
19.2k
    if (callbacks) {
1553
0
        callbacks->handleEOF();
1554
0
    }
1555
19.2k
}
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
19.5k
{
1564
19.5k
    size_t stream_length = stream_data.size();
1565
19.5k
    auto input = is::OffsetBuffer(description, stream_data);
1566
19.5k
    Tokenizer tokenizer;
1567
19.5k
    tokenizer.allowEOF();
1568
19.5k
    auto sp_description = QPDFParser::make_description(description, "content");
1569
738k
    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
722k
        tokenizer.nextToken(input, "content", true);
1574
722k
        qpdf_offset_t offset = input.getLastOffset();
1575
722k
        input.seek(offset, SEEK_SET);
1576
722k
        auto obj = QPDFParser::parse_content(input, sp_description, tokenizer, context);
1577
722k
        if (!obj) {
1578
            // EOF
1579
3.59k
            break;
1580
3.59k
        }
1581
718k
        size_t length = QIntC::to_size(input.tell() - offset);
1582
718k
        if (callbacks) {
1583
0
            callbacks->handleObject(obj, QIntC::to_size(offset), length);
1584
0
        }
1585
718k
        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.47k
            char ch;
1589
2.47k
            input.read(&ch, 1);
1590
2.47k
            tokenizer.expectInlineImage(input);
1591
2.47k
            tokenizer.nextToken(input, description);
1592
2.47k
            offset = input.getLastOffset();
1593
2.47k
            length = QIntC::to_size(input.tell() - offset);
1594
2.47k
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1595
80
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1596
80
                warn(
1597
80
                    context,
1598
80
                    QPDFExc(
1599
80
                        qpdf_e_damaged_pdf,
1600
80
                        description,
1601
80
                        "stream data",
1602
80
                        input.tell(),
1603
80
                        "EOF found while reading inline image"));
1604
2.39k
            } else {
1605
2.39k
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1606
2.39k
                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.39k
            }
1613
2.47k
        }
1614
718k
    }
1615
19.5k
}
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.41k
{
1627
4.41k
    return as_stream(error).addTokenFilter(filter);
1628
4.41k
}
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
148k
{
1657
148k
    return {QPDFObject::create<QPDF_Null>()};
1658
148k
}
1659
1660
QPDFObjectHandle
1661
QPDFObjectHandle::newInteger(long long value)
1662
17.7k
{
1663
17.7k
    return {QPDFObject::create<QPDF_Integer>(value)};
1664
17.7k
}
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
33.5k
{
1675
33.5k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1676
33.5k
}
1677
1678
QPDFObjectHandle
1679
QPDFObjectHandle::newName(std::string const& name)
1680
5.64k
{
1681
5.64k
    return {QPDFObject::create<QPDF_Name>(name)};
1682
5.64k
}
1683
1684
QPDFObjectHandle
1685
QPDFObjectHandle::newString(std::string const& str)
1686
14
{
1687
14
    return {QPDFObject::create<QPDF_String>(str)};
1688
14
}
1689
1690
QPDFObjectHandle
1691
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1692
600
{
1693
600
    return {QPDF_String::create_utf16(utf8_str)};
1694
600
}
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
24
{
1711
24
    return newArray(std::vector<QPDFObjectHandle>());
1712
24
}
1713
1714
QPDFObjectHandle
1715
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1716
17.0k
{
1717
17.0k
    return {QPDFObject::create<QPDF_Array>(items)};
1718
17.0k
}
1719
1720
QPDFObjectHandle
1721
QPDFObjectHandle::newArray(Rectangle const& rect)
1722
8.38k
{
1723
8.38k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1724
8.38k
}
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.98k
{
1753
2.98k
    return newArray(rect);
1754
2.98k
}
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
26.2k
{
1771
26.2k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1772
26.2k
}
1773
1774
QPDFObjectHandle
1775
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1776
26.2k
{
1777
26.2k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1778
26.2k
}
1779
1780
QPDFObjectHandle
1781
QPDFObjectHandle::newStream(QPDF* qpdf)
1782
2.84k
{
1783
2.84k
    if (qpdf == nullptr) {
1784
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1785
0
    }
1786
2.84k
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1787
2.84k
    return qpdf->newStream();
1788
2.84k
}
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.98k
{
1803
2.98k
    if (qpdf == nullptr) {
1804
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1805
0
    }
1806
2.98k
    QTC::TC("qpdf", "QPDFObjectHandle newStream with string");
1807
2.98k
    return qpdf->newStream(data);
1808
2.98k
}
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
141k
{
1822
141k
    return obj ? obj->getDescription() : ""s;
1823
141k
}
1824
1825
void
1826
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
1827
10.1k
{
1828
10.1k
    if (obj) {
1829
10.1k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
1830
10.1k
        obj->setDescription(owning_qpdf, descr);
1831
10.1k
    }
1832
10.1k
}
1833
1834
bool
1835
QPDFObjectHandle::hasObjectDescription() const
1836
44.5k
{
1837
44.5k
    return obj && obj->hasDescription();
1838
44.5k
}
1839
1840
QPDFObjectHandle
1841
QPDFObjectHandle::shallowCopy()
1842
37.5k
{
1843
37.5k
    if (!obj) {
1844
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
1845
0
    }
1846
37.5k
    return {copy()};
1847
37.5k
}
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
25.5k
{
1945
25.5k
    return {
1946
25.5k
        qpdf_e_object,
1947
25.5k
        "",
1948
25.5k
        description(),
1949
25.5k
        0,
1950
25.5k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
1951
25.5k
            message};
1952
25.5k
}
1953
1954
void
1955
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
1956
25.5k
{
1957
25.5k
    if (!obj) {
1958
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1959
0
    }
1960
25.5k
    warn(type_error(expected_type, message));
1961
25.5k
}
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.42k
{
1978
4.42k
    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.42k
}
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.42k
{
2048
4.42k
    assertType("stream", isStream());
2049
4.42k
}
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
80.8k
{
2103
80.8k
    return isStreamOfType("", "/Form");
2104
80.8k
}
2105
2106
bool
2107
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2108
14.3k
{
2109
14.3k
    return (
2110
14.3k
        isStreamOfType("", "/Image") &&
2111
14.3k
        ((!exclude_imagemask) ||
2112
2.08k
         (!(getDict().getKey("/ImageMask").isBool() &&
2113
2.08k
            getDict().getKey("/ImageMask").getBoolValue()))));
2114
14.3k
}
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
80
{
2127
80
    if (!qpdf) {
2128
0
        throw std::move(e);
2129
0
    }
2130
80
    qpdf->warn(std::move(e));
2131
80
}
2132
2133
void
2134
BaseHandle::warn(QPDFExc&& e) const
2135
144k
{
2136
144k
    if (!qpdf()) {
2137
561
        throw std::move(e);
2138
561
    }
2139
144k
    qpdf()->warn(std::move(e));
2140
144k
}
2141
2142
void
2143
BaseHandle::warn(std::string const& warning) const
2144
178k
{
2145
178k
    if (qpdf()) {
2146
115k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2147
115k
    } else {
2148
62.6k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2149
62.6k
    }
2150
178k
}
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
10.9k
    oh(oh)
2239
10.9k
{
2240
10.9k
}
2241
2242
QPDFObjectHandle::QPDFArrayItems::iterator&
2243
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2244
436k
{
2245
436k
    if (!m->is_end) {
2246
436k
        ++m->item_number;
2247
436k
        updateIValue();
2248
436k
    }
2249
436k
    return *this;
2250
436k
}
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
436k
{
2265
436k
    updateIValue();
2266
436k
    return ivalue;
2267
436k
}
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
447k
{
2279
447k
    return (m->item_number == other.m->item_number);
2280
447k
}
2281
2282
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2283
21.8k
    m(new Members(oh, for_begin))
2284
21.8k
{
2285
21.8k
    updateIValue();
2286
21.8k
}
2287
2288
void
2289
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2290
895k
{
2291
895k
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2292
895k
    if (m->is_end) {
2293
21.5k
        ivalue = QPDFObjectHandle();
2294
873k
    } else {
2295
873k
        ivalue = m->oh.getArrayItem(m->item_number);
2296
873k
    }
2297
895k
}
2298
2299
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2300
21.8k
    oh(oh)
2301
21.8k
{
2302
21.8k
    item_number = for_begin ? 0 : oh.getArrayNItems();
2303
21.8k
}
2304
2305
QPDFObjectHandle::QPDFArrayItems::iterator
2306
QPDFObjectHandle::QPDFArrayItems::begin()
2307
10.9k
{
2308
10.9k
    return {oh, true};
2309
10.9k
}
2310
2311
QPDFObjectHandle::QPDFArrayItems::iterator
2312
QPDFObjectHandle::QPDFArrayItems::end()
2313
10.9k
{
2314
10.9k
    return {oh, false};
2315
10.9k
}
2316
2317
QPDFObjGen
2318
QPDFObjectHandle::getObjGen() const
2319
878k
{
2320
878k
    return obj ? obj->getObjGen() : QPDFObjGen();
2321
878k
}
2322
2323
int
2324
QPDFObjectHandle::getObjectID() const
2325
684k
{
2326
684k
    return getObjGen().getObj();
2327
684k
}
2328
2329
int
2330
QPDFObjectHandle::getGeneration() const
2331
0
{
2332
0
    return getObjGen().getGen();
2333
0
}
2334
2335
bool
2336
QPDFObjectHandle::isIndirect() const
2337
676k
{
2338
676k
    return getObjectID() != 0;
2339
676k
}
2340
2341
// Indirect object accessors
2342
QPDF*
2343
QPDFObjectHandle::getOwningQPDF() const
2344
348k
{
2345
348k
    return obj ? obj->getQPDF() : nullptr;
2346
348k
}
2347
2348
QPDF&
2349
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2350
2.85k
{
2351
2.85k
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2352
2.85k
        return *result;
2353
2.85k
    }
2354
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2355
2.85k
}
2356
2357
void
2358
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2359
14
{
2360
14
    if (obj) {
2361
14
        obj->setParsedOffset(offset);
2362
14
    }
2363
14
}
2364
2365
QPDFObjectHandle
2366
operator""_qpdf(char const* v, size_t len)
2367
16.0k
{
2368
16.0k
    return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal");
2369
16.0k
}