Commit b1dad0de2a12a0feb0608d1f68f3f1e8144592e6

Authored by Jay Berkenbilt
1 parent b1b789df

Fix previous fix to setting checkbox value (fixes #1056)

The code accepted values other than /Yes but still used /Yes as the
checked value instead of obeying the normal appearance dictionary.
ChangeLog
  1 +2024-02-11 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * The previous fix to #1056 was incomplete. When setting a check
  4 + box value, the previous fix allowed any value other than /Off to
  5 + mean checked. Now we also set the actual value based on the
  6 + allowable non-/Off value in the normal appearance dictionary.
  7 + Fixes #1056.
  8 +
1 9 2024-02-03 Jay Berkenbilt <ejb@ql.org>
2 10  
3 11 * Add fuzz testing for JSON.
... ...
include/qpdf/QPDFFormFieldObjectHelper.hh
... ... @@ -166,7 +166,9 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper
166 166 // either /Tx (text) or /Ch (choice), set /NeedAppearances to true. You can explicitly tell this
167 167 // method not to set /NeedAppearances if you are going to generate an appearance stream
168 168 // yourself. Starting with qpdf 8.3.0, this method handles fields of type /Btn (checkboxes,
169   - // radio buttons, pushbuttons) specially.
  169 + // radio buttons, pushbuttons) specially. When setting a checkbox value, any value other than
  170 + // /Off will be treated as on, and the actual value set will be based on the appearance stream's
  171 + // /N dictionary, so the value that ends up in /V may not exactly match the value you pass in.
170 172 QPDF_DLL
171 173 void setV(QPDFObjectHandle value, bool need_appearances = true);
172 174  
... ...
libqpdf/QPDFFormFieldObjectHelper.cc
... ... @@ -310,8 +310,8 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances)
310 310 setCheckBoxValue((name != "/Off"));
311 311 }
312 312 if (!okay) {
313   - this->oh.warnIfPossible("ignoring attempt to set a checkbox field to a value of "
314   - "other than /Yes or /Off");
  313 + this->oh.warnIfPossible(
  314 + "ignoring attempt to set a checkbox field to a value whose type is not name");
315 315 }
316 316 } else if (isRadioButton()) {
317 317 if (value.isName()) {
... ... @@ -415,9 +415,6 @@ QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name)
415 415 void
416 416 QPDFFormFieldObjectHelper::setCheckBoxValue(bool value)
417 417 {
418   - // Set /AS to /Yes or /Off in addition to setting /V.
419   - QPDFObjectHandle name = QPDFObjectHandle::newName(value ? "/Yes" : "/Off");
420   - setFieldAttribute("/V", name);
421 418 QPDFObjectHandle AP = this->oh.getKey("/AP");
422 419 QPDFObjectHandle annot;
423 420 if (AP.isNull()) {
... ... @@ -439,6 +436,29 @@ QPDFFormFieldObjectHelper::setCheckBoxValue(bool value)
439 436 } else {
440 437 annot = this->oh;
441 438 }
  439 + std::string on_value;
  440 + if (value) {
  441 + // Set the "on" value to the first value in the appearance stream's normal state dictionary
  442 + // that isn't /Off. If not found, fall back to /Yes.
  443 + if (AP.isDictionary()) {
  444 + auto N = AP.getKey("/N");
  445 + if (N.isDictionary()) {
  446 + for (auto const& iter: N.ditems()) {
  447 + if (iter.first != "/Off") {
  448 + on_value = iter.first;
  449 + break;
  450 + }
  451 + }
  452 + }
  453 + }
  454 + if (on_value.empty()) {
  455 + on_value = "/Yes";
  456 + }
  457 + }
  458 +
  459 + // Set /AS to the on value or /Off in addition to setting /V.
  460 + QPDFObjectHandle name = QPDFObjectHandle::newName(value ? on_value : "/Off");
  461 + setFieldAttribute("/V", name);
442 462 if (!annot.isInitialized()) {
443 463 QTC::TC("qpdf", "QPDFObjectHandle broken checkbox");
444 464 this->oh.warnIfPossible("unable to set the value of this checkbox");
... ...
qpdf/qtest/qpdf/button-set-out.pdf
No preview for this file type
qpdf/qtest/qpdf/button-set.pdf
No preview for this file type
qpdf/test_driver.cc
... ... @@ -1942,7 +1942,9 @@ test_51(QPDF&amp; pdf, char const* arg2)
1942 1942 } else if (Tval == "checkbox1") {
1943 1943 std::cout << "turning checkbox1 on\n";
1944 1944 QPDFFormFieldObjectHelper foh(field);
1945   - foh.setV(QPDFObjectHandle::newName("/Yes"));
  1945 + // The value that eventually gets set is based on what's allowed in /N and may not match
  1946 + // this value.
  1947 + foh.setV(QPDFObjectHandle::newName("/Sure"));
1946 1948 } else if (Tval == "checkbox2") {
1947 1949 std::cout << "turning checkbox2 off\n";
1948 1950 QPDFFormFieldObjectHelper foh(field);
... ...