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
3 // found in the LICENSE file.
4
5 #include "components/json_schema/json_schema_validator.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <cfloat>
11 #include <cmath>
12 #include <memory>
13 #include <vector>
14
15 #include "base/json/json_reader.h"
16 #include "base/logging.h"
17 #include "base/macros.h"
18 #include "base/memory/ptr_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/values.h"
23 #include "components/json_schema/json_schema_constants.h"
24 #include "third_party/re2/src/re2/re2.h"
25
26 namespace schema = json_schema_constants;
27
28 namespace {
29
GetNumberValue(const base::Value * value)30 double GetNumberValue(const base::Value* value) {
31 double result = 0;
32 CHECK(value->GetAsDouble(&result))
33 << "Unexpected value type: " << value->type();
34 return result;
35 }
36
IsValidType(const std::string & type)37 bool IsValidType(const std::string& type) {
38 static const char* kValidTypes[] = {
39 schema::kAny,
40 schema::kArray,
41 schema::kBoolean,
42 schema::kInteger,
43 schema::kNull,
44 schema::kNumber,
45 schema::kObject,
46 schema::kString,
47 };
48 const char** end = kValidTypes + arraysize(kValidTypes);
49 return std::find(kValidTypes, end, type) != end;
50 }
51
52 // Maps a schema attribute name to its expected type.
53 struct ExpectedType {
54 const char* key;
55 base::Value::Type type;
56 };
57
58 // Helper for std::lower_bound.
CompareToString(const ExpectedType & entry,const std::string & key)59 bool CompareToString(const ExpectedType& entry, const std::string& key) {
60 return entry.key < key;
61 }
62
63 // If |value| is a dictionary, returns the "name" attribute of |value| or NULL
64 // if |value| does not contain a "name" attribute. Otherwise, returns |value|.
ExtractNameFromDictionary(const base::Value * value)65 const base::Value* ExtractNameFromDictionary(const base::Value* value) {
66 const base::DictionaryValue* value_dict = nullptr;
67 const base::Value* name_value = nullptr;
68 if (value->GetAsDictionary(&value_dict)) {
69 value_dict->Get("name", &name_value);
70 return name_value;
71 }
72 return value;
73 }
74
IsValidSchema(const base::DictionaryValue * dict,int options,std::string * error)75 bool IsValidSchema(const base::DictionaryValue* dict,
76 int options,
77 std::string* error) {
78 // This array must be sorted, so that std::lower_bound can perform a
79 // binary search.
80 static const ExpectedType kExpectedTypes[] = {
81 // Note: kRef == "$ref", kSchema == "$schema"
82 { schema::kRef, base::Value::Type::STRING },
83 { schema::kSchema, base::Value::Type::STRING },
84
85 { schema::kAdditionalProperties, base::Value::Type::DICTIONARY },
86 { schema::kChoices, base::Value::Type::LIST },
87 { schema::kDescription, base::Value::Type::STRING },
88 { schema::kEnum, base::Value::Type::LIST },
89 { schema::kId, base::Value::Type::STRING },
90 { schema::kMaxItems, base::Value::Type::INTEGER },
91 { schema::kMaxLength, base::Value::Type::INTEGER },
92 { schema::kMaximum, base::Value::Type::DOUBLE },
93 { schema::kMinItems, base::Value::Type::INTEGER },
94 { schema::kMinLength, base::Value::Type::INTEGER },
95 { schema::kMinimum, base::Value::Type::DOUBLE },
96 { schema::kOptional, base::Value::Type::BOOLEAN },
97 { schema::kPattern, base::Value::Type::STRING },
98 { schema::kPatternProperties, base::Value::Type::DICTIONARY },
99 { schema::kProperties, base::Value::Type::DICTIONARY },
100 { schema::kRequired, base::Value::Type::LIST },
101 { schema::kTitle, base::Value::Type::STRING },
102 };
103
104 bool has_type_or_ref = false;
105 const base::ListValue* list_value = nullptr;
106 const base::DictionaryValue* dictionary_value = nullptr;
107 std::string string_value;
108
109 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
110 // Validate the "type" attribute, which may be a string or a list.
111 if (it.key() == schema::kType) {
112 switch (it.value().type()) {
113 case base::Value::Type::STRING:
114 it.value().GetAsString(&string_value);
115 if (!IsValidType(string_value)) {
116 *error = "Invalid value for type attribute";
117 return false;
118 }
119 break;
120 case base::Value::Type::LIST:
121 it.value().GetAsList(&list_value);
122 for (size_t i = 0; i < list_value->GetSize(); ++i) {
123 if (!list_value->GetString(i, &string_value) ||
124 !IsValidType(string_value)) {
125 *error = "Invalid value for type attribute";
126 return false;
127 }
128 }
129 break;
130 default:
131 *error = "Invalid value for type attribute";
132 return false;
133 }
134 has_type_or_ref = true;
135 continue;
136 }
137
138 // Validate the "items" attribute, which is a schema or a list of schemas.
139 if (it.key() == schema::kItems) {
140 if (it.value().GetAsDictionary(&dictionary_value)) {
141 if (!IsValidSchema(dictionary_value, options, error)) {
142 DCHECK(!error->empty());
143 return false;
144 }
145 } else if (it.value().GetAsList(&list_value)) {
146 for (size_t i = 0; i < list_value->GetSize(); ++i) {
147 if (!list_value->GetDictionary(i, &dictionary_value)) {
148 *error = base::StringPrintf(
149 "Invalid entry in items attribute at index %d",
150 static_cast<int>(i));
151 return false;
152 }
153 if (!IsValidSchema(dictionary_value, options, error)) {
154 DCHECK(!error->empty());
155 return false;
156 }
157 }
158 } else {
159 *error = "Invalid value for items attribute";
160 return false;
161 }
162 continue;
163 }
164
165 // All the other attributes have a single valid type.
166 const ExpectedType* end = kExpectedTypes + arraysize(kExpectedTypes);
167 const ExpectedType* entry = std::lower_bound(
168 kExpectedTypes, end, it.key(), CompareToString);
169 if (entry == end || entry->key != it.key()) {
170 if (options & JSONSchemaValidator::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES)
171 continue;
172 *error = base::StringPrintf("Invalid attribute %s", it.key().c_str());
173 return false;
174 }
175
176 // Integer can be converted to double.
177 if (!(it.value().type() == entry->type ||
178 (it.value().is_int() && entry->type == base::Value::Type::DOUBLE))) {
179 *error = base::StringPrintf("Invalid value for %s attribute",
180 it.key().c_str());
181 return false;
182 }
183
184 // base::Value::Type::INTEGER attributes must be >= 0.
185 // This applies to "minItems", "maxItems", "minLength" and "maxLength".
186 if (it.value().is_int()) {
187 int integer_value;
188 it.value().GetAsInteger(&integer_value);
189 if (integer_value < 0) {
190 *error = base::StringPrintf("Value of %s must be >= 0, got %d",
191 it.key().c_str(), integer_value);
192 return false;
193 }
194 }
195
196 // Validate the "properties" attribute. Each entry maps a key to a schema.
197 if (it.key() == schema::kProperties) {
198 it.value().GetAsDictionary(&dictionary_value);
199 for (base::DictionaryValue::Iterator iter(*dictionary_value);
200 !iter.IsAtEnd(); iter.Advance()) {
201 if (!iter.value().GetAsDictionary(&dictionary_value)) {
202 *error = "properties must be a dictionary";
203 return false;
204 }
205 if (!IsValidSchema(dictionary_value, options, error)) {
206 DCHECK(!error->empty());
207 return false;
208 }
209 }
210 }
211
212 // Validate the "patternProperties" attribute. Each entry maps a regular
213 // expression to a schema. The validity of the regular expression expression
214 // won't be checked here for performance reasons. Instead, invalid regular
215 // expressions will be caught as validation errors in Validate().
216 if (it.key() == schema::kPatternProperties) {
217 it.value().GetAsDictionary(&dictionary_value);
218 for (base::DictionaryValue::Iterator iter(*dictionary_value);
219 !iter.IsAtEnd(); iter.Advance()) {
220 if (!iter.value().GetAsDictionary(&dictionary_value)) {
221 *error = "patternProperties must be a dictionary";
222 return false;
223 }
224 if (!IsValidSchema(dictionary_value, options, error)) {
225 DCHECK(!error->empty());
226 return false;
227 }
228 }
229 }
230
231 // Validate "additionalProperties" attribute, which is a schema.
232 if (it.key() == schema::kAdditionalProperties) {
233 it.value().GetAsDictionary(&dictionary_value);
234 if (!IsValidSchema(dictionary_value, options, error)) {
235 DCHECK(!error->empty());
236 return false;
237 }
238 }
239
240 // Validate "required" attribute.
241 if (it.key() == schema::kRequired) {
242 it.value().GetAsList(&list_value);
243 for (const base::Value& value : *list_value) {
244 if (value.type() != base::Value::Type::STRING) {
245 *error = "Invalid value in 'required' attribute";
246 return false;
247 }
248 // TODO(crbug.com/856903): Check that |value| is a key in
249 // schema::kProperties
250 }
251 }
252
253 // Validate the values contained in an "enum" attribute.
254 if (it.key() == schema::kEnum) {
255 it.value().GetAsList(&list_value);
256 for (size_t i = 0; i < list_value->GetSize(); ++i) {
257 const base::Value* value = nullptr;
258 list_value->Get(i, &value);
259 // Sometimes the enum declaration is a dictionary with the enum value
260 // under "name".
261 value = ExtractNameFromDictionary(value);
262 if (!value) {
263 *error = "Invalid value in enum attribute";
264 return false;
265 }
266 switch (value->type()) {
267 case base::Value::Type::NONE:
268 case base::Value::Type::BOOLEAN:
269 case base::Value::Type::INTEGER:
270 case base::Value::Type::DOUBLE:
271 case base::Value::Type::STRING:
272 break;
273 default:
274 *error = "Invalid value in enum attribute";
275 return false;
276 }
277 }
278 }
279
280 // Validate the schemas contained in a "choices" attribute.
281 if (it.key() == schema::kChoices) {
282 it.value().GetAsList(&list_value);
283 for (size_t i = 0; i < list_value->GetSize(); ++i) {
284 if (!list_value->GetDictionary(i, &dictionary_value)) {
285 *error = "Invalid choices attribute";
286 return false;
287 }
288 if (!IsValidSchema(dictionary_value, options, error)) {
289 DCHECK(!error->empty());
290 return false;
291 }
292 }
293 }
294
295 if (it.key() == schema::kRef)
296 has_type_or_ref = true;
297 }
298
299 if (!has_type_or_ref) {
300 *error = "Schema must have a type or a $ref attribute";
301 return false;
302 }
303
304 return true;
305 }
306
307 } // namespace
308
309
Error()310 JSONSchemaValidator::Error::Error() {
311 }
312
Error(const std::string & message)313 JSONSchemaValidator::Error::Error(const std::string& message)
314 : path(message) {
315 }
316
Error(const std::string & path,const std::string & message)317 JSONSchemaValidator::Error::Error(const std::string& path,
318 const std::string& message)
319 : path(path), message(message) {
320 }
321
322
323 const char JSONSchemaValidator::kUnknownTypeReference[] =
324 "Unknown schema reference: *.";
325 const char JSONSchemaValidator::kInvalidChoice[] =
326 "Value does not match any valid type choices.";
327 const char JSONSchemaValidator::kInvalidEnum[] =
328 "Value does not match any valid enum choices.";
329 const char JSONSchemaValidator::kObjectPropertyIsRequired[] =
330 "Property is required.";
331 const char JSONSchemaValidator::kUnexpectedProperty[] =
332 "Unexpected property.";
333 const char JSONSchemaValidator::kArrayMinItems[] =
334 "Array must have at least * items.";
335 const char JSONSchemaValidator::kArrayMaxItems[] =
336 "Array must not have more than * items.";
337 const char JSONSchemaValidator::kArrayItemRequired[] =
338 "Item is required.";
339 const char JSONSchemaValidator::kStringMinLength[] =
340 "String must be at least * characters long.";
341 const char JSONSchemaValidator::kStringMaxLength[] =
342 "String must not be more than * characters long.";
343 const char JSONSchemaValidator::kStringPattern[] =
344 "String must match the pattern: *.";
345 const char JSONSchemaValidator::kNumberMinimum[] =
346 "Value must not be less than *.";
347 const char JSONSchemaValidator::kNumberMaximum[] =
348 "Value must not be greater than *.";
349 const char JSONSchemaValidator::kInvalidType[] =
350 "Expected '*' but got '*'.";
351 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] =
352 "Expected 'integer' but got 'number', consider using Math.round().";
353 const char JSONSchemaValidator::kInvalidRegex[] =
354 "Regular expression /*/ is invalid: *";
355
356
357 // static
GetJSONSchemaType(const base::Value * value)358 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) {
359 switch (value->type()) {
360 case base::Value::Type::NONE:
361 return schema::kNull;
362 case base::Value::Type::BOOLEAN:
363 return schema::kBoolean;
364 case base::Value::Type::INTEGER:
365 return schema::kInteger;
366 case base::Value::Type::DOUBLE: {
367 double double_value = 0;
368 value->GetAsDouble(&double_value);
369 if (std::abs(double_value) <= std::pow(2.0, DBL_MANT_DIG) &&
370 double_value == floor(double_value)) {
371 return schema::kInteger;
372 }
373 return schema::kNumber;
374 }
375 case base::Value::Type::STRING:
376 return schema::kString;
377 case base::Value::Type::DICTIONARY:
378 return schema::kObject;
379 case base::Value::Type::LIST:
380 return schema::kArray;
381 default:
382 NOTREACHED() << "Unexpected value type: " << value->type();
383 return std::string();
384 }
385 }
386
387 // static
FormatErrorMessage(const std::string & format,const std::string & s1)388 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format,
389 const std::string& s1) {
390 std::string ret_val = format;
391 base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
392 return ret_val;
393 }
394
395 // static
FormatErrorMessage(const std::string & format,const std::string & s1,const std::string & s2)396 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format,
397 const std::string& s1,
398 const std::string& s2) {
399 std::string ret_val = format;
400 base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
401 base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
402 return ret_val;
403 }
404
405 // static
IsValidSchema(const std::string & schema,std::string * error)406 std::unique_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema(
407 const std::string& schema,
408 std::string* error) {
409 return JSONSchemaValidator::IsValidSchema(schema, 0, error);
410 }
411
412 // static
IsValidSchema(const std::string & schema,int validator_options,std::string * error)413 std::unique_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema(
414 const std::string& schema,
415 int validator_options,
416 std::string* error) {
417 base::JSONParserOptions json_options = base::JSON_PARSE_RFC;
418 std::unique_ptr<base::Value> json = base::JSONReader::ReadAndReturnError(
419 schema, json_options, nullptr, error);
420 if (!json)
421 return std::unique_ptr<base::DictionaryValue>();
422 base::DictionaryValue* dict = nullptr;
423 if (!json->GetAsDictionary(&dict)) {
424 *error = "Schema must be a JSON object";
425 return std::unique_ptr<base::DictionaryValue>();
426 }
427 if (!::IsValidSchema(dict, validator_options, error))
428 return std::unique_ptr<base::DictionaryValue>();
429 ignore_result(json.release());
430 return base::WrapUnique(dict);
431 }
432
JSONSchemaValidator(base::DictionaryValue * schema)433 JSONSchemaValidator::JSONSchemaValidator(base::DictionaryValue* schema)
434 : schema_root_(schema), default_allow_additional_properties_(false) {
435 }
436
JSONSchemaValidator(base::DictionaryValue * schema,base::ListValue * types)437 JSONSchemaValidator::JSONSchemaValidator(base::DictionaryValue* schema,
438 base::ListValue* types)
439 : schema_root_(schema), default_allow_additional_properties_(false) {
440 if (!types)
441 return;
442
443 for (size_t i = 0; i < types->GetSize(); ++i) {
444 base::DictionaryValue* type = nullptr;
445 CHECK(types->GetDictionary(i, &type));
446
447 std::string id;
448 CHECK(type->GetString(schema::kId, &id));
449
450 CHECK(types_.find(id) == types_.end());
451 types_[id] = type;
452 }
453 }
454
~JSONSchemaValidator()455 JSONSchemaValidator::~JSONSchemaValidator() {}
456
Validate(const base::Value * instance)457 bool JSONSchemaValidator::Validate(const base::Value* instance) {
458 errors_.clear();
459 Validate(instance, schema_root_, std::string());
460 return errors_.empty();
461 }
462
Validate(const base::Value * instance,const base::DictionaryValue * schema,const std::string & path)463 void JSONSchemaValidator::Validate(const base::Value* instance,
464 const base::DictionaryValue* schema,
465 const std::string& path) {
466 // If this schema defines itself as reference type, save it in this.types.
467 std::string id;
468 if (schema->GetString(schema::kId, &id)) {
469 TypeMap::iterator iter = types_.find(id);
470 if (iter == types_.end())
471 types_[id] = schema;
472 else
473 DCHECK(iter->second == schema);
474 }
475
476 // If the schema has a $ref property, the instance must validate against
477 // that schema. It must be present in types_ to be referenced.
478 std::string ref;
479 if (schema->GetString(schema::kRef, &ref)) {
480 TypeMap::iterator type = types_.find(ref);
481 if (type == types_.end()) {
482 errors_.push_back(
483 Error(path, FormatErrorMessage(kUnknownTypeReference, ref)));
484 } else {
485 Validate(instance, type->second, path);
486 }
487 return;
488 }
489
490 // If the schema has a choices property, the instance must validate against at
491 // least one of the items in that array.
492 const base::ListValue* choices = nullptr;
493 if (schema->GetList(schema::kChoices, &choices)) {
494 ValidateChoices(instance, choices, path);
495 return;
496 }
497
498 // If the schema has an enum property, the instance must be one of those
499 // values.
500 const base::ListValue* enumeration = nullptr;
501 if (schema->GetList(schema::kEnum, &enumeration)) {
502 ValidateEnum(instance, enumeration, path);
503 return;
504 }
505
506 std::string type;
507 schema->GetString(schema::kType, &type);
508 CHECK(!type.empty());
509 if (type != schema::kAny) {
510 if (!ValidateType(instance, type, path))
511 return;
512
513 // These casts are safe because of checks in ValidateType().
514 if (type == schema::kObject) {
515 ValidateObject(static_cast<const base::DictionaryValue*>(instance),
516 schema,
517 path);
518 } else if (type == schema::kArray) {
519 ValidateArray(static_cast<const base::ListValue*>(instance),
520 schema, path);
521 } else if (type == schema::kString) {
522 // Intentionally NOT downcasting to StringValue*. Type::STRING only
523 // implies GetAsString() can safely be carried out, not that it's a
524 // StringValue.
525 ValidateString(instance, schema, path);
526 } else if (type == schema::kNumber || type == schema::kInteger) {
527 ValidateNumber(instance, schema, path);
528 } else if (type != schema::kBoolean && type != schema::kNull) {
529 NOTREACHED() << "Unexpected type: " << type;
530 }
531 }
532 }
533
ValidateChoices(const base::Value * instance,const base::ListValue * choices,const std::string & path)534 void JSONSchemaValidator::ValidateChoices(const base::Value* instance,
535 const base::ListValue* choices,
536 const std::string& path) {
537 size_t original_num_errors = errors_.size();
538
539 for (size_t i = 0; i < choices->GetSize(); ++i) {
540 const base::DictionaryValue* choice = nullptr;
541 CHECK(choices->GetDictionary(i, &choice));
542
543 Validate(instance, choice, path);
544 if (errors_.size() == original_num_errors)
545 return;
546
547 // We discard the error from each choice. We only want to know if any of the
548 // validations succeeded.
549 errors_.resize(original_num_errors);
550 }
551
552 // Now add a generic error that no choices matched.
553 errors_.push_back(Error(path, kInvalidChoice));
554 return;
555 }
556
ValidateEnum(const base::Value * instance,const base::ListValue * choices,const std::string & path)557 void JSONSchemaValidator::ValidateEnum(const base::Value* instance,
558 const base::ListValue* choices,
559 const std::string& path) {
560 for (size_t i = 0; i < choices->GetSize(); ++i) {
561 const base::Value* choice = nullptr;
562 CHECK(choices->Get(i, &choice));
563 // Sometimes the enum declaration is a dictionary with the enum value under
564 // "name".
565 choice = ExtractNameFromDictionary(choice);
566 if (!choice) {
567 NOTREACHED();
568 }
569 switch (choice->type()) {
570 case base::Value::Type::NONE:
571 case base::Value::Type::BOOLEAN:
572 case base::Value::Type::STRING:
573 if (instance->Equals(choice))
574 return;
575 break;
576
577 case base::Value::Type::INTEGER:
578 case base::Value::Type::DOUBLE:
579 if (instance->is_int() || instance->is_double()) {
580 if (GetNumberValue(choice) == GetNumberValue(instance))
581 return;
582 }
583 break;
584
585 default:
586 NOTREACHED() << "Unexpected type in enum: " << choice->type();
587 }
588 }
589
590 errors_.push_back(Error(path, kInvalidEnum));
591 }
592
ValidateObject(const base::DictionaryValue * instance,const base::DictionaryValue * schema,const std::string & path)593 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance,
594 const base::DictionaryValue* schema,
595 const std::string& path) {
596 const base::DictionaryValue* properties = nullptr;
597 if (schema->GetDictionary(schema::kProperties, &properties)) {
598 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd();
599 it.Advance()) {
600 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key());
601 const base::DictionaryValue* prop_schema = nullptr;
602 CHECK(it.value().GetAsDictionary(&prop_schema));
603
604 const base::Value* prop_value = nullptr;
605 if (instance->Get(it.key(), &prop_value)) {
606 Validate(prop_value, prop_schema, prop_path);
607 } else {
608 // Properties are required unless there is an optional field set to
609 // 'true'.
610 bool is_optional = false;
611 prop_schema->GetBoolean(schema::kOptional, &is_optional);
612 if (!is_optional) {
613 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired));
614 }
615 }
616 }
617 }
618
619 const base::DictionaryValue* additional_properties_schema = nullptr;
620 bool allow_any_additional_properties =
621 SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema);
622
623 const base::DictionaryValue* pattern_properties = nullptr;
624 std::vector<std::unique_ptr<re2::RE2>> pattern_properties_pattern;
625 std::vector<const base::DictionaryValue*> pattern_properties_schema;
626
627 if (schema->GetDictionary(schema::kPatternProperties, &pattern_properties)) {
628 for (base::DictionaryValue::Iterator it(*pattern_properties); !it.IsAtEnd();
629 it.Advance()) {
630 auto prop_pattern = std::make_unique<re2::RE2>(it.key());
631 if (!prop_pattern->ok()) {
632 LOG(WARNING) << "Regular expression /" << it.key()
633 << "/ is invalid: " << prop_pattern->error() << ".";
634 errors_.push_back(
635 Error(path,
636 FormatErrorMessage(
637 kInvalidRegex, it.key(), prop_pattern->error())));
638 continue;
639 }
640 const base::DictionaryValue* prop_schema = nullptr;
641 CHECK(it.value().GetAsDictionary(&prop_schema));
642 pattern_properties_pattern.push_back(std::move(prop_pattern));
643 pattern_properties_schema.push_back(prop_schema);
644 }
645 }
646
647 // Validate pattern properties and additional properties.
648 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd();
649 it.Advance()) {
650 std::string prop_path = path.empty() ? it.key() : path + "." + it.key();
651
652 bool found_matching_pattern = false;
653 for (size_t index = 0; index < pattern_properties_pattern.size(); ++index) {
654 if (re2::RE2::PartialMatch(it.key(),
655 *pattern_properties_pattern[index])) {
656 found_matching_pattern = true;
657 Validate(&it.value(), pattern_properties_schema[index], prop_path);
658 break;
659 }
660 }
661
662 if (found_matching_pattern || allow_any_additional_properties ||
663 (properties && properties->HasKey(it.key())))
664 continue;
665
666 if (!additional_properties_schema) {
667 errors_.push_back(Error(prop_path, kUnexpectedProperty));
668 } else {
669 Validate(&it.value(), additional_properties_schema, prop_path);
670 }
671 }
672 }
673
ValidateArray(const base::ListValue * instance,const base::DictionaryValue * schema,const std::string & path)674 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance,
675 const base::DictionaryValue* schema,
676 const std::string& path) {
677 const base::DictionaryValue* single_type = nullptr;
678 size_t instance_size = instance->GetSize();
679 if (schema->GetDictionary(schema::kItems, &single_type)) {
680 int min_items = 0;
681 if (schema->GetInteger(schema::kMinItems, &min_items)) {
682 CHECK(min_items >= 0);
683 if (instance_size < static_cast<size_t>(min_items)) {
684 errors_.push_back(Error(path, FormatErrorMessage(
685 kArrayMinItems, base::IntToString(min_items))));
686 }
687 }
688
689 int max_items = 0;
690 if (schema->GetInteger(schema::kMaxItems, &max_items)) {
691 CHECK(max_items >= 0);
692 if (instance_size > static_cast<size_t>(max_items)) {
693 errors_.push_back(Error(path, FormatErrorMessage(
694 kArrayMaxItems, base::IntToString(max_items))));
695 }
696 }
697
698 // If the items property is a single schema, each item in the array must
699 // validate against that schema.
700 for (size_t i = 0; i < instance_size; ++i) {
701 const base::Value* item = nullptr;
702 CHECK(instance->Get(i, &item));
703 std::string i_str = base::NumberToString(i);
704 std::string item_path = path.empty() ? i_str : (path + "." + i_str);
705 Validate(item, single_type, item_path);
706 }
707
708 return;
709 }
710
711 // Otherwise, the list must be a tuple type, where each item in the list has a
712 // particular schema.
713 ValidateTuple(instance, schema, path);
714 }
715
ValidateTuple(const base::ListValue * instance,const base::DictionaryValue * schema,const std::string & path)716 void JSONSchemaValidator::ValidateTuple(const base::ListValue* instance,
717 const base::DictionaryValue* schema,
718 const std::string& path) {
719 const base::ListValue* tuple_type = nullptr;
720 schema->GetList(schema::kItems, &tuple_type);
721 size_t tuple_size = tuple_type ? tuple_type->GetSize() : 0;
722 if (tuple_type) {
723 for (size_t i = 0; i < tuple_size; ++i) {
724 std::string i_str = base::NumberToString(i);
725 std::string item_path = path.empty() ? i_str : (path + "." + i_str);
726 const base::DictionaryValue* item_schema = nullptr;
727 CHECK(tuple_type->GetDictionary(i, &item_schema));
728 const base::Value* item_value = nullptr;
729 instance->Get(i, &item_value);
730 if (item_value && item_value->type() != base::Value::Type::NONE) {
731 Validate(item_value, item_schema, item_path);
732 } else {
733 bool is_optional = false;
734 item_schema->GetBoolean(schema::kOptional, &is_optional);
735 if (!is_optional) {
736 errors_.push_back(Error(item_path, kArrayItemRequired));
737 return;
738 }
739 }
740 }
741 }
742
743 const base::DictionaryValue* additional_properties_schema = nullptr;
744 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema))
745 return;
746
747 size_t instance_size = instance->GetSize();
748 if (additional_properties_schema) {
749 // Any additional properties must validate against the additionalProperties
750 // schema.
751 for (size_t i = tuple_size; i < instance_size; ++i) {
752 std::string i_str = base::NumberToString(i);
753 std::string item_path = path.empty() ? i_str : (path + "." + i_str);
754 const base::Value* item_value = nullptr;
755 CHECK(instance->Get(i, &item_value));
756 Validate(item_value, additional_properties_schema, item_path);
757 }
758 } else if (instance_size > tuple_size) {
759 errors_.push_back(Error(
760 path,
761 FormatErrorMessage(kArrayMaxItems, base::NumberToString(tuple_size))));
762 }
763 }
764
ValidateString(const base::Value * instance,const base::DictionaryValue * schema,const std::string & path)765 void JSONSchemaValidator::ValidateString(const base::Value* instance,
766 const base::DictionaryValue* schema,
767 const std::string& path) {
768 std::string value;
769 CHECK(instance->GetAsString(&value));
770
771 int min_length = 0;
772 if (schema->GetInteger(schema::kMinLength, &min_length)) {
773 CHECK(min_length >= 0);
774 if (value.size() < static_cast<size_t>(min_length)) {
775 errors_.push_back(Error(path, FormatErrorMessage(
776 kStringMinLength, base::IntToString(min_length))));
777 }
778 }
779
780 int max_length = 0;
781 if (schema->GetInteger(schema::kMaxLength, &max_length)) {
782 CHECK(max_length >= 0);
783 if (value.size() > static_cast<size_t>(max_length)) {
784 errors_.push_back(Error(path, FormatErrorMessage(
785 kStringMaxLength, base::IntToString(max_length))));
786 }
787 }
788
789 std::string pattern;
790 if (schema->GetString(schema::kPattern, &pattern)) {
791 re2::RE2 compiled_regex(pattern);
792 if (!compiled_regex.ok()) {
793 LOG(WARNING) << "Regular expression /" << pattern
794 << "/ is invalid: " << compiled_regex.error() << ".";
795 errors_.push_back(Error(
796 path,
797 FormatErrorMessage(kInvalidRegex, pattern, compiled_regex.error())));
798 } else if (!re2::RE2::PartialMatch(value, compiled_regex)) {
799 errors_.push_back(
800 Error(path, FormatErrorMessage(kStringPattern, pattern)));
801 }
802 }
803 }
804
ValidateNumber(const base::Value * instance,const base::DictionaryValue * schema,const std::string & path)805 void JSONSchemaValidator::ValidateNumber(const base::Value* instance,
806 const base::DictionaryValue* schema,
807 const std::string& path) {
808 double value = GetNumberValue(instance);
809
810 // TODO(aa): It would be good to test that the double is not infinity or nan,
811 // but isnan and isinf aren't defined on Windows.
812
813 double minimum = 0;
814 if (schema->GetDouble(schema::kMinimum, &minimum)) {
815 if (value < minimum)
816 errors_.push_back(Error(
817 path,
818 FormatErrorMessage(kNumberMinimum, base::NumberToString(minimum))));
819 }
820
821 double maximum = 0;
822 if (schema->GetDouble(schema::kMaximum, &maximum)) {
823 if (value > maximum)
824 errors_.push_back(Error(
825 path,
826 FormatErrorMessage(kNumberMaximum, base::NumberToString(maximum))));
827 }
828 }
829
ValidateType(const base::Value * instance,const std::string & expected_type,const std::string & path)830 bool JSONSchemaValidator::ValidateType(const base::Value* instance,
831 const std::string& expected_type,
832 const std::string& path) {
833 std::string actual_type = GetJSONSchemaType(instance);
834 if (expected_type == actual_type ||
835 (expected_type == schema::kNumber && actual_type == schema::kInteger)) {
836 return true;
837 }
838 if (expected_type == schema::kInteger && actual_type == schema::kNumber) {
839 errors_.push_back(Error(path, kInvalidTypeIntegerNumber));
840 return false;
841 }
842 errors_.push_back(Error(
843 path, FormatErrorMessage(kInvalidType, expected_type, actual_type)));
844 return false;
845 }
846
SchemaAllowsAnyAdditionalItems(const base::DictionaryValue * schema,const base::DictionaryValue ** additional_properties_schema)847 bool JSONSchemaValidator::SchemaAllowsAnyAdditionalItems(
848 const base::DictionaryValue* schema,
849 const base::DictionaryValue** additional_properties_schema) {
850 // If the validator allows additional properties globally, and this schema
851 // doesn't override, then we can exit early.
852 schema->GetDictionary(schema::kAdditionalProperties,
853 additional_properties_schema);
854
855 if (*additional_properties_schema) {
856 std::string additional_properties_type(schema::kAny);
857 CHECK((*additional_properties_schema)->GetString(
858 schema::kType, &additional_properties_type));
859 return additional_properties_type == schema::kAny;
860 }
861 return default_allow_additional_properties_;
862 }
863