Commit e848395d3778197893a10e33b1b7e78710d8c8b7

Authored by m-holger
Committed by GitHub
2 parents 04b18627 28441124

Merge pull request #1589 from m-holger/global

Introduce `global` namespace for managing qpdf global options and lim…
include/qpdf/Constants.h
@@ -244,4 +244,50 @@ enum qpdf_page_label_e { @@ -244,4 +244,50 @@ enum qpdf_page_label_e {
244 pl_roman_upper, 244 pl_roman_upper,
245 }; 245 };
246 246
  247 +/**
  248 + * @enum qpdf_result_e
  249 + * @brief Enum representing result codes for qpdf C-API functions.
  250 + *
  251 + * Results <= qpdf_r_no_warn indicate success without warnings,
  252 + * qpdf_r_no_warn < result <= qpdf_r_success indicates success with warnings, and
  253 + * qpdf_r_success < result indicates failure.
  254 + */
  255 +enum qpdf_result_e {
  256 + /* success */
  257 + qpdf_r_ok = 0,
  258 + qpdf_r_no_warn = 0xff, /// any result <= qpdf_no_warn indicates success without warning
  259 + qpdf_r_success = 0xffff, /// any result <= qpdf_r_success indicates success
  260 + /* failure */
  261 + qpdf_r_bad_parameter = 0x10000,
  262 +
  263 + qpdf_r_no_warn_mask = 0x7fffff00,
  264 + qpdf_r_success_mask = 0x7fff0000,
  265 +};
  266 +
  267 +/**
  268 + * @enum qpdf_param_e
  269 + * @brief This enumeration defines various parameters and configuration options for qpdf C-API
  270 + * functions.
  271 + *
  272 + * The enum values are grouped into sections based on their functionality, such as global
  273 + * options or global limits. For the meaning of individual parameters see `qpdf/global.cc`
  274 + */
  275 +enum qpdf_param_e {
  276 + /* global state */
  277 + qpdf_p_limit_errors = 0x10020,
  278 +
  279 + /* global options */
  280 + qpdf_p_default_limits = 0x11100,
  281 + /* global limits */
  282 +
  283 + /* object - parser limits */
  284 + qpdf_p_parser_max_nesting = 0x13000,
  285 + qpdf_p_parser_max_errors,
  286 + qpdf_p_parser_max_container_size,
  287 + qpdf_p_parser_max_container_size_damaged,
  288 +
  289 + /* next section = 0x20000 */
  290 + qpdf_enum_max = 0x7fffffff,
  291 +};
  292 +
247 #endif /* QPDFCONSTANTS_H */ 293 #endif /* QPDFCONSTANTS_H */
include/qpdf/QPDFJob.hh
@@ -306,6 +306,24 @@ class QPDFJob @@ -306,6 +306,24 @@ class QPDFJob
306 Config* config; 306 Config* config;
307 }; 307 };
308 308
  309 + class GlobalConfig
  310 + {
  311 + friend class QPDFJob;
  312 + friend class Config;
  313 +
  314 + public:
  315 + QPDF_DLL
  316 + Config* endGlobal();
  317 +
  318 +#include <qpdf/auto_job_c_global.hh>
  319 +
  320 + GlobalConfig(Config*); // for qpdf internal use only
  321 + GlobalConfig(GlobalConfig const&) = delete;
  322 +
  323 + private:
  324 + Config* config;
  325 + };
  326 +
309 class Config 327 class Config
310 { 328 {
311 friend class QPDFJob; 329 friend class QPDFJob;
@@ -331,6 +349,8 @@ class QPDFJob @@ -331,6 +349,8 @@ class QPDFJob
331 QPDF_DLL 349 QPDF_DLL
332 std::shared_ptr<AttConfig> addAttachment(); 350 std::shared_ptr<AttConfig> addAttachment();
333 QPDF_DLL 351 QPDF_DLL
  352 + std::shared_ptr<GlobalConfig> global();
  353 + QPDF_DLL
334 std::shared_ptr<PagesConfig> pages(); 354 std::shared_ptr<PagesConfig> pages();
335 QPDF_DLL 355 QPDF_DLL
336 std::shared_ptr<UOConfig> overlay(); 356 std::shared_ptr<UOConfig> overlay();
include/qpdf/QUtil.hh
@@ -20,8 +20,10 @@ @@ -20,8 +20,10 @@
20 #ifndef QUTIL_HH 20 #ifndef QUTIL_HH
21 #define QUTIL_HH 21 #define QUTIL_HH
22 22
  23 +#include <qpdf/Constants.h>
23 #include <qpdf/DLL.h> 24 #include <qpdf/DLL.h>
24 #include <qpdf/Types.h> 25 #include <qpdf/Types.h>
  26 +
25 #include <cstdio> 27 #include <cstdio>
26 #include <cstring> 28 #include <cstring>
27 #include <ctime> 29 #include <ctime>
@@ -441,6 +443,25 @@ namespace QUtil @@ -441,6 +443,25 @@ namespace QUtil
441 QPDF_DLL 443 QPDF_DLL
442 bool is_number(char const*); 444 bool is_number(char const*);
443 445
  446 + /// @brief Handles the result code from qpdf functions.
  447 + ///
  448 + /// **For qpdf internal use only - not part of the public API**
  449 + /// @par
  450 + /// Depending on the result code, either continues execution or throws an
  451 + /// exception in case of an invalid parameter.
  452 + ///
  453 + /// @param result The result code of type qpdf_result_e, indicating success or failure status.
  454 + /// @param context A string describing the context where this function is invoked, used for
  455 + /// error reporting if an exception is thrown.
  456 + ///
  457 + /// @throws std::logic_error If the result code is `qpdf_bad_parameter`, indicating an invalid
  458 + /// parameter was supplied to a function. The exception message will
  459 + /// include the provided context for easier debugging.
  460 + ///
  461 + /// @since 12.3
  462 + QPDF_DLL
  463 + void handle_result_code(qpdf_result_e result, std::string_view context);
  464 +
444 // This method parses the numeric range syntax used by the qpdf command-line tool. May throw 465 // This method parses the numeric range syntax used by the qpdf command-line tool. May throw
445 // std::runtime_error. A numeric range is as comma-separated list of groups. A group may be a 466 // std::runtime_error. A numeric range is as comma-separated list of groups. A group may be a
446 // number specification or a range of number specifications separated by a dash. A number 467 // number specification or a range of number specifications separated by a dash. A number
include/qpdf/auto_job_c_global.hh 0 → 100644
  1 +//
  2 +// This file is automatically generated by generate_auto_job.
  3 +// Edits will be automatically overwritten if the build is
  4 +// run in maintainer mode.
  5 +//
  6 +// clang-format off
  7 +//
  8 +QPDF_DLL GlobalConfig* noDefaultLimits();
  9 +QPDF_DLL GlobalConfig* parserMaxContainerSize(std::string const& parameter);
  10 +QPDF_DLL GlobalConfig* parserMaxContainerSizeDamaged(std::string const& parameter);
  11 +QPDF_DLL GlobalConfig* parserMaxErrors(std::string const& parameter);
  12 +QPDF_DLL GlobalConfig* parserMaxNesting(std::string const& parameter);
include/qpdf/auto_job_c_limits.hh 0 → 100644
include/qpdf/global.hh 0 → 100644
  1 +// Copyright (c) 2005-2021 Jay Berkenbilt
  2 +// Copyright (c) 2022-2025 Jay Berkenbilt and Manfred Holger
  3 +//
  4 +// This file is part of qpdf.
  5 +//
  6 +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  7 +// in compliance with the License. You may obtain a copy of the License at
  8 +//
  9 +// http://www.apache.org/licenses/LICENSE-2.0
  10 +//
  11 +// Unless required by applicable law or agreed to in writing, software distributed under the License
  12 +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  13 +// or implied. See the License for the specific language governing permissions and limitations under
  14 +// the License.
  15 +//
  16 +// Versions of qpdf prior to version 7 were released under the terms of version 2.0 of the Artistic
  17 +// License. At your option, you may continue to consider qpdf to be licensed under those terms.
  18 +// Please see the manual for additional information.
  19 +
  20 +#ifndef GLOBAL_HH
  21 +#define GLOBAL_HH
  22 +
  23 +#include <qpdf/Constants.h>
  24 +
  25 +#include <qpdf/QUtil.hh>
  26 +#include <qpdf/qpdf-c.h>
  27 +
  28 +#include <cstdint>
  29 +
  30 +namespace qpdf::global
  31 +{
  32 + /// Helper function to translate result codes into C++ exceptions - for qpdf internal use only.
  33 + inline void
  34 + handle_result(qpdf_result_e result)
  35 + {
  36 + if (result != qpdf_r_ok) {
  37 + QUtil::handle_result_code(result, "qpdf::global");
  38 + }
  39 + }
  40 +
  41 + /// Helper function to wrap calls to qpdf_global_get_uint32 - for qpdf internal use only.
  42 + inline uint32_t
  43 + get_uint32(qpdf_param_e param)
  44 + {
  45 + uint32_t value;
  46 + handle_result(qpdf_global_get_uint32(param, &value));
  47 + return value;
  48 + }
  49 +
  50 + /// Helper function to wrap calls to qpdf_global_set_uint32 - for qpdf internal use only.
  51 + inline void
  52 + set_uint32(qpdf_param_e param, uint32_t value)
  53 + {
  54 + handle_result(qpdf_global_set_uint32(param, value));
  55 + }
  56 +
  57 + /// @brief Retrieves the number of limit errors.
  58 + ///
  59 + /// Returns the number a global limit was exceeded. This item is read only.
  60 + ///
  61 + /// @return The number of limit errors.
  62 + ///
  63 + /// @since 12.3
  64 + uint32_t inline limit_errors()
  65 + {
  66 + return get_uint32(qpdf_p_limit_errors);
  67 + }
  68 +
  69 + namespace options
  70 + {
  71 + /// @brief Retrieves whether default limits are enabled.
  72 + ///
  73 + /// @return True if default limits are enabled.
  74 + ///
  75 + /// @since 12.3
  76 + bool inline default_limits()
  77 + {
  78 + return get_uint32(qpdf_p_default_limits) != 0;
  79 + }
  80 +
  81 + /// @brief Disable all optional default limits if `false` is passed.
  82 + ///
  83 + /// This function disables all optional default limits if `false` is passed. Once default
  84 + /// values have been disabled they cannot be re-enabled. Passing `true` has no effect. This
  85 + /// function will leave any limits that have been explicitly set unchanged. Some limits,
  86 + /// such as limits imposed to avoid stack overflows, cannot be disabled but can be changed.
  87 + ///
  88 + /// @param value A boolean indicating whether to disable (false) the default limits.
  89 + ///
  90 + /// @since 12.3
  91 + void inline default_limits(bool value)
  92 + {
  93 + set_uint32(qpdf_p_default_limits, value ? QPDF_TRUE : QPDF_FALSE);
  94 + }
  95 +
  96 + } // namespace options
  97 +
  98 + namespace limits
  99 + {
  100 + /// @brief Retrieves the maximum nesting level while parsing objects.
  101 + ///
  102 + /// @return The maximum nesting level while parsing objects.
  103 + ///
  104 + /// @note The maximum nesting level cannot be disabled by calling `default_limit(false)`.
  105 + ///
  106 + /// @since 12.3
  107 + uint32_t inline parser_max_nesting()
  108 + {
  109 + return get_uint32(qpdf_p_parser_max_nesting);
  110 + }
  111 +
  112 + /// @brief Sets the maximum nesting level while parsing objects.
  113 + ///
  114 + /// @param value The maximum nesting level to set.
  115 + ///
  116 + /// @note The maximum nesting level cannot be disabled by calling `default_limit(false)`.
  117 + ///
  118 + /// @since 12.3
  119 + void inline parser_max_nesting(uint32_t value)
  120 + {
  121 + set_uint32(qpdf_p_parser_max_nesting, value);
  122 + }
  123 +
  124 + /// @brief Retrieves the maximum number of errors allowed while parsing objects.
  125 + ///
  126 + /// A value of 0 means that there is no maximum imposed.
  127 + ///
  128 + /// @return The maximum number of errors allowed while parsing objects.
  129 + ///
  130 + /// @since 12.3
  131 + uint32_t inline parser_max_errors()
  132 + {
  133 + return get_uint32(qpdf_p_parser_max_errors);
  134 + }
  135 +
  136 + /// Sets the maximum number of errors allowed while parsing objects.
  137 + ///
  138 + /// A value of 0 means that there is no maximum imposed.
  139 + ///
  140 + /// @param value The maximum number of errors allowed while parsing objects to set.
  141 + ///
  142 + /// @since 12.3
  143 + void inline parser_max_errors(uint32_t value)
  144 + {
  145 + set_uint32(qpdf_p_parser_max_errors, value);
  146 + }
  147 +
  148 + /// @brief Retrieves the maximum number of top-level objects allowed in a container while
  149 + /// parsing.
  150 + ///
  151 + /// The limit applies when the PDF document's xref table is undamaged and the object itself
  152 + /// can be parsed without errors. The default limit is 4,294,967,295.
  153 + ///
  154 + /// @return The maximum number of top-level objects allowed in a container while parsing
  155 + /// objects.
  156 + ///
  157 + /// @since 12.3
  158 + uint32_t inline parser_max_container_size()
  159 + {
  160 + return get_uint32(qpdf_p_parser_max_container_size);
  161 + }
  162 +
  163 + /// @brief Sets the maximum number of top-level objects allowed in a container while
  164 + /// parsing.
  165 + ///
  166 + /// The limit applies when the PDF document's xref table is undamaged and the object itself
  167 + /// can be parsed without errors. The default limit is 4,294,967,295.
  168 + ///
  169 + /// @param value The maximum number of top-level objects allowed in a container while
  170 + /// parsing objects to set.
  171 + ///
  172 + /// @since 12.3
  173 + void inline parser_max_container_size(uint32_t value)
  174 + {
  175 + set_uint32(qpdf_p_parser_max_container_size, value);
  176 + }
  177 +
  178 + /// @brief Retrieves the maximum number of top-level objects allowed in a container while
  179 + /// parsing objects.
  180 + ///
  181 + /// The limit applies when the PDF document's xref table is damaged or the object itself is
  182 + /// damaged. The limit also applies when parsing xref streams. The default limit is 5,000.
  183 + ///
  184 + /// @return The maximum number of top-level objects allowed in a container while parsing
  185 + /// objects.
  186 + ///
  187 + /// @since 12.3
  188 + uint32_t inline parser_max_container_size_damaged()
  189 + {
  190 + return get_uint32(qpdf_p_parser_max_container_size_damaged);
  191 + }
  192 +
  193 + /// @brief Sets the maximum number of top-level objects allowed in a container while
  194 + /// parsing.
  195 + ///
  196 + /// The limit applies when the PDF document's xref table is damaged or the object itself is
  197 + /// damaged. The limit also applies when parsing trailer dictionaries and xref streams. The
  198 + /// default limit is 5,000.
  199 + ///
  200 + /// @param value The maximum number of top-level objects allowed in a container while
  201 + /// parsing objects to set.
  202 + ///
  203 + /// @since 12.3
  204 + void inline parser_max_container_size_damaged(uint32_t value)
  205 + {
  206 + set_uint32(qpdf_p_parser_max_container_size_damaged, value);
  207 + }
  208 + } // namespace limits
  209 +
  210 +} // namespace qpdf::global
  211 +
  212 +#endif // GLOBAL_HH
include/qpdf/qpdf-c.h
@@ -119,6 +119,8 @@ @@ -119,6 +119,8 @@
119 #include <qpdf/DLL.h> 119 #include <qpdf/DLL.h>
120 #include <qpdf/Types.h> 120 #include <qpdf/Types.h>
121 #include <qpdf/qpdflogger-c.h> 121 #include <qpdf/qpdflogger-c.h>
  122 +
  123 +#include <stdint.h>
122 #include <string.h> 124 #include <string.h>
123 125
124 #ifdef __cplusplus 126 #ifdef __cplusplus
@@ -1000,6 +1002,50 @@ extern &quot;C&quot; { @@ -1000,6 +1002,50 @@ extern &quot;C&quot; {
1000 /* removePage() */ 1002 /* removePage() */
1001 QPDF_DLL 1003 QPDF_DLL
1002 QPDF_ERROR_CODE qpdf_remove_page(qpdf_data qpdf, qpdf_oh page); 1004 QPDF_ERROR_CODE qpdf_remove_page(qpdf_data qpdf, qpdf_oh page);
  1005 +
  1006 + /* GLOBAL OPTIONS AND SETTINGS */
  1007 +
  1008 + QPDF_DLL
  1009 + /**
  1010 + * @brief Retrieves a 32-bit unsigned integer value associated with a global option or limit.
  1011 + *
  1012 + * This function allows querying of specific parameters, identified by the qpdf_param_e enum,
  1013 + * and retrieves their associated unsigned 32-bit integer values. The result will be stored in
  1014 + * the variable pointed to by `value`. For details about the available parameters and their
  1015 + * meanings see `qpdf/global.hh`.
  1016 + *
  1017 + * @param param[in] The parameter for which the value is being retrieved. This must be a valid
  1018 + * value from the qpdf_param_e enumeration.
  1019 + * @param value[out] A pointer to a uint32_t to store the retrieved value. This must be a valid,
  1020 + * non-null pointer.
  1021 + *
  1022 + * @return An enumeration of type qpdf_result_e indicating the result of the operation. Possible
  1023 + * values include success or specific error statuses related to the retrieval process.
  1024 + *
  1025 + * @since 12.3
  1026 + */
  1027 + enum qpdf_result_e qpdf_global_get_uint32(enum qpdf_param_e param, uint32_t* value);
  1028 +
  1029 + QPDF_DLL
  1030 + /**
  1031 + * @brief Sets a global option or limit for the qpdf library to a specified value.
  1032 + *
  1033 + * This function is used to configure global options or limits for the qpdf library based on the
  1034 + * provided parameter and value. The behavior depends on the specific `param` provided and its
  1035 + * valid range of values. For details about the available parameters and their meanings see
  1036 + * `qpdf/global.hh`.
  1037 + *
  1038 + * @param param[in] The parameter to be set. Must be one of the values defined in the
  1039 + * qpdf_param_e enumeration.
  1040 + * @param value[in] The value to assign to the specified parameter. Interpretation of this value
  1041 + * depends on the parameter being set.
  1042 + *
  1043 + * @return An enumeration of type qpdf_result_e indicating the result of the operation. Possible
  1044 + * values include success or specific error statuses related to the retrieval process.
  1045 + *
  1046 + * @since 12.3
  1047 + */
  1048 + enum qpdf_result_e qpdf_global_set_uint32(enum qpdf_param_e param, uint32_t value);
1003 #ifdef __cplusplus 1049 #ifdef __cplusplus
1004 } 1050 }
1005 1051
job.sums
@@ -4,17 +4,18 @@ generate_auto_job 8e3175a515aa8837d8a01bba0346b04b3d777d70330ba5b7d52f691316054a @@ -4,17 +4,18 @@ generate_auto_job 8e3175a515aa8837d8a01bba0346b04b3d777d70330ba5b7d52f691316054a
4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5 6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5
  7 +include/qpdf/auto_job_c_global.hh f1dc365206d033a0d6b19b6e561cc244fbd5b49a8d9604b5b646a5fd92895a5a
7 include/qpdf/auto_job_c_main.hh b865eb827356554763bb8349eadfcbc5cb260f80e025a5e229467c525007356d 8 include/qpdf/auto_job_c_main.hh b865eb827356554763bb8349eadfcbc5cb260f80e025a5e229467c525007356d
8 include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506 9 include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506
9 include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 10 include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62
10 -job.yml e136726a6c7e43736b1f75f4de347fa50baf5f38ed1da58647ce2e980751fb29  
11 -libqpdf/qpdf/auto_job_decl.hh 34ba07d3891c3e5cdd8712f991e508a0652c9db314c5d5bcdf4421b76e6f6e01  
12 -libqpdf/qpdf/auto_job_help.hh d0cca031e99f10caa3f4b70ea574b36b0af63d24de333e7d6f0bf835e959f0be  
13 -libqpdf/qpdf/auto_job_init.hh 02c526c37ad4051cac956ac7c12ae1d020517264f3f3d3beabb066ae2529e4bf  
14 -libqpdf/qpdf/auto_job_json_decl.hh 04965f6321e54b8b3b1dd2ca101d763a22ab44fa81c69e4b6fc0fd6bb7f50f92  
15 -libqpdf/qpdf/auto_job_json_init.hh b49378f00d521a9f3e0ce9086e30b082bc6ef8e43c845e2a3c99857b72448307  
16 -libqpdf/qpdf/auto_job_schema.hh f6a3e8b663714bba50b594f5e31437bbcb96ca4609d2c150c3bbc172e3b000fa 11 +job.yml 131922d22086d9f4710743e18229cc1e956268197bcae8e1aae30f3be42877be
  12 +libqpdf/qpdf/auto_job_decl.hh d612a02839e4f20a80e1c6a3ba09c17187fccddc3581ec7ebb1e3919ffd6801d
  13 +libqpdf/qpdf/auto_job_help.hh 00ac90c621b6c0529d7bad9ea596f57595517901c8d33f49d2812fbea52dfb41
  14 +libqpdf/qpdf/auto_job_init.hh 889dde948e0ab53616584976d9520ab7ab3773c787d241f8a107f5e2f9f2112f
  15 +libqpdf/qpdf/auto_job_json_decl.hh 7dbb83ddadcea39bfd1faa4ca061e1e3c3134d693b8ae634b463e7e19dc8bd0a
  16 +libqpdf/qpdf/auto_job_json_init.hh 3c5f3d07a85e89dd7ecd79342c18e1f0ad580fc57758abb434aa9c9ae277c01e
  17 +libqpdf/qpdf/auto_job_schema.hh eb21c99d3a4dc40b333fd1b19d5de52f8813c74a1d4ca830ea4c3311c120d63e
17 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 18 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
18 -manual/cli.rst b7bd5e34495d3f9156ff6242988dba73a2e5dce33d71f75ec1415514a3843f35  
19 -manual/qpdf.1 d5785d23e77b02a77180419d87787002dc244d82d586d56008ab603299f565fd 19 +manual/cli.rst 08e9e7a18d2b0d05102a072f82eabf9ede6bfb1fb797be307ea680eed93ea60f
  20 +manual/qpdf.1 19a45f8de6b7c0584fe4395c4ae98b92147a2875e45dbdf729c70e644ccca295
20 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b 21 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b
@@ -84,12 +84,24 @@ options: @@ -84,12 +84,24 @@ options:
84 - zopfli 84 - zopfli
85 optional_choices: 85 optional_choices:
86 json-help: json_version 86 json-help: json_version
  87 + - table: global
  88 + prefix: Global
  89 + config: c_global
  90 + positional: false
  91 + bare:
  92 + - no-default-limits
  93 + required_parameter:
  94 + parser-max-container-size: level
  95 + parser-max-container-size-damaged: level
  96 + parser-max-errors: level
  97 + parser-max-nesting: level
87 - table: main 98 - table: main
88 config: c_main 99 config: c_main
89 manual: 100 manual:
90 - add-attachment 101 - add-attachment
91 - copy-attachments-from 102 - copy-attachments-from
92 - encrypt 103 - encrypt
  104 + - global
93 - overlay 105 - overlay
94 - pages 106 - pages
95 - underlay 107 - underlay
@@ -112,6 +124,7 @@ options: @@ -112,6 +124,7 @@ options:
112 - filtered-stream-data 124 - filtered-stream-data
113 - flatten-rotation 125 - flatten-rotation
114 - generate-appearances 126 - generate-appearances
  127 + - global
115 - ignore-xref-streams 128 - ignore-xref-streams
116 - is-encrypted 129 - is-encrypted
117 - json-input 130 - json-input
@@ -399,6 +412,13 @@ json: @@ -399,6 +412,13 @@ json:
399 - null 412 - null
400 json-stream-data: 413 json-stream-data:
401 json-stream-prefix: 414 json-stream-prefix:
  415 + # global options
  416 + global:
  417 + no-default-limits:
  418 + parser-max-container-size:
  419 + parser-max-container-size-damaged:
  420 + parser-max-errors:
  421 + parser-max-nesting:
402 # other options 422 # other options
403 update-from-json: 423 update-from-json:
404 allow-weak-crypto: 424 allow-weak-crypto:
libqpdf/QPDFJob.cc
@@ -25,6 +25,7 @@ @@ -25,6 +25,7 @@
25 #include <qpdf/QTC.hh> 25 #include <qpdf/QTC.hh>
26 #include <qpdf/QUtil.hh> 26 #include <qpdf/QUtil.hh>
27 #include <qpdf/Util.hh> 27 #include <qpdf/Util.hh>
  28 +#include <qpdf/global_private.hh>
28 29
29 #include <qpdf/auto_job_schema.hh> // JOB_SCHEMA_DATA 30 #include <qpdf/auto_job_schema.hh> // JOB_SCHEMA_DATA
30 31
@@ -485,6 +486,11 @@ QPDFJob::writeQPDF(QPDF&amp; pdf) @@ -485,6 +486,11 @@ QPDFJob::writeQPDF(QPDF&amp; pdf)
485 *m->log->getWarn() << m->message_prefix << ": operation succeeded with warnings\n"; 486 *m->log->getWarn() << m->message_prefix << ": operation succeeded with warnings\n";
486 } 487 }
487 } 488 }
  489 + if (!m->d_cfg.suppress_warnings() && global::Limits::errors()) {
  490 + *m->log->getWarn() << m->message_prefix
  491 + << ": some configurable limits were exceeded; for more details "
  492 + "see https://qpdf.readthedocs.io/en/stable/cli.html#global-limits\n";
  493 + }
488 if (m->report_mem_usage) { 494 if (m->report_mem_usage) {
489 // Call get_max_memory_usage before generating output. When debugging, it's easier if print 495 // Call get_max_memory_usage before generating output. When debugging, it's easier if print
490 // statements from get_max_memory_usage are not interleaved with the output. 496 // statements from get_max_memory_usage are not interleaved with the output.
libqpdf/QPDFJob_argv.cc
@@ -31,6 +31,7 @@ namespace @@ -31,6 +31,7 @@ namespace
31 std::shared_ptr<QPDFJob::Config> c_main; 31 std::shared_ptr<QPDFJob::Config> c_main;
32 std::shared_ptr<QPDFJob::CopyAttConfig> c_copy_att; 32 std::shared_ptr<QPDFJob::CopyAttConfig> c_copy_att;
33 std::shared_ptr<QPDFJob::AttConfig> c_att; 33 std::shared_ptr<QPDFJob::AttConfig> c_att;
  34 + std::shared_ptr<QPDFJob::GlobalConfig> c_global;
34 std::shared_ptr<QPDFJob::PagesConfig> c_pages; 35 std::shared_ptr<QPDFJob::PagesConfig> c_pages;
35 std::shared_ptr<QPDFJob::UOConfig> c_uo; 36 std::shared_ptr<QPDFJob::UOConfig> c_uo;
36 std::shared_ptr<QPDFJob::EncConfig> c_enc; 37 std::shared_ptr<QPDFJob::EncConfig> c_enc;
@@ -418,6 +419,21 @@ ArgParser::argEndSetPageLabels() @@ -418,6 +419,21 @@ ArgParser::argEndSetPageLabels()
418 } 419 }
419 420
420 void 421 void
  422 +ArgParser::argGlobal()
  423 +{
  424 + accumulated_args.clear();
  425 + c_global = c_main->global();
  426 + ap.selectOptionTable(O_GLOBAL);
  427 +}
  428 +
  429 +void
  430 +ArgParser::argEndGlobal()
  431 +{
  432 + c_global->endGlobal();
  433 + c_global = nullptr;
  434 +}
  435 +
  436 +void
421 ArgParser::argJobJsonHelp() 437 ArgParser::argJobJsonHelp()
422 { 438 {
423 *QPDFLogger::defaultLogger()->getInfo() 439 *QPDFLogger::defaultLogger()->getInfo()
libqpdf/QPDFJob_config.cc
@@ -4,31 +4,53 @@ @@ -4,31 +4,53 @@
4 #include <qpdf/QPDFUsage.hh> 4 #include <qpdf/QPDFUsage.hh>
5 #include <qpdf/QTC.hh> 5 #include <qpdf/QTC.hh>
6 #include <qpdf/QUtil.hh> 6 #include <qpdf/QUtil.hh>
  7 +#include <qpdf/Util.hh>
  8 +#include <qpdf/global_private.hh>
7 9
8 #include <concepts> 10 #include <concepts>
9 #include <regex> 11 #include <regex>
10 12
11 -static void  
12 -int_usage(std::string_view option, std::integral auto min, std::integral auto max) 13 +[[noreturn]] static void
  14 +int_usage(std::string_view option, std::integral auto max, std::integral auto min)
13 { 15 {
  16 + qpdf_expect(min < max);
14 throw QPDFUsage( 17 throw QPDFUsage(
15 "invalid "s.append(option) + ": must be a number between " + std::to_string(min) + " and " + 18 "invalid "s.append(option) + ": must be a number between " + std::to_string(min) + " and " +
16 std::to_string(max)); 19 std::to_string(max));
17 } 20 }
18 21
19 static int 22 static int
20 -to_int(std::string_view option, std::string const& value, int min, int max) 23 +to_int(std::string_view option, std::string const& value, int max, int min)
21 { 24 {
22 - int result = 0; 25 + qpdf_expect(min < max);
23 try { 26 try {
24 - result = std::stoi(value); 27 + int result = std::stoi(value);
25 if (result < min || result > max) { 28 if (result < min || result > max) {
26 - int_usage(option, min, max); 29 + int_usage(option, max, min);
27 } 30 }
  31 + return result;
28 } catch (std::exception&) { 32 } catch (std::exception&) {
29 - int_usage(option, min, max); 33 + int_usage(option, max, min);
  34 + }
  35 +}
  36 +
  37 +static uint32_t
  38 +to_uint32(
  39 + std::string_view option,
  40 + std::string const& value,
  41 + uint32_t max = std::numeric_limits<uint32_t>::max(),
  42 + uint32_t min = 0)
  43 +{
  44 + qpdf_expect(min < max);
  45 + try {
  46 + auto result = std::stoll(value);
  47 + if (std::cmp_less(result, min) || std::cmp_greater(result, max)) {
  48 + int_usage(option, max, min);
  49 + }
  50 + return static_cast<uint32_t>(result);
  51 + } catch (std::exception&) {
  52 + int_usage(option, max, min);
30 } 53 }
31 - return result;  
32 } 54 }
33 55
34 void 56 void
@@ -156,14 +178,14 @@ QPDFJob::Config::compressStreams(std::string const&amp; parameter) @@ -156,14 +178,14 @@ QPDFJob::Config::compressStreams(std::string const&amp; parameter)
156 QPDFJob::Config* 178 QPDFJob::Config*
157 QPDFJob::Config::compressionLevel(std::string const& parameter) 179 QPDFJob::Config::compressionLevel(std::string const& parameter)
158 { 180 {
159 - o.m->compression_level = to_int("compression-level", parameter, 1, 9); 181 + o.m->compression_level = to_int("compression-level", parameter, 9, 1);
160 return this; 182 return this;
161 } 183 }
162 184
163 QPDFJob::Config* 185 QPDFJob::Config*
164 QPDFJob::Config::jpegQuality(std::string const& parameter) 186 QPDFJob::Config::jpegQuality(std::string const& parameter)
165 { 187 {
166 - o.m->jpeg_quality = to_int("jpeg-quality", parameter, 0, 100); 188 + o.m->jpeg_quality = to_int("jpeg-quality", parameter, 100, 0);
167 return this; 189 return this;
168 } 190 }
169 191
@@ -1140,6 +1162,60 @@ QPDFJob::Config::encrypt( @@ -1140,6 +1162,60 @@ QPDFJob::Config::encrypt(
1140 return std::shared_ptr<EncConfig>(new EncConfig(this)); 1162 return std::shared_ptr<EncConfig>(new EncConfig(this));
1141 } 1163 }
1142 1164
  1165 +QPDFJob::GlobalConfig::GlobalConfig(Config* c) :
  1166 + config(c)
  1167 +{
  1168 +}
  1169 +
  1170 +std::shared_ptr<QPDFJob::GlobalConfig>
  1171 +QPDFJob::Config::global()
  1172 +{
  1173 + return std::make_shared<GlobalConfig>(this);
  1174 +}
  1175 +
  1176 +QPDFJob::Config*
  1177 +QPDFJob::GlobalConfig::endGlobal()
  1178 +{
  1179 + return config;
  1180 +}
  1181 +
  1182 +QPDFJob::GlobalConfig*
  1183 +QPDFJob::GlobalConfig::noDefaultLimits()
  1184 +{
  1185 + global::Options::default_limits(false);
  1186 + return this;
  1187 +}
  1188 +
  1189 +QPDFJob::GlobalConfig*
  1190 +QPDFJob::GlobalConfig::parserMaxContainerSize(const std::string& parameter)
  1191 +{
  1192 + global::Limits::parser_max_container_size(
  1193 + false, to_uint32("parser-max-container-size", parameter, 4'294'967'295));
  1194 + return this;
  1195 +}
  1196 +
  1197 +QPDFJob::GlobalConfig*
  1198 +QPDFJob::GlobalConfig::parserMaxContainerSizeDamaged(const std::string& parameter)
  1199 +{
  1200 + global::Limits::parser_max_container_size(
  1201 + true, to_uint32("parser-max-container-size-damaged", parameter, 4'294'967'295));
  1202 + return this;
  1203 +}
  1204 +
  1205 +QPDFJob::GlobalConfig*
  1206 +QPDFJob::GlobalConfig::parserMaxErrors(const std::string& parameter)
  1207 +{
  1208 + global::Limits::parser_max_errors(to_uint32("parser-max-errors", parameter));
  1209 + return this;
  1210 +}
  1211 +
  1212 +QPDFJob::GlobalConfig*
  1213 +QPDFJob::GlobalConfig::parserMaxNesting(const std::string& parameter)
  1214 +{
  1215 + global::Limits::parser_max_nesting(to_uint32("parser-max-nesting", parameter));
  1216 + return this;
  1217 +}
  1218 +
1143 QPDFJob::Config* 1219 QPDFJob::Config*
1144 QPDFJob::Config::setPageLabels(const std::vector<std::string>& specs) 1220 QPDFJob::Config::setPageLabels(const std::vector<std::string>& specs)
1145 { 1221 {
libqpdf/QPDFJob_json.cc
@@ -63,6 +63,7 @@ namespace @@ -63,6 +63,7 @@ namespace
63 std::shared_ptr<QPDFJob::Config> c_main; 63 std::shared_ptr<QPDFJob::Config> c_main;
64 std::shared_ptr<QPDFJob::CopyAttConfig> c_copy_att; 64 std::shared_ptr<QPDFJob::CopyAttConfig> c_copy_att;
65 std::shared_ptr<QPDFJob::AttConfig> c_att; 65 std::shared_ptr<QPDFJob::AttConfig> c_att;
  66 + std::shared_ptr<QPDFJob::GlobalConfig> c_global;
66 std::shared_ptr<QPDFJob::PagesConfig> c_pages; 67 std::shared_ptr<QPDFJob::PagesConfig> c_pages;
67 std::shared_ptr<QPDFJob::UOConfig> c_uo; 68 std::shared_ptr<QPDFJob::UOConfig> c_uo;
68 std::shared_ptr<QPDFJob::EncConfig> c_enc; 69 std::shared_ptr<QPDFJob::EncConfig> c_enc;
@@ -620,6 +621,19 @@ Handlers::beginSetPageLabelsArray(JSON) @@ -620,6 +621,19 @@ Handlers::beginSetPageLabelsArray(JSON)
620 } 621 }
621 622
622 void 623 void
  624 +Handlers::beginGlobal(JSON)
  625 +{
  626 + c_global = c_main->global();
  627 +}
  628 +
  629 +void
  630 +Handlers::endGlobal()
  631 +{
  632 + c_global->endGlobal();
  633 + c_global = nullptr;
  634 +}
  635 +
  636 +void
623 QPDFJob::initializeFromJson(std::string const& json, bool partial) 637 QPDFJob::initializeFromJson(std::string const& json, bool partial)
624 { 638 {
625 std::list<std::string> errors; 639 std::list<std::string> errors;
libqpdf/QPDFParser.cc
@@ -15,7 +15,7 @@ using namespace qpdf; @@ -15,7 +15,7 @@ using namespace qpdf;
15 15
16 using ObjectPtr = std::shared_ptr<QPDFObject>; 16 using ObjectPtr = std::shared_ptr<QPDFObject>;
17 17
18 -static uint32_t const& max_nesting{global::Limits::objects_max_nesting()}; 18 +static uint32_t const& max_nesting{global::Limits::parser_max_nesting()};
19 19
20 // The ParseGuard class allows QPDFParser to detect re-entrant parsing. It also provides 20 // The ParseGuard class allows QPDFParser to detect re-entrant parsing. It also provides
21 // special access to allow the parser to create unresolved objects and dangling references. 21 // special access to allow the parser to create unresolved objects and dangling references.
@@ -437,17 +437,16 @@ QPDFParser::parseRemainder(bool content_stream) @@ -437,17 +437,16 @@ QPDFParser::parseRemainder(bool content_stream)
437 case QPDFTokenizer::tt_array_open: 437 case QPDFTokenizer::tt_array_open:
438 case QPDFTokenizer::tt_dict_open: 438 case QPDFTokenizer::tt_dict_open:
439 if (stack.size() > max_nesting) { 439 if (stack.size() > max_nesting) {
440 - warn("ignoring excessively deeply nested data structure");  
441 - return {};  
442 - } else {  
443 - b_contents = false;  
444 - stack.emplace_back(  
445 - input,  
446 - (tokenizer.getType() == QPDFTokenizer::tt_array_open) ? st_array  
447 - : st_dictionary_key);  
448 - frame = &stack.back();  
449 - continue; 440 + limits_error(
  441 + "parser-max-nesting", "ignoring excessively deeply nested data structure");
450 } 442 }
  443 + b_contents = false;
  444 + stack.emplace_back(
  445 + input,
  446 + (tokenizer.getType() == QPDFTokenizer::tt_array_open) ? st_array
  447 + : st_dictionary_key);
  448 + frame = &stack.back();
  449 + continue;
451 450
452 case QPDFTokenizer::tt_bool: 451 case QPDFTokenizer::tt_bool:
453 addScalar<QPDF_Bool>(tokenizer.getValue() == "true"); 452 addScalar<QPDF_Bool>(tokenizer.getValue() == "true");
@@ -586,11 +585,11 @@ template &lt;typename T, typename... Args&gt; @@ -586,11 +585,11 @@ template &lt;typename T, typename... Args&gt;
586 void 585 void
587 QPDFParser::addScalar(Args&&... args) 586 QPDFParser::addScalar(Args&&... args)
588 { 587 {
589 - auto limit = Limits::objects_max_container_size(bad_count || sanity_checks);  
590 - if (frame->olist.size() > limit || frame->dict.size() > limit) { 588 + auto limit = Limits::parser_max_container_size(bad_count || sanity_checks);
  589 + if (frame->olist.size() >= limit || frame->dict.size() >= limit) {
591 // Stop adding scalars. We are going to abort when the close token or a bad token is 590 // Stop adding scalars. We are going to abort when the close token or a bad token is
592 // encountered. 591 // encountered.
593 - max_bad_count = 0; 592 + max_bad_count = 1;
594 check_too_many_bad_tokens(); // always throws Error() 593 check_too_many_bad_tokens(); // always throws Error()
595 } 594 }
596 auto obj = QPDFObject::create<T>(std::forward<Args>(args)...); 595 auto obj = QPDFObject::create<T>(std::forward<Args>(args)...);
@@ -644,25 +643,30 @@ QPDFParser::fixMissingKeys() @@ -644,25 +643,30 @@ QPDFParser::fixMissingKeys()
644 void 643 void
645 QPDFParser::check_too_many_bad_tokens() 644 QPDFParser::check_too_many_bad_tokens()
646 { 645 {
647 - auto limit = Limits::objects_max_container_size(bad_count || sanity_checks);  
648 - if (frame->olist.size() > limit || frame->dict.size() > limit) { 646 + auto limit = Limits::parser_max_container_size(bad_count || sanity_checks);
  647 + if (frame->olist.size() >= limit || frame->dict.size() >= limit) {
649 if (bad_count) { 648 if (bad_count) {
650 - warn( 649 + limits_error(
  650 + "parser-max-container-size-damaged",
651 "encountered errors while parsing an array or dictionary with more than " + 651 "encountered errors while parsing an array or dictionary with more than " +
652 - std::to_string(limit) + " elements; giving up on reading object");  
653 - throw Error(); 652 + std::to_string(limit) + " elements; giving up on reading object");
654 } 653 }
655 - warn( 654 + limits_error(
  655 + "parser-max-container-size",
656 "encountered an array or dictionary with more than " + std::to_string(limit) + 656 "encountered an array or dictionary with more than " + std::to_string(limit) +
657 - " elements during xref recovery; giving up on reading object"); 657 + " elements during xref recovery; giving up on reading object");
658 } 658 }
659 - if (max_bad_count && --max_bad_count > 0 && good_count > 4) { 659 + if (max_bad_count && --max_bad_count == 0) {
  660 + limits_error(
  661 + "parser-max-errors", "too many errors during parsing; treating object as null");
  662 + }
  663 + if (good_count > 4) {
660 good_count = 0; 664 good_count = 0;
661 bad_count = 1; 665 bad_count = 1;
662 return; 666 return;
663 } 667 }
664 if (++bad_count > 5 || 668 if (++bad_count > 5 ||
665 - (frame->state != st_array && QIntC::to_size(max_bad_count) < frame->olist.size())) { 669 + (frame->state != st_array && std::cmp_less(max_bad_count, frame->olist.size()))) {
666 // Give up after 5 errors in close proximity or if the number of missing dictionary keys 670 // Give up after 5 errors in close proximity or if the number of missing dictionary keys
667 // exceeds the remaining number of allowable total errors. 671 // exceeds the remaining number of allowable total errors.
668 warn("too many errors; giving up on reading object"); 672 warn("too many errors; giving up on reading object");
@@ -672,6 +676,14 @@ QPDFParser::check_too_many_bad_tokens() @@ -672,6 +676,14 @@ QPDFParser::check_too_many_bad_tokens()
672 } 676 }
673 677
674 void 678 void
  679 +QPDFParser::limits_error(std::string const& limit, std::string const& msg)
  680 +{
  681 + Limits::error();
  682 + warn("limits error("s + limit + "): " + msg);
  683 + throw Error();
  684 +}
  685 +
  686 +void
675 QPDFParser::warn(QPDFExc const& e) const 687 QPDFParser::warn(QPDFExc const& e) const
676 { 688 {
677 // If parsing on behalf of a QPDF object and want to give a warning, we can warn through the 689 // If parsing on behalf of a QPDF object and want to give a warning, we can warn through the
libqpdf/QUtil.cc
@@ -40,6 +40,7 @@ @@ -40,6 +40,7 @@
40 #endif 40 #endif
41 41
42 using namespace qpdf; 42 using namespace qpdf;
  43 +using namespace std::literals;
43 44
44 // First element is 24 45 // First element is 24
45 static unsigned short pdf_doc_low_to_unicode[] = { 46 static unsigned short pdf_doc_low_to_unicode[] = {
@@ -2068,3 +2069,15 @@ QUtil::is_hex_digit(char c) @@ -2068,3 +2069,15 @@ QUtil::is_hex_digit(char c)
2068 { 2069 {
2069 return util::is_hex_digit(c); 2070 return util::is_hex_digit(c);
2070 } 2071 }
  2072 +
  2073 +void
  2074 +QUtil::handle_result_code(qpdf_result_e result, std::string_view context)
  2075 +{
  2076 + if (result == qpdf_r_ok) {
  2077 + return;
  2078 + }
  2079 + qpdf::util::assertion(
  2080 + result == qpdf_r_bad_parameter,
  2081 + "unexpected result code received from function in "s.append(context));
  2082 + throw std::logic_error("invalid parameter supplied to function in "s.append(context));
  2083 +}
libqpdf/global.cc
1 #include <qpdf/global_private.hh> 1 #include <qpdf/global_private.hh>
2 2
  3 +#include <qpdf/Util.hh>
  4 +
3 using namespace qpdf; 5 using namespace qpdf;
  6 +using namespace qpdf::global;
  7 +
  8 +Limits Limits::l;
  9 +Options Options::o;
  10 +
  11 +void
  12 +Limits::parser_max_container_size(bool damaged, uint32_t value)
  13 +{
  14 + if (damaged) {
  15 + l.parser_max_container_size_damaged_set_ = true;
  16 + l.parser_max_container_size_damaged_ = value;
  17 + } else {
  18 + l.parser_max_container_size_ = value;
  19 + }
  20 +}
  21 +
  22 +void
  23 +Limits::disable_defaults()
  24 +{
  25 + if (!l.parser_max_errors_set_) {
  26 + l.parser_max_errors_ = 0;
  27 + }
  28 + if (!l.parser_max_container_size_damaged_set_) {
  29 + l.parser_max_container_size_damaged_ = std::numeric_limits<uint32_t>::max();
  30 + }
  31 +}
  32 +
  33 +qpdf_result_e
  34 +qpdf_global_get_uint32(qpdf_param_e param, uint32_t* value)
  35 +{
  36 + qpdf_expect(value);
  37 + switch (param) {
  38 + case qpdf_p_default_limits:
  39 + *value = Options::default_limits();
  40 + return qpdf_r_ok;
  41 + case qpdf_p_limit_errors:
  42 + *value = Limits::errors();
  43 + return qpdf_r_ok;
  44 + case qpdf_p_parser_max_nesting:
  45 + *value = Limits::parser_max_nesting();
  46 + return qpdf_r_ok;
  47 + case qpdf_p_parser_max_errors:
  48 + *value = Limits::parser_max_errors();
  49 + return qpdf_r_ok;
  50 + case qpdf_p_parser_max_container_size:
  51 + *value = Limits::parser_max_container_size(false);
  52 + return qpdf_r_ok;
  53 + case qpdf_p_parser_max_container_size_damaged:
  54 + *value = Limits::parser_max_container_size(true);
  55 + return qpdf_r_ok;
  56 + default:
  57 + return qpdf_r_bad_parameter;
  58 + }
  59 +}
4 60
5 -global::Limits global::Limits::l; 61 +qpdf_result_e
  62 +qpdf_global_set_uint32(qpdf_param_e param, uint32_t value)
  63 +{
  64 + switch (param) {
  65 + case qpdf_p_default_limits:
  66 + Options::default_limits(value);
  67 + return qpdf_r_ok;
  68 + case qpdf_p_parser_max_nesting:
  69 + Limits::parser_max_nesting(value);
  70 + return qpdf_r_ok;
  71 + case qpdf_p_parser_max_errors:
  72 + Limits::parser_max_errors(value);
  73 + return qpdf_r_ok;
  74 + case qpdf_p_parser_max_container_size:
  75 + Limits::parser_max_container_size(false, value);
  76 + return qpdf_r_ok;
  77 + case qpdf_p_parser_max_container_size_damaged:
  78 + Limits::parser_max_container_size(true, value);
  79 + return qpdf_r_ok;
  80 + default:
  81 + return qpdf_r_bad_parameter;
  82 + }
  83 +}
libqpdf/qpdf/QPDFParser.hh
@@ -124,6 +124,7 @@ class QPDFParser @@ -124,6 +124,7 @@ class QPDFParser
124 void check_too_many_bad_tokens(); 124 void check_too_many_bad_tokens();
125 void warnDuplicateKey(); 125 void warnDuplicateKey();
126 void fixMissingKeys(); 126 void fixMissingKeys();
  127 + [[noreturn]] void limits_error(std::string const& limit, std::string const& msg);
127 void warn(qpdf_offset_t offset, std::string const& msg) const; 128 void warn(qpdf_offset_t offset, std::string const& msg) const;
128 void warn(std::string const& msg) const; 129 void warn(std::string const& msg) const;
129 void warn(QPDFExc const&) const; 130 void warn(QPDFExc const&) const;
@@ -149,7 +150,7 @@ class QPDFParser @@ -149,7 +150,7 @@ class QPDFParser
149 // it only gets incremented or reset when a bad token is encountered. 150 // it only gets incremented or reset when a bad token is encountered.
150 int bad_count{0}; 151 int bad_count{0};
151 // Number of bad tokens (remaining) before giving up. 152 // Number of bad tokens (remaining) before giving up.
152 - uint32_t max_bad_count{Limits::objects_max_errors()}; 153 + uint32_t max_bad_count{Limits::parser_max_errors()};
153 // Number of good tokens since last bad token. Irrelevant if bad_count == 0. 154 // Number of good tokens since last bad token. Irrelevant if bad_count == 0.
154 int good_count{0}; 155 int good_count{0};
155 // Start offset including any leading whitespace. 156 // Start offset including any leading whitespace.
libqpdf/qpdf/auto_job_decl.hh
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 // 5 //
6 // clang-format off 6 // clang-format off
7 // 7 //
  8 +static constexpr char const* O_GLOBAL = "global";
8 static constexpr char const* O_PAGES = "pages"; 9 static constexpr char const* O_PAGES = "pages";
9 static constexpr char const* O_ENCRYPTION = "encryption"; 10 static constexpr char const* O_ENCRYPTION = "encryption";
10 static constexpr char const* O_40_BIT_ENCRYPTION = "40-bit encryption"; 11 static constexpr char const* O_40_BIT_ENCRYPTION = "40-bit encryption";
@@ -21,11 +22,13 @@ void argShowCrypto(); @@ -21,11 +22,13 @@ void argShowCrypto();
21 void argJobJsonHelp(); 22 void argJobJsonHelp();
22 void argZopfli(); 23 void argZopfli();
23 void argJsonHelp(std::string const&); 24 void argJsonHelp(std::string const&);
  25 +void argEndGlobal();
24 void argPositional(std::string const&); 26 void argPositional(std::string const&);
25 void argAddAttachment(); 27 void argAddAttachment();
26 void argCopyAttachmentsFrom(); 28 void argCopyAttachmentsFrom();
27 void argEmpty(); 29 void argEmpty();
28 void argEncrypt(); 30 void argEncrypt();
  31 +void argGlobal();
29 void argOverlay(); 32 void argOverlay();
30 void argPages(); 33 void argPages();
31 void argReplaceInput(); 34 void argReplaceInput();
libqpdf/qpdf/auto_job_help.hh
@@ -1004,6 +1004,46 @@ Update a PDF file from a JSON file. Please see the &quot;qpdf JSON&quot; @@ -1004,6 +1004,46 @@ Update a PDF file from a JSON file. Please see the &quot;qpdf JSON&quot;
1004 chapter of the manual for information about how to use this 1004 chapter of the manual for information about how to use this
1005 option. 1005 option.
1006 )"); 1006 )");
  1007 +ap.addHelpTopic("global", "options for changing the behaviour of qpdf", R"(The options below modify the overall behaviour of qpdf. This includes modifying
  1008 +implementation limits and changing modes of operation.
  1009 +)");
  1010 +ap.addOptionHelp("--global", "global", "begin setting global options and limits", R"(--global [options] --
  1011 +
  1012 +Begin setting global options and limits.
  1013 +)");
  1014 +ap.addOptionHelp("--no-default-limits", "global", "disable optional default limits", R"(Disables all optional default limits. Explicitly set limits are unaffected. Some
  1015 +limits, especially limits designed to prevent stack overflow, cannot be removed
  1016 +with this option but can be modified. Where this is the case it is mentioned
  1017 +in the entry for the relevant option.
  1018 +)");
  1019 +ap.addOptionHelp("--parser-max-nesting", "global", "set the maximum nesting level while parsing objects", R"(--parser-max-nesting=n
  1020 +
  1021 +Set the maximum nesting level while parsing objects. The maximum nesting level
  1022 +is not disabled by --no-default-limits. Defaults to 499.
  1023 +)");
  1024 +ap.addOptionHelp("--parser-max-errors", "global", "set the maximum number of errors while parsing", R"(--parser-max-errors=n
  1025 +
  1026 +Set the maximum number of errors allowed while parsing an indirect object.
  1027 +A value of 0 means that no maximum is imposed. Defaults to 15.
  1028 +)");
  1029 +ap.addOptionHelp("--parser-max-container-size", "global", "set the maximum container size while parsing", R"(--parser-max-container-size=n
  1030 +
  1031 +Set the maximum number of top-level objects allowed in a container while
  1032 +parsing. The limit applies when the PDF document's xref table is undamaged
  1033 +and the object itself can be parsed without errors. The default limit
  1034 +is 4,294,967,295. See also --parser-max-container-size-damaged.
  1035 +)");
  1036 +ap.addOptionHelp("--parser-max-container-size-damaged", "global", "set the maximum container size while parsing damaged files", R"(--parser-max-container-size-damaged=n
  1037 +
  1038 +Set the maximum number of top-level objects allowed in a container while
  1039 +parsing. The limit applies when the PDF document's xref table is damaged
  1040 +or the object itself is damaged. The limit also applies when parsing
  1041 +xref streams. The default limit is 5,000.
  1042 +See also --parser-max-container-size.
  1043 +)");
  1044 +}
  1045 +static void add_help_9(QPDFArgParser& ap)
  1046 +{
1007 ap.addHelpTopic("testing", "options for testing or debugging", R"(The options below are useful when writing automated test code that 1047 ap.addHelpTopic("testing", "options for testing or debugging", R"(The options below are useful when writing automated test code that
1008 includes files created by qpdf or when testing qpdf itself. 1048 includes files created by qpdf or when testing qpdf itself.
1009 )"); 1049 )");
@@ -1039,6 +1079,7 @@ static void add_help(QPDFArgParser&amp; ap) @@ -1039,6 +1079,7 @@ static void add_help(QPDFArgParser&amp; ap)
1039 add_help_6(ap); 1079 add_help_6(ap);
1040 add_help_7(ap); 1080 add_help_7(ap);
1041 add_help_8(ap); 1081 add_help_8(ap);
  1082 + add_help_9(ap);
1042 ap.addHelpFooter("For detailed help, visit the qpdf manual: https://qpdf.readthedocs.io\n"); 1083 ap.addHelpFooter("For detailed help, visit the qpdf manual: https://qpdf.readthedocs.io\n");
1043 } 1084 }
1044 1085
libqpdf/qpdf/auto_job_init.hh
@@ -34,6 +34,12 @@ this-&gt;ap.addBare(&quot;show-crypto&quot;, b(&amp;ArgParser::argShowCrypto)); @@ -34,6 +34,12 @@ this-&gt;ap.addBare(&quot;show-crypto&quot;, b(&amp;ArgParser::argShowCrypto));
34 this->ap.addBare("job-json-help", b(&ArgParser::argJobJsonHelp)); 34 this->ap.addBare("job-json-help", b(&ArgParser::argJobJsonHelp));
35 this->ap.addBare("zopfli", b(&ArgParser::argZopfli)); 35 this->ap.addBare("zopfli", b(&ArgParser::argZopfli));
36 this->ap.addChoices("json-help", p(&ArgParser::argJsonHelp), false, json_version_choices); 36 this->ap.addChoices("json-help", p(&ArgParser::argJsonHelp), false, json_version_choices);
  37 +this->ap.registerOptionTable("global", b(&ArgParser::argEndGlobal));
  38 +this->ap.addBare("no-default-limits", [this](){c_global->noDefaultLimits();});
  39 +this->ap.addRequiredParameter("parser-max-container-size", [this](std::string const& x){c_global->parserMaxContainerSize(x);}, "level");
  40 +this->ap.addRequiredParameter("parser-max-container-size-damaged", [this](std::string const& x){c_global->parserMaxContainerSizeDamaged(x);}, "level");
  41 +this->ap.addRequiredParameter("parser-max-errors", [this](std::string const& x){c_global->parserMaxErrors(x);}, "level");
  42 +this->ap.addRequiredParameter("parser-max-nesting", [this](std::string const& x){c_global->parserMaxNesting(x);}, "level");
37 this->ap.selectMainOptionTable(); 43 this->ap.selectMainOptionTable();
38 this->ap.addPositional(p(&ArgParser::argPositional)); 44 this->ap.addPositional(p(&ArgParser::argPositional));
39 this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment)); 45 this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment));
@@ -50,6 +56,7 @@ this-&gt;ap.addBare(&quot;externalize-inline-images&quot;, [this](){c_main-&gt;externalizeInline @@ -50,6 +56,7 @@ this-&gt;ap.addBare(&quot;externalize-inline-images&quot;, [this](){c_main-&gt;externalizeInline
50 this->ap.addBare("filtered-stream-data", [this](){c_main->filteredStreamData();}); 56 this->ap.addBare("filtered-stream-data", [this](){c_main->filteredStreamData();});
51 this->ap.addBare("flatten-rotation", [this](){c_main->flattenRotation();}); 57 this->ap.addBare("flatten-rotation", [this](){c_main->flattenRotation();});
52 this->ap.addBare("generate-appearances", [this](){c_main->generateAppearances();}); 58 this->ap.addBare("generate-appearances", [this](){c_main->generateAppearances();});
  59 +this->ap.addBare("global", b(&ArgParser::argGlobal));
53 this->ap.addBare("ignore-xref-streams", [this](){c_main->ignoreXrefStreams();}); 60 this->ap.addBare("ignore-xref-streams", [this](){c_main->ignoreXrefStreams();});
54 this->ap.addBare("is-encrypted", [this](){c_main->isEncrypted();}); 61 this->ap.addBare("is-encrypted", [this](){c_main->isEncrypted();});
55 this->ap.addBare("json-input", [this](){c_main->jsonInput();}); 62 this->ap.addBare("json-input", [this](){c_main->jsonInput();});
libqpdf/qpdf/auto_job_json_decl.hh
@@ -24,6 +24,8 @@ void beginJsonKeyArray(JSON); @@ -24,6 +24,8 @@ void beginJsonKeyArray(JSON);
24 void endJsonKeyArray(); 24 void endJsonKeyArray();
25 void beginJsonObjectArray(JSON); 25 void beginJsonObjectArray(JSON);
26 void endJsonObjectArray(); 26 void endJsonObjectArray();
  27 +void beginGlobal(JSON);
  28 +void endGlobal();
27 void beginAddAttachmentArray(JSON); 29 void beginAddAttachmentArray(JSON);
28 void endAddAttachmentArray(); 30 void endAddAttachmentArray();
29 void beginAddAttachment(JSON); 31 void beginAddAttachment(JSON);
libqpdf/qpdf/auto_job_json_init.hh
@@ -272,6 +272,24 @@ popHandler(); // key: jsonStreamData @@ -272,6 +272,24 @@ popHandler(); // key: jsonStreamData
272 pushKey("jsonStreamPrefix"); 272 pushKey("jsonStreamPrefix");
273 addParameter([this](std::string const& p) { c_main->jsonStreamPrefix(p); }); 273 addParameter([this](std::string const& p) { c_main->jsonStreamPrefix(p); });
274 popHandler(); // key: jsonStreamPrefix 274 popHandler(); // key: jsonStreamPrefix
  275 +pushKey("global");
  276 +beginDict(bindJSON(&Handlers::beginGlobal), bindBare(&Handlers::endGlobal)); // .global
  277 +pushKey("noDefaultLimits");
  278 +addBare([this]() { c_global->noDefaultLimits(); });
  279 +popHandler(); // key: noDefaultLimits
  280 +pushKey("parserMaxContainerSize");
  281 +addParameter([this](std::string const& p) { c_global->parserMaxContainerSize(p); });
  282 +popHandler(); // key: parserMaxContainerSize
  283 +pushKey("parserMaxContainerSizeDamaged");
  284 +addParameter([this](std::string const& p) { c_global->parserMaxContainerSizeDamaged(p); });
  285 +popHandler(); // key: parserMaxContainerSizeDamaged
  286 +pushKey("parserMaxErrors");
  287 +addParameter([this](std::string const& p) { c_global->parserMaxErrors(p); });
  288 +popHandler(); // key: parserMaxErrors
  289 +pushKey("parserMaxNesting");
  290 +addParameter([this](std::string const& p) { c_global->parserMaxNesting(p); });
  291 +popHandler(); // key: parserMaxNesting
  292 +popHandler(); // key: global
275 pushKey("updateFromJson"); 293 pushKey("updateFromJson");
276 addParameter([this](std::string const& p) { c_main->updateFromJson(p); }); 294 addParameter([this](std::string const& p) { c_main->updateFromJson(p); });
277 popHandler(); // key: updateFromJson 295 popHandler(); // key: updateFromJson
libqpdf/qpdf/auto_job_schema.hh
@@ -90,6 +90,13 @@ static constexpr char const* JOB_SCHEMA_DATA = R&quot;({ @@ -90,6 +90,13 @@ static constexpr char const* JOB_SCHEMA_DATA = R&quot;({
90 ], 90 ],
91 "jsonStreamData": "how to handle streams in json output", 91 "jsonStreamData": "how to handle streams in json output",
92 "jsonStreamPrefix": "prefix for json stream data files", 92 "jsonStreamPrefix": "prefix for json stream data files",
  93 + "global": {
  94 + "noDefaultLimits": "disable optional default limits",
  95 + "parserMaxContainerSize": "set the maximum container size while parsing",
  96 + "parserMaxContainerSizeDamaged": "set the maximum container size while parsing damaged files",
  97 + "parserMaxErrors": "set the maximum number of errors while parsing",
  98 + "parserMaxNesting": "set the maximum nesting level while parsing objects"
  99 + },
93 "updateFromJson": "update a PDF from qpdf JSON", 100 "updateFromJson": "update a PDF from qpdf JSON",
94 "allowWeakCrypto": "allow insecure cryptographic algorithms", 101 "allowWeakCrypto": "allow insecure cryptographic algorithms",
95 "keepFilesOpen": "manage keeping multiple files open", 102 "keepFilesOpen": "manage keeping multiple files open",
libqpdf/qpdf/global_private.hh
1 -  
2 #ifndef GLOBAL_PRIVATE_HH 1 #ifndef GLOBAL_PRIVATE_HH
3 #define GLOBAL_PRIVATE_HH 2 #define GLOBAL_PRIVATE_HH
4 3
5 -#include <qpdf/Constants.h> 4 +#include <qpdf/global.hh>
6 5
7 -#include <cstdint>  
8 #include <limits> 6 #include <limits>
9 7
10 -namespace qpdf 8 +namespace qpdf::global
11 { 9 {
12 - namespace global 10 + class Limits
13 { 11 {
14 - class Limits  
15 - {  
16 - public:  
17 - Limits(Limits const&) = delete;  
18 - Limits(Limits&&) = delete;  
19 - Limits& operator=(Limits const&) = delete;  
20 - Limits& operator=(Limits&&) = delete;  
21 -  
22 - static uint32_t const&  
23 - objects_max_nesting()  
24 - {  
25 - return l.objects_max_nesting_;  
26 - } 12 + public:
  13 + Limits(Limits const&) = delete;
  14 + Limits(Limits&&) = delete;
  15 + Limits& operator=(Limits const&) = delete;
  16 + Limits& operator=(Limits&&) = delete;
27 17
28 - static uint32_t const&  
29 - objects_max_errors()  
30 - {  
31 - return l.objects_max_errors_;  
32 - } 18 + static uint32_t const&
  19 + parser_max_nesting()
  20 + {
  21 + return l.parser_max_nesting_;
  22 + }
  23 +
  24 + static void
  25 + parser_max_nesting(uint32_t value)
  26 + {
  27 + l.parser_max_nesting_ = value;
  28 + }
  29 +
  30 + static uint32_t const&
  31 + parser_max_errors()
  32 + {
  33 + return l.parser_max_errors_;
  34 + }
  35 +
  36 + static void
  37 + parser_max_errors(uint32_t value)
  38 + {
  39 + l.parser_max_errors_set_ = true;
  40 + l.parser_max_errors_ = value;
  41 + }
  42 +
  43 + static uint32_t const&
  44 + parser_max_container_size(bool damaged)
  45 + {
  46 + return damaged ? l.parser_max_container_size_damaged_ : l.parser_max_container_size_;
  47 + }
33 48
34 - static uint32_t const&  
35 - objects_max_container_size(bool damaged)  
36 - {  
37 - return damaged ? l.objects_max_container_size_damaged_  
38 - : l.objects_max_container_size_; 49 + static void parser_max_container_size(bool damaged, uint32_t value);
  50 +
  51 + /// Record a limit error.
  52 + static void
  53 + error()
  54 + {
  55 + if (l.errors_ < std::numeric_limits<uint32_t>::max()) {
  56 + ++l.errors_;
39 } 57 }
  58 + }
  59 +
  60 + static uint32_t const&
  61 + errors()
  62 + {
  63 + return l.errors_;
  64 + }
  65 +
  66 + static void disable_defaults();
  67 +
  68 + private:
  69 + Limits() = default;
  70 + ~Limits() = default;
40 71
41 - private:  
42 - Limits() = default;  
43 - ~Limits() = default; 72 + static Limits l;
44 73
45 - static Limits l; 74 + uint32_t errors_{0};
46 75
47 - uint32_t objects_max_nesting_{499};  
48 - uint32_t objects_max_errors_{15};  
49 - uint32_t objects_max_container_size_{std::numeric_limits<uint32_t>::max()};  
50 - uint32_t objects_max_container_size_damaged_{5'000};  
51 - }; 76 + uint32_t parser_max_nesting_{499};
  77 + uint32_t parser_max_errors_{15};
  78 + bool parser_max_errors_set_{false};
  79 + uint32_t parser_max_container_size_{std::numeric_limits<uint32_t>::max()};
  80 + uint32_t parser_max_container_size_damaged_{5'000};
  81 + bool parser_max_container_size_damaged_set_{false};
  82 + };
  83 +
  84 + class Options
  85 + {
  86 + public:
  87 + static bool
  88 + default_limits()
  89 + {
  90 + return static_cast<bool>(o.default_limits_);
  91 + }
  92 +
  93 + static void
  94 + default_limits(bool value)
  95 + {
  96 + if (!value) {
  97 + o.default_limits_ = false;
  98 + Limits::disable_defaults();
  99 + }
  100 + }
52 101
53 - } // namespace global 102 + private:
  103 + static Options o;
54 104
55 -} // namespace qpdf 105 + bool default_limits_{true};
  106 + };
  107 +} // namespace qpdf::global
56 108
57 #endif // GLOBAL_PRIVATE_HH 109 #endif // GLOBAL_PRIVATE_HH
libtests/objects.cc
@@ -5,8 +5,10 @@ @@ -5,8 +5,10 @@
5 #include <qpdf/QPDF.hh> 5 #include <qpdf/QPDF.hh>
6 6
7 #include <qpdf/QIntC.hh> 7 #include <qpdf/QIntC.hh>
  8 +#include <qpdf/QPDFJob.hh>
8 #include <qpdf/QPDFObjectHandle_private.hh> 9 #include <qpdf/QPDFObjectHandle_private.hh>
9 #include <qpdf/QUtil.hh> 10 #include <qpdf/QUtil.hh>
  11 +#include <qpdf/global.hh>
10 12
11 #include <climits> 13 #include <climits>
12 #include <cstdio> 14 #include <cstdio>
@@ -153,6 +155,132 @@ test_1(QPDF&amp; pdf, char const* arg2) @@ -153,6 +155,132 @@ test_1(QPDF&amp; pdf, char const* arg2)
153 assert(QPDFObjectHandle(d).getDictAsMap().size() == 4); 155 assert(QPDFObjectHandle(d).getDictAsMap().size() == 4);
154 } 156 }
155 157
  158 +static void
  159 +test_2(QPDF& pdf, char const* arg2)
  160 +{
  161 + // Test global limits.
  162 + using namespace qpdf::global::options;
  163 + using namespace qpdf::global::limits;
  164 +
  165 + // Check default values
  166 + assert(parser_max_nesting() == 499);
  167 + assert(parser_max_errors() == 15);
  168 + assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max());
  169 + assert(parser_max_container_size_damaged() == 5'000);
  170 + assert(default_limits());
  171 +
  172 + // Test disabling optional default limits
  173 + default_limits(false);
  174 + assert(parser_max_nesting() == 499);
  175 + assert(parser_max_errors() == 0);
  176 + assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max());
  177 + assert(parser_max_container_size_damaged() == std::numeric_limits<uint32_t>::max());
  178 + assert(!default_limits());
  179 +
  180 + // Check disabling default limits is irreversible
  181 + default_limits(true);
  182 + assert(!default_limits());
  183 +
  184 + // Test setting limits
  185 + parser_max_nesting(11);
  186 + parser_max_errors(12);
  187 + parser_max_container_size(13);
  188 + parser_max_container_size_damaged(14);
  189 +
  190 + assert(parser_max_nesting() == 11);
  191 + assert(parser_max_errors() == 12);
  192 + assert(parser_max_container_size() == 13);
  193 + assert(parser_max_container_size_damaged() == 14);
  194 +
  195 + // Check disabling default limits does not override explicit limits
  196 + default_limits(false);
  197 + assert(parser_max_nesting() == 11);
  198 + assert(parser_max_errors() == 12);
  199 + assert(parser_max_container_size() == 13);
  200 + assert(parser_max_container_size_damaged() == 14);
  201 +
  202 + // Test parameter checking
  203 + QUtil::handle_result_code(qpdf_r_ok, "");
  204 + bool thrown = false;
  205 + try {
  206 + qpdf::global::handle_result(qpdf_r_success_mask);
  207 + } catch (std::logic_error const&) {
  208 + thrown = true;
  209 + }
  210 + assert(thrown);
  211 + thrown = false;
  212 + try {
  213 + qpdf::global::get_uint32(qpdf_param_e(42));
  214 + } catch (std::logic_error const&) {
  215 + thrown = true;
  216 + }
  217 + assert(thrown);
  218 + thrown = false;
  219 + try {
  220 + qpdf::global::set_uint32(qpdf_param_e(42), 42);
  221 + } catch (std::logic_error const&) {
  222 + thrown = true;
  223 + }
  224 + assert(thrown);
  225 +
  226 + /* Test limit errors */
  227 + assert(qpdf::global::limit_errors() == 0);
  228 + QPDFObjectHandle::parse("[[[[]]]]");
  229 + assert(qpdf::global::limit_errors() == 0);
  230 + parser_max_nesting(3);
  231 + try {
  232 + QPDFObjectHandle::parse("[[[[[]]]]]");
  233 + } catch (std::exception&) {
  234 + }
  235 + assert(qpdf::global::limit_errors() == 1);
  236 + try {
  237 + QPDFObjectHandle::parse("[[[[[]]]]]");
  238 + } catch (std::exception&) {
  239 + }
  240 + assert(qpdf::global::limit_errors() == 2);
  241 +
  242 + // Test global settings using the QPDFJob interface
  243 + QPDFJob j;
  244 + j.config()
  245 + ->inputFile("minimal.pdf")
  246 + ->global()
  247 + ->parserMaxNesting("111")
  248 + ->parserMaxErrors("112")
  249 + ->parserMaxContainerSize("113")
  250 + ->parserMaxContainerSizeDamaged("114")
  251 + ->noDefaultLimits()
  252 + ->endGlobal()
  253 + ->outputFile("a.pdf");
  254 + auto qpdf = j.createQPDF();
  255 + assert(parser_max_nesting() == 111);
  256 + assert(parser_max_errors() == 112);
  257 + assert(parser_max_container_size() == 113);
  258 + assert(parser_max_container_size_damaged() == 114);
  259 + assert(!default_limits());
  260 +
  261 + // Test global settings using the JobJSON
  262 + QPDFJob jj;
  263 + jj.initializeFromJson(R"(
  264 + {
  265 + "inputFile": "minimal.pdf",
  266 + "global": {
  267 + "parserMaxNesting": "211",
  268 + "parserMaxErrors": "212",
  269 + "parserMaxContainerSize": "213",
  270 + "parserMaxContainerSizeDamaged": "214",
  271 + "noDefaultLimits": ""
  272 + },
  273 + "outputFile": "a.pdf"
  274 + }
  275 + )");
  276 + qpdf = j.createQPDF();
  277 + assert(parser_max_nesting() == 211);
  278 + assert(parser_max_errors() == 212);
  279 + assert(parser_max_container_size() == 213);
  280 + assert(parser_max_container_size_damaged() == 214);
  281 + assert(!default_limits());
  282 +}
  283 +
156 void 284 void
157 runtest(int n, char const* filename1, char const* arg2) 285 runtest(int n, char const* filename1, char const* arg2)
158 { 286 {
@@ -160,9 +288,7 @@ runtest(int n, char const* filename1, char const* arg2) @@ -160,9 +288,7 @@ runtest(int n, char const* filename1, char const* arg2)
160 // the test suite to see how the test is invoked to find the file 288 // the test suite to see how the test is invoked to find the file
161 // that the test is supposed to operate on. 289 // that the test is supposed to operate on.
162 290
163 - std::set<int> ignore_filename = {  
164 - 1,  
165 - }; 291 + std::set<int> ignore_filename = {1, 2};
166 292
167 QPDF pdf; 293 QPDF pdf;
168 std::shared_ptr<char> file_buf; 294 std::shared_ptr<char> file_buf;
@@ -176,9 +302,7 @@ runtest(int n, char const* filename1, char const* arg2) @@ -176,9 +302,7 @@ runtest(int n, char const* filename1, char const* arg2)
176 } 302 }
177 303
178 std::map<int, void (*)(QPDF&, char const*)> test_functions = { 304 std::map<int, void (*)(QPDF&, char const*)> test_functions = {
179 - {0, test_0},  
180 - {1, test_1},  
181 - }; 305 + {0, test_0}, {1, test_1}, {2, test_2}};
182 306
183 auto fn = test_functions.find(n); 307 auto fn = test_functions.find(n);
184 if (fn == test_functions.end()) { 308 if (fn == test_functions.end()) {
libtests/qtest/objects.test
@@ -11,7 +11,7 @@ require TestDriver; @@ -11,7 +11,7 @@ require TestDriver;
11 11
12 my $td = new TestDriver('objects'); 12 my $td = new TestDriver('objects');
13 13
14 -my $n_tests = 2; 14 +my $n_tests = 3;
15 15
16 $td->runtest("integer type checks", 16 $td->runtest("integer type checks",
17 {$td->COMMAND => "objects 0 minimal.pdf"}, 17 {$td->COMMAND => "objects 0 minimal.pdf"},
@@ -23,4 +23,9 @@ $td-&gt;runtest(&quot;dictionary checks&quot;, @@ -23,4 +23,9 @@ $td-&gt;runtest(&quot;dictionary checks&quot;,
23 {$td->STRING => => "test 1 done\n", $td->EXIT_STATUS => 0}, 23 {$td->STRING => => "test 1 done\n", $td->EXIT_STATUS => 0},
24 $td->NORMALIZE_NEWLINES); 24 $td->NORMALIZE_NEWLINES);
25 25
  26 +$td->runtest("global object limits",
  27 + {$td->COMMAND => "objects 2 -"},
  28 + {$td->STRING => => "test 2 done\n", $td->EXIT_STATUS => 0},
  29 + $td->NORMALIZE_NEWLINES);
  30 +
26 $td->report($n_tests); 31 $td->report($n_tests);
manual/cli.rst
@@ -3762,6 +3762,110 @@ Related Options @@ -3762,6 +3762,110 @@ Related Options
3762 For a information about how to use this option, please see 3762 For a information about how to use this option, please see
3763 :ref:`json`. 3763 :ref:`json`.
3764 3764
  3765 +.. _global-options:
  3766 +
  3767 +Global Options
  3768 +--------------
  3769 +
  3770 +.. help-topic global: options for changing the behaviour of qpdf
  3771 +
  3772 + The options below modify the overall behaviour of qpdf. This includes modifying
  3773 + implementation limits and changing modes of operation.
  3774 +
  3775 +The options below modify the overall behaviour of qpdf. This includes modifying implementation
  3776 +limits and changing modes of operation.
  3777 +
  3778 +Related Options
  3779 +~~~~~~~~~~~~~~~
  3780 +
  3781 +.. qpdf:option:: --global [options] --
  3782 +
  3783 + .. help: begin setting global options and limits
  3784 +
  3785 + Begin setting global options and limits.
  3786 +
  3787 +Begin setting global options and limits.
  3788 +
  3789 +
  3790 +Global Limits
  3791 +~~~~~~~~~~~~~
  3792 +
  3793 +qpdf uses a number of global limits to protect itself from damaged and specially constructed PDF
  3794 +files. Without these limits such files can cause qpdf to crash and/or to consume excessive
  3795 +processor and memory resources. Very few legitimate PDF files exceed these limits, however
  3796 +where necessary the limits can be modified or entirely removed by the following options.
  3797 +
  3798 +.. qpdf:option:: --no-default-limits
  3799 +
  3800 + .. help: disable optional default limits
  3801 +
  3802 + Disables all optional default limits. Explicitly set limits are unaffected. Some
  3803 + limits, especially limits designed to prevent stack overflow, cannot be removed
  3804 + with this option but can be modified. Where this is the case it is mentioned
  3805 + in the entry for the relevant option.
  3806 +
  3807 +Disables all optional default limits. Explicitly set limits are unaffected. Some limits,
  3808 +especially limits designed to prevent stack overflow, cannot be removed with this option
  3809 +but can be modified. Where this is the case it is mentioned in the entry for the relevant
  3810 +option.
  3811 +
  3812 +Parser Limits
  3813 +.............
  3814 +
  3815 +.. qpdf:option:: --parser-max-nesting=n
  3816 +
  3817 + .. help: set the maximum nesting level while parsing objects
  3818 +
  3819 + Set the maximum nesting level while parsing objects. The maximum nesting level
  3820 + is not disabled by --no-default-limits. Defaults to 499.
  3821 +
  3822 +Set the maximum nesting level while parsing objects. The maximum nesting level is not
  3823 +disabled by :qpdf:ref:`--no-default-limits`. Defaults to 499.
  3824 +
  3825 +
  3826 +.. qpdf:option:: --parser-max-errors=n
  3827 +
  3828 + .. help: set the maximum number of errors while parsing
  3829 +
  3830 + Set the maximum number of errors allowed while parsing an indirect object.
  3831 + A value of 0 means that no maximum is imposed. Defaults to 15.
  3832 +
  3833 +Set the maximum number of errors allowed while parsing an indirect object.
  3834 +A value of 0 means that no maximum is imposed. Defaults to 15.
  3835 +
  3836 +.. qpdf:option:: --parser-max-container-size=n
  3837 +
  3838 + .. help: set the maximum container size while parsing
  3839 +
  3840 + Set the maximum number of top-level objects allowed in a container while
  3841 + parsing. The limit applies when the PDF document's xref table is undamaged
  3842 + and the object itself can be parsed without errors. The default limit
  3843 + is 4,294,967,295. See also --parser-max-container-size-damaged.
  3844 +
  3845 +
  3846 +Set the maximum number of top-level objects allowed in a container while
  3847 +parsing. The limit applies when the PDF document's xref table is undamaged
  3848 +and the object itself can be parsed without errors. The default limit
  3849 +is 4,294,967,295. See also :qpdf:ref:`--parser-max-container-size-damaged`.
  3850 +
  3851 +
  3852 +.. qpdf:option:: --parser-max-container-size-damaged=n
  3853 +
  3854 + .. help: set the maximum container size while parsing damaged files
  3855 +
  3856 + Set the maximum number of top-level objects allowed in a container while
  3857 + parsing. The limit applies when the PDF document's xref table is damaged
  3858 + or the object itself is damaged. The limit also applies when parsing
  3859 + xref streams. The default limit is 5,000.
  3860 + See also --parser-max-container-size.
  3861 +
  3862 +
  3863 +Set the maximum number of top-level objects allowed in a container while
  3864 +parsing. The limit applies when the PDF document's xref table is damaged
  3865 +or the object itself is damaged. The limit also applies when parsing
  3866 +xref streams. The default limit is 5,000.
  3867 +See also :qpdf:ref:`--parser-max-container-size`.
  3868 +
3765 .. _test-options: 3869 .. _test-options:
3766 3870
3767 Options for Testing or Debugging 3871 Options for Testing or Debugging
manual/qpdf.1
@@ -1191,6 +1191,51 @@ how to use this option. @@ -1191,6 +1191,51 @@ how to use this option.
1191 Update a PDF file from a JSON file. Please see the "qpdf JSON" 1191 Update a PDF file from a JSON file. Please see the "qpdf JSON"
1192 chapter of the manual for information about how to use this 1192 chapter of the manual for information about how to use this
1193 option. 1193 option.
  1194 +.SH GLOBAL (options for changing the behaviour of qpdf)
  1195 +The options below modify the overall behaviour of qpdf. This includes modifying
  1196 +implementation limits and changing modes of operation.
  1197 +.PP
  1198 +Related Options:
  1199 +.TP
  1200 +.B --global \-\- begin setting global options and limits
  1201 +--global [options] --
  1202 +
  1203 +Begin setting global options and limits.
  1204 +.TP
  1205 +.B --no-default-limits \-\- disable optional default limits
  1206 +Disables all optional default limits. Explicitly set limits are unaffected. Some
  1207 +limits, especially limits designed to prevent stack overflow, cannot be removed
  1208 +with this option but can be modified. Where this is the case it is mentioned
  1209 +in the entry for the relevant option.
  1210 +.TP
  1211 +.B --parser-max-nesting \-\- set the maximum nesting level while parsing objects
  1212 +--parser-max-nesting=n
  1213 +
  1214 +Set the maximum nesting level while parsing objects. The maximum nesting level
  1215 +is not disabled by --no-default-limits. Defaults to 499.
  1216 +.TP
  1217 +.B --parser-max-errors \-\- set the maximum number of errors while parsing
  1218 +--parser-max-errors=n
  1219 +
  1220 +Set the maximum number of errors allowed while parsing an indirect object.
  1221 +A value of 0 means that no maximum is imposed. Defaults to 15.
  1222 +.TP
  1223 +.B --parser-max-container-size \-\- set the maximum container size while parsing
  1224 +--parser-max-container-size=n
  1225 +
  1226 +Set the maximum number of top-level objects allowed in a container while
  1227 +parsing. The limit applies when the PDF document's xref table is undamaged
  1228 +and the object itself can be parsed without errors. The default limit
  1229 +is 4,294,967,295. See also --parser-max-container-size-damaged.
  1230 +.TP
  1231 +.B --parser-max-container-size-damaged \-\- set the maximum container size while parsing damaged files
  1232 +--parser-max-container-size-damaged=n
  1233 +
  1234 +Set the maximum number of top-level objects allowed in a container while
  1235 +parsing. The limit applies when the PDF document's xref table is damaged
  1236 +or the object itself is damaged. The limit also applies when parsing
  1237 +xref streams. The default limit is 5,000.
  1238 +See also --parser-max-container-size.
1194 .SH TESTING (options for testing or debugging) 1239 .SH TESTING (options for testing or debugging)
1195 The options below are useful when writing automated test code that 1240 The options below are useful when writing automated test code that
1196 includes files created by qpdf or when testing qpdf itself. 1241 includes files created by qpdf or when testing qpdf itself.
manual/release-notes.rst
@@ -33,7 +33,7 @@ more detail. @@ -33,7 +33,7 @@ more detail.
33 33
34 - Bug fixes 34 - Bug fixes
35 35
36 - - Set `is_different` flag in `QPDFFormFieldObjectHelper::getTopLevelField` to 36 + - Set ``is_different`` flag in ``QPDFFormFieldObjectHelper::getTopLevelField`` to
37 false if the field is a top-level field. Previously the flag was only set 37 false if the field is a top-level field. Previously the flag was only set
38 if the field is a top-level field. 38 if the field is a top-level field.
39 39
@@ -58,7 +58,15 @@ more detail. @@ -58,7 +58,15 @@ more detail.
58 - Add new ``Buffer`` methods ``move``, ``view``, ``data``, ``size`` and 58 - Add new ``Buffer`` methods ``move``, ``view``, ``data``, ``size`` and
59 ``empty``. The new methods present the ``Buffer`` as a ``char`` (rather 59 ``empty``. The new methods present the ``Buffer`` as a ``char`` (rather
60 than ``unsigned char``) container and facilitate the efficient moving 60 than ``unsigned char``) container and facilitate the efficient moving
61 - of its content into a `std::string``. 61 + of its content into a ``std::string``.
  62 +
  63 + - Add various new functions in the ``qpdf::`global`` namespace to access
  64 + and set/modify global settings and limits. See :ref:`global-options`
  65 + and header file ``qpdf/global.hh`` for further detail.
  66 +
  67 + - Add new C-API functions ``qpdf_global_get_uint32`` and
  68 + ``qpdf_global_set_uint32`` to access and set/modify various global
  69 + settings and limits.
62 70
63 - Build fixes 71 - Build fixes
64 72
@@ -74,6 +82,9 @@ more detail. @@ -74,6 +82,9 @@ more detail.
74 - Option :qpdf:ref:`--check` now includes additional basic checks of the 82 - Option :qpdf:ref:`--check` now includes additional basic checks of the
75 AcroForm, Dests, Outlines, and PageLabels structures. 83 AcroForm, Dests, Outlines, and PageLabels structures.
76 84
  85 + - Add new option :qpdf:ref:`--global` to set or modify various global
  86 + options and limits. See :ref:`global-options` for further detail.
  87 +
77 - Fix completion scripts and handling to avoid leaking arguments 88 - Fix completion scripts and handling to avoid leaking arguments
78 into the environment during completion and to correctly handle 89 into the environment during completion and to correctly handle
79 ``bashcompinit`` for zsh users. 90 ``bashcompinit`` for zsh users.
qpdf/qtest/arg-parsing.test
@@ -15,7 +15,7 @@ cleanup(); @@ -15,7 +15,7 @@ cleanup();
15 15
16 my $td = new TestDriver('arg-parsing'); 16 my $td = new TestDriver('arg-parsing');
17 17
18 -my $n_tests = 32; 18 +my $n_tests = 33;
19 19
20 $td->runtest("required argument", 20 $td->runtest("required argument",
21 {$td->COMMAND => "qpdf --password minimal.pdf"}, 21 {$td->COMMAND => "qpdf --password minimal.pdf"},
@@ -187,5 +187,12 @@ $td-&gt;runtest(&quot;bad jpeg-quality&quot;, @@ -187,5 +187,12 @@ $td-&gt;runtest(&quot;bad jpeg-quality&quot;,
187 $td->EXIT_STATUS => 2}, 187 $td->EXIT_STATUS => 2},
188 $td->NORMALIZE_NEWLINES); 188 $td->NORMALIZE_NEWLINES);
189 189
  190 +$td->runtest("bad objects-container-max-container-size",
  191 + {$td->COMMAND => "qpdf --global --parser-max-container-size=-1 -- minimal.pdf"},
  192 + {$td->REGEXP =>
  193 + "invalid parser-max-container-size: must be a number between 0 and 4294967295",
  194 + $td->EXIT_STATUS => 2},
  195 + $td->NORMALIZE_NEWLINES);
  196 +
190 cleanup(); 197 cleanup();
191 $td->report($n_tests); 198 $td->report($n_tests);
qpdf/qtest/global.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +use File::Copy;
  6 +
  7 +unshift(@INC, '.');
  8 +require qpdf_test_helpers;
  9 +
  10 +chdir("qpdf") or die "chdir testdir failed: $!\n";
  11 +
  12 +require TestDriver;
  13 +
  14 +cleanup();
  15 +
  16 +my $td = new TestDriver('global');
  17 +
  18 +my $n_tests = 4;
  19 +
  20 +$td->runtest("parser-max-nesting",
  21 + {$td->COMMAND => "qpdf --global --parser-max-nesting=4 -- global.pdf a.pdf"},
  22 + {$td->FILE => "global1.out",
  23 + $td->EXIT_STATUS => 3},
  24 + $td->NORMALIZE_NEWLINES);
  25 +
  26 +$td->runtest("parser-max-errors",
  27 + {$td->COMMAND => "qpdf --global --parser-max-errors=2 -- global_damaged.pdf a.pdf"},
  28 + {$td->FILE => "global2.out",
  29 + $td->EXIT_STATUS => 3},
  30 + $td->NORMALIZE_NEWLINES);
  31 +
  32 +$td->runtest("parser-max-container-size",
  33 + {$td->COMMAND => "qpdf --global --parser-max-container-size=3 -- global.pdf a.pdf"},
  34 + {$td->FILE => "global3.out",
  35 + $td->EXIT_STATUS => 3},
  36 + $td->NORMALIZE_NEWLINES);
  37 +
  38 +$td->runtest("parser-max-container-size-damaged",
  39 + {$td->COMMAND => "qpdf --global --parser-max-container-size-damaged=9 -- global_damaged.pdf a.pdf"},
  40 + {$td->FILE => "global4.out",
  41 + $td->EXIT_STATUS => 3},
  42 + $td->NORMALIZE_NEWLINES);
  43 +
  44 +cleanup();
  45 +$td->report($n_tests);
qpdf/qtest/qpdf/global.pdf 0 → 100644
  1 +%PDF-1.3
  2 +1 0 obj
  3 +<<
  4 + /Type /Catalog
  5 + /Pages 2 0 R
  6 +>>
  7 +endobj
  8 +
  9 +2 0 obj
  10 +<<
  11 + /Type /Pages
  12 + /Kids [
  13 + 3 0 R
  14 + ]
  15 + /Count 1
  16 +>>
  17 +endobj
  18 +
  19 +3 0 obj
  20 +<<
  21 + /Type /Page
  22 + /Parent 2 0 R
  23 + /MediaBox [0 0 612 792]
  24 + /Contents 4 0 R
  25 + /Resources <<
  26 + /ProcSet 5 0 R
  27 + /Font <<
  28 + /F1 6 0 R
  29 + >>
  30 + >>
  31 +>>
  32 +endobj
  33 +
  34 +4 0 obj
  35 +<<
  36 + /Length 44
  37 +>>
  38 +stream
  39 +BT
  40 + /F1 24 Tf
  41 + 72 720 Td
  42 + (Potato) Tj
  43 +ET
  44 +endstream
  45 +endobj
  46 +
  47 +5 0 obj
  48 +[
  49 + /PDF
  50 + /Text
  51 +]
  52 +endobj
  53 +
  54 +6 0 obj
  55 +<<
  56 + /Type /Font
  57 + /Subtype /Type1
  58 + /Name /F1
  59 + /BaseFont /Helvetica
  60 + /Encoding /WinAnsiEncoding
  61 +>>
  62 +endobj
  63 +
  64 +xref
  65 +0 7
  66 +0000000000 65535 f
  67 +0000000009 00000 n
  68 +0000000063 00000 n
  69 +0000000135 00000 n
  70 +0000000307 00000 n
  71 +0000000403 00000 n
  72 +0000000438 00000 n
  73 +trailer <<
  74 + /Size 7
  75 + /Root 1 0 R
  76 + /Nesting [ [ [ [ [ /1 /2 /3 /4 /5 /6 /7 /8 /9 /10 ] ] ] ] ]
  77 +>>
  78 +startxref
  79 +556
  80 +%%EOF
qpdf/qtest/qpdf/global1.out 0 → 100644
  1 +WARNING: global.pdf (trailer, offset 759): limits error(parser-max-nesting): ignoring excessively deeply nested data structure
  2 +WARNING: global.pdf: file is damaged
  3 +WARNING: global.pdf (offset 712): expected trailer dictionary
  4 +WARNING: global.pdf: Attempting to reconstruct cross-reference table
  5 +WARNING: global.pdf (trailer, offset 759): limits error(parser-max-nesting): ignoring excessively deeply nested data structure
  6 +WARNING: global.pdf: unable to find trailer dictionary while recovering damaged file
  7 +qpdf: operation succeeded with warnings; resulting file may have some problems
  8 +qpdf: some configurable limits were exceeded; for more details see https://qpdf.readthedocs.io/en/stable/cli.html#global-limits
qpdf/qtest/qpdf/global2.out 0 → 100644
  1 +WARNING: global_damaged.pdf: file is damaged
  2 +WARNING: global_damaged.pdf (offset 55): xref not found
  3 +WARNING: global_damaged.pdf: Attempting to reconstruct cross-reference table
  4 +WARNING: global_damaged.pdf (trailer, offset 764): unknown token while reading object; treating as null
  5 +WARNING: global_damaged.pdf (trailer, offset 767): unknown token while reading object; treating as null
  6 +WARNING: global_damaged.pdf (trailer, offset 767): limits error(parser-max-errors): too many errors during parsing; treating object as null
  7 +WARNING: global_damaged.pdf: unable to find trailer dictionary while recovering damaged file
  8 +qpdf: operation succeeded with warnings; resulting file may have some problems
  9 +qpdf: some configurable limits were exceeded; for more details see https://qpdf.readthedocs.io/en/stable/cli.html#global-limits
qpdf/qtest/qpdf/global3.out 0 → 100644
  1 +WARNING: global.pdf (trailer, offset 770): limits error(parser-max-container-size): encountered an array or dictionary with more than 3 elements during xref recovery; giving up on reading object
  2 +WARNING: global.pdf: file is damaged
  3 +WARNING: global.pdf (offset 712): expected trailer dictionary
  4 +WARNING: global.pdf: Attempting to reconstruct cross-reference table
  5 +qpdf: operation succeeded with warnings; resulting file may have some problems
  6 +qpdf: some configurable limits were exceeded; for more details see https://qpdf.readthedocs.io/en/stable/cli.html#global-limits
qpdf/qtest/qpdf/global4.out 0 → 100644
  1 +WARNING: global_damaged.pdf: file is damaged
  2 +WARNING: global_damaged.pdf (offset 55): xref not found
  3 +WARNING: global_damaged.pdf: Attempting to reconstruct cross-reference table
  4 +WARNING: global_damaged.pdf (trailer, offset 764): unknown token while reading object; treating as null
  5 +WARNING: global_damaged.pdf (trailer, offset 767): unknown token while reading object; treating as null
  6 +WARNING: global_damaged.pdf (trailer, offset 770): unknown token while reading object; treating as null
  7 +WARNING: global_damaged.pdf (trailer, offset 788): limits error(parser-max-container-size-damaged): encountered errors while parsing an array or dictionary with more than 9 elements; giving up on reading object
  8 +WARNING: global_damaged.pdf: unable to find trailer dictionary while recovering damaged file
  9 +qpdf: operation succeeded with warnings; resulting file may have some problems
  10 +qpdf: some configurable limits were exceeded; for more details see https://qpdf.readthedocs.io/en/stable/cli.html#global-limits
qpdf/qtest/qpdf/global_damaged.pdf 0 → 100644
  1 +%PDF-1.3
  2 +1 0 obj
  3 +<<
  4 + /Type /Catalog
  5 + /Pages 2 0 R
  6 +>>
  7 +endobj
  8 +
  9 +2 0 obj
  10 +<<
  11 + /Type /Pages
  12 + /Kids [
  13 + 3 0 R
  14 + ]
  15 + /Count 1
  16 +>>
  17 +endobj
  18 +
  19 +3 0 obj
  20 +<<
  21 + /Type /Page
  22 + /Parent 2 0 R
  23 + /MediaBox [0 0 612 792]
  24 + /Contents 4 0 R
  25 + /Resources <<
  26 + /ProcSet 5 0 R
  27 + /Font <<
  28 + /F1 6 0 R
  29 + >>
  30 + >>
  31 +>>
  32 +endobj
  33 +
  34 +4 0 obj
  35 +<<
  36 + /Length 44
  37 +>>
  38 +stream
  39 +BT
  40 + /F1 24 Tf
  41 + 72 720 Td
  42 + (Potato) Tj
  43 +ET
  44 +endstream
  45 +endobj
  46 +
  47 +5 0 obj
  48 +[
  49 + /PDF
  50 + /Text
  51 +]
  52 +endobj
  53 +
  54 +6 0 obj
  55 +<<
  56 + /Type /Font
  57 + /Subtype /Type1
  58 + /Name /F1
  59 + /BaseFont /Helvetica
  60 + /Encoding /WinAnsiEncoding
  61 +>>
  62 +endobj
  63 +
  64 +xref
  65 +0 7
  66 +0000000000 65535 f
  67 +0000000009 00000 n
  68 +0000000063 00000 n
  69 +0000000135 00000 n
  70 +0000000307 00000 n
  71 +0000000403 00000 n
  72 +0000000438 00000 n
  73 +trailer <<
  74 + /Size 7
  75 + /Root 1 0 R
  76 + /Nesting [ [ [ [ [ /1 !2 !3 !4 /5 /6 /7 /8 /9 /10 ] ] ] ] ]
  77 +>>
  78 +startxref
  79 +55
  80 +%%EOF
qpdf/qtest/qpdf/issue-146.out
1 WARNING: issue-146.pdf: file is damaged 1 WARNING: issue-146.pdf: file is damaged
2 WARNING: issue-146.pdf: can't find startxref 2 WARNING: issue-146.pdf: can't find startxref
3 WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table 3 WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table
4 -WARNING: issue-146.pdf (trailer, offset 695): ignoring excessively deeply nested data structure 4 +WARNING: issue-146.pdf (trailer, offset 695): limits error(parser-max-nesting): ignoring excessively deeply nested data structure
5 WARNING: issue-146.pdf (object 1 0, offset 92): expected endobj 5 WARNING: issue-146.pdf (object 1 0, offset 92): expected endobj
6 WARNING: issue-146.pdf (object 7 0, offset 146): unknown token while reading object; treating as null 6 WARNING: issue-146.pdf (object 7 0, offset 146): unknown token while reading object; treating as null
7 WARNING: issue-146.pdf (object 7 0, offset 168): expected endobj 7 WARNING: issue-146.pdf (object 7 0, offset 168): expected endobj
qpdf/qtest/qpdf/issue-202.out
1 -WARNING: issue-202.pdf (trailer, offset 55770): ignoring excessively deeply nested data structure 1 +WARNING: issue-202.pdf (trailer, offset 55770): limits error(parser-max-nesting): ignoring excessively deeply nested data structure
2 WARNING: issue-202.pdf: file is damaged 2 WARNING: issue-202.pdf: file is damaged
3 WARNING: issue-202.pdf (offset 54769): expected trailer dictionary 3 WARNING: issue-202.pdf (offset 54769): expected trailer dictionary
4 WARNING: issue-202.pdf: Attempting to reconstruct cross-reference table 4 WARNING: issue-202.pdf: Attempting to reconstruct cross-reference table
5 -WARNING: issue-202.pdf (trailer, offset 55770): ignoring excessively deeply nested data structure 5 +WARNING: issue-202.pdf (trailer, offset 55770): limits error(parser-max-nesting): ignoring excessively deeply nested data structure
6 WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Creator; last occurrence overrides earlier ones 6 WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Creator; last occurrence overrides earlier ones
7 WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Producer; last occurrence overrides earlier ones 7 WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Producer; last occurrence overrides earlier ones
8 WARNING: issue-202.pdf: unable to find trailer dictionary while recovering damaged file 8 WARNING: issue-202.pdf: unable to find trailer dictionary while recovering damaged file
9 qpdf: operation succeeded with warnings; resulting file may have some problems 9 qpdf: operation succeeded with warnings; resulting file may have some problems
  10 +qpdf: some configurable limits were exceeded; for more details see https://qpdf.readthedocs.io/en/stable/cli.html#global-limits