Coverage Report

Created: 2026-07-30 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/qpdf/QPDFParser.hh
Line
Count
Source
1
#ifndef QPDFPARSER_HH
2
#define QPDFPARSER_HH
3
4
#include <qpdf/InputSource_private.hh>
5
#include <qpdf/QPDFObjectHandle_private.hh>
6
#include <qpdf/QPDFObject_private.hh>
7
#include <qpdf/QPDFTokenizer_private.hh>
8
#include <qpdf/global_private.hh>
9
10
#include <memory>
11
#include <string>
12
13
using namespace qpdf;
14
using namespace qpdf::global;
15
16
namespace qpdf::impl
17
{
18
    /// @class  Parser
19
    /// @brief  Internal parser for PDF objects and content streams.
20
    /// @par
21
    ///         The Parser class provides static methods for parsing PDF objects from input sources.
22
    ///         It handles tokenization, error recovery, and object construction with proper offset
23
    ///         tracking and description for error reporting.
24
    class Parser
25
    {
26
      public:
27
        /// @brief Exception thrown when parser encounters an unrecoverable error.
28
        class Error: public std::exception
29
        {
30
          public:
31
9.15k
            Error() = default;
32
            virtual ~Error() noexcept = default;
33
        };
34
35
        /// @brief Parse a PDF object from an input source.
36
        /// @param input The input source to read from.
37
        /// @param object_description Description of the object for error messages.
38
        /// @param context The QPDF context, or nullptr if parsing standalone.
39
        /// @return The parsed QPDFObjectHandle, or null if parsing fails.
40
        static QPDFObjectHandle
41
        parse(InputSource& input, std::string const& object_description, QPDF* context);
42
43
        /// @brief Parse a content stream from an input source.
44
        /// @param input The input source to read from.
45
        /// @param sp_description Shared pointer to object description.
46
        /// @param tokenizer The tokenizer to use for parsing.
47
        /// @param context The QPDF context.
48
        /// @return A pair whose first element is the parsed QPDFObjectHandle and whose
49
        ///         second element is a bool indicating EOF. The object handle will be
50
        ///         uninitialized on EOF or error; the bool is true if EOF was encountered
51
        ///         (no object parsed).
52
        static std::pair<QPDFObjectHandle, bool> parse_content(
53
            InputSource& input,
54
            std::shared_ptr<QPDFObject::Description> sp_description,
55
            qpdf::Tokenizer& tokenizer,
56
            QPDF* context);
57
58
        /// @brief Parse a PDF object (interface for deprecated QPDFObjectHandle::parse).
59
        /// @param input The input source to read from.
60
        /// @param object_description Description of the object for error messages.
61
        /// @param tokenizer The tokenizer to use for parsing.
62
        /// @param empty Output parameter indicating if object was empty.
63
        /// @param decrypter String decrypter for encrypted strings, or nullptr.
64
        /// @param context The QPDF context, or nullptr if parsing standalone.
65
        /// @return The parsed QPDFObjectHandle.
66
        static QPDFObjectHandle parse(
67
            InputSource& input,
68
            std::string const& object_description,
69
            QPDFTokenizer& tokenizer,
70
            bool& empty,
71
            QPDFObjectHandle::StringDecrypter* decrypter,
72
            QPDF* context);
73
74
        /// @brief Parse a PDF object for use by QPDF.
75
        /// @param input The input source to read from.
76
        /// @param object_description Description of the object for error messages.
77
        /// @param tokenizer The tokenizer to use for parsing.
78
        /// @param decrypter String decrypter for encrypted strings, or nullptr.
79
        /// @param context The QPDF context.
80
        /// @param sanity_checks Enable additional sanity checks during parsing.
81
        /// @return The parsed QPDFObjectHandle.
82
        static QPDFObjectHandle parse(
83
            InputSource& input,
84
            std::string const& object_description,
85
            qpdf::Tokenizer& tokenizer,
86
            QPDFObjectHandle::StringDecrypter* decrypter,
87
            QPDF& context,
88
            bool sanity_checks);
89
90
        /// @brief Parse an object from an object stream.
91
        /// @param input The offset buffer containing the object data.
92
        /// @param stream_id The object stream number.
93
        /// @param obj_id The object ID within the stream.
94
        /// @param tokenizer The tokenizer to use for parsing.
95
        /// @param context The QPDF context.
96
        /// @return The parsed QPDFObjectHandle.
97
        static QPDFObjectHandle parse(
98
            qpdf::is::OffsetBuffer& input,
99
            int stream_id,
100
            int obj_id,
101
            qpdf::Tokenizer& tokenizer,
102
            QPDF& context);
103
104
        /// @brief Create a description for a parsed object.
105
        /// @param input_name The name of the input source.
106
        /// @param object_description Description of the object being parsed.
107
        /// @return Shared pointer to object description with offset placeholder.
108
        static std::shared_ptr<QPDFObject::Description>
109
        make_description(std::string const& input_name, std::string const& object_description)
110
150k
        {
111
150k
            using namespace std::literals;
112
150k
            return std::make_shared<QPDFObject::Description>(
113
150k
                input_name + ", " + object_description + " at offset $PO");
114
150k
        }
115
116
      private:
117
        /// @brief Construct a parser instance.
118
        /// @param input The input source to read from.
119
        /// @param sp_description Shared pointer to object description.
120
        /// @param object_description Description string for error messages.
121
        /// @param tokenizer The tokenizer to use for parsing.
122
        /// @param decrypter String decrypter for encrypted content.
123
        /// @param context The QPDF context.
124
        /// @param parse_pdf Whether parsing PDF objects (vs content streams).
125
        /// @param stream_id Object stream ID for object stream parsing.
126
        /// @param obj_id Object ID within object stream.
127
        /// @param sanity_checks Enable additional sanity checks.
128
        Parser(
129
            InputSource& input,
130
            std::shared_ptr<QPDFObject::Description> sp_description,
131
            std::string const& object_description,
132
            qpdf::Tokenizer& tokenizer,
133
            QPDFObjectHandle::StringDecrypter* decrypter,
134
            QPDF* context,
135
            bool parse_pdf,
136
            int stream_id = 0,
137
            int obj_id = 0,
138
            bool sanity_checks = false) :
139
190k
            input_(input),
140
190k
            object_description_(object_description),
141
190k
            tokenizer_(tokenizer),
142
190k
            decrypter_(decrypter),
143
190k
            context_(context),
144
190k
            description_(std::move(sp_description)),
145
190k
            parse_pdf_(parse_pdf),
146
190k
            stream_id_(stream_id),
147
190k
            obj_id_(obj_id),
148
190k
            sanity_checks_(sanity_checks)
149
190k
        {
150
190k
        }
151
152
        /// @brief Parser state enumeration.
153
        /// @note state <= st_dictionary_value indicates we're in a dictionary context.
154
        enum parser_state_e { st_dictionary_key, st_dictionary_value, st_array };
155
156
        /// @brief Stack frame for tracking nested arrays and dictionaries.
157
        struct StackFrame
158
        {
159
            StackFrame(InputSource& input, parser_state_e state) :
160
459k
                state(state),
161
459k
                offset(input.tell())
162
459k
            {
163
459k
            }
164
165
            std::vector<QPDFObjectHandle> olist;          ///< Object list for arrays/dict values
166
            std::map<std::string, QPDFObjectHandle> dict; ///< Dictionary entries
167
            parser_state_e state;                         ///< Current parser state
168
            std::string key;                              ///< Current dictionary key
169
            qpdf_offset_t offset;                         ///< Offset of container start
170
            std::string contents_string;                  ///< For /Contents field in signatures
171
            qpdf_offset_t contents_offset{-1};            ///< Offset of /Contents value
172
            int null_count{0};                            ///< Count of null values in container
173
        };
174
175
        /// @brief Parse an object, handling exceptions and returning null on error.
176
        /// @param content_stream True if parsing a content stream.
177
        /// @return The parsed object handle, or null/uninitialized on error.
178
        QPDFObjectHandle parse(bool content_stream = false);
179
180
        /// @brief Parse the first token and dispatch to appropriate handler.
181
        /// @param content_stream True if parsing a content stream.
182
        /// @return The parsed object handle.
183
        QPDFObjectHandle parse_first(bool content_stream);
184
185
        /// @brief Parse the remainder of a composite object (array/dict/reference).
186
        /// @param content_stream True if parsing a content stream.
187
        /// @return The completed object handle.
188
        QPDFObjectHandle parse_remainder(bool content_stream);
189
190
        /// @brief Add an object to the current container.
191
        /// @param obj The object to add.
192
        void add(std::shared_ptr<QPDFObject>&& obj);
193
194
        /// @brief Add a null object to the current container.
195
        void add_null();
196
197
        /// @brief Add a null with a warning message.
198
        /// @param msg Warning message describing the error.
199
        void add_bad_null(std::string const& msg);
200
201
        /// @brief Add a buffered integer from int_buffer_.
202
        /// @param count Buffer index (1 or 2) to read from.
203
        void add_int(int count);
204
205
        /// @brief Create and add a scalar object to the current container.
206
        /// @tparam T The scalar object type (e.g., QPDF_Integer, QPDF_String).
207
        /// @tparam Args Constructor argument types.
208
        /// @param args Arguments to forward to the object constructor.
209
        template <typename T, typename... Args>
210
        void add_scalar(Args&&... args);
211
212
        /// @brief Check if too many bad tokens have been encountered and throw if so.
213
        void check_too_many_bad_tokens();
214
215
        /// @brief Issue a warning about a duplicate dictionary key.
216
        void warn_duplicate_key();
217
218
        /// @brief Fix dictionaries with missing keys by generating fake keys.
219
        void fix_missing_keys();
220
221
        /// @brief Report a limits error and throw.
222
        /// @param limit The limit identifier.
223
        /// @param msg Error message.
224
        [[noreturn]] void limits_error(std::string const& limit, std::string const& msg);
225
226
        /// @brief Issue a warning at a specific offset.
227
        /// @param offset File offset for the warning.
228
        /// @param msg Warning message.
229
        void warn(qpdf_offset_t offset, std::string const& msg) const;
230
231
        /// @brief Issue a warning at the current offset.
232
        /// @param msg Warning message.
233
        void warn(std::string const& msg) const;
234
235
        /// @brief Issue a warning from a QPDFExc exception.
236
        /// @param e The exception to report.
237
        void warn(QPDFExc const& e) const;
238
239
        /// @brief Create a scalar object with description and parsed offset.
240
        /// @tparam T The scalar object type.
241
        /// @tparam Args Constructor argument types.
242
        /// @param args Arguments to forward to the object constructor.
243
        /// @return Object handle with description and offset set.
244
        /// @note The offset includes any leading whitespace.
245
        template <typename T, typename... Args>
246
        QPDFObjectHandle with_description(Args&&... args);
247
248
        /// @brief Set the description and offset on an existing object.
249
        /// @param obj The object to update.
250
        /// @param parsed_offset The file offset where the object was parsed.
251
        void set_description(std::shared_ptr<QPDFObject>& obj, qpdf_offset_t parsed_offset);
252
253
        // Core parsing state
254
        InputSource& input_;                           ///< Input source to read from
255
        std::string const& object_description_;        ///< Description for error messages
256
        qpdf::Tokenizer& tokenizer_;                   ///< Tokenizer for lexical analysis
257
        QPDFObjectHandle::StringDecrypter* decrypter_; ///< Decrypter for encrypted strings
258
        QPDF* context_;                                ///< QPDF context for object resolution
259
        std::shared_ptr<QPDFObject::Description> description_; ///< Shared description for objects
260
        bool parse_pdf_{false};     ///< True if parsing PDF objects vs content streams
261
        int stream_id_{0};          ///< Object stream ID (for object stream parsing)
262
        int obj_id_{0};             ///< Object ID within object stream
263
        bool sanity_checks_{false}; ///< Enable additional validation checks
264
265
        // Composite object parsing state
266
        std::vector<StackFrame> stack_; ///< Stack of nested containers
267
        StackFrame* frame_{nullptr};    ///< Current stack frame pointer
268
269
        // Error tracking state
270
        /// Number of recent bad tokens. Always > 0 after first bad token encountered.
271
        int bad_count_{0};
272
        /// Number of bad tokens remaining before giving up.
273
        uint32_t max_bad_count_{Limits::parser_max_errors()};
274
        /// Number of good tokens since last bad token. Irrelevant if bad_count == 0.
275
        int good_count_{0};
276
277
        // Token buffering state
278
        /// Start offset of current object, including any leading whitespace.
279
        qpdf_offset_t start_{0};
280
        /// Number of successive integer tokens (for indirect reference detection).
281
        int int_count_{0};
282
        /// Buffer for up to 2 integer tokens.
283
        long long int_buffer_[2]{0, 0};
284
        /// Offsets corresponding to buffered integers.
285
        qpdf_offset_t last_offset_buffer_[2]{0, 0};
286
287
        /// True if object was empty (endobj without content).
288
        bool empty_{false};
289
    };
290
} // namespace qpdf::impl
291
292
#endif // QPDFPARSER_HH