Commit 3eb77a700434ed6d9b51e326fa4d49c530fcd473
1 parent
6d4e3ba8
JSON: detect duplicate dictionary keys while parsing
Showing
6 changed files
with
44 additions
and
1 deletions
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& key, JSON const& 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
libtests/qtest/json_parse.test
libtests/qtest/json_parse/bad-40.json
0 → 100644
libtests/qtest/json_parse/bad-40.out
0 → 100644
| 1 | +exception: bad-40.json: JSON: offset 28: duplicated dictionary key | ... | ... |