Change implementation of CPDF_FormField::IsItemSelected()
The PDF spec indicates that the /V entry of a choice field should take
precendence over the /I entry for identifying the selected options.
Additionally, the /I entry was only introduced in PDF 1.4, meaning that
there can exist PDF choice fields that contain an /V entry and no /I
entry, which is not correctly handled by the current implementation of
the method.
Bug: pdfium:1505
Change-Id: I7646e2f6d69e581ea68c55da11a24b26e26ee45d
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/68475
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfdoc/cpdf_formfield.cpp b/core/fpdfdoc/cpdf_formfield.cpp
index 3e33c79..2d18612 100644
--- a/core/fpdfdoc/cpdf_formfield.cpp
+++ b/core/fpdfdoc/cpdf_formfield.cpp
@@ -6,6 +6,7 @@
#include "core/fpdfdoc/cpdf_formfield.h"
+#include <map>
#include <memory>
#include <set>
#include <utility>
@@ -133,6 +134,7 @@
m_Type = kListBox;
m_bIsMultiSelectListBox = flags & pdfium::form_flags::kChoiceMultiSelect;
}
+ m_bUseSelectedIndices = UseSelectedIndicesObject();
LoadDA();
} else if (type_name == pdfium::form_fields::kSig) {
m_Type = kSign;
@@ -476,34 +478,10 @@
ASSERT(GetType() == kComboBox || GetType() == kListBox);
if (index < 0 || index >= CountOptions())
return false;
- if (IsOptionSelected(index))
- return true;
- WideString opt_value = GetOptionValue(index);
- const CPDF_Object* pValue = GetValueOrSelectedIndicesObject();
- if (!pValue)
- return false;
-
- if (pValue->IsString())
- return pValue->GetUnicodeText() == opt_value;
-
- if (pValue->IsNumber()) {
- if (pValue->GetString().IsEmpty())
- return false;
- return (pValue->GetInteger() == index);
- }
-
- const CPDF_Array* pArray = pValue->AsArray();
- if (!pArray)
- return false;
-
- for (int i = 0; i < CountSelectedOptions(); ++i) {
- if (GetSelectedOptionIndex(i) == index) {
- const CPDF_Object* pDirectObj = pArray->GetDirectObjectAt(i);
- return pDirectObj && pDirectObj->GetUnicodeText() == opt_value;
- }
- }
- return false;
+ // First consider the /I entry if it is valid, then fall back to the /V entry.
+ return m_bUseSelectedIndices ? IsSelectedIndex(index)
+ : IsSelectedOption(GetOptionValue(index));
}
bool CPDF_FormField::SetItemSelection(int index,
@@ -524,6 +502,11 @@
else
SetItemSelectionUnselected(index, opt_value);
+ // UseSelectedIndicesObject() has a non-trivial linearithmic run-time, so run
+ // only if necessary.
+ if (!m_bUseSelectedIndices)
+ m_bUseSelectedIndices = UseSelectedIndicesObject();
+
if (notify == NotificationOption::kNotify)
NotifyListOrComboBoxAfterChange();
return true;
@@ -755,17 +738,40 @@
return pArray->GetIntegerAt(index);
}
-bool CPDF_FormField::IsOptionSelected(int iOptIndex) const {
- const CPDF_Array* pArray = ToArray(GetSelectedIndicesObject());
- if (!pArray)
+bool CPDF_FormField::IsSelectedOption(const WideString& wsOptValue) const {
+ const CPDF_Object* pValueObject = GetValueObject();
+ if (!pValueObject)
return false;
- CPDF_ArrayLocker locker(pArray);
- for (const auto& pObj : locker) {
- if (pObj->GetInteger() == iOptIndex)
- return true;
+ const CPDF_Array* pValueArray = pValueObject->AsArray();
+ if (pValueArray) {
+ CPDF_ArrayLocker locker(pValueArray);
+ for (const auto& pObj : locker) {
+ if (pObj->IsString() && pObj->GetUnicodeText() == wsOptValue)
+ return true;
+ }
}
- return false;
+
+ return pValueObject->IsString() &&
+ pValueObject->GetUnicodeText() == wsOptValue;
+}
+
+bool CPDF_FormField::IsSelectedIndex(int iOptIndex) const {
+ const CPDF_Object* pSelectedIndicesObject = GetSelectedIndicesObject();
+ if (!pSelectedIndicesObject)
+ return false;
+
+ const CPDF_Array* pSelectedIndicesArray = pSelectedIndicesObject->AsArray();
+ if (pSelectedIndicesArray) {
+ CPDF_ArrayLocker locker(pSelectedIndicesArray);
+ for (const auto& pObj : locker) {
+ if (pObj->IsNumber() && pObj->GetInteger() == iOptIndex)
+ return true;
+ }
+ }
+
+ return pSelectedIndicesObject->IsNumber() &&
+ pSelectedIndicesObject->GetInteger() == iOptIndex;
}
bool CPDF_FormField::SelectOption(int iOptIndex,
@@ -822,6 +828,83 @@
return true;
}
+bool CPDF_FormField::UseSelectedIndicesObject() const {
+ ASSERT(GetType() == kComboBox || GetType() == kListBox);
+
+ const CPDF_Object* pSelectedIndicesObject = GetSelectedIndicesObject();
+ if (!pSelectedIndicesObject)
+ return false;
+
+ // If there's not value object, then just use the indices object.
+ const CPDF_Object* pValueObject = GetValueObject();
+ if (!pValueObject)
+ return true;
+
+ // Verify that the selected indices object is either an array or a number and
+ // count the number of indices.
+ size_t selected_indices_size;
+ const CPDF_Array* pSelectedIndicesArray = pSelectedIndicesObject->AsArray();
+ if (pSelectedIndicesArray)
+ selected_indices_size = pSelectedIndicesArray->size();
+ else if (pSelectedIndicesObject->IsNumber())
+ selected_indices_size = 1;
+ else
+ return false;
+
+ // Verify that the number of values is equal to |selected_indices_size|. Then,
+ // count the number of occurances of each of the distinct values in the values
+ // object.
+ std::map<WideString, size_t> values;
+ const CPDF_Array* pValueArray = pValueObject->AsArray();
+ if (pValueArray) {
+ if (pValueArray->size() != selected_indices_size)
+ return false;
+ CPDF_ArrayLocker locker(pValueArray);
+ for (const auto& pObj : locker) {
+ if (pObj->IsString())
+ values[pObj->GetUnicodeText()]++;
+ }
+ } else if (pValueObject->IsString()) {
+ if (selected_indices_size != 1)
+ return false;
+ values[pValueObject->GetUnicodeText()]++;
+ }
+
+ // Validate each index in the selected indices object. Then, verify that items
+ // identified by selected indices entry do not differ from those in the values
+ // entry of the field dictionary.
+ const int num_options = CountOptions();
+ if (pSelectedIndicesArray) {
+ CPDF_ArrayLocker locker(pSelectedIndicesArray);
+ for (const auto& pObj : locker) {
+ if (!pObj->IsNumber())
+ return false;
+
+ int index = pObj->GetInteger();
+ if (index < 0 || index >= num_options)
+ return false;
+
+ WideString wsOptValue = GetOptionValue(index);
+ auto it = values.find(wsOptValue);
+ if (it == values.end())
+ return false;
+
+ it->second--;
+ if (it->second == 0)
+ values.erase(it);
+ }
+
+ return values.empty();
+ }
+
+ ASSERT(pSelectedIndicesObject->IsNumber());
+ int index = pSelectedIndicesObject->GetInteger();
+ if (index < 0 || index >= num_options)
+ return false;
+
+ return pdfium::ContainsKey(values, GetOptionValue(index));
+}
+
void CPDF_FormField::LoadDA() {
CPDF_Dictionary* pFormDict = m_pForm->GetFormDict();
if (!pFormDict)
diff --git a/core/fpdfdoc/cpdf_formfield.h b/core/fpdfdoc/cpdf_formfield.h
index aa51ddc..826f8ec 100644
--- a/core/fpdfdoc/cpdf_formfield.h
+++ b/core/fpdfdoc/cpdf_formfield.h
@@ -130,9 +130,14 @@
int GetTopVisibleIndex() const;
int CountSelectedOptions() const;
int GetSelectedOptionIndex(int index) const;
- bool IsOptionSelected(int iOptIndex) const;
+ bool IsSelectedOption(const WideString& wsOptValue) const;
+ bool IsSelectedIndex(int iOptIndex) const;
bool SelectOption(int iOptIndex, bool bSelected, NotificationOption notify);
+ // Verifies if there is a valid selected indicies (/I) object and whether its
+ // entries are consistent with the value (/V) object.
+ bool UseSelectedIndicesObject() const;
+
float GetFontSize() const { return m_FontSize; }
CPDF_Font* GetFont() const { return m_pFont.Get(); }
@@ -181,6 +186,7 @@
bool m_bNoExport = false;
bool m_bIsMultiSelectListBox = false;
bool m_bIsUnison = false;
+ bool m_bUseSelectedIndices = false;
float m_FontSize = 0;
UnownedPtr<CPDF_InteractiveForm> const m_pForm;
RetainPtr<CPDF_Dictionary> const m_pDict;
diff --git a/core/fpdfdoc/cpdf_formfield_unittest.cpp b/core/fpdfdoc/cpdf_formfield_unittest.cpp
index 2174efb..63346e0 100644
--- a/core/fpdfdoc/cpdf_formfield_unittest.cpp
+++ b/core/fpdfdoc/cpdf_formfield_unittest.cpp
@@ -43,6 +43,7 @@
void TestMultiselectFieldDict(RetainPtr<CPDF_Array> opt_array,
RetainPtr<CPDF_Object> values,
RetainPtr<CPDF_Object> selected_indices,
+ bool expected_use_indices,
const std::vector<int>& expected_indices,
const std::vector<int>& excluded_indices) {
auto form_dict = pdfium::MakeRetain<CPDF_Dictionary>();
@@ -59,6 +60,7 @@
CPDF_TestEmptyDocument doc;
CPDF_InteractiveForm form(&doc);
CPDF_FormField form_field(&form, form_dict.Get());
+ EXPECT_EQ(expected_use_indices, form_field.UseSelectedIndicesObject());
for (int i = 0; i < form_field.CountOptions(); i++) {
const bool expected_selected = pdfium::ContainsValue(expected_indices, i);
EXPECT_EQ(expected_selected, form_field.IsItemSelected(i));
@@ -123,7 +125,8 @@
std::vector<int> expected_indices;
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, /*values=*/nullptr,
- /*selected_indices=*/nullptr, expected_indices,
+ /*selected_indices=*/nullptr,
+ /*expected_use_indices=*/false, expected_indices,
excluded_indices);
}
{
@@ -132,7 +135,8 @@
std::vector<int> expected_indices{2};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) object is just an invalid string.
@@ -140,17 +144,18 @@
std::vector<int> expected_indices;
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) object is an array with one object.
auto values = pdfium::MakeRetain<CPDF_Array>();
values->AppendNew<CPDF_String>(L"Beta");
- // TODO(bug_1505): Should be selected at index 1.
- std::vector<int> expected_indices;
+ std::vector<int> expected_indices{1};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) object is an array with one invalid object.
@@ -159,18 +164,19 @@
std::vector<int> expected_indices;
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) object is an array with multiple objects.
auto values = pdfium::MakeRetain<CPDF_Array>();
values->AppendNew<CPDF_String>(L"Beta");
values->AppendNew<CPDF_String>(L"Epsilon");
- // TODO(bug_1505): Should be selected at index 1 and index 4.
- std::vector<int> expected_indices;
+ std::vector<int> expected_indices{1, 4};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) object is an array with multiple objects with one invalid.
@@ -178,11 +184,11 @@
values->AppendNew<CPDF_String>(L"Beta");
values->AppendNew<CPDF_String>(L"Epsilon");
values->AppendNew<CPDF_String>(L"Omega");
- // TODO(bug_1505): Should be selected at index 1 and index 4.
- std::vector<int> expected_indices;
+ std::vector<int> expected_indices{1, 4};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Selected indices (/I) object is just a number.
@@ -190,7 +196,8 @@
std::vector<int> expected_indices{3};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/true, expected_indices,
+ excluded_indices);
}
{
// Selected indices (/I) object is just an invalid number.
@@ -198,7 +205,8 @@
std::vector<int> expected_indices;
std::vector<int> excluded_indices{-1, 5, 26};
TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/true, expected_indices,
+ excluded_indices);
}
{
// Selected indices (/I) object is an array with one object.
@@ -207,7 +215,8 @@
std::vector<int> expected_indices{0};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/true, expected_indices,
+ excluded_indices);
}
{
// Selected indices (/I) object is an array with multiple objects.
@@ -218,7 +227,8 @@
std::vector<int> expected_indices{0, 2, 3};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/true, expected_indices,
+ excluded_indices);
}
{
// Selected indices (/I) object is an array with multiple objects and some
@@ -233,7 +243,8 @@
std::vector<int> expected_indices{0, 2, 3};
std::vector<int> excluded_indices{-5, -1, 5, 12, 42};
TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/true, expected_indices,
+ excluded_indices);
}
{
// Values (/V) or Selected Indices (/I) objects conflict with different
@@ -245,11 +256,11 @@
selected_indices->AppendNew<CPDF_Number>(0);
selected_indices->AppendNew<CPDF_Number>(2);
selected_indices->AppendNew<CPDF_Number>(3);
- // TODO(bug_1505): Should be selected at index 1 and index 4.
- std::vector<int> expected_indices{0, 2, 3};
+ std::vector<int> expected_indices{1, 4};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) or Selected Indices (/I) objects conflict with same lengths.
@@ -259,11 +270,11 @@
auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
selected_indices->AppendNew<CPDF_Number>(2);
selected_indices->AppendNew<CPDF_Number>(3);
- // TODO(bug_1505): Should be selected at index 0 and index 4.
- std::vector<int> expected_indices{2, 3};
+ std::vector<int> expected_indices{0, 4};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) or Selected Indices (/I) objects conflict with values being
@@ -278,7 +289,8 @@
std::vector<int> expected_indices{1, 4};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) or Selected Indices (/I) objects conflict with selected
@@ -293,7 +305,8 @@
std::vector<int> expected_indices{1, 4};
std::vector<int> excluded_indices{-1, 5, 26};
TestMultiselectFieldDict(opt_array, values, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) or Selected Indices (/I) objects conflict with both being
@@ -307,11 +320,11 @@
selected_indices->AppendNew<CPDF_Number>(2);
selected_indices->AppendNew<CPDF_Number>(3);
selected_indices->AppendNew<CPDF_Number>(26);
- // TODO(bug_1505): Should be selected at index 1 and index 4.
- std::vector<int> expected_indices{0, 2, 3};
+ std::vector<int> expected_indices{1, 4};
std::vector<int> excluded_indices{-1, 5, 26};
TestMultiselectFieldDict(opt_array, values, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
{
// Values (/V) or Selected Indices (/I) objects conflict with each not being
@@ -321,6 +334,7 @@
std::vector<int> expected_indices{2};
std::vector<int> excluded_indices{-1, 5};
TestMultiselectFieldDict(opt_array, values, selected_indices,
- expected_indices, excluded_indices);
+ /*expected_use_indices=*/false, expected_indices,
+ excluded_indices);
}
}
diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp
index a12e3b0..e5ab09f 100644
--- a/fpdfsdk/fpdf_formfill_embeddertest.cpp
+++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp
@@ -2838,8 +2838,7 @@
// opening.
FocusOnMultiSelectMultipleValuesForm();
for (int i = 0; i < 5; i++) {
- // TODO(bug_1505): Should be selected at index 2 and index 4.
- bool expected = false;
+ bool expected = (i == 2 || i == 4);
CheckIsIndexSelected(i, expected);
}
}
@@ -2849,8 +2848,7 @@
// opening.
FocusOnMultiSelectMultipleMismatchForm();
for (int i = 0; i < 5; i++) {
- // TODO(bug_1505): Should be selected at index 0 and index 2.
- bool expected = (i == 1 || i == 3 || i == 4);
+ bool expected = (i == 0 || i == 2);
CheckIsIndexSelected(i, expected);
}
}