Coverage Report

Created: 2025-07-11 07:03

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