Commit 08ba21cf4935fec39d3454714c03d36ff6a6b836

Authored by Jay Berkenbilt
1 parent 4be2f360

Fix some bugs around null values in dictionaries

Make it so that a key with a null value is always treated as not being
present. This was inconsistent before.
libqpdf/QPDF_Dictionary.cc
@@ -24,12 +24,11 @@ std::string @@ -24,12 +24,11 @@ std::string
24 QPDF_Dictionary::unparse() 24 QPDF_Dictionary::unparse()
25 { 25 {
26 std::string result = "<< "; 26 std::string result = "<< ";
27 - for (std::map<std::string, QPDFObjectHandle>::iterator iter =  
28 - this->items.begin();  
29 - iter != this->items.end();  
30 - ++iter) {  
31 - result += QPDF_Name::normalizeName((*iter).first) + " " +  
32 - (*iter).second.unparse() + " "; 27 + for (auto& iter : this->items) {
  28 + if (!iter.second.isNull()) {
  29 + result += QPDF_Name::normalizeName(iter.first) + " " +
  30 + iter.second.unparse() + " ";
  31 + }
33 } 32 }
34 result += ">>"; 33 result += ">>";
35 return result; 34 return result;
@@ -39,12 +38,11 @@ JSON @@ -39,12 +38,11 @@ JSON
39 QPDF_Dictionary::getJSON() 38 QPDF_Dictionary::getJSON()
40 { 39 {
41 JSON j = JSON::makeDictionary(); 40 JSON j = JSON::makeDictionary();
42 - for (std::map<std::string, QPDFObjectHandle>::iterator iter =  
43 - this->items.begin();  
44 - iter != this->items.end();  
45 - ++iter) {  
46 - j.addDictionaryMember(  
47 - QPDF_Name::normalizeName((*iter).first), (*iter).second.getJSON()); 41 + for (auto& iter : this->items) {
  42 + if (!iter.second.isNull()) {
  43 + j.addDictionaryMember(
  44 + QPDF_Name::normalizeName(iter.first), iter.second.getJSON());
  45 + }
48 } 46 }
49 return j; 47 return j;
50 } 48 }
@@ -78,9 +76,10 @@ QPDF_Dictionary::getKey(std::string const&amp; key) @@ -78,9 +76,10 @@ QPDF_Dictionary::getKey(std::string const&amp; key)
78 { 76 {
79 // PDF spec says fetching a non-existent key from a dictionary 77 // PDF spec says fetching a non-existent key from a dictionary
80 // returns the null object. 78 // returns the null object.
81 - if (this->items.count(key)) { 79 + auto item = this->items.find(key);
  80 + if (item != this->items.end()) {
82 // May be a null object 81 // May be a null object
83 - return (*(this->items.find(key))).second; 82 + return item->second;
84 } else { 83 } else {
85 QPDFObjectHandle null = QPDFObjectHandle::newNull(); 84 QPDFObjectHandle null = QPDFObjectHandle::newNull();
86 QPDF* qpdf = 0; 85 QPDF* qpdf = 0;
@@ -97,12 +96,9 @@ std::set&lt;std::string&gt; @@ -97,12 +96,9 @@ std::set&lt;std::string&gt;
97 QPDF_Dictionary::getKeys() 96 QPDF_Dictionary::getKeys()
98 { 97 {
99 std::set<std::string> result; 98 std::set<std::string> result;
100 - for (std::map<std::string, QPDFObjectHandle>::const_iterator iter =  
101 - this->items.begin();  
102 - iter != this->items.end();  
103 - ++iter) {  
104 - if (hasKey((*iter).first)) {  
105 - result.insert((*iter).first); 99 + for (auto& iter : this->items) {
  100 + if (!iter.second.isNull()) {
  101 + result.insert(iter.first);
106 } 102 }
107 } 103 }
108 return result; 104 return result;
qpdf/qtest/qpdf.test
@@ -1421,6 +1421,17 @@ foreach (my $i = 1; $i &lt;= 3; ++$i) @@ -1421,6 +1421,17 @@ foreach (my $i = 1; $i &lt;= 3; ++$i)
1421 1421
1422 show_ntests(); 1422 show_ntests();
1423 # ---------- 1423 # ----------
  1424 +$td->notify("--- Dictionary keys ---");
  1425 +$n_tests += 1;
  1426 +
  1427 +$td->runtest("dictionary keys",
  1428 + {$td->COMMAND => "test_driver 87 - -"},
  1429 + {$td->STRING => "test 87 done\n",
  1430 + $td->EXIT_STATUS => 0},
  1431 + $td->NORMALIZE_NEWLINES);
  1432 +
  1433 +show_ntests();
  1434 +# ----------
1424 $td->notify("--- Stream data ---"); 1435 $td->notify("--- Stream data ---");
1425 $n_tests += 2; 1436 $n_tests += 2;
1426 1437
qpdf/qtest/qpdf/bad34-recover.out
@@ -2,7 +2,7 @@ WARNING: bad34.pdf: file is damaged @@ -2,7 +2,7 @@ WARNING: bad34.pdf: file is damaged
2 WARNING: bad34.pdf (object 4 0, offset 322): expected n n obj 2 WARNING: bad34.pdf (object 4 0, offset 322): expected n n obj
3 WARNING: bad34.pdf: Attempting to reconstruct cross-reference table 3 WARNING: bad34.pdf: Attempting to reconstruct cross-reference table
4 /QTest is indirect and has type stream (10) 4 /QTest is indirect and has type stream (10)
5 -/QTest is a stream. Dictionary: << /Length 44 /Quack 9 0 R >> 5 +/QTest is a stream. Dictionary: << /Length 44 >>
6 Raw stream data: 6 Raw stream data:
7 BT 7 BT
8 /F1 24 Tf 8 /F1 24 Tf
qpdf/qtest/qpdf/good11.out
1 /QTest is direct and has type dictionary (9) 1 /QTest is direct and has type dictionary (9)
2 /QTest is a dictionary 2 /QTest is a dictionary
3 /a is direct 3 /a is direct
4 -unparse: << /a (a) /b 8 0 R >>  
5 -unparseResolved: << /a (a) /b 8 0 R >> 4 +unparse: << /a (a) >>
  5 +unparseResolved: << /a (a) >>
6 test 1 done 6 test 1 done
qpdf/test_driver.cc
@@ -3158,6 +3158,26 @@ test_86(QPDF&amp; pdf, char const* arg2) @@ -3158,6 +3158,26 @@ test_86(QPDF&amp; pdf, char const* arg2)
3158 assert(h.getUTF8Value() == utf8_val); 3158 assert(h.getUTF8Value() == utf8_val);
3159 } 3159 }
3160 3160
  3161 +static void
  3162 +test_87(QPDF& pdf, char const* arg2)
  3163 +{
  3164 + // Explicitly demonstrate null dictionary values being the same as
  3165 + // missing keys.
  3166 + auto dict = "<< /A 1 /B null >>"_qpdf;
  3167 + assert(dict.unparse() == "<< /A 1 >>");
  3168 + assert(dict.getKeys() == std::set<std::string>({"/A"}));
  3169 + dict.replaceKey("/A", QPDFObjectHandle::newNull());
  3170 + assert(dict.unparse() == "<< >>");
  3171 + assert(dict.getKeys() == std::set<std::string>());
  3172 + dict = QPDFObjectHandle::newDictionary({
  3173 + {"/A", "2"_qpdf},
  3174 + {"/B", QPDFObjectHandle::newNull()},
  3175 + });
  3176 + assert(dict.unparse() == "<< /A 2 >>");
  3177 + assert(dict.getKeys() == std::set<std::string>({"/A"}));
  3178 + assert(dict.getJSON().unparse() == "{\n \"/A\": 2\n}");
  3179 +}
  3180 +
3161 void 3181 void
3162 runtest(int n, char const* filename1, char const* arg2) 3182 runtest(int n, char const* filename1, char const* arg2)
3163 { 3183 {
@@ -3165,7 +3185,7 @@ runtest(int n, char const* filename1, char const* arg2) @@ -3165,7 +3185,7 @@ runtest(int n, char const* filename1, char const* arg2)
3165 // the test suite to see how the test is invoked to find the file 3185 // the test suite to see how the test is invoked to find the file
3166 // that the test is supposed to operate on. 3186 // that the test is supposed to operate on.
3167 3187
3168 - std::set<int> ignore_filename = {61, 81, 83, 84, 85, 86}; 3188 + std::set<int> ignore_filename = {61, 81, 83, 84, 85, 86, 87};
3169 3189
3170 if (n == 0) { 3190 if (n == 0) {
3171 // Throw in some random test cases that don't fit anywhere 3191 // Throw in some random test cases that don't fit anywhere
@@ -3259,7 +3279,7 @@ runtest(int n, char const* filename1, char const* arg2) @@ -3259,7 +3279,7 @@ runtest(int n, char const* filename1, char const* arg2)
3259 {72, test_72}, {73, test_73}, {74, test_74}, {75, test_75}, 3279 {72, test_72}, {73, test_73}, {74, test_74}, {75, test_75},
3260 {76, test_76}, {77, test_77}, {78, test_78}, {79, test_79}, 3280 {76, test_76}, {77, test_77}, {78, test_78}, {79, test_79},
3261 {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83}, 3281 {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83},
3262 - {84, test_84}, {85, test_85}, {86, test_86}, 3282 + {84, test_84}, {85, test_85}, {86, test_86}, {87, test_87},
3263 }; 3283 };
3264 3284
3265 auto fn = test_functions.find(n); 3285 auto fn = test_functions.find(n);