| 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 reasons. Instead, invalid regular |
| 211 // expressions will be caught as validation errors in Validate(). |
| 212 if (it.key() == schema::kPatternProperties) { |
| 213 it.value().GetAsDictionary(&dictionary_value); |
| 214 for (base::DictionaryValue::Iterator iter(*dictionary_value); |
| 215 !iter.IsAtEnd(); iter.Advance()) { |
| 216 if (!iter.value().GetAsDictionary(&dictionary_value)) { |
| 217 *error = "patternProperties must be a dictionary"; |
| 218 return false; |
| 219 } |
| 220 if (!IsValidSchema(dictionary_value, options, error)) { |
| 196 DCHECK(!error->empty()); | 221 DCHECK(!error->empty()); |
| 197 return false; | 222 return false; |
| 198 } | 223 } |
| 199 } | 224 } |
| 200 } | 225 } |
| 201 | 226 |
| 202 // Validate "additionalProperties" attribute, which is a schema. | 227 // Validate "additionalProperties" attribute, which is a schema. |
| 203 if (it.key() == schema::kAdditionalProperties) { | 228 if (it.key() == schema::kAdditionalProperties) { |
| 204 it.value().GetAsDictionary(&dictionary_value); | 229 it.value().GetAsDictionary(&dictionary_value); |
| 205 if (!IsValidSchema(dictionary_value, options, error)) { | 230 if (!IsValidSchema(dictionary_value, options, error)) { |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 301 const char JSONSchemaValidator::kStringPattern[] = | 326 const char JSONSchemaValidator::kStringPattern[] = |
| 302 "String must match the pattern: *."; | 327 "String must match the pattern: *."; |
| 303 const char JSONSchemaValidator::kNumberMinimum[] = | 328 const char JSONSchemaValidator::kNumberMinimum[] = |
| 304 "Value must not be less than *."; | 329 "Value must not be less than *."; |
| 305 const char JSONSchemaValidator::kNumberMaximum[] = | 330 const char JSONSchemaValidator::kNumberMaximum[] = |
| 306 "Value must not be greater than *."; | 331 "Value must not be greater than *."; |
| 307 const char JSONSchemaValidator::kInvalidType[] = | 332 const char JSONSchemaValidator::kInvalidType[] = |
| 308 "Expected '*' but got '*'."; | 333 "Expected '*' but got '*'."; |
| 309 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] = | 334 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] = |
| 310 "Expected 'integer' but got 'number', consider using Math.round()."; | 335 "Expected 'integer' but got 'number', consider using Math.round()."; |
| 336 const char JSONSchemaValidator::kInvalidRegex[] = |
| 337 "Regular expression /*/ is invalid: *"; |
| 311 | 338 |
| 312 | 339 |
| 313 // static | 340 // static |
| 314 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) { | 341 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) { |
| 315 switch (value->GetType()) { | 342 switch (value->GetType()) { |
| 316 case base::Value::TYPE_NULL: | 343 case base::Value::TYPE_NULL: |
| 317 return schema::kNull; | 344 return schema::kNull; |
| 318 case base::Value::TYPE_BOOLEAN: | 345 case base::Value::TYPE_BOOLEAN: |
| 319 return schema::kBoolean; | 346 return schema::kBoolean; |
| 320 case base::Value::TYPE_INTEGER: | 347 case base::Value::TYPE_INTEGER: |
| (...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 544 } | 571 } |
| 545 } | 572 } |
| 546 | 573 |
| 547 errors_.push_back(Error(path, kInvalidEnum)); | 574 errors_.push_back(Error(path, kInvalidEnum)); |
| 548 } | 575 } |
| 549 | 576 |
| 550 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance, | 577 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance, |
| 551 const base::DictionaryValue* schema, | 578 const base::DictionaryValue* schema, |
| 552 const std::string& path) { | 579 const std::string& path) { |
| 553 const base::DictionaryValue* properties = NULL; | 580 const base::DictionaryValue* properties = NULL; |
| 554 schema->GetDictionary(schema::kProperties, &properties); | 581 if (schema->GetDictionary(schema::kProperties, &properties)) { |
| 555 if (properties) { | |
| 556 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); | 582 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); |
| 557 it.Advance()) { | 583 it.Advance()) { |
| 558 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key()); | 584 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key()); |
| 559 const base::DictionaryValue* prop_schema = NULL; | 585 const base::DictionaryValue* prop_schema = NULL; |
| 560 CHECK(it.value().GetAsDictionary(&prop_schema)); | 586 CHECK(it.value().GetAsDictionary(&prop_schema)); |
| 561 | 587 |
| 562 const base::Value* prop_value = NULL; | 588 const base::Value* prop_value = NULL; |
| 563 if (instance->Get(it.key(), &prop_value)) { | 589 if (instance->Get(it.key(), &prop_value)) { |
| 564 Validate(prop_value, prop_schema, prop_path); | 590 Validate(prop_value, prop_schema, prop_path); |
| 565 } else { | 591 } else { |
| 566 // Properties are required unless there is an optional field set to | 592 // Properties are required unless there is an optional field set to |
| 567 // 'true'. | 593 // 'true'. |
| 568 bool is_optional = false; | 594 bool is_optional = false; |
| 569 prop_schema->GetBoolean(schema::kOptional, &is_optional); | 595 prop_schema->GetBoolean(schema::kOptional, &is_optional); |
| 570 if (!is_optional) { | 596 if (!is_optional) { |
| 571 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); | 597 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); |
| 572 } | 598 } |
| 573 } | 599 } |
| 574 } | 600 } |
| 575 } | 601 } |
| 576 | 602 |
| 577 const base::DictionaryValue* additional_properties_schema = NULL; | 603 const base::DictionaryValue* additional_properties_schema = NULL; |
| 578 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) | 604 bool allow_any_additional_properties = |
| 579 return; | 605 SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema); |
| 580 | 606 |
| 581 // Validate additional properties. | 607 const base::DictionaryValue* pattern_properties = NULL; |
| 608 ScopedVector<re2::RE2> pattern_properties_pattern; |
| 609 std::vector<const base::DictionaryValue*> pattern_properties_schema; |
| 610 |
| 611 if (schema->GetDictionary(schema::kPatternProperties, &pattern_properties)) { |
| 612 for (base::DictionaryValue::Iterator it(*pattern_properties); !it.IsAtEnd(); |
| 613 it.Advance()) { |
| 614 re2::RE2* prop_pattern = new re2::RE2(it.key()); |
| 615 if (!prop_pattern->ok()) { |
| 616 LOG(WARNING) << "Regular expression /" << it.key() |
| 617 << "/ is invalid: " << prop_pattern->error() << "."; |
| 618 errors_.push_back( |
| 619 Error(path, |
| 620 FormatErrorMessage( |
| 621 kInvalidRegex, it.key(), prop_pattern->error()))); |
| 622 continue; |
| 623 } |
| 624 const base::DictionaryValue* prop_schema = NULL; |
| 625 CHECK(it.value().GetAsDictionary(&prop_schema)); |
| 626 pattern_properties_pattern.push_back(prop_pattern); |
| 627 pattern_properties_schema.push_back(prop_schema); |
| 628 } |
| 629 } |
| 630 |
| 631 // Validate pattern properties and additional properties. |
| 582 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); | 632 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); |
| 583 it.Advance()) { | 633 it.Advance()) { |
| 584 if (properties && properties->HasKey(it.key())) | 634 std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); |
| 635 |
| 636 bool found_matching_pattern = false; |
| 637 for (size_t index = 0; index < pattern_properties_pattern.size(); ++index) { |
| 638 if (re2::RE2::PartialMatch(it.key(), |
| 639 *pattern_properties_pattern[index])) { |
| 640 found_matching_pattern = true; |
| 641 Validate(&it.value(), pattern_properties_schema[index], prop_path); |
| 642 break; |
| 643 } |
| 644 } |
| 645 |
| 646 if (found_matching_pattern || allow_any_additional_properties || |
| 647 (properties && properties->HasKey(it.key()))) |
| 585 continue; | 648 continue; |
| 586 | 649 |
| 587 std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); | |
| 588 if (!additional_properties_schema) { | 650 if (!additional_properties_schema) { |
| 589 errors_.push_back(Error(prop_path, kUnexpectedProperty)); | 651 errors_.push_back(Error(prop_path, kUnexpectedProperty)); |
| 590 } else { | 652 } else { |
| 591 Validate(&it.value(), additional_properties_schema, prop_path); | 653 Validate(&it.value(), additional_properties_schema, prop_path); |
| 592 } | 654 } |
| 593 } | 655 } |
| 594 } | 656 } |
| 595 | 657 |
| 596 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, | 658 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, |
| 597 const base::DictionaryValue* schema, | 659 const base::DictionaryValue* schema, |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 700 | 762 |
| 701 int max_length = 0; | 763 int max_length = 0; |
| 702 if (schema->GetInteger(schema::kMaxLength, &max_length)) { | 764 if (schema->GetInteger(schema::kMaxLength, &max_length)) { |
| 703 CHECK(max_length >= 0); | 765 CHECK(max_length >= 0); |
| 704 if (value.size() > static_cast<size_t>(max_length)) { | 766 if (value.size() > static_cast<size_t>(max_length)) { |
| 705 errors_.push_back(Error(path, FormatErrorMessage( | 767 errors_.push_back(Error(path, FormatErrorMessage( |
| 706 kStringMaxLength, base::IntToString(max_length)))); | 768 kStringMaxLength, base::IntToString(max_length)))); |
| 707 } | 769 } |
| 708 } | 770 } |
| 709 | 771 |
| 710 CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported."; | 772 std::string pattern; |
| 773 if (schema->GetString(schema::kPattern, &pattern)) { |
| 774 re2::RE2 compiled_regex(pattern); |
| 775 if (!compiled_regex.ok()) { |
| 776 LOG(WARNING) << "Regular expression /" << pattern |
| 777 << "/ is invalid: " << compiled_regex.error() << "."; |
| 778 errors_.push_back(Error( |
| 779 path, |
| 780 FormatErrorMessage(kInvalidRegex, pattern, compiled_regex.error()))); |
| 781 } else if (!re2::RE2::PartialMatch(value, compiled_regex)) { |
| 782 errors_.push_back( |
| 783 Error(path, FormatErrorMessage(kStringPattern, pattern))); |
| 784 } |
| 785 } |
| 711 } | 786 } |
| 712 | 787 |
| 713 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, | 788 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, |
| 714 const base::DictionaryValue* schema, | 789 const base::DictionaryValue* schema, |
| 715 const std::string& path) { | 790 const std::string& path) { |
| 716 double value = GetNumberValue(instance); | 791 double value = GetNumberValue(instance); |
| 717 | 792 |
| 718 // TODO(aa): It would be good to test that the double is not infinity or nan, | 793 // 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. | 794 // but isnan and isinf aren't defined on Windows. |
| 720 | 795 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 761 | 836 |
| 762 if (*additional_properties_schema) { | 837 if (*additional_properties_schema) { |
| 763 std::string additional_properties_type(schema::kAny); | 838 std::string additional_properties_type(schema::kAny); |
| 764 CHECK((*additional_properties_schema)->GetString( | 839 CHECK((*additional_properties_schema)->GetString( |
| 765 schema::kType, &additional_properties_type)); | 840 schema::kType, &additional_properties_type)); |
| 766 return additional_properties_type == schema::kAny; | 841 return additional_properties_type == schema::kAny; |
| 767 } else { | 842 } else { |
| 768 return default_allow_additional_properties_; | 843 return default_allow_additional_properties_; |
| 769 } | 844 } |
| 770 } | 845 } |
| OLD | NEW |