Coverage Report

Created: 2024-09-08 06:05

/src/qpdf/libqpdf/QPDFParser.cc
Line
Count
Source (jump to first uncovered line)
1
#include <qpdf/QPDFParser.hh>
2
3
#include <qpdf/QPDF.hh>
4
#include <qpdf/QPDFObjGen.hh>
5
#include <qpdf/QPDFObjectHandle.hh>
6
#include <qpdf/QPDFObject_private.hh>
7
#include <qpdf/QPDF_Array.hh>
8
#include <qpdf/QPDF_Bool.hh>
9
#include <qpdf/QPDF_Dictionary.hh>
10
#include <qpdf/QPDF_InlineImage.hh>
11
#include <qpdf/QPDF_Integer.hh>
12
#include <qpdf/QPDF_Name.hh>
13
#include <qpdf/QPDF_Null.hh>
14
#include <qpdf/QPDF_Operator.hh>
15
#include <qpdf/QPDF_Real.hh>
16
#include <qpdf/QPDF_Reserved.hh>
17
#include <qpdf/QPDF_Stream.hh>
18
#include <qpdf/QPDF_String.hh>
19
#include <qpdf/QTC.hh>
20
#include <qpdf/QUtil.hh>
21
22
#include <memory>
23
24
using ObjectPtr = std::shared_ptr<QPDFObject>;
25
26
QPDFObjectHandle
27
QPDFParser::parse(bool& empty, bool content_stream)
28
172k
{
29
    // This method must take care not to resolve any objects. Don't check the type of any object
30
    // without first ensuring that it is a direct object. Otherwise, doing so may have the side
31
    // effect of reading the object and changing the file pointer. If you do this, it will cause a
32
    // logic error to be thrown from QPDF::inParse().
33
34
172k
    QPDF::ParseGuard pg(context);
35
172k
    empty = false;
36
172k
    start = input->tell();
37
38
172k
    if (!tokenizer.nextToken(*input, object_description)) {
39
1.20k
        warn(tokenizer.getErrorMessage());
40
1.20k
    }
41
42
172k
    switch (tokenizer.getType()) {
43
1.25k
    case QPDFTokenizer::tt_eof:
44
1.25k
        if (content_stream) {
45
            // In content stream mode, leave object uninitialized to indicate EOF
46
0
            return {};
47
0
        }
48
1.25k
        QTC::TC("qpdf", "QPDFParser eof in parse");
49
1.25k
        warn("unexpected EOF");
50
1.25k
        return {QPDF_Null::create()};
51
52
1.20k
    case QPDFTokenizer::tt_bad:
53
1.20k
        QTC::TC("qpdf", "QPDFParser bad token in parse");
54
1.20k
        return {QPDF_Null::create()};
55
56
6
    case QPDFTokenizer::tt_brace_open:
57
9
    case QPDFTokenizer::tt_brace_close:
58
9
        QTC::TC("qpdf", "QPDFParser bad brace");
59
9
        warn("treating unexpected brace token as null");
60
9
        return {QPDF_Null::create()};
61
62
92
    case QPDFTokenizer::tt_array_close:
63
92
        QTC::TC("qpdf", "QPDFParser bad array close");
64
92
        warn("treating unexpected array close token as null");
65
92
        return {QPDF_Null::create()};
66
67
232
    case QPDFTokenizer::tt_dict_close:
68
232
        QTC::TC("qpdf", "QPDFParser bad dictionary close");
69
232
        warn("unexpected dictionary close token");
70
232
        return {QPDF_Null::create()};
71
72
2.19k
    case QPDFTokenizer::tt_array_open:
73
122k
    case QPDFTokenizer::tt_dict_open:
74
122k
        stack.clear();
75
122k
        stack.emplace_back(
76
122k
            input,
77
122k
            (tokenizer.getType() == QPDFTokenizer::tt_array_open) ? st_array : st_dictionary_key);
78
122k
        frame = &stack.back();
79
122k
        return parseRemainder(content_stream);
80
81
3
    case QPDFTokenizer::tt_bool:
82
3
        return withDescription<QPDF_Bool>(tokenizer.getValue() == "true");
83
84
259
    case QPDFTokenizer::tt_null:
85
259
        return {QPDF_Null::create()};
86
87
38.6k
    case QPDFTokenizer::tt_integer:
88
38.6k
        return withDescription<QPDF_Integer>(QUtil::string_to_ll(tokenizer.getValue().c_str()));
89
90
258
    case QPDFTokenizer::tt_real:
91
258
        return withDescription<QPDF_Real>(tokenizer.getValue());
92
93
3.23k
    case QPDFTokenizer::tt_name:
94
3.23k
        return withDescription<QPDF_Name>(tokenizer.getValue());
95
96
4.50k
    case QPDFTokenizer::tt_word:
97
4.50k
        {
98
4.50k
            auto const& value = tokenizer.getValue();
99
4.50k
            if (content_stream) {
100
0
                return withDescription<QPDF_Operator>(value);
101
4.50k
            } else if (value == "endobj") {
102
                // We just saw endobj without having read anything.  Treat this as a null and do
103
                // not move the input source's offset.
104
71
                input->seek(input->getLastOffset(), SEEK_SET);
105
71
                empty = true;
106
71
                return {QPDF_Null::create()};
107
4.43k
            } else {
108
4.43k
                QTC::TC("qpdf", "QPDFParser treat word as string");
109
4.43k
                warn("unknown token while reading object; treating as string");
110
4.43k
                return withDescription<QPDF_String>(value);
111
4.43k
            }
112
4.50k
        }
113
114
181
    case QPDFTokenizer::tt_string:
115
181
        if (decrypter) {
116
0
            std::string s{tokenizer.getValue()};
117
0
            decrypter->decryptString(s);
118
0
            return withDescription<QPDF_String>(s);
119
181
        } else {
120
181
            return withDescription<QPDF_String>(tokenizer.getValue());
121
181
        }
122
123
0
    default:
124
0
        warn("treating unknown token type as null while reading object");
125
0
        return {QPDF_Null::create()};
126
172k
    }
127
172k
}
128
129
QPDFObjectHandle
130
QPDFParser::parseRemainder(bool content_stream)
131
122k
{
132
    // This method must take care not to resolve any objects. Don't check the type of any object
133
    // without first ensuring that it is a direct object. Otherwise, doing so may have the side
134
    // effect of reading the object and changing the file pointer. If you do this, it will cause a
135
    // logic error to be thrown from QPDF::inParse().
136
137
122k
    bad_count = 0;
138
122k
    bool b_contents = false;
139
140
4.45M
    while (true) {
141
4.45M
        if (!tokenizer.nextToken(*input, object_description)) {
142
11.3k
            warn(tokenizer.getErrorMessage());
143
11.3k
        }
144
4.45M
        ++good_count; // optimistically
145
146
4.45M
        if (int_count != 0) {
147
            // Special handling of indirect references. Treat integer tokens as part of an indirect
148
            // reference until proven otherwise.
149
1.23M
            if (tokenizer.getType() == QPDFTokenizer::tt_integer) {
150
622k
                if (++int_count > 2) {
151
                    // Process the oldest buffered integer.
152
159k
                    addInt(int_count);
153
159k
                }
154
622k
                last_offset_buffer[int_count % 2] = input->getLastOffset();
155
622k
                int_buffer[int_count % 2] = QUtil::string_to_ll(tokenizer.getValue().c_str());
156
622k
                continue;
157
158
622k
            } else if (
159
614k
                int_count >= 2 && tokenizer.getType() == QPDFTokenizer::tt_word &&
160
614k
                tokenizer.getValue() == "R") {
161
383k
                if (context == nullptr) {
162
0
                    QTC::TC("qpdf", "QPDFParser indirect without context");
163
0
                    throw std::logic_error("QPDFParser::parse called without context on an object "
164
0
                                           "with indirect references");
165
0
                }
166
383k
                auto id = QIntC::to_int(int_buffer[(int_count - 1) % 2]);
167
383k
                auto gen = QIntC::to_int(int_buffer[(int_count) % 2]);
168
383k
                if (!(id < 1 || gen < 0 || gen >= 65535)) {
169
381k
                    add(QPDF::ParseGuard::getObject(context, id, gen, parse_pdf));
170
381k
                } else {
171
2.08k
                    QTC::TC("qpdf", "QPDFParser invalid objgen");
172
2.08k
                    addNull();
173
2.08k
                }
174
383k
                int_count = 0;
175
383k
                continue;
176
177
383k
            } else if (int_count > 0) {
178
                // Process the buffered integers before processing the current token.
179
230k
                if (int_count > 1) {
180
78.4k
                    addInt(int_count - 1);
181
78.4k
                }
182
230k
                addInt(int_count);
183
230k
                int_count = 0;
184
230k
            }
185
1.23M
        }
186
187
3.44M
        switch (tokenizer.getType()) {
188
1.36k
        case QPDFTokenizer::tt_eof:
189
1.36k
            warn("parse error while reading object");
190
1.36k
            if (content_stream) {
191
                // In content stream mode, leave object uninitialized to indicate EOF
192
0
                return {};
193
0
            }
194
1.36k
            QTC::TC("qpdf", "QPDFParser eof in parseRemainder");
195
1.36k
            warn("unexpected EOF");
196
1.36k
            return {QPDF_Null::create()};
197
198
10.9k
        case QPDFTokenizer::tt_bad:
199
10.9k
            QTC::TC("qpdf", "QPDFParser bad token in parseRemainder");
200
10.9k
            if (tooManyBadTokens()) {
201
674
                return {QPDF_Null::create()};
202
674
            }
203
10.2k
            addNull();
204
10.2k
            continue;
205
206
710
        case QPDFTokenizer::tt_brace_open:
207
1.08k
        case QPDFTokenizer::tt_brace_close:
208
1.08k
            QTC::TC("qpdf", "QPDFParser bad brace in parseRemainder");
209
1.08k
            warn("treating unexpected brace token as null");
210
1.08k
            if (tooManyBadTokens()) {
211
124
                return {QPDF_Null::create()};
212
124
            }
213
956
            addNull();
214
956
            continue;
215
216
109k
        case QPDFTokenizer::tt_array_close:
217
109k
            if (frame->state == st_array) {
218
106k
                auto object = QPDF_Array::create(std::move(frame->olist), frame->null_count > 100);
219
106k
                setDescription(object, frame->offset - 1);
220
                // The `offset` points to the next of "[".  Set the rewind offset to point to the
221
                // beginning of "[". This has been explicitly tested with whitespace surrounding the
222
                // array start delimiter. getLastOffset points to the array end token and therefore
223
                // can't be used here.
224
106k
                if (stack.size() <= 1) {
225
1.88k
                    return object;
226
1.88k
                }
227
104k
                stack.pop_back();
228
104k
                frame = &stack.back();
229
104k
                add(std::move(object));
230
104k
            } else {
231
3.20k
                QTC::TC("qpdf", "QPDFParser bad array close in parseRemainder");
232
3.20k
                warn("treating unexpected array close token as null");
233
3.20k
                if (tooManyBadTokens()) {
234
164
                    return {QPDF_Null::create()};
235
164
                }
236
3.03k
                addNull();
237
3.03k
            }
238
107k
            continue;
239
240
214k
        case QPDFTokenizer::tt_dict_close:
241
214k
            if (frame->state <= st_dictionary_value) {
242
                // Attempt to recover more or less gracefully from invalid dictionaries.
243
211k
                auto& dict = frame->dict;
244
245
211k
                if (frame->state == st_dictionary_value) {
246
1.20k
                    QTC::TC("qpdf", "QPDFParser no val for last key");
247
1.20k
                    warn(
248
1.20k
                        frame->offset,
249
1.20k
                        "dictionary ended prematurely; using null as value for last key");
250
1.20k
                    dict[frame->key] = QPDF_Null::create();
251
1.20k
                }
252
253
211k
                if (!frame->olist.empty()) {
254
12.3k
                    fixMissingKeys();
255
12.3k
                }
256
257
211k
                if (!frame->contents_string.empty() && dict.count("/Type") &&
258
211k
                    dict["/Type"].isNameAndEquals("/Sig") && dict.count("/ByteRange") &&
259
211k
                    dict.count("/Contents") && dict["/Contents"].isString()) {
260
0
                    dict["/Contents"] = QPDFObjectHandle::newString(frame->contents_string);
261
0
                    dict["/Contents"].setParsedOffset(frame->contents_offset);
262
0
                }
263
211k
                auto object = QPDF_Dictionary::create(std::move(dict));
264
211k
                setDescription(object, frame->offset - 2);
265
                // The `offset` points to the next of "<<". Set the rewind offset to point to the
266
                // beginning of "<<". This has been explicitly tested with whitespace surrounding
267
                // the dictionary start delimiter. getLastOffset points to the dictionary end token
268
                // and therefore can't be used here.
269
211k
                if (stack.size() <= 1) {
270
113k
                    return object;
271
113k
                }
272
98.3k
                stack.pop_back();
273
98.3k
                frame = &stack.back();
274
98.3k
                add(std::move(object));
275
98.3k
            } else {
276
2.30k
                QTC::TC("qpdf", "QPDFParser bad dictionary close in parseRemainder");
277
2.30k
                warn("unexpected dictionary close token");
278
2.30k
                if (tooManyBadTokens()) {
279
44
                    return {QPDF_Null::create()};
280
44
                }
281
2.26k
                addNull();
282
2.26k
            }
283
100k
            continue;
284
285
131k
        case QPDFTokenizer::tt_array_open:
286
243k
        case QPDFTokenizer::tt_dict_open:
287
243k
            if (stack.size() > 499) {
288
46
                QTC::TC("qpdf", "QPDFParser too deep");
289
46
                warn("ignoring excessively deeply nested data structure");
290
46
                return {QPDF_Null::create()};
291
243k
            } else {
292
243k
                b_contents = false;
293
243k
                stack.emplace_back(
294
243k
                    input,
295
243k
                    (tokenizer.getType() == QPDFTokenizer::tt_array_open) ? st_array
296
243k
                                                                          : st_dictionary_key);
297
243k
                frame = &stack.back();
298
243k
                continue;
299
243k
            }
300
301
2.10k
        case QPDFTokenizer::tt_bool:
302
2.10k
            addScalar<QPDF_Bool>(tokenizer.getValue() == "true");
303
2.10k
            continue;
304
305
344k
        case QPDFTokenizer::tt_null:
306
344k
            addNull();
307
344k
            continue;
308
309
614k
        case QPDFTokenizer::tt_integer:
310
614k
            if (!content_stream) {
311
                // Buffer token in case it is part of an indirect reference.
312
614k
                last_offset_buffer[1] = input->getLastOffset();
313
614k
                int_buffer[1] = QUtil::string_to_ll(tokenizer.getValue().c_str());
314
614k
                int_count = 1;
315
614k
            } else {
316
0
                addScalar<QPDF_Integer>(QUtil::string_to_ll(tokenizer.getValue().c_str()));
317
0
            }
318
614k
            continue;
319
320
31.4k
        case QPDFTokenizer::tt_real:
321
31.4k
            addScalar<QPDF_Real>(tokenizer.getValue());
322
31.4k
            continue;
323
324
1.75M
        case QPDFTokenizer::tt_name:
325
1.75M
            if (frame->state == st_dictionary_key) {
326
788k
                frame->key = tokenizer.getValue();
327
788k
                frame->state = st_dictionary_value;
328
788k
                b_contents = decrypter && frame->key == "/Contents";
329
788k
                continue;
330
967k
            } else {
331
967k
                addScalar<QPDF_Name>(tokenizer.getValue());
332
967k
            }
333
967k
            continue;
334
335
967k
        case QPDFTokenizer::tt_word:
336
84.6k
            if (content_stream) {
337
0
                addScalar<QPDF_Operator>(tokenizer.getValue());
338
84.6k
            } else {
339
84.6k
                QTC::TC("qpdf", "QPDFParser treat word as string in parseRemainder");
340
84.6k
                warn("unknown token while reading object; treating as string");
341
84.6k
                if (tooManyBadTokens()) {
342
4.28k
                    return {QPDF_Null::create()};
343
4.28k
                }
344
80.3k
                addScalar<QPDF_String>(tokenizer.getValue());
345
80.3k
            }
346
80.3k
            continue;
347
348
80.3k
        case QPDFTokenizer::tt_string:
349
35.6k
            {
350
35.6k
                auto const& val = tokenizer.getValue();
351
35.6k
                if (decrypter) {
352
0
                    if (b_contents) {
353
0
                        frame->contents_string = val;
354
0
                        frame->contents_offset = input->getLastOffset();
355
0
                        b_contents = false;
356
0
                    }
357
0
                    std::string s{val};
358
0
                    decrypter->decryptString(s);
359
0
                    addScalar<QPDF_String>(s);
360
35.6k
                } else {
361
35.6k
                    addScalar<QPDF_String>(val);
362
35.6k
                }
363
35.6k
            }
364
35.6k
            continue;
365
366
0
        default:
367
0
            warn("treating unknown token type as null while reading object");
368
0
            if (tooManyBadTokens()) {
369
0
                return {QPDF_Null::create()};
370
0
            }
371
0
            addNull();
372
3.44M
        }
373
3.44M
    }
374
122k
}
375
376
void
377
QPDFParser::add(std::shared_ptr<QPDFObject>&& obj)
378
2.17M
{
379
2.17M
    if (frame->state != st_dictionary_value) {
380
        // If state is st_dictionary_key then there is a missing key. Push onto olist for
381
        // processing once the tt_dict_close token has been found.
382
1.39M
        frame->olist.emplace_back(std::move(obj));
383
1.39M
    } else {
384
778k
        if (auto res = frame->dict.insert_or_assign(frame->key, std::move(obj)); !res.second) {
385
3.65k
            warnDuplicateKey();
386
3.65k
        }
387
778k
        frame->state = st_dictionary_key;
388
778k
    }
389
2.17M
}
390
391
void
392
QPDFParser::addNull()
393
363k
{
394
363k
    const static ObjectPtr null_obj = QPDF_Null::create();
395
396
363k
    if (frame->state != st_dictionary_value) {
397
        // If state is st_dictionary_key then there is a missing key. Push onto olist for
398
        // processing once the tt_dict_close token has been found.
399
359k
        frame->olist.emplace_back(null_obj);
400
359k
    } else {
401
3.63k
        if (auto res = frame->dict.insert_or_assign(frame->key, null_obj); !res.second) {
402
49
            warnDuplicateKey();
403
49
        }
404
3.63k
        frame->state = st_dictionary_key;
405
3.63k
    }
406
363k
    ++frame->null_count;
407
363k
}
408
409
void
410
QPDFParser::addInt(int count)
411
469k
{
412
469k
    auto obj = QPDF_Integer::create(int_buffer[count % 2]);
413
469k
    obj->setDescription(context, description, last_offset_buffer[count % 2]);
414
469k
    add(std::move(obj));
415
469k
}
416
417
template <typename T, typename... Args>
418
void
419
QPDFParser::addScalar(Args&&... args)
420
1.11M
{
421
1.11M
    auto obj = T::create(args...);
422
1.11M
    obj->setDescription(context, description, input->getLastOffset());
423
1.11M
    add(std::move(obj));
424
1.11M
}
void QPDFParser::addScalar<QPDF_Bool, bool>(bool&&)
Line
Count
Source
420
2.10k
{
421
2.10k
    auto obj = T::create(args...);
422
2.10k
    obj->setDescription(context, description, input->getLastOffset());
423
2.10k
    add(std::move(obj));
424
2.10k
}
Unexecuted instantiation: void QPDFParser::addScalar<QPDF_Integer, long long>(long long&&)
void QPDFParser::addScalar<QPDF_Real, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Line
Count
Source
420
31.4k
{
421
31.4k
    auto obj = T::create(args...);
422
31.4k
    obj->setDescription(context, description, input->getLastOffset());
423
31.4k
    add(std::move(obj));
424
31.4k
}
void QPDFParser::addScalar<QPDF_Name, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Line
Count
Source
420
967k
{
421
967k
    auto obj = T::create(args...);
422
967k
    obj->setDescription(context, description, input->getLastOffset());
423
967k
    add(std::move(obj));
424
967k
}
Unexecuted instantiation: void QPDFParser::addScalar<QPDF_Operator, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
void QPDFParser::addScalar<QPDF_String, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Line
Count
Source
420
115k
{
421
115k
    auto obj = T::create(args...);
422
115k
    obj->setDescription(context, description, input->getLastOffset());
423
115k
    add(std::move(obj));
424
115k
}
Unexecuted instantiation: void QPDFParser::addScalar<QPDF_String, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)
425
426
template <typename T, typename... Args>
427
QPDFObjectHandle
428
QPDFParser::withDescription(Args&&... args)
429
46.7k
{
430
46.7k
    auto obj = T::create(args...);
431
46.7k
    obj->setDescription(context, description, start);
432
46.7k
    return {obj};
433
46.7k
}
QPDFObjectHandle QPDFParser::withDescription<QPDF_Bool, bool>(bool&&)
Line
Count
Source
429
3
{
430
3
    auto obj = T::create(args...);
431
3
    obj->setDescription(context, description, start);
432
3
    return {obj};
433
3
}
QPDFObjectHandle QPDFParser::withDescription<QPDF_Integer, long long>(long long&&)
Line
Count
Source
429
38.6k
{
430
38.6k
    auto obj = T::create(args...);
431
38.6k
    obj->setDescription(context, description, start);
432
38.6k
    return {obj};
433
38.6k
}
QPDFObjectHandle QPDFParser::withDescription<QPDF_Real, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Line
Count
Source
429
258
{
430
258
    auto obj = T::create(args...);
431
258
    obj->setDescription(context, description, start);
432
258
    return {obj};
433
258
}
QPDFObjectHandle QPDFParser::withDescription<QPDF_Name, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Line
Count
Source
429
3.23k
{
430
3.23k
    auto obj = T::create(args...);
431
3.23k
    obj->setDescription(context, description, start);
432
3.23k
    return {obj};
433
3.23k
}
Unexecuted instantiation: QPDFObjectHandle QPDFParser::withDescription<QPDF_Operator, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
QPDFObjectHandle QPDFParser::withDescription<QPDF_String, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Line
Count
Source
429
4.60k
{
430
4.60k
    auto obj = T::create(args...);
431
4.60k
    obj->setDescription(context, description, start);
432
4.60k
    return {obj};
433
4.60k
}
Unexecuted instantiation: QPDFObjectHandle QPDFParser::withDescription<QPDF_String, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)
434
435
void
436
QPDFParser::setDescription(ObjectPtr& obj, qpdf_offset_t parsed_offset)
437
318k
{
438
318k
    if (obj) {
439
318k
        obj->setDescription(context, description, parsed_offset);
440
318k
    }
441
318k
}
442
443
void
444
QPDFParser::fixMissingKeys()
445
12.3k
{
446
12.3k
    std::set<std::string> names;
447
69.0k
    for (auto& obj: frame->olist) {
448
69.0k
        if (obj->getTypeCode() == ::ot_name) {
449
9
            names.insert(obj->getStringValue());
450
9
        }
451
69.0k
    }
452
12.3k
    int next_fake_key = 1;
453
50.0k
    for (auto const& item: frame->olist) {
454
50.0k
        while (true) {
455
50.0k
            const std::string key = "/QPDFFake" + std::to_string(next_fake_key++);
456
50.0k
            const bool found_fake = frame->dict.count(key) == 0 && names.count(key) == 0;
457
50.0k
            QTC::TC("qpdf", "QPDFParser found fake", (found_fake ? 0 : 1));
458
50.0k
            if (found_fake) {
459
50.0k
                warn(
460
50.0k
                    frame->offset,
461
50.0k
                    "expected dictionary key but found non-name object; inserting key " + key);
462
50.0k
                frame->dict[key] = item;
463
50.0k
                break;
464
50.0k
            }
465
50.0k
        }
466
50.0k
    }
467
12.3k
}
468
469
bool
470
QPDFParser::tooManyBadTokens()
471
101k
{
472
101k
    if (good_count <= 4) {
473
63.8k
        if (++bad_count > 5) {
474
5.29k
            warn("too many errors; giving up on reading object");
475
5.29k
            return true;
476
5.29k
        }
477
63.8k
    } else {
478
37.9k
        bad_count = 1;
479
37.9k
    }
480
96.5k
    good_count = 0;
481
96.5k
    return false;
482
101k
}
483
484
void
485
QPDFParser::warn(QPDFExc const& e) const
486
172k
{
487
    // If parsing on behalf of a QPDF object and want to give a warning, we can warn through the
488
    // object. If parsing for some other reason, such as an explicit creation of an object from a
489
    // string, then just throw the exception.
490
172k
    if (context) {
491
172k
        context->warn(e);
492
172k
    } else {
493
0
        throw e;
494
0
    }
495
172k
}
496
497
void
498
QPDFParser::warnDuplicateKey()
499
3.70k
{
500
3.70k
    QTC::TC("qpdf", "QPDFParser duplicate dict key");
501
3.70k
    warn(
502
3.70k
        frame->offset,
503
3.70k
        "dictionary has duplicated key " + frame->key + "; last occurrence overrides earlier ones");
504
3.70k
}
505
506
void
507
QPDFParser::warn(qpdf_offset_t offset, std::string const& msg) const
508
172k
{
509
172k
    warn(QPDFExc(qpdf_e_damaged_pdf, input->getName(), object_description, offset, msg));
510
172k
}
511
512
void
513
QPDFParser::warn(std::string const& msg) const
514
117k
{
515
117k
    warn(input->getLastOffset(), msg);
516
117k
}