OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/json_schema/json_schema_validator.h" | 5 #include "components/json_schema/json_schema_validator.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <cfloat> | 8 #include <cfloat> |
9 #include <cmath> | 9 #include <cmath> |
10 #include <vector> | |
10 | 11 |
11 #include "base/json/json_reader.h" | 12 #include "base/json/json_reader.h" |
13 #include "base/memory/scoped_vector.h" | |
12 #include "base/strings/string_number_conversions.h" | 14 #include "base/strings/string_number_conversions.h" |
13 #include "base/strings/string_util.h" | 15 #include "base/strings/string_util.h" |
14 #include "base/strings/stringprintf.h" | 16 #include "base/strings/stringprintf.h" |
15 #include "base/values.h" | 17 #include "base/values.h" |
16 #include "components/json_schema/json_schema_constants.h" | 18 #include "components/json_schema/json_schema_constants.h" |
19 #include "third_party/re2/re2/re2.h" | |
17 | 20 |
18 namespace schema = json_schema_constants; | 21 namespace schema = json_schema_constants; |
19 | 22 |
20 namespace { | 23 namespace { |
21 | 24 |
22 double GetNumberValue(const base::Value* value) { | 25 double GetNumberValue(const base::Value* value) { |
23 double result = 0; | 26 double result = 0; |
24 CHECK(value->GetAsDouble(&result)) | 27 CHECK(value->GetAsDouble(&result)) |
25 << "Unexpected value type: " << value->GetType(); | 28 << "Unexpected value type: " << value->GetType(); |
26 return result; | 29 return result; |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
79 { schema::kDescription, base::Value::TYPE_STRING }, | 82 { schema::kDescription, base::Value::TYPE_STRING }, |
80 { schema::kEnum, base::Value::TYPE_LIST }, | 83 { schema::kEnum, base::Value::TYPE_LIST }, |
81 { schema::kId, base::Value::TYPE_STRING }, | 84 { schema::kId, base::Value::TYPE_STRING }, |
82 { schema::kMaxItems, base::Value::TYPE_INTEGER }, | 85 { schema::kMaxItems, base::Value::TYPE_INTEGER }, |
83 { schema::kMaxLength, base::Value::TYPE_INTEGER }, | 86 { schema::kMaxLength, base::Value::TYPE_INTEGER }, |
84 { schema::kMaximum, base::Value::TYPE_DOUBLE }, | 87 { schema::kMaximum, base::Value::TYPE_DOUBLE }, |
85 { schema::kMinItems, base::Value::TYPE_INTEGER }, | 88 { schema::kMinItems, base::Value::TYPE_INTEGER }, |
86 { schema::kMinLength, base::Value::TYPE_INTEGER }, | 89 { schema::kMinLength, base::Value::TYPE_INTEGER }, |
87 { schema::kMinimum, base::Value::TYPE_DOUBLE }, | 90 { schema::kMinimum, base::Value::TYPE_DOUBLE }, |
88 { schema::kOptional, base::Value::TYPE_BOOLEAN }, | 91 { schema::kOptional, base::Value::TYPE_BOOLEAN }, |
92 { schema::kPattern, base::Value::TYPE_STRING }, | |
93 { schema::kPatternProperties, base::Value::TYPE_DICTIONARY }, | |
89 { schema::kProperties, base::Value::TYPE_DICTIONARY }, | 94 { schema::kProperties, base::Value::TYPE_DICTIONARY }, |
90 { schema::kTitle, base::Value::TYPE_STRING }, | 95 { schema::kTitle, base::Value::TYPE_STRING }, |
91 }; | 96 }; |
92 | 97 |
93 bool has_type_or_ref = false; | 98 bool has_type_or_ref = false; |
94 const base::ListValue* list_value = NULL; | 99 const base::ListValue* list_value = NULL; |
95 const base::DictionaryValue* dictionary_value = NULL; | 100 const base::DictionaryValue* dictionary_value = NULL; |
96 std::string string_value; | 101 std::string string_value; |
97 | 102 |
98 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | 103 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
176 if (it.value().IsType(base::Value::TYPE_INTEGER)) { | 181 if (it.value().IsType(base::Value::TYPE_INTEGER)) { |
177 int integer_value; | 182 int integer_value; |
178 it.value().GetAsInteger(&integer_value); | 183 it.value().GetAsInteger(&integer_value); |
179 if (integer_value < 0) { | 184 if (integer_value < 0) { |
180 *error = base::StringPrintf("Value of %s must be >= 0, got %d", | 185 *error = base::StringPrintf("Value of %s must be >= 0, got %d", |
181 it.key().c_str(), integer_value); | 186 it.key().c_str(), integer_value); |
182 return false; | 187 return false; |
183 } | 188 } |
184 } | 189 } |
185 | 190 |
191 if (it.key() == schema::kPattern) { | |
192 it.value().GetAsString(&string_value); | |
193 if (!re2::RE2(string_value).ok()) { | |
not at google - send to devlin
2014/03/11 14:53:39
How often does Validate get called? If it's a lot
binjin
2014/03/11 16:27:36
Yes, regular expression compiling is not very chea
not at google - send to devlin
2014/03/11 18:37:16
Good point re uncachability of this stuff. Ok. You
| |
194 *error = "Invalid regular expression in pattern attribute"; | |
not at google - send to devlin
2014/03/11 14:53:39
Could you include the RE2 error message here?
htt
binjin
2014/03/11 16:27:36
Done.
| |
195 return false; | |
196 } | |
197 } | |
198 | |
186 // Validate the "properties" attribute. Each entry maps a key to a schema. | 199 // Validate the "properties" attribute. Each entry maps a key to a schema. |
187 if (it.key() == schema::kProperties) { | 200 if (it.key() == schema::kProperties) { |
188 it.value().GetAsDictionary(&dictionary_value); | 201 it.value().GetAsDictionary(&dictionary_value); |
189 for (base::DictionaryValue::Iterator it(*dictionary_value); | 202 for (base::DictionaryValue::Iterator it(*dictionary_value); |
190 !it.IsAtEnd(); it.Advance()) { | 203 !it.IsAtEnd(); it.Advance()) { |
191 if (!it.value().GetAsDictionary(&dictionary_value)) { | 204 if (!it.value().GetAsDictionary(&dictionary_value)) { |
192 *error = "Invalid value for properties attribute"; | 205 *error = "Invalid value for properties attribute"; |
193 return false; | 206 return false; |
194 } | 207 } |
195 if (!IsValidSchema(dictionary_value, options, error)) { | 208 if (!IsValidSchema(dictionary_value, options, error)) { |
196 DCHECK(!error->empty()); | 209 DCHECK(!error->empty()); |
197 return false; | 210 return false; |
198 } | 211 } |
199 } | 212 } |
200 } | 213 } |
201 | 214 |
215 // Validate the "patternProperties" attribute. Each entry maps a valid | |
216 // regular expression to a schema. | |
217 if (it.key() == schema::kPatternProperties) { | |
218 it.value().GetAsDictionary(&dictionary_value); | |
219 for (base::DictionaryValue::Iterator it(*dictionary_value); | |
not at google - send to devlin
2014/03/11 14:53:39
huh, how does the |it| declaration here not kill t
Joao da Silva
2014/03/11 16:20:13
IIUC this is the same as shadowing an outer variab
binjin
2014/03/11 16:27:36
My bad, I just copy-and-modified code from above b
| |
220 !it.IsAtEnd(); it.Advance()) { | |
221 if (!re2::RE2(it.key()).ok()) { | |
222 *error = "Invalid regular expression in patternProperties attribute"; | |
not at google - send to devlin
2014/03/11 14:53:39
ditto error message
binjin
2014/03/11 16:27:36
Done.
| |
223 return false; | |
224 } | |
225 if (!it.value().GetAsDictionary(&dictionary_value)) { | |
226 *error = "Invalid value for patternProperties attribute"; | |
not at google - send to devlin
2014/03/11 14:53:39
How about "patternProperties must be a dictionary"
binjin
2014/03/11 16:27:36
Done(for this and above).
| |
227 return false; | |
228 } | |
229 if (!IsValidSchema(dictionary_value, options, error)) { | |
230 DCHECK(!error->empty()); | |
231 return false; | |
232 } | |
233 } | |
234 } | |
235 | |
202 // Validate "additionalProperties" attribute, which is a schema. | 236 // Validate "additionalProperties" attribute, which is a schema. |
203 if (it.key() == schema::kAdditionalProperties) { | 237 if (it.key() == schema::kAdditionalProperties) { |
204 it.value().GetAsDictionary(&dictionary_value); | 238 it.value().GetAsDictionary(&dictionary_value); |
205 if (!IsValidSchema(dictionary_value, options, error)) { | 239 if (!IsValidSchema(dictionary_value, options, error)) { |
206 DCHECK(!error->empty()); | 240 DCHECK(!error->empty()); |
207 return false; | 241 return false; |
208 } | 242 } |
209 } | 243 } |
210 | 244 |
211 // Validate the values contained in an "enum" attribute. | 245 // Validate the values contained in an "enum" attribute. |
(...skipping 355 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
567 // 'true'. | 601 // 'true'. |
568 bool is_optional = false; | 602 bool is_optional = false; |
569 prop_schema->GetBoolean(schema::kOptional, &is_optional); | 603 prop_schema->GetBoolean(schema::kOptional, &is_optional); |
570 if (!is_optional) { | 604 if (!is_optional) { |
571 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); | 605 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); |
572 } | 606 } |
573 } | 607 } |
574 } | 608 } |
575 } | 609 } |
576 | 610 |
611 // Allowing any additional items will ignore pattern properties as well. | |
577 const base::DictionaryValue* additional_properties_schema = NULL; | 612 const base::DictionaryValue* additional_properties_schema = NULL; |
578 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) | 613 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) |
579 return; | 614 return; |
580 | 615 |
581 // Validate additional properties. | 616 const base::DictionaryValue* pattern_properties = NULL; |
617 ScopedVector<re2::RE2> pattern_properties_pattern; | |
618 std::vector<const base::DictionaryValue*> pattern_properties_schema; | |
619 | |
620 schema->GetDictionary(schema::kPatternProperties, &pattern_properties); | |
621 if (pattern_properties) { | |
not at google - send to devlin
2014/03/11 14:53:39
usual style I see would be to have the schema->Get
binjin
2014/03/11 16:27:36
Done(for this and above).
| |
622 for (base::DictionaryValue::Iterator it(*pattern_properties); !it.IsAtEnd(); | |
623 it.Advance()) { | |
624 re2::RE2* prop_pattern = new re2::RE2(it.key()); | |
625 CHECK(prop_pattern->ok()); | |
not at google - send to devlin
2014/03/11 14:53:39
See comment above about caching these. In fact cou
| |
626 const base::DictionaryValue* prop_schema = NULL; | |
627 CHECK(it.value().GetAsDictionary(&prop_schema)); | |
628 pattern_properties_pattern.push_back(prop_pattern); | |
629 pattern_properties_schema.push_back(prop_schema); | |
630 } | |
631 } | |
632 | |
633 // Validate pattern properties and additional properties. | |
582 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); | 634 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); |
583 it.Advance()) { | 635 it.Advance()) { |
584 if (properties && properties->HasKey(it.key())) | 636 if (properties && properties->HasKey(it.key())) |
585 continue; | 637 continue; |
586 | 638 |
587 std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); | 639 std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); |
640 | |
641 bool found_matching_pattern = false; | |
642 for (size_t index = 0; index < pattern_properties_pattern.size(); index++) { | |
not at google - send to devlin
2014/03/11 14:53:39
++index
binjin
2014/03/11 16:27:36
Done.
| |
643 if (re2::RE2::PartialMatch(it.key(), | |
644 *pattern_properties_pattern[index])) { | |
645 found_matching_pattern = true; | |
646 Validate(&it.value(), pattern_properties_schema[index], prop_path); | |
647 break; | |
648 } | |
649 } | |
Joao da Silva
2014/03/11 16:20:13
The order of these checks is not correct. See http
binjin
2014/03/11 17:20:04
I just read the specification, and from what I und
| |
650 | |
651 if (found_matching_pattern) | |
652 continue; | |
653 | |
588 if (!additional_properties_schema) { | 654 if (!additional_properties_schema) { |
589 errors_.push_back(Error(prop_path, kUnexpectedProperty)); | 655 errors_.push_back(Error(prop_path, kUnexpectedProperty)); |
590 } else { | 656 } else { |
591 Validate(&it.value(), additional_properties_schema, prop_path); | 657 Validate(&it.value(), additional_properties_schema, prop_path); |
592 } | 658 } |
593 } | 659 } |
594 } | 660 } |
595 | 661 |
596 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, | 662 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, |
597 const base::DictionaryValue* schema, | 663 const base::DictionaryValue* schema, |
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
700 | 766 |
701 int max_length = 0; | 767 int max_length = 0; |
702 if (schema->GetInteger(schema::kMaxLength, &max_length)) { | 768 if (schema->GetInteger(schema::kMaxLength, &max_length)) { |
703 CHECK(max_length >= 0); | 769 CHECK(max_length >= 0); |
704 if (value.size() > static_cast<size_t>(max_length)) { | 770 if (value.size() > static_cast<size_t>(max_length)) { |
705 errors_.push_back(Error(path, FormatErrorMessage( | 771 errors_.push_back(Error(path, FormatErrorMessage( |
706 kStringMaxLength, base::IntToString(max_length)))); | 772 kStringMaxLength, base::IntToString(max_length)))); |
707 } | 773 } |
708 } | 774 } |
709 | 775 |
710 CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported."; | 776 std::string pattern; |
777 if (schema->GetString(schema::kPattern, &pattern) && | |
778 !re2::RE2::PartialMatch(value, pattern)) { | |
779 errors_.push_back(Error(path, FormatErrorMessage(kStringPattern, pattern))); | |
780 } | |
711 } | 781 } |
712 | 782 |
713 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, | 783 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, |
714 const base::DictionaryValue* schema, | 784 const base::DictionaryValue* schema, |
715 const std::string& path) { | 785 const std::string& path) { |
716 double value = GetNumberValue(instance); | 786 double value = GetNumberValue(instance); |
717 | 787 |
718 // TODO(aa): It would be good to test that the double is not infinity or nan, | 788 // TODO(aa): It would be good to test that the double is not infinity or nan, |
719 // but isnan and isinf aren't defined on Windows. | 789 // but isnan and isinf aren't defined on Windows. |
720 | 790 |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
761 | 831 |
762 if (*additional_properties_schema) { | 832 if (*additional_properties_schema) { |
763 std::string additional_properties_type(schema::kAny); | 833 std::string additional_properties_type(schema::kAny); |
764 CHECK((*additional_properties_schema)->GetString( | 834 CHECK((*additional_properties_schema)->GetString( |
765 schema::kType, &additional_properties_type)); | 835 schema::kType, &additional_properties_type)); |
766 return additional_properties_type == schema::kAny; | 836 return additional_properties_type == schema::kAny; |
767 } else { | 837 } else { |
768 return default_allow_additional_properties_; | 838 return default_allow_additional_properties_; |
769 } | 839 } |
770 } | 840 } |
OLD | NEW |