Ignore title parameter for navigator.registerProtocolHandler
[chromium-blink-merge.git] / components / policy / core / common / schema.cc
blob7359938f3d9acc9e17754cbe3b111e0c3ed1f9f1
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.
5 #include "components/policy/core/common/schema.h"
7 #include <algorithm>
8 #include <climits>
9 #include <map>
10 #include <utility>
12 #include "base/compiler_specific.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/memory/scoped_vector.h"
16 #include "base/stl_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "components/json_schema/json_schema_constants.h"
19 #include "components/json_schema/json_schema_validator.h"
20 #include "components/policy/core/common/schema_internal.h"
21 #include "third_party/re2/re2/re2.h"
23 namespace schema = json_schema_constants;
25 namespace policy {
27 using internal::PropertiesNode;
28 using internal::PropertyNode;
29 using internal::RestrictionNode;
30 using internal::SchemaData;
31 using internal::SchemaNode;
33 namespace {
35 // Maps schema "id" attributes to the corresponding SchemaNode index.
36 typedef std::map<std::string, int> IdMap;
38 // List of pairs of references to be assigned later. The string is the "id"
39 // whose corresponding index should be stored in the pointer, once all the IDs
40 // are available.
41 typedef std::vector<std::pair<std::string, int*> > ReferenceList;
43 // Sizes for the storage arrays. These are calculated in advance so that the
44 // arrays don't have to be resized during parsing, which would invalidate
45 // pointers into their contents (i.e. string's c_str() and address of indices
46 // for "$ref" attributes).
47 struct StorageSizes {
48 StorageSizes()
49 : strings(0), schema_nodes(0), property_nodes(0), properties_nodes(0),
50 restriction_nodes(0), int_enums(0), string_enums(0) { }
51 size_t strings;
52 size_t schema_nodes;
53 size_t property_nodes;
54 size_t properties_nodes;
55 size_t restriction_nodes;
56 size_t int_enums;
57 size_t string_enums;
60 // An invalid index, indicating that a node is not present; similar to a NULL
61 // pointer.
62 const int kInvalid = -1;
64 bool SchemaTypeToValueType(const std::string& type_string,
65 base::Value::Type* type) {
66 // Note: "any" is not an accepted type.
67 static const struct {
68 const char* schema_type;
69 base::Value::Type value_type;
70 } kSchemaToValueTypeMap[] = {
71 { schema::kArray, base::Value::TYPE_LIST },
72 { schema::kBoolean, base::Value::TYPE_BOOLEAN },
73 { schema::kInteger, base::Value::TYPE_INTEGER },
74 { schema::kNull, base::Value::TYPE_NULL },
75 { schema::kNumber, base::Value::TYPE_DOUBLE },
76 { schema::kObject, base::Value::TYPE_DICTIONARY },
77 { schema::kString, base::Value::TYPE_STRING },
79 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSchemaToValueTypeMap); ++i) {
80 if (kSchemaToValueTypeMap[i].schema_type == type_string) {
81 *type = kSchemaToValueTypeMap[i].value_type;
82 return true;
85 return false;
88 bool StrategyAllowInvalidOnTopLevel(SchemaOnErrorStrategy strategy) {
89 return strategy == SCHEMA_ALLOW_INVALID ||
90 strategy == SCHEMA_ALLOW_INVALID_TOPLEVEL ||
91 strategy == SCHEMA_ALLOW_INVALID_TOPLEVEL_AND_ALLOW_UNKNOWN;
94 bool StrategyAllowUnknownOnTopLevel(SchemaOnErrorStrategy strategy) {
95 return strategy != SCHEMA_STRICT;
98 SchemaOnErrorStrategy StrategyForNextLevel(SchemaOnErrorStrategy strategy) {
99 static SchemaOnErrorStrategy next_level_strategy[] = {
100 SCHEMA_STRICT, // SCHEMA_STRICT
101 SCHEMA_STRICT, // SCHEMA_ALLOW_UNKNOWN_TOPLEVEL
102 SCHEMA_ALLOW_UNKNOWN, // SCHEMA_ALLOW_UNKNOWN
103 SCHEMA_STRICT, // SCHEMA_ALLOW_INVALID_TOPLEVEL
104 SCHEMA_ALLOW_UNKNOWN, // SCHEMA_ALLOW_INVALID_TOPLEVEL_AND_ALLOW_UNKNOWN
105 SCHEMA_ALLOW_INVALID, // SCHEMA_ALLOW_INVALID
107 return next_level_strategy[(int)strategy];
110 void SchemaErrorFound(std::string* error_path,
111 std::string* error,
112 const std::string& msg) {
113 if (error_path)
114 *error_path = "";
115 *error = msg;
118 void AddListIndexPrefixToPath(int index, std::string* path) {
119 if (path) {
120 if (path->empty())
121 *path = base::StringPrintf("items[%d]", index);
122 else
123 *path = base::StringPrintf("items[%d].", index) + *path;
127 void AddDictKeyPrefixToPath(const std::string& key, std::string* path) {
128 if (path) {
129 if (path->empty())
130 *path = key;
131 else
132 *path = key + "." + *path;
136 } // namespace
138 // Contains the internal data representation of a Schema. This can either wrap
139 // a SchemaData owned elsewhere (currently used to wrap the Chrome schema, which
140 // is generated at compile time), or it can own its own SchemaData.
141 class Schema::InternalStorage
142 : public base::RefCountedThreadSafe<InternalStorage> {
143 public:
144 static scoped_refptr<const InternalStorage> Wrap(const SchemaData* data);
146 static scoped_refptr<const InternalStorage> ParseSchema(
147 const base::DictionaryValue& schema,
148 std::string* error);
150 const SchemaData* data() const { return &schema_data_; }
152 const SchemaNode* root_node() const {
153 return schema(0);
156 const SchemaNode* schema(int index) const {
157 return schema_data_.schema_nodes + index;
160 const PropertiesNode* properties(int index) const {
161 return schema_data_.properties_nodes + index;
164 const PropertyNode* property(int index) const {
165 return schema_data_.property_nodes + index;
168 const RestrictionNode* restriction(int index) const {
169 return schema_data_.restriction_nodes + index;
172 const int* int_enums(int index) const {
173 return schema_data_.int_enums + index;
176 const char** string_enums(int index) const {
177 return schema_data_.string_enums + index;
180 // Compiles regular expression |pattern|. The result is cached and will be
181 // returned directly next time.
182 re2::RE2* CompileRegex(const std::string& pattern) const;
184 private:
185 friend class base::RefCountedThreadSafe<InternalStorage>;
187 InternalStorage();
188 ~InternalStorage();
190 // Determines the expected |sizes| of the storage for the representation
191 // of |schema|.
192 static void DetermineStorageSizes(const base::DictionaryValue& schema,
193 StorageSizes* sizes);
195 // Parses the JSON schema in |schema|.
197 // If |schema| has a "$ref" attribute then a pending reference is appended
198 // to the |reference_list|, and nothing else is done.
200 // Otherwise, |index| gets assigned the index of the corresponding SchemaNode
201 // in |schema_nodes_|. If the |schema| contains an "id" then that ID is mapped
202 // to the |index| in the |id_map|.
204 // If |schema| is invalid then |error| gets the error reason and false is
205 // returned. Otherwise returns true.
206 bool Parse(const base::DictionaryValue& schema,
207 int* index,
208 IdMap* id_map,
209 ReferenceList* reference_list,
210 std::string* error);
212 // Helper for Parse() that gets an already assigned |schema_node| instead of
213 // an |index| pointer.
214 bool ParseDictionary(const base::DictionaryValue& schema,
215 SchemaNode* schema_node,
216 IdMap* id_map,
217 ReferenceList* reference_list,
218 std::string* error);
220 // Helper for Parse() that gets an already assigned |schema_node| instead of
221 // an |index| pointer.
222 bool ParseList(const base::DictionaryValue& schema,
223 SchemaNode* schema_node,
224 IdMap* id_map,
225 ReferenceList* reference_list,
226 std::string* error);
228 bool ParseEnum(const base::DictionaryValue& schema,
229 base::Value::Type type,
230 SchemaNode* schema_node,
231 std::string* error);
233 bool ParseRangedInt(const base::DictionaryValue& schema,
234 SchemaNode* schema_node,
235 std::string* error);
237 bool ParseStringPattern(const base::DictionaryValue& schema,
238 SchemaNode* schema_node,
239 std::string* error);
241 // Assigns the IDs in |id_map| to the pending references in the
242 // |reference_list|. If an ID is missing then |error| is set and false is
243 // returned; otherwise returns true.
244 static bool ResolveReferences(const IdMap& id_map,
245 const ReferenceList& reference_list,
246 std::string* error);
248 // Cache for CompileRegex(), will memorize return value of every call to
249 // CompileRegex() and return results directly next time.
250 mutable std::map<std::string, re2::RE2*> regex_cache_;
251 STLValueDeleter<std::map<std::string, re2::RE2*> > regex_cache_deleter_;
253 SchemaData schema_data_;
254 std::vector<std::string> strings_;
255 std::vector<SchemaNode> schema_nodes_;
256 std::vector<PropertyNode> property_nodes_;
257 std::vector<PropertiesNode> properties_nodes_;
258 std::vector<RestrictionNode> restriction_nodes_;
259 std::vector<int> int_enums_;
260 std::vector<const char*> string_enums_;
262 DISALLOW_COPY_AND_ASSIGN(InternalStorage);
265 Schema::InternalStorage::InternalStorage()
266 : regex_cache_deleter_(&regex_cache_) {}
268 Schema::InternalStorage::~InternalStorage() {}
270 // static
271 scoped_refptr<const Schema::InternalStorage> Schema::InternalStorage::Wrap(
272 const SchemaData* data) {
273 InternalStorage* storage = new InternalStorage();
274 storage->schema_data_.schema_nodes = data->schema_nodes;
275 storage->schema_data_.property_nodes = data->property_nodes;
276 storage->schema_data_.properties_nodes = data->properties_nodes;
277 storage->schema_data_.restriction_nodes = data->restriction_nodes;
278 storage->schema_data_.int_enums = data->int_enums;
279 storage->schema_data_.string_enums = data->string_enums;
280 return storage;
283 // static
284 scoped_refptr<const Schema::InternalStorage>
285 Schema::InternalStorage::ParseSchema(const base::DictionaryValue& schema,
286 std::string* error) {
287 // Determine the sizes of the storage arrays and reserve the capacity before
288 // starting to append nodes and strings. This is important to prevent the
289 // arrays from being reallocated, which would invalidate the c_str() pointers
290 // and the addresses of indices to fix.
291 StorageSizes sizes;
292 DetermineStorageSizes(schema, &sizes);
294 scoped_refptr<InternalStorage> storage = new InternalStorage();
295 storage->strings_.reserve(sizes.strings);
296 storage->schema_nodes_.reserve(sizes.schema_nodes);
297 storage->property_nodes_.reserve(sizes.property_nodes);
298 storage->properties_nodes_.reserve(sizes.properties_nodes);
299 storage->restriction_nodes_.reserve(sizes.restriction_nodes);
300 storage->int_enums_.reserve(sizes.int_enums);
301 storage->string_enums_.reserve(sizes.string_enums);
303 int root_index = kInvalid;
304 IdMap id_map;
305 ReferenceList reference_list;
306 if (!storage->Parse(schema, &root_index, &id_map, &reference_list, error))
307 return NULL;
309 if (root_index == kInvalid) {
310 *error = "The main schema can't have a $ref";
311 return NULL;
314 // None of this should ever happen without having been already detected.
315 // But, if it does happen, then it will lead to corrupted memory; drop
316 // everything in that case.
317 if (root_index != 0 ||
318 sizes.strings != storage->strings_.size() ||
319 sizes.schema_nodes != storage->schema_nodes_.size() ||
320 sizes.property_nodes != storage->property_nodes_.size() ||
321 sizes.properties_nodes != storage->properties_nodes_.size() ||
322 sizes.restriction_nodes != storage->restriction_nodes_.size() ||
323 sizes.int_enums != storage->int_enums_.size() ||
324 sizes.string_enums != storage->string_enums_.size()) {
325 *error = "Failed to parse the schema due to a Chrome bug. Please file a "
326 "new issue at http://crbug.com";
327 return NULL;
330 if (!ResolveReferences(id_map, reference_list, error))
331 return NULL;
333 SchemaData* data = &storage->schema_data_;
334 data->schema_nodes = vector_as_array(&storage->schema_nodes_);
335 data->property_nodes = vector_as_array(&storage->property_nodes_);
336 data->properties_nodes = vector_as_array(&storage->properties_nodes_);
337 data->restriction_nodes = vector_as_array(&storage->restriction_nodes_);
338 data->int_enums = vector_as_array(&storage->int_enums_);
339 data->string_enums = vector_as_array(&storage->string_enums_);
340 return storage;
343 re2::RE2* Schema::InternalStorage::CompileRegex(
344 const std::string& pattern) const {
345 std::map<std::string, re2::RE2*>::iterator it = regex_cache_.find(pattern);
346 if (it == regex_cache_.end()) {
347 re2::RE2* compiled = new re2::RE2(pattern);
348 regex_cache_[pattern] = compiled;
349 return compiled;
351 return it->second;
354 // static
355 void Schema::InternalStorage::DetermineStorageSizes(
356 const base::DictionaryValue& schema,
357 StorageSizes* sizes) {
358 std::string ref_string;
359 if (schema.GetString(schema::kRef, &ref_string)) {
360 // Schemas with a "$ref" attribute don't take additional storage.
361 return;
364 std::string type_string;
365 base::Value::Type type = base::Value::TYPE_NULL;
366 if (!schema.GetString(schema::kType, &type_string) ||
367 !SchemaTypeToValueType(type_string, &type)) {
368 // This schema is invalid.
369 return;
372 sizes->schema_nodes++;
374 if (type == base::Value::TYPE_LIST) {
375 const base::DictionaryValue* items = NULL;
376 if (schema.GetDictionary(schema::kItems, &items))
377 DetermineStorageSizes(*items, sizes);
378 } else if (type == base::Value::TYPE_DICTIONARY) {
379 sizes->properties_nodes++;
381 const base::DictionaryValue* dict = NULL;
382 if (schema.GetDictionary(schema::kAdditionalProperties, &dict))
383 DetermineStorageSizes(*dict, sizes);
385 const base::DictionaryValue* properties = NULL;
386 if (schema.GetDictionary(schema::kProperties, &properties)) {
387 for (base::DictionaryValue::Iterator it(*properties);
388 !it.IsAtEnd(); it.Advance()) {
389 // This should have been verified by the JSONSchemaValidator.
390 CHECK(it.value().GetAsDictionary(&dict));
391 DetermineStorageSizes(*dict, sizes);
392 sizes->strings++;
393 sizes->property_nodes++;
397 const base::DictionaryValue* pattern_properties = NULL;
398 if (schema.GetDictionary(schema::kPatternProperties, &pattern_properties)) {
399 for (base::DictionaryValue::Iterator it(*pattern_properties);
400 !it.IsAtEnd(); it.Advance()) {
401 CHECK(it.value().GetAsDictionary(&dict));
402 DetermineStorageSizes(*dict, sizes);
403 sizes->strings++;
404 sizes->property_nodes++;
407 } else if (schema.HasKey(schema::kEnum)) {
408 const base::ListValue* possible_values = NULL;
409 if (schema.GetList(schema::kEnum, &possible_values)) {
410 if (type == base::Value::TYPE_INTEGER) {
411 sizes->int_enums += possible_values->GetSize();
412 } else if (type == base::Value::TYPE_STRING) {
413 sizes->string_enums += possible_values->GetSize();
414 sizes->strings += possible_values->GetSize();
416 sizes->restriction_nodes++;
418 } else if (type == base::Value::TYPE_INTEGER) {
419 if (schema.HasKey(schema::kMinimum) || schema.HasKey(schema::kMaximum))
420 sizes->restriction_nodes++;
421 } else if (type == base::Value::TYPE_STRING) {
422 if (schema.HasKey(schema::kPattern)) {
423 sizes->strings++;
424 sizes->string_enums++;
425 sizes->restriction_nodes++;
430 bool Schema::InternalStorage::Parse(const base::DictionaryValue& schema,
431 int* index,
432 IdMap* id_map,
433 ReferenceList* reference_list,
434 std::string* error) {
435 std::string ref_string;
436 if (schema.GetString(schema::kRef, &ref_string)) {
437 std::string id_string;
438 if (schema.GetString(schema::kId, &id_string)) {
439 *error = "Schemas with a $ref can't have an id";
440 return false;
442 reference_list->push_back(std::make_pair(ref_string, index));
443 return true;
446 std::string type_string;
447 if (!schema.GetString(schema::kType, &type_string)) {
448 *error = "The schema type must be declared.";
449 return false;
452 base::Value::Type type = base::Value::TYPE_NULL;
453 if (!SchemaTypeToValueType(type_string, &type)) {
454 *error = "Type not supported: " + type_string;
455 return false;
458 *index = static_cast<int>(schema_nodes_.size());
459 schema_nodes_.push_back(SchemaNode());
460 SchemaNode* schema_node = &schema_nodes_.back();
461 schema_node->type = type;
462 schema_node->extra = kInvalid;
464 if (type == base::Value::TYPE_DICTIONARY) {
465 if (!ParseDictionary(schema, schema_node, id_map, reference_list, error))
466 return false;
467 } else if (type == base::Value::TYPE_LIST) {
468 if (!ParseList(schema, schema_node, id_map, reference_list, error))
469 return false;
470 } else if (schema.HasKey(schema::kEnum)) {
471 if (!ParseEnum(schema, type, schema_node, error))
472 return false;
473 } else if (schema.HasKey(schema::kPattern)) {
474 if (!ParseStringPattern(schema, schema_node, error))
475 return false;
476 } else if (schema.HasKey(schema::kMinimum) ||
477 schema.HasKey(schema::kMaximum)) {
478 if (type != base::Value::TYPE_INTEGER) {
479 *error = "Only integers can have minimum and maximum";
480 return false;
482 if (!ParseRangedInt(schema, schema_node, error))
483 return false;
485 std::string id_string;
486 if (schema.GetString(schema::kId, &id_string)) {
487 if (ContainsKey(*id_map, id_string)) {
488 *error = "Duplicated id: " + id_string;
489 return false;
491 (*id_map)[id_string] = *index;
494 return true;
497 bool Schema::InternalStorage::ParseDictionary(
498 const base::DictionaryValue& schema,
499 SchemaNode* schema_node,
500 IdMap* id_map,
501 ReferenceList* reference_list,
502 std::string* error) {
503 int extra = static_cast<int>(properties_nodes_.size());
504 properties_nodes_.push_back(PropertiesNode());
505 properties_nodes_[extra].additional = kInvalid;
506 schema_node->extra = extra;
508 const base::DictionaryValue* dict = NULL;
509 if (schema.GetDictionary(schema::kAdditionalProperties, &dict)) {
510 if (!Parse(*dict, &properties_nodes_[extra].additional,
511 id_map, reference_list, error)) {
512 return false;
516 properties_nodes_[extra].begin = static_cast<int>(property_nodes_.size());
518 const base::DictionaryValue* properties = NULL;
519 if (schema.GetDictionary(schema::kProperties, &properties)) {
520 // This and below reserves nodes for all of the |properties|, and makes sure
521 // they are contiguous. Recursive calls to Parse() will append after these
522 // elements.
523 property_nodes_.resize(property_nodes_.size() + properties->size());
526 properties_nodes_[extra].end = static_cast<int>(property_nodes_.size());
528 const base::DictionaryValue* pattern_properties = NULL;
529 if (schema.GetDictionary(schema::kPatternProperties, &pattern_properties))
530 property_nodes_.resize(property_nodes_.size() + pattern_properties->size());
532 properties_nodes_[extra].pattern_end =
533 static_cast<int>(property_nodes_.size());
535 if (properties != NULL) {
536 int base_index = properties_nodes_[extra].begin;
537 int index = base_index;
539 for (base::DictionaryValue::Iterator it(*properties);
540 !it.IsAtEnd(); it.Advance(), ++index) {
541 // This should have been verified by the JSONSchemaValidator.
542 CHECK(it.value().GetAsDictionary(&dict));
543 strings_.push_back(it.key());
544 property_nodes_[index].key = strings_.back().c_str();
545 if (!Parse(*dict, &property_nodes_[index].schema,
546 id_map, reference_list, error)) {
547 return false;
550 CHECK_EQ(static_cast<int>(properties->size()), index - base_index);
553 if (pattern_properties != NULL) {
554 int base_index = properties_nodes_[extra].end;
555 int index = base_index;
557 for (base::DictionaryValue::Iterator it(*pattern_properties);
558 !it.IsAtEnd(); it.Advance(), ++index) {
559 CHECK(it.value().GetAsDictionary(&dict));
560 re2::RE2* compiled_regex = CompileRegex(it.key());
561 if (!compiled_regex->ok()) {
562 *error =
563 "/" + it.key() + "/ is a invalid regex: " + compiled_regex->error();
564 return false;
566 strings_.push_back(it.key());
567 property_nodes_[index].key = strings_.back().c_str();
568 if (!Parse(*dict, &property_nodes_[index].schema,
569 id_map, reference_list, error)) {
570 return false;
573 CHECK_EQ(static_cast<int>(pattern_properties->size()), index - base_index);
576 if (properties_nodes_[extra].begin == properties_nodes_[extra].pattern_end) {
577 properties_nodes_[extra].begin = kInvalid;
578 properties_nodes_[extra].end = kInvalid;
579 properties_nodes_[extra].pattern_end = kInvalid;
582 return true;
585 bool Schema::InternalStorage::ParseList(const base::DictionaryValue& schema,
586 SchemaNode* schema_node,
587 IdMap* id_map,
588 ReferenceList* reference_list,
589 std::string* error) {
590 const base::DictionaryValue* dict = NULL;
591 if (!schema.GetDictionary(schema::kItems, &dict)) {
592 *error = "Arrays must declare a single schema for their items.";
593 return false;
595 return Parse(*dict, &schema_node->extra, id_map, reference_list, error);
598 bool Schema::InternalStorage::ParseEnum(const base::DictionaryValue& schema,
599 base::Value::Type type,
600 SchemaNode* schema_node,
601 std::string* error) {
602 const base::ListValue *possible_values = NULL;
603 if (!schema.GetList(schema::kEnum, &possible_values)) {
604 *error = "Enum attribute must be a list value";
605 return false;
607 if (possible_values->empty()) {
608 *error = "Enum attribute must be non-empty";
609 return false;
611 int offset_begin;
612 int offset_end;
613 if (type == base::Value::TYPE_INTEGER) {
614 offset_begin = static_cast<int>(int_enums_.size());
615 int value;
616 for (base::ListValue::const_iterator it = possible_values->begin();
617 it != possible_values->end(); ++it) {
618 if (!(*it)->GetAsInteger(&value)) {
619 *error = "Invalid enumeration member type";
620 return false;
622 int_enums_.push_back(value);
624 offset_end = static_cast<int>(int_enums_.size());
625 } else if (type == base::Value::TYPE_STRING) {
626 offset_begin = static_cast<int>(string_enums_.size());
627 std::string value;
628 for (base::ListValue::const_iterator it = possible_values->begin();
629 it != possible_values->end(); ++it) {
630 if (!(*it)->GetAsString(&value)) {
631 *error = "Invalid enumeration member type";
632 return false;
634 strings_.push_back(value);
635 string_enums_.push_back(strings_.back().c_str());
637 offset_end = static_cast<int>(string_enums_.size());
638 } else {
639 *error = "Enumeration is only supported for integer and string.";
640 return false;
642 schema_node->extra = static_cast<int>(restriction_nodes_.size());
643 restriction_nodes_.push_back(RestrictionNode());
644 restriction_nodes_.back().enumeration_restriction.offset_begin = offset_begin;
645 restriction_nodes_.back().enumeration_restriction.offset_end = offset_end;
646 return true;
649 bool Schema::InternalStorage::ParseRangedInt(
650 const base::DictionaryValue& schema,
651 SchemaNode* schema_node,
652 std::string* error) {
653 int min_value = INT_MIN;
654 int max_value = INT_MAX;
655 int value;
656 if (schema.GetInteger(schema::kMinimum, &value))
657 min_value = value;
658 if (schema.GetInteger(schema::kMaximum, &value))
659 max_value = value;
660 if (min_value > max_value) {
661 *error = "Invalid range restriction for int type.";
662 return false;
664 schema_node->extra = static_cast<int>(restriction_nodes_.size());
665 restriction_nodes_.push_back(RestrictionNode());
666 restriction_nodes_.back().ranged_restriction.max_value = max_value;
667 restriction_nodes_.back().ranged_restriction.min_value = min_value;
668 return true;
671 bool Schema::InternalStorage::ParseStringPattern(
672 const base::DictionaryValue& schema,
673 SchemaNode* schema_node,
674 std::string* error) {
675 std::string pattern;
676 if (!schema.GetString(schema::kPattern, &pattern)) {
677 *error = "Schema pattern must be a string.";
678 return false;
680 re2::RE2* compiled_regex = CompileRegex(pattern);
681 if (!compiled_regex->ok()) {
682 *error = "/" + pattern + "/ is invalid regex: " + compiled_regex->error();
683 return false;
685 int index = static_cast<int>(string_enums_.size());
686 strings_.push_back(pattern);
687 string_enums_.push_back(strings_.back().c_str());
688 schema_node->extra = static_cast<int>(restriction_nodes_.size());
689 restriction_nodes_.push_back(RestrictionNode());
690 restriction_nodes_.back().string_pattern_restriction.pattern_index = index;
691 restriction_nodes_.back().string_pattern_restriction.pattern_index_backup =
692 index;
693 return true;
696 // static
697 bool Schema::InternalStorage::ResolveReferences(
698 const IdMap& id_map,
699 const ReferenceList& reference_list,
700 std::string* error) {
701 for (ReferenceList::const_iterator ref = reference_list.begin();
702 ref != reference_list.end(); ++ref) {
703 IdMap::const_iterator id = id_map.find(ref->first);
704 if (id == id_map.end()) {
705 *error = "Invalid $ref: " + ref->first;
706 return false;
708 *ref->second = id->second;
710 return true;
713 Schema::Iterator::Iterator(const scoped_refptr<const InternalStorage>& storage,
714 const PropertiesNode* node)
715 : storage_(storage),
716 it_(storage->property(node->begin)),
717 end_(storage->property(node->end)) {}
719 Schema::Iterator::Iterator(const Iterator& iterator)
720 : storage_(iterator.storage_),
721 it_(iterator.it_),
722 end_(iterator.end_) {}
724 Schema::Iterator::~Iterator() {}
726 Schema::Iterator& Schema::Iterator::operator=(const Iterator& iterator) {
727 storage_ = iterator.storage_;
728 it_ = iterator.it_;
729 end_ = iterator.end_;
730 return *this;
733 bool Schema::Iterator::IsAtEnd() const {
734 return it_ == end_;
737 void Schema::Iterator::Advance() {
738 ++it_;
741 const char* Schema::Iterator::key() const {
742 return it_->key;
745 Schema Schema::Iterator::schema() const {
746 return Schema(storage_, storage_->schema(it_->schema));
749 Schema::Schema() : node_(NULL) {}
751 Schema::Schema(const scoped_refptr<const InternalStorage>& storage,
752 const SchemaNode* node)
753 : storage_(storage), node_(node) {}
755 Schema::Schema(const Schema& schema)
756 : storage_(schema.storage_), node_(schema.node_) {}
758 Schema::~Schema() {}
760 Schema& Schema::operator=(const Schema& schema) {
761 storage_ = schema.storage_;
762 node_ = schema.node_;
763 return *this;
766 // static
767 Schema Schema::Wrap(const SchemaData* data) {
768 scoped_refptr<const InternalStorage> storage = InternalStorage::Wrap(data);
769 return Schema(storage, storage->root_node());
772 bool Schema::Validate(const base::Value& value,
773 SchemaOnErrorStrategy strategy,
774 std::string* error_path,
775 std::string* error) const {
776 if (!valid()) {
777 SchemaErrorFound(error_path, error, "The schema is invalid.");
778 return false;
781 if (!value.IsType(type())) {
782 // Allow the integer to double promotion. Note that range restriction on
783 // double is not supported now.
784 if (value.IsType(base::Value::TYPE_INTEGER) &&
785 type() == base::Value::TYPE_DOUBLE) {
786 return true;
789 SchemaErrorFound(
790 error_path, error, "The value type doesn't match the schema type.");
791 return false;
794 const base::DictionaryValue* dict = NULL;
795 const base::ListValue* list = NULL;
796 int int_value;
797 std::string str_value;
798 if (value.GetAsDictionary(&dict)) {
799 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd();
800 it.Advance()) {
801 SchemaList schema_list = GetMatchingProperties(it.key());
802 if (schema_list.empty()) {
803 // Unknown property was detected.
804 SchemaErrorFound(error_path, error, "Unknown property: " + it.key());
805 if (!StrategyAllowUnknownOnTopLevel(strategy))
806 return false;
807 } else {
808 for (SchemaList::iterator subschema = schema_list.begin();
809 subschema != schema_list.end(); ++subschema) {
810 if (!subschema->Validate(it.value(),
811 StrategyForNextLevel(strategy),
812 error_path,
813 error)) {
814 // Invalid property was detected.
815 AddDictKeyPrefixToPath(it.key(), error_path);
816 if (!StrategyAllowInvalidOnTopLevel(strategy))
817 return false;
822 } else if (value.GetAsList(&list)) {
823 for (base::ListValue::const_iterator it = list->begin(); it != list->end();
824 ++it) {
825 if (!*it ||
826 !GetItems().Validate(**it,
827 StrategyForNextLevel(strategy),
828 error_path,
829 error)) {
830 // Invalid list item was detected.
831 AddListIndexPrefixToPath(it - list->begin(), error_path);
832 if (!StrategyAllowInvalidOnTopLevel(strategy))
833 return false;
836 } else if (value.GetAsInteger(&int_value)) {
837 if (node_->extra != kInvalid &&
838 !ValidateIntegerRestriction(node_->extra, int_value)) {
839 SchemaErrorFound(error_path, error, "Invalid value for integer");
840 return false;
842 } else if (value.GetAsString(&str_value)) {
843 if (node_->extra != kInvalid &&
844 !ValidateStringRestriction(node_->extra, str_value.c_str())) {
845 SchemaErrorFound(error_path, error, "Invalid value for string");
846 return false;
850 return true;
853 bool Schema::Normalize(base::Value* value,
854 SchemaOnErrorStrategy strategy,
855 std::string* error_path,
856 std::string* error,
857 bool* changed) const {
858 if (!valid()) {
859 SchemaErrorFound(error_path, error, "The schema is invalid.");
860 return false;
863 if (!value->IsType(type())) {
864 // Allow the integer to double promotion. Note that range restriction on
865 // double is not supported now.
866 if (value->IsType(base::Value::TYPE_INTEGER) &&
867 type() == base::Value::TYPE_DOUBLE) {
868 return true;
871 SchemaErrorFound(
872 error_path, error, "The value type doesn't match the schema type.");
873 return false;
876 base::DictionaryValue* dict = NULL;
877 base::ListValue* list = NULL;
878 if (value->GetAsDictionary(&dict)) {
879 std::vector<std::string> drop_list; // Contains the keys to drop.
880 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd();
881 it.Advance()) {
882 SchemaList schema_list = GetMatchingProperties(it.key());
883 if (schema_list.empty()) {
884 // Unknown property was detected.
885 SchemaErrorFound(error_path, error, "Unknown property: " + it.key());
886 if (StrategyAllowUnknownOnTopLevel(strategy))
887 drop_list.push_back(it.key());
888 else
889 return false;
890 } else {
891 for (SchemaList::iterator subschema = schema_list.begin();
892 subschema != schema_list.end(); ++subschema) {
893 base::Value* sub_value = NULL;
894 dict->GetWithoutPathExpansion(it.key(), &sub_value);
895 if (!subschema->Normalize(sub_value,
896 StrategyForNextLevel(strategy),
897 error_path,
898 error,
899 changed)) {
900 // Invalid property was detected.
901 AddDictKeyPrefixToPath(it.key(), error_path);
902 if (StrategyAllowInvalidOnTopLevel(strategy)) {
903 drop_list.push_back(it.key());
904 break;
905 } else {
906 return false;
912 if (changed && !drop_list.empty())
913 *changed = true;
914 for (std::vector<std::string>::const_iterator it = drop_list.begin();
915 it != drop_list.end();
916 ++it) {
917 dict->RemoveWithoutPathExpansion(*it, NULL);
919 return true;
920 } else if (value->GetAsList(&list)) {
921 std::vector<size_t> drop_list; // Contains the indexes to drop.
922 for (size_t index = 0; index < list->GetSize(); index++) {
923 base::Value* sub_value = NULL;
924 list->Get(index, &sub_value);
925 if (!sub_value || !GetItems().Normalize(sub_value,
926 StrategyForNextLevel(strategy),
927 error_path,
928 error,
929 changed)) {
930 // Invalid list item was detected.
931 AddListIndexPrefixToPath(index, error_path);
932 if (StrategyAllowInvalidOnTopLevel(strategy))
933 drop_list.push_back(index);
934 else
935 return false;
938 if (changed && !drop_list.empty())
939 *changed = true;
940 for (std::vector<size_t>::reverse_iterator it = drop_list.rbegin();
941 it != drop_list.rend(); ++it) {
942 list->Remove(*it, NULL);
944 return true;
947 return Validate(*value, strategy, error_path, error);
950 // static
951 Schema Schema::Parse(const std::string& content, std::string* error) {
952 // Validate as a generic JSON schema, and ignore unknown attributes; they
953 // may become used in a future version of the schema format.
954 scoped_ptr<base::DictionaryValue> dict = JSONSchemaValidator::IsValidSchema(
955 content, JSONSchemaValidator::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES, error);
956 if (!dict)
957 return Schema();
959 // Validate the main type.
960 std::string string_value;
961 if (!dict->GetString(schema::kType, &string_value) ||
962 string_value != schema::kObject) {
963 *error =
964 "The main schema must have a type attribute with \"object\" value.";
965 return Schema();
968 // Checks for invalid attributes at the top-level.
969 if (dict->HasKey(schema::kAdditionalProperties) ||
970 dict->HasKey(schema::kPatternProperties)) {
971 *error = "\"additionalProperties\" and \"patternProperties\" are not "
972 "supported at the main schema.";
973 return Schema();
976 scoped_refptr<const InternalStorage> storage =
977 InternalStorage::ParseSchema(*dict, error);
978 if (!storage)
979 return Schema();
980 return Schema(storage, storage->root_node());
983 base::Value::Type Schema::type() const {
984 CHECK(valid());
985 return node_->type;
988 Schema::Iterator Schema::GetPropertiesIterator() const {
989 CHECK(valid());
990 CHECK_EQ(base::Value::TYPE_DICTIONARY, type());
991 return Iterator(storage_, storage_->properties(node_->extra));
994 namespace {
996 bool CompareKeys(const PropertyNode& node, const std::string& key) {
997 return node.key < key;
1000 } // namespace
1002 Schema Schema::GetKnownProperty(const std::string& key) const {
1003 CHECK(valid());
1004 CHECK_EQ(base::Value::TYPE_DICTIONARY, type());
1005 const PropertiesNode* node = storage_->properties(node_->extra);
1006 const PropertyNode* begin = storage_->property(node->begin);
1007 const PropertyNode* end = storage_->property(node->end);
1008 const PropertyNode* it = std::lower_bound(begin, end, key, CompareKeys);
1009 if (it != end && it->key == key)
1010 return Schema(storage_, storage_->schema(it->schema));
1011 return Schema();
1014 Schema Schema::GetAdditionalProperties() const {
1015 CHECK(valid());
1016 CHECK_EQ(base::Value::TYPE_DICTIONARY, type());
1017 const PropertiesNode* node = storage_->properties(node_->extra);
1018 if (node->additional == kInvalid)
1019 return Schema();
1020 return Schema(storage_, storage_->schema(node->additional));
1023 SchemaList Schema::GetPatternProperties(const std::string& key) const {
1024 CHECK(valid());
1025 CHECK_EQ(base::Value::TYPE_DICTIONARY, type());
1026 const PropertiesNode* node = storage_->properties(node_->extra);
1027 const PropertyNode* begin = storage_->property(node->end);
1028 const PropertyNode* end = storage_->property(node->pattern_end);
1029 SchemaList matching_properties;
1030 for (const PropertyNode* it = begin; it != end; ++it) {
1031 if (re2::RE2::PartialMatch(key, *storage_->CompileRegex(it->key))) {
1032 matching_properties.push_back(
1033 Schema(storage_, storage_->schema(it->schema)));
1036 return matching_properties;
1039 Schema Schema::GetProperty(const std::string& key) const {
1040 Schema schema = GetKnownProperty(key);
1041 if (schema.valid())
1042 return schema;
1043 return GetAdditionalProperties();
1046 SchemaList Schema::GetMatchingProperties(const std::string& key) const {
1047 SchemaList schema_list;
1049 Schema known_property = GetKnownProperty(key);
1050 if (known_property.valid())
1051 schema_list.push_back(known_property);
1053 SchemaList pattern_properties = GetPatternProperties(key);
1054 schema_list.insert(
1055 schema_list.end(), pattern_properties.begin(), pattern_properties.end());
1057 if (schema_list.empty()) {
1058 Schema additional_property = GetAdditionalProperties();
1059 if (additional_property.valid())
1060 schema_list.push_back(additional_property);
1063 return schema_list;
1066 Schema Schema::GetItems() const {
1067 CHECK(valid());
1068 CHECK_EQ(base::Value::TYPE_LIST, type());
1069 if (node_->extra == kInvalid)
1070 return Schema();
1071 return Schema(storage_, storage_->schema(node_->extra));
1074 bool Schema::ValidateIntegerRestriction(int index, int value) const {
1075 const RestrictionNode* rnode = storage_->restriction(index);
1076 if (rnode->ranged_restriction.min_value <=
1077 rnode->ranged_restriction.max_value) {
1078 return rnode->ranged_restriction.min_value <= value &&
1079 rnode->ranged_restriction.max_value >= value;
1080 } else {
1081 for (int i = rnode->enumeration_restriction.offset_begin;
1082 i < rnode->enumeration_restriction.offset_end; ++i) {
1083 if (*storage_->int_enums(i) == value)
1084 return true;
1086 return false;
1090 bool Schema::ValidateStringRestriction(int index, const char* str) const {
1091 const RestrictionNode* rnode = storage_->restriction(index);
1092 if (rnode->enumeration_restriction.offset_begin <
1093 rnode->enumeration_restriction.offset_end) {
1094 for (int i = rnode->enumeration_restriction.offset_begin;
1095 i < rnode->enumeration_restriction.offset_end; ++i) {
1096 if (strcmp(*storage_->string_enums(i), str) == 0)
1097 return true;
1099 return false;
1100 } else {
1101 int index = rnode->string_pattern_restriction.pattern_index;
1102 DCHECK(index == rnode->string_pattern_restriction.pattern_index_backup);
1103 re2::RE2* regex = storage_->CompileRegex(*storage_->string_enums(index));
1104 return re2::RE2::PartialMatch(str, *regex);
1108 } // namespace policy