Commit 1d6da60048b3984565b09172cddb9f83949ccb45

Authored by m-holger
1 parent e2823965

Expose global settings in `QPDFJob` / the CLI

include/qpdf/QPDFJob.hh
... ... @@ -306,6 +306,24 @@ class QPDFJob
306 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 327 class Config
310 328 {
311 329 friend class QPDFJob;
... ... @@ -331,6 +349,8 @@ class QPDFJob
331 349 QPDF_DLL
332 350 std::shared_ptr<AttConfig> addAttachment();
333 351 QPDF_DLL
  352 + std::shared_ptr<GlobalConfig> global();
  353 + QPDF_DLL
334 354 std::shared_ptr<PagesConfig> pages();
335 355 QPDF_DLL
336 356 std::shared_ptr<UOConfig> overlay();
... ...
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
... ... @@ -145,7 +145,7 @@ namespace qpdf::global
145 145 set_uint32(qpdf_p_parser_max_errors, value);
146 146 }
147 147  
148   - /// @brief Retrieves the maximum number of objectstop-level allowed in a container while
  148 + /// @brief Retrieves the maximum number of top-level objects allowed in a container while
149 149 /// parsing.
150 150 ///
151 151 /// The limit applies when the PDF document's xref table is undamaged and the object itself
... ... @@ -160,7 +160,8 @@ namespace qpdf::global
160 160 return get_uint32(qpdf_p_parser_max_container_size);
161 161 }
162 162  
163   - /// @brief Sets the maximum number oftop-level objects allowed in a container while parsing.
  163 + /// @brief Sets the maximum number of top-level objects allowed in a container while
  164 + /// parsing.
164 165 ///
165 166 /// The limit applies when the PDF document's xref table is undamaged and the object itself
166 167 /// can be parsed without errors. The default limit is 4,294,967,295.
... ... @@ -178,8 +179,7 @@ namespace qpdf::global
178 179 /// parsing objects.
179 180 ///
180 181 /// The limit applies when the PDF document's xref table is damaged or the object itself is
181   - /// damaged. The limit also applies when parsing trailer dictionaries and xref streams. The
182   - /// default limit is 5,000.
  182 + /// damaged. The limit also applies when parsing xref streams. The default limit is 5,000.
183 183 ///
184 184 /// @return The maximum number of top-level objects allowed in a container while parsing
185 185 /// objects.
... ...
job.sums
... ... @@ -4,17 +4,18 @@ generate_auto_job 8e3175a515aa8837d8a01bba0346b04b3d777d70330ba5b7d52f691316054a
4 4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
5 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
6 6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5
  7 +include/qpdf/auto_job_c_global.hh f1dc365206d033a0d6b19b6e561cc244fbd5b49a8d9604b5b646a5fd92895a5a
7 8 include/qpdf/auto_job_c_main.hh b865eb827356554763bb8349eadfcbc5cb260f80e025a5e229467c525007356d
8 9 include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506
9 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 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 21 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b
... ...
... ... @@ -84,12 +84,24 @@ options:
84 84 - zopfli
85 85 optional_choices:
86 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 98 - table: main
88 99 config: c_main
89 100 manual:
90 101 - add-attachment
91 102 - copy-attachments-from
92 103 - encrypt
  104 + - global
93 105 - overlay
94 106 - pages
95 107 - underlay
... ... @@ -112,6 +124,7 @@ options:
112 124 - filtered-stream-data
113 125 - flatten-rotation
114 126 - generate-appearances
  127 + - global
115 128 - ignore-xref-streams
116 129 - is-encrypted
117 130 - json-input
... ... @@ -399,6 +412,13 @@ json:
399 412 - null
400 413 json-stream-data:
401 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 422 # other options
403 423 update-from-json:
404 424 allow-weak-crypto:
... ...
libqpdf/QPDFJob_argv.cc
... ... @@ -31,6 +31,7 @@ namespace
31 31 std::shared_ptr<QPDFJob::Config> c_main;
32 32 std::shared_ptr<QPDFJob::CopyAttConfig> c_copy_att;
33 33 std::shared_ptr<QPDFJob::AttConfig> c_att;
  34 + std::shared_ptr<QPDFJob::GlobalConfig> c_global;
34 35 std::shared_ptr<QPDFJob::PagesConfig> c_pages;
35 36 std::shared_ptr<QPDFJob::UOConfig> c_uo;
36 37 std::shared_ptr<QPDFJob::EncConfig> c_enc;
... ... @@ -418,6 +419,21 @@ ArgParser::argEndSetPageLabels()
418 419 }
419 420  
420 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 437 ArgParser::argJobJsonHelp()
422 438 {
423 439 *QPDFLogger::defaultLogger()->getInfo()
... ...
libqpdf/QPDFJob_config.cc
... ... @@ -4,31 +4,53 @@
4 4 #include <qpdf/QPDFUsage.hh>
5 5 #include <qpdf/QTC.hh>
6 6 #include <qpdf/QUtil.hh>
  7 +#include <qpdf/Util.hh>
  8 +#include <qpdf/global_private.hh>
7 9  
8 10 #include <concepts>
9 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 17 throw QPDFUsage(
15 18 "invalid "s.append(option) + ": must be a number between " + std::to_string(min) + " and " +
16 19 std::to_string(max));
17 20 }
18 21  
19 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 26 try {
24   - result = std::stoi(value);
  27 + int result = std::stoi(value);
25 28 if (result < min || result > max) {
26   - int_usage(option, min, max);
  29 + int_usage(option, max, min);
27 30 }
  31 + return result;
28 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 56 void
... ... @@ -156,14 +178,14 @@ QPDFJob::Config::compressStreams(std::string const&amp; parameter)
156 178 QPDFJob::Config*
157 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 182 return this;
161 183 }
162 184  
163 185 QPDFJob::Config*
164 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 189 return this;
168 190 }
169 191  
... ... @@ -1140,6 +1162,60 @@ QPDFJob::Config::encrypt(
1140 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 1219 QPDFJob::Config*
1144 1220 QPDFJob::Config::setPageLabels(const std::vector<std::string>& specs)
1145 1221 {
... ...
libqpdf/QPDFJob_json.cc
... ... @@ -63,6 +63,7 @@ namespace
63 63 std::shared_ptr<QPDFJob::Config> c_main;
64 64 std::shared_ptr<QPDFJob::CopyAttConfig> c_copy_att;
65 65 std::shared_ptr<QPDFJob::AttConfig> c_att;
  66 + std::shared_ptr<QPDFJob::GlobalConfig> c_global;
66 67 std::shared_ptr<QPDFJob::PagesConfig> c_pages;
67 68 std::shared_ptr<QPDFJob::UOConfig> c_uo;
68 69 std::shared_ptr<QPDFJob::EncConfig> c_enc;
... ... @@ -620,6 +621,19 @@ Handlers::beginSetPageLabelsArray(JSON)
620 621 }
621 622  
622 623 void
  624 +Handlers::beginGlobal(JSON)
  625 +{
  626 + this->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 637 QPDFJob::initializeFromJson(std::string const& json, bool partial)
624 638 {
625 639 std::list<std::string> errors;
... ...
libqpdf/qpdf/auto_job_decl.hh
... ... @@ -5,6 +5,7 @@
5 5 //
6 6 // clang-format off
7 7 //
  8 +static constexpr char const* O_GLOBAL = "global";
8 9 static constexpr char const* O_PAGES = "pages";
9 10 static constexpr char const* O_ENCRYPTION = "encryption";
10 11 static constexpr char const* O_40_BIT_ENCRYPTION = "40-bit encryption";
... ... @@ -21,11 +22,13 @@ void argShowCrypto();
21 22 void argJobJsonHelp();
22 23 void argZopfli();
23 24 void argJsonHelp(std::string const&);
  25 +void argEndGlobal();
24 26 void argPositional(std::string const&);
25 27 void argAddAttachment();
26 28 void argCopyAttachmentsFrom();
27 29 void argEmpty();
28 30 void argEncrypt();
  31 +void argGlobal();
29 32 void argOverlay();
30 33 void argPages();
31 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 1004 chapter of the manual for information about how to use this
1005 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 1047 ap.addHelpTopic("testing", "options for testing or debugging", R"(The options below are useful when writing automated test code that
1008 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 1079 add_help_6(ap);
1040 1080 add_help_7(ap);
1041 1081 add_help_8(ap);
  1082 + add_help_9(ap);
1042 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 34 this->ap.addBare("job-json-help", b(&ArgParser::argJobJsonHelp));
35 35 this->ap.addBare("zopfli", b(&ArgParser::argZopfli));
36 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 43 this->ap.selectMainOptionTable();
38 44 this->ap.addPositional(p(&ArgParser::argPositional));
39 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 56 this->ap.addBare("filtered-stream-data", [this](){c_main->filteredStreamData();});
51 57 this->ap.addBare("flatten-rotation", [this](){c_main->flattenRotation();});
52 58 this->ap.addBare("generate-appearances", [this](){c_main->generateAppearances();});
  59 +this->ap.addBare("global", b(&ArgParser::argGlobal));
53 60 this->ap.addBare("ignore-xref-streams", [this](){c_main->ignoreXrefStreams();});
54 61 this->ap.addBare("is-encrypted", [this](){c_main->isEncrypted();});
55 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 24 void endJsonKeyArray();
25 25 void beginJsonObjectArray(JSON);
26 26 void endJsonObjectArray();
  27 +void beginGlobal(JSON);
  28 +void endGlobal();
27 29 void beginAddAttachmentArray(JSON);
28 30 void endAddAttachmentArray();
29 31 void beginAddAttachment(JSON);
... ...
libqpdf/qpdf/auto_job_json_init.hh
... ... @@ -272,6 +272,24 @@ popHandler(); // key: jsonStreamData
272 272 pushKey("jsonStreamPrefix");
273 273 addParameter([this](std::string const& p) { c_main->jsonStreamPrefix(p); });
274 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 293 pushKey("updateFromJson");
276 294 addParameter([this](std::string const& p) { c_main->updateFromJson(p); });
277 295 popHandler(); // key: updateFromJson
... ...
libqpdf/qpdf/auto_job_schema.hh
... ... @@ -90,6 +90,13 @@ static constexpr char const* JOB_SCHEMA_DATA = R&quot;({
90 90 ],
91 91 "jsonStreamData": "how to handle streams in json output",
92 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 100 "updateFromJson": "update a PDF from qpdf JSON",
94 101 "allowWeakCrypto": "allow insecure cryptographic algorithms",
95 102 "keepFilesOpen": "manage keeping multiple files open",
... ...
libtests/objects.cc
... ... @@ -5,6 +5,7 @@
5 5 #include <qpdf/QPDF.hh>
6 6  
7 7 #include <qpdf/QIntC.hh>
  8 +#include <qpdf/QPDFJob.hh>
8 9 #include <qpdf/QPDFObjectHandle_private.hh>
9 10 #include <qpdf/QUtil.hh>
10 11 #include <qpdf/global.hh>
... ... @@ -237,6 +238,47 @@ test_2(QPDF&amp; pdf, char const* arg2)
237 238 } catch (std::exception&) {
238 239 }
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());
240 282 }
241 283  
242 284 void
... ...
manual/cli.rst
... ... @@ -3762,6 +3762,110 @@ Related Options
3762 3762 For a information about how to use this option, please see
3763 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 3869 .. _test-options:
3766 3870  
3767 3871 Options for Testing or Debugging
... ...
manual/qpdf.1
... ... @@ -1191,6 +1191,51 @@ how to use this option.
1191 1191 Update a PDF file from a JSON file. Please see the "qpdf JSON"
1192 1192 chapter of the manual for information about how to use this
1193 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 1239 .SH TESTING (options for testing or debugging)
1195 1240 The options below are useful when writing automated test code that
1196 1241 includes files created by qpdf or when testing qpdf itself.
... ...
qpdf/qtest/arg-parsing.test
... ... @@ -15,7 +15,7 @@ cleanup();
15 15  
16 16 my $td = new TestDriver('arg-parsing');
17 17  
18   -my $n_tests = 32;
  18 +my $n_tests = 33;
19 19  
20 20 $td->runtest("required argument",
21 21 {$td->COMMAND => "qpdf --password minimal.pdf"},
... ... @@ -187,5 +187,12 @@ $td-&gt;runtest(&quot;bad jpeg-quality&quot;,
187 187 $td->EXIT_STATUS => 2},
188 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 197 cleanup();
191 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
... ...
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
... ...
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
... ...
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
... ...
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
... ...