Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(95)

Side by Side Diff: components/policy/core/common/schema.cc

Issue 138003003: Add additional restriction to policy schema internal (part1) (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: more fixes Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/policy/core/common/schema.h" 5 #include "components/policy/core/common/schema.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <climits>
8 #include <map> 9 #include <map>
9 #include <utility> 10 #include <utility>
10 #include <vector> 11 #include <vector>
11 12
12 #include "base/compiler_specific.h" 13 #include "base/compiler_specific.h"
13 #include "base/logging.h" 14 #include "base/logging.h"
14 #include "base/memory/scoped_vector.h" 15 #include "base/memory/scoped_vector.h"
15 #include "components/json_schema/json_schema_constants.h" 16 #include "components/json_schema/json_schema_constants.h"
16 #include "components/json_schema/json_schema_validator.h" 17 #include "components/json_schema/json_schema_validator.h"
17 #include "components/policy/core/common/schema_internal.h" 18 #include "components/policy/core/common/schema_internal.h"
18 19
19 namespace schema = json_schema_constants; 20 namespace schema = json_schema_constants;
20 21
21 namespace policy { 22 namespace policy {
22 23
23 using internal::PropertiesNode; 24 using internal::PropertiesNode;
24 using internal::PropertyNode; 25 using internal::PropertyNode;
26 using internal::RestrictionNode;
25 using internal::SchemaData; 27 using internal::SchemaData;
26 using internal::SchemaNode; 28 using internal::SchemaNode;
27 29
28 namespace { 30 namespace {
29 31
30 // Maps schema "id" attributes to the corresponding SchemaNode index. 32 // Maps schema "id" attributes to the corresponding SchemaNode index.
31 typedef std::map<std::string, int> IdMap; 33 typedef std::map<std::string, int> IdMap;
32 34
33 // List of pairs of references to be assigned later. The string is the "id" 35 // List of pairs of references to be assigned later. The string is the "id"
34 // whose corresponding index should be stored in the pointer, once all the IDs 36 // whose corresponding index should be stored in the pointer, once all the IDs
35 // are available. 37 // are available.
36 typedef std::vector<std::pair<std::string, int*> > ReferenceList; 38 typedef std::vector<std::pair<std::string, int*> > ReferenceList;
37 39
38 // Sizes for the storage arrays. These are calculated in advance so that the 40 // Sizes for the storage arrays. These are calculated in advance so that the
39 // arrays don't have to be resized during parsing, which would invalidate 41 // arrays don't have to be resized during parsing, which would invalidate
40 // pointers into their contents (i.e. string's c_str() and address of indices 42 // pointers into their contents (i.e. string's c_str() and address of indices
41 // for "$ref" attributes). 43 // for "$ref" attributes).
42 struct StorageSizes { 44 struct StorageSizes {
43 StorageSizes() 45 StorageSizes()
44 : strings(0), schema_nodes(0), property_nodes(0), properties_nodes(0) {} 46 : strings(0), schema_nodes(0), property_nodes(0), properties_nodes(0),
47 restriction_nodes(0), int_enums(0), string_enums(0) { }
45 size_t strings; 48 size_t strings;
46 size_t schema_nodes; 49 size_t schema_nodes;
47 size_t property_nodes; 50 size_t property_nodes;
48 size_t properties_nodes; 51 size_t properties_nodes;
52 size_t restriction_nodes;
53 size_t int_enums;
54 size_t string_enums;
49 }; 55 };
50 56
51 // An invalid index, indicating that a node is not present; similar to a NULL 57 // An invalid index, indicating that a node is not present; similar to a NULL
52 // pointer. 58 // pointer.
53 const int kInvalid = -1; 59 const int kInvalid = -1;
54 60
55 bool SchemaTypeToValueType(const std::string& type_string, 61 bool SchemaTypeToValueType(const std::string& type_string,
56 base::Value::Type* type) { 62 base::Value::Type* type) {
57 // Note: "any" is not an accepted type. 63 // Note: "any" is not an accepted type.
58 static const struct { 64 static const struct {
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
101 } 107 }
102 108
103 const PropertiesNode* properties(int index) const { 109 const PropertiesNode* properties(int index) const {
104 return schema_data_.properties_nodes + index; 110 return schema_data_.properties_nodes + index;
105 } 111 }
106 112
107 const PropertyNode* property(int index) const { 113 const PropertyNode* property(int index) const {
108 return schema_data_.property_nodes + index; 114 return schema_data_.property_nodes + index;
109 } 115 }
110 116
117 const RestrictionNode* restriction(int index) const {
118 return schema_data_.restriction_nodes + index;
119 }
120
121 const int* int_enums(int index) const {
122 return schema_data_.int_enums + index;
123 }
124
125 const char** string_enums(int index) const {
126 return schema_data_.string_enums + index;
127 }
128
111 private: 129 private:
112 friend class base::RefCountedThreadSafe<InternalStorage>; 130 friend class base::RefCountedThreadSafe<InternalStorage>;
113 131
114 InternalStorage(); 132 InternalStorage();
115 ~InternalStorage(); 133 ~InternalStorage();
116 134
117 // Determines the expected |sizes| of the storage for the representation 135 // Determines the expected |sizes| of the storage for the representation
118 // of |schema|. 136 // of |schema|.
119 static void DetermineStorageSizes(const base::DictionaryValue& schema, 137 static void DetermineStorageSizes(const base::DictionaryValue& schema,
120 StorageSizes* sizes); 138 StorageSizes* sizes);
(...skipping 24 matching lines...) Expand all
145 std::string* error); 163 std::string* error);
146 164
147 // Helper for Parse() that gets an already assigned |schema_node| instead of 165 // Helper for Parse() that gets an already assigned |schema_node| instead of
148 // an |index| pointer. 166 // an |index| pointer.
149 bool ParseList(const base::DictionaryValue& schema, 167 bool ParseList(const base::DictionaryValue& schema,
150 SchemaNode* schema_node, 168 SchemaNode* schema_node,
151 IdMap* id_map, 169 IdMap* id_map,
152 ReferenceList* reference_list, 170 ReferenceList* reference_list,
153 std::string* error); 171 std::string* error);
154 172
173 bool ParseEnum(const base::DictionaryValue& schema,
174 base::Value::Type type,
175 SchemaNode* schema_node,
176 std::string* error);
177
178 bool ParseRangedInt(const base::DictionaryValue& schema,
179 SchemaNode* schema_node,
180 std::string* error);
181
155 // Assigns the IDs in |id_map| to the pending references in the 182 // Assigns the IDs in |id_map| to the pending references in the
156 // |reference_list|. If an ID is missing then |error| is set and false is 183 // |reference_list|. If an ID is missing then |error| is set and false is
157 // returned; otherwise returns true. 184 // returned; otherwise returns true.
158 static bool ResolveReferences(const IdMap& id_map, 185 static bool ResolveReferences(const IdMap& id_map,
159 const ReferenceList& reference_list, 186 const ReferenceList& reference_list,
160 std::string* error); 187 std::string* error);
161 188
162 SchemaData schema_data_; 189 SchemaData schema_data_;
163 std::vector<std::string> strings_; 190 std::vector<std::string> strings_;
164 std::vector<SchemaNode> schema_nodes_; 191 std::vector<SchemaNode> schema_nodes_;
165 std::vector<PropertyNode> property_nodes_; 192 std::vector<PropertyNode> property_nodes_;
166 std::vector<PropertiesNode> properties_nodes_; 193 std::vector<PropertiesNode> properties_nodes_;
194 std::vector<RestrictionNode> restriction_nodes_;
195 std::vector<int> int_enums_;
196 std::vector<const char*> string_enums_;
167 197
168 DISALLOW_COPY_AND_ASSIGN(InternalStorage); 198 DISALLOW_COPY_AND_ASSIGN(InternalStorage);
169 }; 199 };
170 200
171 Schema::InternalStorage::InternalStorage() {} 201 Schema::InternalStorage::InternalStorage() {}
172 202
173 Schema::InternalStorage::~InternalStorage() {} 203 Schema::InternalStorage::~InternalStorage() {}
174 204
175 // static 205 // static
176 scoped_refptr<const Schema::InternalStorage> Schema::InternalStorage::Wrap( 206 scoped_refptr<const Schema::InternalStorage> Schema::InternalStorage::Wrap(
177 const SchemaData* data) { 207 const SchemaData* data) {
178 InternalStorage* storage = new InternalStorage(); 208 InternalStorage* storage = new InternalStorage();
179 storage->schema_data_.schema_nodes = data->schema_nodes; 209 storage->schema_data_.schema_nodes = data->schema_nodes;
180 storage->schema_data_.property_nodes = data->property_nodes; 210 storage->schema_data_.property_nodes = data->property_nodes;
181 storage->schema_data_.properties_nodes = data->properties_nodes; 211 storage->schema_data_.properties_nodes = data->properties_nodes;
212 storage->schema_data_.restriction_nodes = data->restriction_nodes;
213 storage->schema_data_.int_enums = data->int_enums;
214 storage->schema_data_.string_enums = data->string_enums;
182 return storage; 215 return storage;
183 } 216 }
184 217
185 // static 218 // static
186 scoped_refptr<const Schema::InternalStorage> 219 scoped_refptr<const Schema::InternalStorage>
187 Schema::InternalStorage::ParseSchema(const base::DictionaryValue& schema, 220 Schema::InternalStorage::ParseSchema(const base::DictionaryValue& schema,
188 std::string* error) { 221 std::string* error) {
189 // Determine the sizes of the storage arrays and reserve the capacity before 222 // Determine the sizes of the storage arrays and reserve the capacity before
190 // starting to append nodes and strings. This is important to prevent the 223 // starting to append nodes and strings. This is important to prevent the
191 // arrays from being reallocated, which would invalidate the c_str() pointers 224 // arrays from being reallocated, which would invalidate the c_str() pointers
192 // and the addresses of indices to fix. 225 // and the addresses of indices to fix.
193 StorageSizes sizes; 226 StorageSizes sizes;
194 DetermineStorageSizes(schema, &sizes); 227 DetermineStorageSizes(schema, &sizes);
195 228
196 scoped_refptr<InternalStorage> storage = new InternalStorage(); 229 scoped_refptr<InternalStorage> storage = new InternalStorage();
197 storage->strings_.reserve(sizes.strings); 230 storage->strings_.reserve(sizes.strings);
198 storage->schema_nodes_.reserve(sizes.schema_nodes); 231 storage->schema_nodes_.reserve(sizes.schema_nodes);
199 storage->property_nodes_.reserve(sizes.property_nodes); 232 storage->property_nodes_.reserve(sizes.property_nodes);
200 storage->properties_nodes_.reserve(sizes.properties_nodes); 233 storage->properties_nodes_.reserve(sizes.properties_nodes);
234 storage->restriction_nodes_.reserve(sizes.restriction_nodes);
235 storage->int_enums_.reserve(sizes.int_enums);
236 storage->string_enums_.reserve(sizes.string_enums);
201 237
202 int root_index = kInvalid; 238 int root_index = kInvalid;
203 IdMap id_map; 239 IdMap id_map;
204 ReferenceList reference_list; 240 ReferenceList reference_list;
205 if (!storage->Parse(schema, &root_index, &id_map, &reference_list, error)) 241 if (!storage->Parse(schema, &root_index, &id_map, &reference_list, error))
206 return NULL; 242 return NULL;
207 243
208 if (root_index == kInvalid) { 244 if (root_index == kInvalid) {
209 *error = "The main schema can't have a $ref"; 245 *error = "The main schema can't have a $ref";
210 return NULL; 246 return NULL;
211 } 247 }
212 248
213 // None of this should ever happen without having been already detected. 249 // None of this should ever happen without having been already detected.
214 // But, if it does happen, then it will lead to corrupted memory; drop 250 // But, if it does happen, then it will lead to corrupted memory; drop
215 // everything in that case. 251 // everything in that case.
216 if (root_index != 0 || 252 if (root_index != 0 ||
217 sizes.strings != storage->strings_.size() || 253 sizes.strings != storage->strings_.size() ||
218 sizes.schema_nodes != storage->schema_nodes_.size() || 254 sizes.schema_nodes != storage->schema_nodes_.size() ||
219 sizes.property_nodes != storage->property_nodes_.size() || 255 sizes.property_nodes != storage->property_nodes_.size() ||
220 sizes.properties_nodes != storage->properties_nodes_.size()) { 256 sizes.properties_nodes != storage->properties_nodes_.size() ||
257 sizes.restriction_nodes != storage->restriction_nodes_.size() ||
258 sizes.int_enums != storage->int_enums_.size() ||
259 sizes.string_enums != storage->string_enums_.size()) {
221 *error = "Failed to parse the schema due to a Chrome bug. Please file a " 260 *error = "Failed to parse the schema due to a Chrome bug. Please file a "
222 "new issue at http://crbug.com"; 261 "new issue at http://crbug.com";
223 return NULL; 262 return NULL;
224 } 263 }
225 264
226 if (!ResolveReferences(id_map, reference_list, error)) 265 if (!ResolveReferences(id_map, reference_list, error))
227 return NULL; 266 return NULL;
228 267
229 SchemaData* data = &storage->schema_data_; 268 SchemaData* data = &storage->schema_data_;
230 data->schema_nodes = vector_as_array(&storage->schema_nodes_); 269 data->schema_nodes = vector_as_array(&storage->schema_nodes_);
231 data->property_nodes = vector_as_array(&storage->property_nodes_); 270 data->property_nodes = vector_as_array(&storage->property_nodes_);
232 data->properties_nodes = vector_as_array(&storage->properties_nodes_); 271 data->properties_nodes = vector_as_array(&storage->properties_nodes_);
272 data->restriction_nodes = vector_as_array(&storage->restriction_nodes_);
273 data->int_enums = vector_as_array(&storage->int_enums_);
274 data->string_enums = vector_as_array(&storage->string_enums_);
233 return storage; 275 return storage;
234 } 276 }
235 277
236 // static 278 // static
237 void Schema::InternalStorage::DetermineStorageSizes( 279 void Schema::InternalStorage::DetermineStorageSizes(
238 const base::DictionaryValue& schema, 280 const base::DictionaryValue& schema,
239 StorageSizes* sizes) { 281 StorageSizes* sizes) {
240 std::string ref_string; 282 std::string ref_string;
241 if (schema.GetString(schema::kRef, &ref_string)) { 283 if (schema.GetString(schema::kRef, &ref_string)) {
242 // Schemas with a "$ref" attribute don't take additional storage. 284 // Schemas with a "$ref" attribute don't take additional storage.
(...skipping 25 matching lines...) Expand all
268 if (schema.GetDictionary(schema::kProperties, &properties)) { 310 if (schema.GetDictionary(schema::kProperties, &properties)) {
269 for (base::DictionaryValue::Iterator it(*properties); 311 for (base::DictionaryValue::Iterator it(*properties);
270 !it.IsAtEnd(); it.Advance()) { 312 !it.IsAtEnd(); it.Advance()) {
271 // This should have been verified by the JSONSchemaValidator. 313 // This should have been verified by the JSONSchemaValidator.
272 CHECK(it.value().GetAsDictionary(&dict)); 314 CHECK(it.value().GetAsDictionary(&dict));
273 DetermineStorageSizes(*dict, sizes); 315 DetermineStorageSizes(*dict, sizes);
274 sizes->strings++; 316 sizes->strings++;
275 sizes->property_nodes++; 317 sizes->property_nodes++;
276 } 318 }
277 } 319 }
320 } else if (schema.HasKey(schema::kEnum)) {
321 const base::ListValue* possible_values = NULL;
322 if (schema.GetList(schema::kEnum, &possible_values)) {
323 if (type == base::Value::TYPE_INTEGER) {
324 sizes->int_enums += possible_values->GetSize();
325 } else if (type == base::Value::TYPE_STRING) {
326 sizes->string_enums += possible_values->GetSize();
327 sizes->strings += possible_values->GetSize();
328 }
329 sizes->restriction_nodes++;
330 }
331 } else if (type == base::Value::TYPE_INTEGER) {
332 if (schema.HasKey(schema::kMinimum) || schema.HasKey(schema::kMaximum))
333 sizes->restriction_nodes++;
278 } 334 }
279 } 335 }
280 336
281 bool Schema::InternalStorage::Parse(const base::DictionaryValue& schema, 337 bool Schema::InternalStorage::Parse(const base::DictionaryValue& schema,
282 int* index, 338 int* index,
283 IdMap* id_map, 339 IdMap* id_map,
284 ReferenceList* reference_list, 340 ReferenceList* reference_list,
285 std::string* error) { 341 std::string* error) {
286 std::string ref_string; 342 std::string ref_string;
287 if (schema.GetString(schema::kRef, &ref_string)) { 343 if (schema.GetString(schema::kRef, &ref_string)) {
(...skipping 23 matching lines...) Expand all
311 SchemaNode* schema_node = &schema_nodes_.back(); 367 SchemaNode* schema_node = &schema_nodes_.back();
312 schema_node->type = type; 368 schema_node->type = type;
313 schema_node->extra = kInvalid; 369 schema_node->extra = kInvalid;
314 370
315 if (type == base::Value::TYPE_DICTIONARY) { 371 if (type == base::Value::TYPE_DICTIONARY) {
316 if (!ParseDictionary(schema, schema_node, id_map, reference_list, error)) 372 if (!ParseDictionary(schema, schema_node, id_map, reference_list, error))
317 return false; 373 return false;
318 } else if (type == base::Value::TYPE_LIST) { 374 } else if (type == base::Value::TYPE_LIST) {
319 if (!ParseList(schema, schema_node, id_map, reference_list, error)) 375 if (!ParseList(schema, schema_node, id_map, reference_list, error))
320 return false; 376 return false;
377 } else if (schema.HasKey(schema::kEnum)) {
378 if (!ParseEnum(schema, type, schema_node, error))
379 return false;
380 } else if (schema.HasKey(schema::kMinimum) ||
381 schema.HasKey(schema::kMaximum)) {
382 if (type != base::Value::TYPE_INTEGER) {
383 *error = "Only integers can have minimum and maximum";
384 return false;
385 }
386 if (!ParseRangedInt(schema, schema_node, error))
387 return false;
321 } 388 }
322
323 std::string id_string; 389 std::string id_string;
324 if (schema.GetString(schema::kId, &id_string)) { 390 if (schema.GetString(schema::kId, &id_string)) {
325 if (ContainsKey(*id_map, id_string)) { 391 if (ContainsKey(*id_map, id_string)) {
326 *error = "Duplicated id: " + id_string; 392 *error = "Duplicated id: " + id_string;
327 return false; 393 return false;
328 } 394 }
329 (*id_map)[id_string] = *index; 395 (*id_map)[id_string] = *index;
330 } 396 }
331 397
332 return true; 398 return true;
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
387 ReferenceList* reference_list, 453 ReferenceList* reference_list,
388 std::string* error) { 454 std::string* error) {
389 const base::DictionaryValue* dict = NULL; 455 const base::DictionaryValue* dict = NULL;
390 if (!schema.GetDictionary(schema::kItems, &dict)) { 456 if (!schema.GetDictionary(schema::kItems, &dict)) {
391 *error = "Arrays must declare a single schema for their items."; 457 *error = "Arrays must declare a single schema for their items.";
392 return false; 458 return false;
393 } 459 }
394 return Parse(*dict, &schema_node->extra, id_map, reference_list, error); 460 return Parse(*dict, &schema_node->extra, id_map, reference_list, error);
395 } 461 }
396 462
463 bool Schema::InternalStorage::ParseEnum(const base::DictionaryValue& schema,
464 base::Value::Type type,
465 SchemaNode* schema_node,
466 std::string* error) {
467 const base::ListValue *possible_values = NULL;
468 if (!schema.GetList(schema::kEnum, &possible_values)) {
469 *error = "Enum attribute must be a list value";
470 return false;
471 }
472 if (possible_values->empty()) {
473 *error = "Enum attribute must be non-empty";
474 return false;
475 }
476 int offset_begin;
477 int offset_end;
478 if (type == base::Value::TYPE_INTEGER) {
479 offset_begin = static_cast<int>(int_enums_.size());
480 int value;
481 for (base::ListValue::const_iterator it = possible_values->begin();
482 it != possible_values->end(); ++it) {
483 if (!(*it)->GetAsInteger(&value)) {
484 *error = "Invalid enumeration member type";
485 return false;
486 }
487 int_enums_.push_back(value);
488 }
489 offset_end = static_cast<int>(int_enums_.size());
490 } else if (type == base::Value::TYPE_STRING) {
491 offset_begin = static_cast<int>(string_enums_.size());
492 std::string value;
493 for (base::ListValue::const_iterator it = possible_values->begin();
494 it != possible_values->end(); ++it) {
495 if (!(*it)->GetAsString(&value)) {
496 *error = "Invalid enumeration member type";
497 return false;
498 }
499 strings_.push_back(value);
500 string_enums_.push_back(strings_.back().c_str());
501 }
502 offset_end = static_cast<int>(string_enums_.size());
503 } else {
504 *error = "Enumeration is only supported for integer and string.";
505 return false;
506 }
507 schema_node->extra = static_cast<int>(restriction_nodes_.size());
508 restriction_nodes_.push_back(RestrictionNode());
509 restriction_nodes_.back().enumeration_restriction.offset_begin = offset_begin;
510 restriction_nodes_.back().enumeration_restriction.offset_end = offset_end;
511 return true;
512 }
513
514 bool Schema::InternalStorage::ParseRangedInt(
515 const base::DictionaryValue& schema,
516 SchemaNode* schema_node,
517 std::string* error) {
518 int min_value = INT_MIN;
519 int max_value = INT_MAX;
520 int value;
521 if (schema.GetInteger(schema::kMinimum, &value))
522 min_value = value;
523 if (schema.GetInteger(schema::kMaximum, &value))
524 max_value = value;
525 if (min_value > max_value) {
526 *error = "Invalid range restriction for int type.";
527 return false;
528 }
529 schema_node->extra = static_cast<int>(restriction_nodes_.size());
530 restriction_nodes_.push_back(RestrictionNode());
531 restriction_nodes_.back().ranged_restriction.max_value = max_value;
532 restriction_nodes_.back().ranged_restriction.min_value = min_value;
533 return true;
534 }
535
397 // static 536 // static
398 bool Schema::InternalStorage::ResolveReferences( 537 bool Schema::InternalStorage::ResolveReferences(
399 const IdMap& id_map, 538 const IdMap& id_map,
400 const ReferenceList& reference_list, 539 const ReferenceList& reference_list,
401 std::string* error) { 540 std::string* error) {
402 for (ReferenceList::const_iterator ref = reference_list.begin(); 541 for (ReferenceList::const_iterator ref = reference_list.begin();
403 ref != reference_list.end(); ++ref) { 542 ref != reference_list.end(); ++ref) {
404 IdMap::const_iterator id = id_map.find(ref->first); 543 IdMap::const_iterator id = id_map.find(ref->first);
405 if (id == id_map.end()) { 544 if (id == id_map.end()) {
406 *error = "Invalid $ref: " + ref->first; 545 *error = "Invalid $ref: " + ref->first;
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
578 717
579 Schema Schema::GetItems() const { 718 Schema Schema::GetItems() const {
580 CHECK(valid()); 719 CHECK(valid());
581 CHECK_EQ(base::Value::TYPE_LIST, type()); 720 CHECK_EQ(base::Value::TYPE_LIST, type());
582 if (node_->extra == kInvalid) 721 if (node_->extra == kInvalid)
583 return Schema(); 722 return Schema();
584 return Schema(storage_, storage_->schema(node_->extra)); 723 return Schema(storage_, storage_->schema(node_->extra));
585 } 724 }
586 725
587 } // namespace policy 726 } // namespace policy
OLDNEW
« no previous file with comments | « components/json_schema/json_schema_validator.cc ('k') | components/policy/core/common/schema_internal.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698