Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "chrome/common/json_schema/json_schema_validator.h" | 5 #include "chrome/common/json_schema/json_schema_validator.h" |
| 6 | 6 |
| 7 #include <algorithm> | |
| 7 #include <cfloat> | 8 #include <cfloat> |
| 8 #include <cmath> | 9 #include <cmath> |
| 9 | 10 |
| 11 #include "base/json/json_reader.h" | |
| 10 #include "base/string_util.h" | 12 #include "base/string_util.h" |
| 13 #include "base/stringprintf.h" | |
| 11 #include "base/strings/string_number_conversions.h" | 14 #include "base/strings/string_number_conversions.h" |
| 12 #include "base/values.h" | 15 #include "base/values.h" |
| 13 #include "chrome/common/json_schema/json_schema_constants.h" | 16 #include "chrome/common/json_schema/json_schema_constants.h" |
| 14 #include "ui/base/l10n/l10n_util.h" | 17 #include "ui/base/l10n/l10n_util.h" |
| 15 | 18 |
| 16 namespace schema = json_schema_constants; | 19 namespace schema = json_schema_constants; |
| 17 | 20 |
| 18 namespace { | 21 namespace { |
| 19 | 22 |
| 20 double GetNumberValue(const Value* value) { | 23 double GetNumberValue(const Value* value) { |
| 21 double result = 0; | 24 double result = 0; |
| 22 CHECK(value->GetAsDouble(&result)) | 25 CHECK(value->GetAsDouble(&result)) |
| 23 << "Unexpected value type: " << value->GetType(); | 26 << "Unexpected value type: " << value->GetType(); |
| 24 return result; | 27 return result; |
| 25 } | 28 } |
| 26 | 29 |
| 30 bool IsValidType(const std::string& type) { | |
| 31 static const char* kValidTypes[] = { | |
| 32 schema::kAny, | |
| 33 schema::kArray, | |
| 34 schema::kBoolean, | |
| 35 schema::kInteger, | |
| 36 schema::kNull, | |
| 37 schema::kNumber, | |
| 38 schema::kObject, | |
| 39 schema::kString, | |
| 40 }; | |
| 41 const char** end = kValidTypes + arraysize(kValidTypes); | |
| 42 return std::find(kValidTypes, end, type) != end; | |
| 43 } | |
| 44 | |
| 45 // Maps a schema attribute name to its expected type. | |
| 46 struct ExpectedType { | |
| 47 const char* key; | |
| 48 base::Value::Type type; | |
| 49 }; | |
| 50 | |
| 51 // Helper for std::lower_bound. | |
| 52 bool CompareToString(const ExpectedType& entry, const std::string& key) { | |
| 53 return entry.key < key; | |
| 54 } | |
| 55 | |
| 56 bool IsValidSchema(const base::DictionaryValue* dict, std::string* error) { | |
| 57 // This array must be sorted, so that std::lower_bound can perform a | |
| 58 // binary search. | |
| 59 static const ExpectedType kExpectedTypes[] = { | |
| 60 // Note: kRef == "$ref", kSchema == "$schema" | |
| 61 { schema::kRef, base::Value::TYPE_STRING }, | |
| 62 { schema::kSchema, base::Value::TYPE_STRING }, | |
| 63 | |
| 64 { schema::kAdditionalProperties, base::Value::TYPE_DICTIONARY }, | |
| 65 { schema::kChoices, base::Value::TYPE_LIST }, | |
| 66 { schema::kDescription, base::Value::TYPE_STRING }, | |
| 67 { schema::kEnum, base::Value::TYPE_LIST }, | |
| 68 { schema::kId, base::Value::TYPE_STRING }, | |
| 69 { schema::kMaxItems, base::Value::TYPE_INTEGER }, | |
| 70 { schema::kMaxLength, base::Value::TYPE_INTEGER }, | |
| 71 { schema::kMaximum, base::Value::TYPE_DOUBLE }, | |
| 72 { schema::kMinItems, base::Value::TYPE_INTEGER }, | |
| 73 { schema::kMinLength, base::Value::TYPE_INTEGER }, | |
| 74 { schema::kMinimum, base::Value::TYPE_DOUBLE }, | |
| 75 { schema::kOptional, base::Value::TYPE_BOOLEAN }, | |
| 76 { schema::kProperties, base::Value::TYPE_DICTIONARY }, | |
| 77 { schema::kTitle, base::Value::TYPE_STRING }, | |
| 78 }; | |
| 79 | |
| 80 bool has_type = false; | |
| 81 const base::ListValue* list_value = NULL; | |
| 82 const base::DictionaryValue* dictionary_value = NULL; | |
| 83 std::string string_value; | |
| 84 | |
| 85 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | |
| 86 // Validate the "type" attribute, which may be a string or a list. | |
| 87 if (it.key() == schema::kType) { | |
| 88 switch (it.value().GetType()) { | |
| 89 case base::Value::TYPE_STRING: | |
| 90 it.value().GetAsString(&string_value); | |
| 91 if (!IsValidType(string_value)) { | |
| 92 *error = "Invalid value for type attribute"; | |
| 93 return false; | |
| 94 } | |
| 95 break; | |
| 96 case base::Value::TYPE_LIST: | |
| 97 it.value().GetAsList(&list_value); | |
| 98 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
| 99 if (!list_value->GetString(i, &string_value) || | |
| 100 !IsValidType(string_value)) { | |
| 101 *error = "Invalid value for type attribute"; | |
| 102 return false; | |
| 103 } | |
| 104 } | |
| 105 break; | |
| 106 default: | |
| 107 *error = "Invalid value for type attribute"; | |
| 108 return false; | |
| 109 } | |
| 110 has_type = true; | |
| 111 continue; | |
| 112 } | |
| 113 | |
| 114 // Validate the "items" attribute, which is a schema or a list of schemas. | |
| 115 if (it.key() == schema::kItems) { | |
| 116 if (it.value().GetAsDictionary(&dictionary_value)) { | |
| 117 if (!IsValidSchema(dictionary_value, error)) { | |
| 118 DCHECK(!error->empty()); | |
| 119 return false; | |
| 120 } | |
| 121 } else if (it.value().GetAsList(&list_value)) { | |
| 122 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
| 123 if (!list_value->GetDictionary(i, &dictionary_value)) { | |
| 124 *error = base::StringPrintf( | |
| 125 "Invalid entry in items attribute at index %d", | |
| 126 static_cast<int>(i)); | |
|
not at google - send to devlin
2013/05/20 21:08:52
can you use %u and avoid the static_cast? or does
Joao da Silva
2013/05/21 15:58:28
The problem is that size_t is 32 bits in some plat
| |
| 127 return false; | |
| 128 } | |
| 129 if (!IsValidSchema(dictionary_value, error)) { | |
| 130 DCHECK(!error->empty()); | |
| 131 return false; | |
| 132 } | |
| 133 } | |
| 134 } else { | |
| 135 *error = "Invalid value for items attribute"; | |
| 136 return false; | |
| 137 } | |
| 138 continue; | |
| 139 } | |
| 140 | |
| 141 // All the other attributes have a single valid type. | |
| 142 const ExpectedType* end = kExpectedTypes + arraysize(kExpectedTypes); | |
| 143 const ExpectedType* entry = std::lower_bound( | |
| 144 kExpectedTypes, end, it.key(), CompareToString); | |
| 145 if (entry == end || entry->key != it.key()) { | |
| 146 *error = base::StringPrintf("Invalid attribute %s", it.key().c_str()); | |
| 147 return false; | |
| 148 } | |
| 149 if (!it.value().IsType(entry->type)) { | |
| 150 *error = base::StringPrintf("Invalid value for %s attribute", | |
| 151 it.key().c_str()); | |
| 152 return false; | |
| 153 } | |
| 154 | |
| 155 // base::Value::TYPE_INTEGER attributes must be >= 0. | |
| 156 // This applies to "minItems", "maxItems", "minLength" and "maxLength". | |
| 157 if (it.value().IsType(base::Value::TYPE_INTEGER)) { | |
| 158 int integer_value; | |
| 159 it.value().GetAsInteger(&integer_value); | |
| 160 if (integer_value < 0) { | |
| 161 *error = base::StringPrintf("Value of %s must be >= 0, got %d", | |
| 162 it.key().c_str(), integer_value); | |
| 163 return false; | |
| 164 } | |
| 165 } | |
| 166 | |
| 167 // Validate the "properties" attribute. Each entry maps a key to a schema. | |
| 168 if (it.key() == schema::kProperties) { | |
| 169 it.value().GetAsDictionary(&dictionary_value); | |
| 170 for (base::DictionaryValue::Iterator it(*dictionary_value); | |
| 171 !it.IsAtEnd(); it.Advance()) { | |
| 172 if (!it.value().GetAsDictionary(&dictionary_value)) { | |
| 173 *error = "Invalid value for properties attribute"; | |
| 174 return false; | |
| 175 } | |
| 176 if (!IsValidSchema(dictionary_value, error)) { | |
| 177 DCHECK(!error->empty()); | |
| 178 return false; | |
| 179 } | |
| 180 } | |
| 181 } | |
| 182 | |
| 183 // Validate "additionalProperties" attribute, which is a schema. | |
| 184 if (it.key() == schema::kAdditionalProperties) { | |
| 185 it.value().GetAsDictionary(&dictionary_value); | |
| 186 if (!IsValidSchema(dictionary_value, error)) { | |
| 187 DCHECK(!error->empty()); | |
| 188 return false; | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 // Validate the values contained in an "enum" attribute. | |
| 193 if (it.key() == schema::kEnum) { | |
| 194 it.value().GetAsList(&list_value); | |
| 195 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
| 196 const base::Value* value = NULL; | |
| 197 list_value->Get(i, &value); | |
| 198 switch (value->GetType()) { | |
| 199 case base::Value::TYPE_NULL: | |
| 200 case base::Value::TYPE_BOOLEAN: | |
| 201 case base::Value::TYPE_INTEGER: | |
| 202 case base::Value::TYPE_DOUBLE: | |
| 203 case base::Value::TYPE_STRING: | |
| 204 break; | |
| 205 default: | |
| 206 *error = "Invalid value in enum attribute"; | |
| 207 return false; | |
| 208 } | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 // Validate the schemas contained in a "choices" attribute. | |
| 213 if (it.key() == schema::kChoices) { | |
| 214 it.value().GetAsList(&list_value); | |
| 215 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
| 216 if (!list_value->GetDictionary(i, &dictionary_value)) { | |
| 217 *error = "Invalid choices attribute"; | |
| 218 return false; | |
| 219 } | |
| 220 if (!IsValidSchema(dictionary_value, error)) { | |
| 221 DCHECK(!error->empty()); | |
| 222 return false; | |
| 223 } | |
| 224 } | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 if (!has_type) { | |
| 229 *error = "Schema must have a type attribute"; | |
| 230 return false; | |
| 231 } | |
| 232 | |
| 233 return true; | |
| 234 } | |
| 235 | |
| 27 } // namespace | 236 } // namespace |
| 28 | 237 |
| 29 | 238 |
| 30 JSONSchemaValidator::Error::Error() { | 239 JSONSchemaValidator::Error::Error() { |
| 31 } | 240 } |
| 32 | 241 |
| 33 JSONSchemaValidator::Error::Error(const std::string& message) | 242 JSONSchemaValidator::Error::Error(const std::string& message) |
| 34 : path(message) { | 243 : path(message) { |
| 35 } | 244 } |
| 36 | 245 |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 114 // static | 323 // static |
| 115 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, | 324 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, |
| 116 const std::string& s1, | 325 const std::string& s1, |
| 117 const std::string& s2) { | 326 const std::string& s2) { |
| 118 std::string ret_val = format; | 327 std::string ret_val = format; |
| 119 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); | 328 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); |
| 120 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); | 329 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); |
| 121 return ret_val; | 330 return ret_val; |
| 122 } | 331 } |
| 123 | 332 |
| 333 // static | |
| 334 scoped_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema( | |
| 335 const std::string& schema, | |
| 336 std::string* error) { | |
| 337 base::JSONParserOptions options = base::JSON_PARSE_RFC; | |
| 338 scoped_ptr<base::Value> json( | |
| 339 base::JSONReader::ReadAndReturnError(schema, options, NULL, error)); | |
| 340 if (!json) | |
| 341 return scoped_ptr<base::DictionaryValue>(); | |
| 342 base::DictionaryValue* dict = NULL; | |
| 343 if (!json->GetAsDictionary(&dict)) { | |
| 344 *error = "Schema must be a JSON object"; | |
| 345 return scoped_ptr<base::DictionaryValue>(); | |
| 346 } | |
| 347 if (!::IsValidSchema(dict, error)) | |
| 348 return scoped_ptr<base::DictionaryValue>(); | |
| 349 ignore_result(json.release()); | |
| 350 return make_scoped_ptr(dict); | |
| 351 } | |
| 352 | |
| 124 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema) | 353 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema) |
| 125 : schema_root_(schema), default_allow_additional_properties_(false) { | 354 : schema_root_(schema), default_allow_additional_properties_(false) { |
| 126 } | 355 } |
| 127 | 356 |
| 128 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema, | 357 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema, |
| 129 ListValue* types) | 358 ListValue* types) |
| 130 : schema_root_(schema), default_allow_additional_properties_(false) { | 359 : schema_root_(schema), default_allow_additional_properties_(false) { |
| 131 if (!types) | 360 if (!types) |
| 132 return; | 361 return; |
| 133 | 362 |
| (...skipping 353 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 487 | 716 |
| 488 if (*additional_properties_schema) { | 717 if (*additional_properties_schema) { |
| 489 std::string additional_properties_type(schema::kAny); | 718 std::string additional_properties_type(schema::kAny); |
| 490 CHECK((*additional_properties_schema)->GetString( | 719 CHECK((*additional_properties_schema)->GetString( |
| 491 schema::kType, &additional_properties_type)); | 720 schema::kType, &additional_properties_type)); |
| 492 return additional_properties_type == schema::kAny; | 721 return additional_properties_type == schema::kAny; |
| 493 } else { | 722 } else { |
| 494 return default_allow_additional_properties_; | 723 return default_allow_additional_properties_; |
| 495 } | 724 } |
| 496 } | 725 } |
| OLD | NEW |