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/logging.h" | |
14 #include "base/memory/scoped_vector.h" | |
12 #include "base/strings/string_number_conversions.h" | 15 #include "base/strings/string_number_conversions.h" |
13 #include "base/strings/string_util.h" | 16 #include "base/strings/string_util.h" |
14 #include "base/strings/stringprintf.h" | 17 #include "base/strings/stringprintf.h" |
15 #include "base/values.h" | 18 #include "base/values.h" |
16 #include "components/json_schema/json_schema_constants.h" | 19 #include "components/json_schema/json_schema_constants.h" |
20 #include "third_party/re2/re2/re2.h" | |
17 | 21 |
18 namespace schema = json_schema_constants; | 22 namespace schema = json_schema_constants; |
19 | 23 |
20 namespace { | 24 namespace { |
21 | 25 |
22 double GetNumberValue(const base::Value* value) { | 26 double GetNumberValue(const base::Value* value) { |
23 double result = 0; | 27 double result = 0; |
24 CHECK(value->GetAsDouble(&result)) | 28 CHECK(value->GetAsDouble(&result)) |
25 << "Unexpected value type: " << value->GetType(); | 29 << "Unexpected value type: " << value->GetType(); |
26 return result; | 30 return result; |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
79 { schema::kDescription, base::Value::TYPE_STRING }, | 83 { schema::kDescription, base::Value::TYPE_STRING }, |
80 { schema::kEnum, base::Value::TYPE_LIST }, | 84 { schema::kEnum, base::Value::TYPE_LIST }, |
81 { schema::kId, base::Value::TYPE_STRING }, | 85 { schema::kId, base::Value::TYPE_STRING }, |
82 { schema::kMaxItems, base::Value::TYPE_INTEGER }, | 86 { schema::kMaxItems, base::Value::TYPE_INTEGER }, |
83 { schema::kMaxLength, base::Value::TYPE_INTEGER }, | 87 { schema::kMaxLength, base::Value::TYPE_INTEGER }, |
84 { schema::kMaximum, base::Value::TYPE_DOUBLE }, | 88 { schema::kMaximum, base::Value::TYPE_DOUBLE }, |
85 { schema::kMinItems, base::Value::TYPE_INTEGER }, | 89 { schema::kMinItems, base::Value::TYPE_INTEGER }, |
86 { schema::kMinLength, base::Value::TYPE_INTEGER }, | 90 { schema::kMinLength, base::Value::TYPE_INTEGER }, |
87 { schema::kMinimum, base::Value::TYPE_DOUBLE }, | 91 { schema::kMinimum, base::Value::TYPE_DOUBLE }, |
88 { schema::kOptional, base::Value::TYPE_BOOLEAN }, | 92 { schema::kOptional, base::Value::TYPE_BOOLEAN }, |
93 { schema::kPattern, base::Value::TYPE_STRING }, | |
94 { schema::kPatternProperties, base::Value::TYPE_DICTIONARY }, | |
89 { schema::kProperties, base::Value::TYPE_DICTIONARY }, | 95 { schema::kProperties, base::Value::TYPE_DICTIONARY }, |
90 { schema::kTitle, base::Value::TYPE_STRING }, | 96 { schema::kTitle, base::Value::TYPE_STRING }, |
91 }; | 97 }; |
92 | 98 |
93 bool has_type_or_ref = false; | 99 bool has_type_or_ref = false; |
94 const base::ListValue* list_value = NULL; | 100 const base::ListValue* list_value = NULL; |
95 const base::DictionaryValue* dictionary_value = NULL; | 101 const base::DictionaryValue* dictionary_value = NULL; |
96 std::string string_value; | 102 std::string string_value; |
97 | 103 |
98 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | 104 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
179 if (integer_value < 0) { | 185 if (integer_value < 0) { |
180 *error = base::StringPrintf("Value of %s must be >= 0, got %d", | 186 *error = base::StringPrintf("Value of %s must be >= 0, got %d", |
181 it.key().c_str(), integer_value); | 187 it.key().c_str(), integer_value); |
182 return false; | 188 return false; |
183 } | 189 } |
184 } | 190 } |
185 | 191 |
186 // Validate the "properties" attribute. Each entry maps a key to a schema. | 192 // Validate the "properties" attribute. Each entry maps a key to a schema. |
187 if (it.key() == schema::kProperties) { | 193 if (it.key() == schema::kProperties) { |
188 it.value().GetAsDictionary(&dictionary_value); | 194 it.value().GetAsDictionary(&dictionary_value); |
189 for (base::DictionaryValue::Iterator it(*dictionary_value); | 195 for (base::DictionaryValue::Iterator iter(*dictionary_value); |
190 !it.IsAtEnd(); it.Advance()) { | 196 !iter.IsAtEnd(); iter.Advance()) { |
191 if (!it.value().GetAsDictionary(&dictionary_value)) { | 197 if (!iter.value().GetAsDictionary(&dictionary_value)) { |
192 *error = "Invalid value for properties attribute"; | 198 *error = "properties must be a dictionary"; |
193 return false; | 199 return false; |
194 } | 200 } |
195 if (!IsValidSchema(dictionary_value, options, error)) { | 201 if (!IsValidSchema(dictionary_value, options, error)) { |
202 DCHECK(!error->empty()); | |
203 return false; | |
204 } | |
205 } | |
206 } | |
207 | |
208 // Validate the "patternProperties" attribute. Each entry maps a regular | |
209 // expression to a schema. The validity of the regular expression expression | |
210 // won't be checked here for performance reason. | |
not at google - send to devlin
2014/03/25 14:38:06
.... "reasons. Instead, invalid regular expression
binjin
2014/03/27 12:21:01
Done.
| |
211 if (it.key() == schema::kPatternProperties) { | |
212 it.value().GetAsDictionary(&dictionary_value); | |
213 for (base::DictionaryValue::Iterator iter(*dictionary_value); | |
214 !iter.IsAtEnd(); iter.Advance()) { | |
215 if (!iter.value().GetAsDictionary(&dictionary_value)) { | |
216 *error = "patternProperties must be a dictionary"; | |
217 return false; | |
218 } | |
219 if (!IsValidSchema(dictionary_value, options, error)) { | |
196 DCHECK(!error->empty()); | 220 DCHECK(!error->empty()); |
197 return false; | 221 return false; |
198 } | 222 } |
199 } | 223 } |
200 } | 224 } |
201 | 225 |
202 // Validate "additionalProperties" attribute, which is a schema. | 226 // Validate "additionalProperties" attribute, which is a schema. |
203 if (it.key() == schema::kAdditionalProperties) { | 227 if (it.key() == schema::kAdditionalProperties) { |
204 it.value().GetAsDictionary(&dictionary_value); | 228 it.value().GetAsDictionary(&dictionary_value); |
205 if (!IsValidSchema(dictionary_value, options, error)) { | 229 if (!IsValidSchema(dictionary_value, options, error)) { |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
301 const char JSONSchemaValidator::kStringPattern[] = | 325 const char JSONSchemaValidator::kStringPattern[] = |
302 "String must match the pattern: *."; | 326 "String must match the pattern: *."; |
303 const char JSONSchemaValidator::kNumberMinimum[] = | 327 const char JSONSchemaValidator::kNumberMinimum[] = |
304 "Value must not be less than *."; | 328 "Value must not be less than *."; |
305 const char JSONSchemaValidator::kNumberMaximum[] = | 329 const char JSONSchemaValidator::kNumberMaximum[] = |
306 "Value must not be greater than *."; | 330 "Value must not be greater than *."; |
307 const char JSONSchemaValidator::kInvalidType[] = | 331 const char JSONSchemaValidator::kInvalidType[] = |
308 "Expected '*' but got '*'."; | 332 "Expected '*' but got '*'."; |
309 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] = | 333 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] = |
310 "Expected 'integer' but got 'number', consider using Math.round()."; | 334 "Expected 'integer' but got 'number', consider using Math.round()."; |
335 const char JSONSchemaValidator::kInvalidRegex[] = | |
336 "Regular expression /*/ is invalid: *"; | |
311 | 337 |
312 | 338 |
313 // static | 339 // static |
314 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) { | 340 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) { |
315 switch (value->GetType()) { | 341 switch (value->GetType()) { |
316 case base::Value::TYPE_NULL: | 342 case base::Value::TYPE_NULL: |
317 return schema::kNull; | 343 return schema::kNull; |
318 case base::Value::TYPE_BOOLEAN: | 344 case base::Value::TYPE_BOOLEAN: |
319 return schema::kBoolean; | 345 return schema::kBoolean; |
320 case base::Value::TYPE_INTEGER: | 346 case base::Value::TYPE_INTEGER: |
(...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
544 } | 570 } |
545 } | 571 } |
546 | 572 |
547 errors_.push_back(Error(path, kInvalidEnum)); | 573 errors_.push_back(Error(path, kInvalidEnum)); |
548 } | 574 } |
549 | 575 |
550 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance, | 576 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance, |
551 const base::DictionaryValue* schema, | 577 const base::DictionaryValue* schema, |
552 const std::string& path) { | 578 const std::string& path) { |
553 const base::DictionaryValue* properties = NULL; | 579 const base::DictionaryValue* properties = NULL; |
554 schema->GetDictionary(schema::kProperties, &properties); | 580 if (schema->GetDictionary(schema::kProperties, &properties)) { |
555 if (properties) { | |
556 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); | 581 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); |
557 it.Advance()) { | 582 it.Advance()) { |
558 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key()); | 583 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key()); |
559 const base::DictionaryValue* prop_schema = NULL; | 584 const base::DictionaryValue* prop_schema = NULL; |
560 CHECK(it.value().GetAsDictionary(&prop_schema)); | 585 CHECK(it.value().GetAsDictionary(&prop_schema)); |
561 | 586 |
562 const base::Value* prop_value = NULL; | 587 const base::Value* prop_value = NULL; |
563 if (instance->Get(it.key(), &prop_value)) { | 588 if (instance->Get(it.key(), &prop_value)) { |
564 Validate(prop_value, prop_schema, prop_path); | 589 Validate(prop_value, prop_schema, prop_path); |
565 } else { | 590 } else { |
566 // Properties are required unless there is an optional field set to | 591 // Properties are required unless there is an optional field set to |
567 // 'true'. | 592 // 'true'. |
568 bool is_optional = false; | 593 bool is_optional = false; |
569 prop_schema->GetBoolean(schema::kOptional, &is_optional); | 594 prop_schema->GetBoolean(schema::kOptional, &is_optional); |
570 if (!is_optional) { | 595 if (!is_optional) { |
571 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); | 596 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); |
572 } | 597 } |
573 } | 598 } |
574 } | 599 } |
575 } | 600 } |
576 | 601 |
577 const base::DictionaryValue* additional_properties_schema = NULL; | 602 const base::DictionaryValue* additional_properties_schema = NULL; |
578 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) | 603 bool allow_any_additional_properties = |
579 return; | 604 SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema); |
580 | 605 |
581 // Validate additional properties. | 606 const base::DictionaryValue* pattern_properties = NULL; |
607 ScopedVector<re2::RE2> pattern_properties_pattern; | |
608 std::vector<const base::DictionaryValue*> pattern_properties_schema; | |
609 | |
610 if (schema->GetDictionary(schema::kPatternProperties, &pattern_properties)) { | |
611 for (base::DictionaryValue::Iterator it(*pattern_properties); !it.IsAtEnd(); | |
612 it.Advance()) { | |
613 re2::RE2* prop_pattern = new re2::RE2(it.key()); | |
614 if (!prop_pattern->ok()) { | |
615 LOG(WARNING) << "Regular expression /" << it.key() | |
616 << "/ is invalid: " << prop_pattern->error() << "."; | |
617 errors_.push_back( | |
618 Error(path, | |
619 FormatErrorMessage( | |
620 kInvalidRegex, it.key(), prop_pattern->error()))); | |
621 continue; | |
622 } | |
623 const base::DictionaryValue* prop_schema = NULL; | |
624 CHECK(it.value().GetAsDictionary(&prop_schema)); | |
625 pattern_properties_pattern.push_back(prop_pattern); | |
626 pattern_properties_schema.push_back(prop_schema); | |
627 } | |
628 } | |
629 | |
630 // Validate pattern properties and additional properties. | |
582 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); | 631 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); |
583 it.Advance()) { | 632 it.Advance()) { |
584 if (properties && properties->HasKey(it.key())) | 633 std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); |
634 | |
635 bool found_matching_pattern = false; | |
636 for (size_t index = 0; index < pattern_properties_pattern.size(); ++index) { | |
637 if (re2::RE2::PartialMatch(it.key(), | |
638 *pattern_properties_pattern[index])) { | |
639 found_matching_pattern = true; | |
640 Validate(&it.value(), pattern_properties_schema[index], prop_path); | |
641 break; | |
642 } | |
643 } | |
644 | |
645 if (found_matching_pattern || allow_any_additional_properties || | |
646 (properties && properties->HasKey(it.key()))) | |
585 continue; | 647 continue; |
586 | 648 |
587 std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); | |
588 if (!additional_properties_schema) { | 649 if (!additional_properties_schema) { |
589 errors_.push_back(Error(prop_path, kUnexpectedProperty)); | 650 errors_.push_back(Error(prop_path, kUnexpectedProperty)); |
590 } else { | 651 } else { |
591 Validate(&it.value(), additional_properties_schema, prop_path); | 652 Validate(&it.value(), additional_properties_schema, prop_path); |
592 } | 653 } |
593 } | 654 } |
594 } | 655 } |
595 | 656 |
596 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, | 657 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, |
597 const base::DictionaryValue* schema, | 658 const base::DictionaryValue* schema, |
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
700 | 761 |
701 int max_length = 0; | 762 int max_length = 0; |
702 if (schema->GetInteger(schema::kMaxLength, &max_length)) { | 763 if (schema->GetInteger(schema::kMaxLength, &max_length)) { |
703 CHECK(max_length >= 0); | 764 CHECK(max_length >= 0); |
704 if (value.size() > static_cast<size_t>(max_length)) { | 765 if (value.size() > static_cast<size_t>(max_length)) { |
705 errors_.push_back(Error(path, FormatErrorMessage( | 766 errors_.push_back(Error(path, FormatErrorMessage( |
706 kStringMaxLength, base::IntToString(max_length)))); | 767 kStringMaxLength, base::IntToString(max_length)))); |
707 } | 768 } |
708 } | 769 } |
709 | 770 |
710 CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported."; | 771 std::string pattern; |
772 if (schema->GetString(schema::kPattern, &pattern)) { | |
773 re2::RE2 compiled_regex(pattern); | |
774 if (!compiled_regex.ok()) { | |
775 LOG(WARNING) << "Regular expression /" << pattern | |
776 << "/ is invalid: " << compiled_regex.error() << "."; | |
777 errors_.push_back(Error( | |
778 path, | |
779 FormatErrorMessage(kInvalidRegex, pattern, compiled_regex.error()))); | |
780 } else if (!re2::RE2::PartialMatch(value, compiled_regex)) { | |
781 errors_.push_back( | |
782 Error(path, FormatErrorMessage(kStringPattern, pattern))); | |
783 } | |
784 } | |
711 } | 785 } |
712 | 786 |
713 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, | 787 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, |
714 const base::DictionaryValue* schema, | 788 const base::DictionaryValue* schema, |
715 const std::string& path) { | 789 const std::string& path) { |
716 double value = GetNumberValue(instance); | 790 double value = GetNumberValue(instance); |
717 | 791 |
718 // TODO(aa): It would be good to test that the double is not infinity or nan, | 792 // 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. | 793 // but isnan and isinf aren't defined on Windows. |
720 | 794 |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
761 | 835 |
762 if (*additional_properties_schema) { | 836 if (*additional_properties_schema) { |
763 std::string additional_properties_type(schema::kAny); | 837 std::string additional_properties_type(schema::kAny); |
764 CHECK((*additional_properties_schema)->GetString( | 838 CHECK((*additional_properties_schema)->GetString( |
765 schema::kType, &additional_properties_type)); | 839 schema::kType, &additional_properties_type)); |
766 return additional_properties_type == schema::kAny; | 840 return additional_properties_type == schema::kAny; |
767 } else { | 841 } else { |
768 return default_allow_additional_properties_; | 842 return default_allow_additional_properties_; |
769 } | 843 } |
770 } | 844 } |
OLD | NEW |