Commit 3eb77a700434ed6d9b51e326fa4d49c530fcd473

Authored by Jay Berkenbilt
1 parent 6d4e3ba8

JSON: detect duplicate dictionary keys while parsing

include/qpdf/JSON.hh
... ... @@ -42,6 +42,7 @@
42 42 #include <list>
43 43 #include <map>
44 44 #include <memory>
  45 +#include <set>
45 46 #include <string>
46 47 #include <vector>
47 48  
... ... @@ -149,6 +150,14 @@ class JSON
149 150 QPDF_DLL
150 151 bool isDictionary() const;
151 152  
  153 + // If the key is already in the dictionary, return true.
  154 + // Otherwise, mark it has seen and return false. This is primarily
  155 + // intended to used by the parser to detect duplicate keys when
  156 + // the reactor blocks them from being added to the final
  157 + // dictionary.
  158 + QPDF_DLL
  159 + bool checkDictionaryKeySeen(std::string const& key);
  160 +
152 161 // Accessors. Accessor behavior:
153 162 //
154 163 // - If argument is wrong type, including null, return false
... ... @@ -314,6 +323,7 @@ class JSON
314 323 virtual ~JSON_dictionary() = default;
315 324 virtual void write(Pipeline*, size_t depth) const;
316 325 std::map<std::string, std::shared_ptr<JSON_value>> members;
  326 + std::set<std::string> parsed_keys;
317 327 };
318 328 struct JSON_array: public JSON_value
319 329 {
... ...
libqpdf/JSON.cc
... ... @@ -274,6 +274,21 @@ JSON::addDictionaryMember(std::string const&amp; key, JSON const&amp; val)
274 274 return obj->members[encode_string(key)];
275 275 }
276 276  
  277 +bool
  278 +JSON::checkDictionaryKeySeen(std::string const& key)
  279 +{
  280 + JSON_dictionary* obj = dynamic_cast<JSON_dictionary*>(this->m->value.get());
  281 + if (0 == obj) {
  282 + throw std::logic_error(
  283 + "JSON::checkDictionaryKey called on non-dictionary");
  284 + }
  285 + if (obj->parsed_keys.count(key)) {
  286 + return true;
  287 + }
  288 + obj->parsed_keys.insert(key);
  289 + return false;
  290 +}
  291 +
277 292 JSON
278 293 JSON::makeArray()
279 294 {
... ... @@ -565,7 +580,8 @@ namespace
565 580 u_count(0),
566 581 offset(0),
567 582 done(false),
568   - parser_state(ps_top)
  583 + parser_state(ps_top),
  584 + dict_key_offset(0)
569 585 {
570 586 }
571 587  
... ... @@ -625,6 +641,7 @@ namespace
625 641 std::vector<std::shared_ptr<JSON>> stack;
626 642 std::vector<parser_state_e> ps_stack;
627 643 std::string dict_key;
  644 + size_t dict_key_offset;
628 645 };
629 646 } // namespace
630 647  
... ... @@ -1201,11 +1218,18 @@ JSONParser::handleToken()
1201 1218 case ps_dict_begin:
1202 1219 case ps_dict_after_comma:
1203 1220 this->dict_key = s_value;
  1221 + this->dict_key_offset = item->getStart();
1204 1222 item = nullptr;
1205 1223 next_state = ps_dict_after_key;
1206 1224 break;
1207 1225  
1208 1226 case ps_dict_after_colon:
  1227 + if (tos->checkDictionaryKeySeen(dict_key)) {
  1228 + QTC::TC("libtests", "JSON parse duplicate key");
  1229 + throw std::runtime_error(
  1230 + "JSON: offset " + QUtil::uint_to_string(dict_key_offset) +
  1231 + ": duplicated dictionary key");
  1232 + }
1209 1233 if (!reactor || !reactor->dictionaryItem(dict_key, *item)) {
1210 1234 tos->addDictionaryMember(dict_key, *item);
1211 1235 }
... ...
libtests/libtests.testcov
... ... @@ -92,3 +92,4 @@ JSON optional key 0
92 92 JSON 16 high high 0
93 93 JSON 16 low not after high 0
94 94 JSON 16 dangling high 0
  95 +JSON parse duplicate key 0
... ...
libtests/qtest/json_parse.test
... ... @@ -120,6 +120,7 @@ my @bad = (
120 120 "stray low surrogate", # 37
121 121 "high high surrogate", # 38
122 122 "dangling high surrogate", # 39
  123 + "duplicate dictionary key", # 40
123 124 );
124 125  
125 126 my $i = 0;
... ...
libtests/qtest/json_parse/bad-40.json 0 → 100644
  1 +{
  2 + "one": 1,
  3 + "two": 2,
  4 + "one": 3,
  5 + "four": 4
  6 +}
... ...
libtests/qtest/json_parse/bad-40.out 0 → 100644
  1 +exception: bad-40.json: JSON: offset 28: duplicated dictionary key
... ...