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

Side by Side Diff: chrome/common/extensions/api/extension_api.cc

Issue 12522004: Lazily load extension API schemas (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixes Created 7 years, 9 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "chrome/common/extensions/api/extension_api.h" 5 #include "chrome/common/extensions/api/extension_api.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <string> 8 #include <string>
9 #include <vector> 9 #include <vector>
10 10
(...skipping 18 matching lines...) Expand all
29 using base::DictionaryValue; 29 using base::DictionaryValue;
30 using base::ListValue; 30 using base::ListValue;
31 using base::Value; 31 using base::Value;
32 32
33 namespace extensions { 33 namespace extensions {
34 34
35 using api::GeneratedSchemas; 35 using api::GeneratedSchemas;
36 36
37 namespace { 37 namespace {
38 38
39 const char kUnavailableMessage[] = "You do not have permission to access this "
40 "API. Ensure that the required permission "
41 "or manifest property is included in your "
42 "manifest.json.";
39 const char* kChildKinds[] = { 43 const char* kChildKinds[] = {
40 "functions", 44 "functions",
41 "events" 45 "events"
42 }; 46 };
43 47
44 // Returns true if |dict| has an unprivileged "true" property. 48 // Returns true if |dict| has an unprivileged "true" property.
45 bool IsUnprivileged(const DictionaryValue* dict) { 49 bool IsUnprivileged(const DictionaryValue* dict) {
46 bool unprivileged = false; 50 bool unprivileged = false;
47 return dict->GetBoolean("unprivileged", &unprivileged) && unprivileged; 51 return dict->GetBoolean("unprivileged", &unprivileged) && unprivileged;
48 } 52 }
(...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 // TODO(aa): Remove this code when all API descriptions have been updated. 227 // TODO(aa): Remove this code when all API descriptions have been updated.
224 *feature_type = "api"; 228 *feature_type = "api";
225 *feature_name = full_name; 229 *feature_name = full_name;
226 return; 230 return;
227 } 231 }
228 232
229 *feature_type = full_name.substr(0, colon_index); 233 *feature_type = full_name.substr(0, colon_index);
230 *feature_name = full_name.substr(colon_index + 1); 234 *feature_name = full_name.substr(colon_index + 1);
231 } 235 }
232 236
237 bool ExtensionAPI::UsesFeatureSystem(const std::string& full_name) {
238 std::string api_name = GetAPINameFromFullName(full_name, NULL);
239 return features_.find(api_name) != features_.end();
240 }
241
233 void ExtensionAPI::LoadSchema(const std::string& name, 242 void ExtensionAPI::LoadSchema(const std::string& name,
234 const base::StringPiece& schema) { 243 const base::StringPiece& schema) {
235 scoped_ptr<ListValue> schema_list(LoadSchemaList(name, schema)); 244 scoped_ptr<ListValue> schema_list(LoadSchemaList(name, schema));
236 std::string schema_namespace; 245 std::string schema_namespace;
237 246
238 while (!schema_list->empty()) { 247 while (!schema_list->empty()) {
239 DictionaryValue* schema = NULL; 248 DictionaryValue* schema = NULL;
240 { 249 {
241 Value* value = NULL; 250 Value* value = NULL;
242 schema_list->Remove(schema_list->GetSize() - 1, &value); 251 schema_list->Remove(schema_list->GetSize() - 1, &value);
243 CHECK(value->IsType(Value::TYPE_DICTIONARY)); 252 CHECK(value->IsType(Value::TYPE_DICTIONARY));
244 schema = static_cast<DictionaryValue*>(value); 253 schema = static_cast<DictionaryValue*>(value);
245 } 254 }
246 255
247 CHECK(schema->GetString("namespace", &schema_namespace)); 256 CHECK(schema->GetString("namespace", &schema_namespace));
248 PrefixWithNamespace(schema_namespace, schema); 257 PrefixWithNamespace(schema_namespace, schema);
249 schemas_[schema_namespace] = make_linked_ptr(schema); 258 schemas_[schema_namespace] = make_linked_ptr(schema);
250 CHECK_EQ(1u, unloaded_schemas_.erase(schema_namespace)); 259 CHECK_EQ(1u, unloaded_schemas_.erase(schema_namespace));
251 260
252 // Populate |{completely,partially}_unprivileged_apis_|. 261 // Populate |{completely,partially}_unprivileged_apis_|.
253 // 262
254 // For "partially", only need to look at functions/events; even though 263 // For "partially", only need to look at functions/events; even though
255 // there are unprivileged properties (e.g. in extensions), access to those 264 // there are unprivileged properties (e.g. in extensions), access to those
256 // never reaches C++ land. 265 // never reaches C++ land.
257 bool unprivileged = false; 266 bool unprivileged = false;
258 if (schema->GetBoolean("unprivileged", &unprivileged) && unprivileged) { 267 if (schema->GetBoolean("unprivileged", &unprivileged) && unprivileged) {
259 completely_unprivileged_apis_.insert(schema_namespace); 268 completely_unprivileged_apis_.insert(schema_namespace);
260 } else if (HasUnprivilegedChild(schema, "functions") || 269 } else if (HasUnprivilegedChild(schema, "functions") ||
261 HasUnprivilegedChild(schema, "events") || 270 HasUnprivilegedChild(schema, "events") ||
262 HasUnprivilegedChild(schema, "properties")) { 271 HasUnprivilegedChild(schema, "properties")) {
263 partially_unprivileged_apis_.insert(schema_namespace); 272 partially_unprivileged_apis_.insert(schema_namespace);
264 } 273 }
265 274
266 // Populate |url_matching_apis_|. 275 bool uses_feature_system = false;
267 ListValue* matches = NULL; 276 schema->GetBoolean("uses_feature_system", &uses_feature_system);
268 if (schema->GetList("matches", &matches)) { 277
269 URLPatternSet pattern_set; 278 if (!uses_feature_system) {
270 for (size_t i = 0; i < matches->GetSize(); ++i) { 279 // Populate |url_matching_apis_|.
271 std::string pattern; 280 ListValue* matches = NULL;
272 CHECK(matches->GetString(i, &pattern)); 281 if (schema->GetList("matches", &matches)) {
273 pattern_set.AddPattern( 282 URLPatternSet pattern_set;
274 URLPattern(UserScript::ValidUserScriptSchemes(), pattern)); 283 for (size_t i = 0; i < matches->GetSize(); ++i) {
284 std::string pattern;
285 CHECK(matches->GetString(i, &pattern));
286 pattern_set.AddPattern(
287 URLPattern(UserScript::ValidUserScriptSchemes(), pattern));
288 }
289 url_matching_apis_[schema_namespace] = pattern_set;
275 } 290 }
276 url_matching_apis_[schema_namespace] = pattern_set; 291 continue;
277 } 292 }
278 293
279 // Populate feature maps. 294 // Populate feature maps.
280 // TODO(aa): Consider not storing features that can never run on the current 295 // TODO(aa): Consider not storing features that can never run on the current
281 // machine (e.g., because of platform restrictions). 296 // machine (e.g., because of platform restrictions).
282 bool uses_feature_system = false;
283 schema->GetBoolean("uses_feature_system", &uses_feature_system);
284 if (!uses_feature_system)
285 continue;
286
287 SimpleFeature* feature = new SimpleFeature(); 297 SimpleFeature* feature = new SimpleFeature();
288 feature->set_name(schema_namespace); 298 feature->set_name(schema_namespace);
289 feature->Parse(schema); 299 feature->Parse(schema);
290 300
291 FeatureMap* schema_features = new FeatureMap(); 301 FeatureMap* schema_features = new FeatureMap();
292 CHECK(features_.insert( 302 CHECK(features_.insert(
293 std::make_pair(schema_namespace, 303 std::make_pair(schema_namespace,
294 make_linked_ptr(schema_features))).second); 304 make_linked_ptr(schema_features))).second);
295 CHECK(schema_features->insert( 305 CHECK(schema_features->insert(
296 std::make_pair("", make_linked_ptr(feature))).second); 306 std::make_pair("", make_linked_ptr(feature))).second);
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
401 void ExtensionAPI::RegisterSchema(const std::string& name, 411 void ExtensionAPI::RegisterSchema(const std::string& name,
402 const base::StringPiece& source) { 412 const base::StringPiece& source) {
403 unloaded_schemas_[name] = source; 413 unloaded_schemas_[name] = source;
404 } 414 }
405 415
406 void ExtensionAPI::RegisterDependencyProvider(const std::string& name, 416 void ExtensionAPI::RegisterDependencyProvider(const std::string& name,
407 FeatureProvider* provider) { 417 FeatureProvider* provider) {
408 dependency_providers_[name] = provider; 418 dependency_providers_[name] = provider;
409 } 419 }
410 420
411 bool ExtensionAPI::IsAvailable(const std::string& full_name, 421 Feature::Availability ExtensionAPI::IsAvailable(const std::string& full_name,
412 const Extension* extension, 422 const Extension* extension,
413 Feature::Context context) { 423 Feature::Context context,
424 const GURL& url) {
414 std::set<std::string> dependency_names; 425 std::set<std::string> dependency_names;
415 dependency_names.insert(full_name); 426 dependency_names.insert(full_name);
416 ResolveDependencies(&dependency_names); 427 ResolveDependencies(&dependency_names);
not at google - send to devlin 2013/03/22 18:13:48 as I mentioned in IRC - I think we can entirely ge
cduvall 2013/03/22 20:26:45 Discussed in IRC, the loop is still needed because
417
418 for (std::set<std::string>::iterator iter = dependency_names.begin(); 428 for (std::set<std::string>::iterator iter = dependency_names.begin();
419 iter != dependency_names.end(); ++iter) { 429 iter != dependency_names.end(); ++iter) {
430 ////////////////////////////////////////////////////////////////////////////
431 // TODO(cduvall): Take this out once all APIs have been converted to
432 // features.
433 std::string feature_type;
434 std::string feature_name;
435 SplitDependencyName(*iter, &feature_type, &feature_name);
436 if (feature_type == "api" && !UsesFeatureSystem(feature_name)) {
437 // Check the parent feature's availability if there are dependencies.
438 if (!IsNonFeatureAPIAvailable(full_name, context, extension, url))
439 return Feature::CreateAvailability(Feature::INVALID_CONTEXT,
440 kUnavailableMessage);
441 continue;
442 }
443 ////////////////////////////////////////////////////////////////////////////
444
445 // This seems wrong. The dependencies aren't checked, it just checks the
446 // parent feature again. Changing to check dependencies makes these fail:
447 // ExtensionApiTest.OptionalPermissionsGranted
448 // ExtensionApiTest.OptionalPermissionsAutoConfirm
449 // ExtensionApiTest.OptionalPermissionsDeny
450 //
451 // Checking dependencies would mean that some APIs, like chrome.bookmarks
452 // would now be required to be in the manifest permissions when they
453 // previously did not need to be (the bookmarks API has the
454 // permission:bookmarks dependency). This seems like it would break a lot
455 // of things.
420 Feature* feature = GetFeatureDependency(full_name); 456 Feature* feature = GetFeatureDependency(full_name);
421 CHECK(feature) << *iter; 457 CHECK(feature) << *iter;
422 458
423 Feature::Availability availability = 459 Feature::Availability availability =
424 feature->IsAvailableToContext(extension, context); 460 feature->IsAvailableToContext(extension, context, url);
425 if (!availability.is_available()) 461 if (!availability.is_available())
426 return false; 462 return availability;
427 } 463 }
428 464
429 return true; 465 return Feature::CreateAvailability(Feature::IS_AVAILABLE, "");
430 } 466 }
431 467
432 bool ExtensionAPI::IsPrivileged(const std::string& full_name) { 468 bool ExtensionAPI::IsPrivileged(const std::string& full_name) {
433 std::string child_name; 469 std::string child_name;
434 std::string api_name = GetAPINameFromFullName(full_name, &child_name); 470 std::string api_name = GetAPINameFromFullName(full_name, &child_name);
435 471
436 // First try to use the feature system. 472 // First try to use the feature system.
437 Feature* feature(GetFeature(full_name)); 473 Feature* feature(GetFeature(full_name));
438 if (feature) { 474 if (feature) {
439 // An API is 'privileged' if it or any of its dependencies can only be run 475 // An API is 'privileged' if it or any of its dependencies can only be run
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
517 if (extension.is_platform_app()) { 553 if (extension.is_platform_app()) {
518 for (size_t i = 0; i < arraysize(kDisallowedPlatformAppFeatures); ++i) { 554 for (size_t i = 0; i < arraysize(kDisallowedPlatformAppFeatures); ++i) {
519 if (feature == kDisallowedPlatformAppFeatures[i]) 555 if (feature == kDisallowedPlatformAppFeatures[i])
520 return false; 556 return false;
521 } 557 }
522 } 558 }
523 559
524 return true; 560 return true;
525 } 561 }
526 562
527 // Removes APIs from |apis| that should not be allowed for |extension|.
528 // TODO(kalman/asargent) - Make it possible to specify these rules
529 // declaratively.
530 void RemoveDisallowedAPIs(const Extension& extension,
531 std::set<std::string>* apis) {
532 CHECK(apis);
533 std::set<std::string>::iterator i = apis->begin();
534 while (i != apis->end()) {
535 if (!IsFeatureAllowedForExtension(*i, extension)) {
536 apis->erase(i++);
537 } else {
538 ++i;
539 }
540 }
541 }
542
543 } // namespace 563 } // namespace
544 564
545 std::set<std::string> ExtensionAPI::GetAPIsForContext( 565 bool ExtensionAPI::IsNonFeatureAPIAvailable(const std::string& name,
546 Feature::Context context, const Extension* extension, const GURL& url) { 566 Feature::Context context,
547 // We're forced to load all schemas now because we need to know the metadata 567 const Extension* extension,
548 // about every API -- and the metadata is stored in the schemas themselves. 568 const GURL& url) {
549 // This is a shame.
550 // TODO(aa/kalman): store metadata in a separate file and don't load all
551 // schemas.
552 LoadAllSchemas();
553
554 std::set<std::string> temp_result;
555
556 // First handle all the APIs that have been converted to the feature system.
557 if (extension) {
558 for (APIFeatureMap::iterator iter = features_.begin();
559 iter != features_.end(); ++iter) {
560 if (IsAvailable(iter->first, extension, context))
561 temp_result.insert(iter->first);
562 }
563 }
564
565 // Second, fall back to the old way.
566 // TODO(aa): Remove this when all APIs have been converted.
567 switch (context) { 569 switch (context) {
568 case Feature::UNSPECIFIED_CONTEXT: 570 case Feature::UNSPECIFIED_CONTEXT:
569 break; 571 break;
570 572
571 case Feature::BLESSED_EXTENSION_CONTEXT: 573 case Feature::BLESSED_EXTENSION_CONTEXT:
572 if (extension) { 574 if (extension) {
573 // Availability is determined by the permissions of the extension. 575 // Availability is determined by the permissions of the extension.
574 GetAllowedAPIs(extension, &temp_result); 576 if (!IsAPIAllowed(name, extension))
575 ResolveDependencies(&temp_result); 577 return false;
576 RemoveDisallowedAPIs(*extension, &temp_result); 578 if (!IsFeatureAllowedForExtension(name, *extension))
579 return false;
577 } 580 }
578 break; 581 break;
579 582
580 case Feature::UNBLESSED_EXTENSION_CONTEXT: 583 case Feature::UNBLESSED_EXTENSION_CONTEXT:
581 case Feature::CONTENT_SCRIPT_CONTEXT: 584 case Feature::CONTENT_SCRIPT_CONTEXT:
582 if (extension) { 585 if (extension) {
583 // Same as BLESSED_EXTENSION_CONTEXT, but only those APIs that are 586 // Same as BLESSED_EXTENSION_CONTEXT, but only those APIs that are
584 // unprivileged. 587 // unprivileged.
585 GetAllowedAPIs(extension, &temp_result); 588 if (!IsAPIAllowed(name, extension))
586 // Resolving dependencies before removing unprivileged APIs means that 589 return false;
587 // some unprivileged APIs may have unrealised dependencies. Too bad! 590 if (!IsPrivilegedAPI(name))
588 ResolveDependencies(&temp_result); 591 return false;
589 RemovePrivilegedAPIs(&temp_result);
590 } 592 }
591 break; 593 break;
592 594
593 case Feature::WEB_PAGE_CONTEXT: 595 case Feature::WEB_PAGE_CONTEXT:
596 if (!url_matching_apis_.count(name))
597 return false;
594 if (url.is_valid()) { 598 if (url.is_valid()) {
not at google - send to devlin 2013/03/22 18:13:48 This is subtly incorrect... if the url is invalid
cduvall 2013/03/22 20:26:45 Done.
595 // Availablility is determined by the url. 599 // Availablility is determined by the url.
596 GetAPIsMatchingURL(url, &temp_result); 600 return url_matching_apis_[name].MatchesURL(url);
597 } 601 }
598 break; 602 break;
599 } 603 }
600 604
601 // Filter out all non-API features and remove the feature type part of the 605 return true;
602 // name.
603 std::set<std::string> result;
604 for (std::set<std::string>::iterator iter = temp_result.begin();
605 iter != temp_result.end(); ++iter) {
606 std::string feature_type;
607 std::string feature_name;
608 SplitDependencyName(*iter, &feature_type, &feature_name);
609 if (feature_type == "api")
610 result.insert(feature_name);
611 }
612
613 return result;
614 } 606 }
615 607
616 std::set<std::string> ExtensionAPI::GetAllAPINames() { 608 std::set<std::string> ExtensionAPI::GetAllAPINames() {
617 std::set<std::string> result; 609 std::set<std::string> result;
618 for (SchemaMap::iterator i = schemas_.begin(); i != schemas_.end(); ++i) 610 for (SchemaMap::iterator i = schemas_.begin(); i != schemas_.end(); ++i)
619 result.insert(i->first); 611 result.insert(i->first);
620 for (UnloadedSchemaMap::iterator i = unloaded_schemas_.begin(); 612 for (UnloadedSchemaMap::iterator i = unloaded_schemas_.begin();
621 i != unloaded_schemas_.end(); ++i) { 613 i != unloaded_schemas_.end(); ++i) {
622 result.insert(i->first); 614 result.insert(i->first);
623 } 615 }
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
692 if (last_dot_index == std::string::npos) 684 if (last_dot_index == std::string::npos)
693 break; 685 break;
694 686
695 api_name_candidate = api_name_candidate.substr(0, last_dot_index); 687 api_name_candidate = api_name_candidate.substr(0, last_dot_index);
696 } 688 }
697 689
698 *child_name = ""; 690 *child_name = "";
699 return ""; 691 return "";
700 } 692 }
701 693
702 void ExtensionAPI::GetAllowedAPIs( 694 bool ExtensionAPI::IsAPIAllowed(const std::string& name,
703 const Extension* extension, std::set<std::string>* out) { 695 const Extension* extension) {
704 for (SchemaMap::const_iterator i = schemas_.begin(); i != schemas_.end(); 696 return extension->required_permission_set()->HasAnyAccessToAPI(name) ||
705 ++i) { 697 extension->optional_permission_set()->HasAnyAccessToAPI(name);
not at google - send to devlin 2013/03/22 18:13:48 Hum, this is interesting actually. Again, since we
706 if (features_.find(i->first) != features_.end()) {
707 // This API is controlled by the feature system. Nothing to do here.
708 continue;
709 }
710
711 if (extension->required_permission_set()->HasAnyAccessToAPI(i->first) ||
712 extension->optional_permission_set()->HasAnyAccessToAPI(i->first)) {
713 out->insert(i->first);
714 }
715 }
716 } 698 }
717 699
718 void ExtensionAPI::ResolveDependencies(std::set<std::string>* out) { 700 void ExtensionAPI::ResolveDependencies(std::set<std::string>* out) {
719 std::set<std::string> missing_dependencies; 701 std::set<std::string> missing_dependencies;
720 for (std::set<std::string>::iterator i = out->begin(); i != out->end(); ++i) 702 for (std::set<std::string>::iterator i = out->begin(); i != out->end(); ++i)
721 GetMissingDependencies(*i, *out, &missing_dependencies); 703 GetMissingDependencies(*i, *out, &missing_dependencies);
722 704
723 while (missing_dependencies.size()) { 705 while (missing_dependencies.size()) {
724 std::string next = *missing_dependencies.begin(); 706 std::string next = *missing_dependencies.begin();
725 missing_dependencies.erase(next); 707 missing_dependencies.erase(next);
(...skipping 23 matching lines...) Expand all
749 731
750 for (size_t i = 0; i < dependencies->GetSize(); ++i) { 732 for (size_t i = 0; i < dependencies->GetSize(); ++i) {
751 std::string dependency_name; 733 std::string dependency_name;
752 if (dependencies->GetString(i, &dependency_name) && 734 if (dependencies->GetString(i, &dependency_name) &&
753 !excluding.count(dependency_name)) { 735 !excluding.count(dependency_name)) {
754 out->insert(dependency_name); 736 out->insert(dependency_name);
755 } 737 }
756 } 738 }
757 } 739 }
758 740
759 void ExtensionAPI::RemovePrivilegedAPIs(std::set<std::string>* apis) { 741 bool ExtensionAPI::IsPrivilegedAPI(const std::string& name) {
760 std::set<std::string> privileged_apis; 742 return completely_unprivileged_apis_.count(name) ||
761 for (std::set<std::string>::iterator i = apis->begin(); i != apis->end(); 743 partially_unprivileged_apis_.count(name);
not at google - send to devlin 2013/03/22 18:13:48 another cleanup I'm looking forward to: not doing
762 ++i) {
763 if (!completely_unprivileged_apis_.count(*i) &&
764 !partially_unprivileged_apis_.count(*i)) {
765 privileged_apis.insert(*i);
766 }
767 }
768 for (std::set<std::string>::iterator i = privileged_apis.begin();
769 i != privileged_apis.end(); ++i) {
770 apis->erase(*i);
771 }
772 }
773
774 void ExtensionAPI::GetAPIsMatchingURL(const GURL& url,
775 std::set<std::string>* out) {
776 for (std::map<std::string, URLPatternSet>::const_iterator i =
777 url_matching_apis_.begin(); i != url_matching_apis_.end(); ++i) {
778 if (features_.find(i->first) != features_.end()) {
779 // This API is controlled by the feature system. Nothing to do.
780 continue;
781 }
782
783 if (i->second.MatchesURL(url))
784 out->insert(i->first);
785 }
786 }
787
788 void ExtensionAPI::LoadAllSchemas() {
789 while (unloaded_schemas_.size()) {
790 std::map<std::string, base::StringPiece>::iterator it =
791 unloaded_schemas_.begin();
792 LoadSchema(it->first, it->second);
793 }
794 } 744 }
795 745
796 } // namespace extensions 746 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698