• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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