blob: 18fa9a76cfe70a3fc5f2972dcc89b3df81574cd5 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/policy/core/common/schema.h"
#include <algorithm>
#include <climits>
#include <map>
#include <utility>
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "components/json_schema/json_schema_constants.h"
#include "components/json_schema/json_schema_validator.h"
#include "components/policy/core/common/schema_internal.h"
#include "third_party/re2/re2/re2.h"
namespace schema = json_schema_constants;
namespace policy {
using internal::PropertiesNode;
using internal::PropertyNode;
using internal::RestrictionNode;
using internal::SchemaData;
using internal::SchemaNode;
namespace {
// Maps schema "id" attributes to the corresponding SchemaNode index.
typedef std::map<std::string, int> IdMap;
// List of pairs of references to be assigned later. The string is the "id"
// whose corresponding index should be stored in the pointer, once all the IDs
// are available.
typedef std::vector<std::pair<std::string, int*> > ReferenceList;
// Sizes for the storage arrays. These are calculated in advance so that the
// arrays don't have to be resized during parsing, which would invalidate
// pointers into their contents (i.e. string's c_str() and address of indices
// for "$ref" attributes).
struct StorageSizes {
StorageSizes()
: strings(0), schema_nodes(0), property_nodes(0), properties_nodes(0),
restriction_nodes(0), int_enums(0), string_enums(0) { }
size_t strings;
size_t schema_nodes;
size_t property_nodes;
size_t properties_nodes;
size_t restriction_nodes;
size_t int_enums;
size_t string_enums;
};
// An invalid index, indicating that a node is not present; similar to a NULL
// pointer.
const int kInvalid = -1;
bool SchemaTypeToValueType(const std::string& type_string,
base::Value::Type* type) {
// Note: "any" is not an accepted type.
static const struct {
const char* schema_type;
base::Value::Type value_type;
} kSchemaToValueTypeMap[] = {
{ schema::kArray, base::Value::TYPE_LIST },
{ schema::kBoolean, base::Value::TYPE_BOOLEAN },
{ schema::kInteger, base::Value::TYPE_INTEGER },
{ schema::kNull, base::Value::TYPE_NULL },
{ schema::kNumber, base::Value::TYPE_DOUBLE },
{ schema::kObject, base::Value::TYPE_DICTIONARY },
{ schema::kString, base::Value::TYPE_STRING },
};
for (size_t i = 0; i < arraysize(kSchemaToValueTypeMap); ++i) {
if (kSchemaToValueTypeMap[i].schema_type == type_string) {
*type = kSchemaToValueTypeMap[i].value_type;
return true;
}
}
return false;
}
bool StrategyAllowInvalidOnTopLevel(SchemaOnErrorStrategy strategy) {
return strategy == SCHEMA_ALLOW_INVALID ||
strategy == SCHEMA_ALLOW_INVALID_TOPLEVEL ||
strategy == SCHEMA_ALLOW_INVALID_TOPLEVEL_AND_ALLOW_UNKNOWN;
}
bool StrategyAllowUnknownOnTopLevel(SchemaOnErrorStrategy strategy) {
return strategy != SCHEMA_STRICT;
}
SchemaOnErrorStrategy StrategyForNextLevel(SchemaOnErrorStrategy strategy) {
static SchemaOnErrorStrategy next_level_strategy[] = {
SCHEMA_STRICT, // SCHEMA_STRICT
SCHEMA_STRICT, // SCHEMA_ALLOW_UNKNOWN_TOPLEVEL
SCHEMA_ALLOW_UNKNOWN, // SCHEMA_ALLOW_UNKNOWN
SCHEMA_STRICT, // SCHEMA_ALLOW_INVALID_TOPLEVEL
SCHEMA_ALLOW_UNKNOWN, // SCHEMA_ALLOW_INVALID_TOPLEVEL_AND_ALLOW_UNKNOWN
SCHEMA_ALLOW_INVALID, // SCHEMA_ALLOW_INVALID
};
return next_level_strategy[(int)strategy];
}
void SchemaErrorFound(std::string* error_path,
std::string* error,
const std::string& msg) {
if (error_path)
*error_path = "";
*error = msg;
}
void AddListIndexPrefixToPath(int index, std::string* path) {
if (path) {
if (path->empty())
*path = base::StringPrintf("items[%d]", index);
else
*path = base::StringPrintf("items[%d].", index) + *path;
}
}
void AddDictKeyPrefixToPath(const std::string& key, std::string* path) {
if (path) {
if (path->empty())
*path = key;
else
*path = key + "." + *path;
}
}
} // namespace
// Contains the internal data representation of a Schema. This can either wrap
// a SchemaData owned elsewhere (currently used to wrap the Chrome schema, which
// is generated at compile time), or it can own its own SchemaData.
class Schema::InternalStorage
: public base::RefCountedThreadSafe<InternalStorage> {
public:
static scoped_refptr<const InternalStorage> Wrap(const SchemaData* data);
static scoped_refptr<const InternalStorage> ParseSchema(
const base::DictionaryValue& schema,
std::string* error);
const SchemaData* data() const { return &schema_data_; }
const SchemaNode* root_node() const {
return schema(0);
}
const SchemaNode* schema(int index) const {
return schema_data_.schema_nodes + index;
}
const PropertiesNode* properties(int index) const {
return schema_data_.properties_nodes + index;
}
const PropertyNode* property(int index) const {
return schema_data_.property_nodes + index;
}
const RestrictionNode* restriction(int index) const {
return schema_data_.restriction_nodes + index;
}
const int* int_enums(int index) const {
return schema_data_.int_enums + index;
}
const char** string_enums(int index) const {
return schema_data_.string_enums + index;
}
// Compiles regular expression |pattern|. The result is cached and will be
// returned directly next time.
re2::RE2* CompileRegex(const std::string& pattern) const;
private:
friend class base::RefCountedThreadSafe<InternalStorage>;
InternalStorage();
~InternalStorage();
// Determines the expected |sizes| of the storage for the representation
// of |schema|.
static void DetermineStorageSizes(const base::DictionaryValue& schema,
StorageSizes* sizes);
// Parses the JSON schema in |schema|.
//
// If |schema| has a "$ref" attribute then a pending reference is appended
// to the |reference_list|, and nothing else is done.
//
// Otherwise, |index| gets assigned the index of the corresponding SchemaNode
// in |schema_nodes_|. If the |schema| contains an "id" then that ID is mapped
// to the |index| in the |id_map|.
//
// If |schema| is invalid then |error| gets the error reason and false is
// returned. Otherwise returns true.
bool Parse(const base::DictionaryValue& schema,
int* index,
IdMap* id_map,
ReferenceList* reference_list,
std::string* error);
// Helper for Parse() that gets an already assigned |schema_node| instead of
// an |index| pointer.
bool ParseDictionary(const base::DictionaryValue& schema,
SchemaNode* schema_node,
IdMap* id_map,
ReferenceList* reference_list,
std::string* error);
// Helper for Parse() that gets an already assigned |schema_node| instead of
// an |index| pointer.
bool ParseList(const base::DictionaryValue& schema,
SchemaNode* schema_node,
IdMap* id_map,
ReferenceList* reference_list,
std::string* error);
bool ParseEnum(const base::DictionaryValue& schema,
base::Value::Type type,
SchemaNode* schema_node,
std::string* error);
bool ParseRangedInt(const base::DictionaryValue& schema,
SchemaNode* schema_node,
std::string* error);
bool ParseStringPattern(const base::DictionaryValue& schema,
SchemaNode* schema_node,
std::string* error);
// Assigns the IDs in |id_map| to the pending references in the
// |reference_list|. If an ID is missing then |error| is set and false is
// returned; otherwise returns true.
static bool ResolveReferences(const IdMap& id_map,
const ReferenceList& reference_list,
std::string* error);
// Cache for CompileRegex(), will memorize return value of every call to
// CompileRegex() and return results directly next time.
mutable std::map<std::string, re2::RE2*> regex_cache_;
STLValueDeleter<std::map<std::string, re2::RE2*> > regex_cache_deleter_;
SchemaData schema_data_;
std::vector<std::string> strings_;
std::vector<SchemaNode> schema_nodes_;
std::vector<PropertyNode> property_nodes_;
std::vector<PropertiesNode> properties_nodes_;
std::vector<RestrictionNode> restriction_nodes_;
std::vector<int> int_enums_;
std::vector<const char*> string_enums_;
DISALLOW_COPY_AND_ASSIGN(InternalStorage);
};
Schema::InternalStorage::InternalStorage()
: regex_cache_deleter_(&regex_cache_) {}
Schema::InternalStorage::~InternalStorage() {}
// static
scoped_refptr<const Schema::InternalStorage> Schema::InternalStorage::Wrap(
const SchemaData* data) {
InternalStorage* storage = new InternalStorage();
storage->schema_data_.schema_nodes = data->schema_nodes;
storage->schema_data_.property_nodes = data->property_nodes;
storage->schema_data_.properties_nodes = data->properties_nodes;
storage->schema_data_.restriction_nodes = data->restriction_nodes;
storage->schema_data_.int_enums = data->int_enums;
storage->schema_data_.string_enums = data->string_enums;
return storage;
}
// static
scoped_refptr<const Schema::InternalStorage>
Schema::InternalStorage::ParseSchema(const base::DictionaryValue& schema,
std::string* error) {
// Determine the sizes of the storage arrays and reserve the capacity before
// starting to append nodes and strings. This is important to prevent the
// arrays from being reallocated, which would invalidate the c_str() pointers
// and the addresses of indices to fix.
StorageSizes sizes;
DetermineStorageSizes(schema, &sizes);
scoped_refptr<InternalStorage> storage = new InternalStorage();
storage->strings_.reserve(sizes.strings);
storage->schema_nodes_.reserve(sizes.schema_nodes);
storage->property_nodes_.reserve(sizes.property_nodes);
storage->properties_nodes_.reserve(sizes.properties_nodes);
storage->restriction_nodes_.reserve(sizes.restriction_nodes);
storage->int_enums_.reserve(sizes.int_enums);
storage->string_enums_.reserve(sizes.string_enums);
int root_index = kInvalid;
IdMap id_map;
ReferenceList reference_list;
if (!storage->Parse(schema, &root_index, &id_map, &reference_list, error))
return NULL;
if (root_index == kInvalid) {
*error = "The main schema can't have a $ref";
return NULL;
}
// None of this should ever happen without having been already detected.
// But, if it does happen, then it will lead to corrupted memory; drop
// everything in that case.
if (root_index != 0 ||
sizes.strings != storage->strings_.size() ||
sizes.schema_nodes != storage->schema_nodes_.size() ||
sizes.property_nodes != storage->property_nodes_.size() ||
sizes.properties_nodes != storage->properties_nodes_.size() ||
sizes.restriction_nodes != storage->restriction_nodes_.size() ||
sizes.int_enums != storage->int_enums_.size() ||
sizes.string_enums != storage->string_enums_.size()) {
*error = "Failed to parse the schema due to a Chrome bug. Please file a "
"new issue at http://crbug.com";
return NULL;
}
if (!ResolveReferences(id_map, reference_list, error))
return NULL;
SchemaData* data = &storage->schema_data_;
data->schema_nodes = vector_as_array(&storage->schema_nodes_);
data->property_nodes = vector_as_array(&storage->property_nodes_);
data->properties_nodes = vector_as_array(&storage->properties_nodes_);
data->restriction_nodes = vector_as_array(&storage->restriction_nodes_);
data->int_enums = vector_as_array(&storage->int_enums_);
data->string_enums = vector_as_array(&storage->string_enums_);
return storage;
}
re2::RE2* Schema::InternalStorage::CompileRegex(
const std::string& pattern) const {
std::map<std::string, re2::RE2*>::iterator it = regex_cache_.find(pattern);
if (it == regex_cache_.end()) {
re2::RE2* compiled = new re2::RE2(pattern);
regex_cache_[pattern] = compiled;
return compiled;
}
return it->second;
}
// static
void Schema::InternalStorage::DetermineStorageSizes(
const base::DictionaryValue& schema,
StorageSizes* sizes) {
std::string ref_string;
if (schema.GetString(schema::kRef, &ref_string)) {
// Schemas with a "$ref" attribute don't take additional storage.
return;
}
std::string type_string;
base::Value::Type type = base::Value::TYPE_NULL;
if (!schema.GetString(schema::kType, &type_string) ||
!SchemaTypeToValueType(type_string, &type)) {
// This schema is invalid.
return;
}
sizes->schema_nodes++;
if (type == base::Value::TYPE_LIST) {
const base::DictionaryValue* items = NULL;
if (schema.GetDictionary(schema::kItems, &items))
DetermineStorageSizes(*items, sizes);
} else if (type == base::Value::TYPE_DICTIONARY) {
sizes->properties_nodes++;
const base::DictionaryValue* dict = NULL;
if (schema.GetDictionary(schema::kAdditionalProperties, &dict))
DetermineStorageSizes(*dict, sizes);
const base::DictionaryValue* properties = NULL;
if (schema.GetDictionary(schema::kProperties, &properties)) {
for (base::DictionaryValue::Iterator it(*properties);
!it.IsAtEnd(); it.Advance()) {
// This should have been verified by the JSONSchemaValidator.
CHECK(it.value().GetAsDictionary(&dict));
DetermineStorageSizes(*dict, sizes);
sizes->strings++;
sizes->property_nodes++;
}
}
const base::DictionaryValue* pattern_properties = NULL;
if (schema.GetDictionary(schema::kPatternProperties, &pattern_properties)) {
for (base::DictionaryValue::Iterator it(*pattern_properties);
!it.IsAtEnd(); it.Advance()) {
CHECK(it.value().GetAsDictionary(&dict));
DetermineStorageSizes(*dict, sizes);
sizes->strings++;
sizes->property_nodes++;
}
}
} else if (schema.HasKey(schema::kEnum)) {
const base::ListValue* possible_values = NULL;
if (schema.GetList(schema::kEnum, &possible_values)) {
if (type == base::Value::TYPE_INTEGER) {
sizes->int_enums += possible_values->GetSize();
} else if (type == base::Value::TYPE_STRING) {
sizes->string_enums += possible_values->GetSize();
sizes->strings += possible_values->GetSize();
}
sizes->restriction_nodes++;
}
} else if (type == base::Value::TYPE_INTEGER) {
if (schema.HasKey(schema::kMinimum) || schema.HasKey(schema::kMaximum))
sizes->restriction_nodes++;
} else if (type == base::Value::TYPE_STRING) {
if (schema.HasKey(schema::kPattern)) {
sizes->strings++;
sizes->string_enums++;
sizes->restriction_nodes++;
}
}
}
bool Schema::InternalStorage::Parse(const base::DictionaryValue& schema,
int* index,
IdMap* id_map,
ReferenceList* reference_list,
std::string* error) {
std::string ref_string;
if (schema.GetString(schema::kRef, &ref_string)) {
std::string id_string;
if (schema.GetString(schema::kId, &id_string)) {
*error = "Schemas with a $ref can't have an id";
return false;
}
reference_list->push_back(std::make_pair(ref_string, index));
return true;
}
std::string type_string;
if (!schema.GetString(schema::kType, &type_string)) {
*error = "The schema type must be declared.";
return false;
}
base::Value::Type type = base::Value::TYPE_NULL;
if (!SchemaTypeToValueType(type_string, &type)) {
*error = "Type not supported: " + type_string;
return false;
}
*index = static_cast<int>(schema_nodes_.size());
schema_nodes_.push_back(SchemaNode());
SchemaNode* schema_node = &schema_nodes_.back();
schema_node->type = type;
schema_node->extra = kInvalid;
if (type == base::Value::TYPE_DICTIONARY) {
if (!ParseDictionary(schema, schema_node, id_map, reference_list, error))
return false;
} else if (type == base::Value::TYPE_LIST) {
if (!ParseList(schema, schema_node, id_map, reference_list, error))
return false;
} else if (schema.HasKey(schema::kEnum)) {
if (!ParseEnum(schema, type, schema_node, error))
return false;
} else if (schema.HasKey(schema::kPattern)) {
if (!ParseStringPattern(schema, schema_node, error))
return false;
} else if (schema.HasKey(schema::kMinimum) ||
schema.HasKey(schema::kMaximum)) {
if (type != base::Value::TYPE_INTEGER) {
*error = "Only integers can have minimum and maximum";
return false;
}
if (!ParseRangedInt(schema, schema_node, error))
return false;
}
std::string id_string;
if (schema.GetString(schema::kId, &id_string)) {
if (ContainsKey(*id_map, id_string)) {
*error = "Duplicated id: " + id_string;
return false;
}
(*id_map)[id_string] = *index;
}
return true;
}
bool Schema::InternalStorage::ParseDictionary(
const base::DictionaryValue& schema,
SchemaNode* schema_node,
IdMap* id_map,
ReferenceList* reference_list,
std::string* error) {
int extra = static_cast<int>(properties_nodes_.size());
properties_nodes_.push_back(PropertiesNode());
properties_nodes_[extra].additional = kInvalid;
schema_node->extra = extra;
const base::DictionaryValue* dict = NULL;
if (schema.GetDictionary(schema::kAdditionalProperties, &dict)) {
if (!Parse(*dict, &properties_nodes_[extra].additional,
id_map, reference_list, error)) {
return false;
}
}
properties_nodes_[extra].begin = static_cast<int>(property_nodes_.size());
const base::DictionaryValue* properties = NULL;
if (schema.GetDictionary(schema::kProperties, &properties)) {
// This and below reserves nodes for all of the |properties|, and makes sure
// they are contiguous. Recursive calls to Parse() will append after these
// elements.
property_nodes_.resize(property_nodes_.size() + properties->size());
}
properties_nodes_[extra].end = static_cast<int>(property_nodes_.size());
const base::DictionaryValue* pattern_properties = NULL;
if (schema.GetDictionary(schema::kPatternProperties, &pattern_properties))
property_nodes_.resize(property_nodes_.size() + pattern_properties->size());
properties_nodes_[extra].pattern_end =
static_cast<int>(property_nodes_.size());
if (properties != NULL) {
int base_index = properties_nodes_[extra].begin;
int index = base_index;
for (base::DictionaryValue::Iterator it(*properties);
!it.IsAtEnd(); it.Advance(), ++index) {
// This should have been verified by the JSONSchemaValidator.
CHECK(it.value().GetAsDictionary(&dict));
strings_.push_back(it.key());
property_nodes_[index].key = strings_.back().c_str();
if (!Parse(*dict, &property_nodes_[index].schema,
id_map, reference_list, error)) {
return false;
}
}
CHECK_EQ(static_cast<int>(properties->size()), index - base_index);
}
if (pattern_properties != NULL) {
int base_index = properties_nodes_[extra].end;
int index = base_index;
for (base::DictionaryValue::Iterator it(*pattern_properties);
!it.IsAtEnd(); it.Advance(), ++index) {
CHECK(it.value().GetAsDictionary(&dict));
re2::RE2* compiled_regex = CompileRegex(it.key());
if (!compiled_regex->ok()) {
*error =
"/" + it.key() + "/ is a invalid regex: " + compiled_regex->error();
return false;
}
strings_.push_back(it.key());
property_nodes_[index].key = strings_.back().c_str();
if (!Parse(*dict, &property_nodes_[index].schema,
id_map, reference_list, error)) {
return false;
}
}
CHECK_EQ(static_cast<int>(pattern_properties->size()), index - base_index);
}
if (properties_nodes_[extra].begin == properties_nodes_[extra].pattern_end) {
properties_nodes_[extra].begin = kInvalid;
properties_nodes_[extra].end = kInvalid;
properties_nodes_[extra].pattern_end = kInvalid;
}
return true;
}
bool Schema::InternalStorage::ParseList(const base::DictionaryValue& schema,
SchemaNode* schema_node,
IdMap* id_map,
ReferenceList* reference_list,
std::string* error) {
const base::DictionaryValue* dict = NULL;
if (!schema.GetDictionary(schema::kItems, &dict)) {
*error = "Arrays must declare a single schema for their items.";
return false;
}
return Parse(*dict, &schema_node->extra, id_map, reference_list, error);
}
bool Schema::InternalStorage::ParseEnum(const base::DictionaryValue& schema,
base::Value::Type type,
SchemaNode* schema_node,
std::string* error) {
const base::ListValue *possible_values = NULL;
if (!schema.GetList(schema::kEnum, &possible_values)) {
*error = "Enum attribute must be a list value";
return false;
}
if (possible_values->empty()) {
*error = "Enum attribute must be non-empty";
return false;
}
int offset_begin;
int offset_end;
if (type == base::Value::TYPE_INTEGER) {
offset_begin = static_cast<int>(int_enums_.size());
int value;
for (base::ListValue::const_iterator it = possible_values->begin();
it != possible_values->end(); ++it) {
if (!(*it)->GetAsInteger(&value)) {
*error = "Invalid enumeration member type";
return false;
}
int_enums_.push_back(value);
}
offset_end = static_cast<int>(int_enums_.size());
} else if (type == base::Value::TYPE_STRING) {
offset_begin = static_cast<int>(string_enums_.size());
std::string value;
for (base::ListValue::const_iterator it = possible_values->begin();
it != possible_values->end(); ++it) {
if (!(*it)->GetAsString(&value)) {
*error = "Invalid enumeration member type";
return false;
}
strings_.push_back(value);
string_enums_.push_back(strings_.back().c_str());
}
offset_end = static_cast<int>(string_enums_.size());
} else {
*error = "Enumeration is only supported for integer and string.";
return false;
}
schema_node->extra = static_cast<int>(restriction_nodes_.size());
restriction_nodes_.push_back(RestrictionNode());
restriction_nodes_.back().enumeration_restriction.offset_begin = offset_begin;
restriction_nodes_.back().enumeration_restriction.offset_end = offset_end;
return true;
}
bool Schema::InternalStorage::ParseRangedInt(
const base::DictionaryValue& schema,
SchemaNode* schema_node,
std::string* error) {
int min_value = INT_MIN;
int max_value = INT_MAX;
int value;
if (schema.GetInteger(schema::kMinimum, &value))
min_value = value;
if (schema.GetInteger(schema::kMaximum, &value))
max_value = value;
if (min_value > max_value) {
*error = "Invalid range restriction for int type.";
return false;
}
schema_node->extra = static_cast<int>(restriction_nodes_.size());
restriction_nodes_.push_back(RestrictionNode());
restriction_nodes_.back().ranged_restriction.max_value = max_value;
restriction_nodes_.back().ranged_restriction.min_value = min_value;
return true;
}
bool Schema::InternalStorage::ParseStringPattern(
const base::DictionaryValue& schema,
SchemaNode* schema_node,
std::string* error) {
std::string pattern;
if (!schema.GetString(schema::kPattern, &pattern)) {
*error = "Schema pattern must be a string.";
return false;
}
re2::RE2* compiled_regex = CompileRegex(pattern);
if (!compiled_regex->ok()) {
*error = "/" + pattern + "/ is invalid regex: " + compiled_regex->error();
return false;
}
int index = static_cast<int>(string_enums_.size());
strings_.push_back(pattern);
string_enums_.push_back(strings_.back().c_str());
schema_node->extra = static_cast<int>(restriction_nodes_.size());
restriction_nodes_.push_back(RestrictionNode());
restriction_nodes_.back().string_pattern_restriction.pattern_index = index;
restriction_nodes_.back().string_pattern_restriction.pattern_index_backup =
index;
return true;
}
// static
bool Schema::InternalStorage::ResolveReferences(
const IdMap& id_map,
const ReferenceList& reference_list,
std::string* error) {
for (ReferenceList::const_iterator ref = reference_list.begin();
ref != reference_list.end(); ++ref) {
IdMap::const_iterator id = id_map.find(ref->first);
if (id == id_map.end()) {
*error = "Invalid $ref: " + ref->first;
return false;
}
*ref->second = id->second;
}
return true;
}
Schema::Iterator::Iterator(const scoped_refptr<const InternalStorage>& storage,
const PropertiesNode* node)
: storage_(storage),
it_(storage->property(node->begin)),
end_(storage->property(node->end)) {}
Schema::Iterator::Iterator(const Iterator& iterator)
: storage_(iterator.storage_),
it_(iterator.it_),
end_(iterator.end_) {}
Schema::Iterator::~Iterator() {}
Schema::Iterator& Schema::Iterator::operator=(const Iterator& iterator) {
storage_ = iterator.storage_;
it_ = iterator.it_;
end_ = iterator.end_;
return *this;
}
bool Schema::Iterator::IsAtEnd() const {
return it_ == end_;
}
void Schema::Iterator::Advance() {
++it_;
}
const char* Schema::Iterator::key() const {
return it_->key;
}
Schema Schema::Iterator::schema() const {
return Schema(storage_, storage_->schema(it_->schema));
}
Schema::Schema() : node_(NULL) {}
Schema::Schema(const scoped_refptr<const InternalStorage>& storage,
const SchemaNode* node)
: storage_(storage), node_(node) {}
Schema::Schema(const Schema& schema)
: storage_(schema.storage_), node_(schema.node_) {}
Schema::~Schema() {}
Schema& Schema::operator=(const Schema& schema) {
storage_ = schema.storage_;
node_ = schema.node_;
return *this;
}
// static
Schema Schema::Wrap(const SchemaData* data) {
scoped_refptr<const InternalStorage> storage = InternalStorage::Wrap(data);
return Schema(storage, storage->root_node());
}
bool Schema::Validate(const base::Value& value,
SchemaOnErrorStrategy strategy,
std::string* error_path,
std::string* error) const {
if (!valid()) {
SchemaErrorFound(error_path, error, "The schema is invalid.");
return false;
}
if (!value.IsType(type())) {
// Allow the integer to double promotion. Note that range restriction on
// double is not supported now.
if (value.IsType(base::Value::TYPE_INTEGER) &&
type() == base::Value::TYPE_DOUBLE) {
return true;
}
SchemaErrorFound(
error_path, error, "The value type doesn't match the schema type.");
return false;
}
const base::DictionaryValue* dict = NULL;
const base::ListValue* list = NULL;
int int_value;
std::string str_value;
if (value.GetAsDictionary(&dict)) {
for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd();
it.Advance()) {
SchemaList schema_list = GetMatchingProperties(it.key());
if (schema_list.empty()) {
// Unknown property was detected.
SchemaErrorFound(error_path, error, "Unknown property: " + it.key());
if (!StrategyAllowUnknownOnTopLevel(strategy))
return false;
} else {
for (SchemaList::iterator subschema = schema_list.begin();
subschema != schema_list.end(); ++subschema) {
if (!subschema->Validate(it.value(),
StrategyForNextLevel(strategy),
error_path,
error)) {
// Invalid property was detected.
AddDictKeyPrefixToPath(it.key(), error_path);
if (!StrategyAllowInvalidOnTopLevel(strategy))
return false;
}
}
}
}
} else if (value.GetAsList(&list)) {
for (base::ListValue::const_iterator it = list->begin(); it != list->end();
++it) {
if (!*it ||
!GetItems().Validate(**it,
StrategyForNextLevel(strategy),
error_path,
error)) {
// Invalid list item was detected.
AddListIndexPrefixToPath(it - list->begin(), error_path);
if (!StrategyAllowInvalidOnTopLevel(strategy))
return false;
}
}
} else if (value.GetAsInteger(&int_value)) {
if (node_->extra != kInvalid &&
!ValidateIntegerRestriction(node_->extra, int_value)) {
SchemaErrorFound(error_path, error, "Invalid value for integer");
return false;
}
} else if (value.GetAsString(&str_value)) {
if (node_->extra != kInvalid &&
!ValidateStringRestriction(node_->extra, str_value.c_str())) {
SchemaErrorFound(error_path, error, "Invalid value for string");
return false;
}
}
return true;
}
bool Schema::Normalize(base::Value* value,
SchemaOnErrorStrategy strategy,
std::string* error_path,
std::string* error,
bool* changed) const {
if (!valid()) {
SchemaErrorFound(error_path, error, "The schema is invalid.");
return false;
}
if (!value->IsType(type())) {
// Allow the integer to double promotion. Note that range restriction on
// double is not supported now.
if (value->IsType(base::Value::TYPE_INTEGER) &&
type() == base::Value::TYPE_DOUBLE) {
return true;
}
SchemaErrorFound(
error_path, error, "The value type doesn't match the schema type.");
return false;
}
base::DictionaryValue* dict = NULL;
base::ListValue* list = NULL;
if (value->GetAsDictionary(&dict)) {
std::vector<std::string> drop_list; // Contains the keys to drop.
for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd();
it.Advance()) {
SchemaList schema_list = GetMatchingProperties(it.key());
if (schema_list.empty()) {
// Unknown property was detected.
SchemaErrorFound(error_path, error, "Unknown property: " + it.key());
if (StrategyAllowUnknownOnTopLevel(strategy))
drop_list.push_back(it.key());
else
return false;
} else {
for (SchemaList::iterator subschema = schema_list.begin();
subschema != schema_list.end(); ++subschema) {
base::Value* sub_value = NULL;
dict->GetWithoutPathExpansion(it.key(), &sub_value);
if (!subschema->Normalize(sub_value,
StrategyForNextLevel(strategy),
error_path,
error,
changed)) {
// Invalid property was detected.
AddDictKeyPrefixToPath(it.key(), error_path);
if (StrategyAllowInvalidOnTopLevel(strategy)) {
drop_list.push_back(it.key());
break;
} else {
return false;
}
}
}
}
}
if (changed && !drop_list.empty())
*changed = true;
for (std::vector<std::string>::const_iterator it = drop_list.begin();
it != drop_list.end();
++it) {
dict->RemoveWithoutPathExpansion(*it, NULL);
}
return true;
} else if (value->GetAsList(&list)) {
std::vector<size_t> drop_list; // Contains the indexes to drop.
for (size_t index = 0; index < list->GetSize(); index++) {
base::Value* sub_value = NULL;
list->Get(index, &sub_value);
if (!sub_value || !GetItems().Normalize(sub_value,
StrategyForNextLevel(strategy),
error_path,
error,
changed)) {
// Invalid list item was detected.
AddListIndexPrefixToPath(index, error_path);
if (StrategyAllowInvalidOnTopLevel(strategy))
drop_list.push_back(index);
else
return false;
}
}
if (changed && !drop_list.empty())
*changed = true;
for (std::vector<size_t>::reverse_iterator it = drop_list.rbegin();
it != drop_list.rend(); ++it) {
list->Remove(*it, NULL);
}
return true;
}
return Validate(*value, strategy, error_path, error);
}
// static
Schema Schema::Parse(const std::string& content, std::string* error) {
// Validate as a generic JSON schema, and ignore unknown attributes; they
// may become used in a future version of the schema format.
scoped_ptr<base::DictionaryValue> dict = JSONSchemaValidator::IsValidSchema(
content, JSONSchemaValidator::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES, error);
if (!dict)
return Schema();
// Validate the main type.
std::string string_value;
if (!dict->GetString(schema::kType, &string_value) ||
string_value != schema::kObject) {
*error =
"The main schema must have a type attribute with \"object\" value.";
return Schema();
}
// Checks for invalid attributes at the top-level.
if (dict->HasKey(schema::kAdditionalProperties) ||
dict->HasKey(schema::kPatternProperties)) {
*error = "\"additionalProperties\" and \"patternProperties\" are not "
"supported at the main schema.";
return Schema();
}
scoped_refptr<const InternalStorage> storage =
InternalStorage::ParseSchema(*dict, error);
if (!storage.get())
return Schema();
return Schema(storage, storage->root_node());
}
base::Value::Type Schema::type() const {
CHECK(valid());
return node_->type;
}
Schema::Iterator Schema::GetPropertiesIterator() const {
CHECK(valid());
CHECK_EQ(base::Value::TYPE_DICTIONARY, type());
return Iterator(storage_, storage_->properties(node_->extra));
}
namespace {
bool CompareKeys(const PropertyNode& node, const std::string& key) {
return node.key < key;
}
} // namespace
Schema Schema::GetKnownProperty(const std::string& key) const {
CHECK(valid());
CHECK_EQ(base::Value::TYPE_DICTIONARY, type());
const PropertiesNode* node = storage_->properties(node_->extra);
const PropertyNode* begin = storage_->property(node->begin);
const PropertyNode* end = storage_->property(node->end);
const PropertyNode* it = std::lower_bound(begin, end, key, CompareKeys);
if (it != end && it->key == key)
return Schema(storage_, storage_->schema(it->schema));
return Schema();
}
Schema Schema::GetAdditionalProperties() const {
CHECK(valid());
CHECK_EQ(base::Value::TYPE_DICTIONARY, type());
const PropertiesNode* node = storage_->properties(node_->extra);
if (node->additional == kInvalid)
return Schema();
return Schema(storage_, storage_->schema(node->additional));
}
SchemaList Schema::GetPatternProperties(const std::string& key) const {
CHECK(valid());
CHECK_EQ(base::Value::TYPE_DICTIONARY, type());
const PropertiesNode* node = storage_->properties(node_->extra);
const PropertyNode* begin = storage_->property(node->end);
const PropertyNode* end = storage_->property(node->pattern_end);
SchemaList matching_properties;
for (const PropertyNode* it = begin; it != end; ++it) {
if (re2::RE2::PartialMatch(key, *storage_->CompileRegex(it->key))) {
matching_properties.push_back(
Schema(storage_, storage_->schema(it->schema)));
}
}
return matching_properties;
}
Schema Schema::GetProperty(const std::string& key) const {
Schema schema = GetKnownProperty(key);
if (schema.valid())
return schema;
return GetAdditionalProperties();
}
SchemaList Schema::GetMatchingProperties(const std::string& key) const {
SchemaList schema_list;
Schema known_property = GetKnownProperty(key);
if (known_property.valid())
schema_list.push_back(known_property);
SchemaList pattern_properties = GetPatternProperties(key);
schema_list.insert(
schema_list.end(), pattern_properties.begin(), pattern_properties.end());
if (schema_list.empty()) {
Schema additional_property = GetAdditionalProperties();
if (additional_property.valid())
schema_list.push_back(additional_property);
}
return schema_list;
}
Schema Schema::GetItems() const {
CHECK(valid());
CHECK_EQ(base::Value::TYPE_LIST, type());
if (node_->extra == kInvalid)
return Schema();
return Schema(storage_, storage_->schema(node_->extra));
}
bool Schema::ValidateIntegerRestriction(int index, int value) const {
const RestrictionNode* rnode = storage_->restriction(index);
if (rnode->ranged_restriction.min_value <=
rnode->ranged_restriction.max_value) {
return rnode->ranged_restriction.min_value <= value &&
rnode->ranged_restriction.max_value >= value;
} else {
for (int i = rnode->enumeration_restriction.offset_begin;
i < rnode->enumeration_restriction.offset_end; ++i) {
if (*storage_->int_enums(i) == value)
return true;
}
return false;
}
}
bool Schema::ValidateStringRestriction(int index, const char* str) const {
const RestrictionNode* rnode = storage_->restriction(index);
if (rnode->enumeration_restriction.offset_begin <
rnode->enumeration_restriction.offset_end) {
for (int i = rnode->enumeration_restriction.offset_begin;
i < rnode->enumeration_restriction.offset_end; ++i) {
if (strcmp(*storage_->string_enums(i), str) == 0)
return true;
}
return false;
} else {
int index = rnode->string_pattern_restriction.pattern_index;
DCHECK(index == rnode->string_pattern_restriction.pattern_index_backup);
re2::RE2* regex = storage_->CompileRegex(*storage_->string_enums(index));
return re2::RE2::PartialMatch(str, *regex);
}
}
} // namespace policy