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 <cfloat> | 7 #include <cfloat> |
| 8 #include <cmath> | 8 #include <cmath> |
| 9 | 9 |
| 10 #include "base/json/json_reader.h" | |
| 10 #include "base/string_util.h" | 11 #include "base/string_util.h" |
| 12 #include "base/stringprintf.h" | |
| 11 #include "base/strings/string_number_conversions.h" | 13 #include "base/strings/string_number_conversions.h" |
| 12 #include "base/values.h" | 14 #include "base/values.h" |
| 13 #include "chrome/common/json_schema/json_schema_constants.h" | 15 #include "chrome/common/json_schema/json_schema_constants.h" |
| 14 #include "ui/base/l10n/l10n_util.h" | 16 #include "ui/base/l10n/l10n_util.h" |
| 15 | 17 |
| 16 namespace schema = json_schema_constants; | 18 namespace schema = json_schema_constants; |
| 17 | 19 |
| 18 namespace { | 20 namespace { |
| 19 | 21 |
| 20 double GetNumberValue(const Value* value) { | 22 double GetNumberValue(const Value* value) { |
| 21 double result = 0; | 23 double result = 0; |
| 22 CHECK(value->GetAsDouble(&result)) | 24 CHECK(value->GetAsDouble(&result)) |
| 23 << "Unexpected value type: " << value->GetType(); | 25 << "Unexpected value type: " << value->GetType(); |
| 24 return result; | 26 return result; |
| 25 } | 27 } |
| 26 | 28 |
| 29 bool IsValidType(const std::string& type) { | |
| 30 static const char* kValidTypes[] = { | |
| 31 schema::kAny, | |
| 32 schema::kArray, | |
| 33 schema::kBoolean, | |
| 34 schema::kInteger, | |
| 35 schema::kNull, | |
| 36 schema::kNumber, | |
| 37 schema::kObject, | |
| 38 schema::kString, | |
| 39 }; | |
| 40 const char** end = kValidTypes + arraysize(kValidTypes); | |
| 41 return std::find(kValidTypes, end, type) != end; | |
| 42 } | |
| 43 | |
| 44 bool IsValidSchema(const base::DictionaryValue* dict, std::string* error) { | |
| 45 static const struct { | |
| 46 const char* key; | |
| 47 base::Value::Type type; | |
| 48 } kExpectedTypes[] = { | |
| 49 { schema::kAdditionalProperties, base::Value::TYPE_DICTIONARY }, | |
| 50 { schema::kProperties, base::Value::TYPE_DICTIONARY }, | |
| 51 | |
| 52 { schema::kMinimum, base::Value::TYPE_DOUBLE }, | |
| 53 { schema::kMaximum, base::Value::TYPE_DOUBLE }, | |
| 54 | |
| 55 // All of these must be >= 0. | |
| 56 { schema::kMinItems, base::Value::TYPE_INTEGER }, | |
| 57 { schema::kMaxItems, base::Value::TYPE_INTEGER }, | |
| 58 { schema::kMinLength, base::Value::TYPE_INTEGER }, | |
| 59 { schema::kMaxLength, base::Value::TYPE_INTEGER }, | |
| 60 | |
| 61 { schema::kEnum, base::Value::TYPE_LIST }, | |
| 62 { schema::kChoices, base::Value::TYPE_LIST }, | |
| 63 | |
| 64 { schema::kId, base::Value::TYPE_STRING }, | |
| 65 { schema::kRef, base::Value::TYPE_STRING }, | |
| 66 { schema::kSchema, base::Value::TYPE_STRING }, | |
| 67 | |
| 68 { schema::kOptional, base::Value::TYPE_BOOLEAN }, | |
| 69 }; | |
| 70 | |
| 71 const base::Value* value = NULL; | |
| 72 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kExpectedTypes); ++i) { | |
| 73 if (!dict->Get(kExpectedTypes[i].key, &value)) | |
| 74 continue; | |
|
not at google - send to devlin
2013/05/16 17:08:20
the problem with this approach is that it won't in
Joao da Silva
2013/05/19 13:16:29
Good idea, done. "type" and "items" is still speci
| |
| 75 | |
| 76 if (!value->IsType(kExpectedTypes[i].type)) { | |
| 77 *error = base::StringPrintf("Invalid value for %s attribute", | |
| 78 kExpectedTypes[i].key); | |
| 79 return false; | |
| 80 } | |
| 81 | |
| 82 // This applies to "minItems", "maxItems", "minLength" and "maxLength". | |
| 83 int integer_value; | |
| 84 if (value->GetAsInteger(&integer_value) && integer_value < 0) { | |
| 85 *error = | |
| 86 base::StringPrintf("Value of %s must be >= 0", kExpectedTypes[i].key); | |
|
not at google - send to devlin
2013/05/16 17:08:20
might as well include integer_value in here.
Joao da Silva
2013/05/19 13:16:29
Done.
| |
| 87 return false; | |
| 88 } | |
| 89 } | |
| 90 | |
| 91 // Validate "type" attribute. | |
| 92 std::string string_value; | |
| 93 const base::ListValue* list_value = NULL; | |
| 94 if (!dict->Get(schema::kType, &value)) { | |
| 95 *error = "Schema must have a type attribute"; | |
| 96 return false; | |
| 97 } else if (value->GetAsString(&string_value)) { | |
|
not at google - send to devlin
2013/05/16 17:08:20
else unnecessary after a return.
Joao da Silva
2013/05/19 13:16:29
Done.
| |
| 98 if (!IsValidType(string_value)) { | |
| 99 *error = "Invalid value for type attribute"; | |
| 100 return false; | |
| 101 } | |
| 102 } else if (value->GetAsList(&list_value)) { | |
|
not at google - send to devlin
2013/05/16 17:08:20
a switch might be nicer
Joao da Silva
2013/05/19 13:16:29
Done.
| |
| 103 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
| 104 if (!list_value->GetString(i, &string_value) || | |
| 105 !IsValidType(string_value)) { | |
| 106 *error = "Invalid value for type attribute"; | |
| 107 return false; | |
| 108 } | |
| 109 } | |
| 110 } else { | |
| 111 *error = "Invalid value for type attribute"; | |
| 112 return false; | |
| 113 } | |
| 114 | |
| 115 // Validate "properties" attribute. Each entry maps a key to a schema. | |
| 116 const base::DictionaryValue* dictionary_value = NULL; | |
| 117 if (dict->GetDictionary(schema::kProperties, &dictionary_value)) { | |
| 118 for (base::DictionaryValue::Iterator it(*dictionary_value); | |
| 119 !it.IsAtEnd(); it.Advance()) { | |
| 120 if (!it.value().GetAsDictionary(&dictionary_value) || | |
| 121 !IsValidSchema(dictionary_value, error)) { | |
| 122 return false; | |
|
not at google - send to devlin
2013/05/16 17:08:20
all these times you validate IsValidSchema and the
Joao da Silva
2013/05/19 13:16:29
That's a good idea, and there's a bug here too: if
| |
| 123 } | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 // Validate "additionalProperties" attribute, which is a schema. | |
| 128 if (dict->GetDictionary(schema::kAdditionalProperties, &dictionary_value) && | |
| 129 !IsValidSchema(dictionary_value, error)) { | |
| 130 return false; | |
| 131 } | |
| 132 | |
| 133 // Validate "items" attribute, which is a schema or a list of schemas. | |
| 134 if (dict->Get(schema::kItems, &value)) { | |
| 135 if (value->GetAsDictionary(&dictionary_value)) { | |
| 136 if (!IsValidSchema(dictionary_value, error)) | |
| 137 return false; | |
| 138 } else if (value->GetAsList(&list_value)) { | |
| 139 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
| 140 if (!list_value->GetDictionary(i, &dictionary_value) || | |
| 141 !IsValidSchema(dictionary_value, error)) { | |
| 142 return false; | |
| 143 } | |
| 144 } | |
| 145 } else { | |
| 146 *error = "Invalid value for items attribute"; | |
| 147 return false; | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 // Validate the values contained in an "enum" attribute. | |
| 152 if (dict->GetList(schema::kEnum, &list_value)) { | |
| 153 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
| 154 list_value->Get(i, &value); | |
| 155 switch (value->GetType()) { | |
| 156 case base::Value::TYPE_NULL: case base::Value::TYPE_BOOLEAN: | |
| 157 case base::Value::TYPE_STRING: case base::Value::TYPE_INTEGER: | |
| 158 case base::Value::TYPE_DOUBLE: | |
|
not at google - send to devlin
2013/05/16 17:08:20
nit: 1 case per line, this looks pretty odd.
Joao da Silva
2013/05/19 13:16:29
Done.
| |
| 159 break; | |
| 160 default: | |
| 161 *error = "Invalid value in enum attribute"; | |
| 162 return false; | |
| 163 } | |
| 164 } | |
| 165 } | |
| 166 | |
| 167 // Validate the schemas contained in a "choices" attribute. | |
| 168 if (dict->GetList(schema::kChoices, &list_value)) { | |
| 169 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
| 170 if (!list_value->GetDictionary(i, &dictionary_value)) { | |
| 171 *error = "Invalid choices attribute"; | |
| 172 return false; | |
| 173 } else if (!IsValidSchema(dictionary_value, error)) { | |
|
not at google - send to devlin
2013/05/16 17:08:20
else unnecessary after a return
Joao da Silva
2013/05/19 13:16:29
Done.
| |
| 174 return false; | |
| 175 } | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 | |
| 180 // Unsupported attributes. | |
|
not at google - send to devlin
2013/05/16 17:08:20
if you do the map thing right at the top then this
Joao da Silva
2013/05/19 13:16:29
Yep, done.
| |
| 181 if (dict->HasKey(schema::kPattern)) { | |
| 182 *error = "pattern attribute is not supported"; | |
| 183 return false; | |
| 184 } | |
| 185 | |
| 186 return true; | |
| 187 } | |
| 188 | |
| 27 } // namespace | 189 } // namespace |
| 28 | 190 |
| 29 | 191 |
| 30 JSONSchemaValidator::Error::Error() { | 192 JSONSchemaValidator::Error::Error() { |
| 31 } | 193 } |
| 32 | 194 |
| 33 JSONSchemaValidator::Error::Error(const std::string& message) | 195 JSONSchemaValidator::Error::Error(const std::string& message) |
| 34 : path(message) { | 196 : path(message) { |
| 35 } | 197 } |
| 36 | 198 |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 114 // static | 276 // static |
| 115 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, | 277 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, |
| 116 const std::string& s1, | 278 const std::string& s1, |
| 117 const std::string& s2) { | 279 const std::string& s2) { |
| 118 std::string ret_val = format; | 280 std::string ret_val = format; |
| 119 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); | 281 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); |
| 120 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); | 282 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); |
| 121 return ret_val; | 283 return ret_val; |
| 122 } | 284 } |
| 123 | 285 |
| 286 // static | |
| 287 scoped_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema( | |
| 288 const std::string& schema, | |
| 289 std::string* error) { | |
| 290 base::JSONParserOptions options = base::JSON_PARSE_RFC; | |
| 291 scoped_ptr<base::Value> json( | |
| 292 base::JSONReader::ReadAndReturnError(schema, options, NULL, error)); | |
| 293 if (!json) | |
| 294 return scoped_ptr<base::DictionaryValue>(); | |
| 295 base::DictionaryValue* dict = NULL; | |
| 296 if (!json->GetAsDictionary(&dict)) { | |
| 297 *error = "Schema must be a JSON object"; | |
| 298 return scoped_ptr<base::DictionaryValue>(); | |
| 299 } | |
| 300 if (!::IsValidSchema(dict, error)) | |
| 301 return scoped_ptr<base::DictionaryValue>(); | |
| 302 ignore_result(json.release()); | |
| 303 return make_scoped_ptr(dict); | |
|
not at google - send to devlin
2013/05/16 17:08:20
does just
return json.PassAs<base::DictionaryValu
Joao da Silva
2013/05/19 13:16:29
It doesn't; PassAs<>() can only do upcasts.
| |
| 304 } | |
| 305 | |
| 124 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema) | 306 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema) |
| 125 : schema_root_(schema), default_allow_additional_properties_(false) { | 307 : schema_root_(schema), default_allow_additional_properties_(false) { |
| 126 } | 308 } |
| 127 | 309 |
| 128 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema, | 310 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema, |
| 129 ListValue* types) | 311 ListValue* types) |
| 130 : schema_root_(schema), default_allow_additional_properties_(false) { | 312 : schema_root_(schema), default_allow_additional_properties_(false) { |
| 131 if (!types) | 313 if (!types) |
| 132 return; | 314 return; |
| 133 | 315 |
| (...skipping 353 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 487 | 669 |
| 488 if (*additional_properties_schema) { | 670 if (*additional_properties_schema) { |
| 489 std::string additional_properties_type(schema::kAny); | 671 std::string additional_properties_type(schema::kAny); |
| 490 CHECK((*additional_properties_schema)->GetString( | 672 CHECK((*additional_properties_schema)->GetString( |
| 491 schema::kType, &additional_properties_type)); | 673 schema::kType, &additional_properties_type)); |
| 492 return additional_properties_type == schema::kAny; | 674 return additional_properties_type == schema::kAny; |
| 493 } else { | 675 } else { |
| 494 return default_allow_additional_properties_; | 676 return default_allow_additional_properties_; |
| 495 } | 677 } |
| 496 } | 678 } |
| OLD | NEW |