| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/webui/extensions/extensions_internals_source.h" |
| |
| #include <memory> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/values.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/browser/activity.h" |
| #include "extensions/browser/disable_reason.h" |
| #include "extensions/browser/event_listener_map.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/process_manager.h" |
| #include "extensions/common/manifest_handlers/permissions_parser.h" |
| #include "extensions/common/permissions/permission_set.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| |
| using extensions::mojom::ManifestLocation; |
| |
| namespace { |
| |
| const char* TypeToString(extensions::Manifest::Type type) { |
| switch (type) { |
| case extensions::Manifest::TYPE_UNKNOWN: |
| return "TYPE_UNKNOWN"; |
| case extensions::Manifest::TYPE_EXTENSION: |
| return "TYPE_EXTENSION"; |
| case extensions::Manifest::TYPE_THEME: |
| return "TYPE_THEME"; |
| case extensions::Manifest::TYPE_USER_SCRIPT: |
| return "TYPE_USER_SCRIPT"; |
| case extensions::Manifest::TYPE_HOSTED_APP: |
| return "TYPE_HOSTED_APP"; |
| case extensions::Manifest::TYPE_LEGACY_PACKAGED_APP: |
| return "TYPE_LEGACY_PACKAGED_APP"; |
| case extensions::Manifest::TYPE_PLATFORM_APP: |
| return "TYPE_PLATFORM_APP"; |
| case extensions::Manifest::TYPE_SHARED_MODULE: |
| return "TYPE_SHARED_MODULE"; |
| case extensions::Manifest::TYPE_LOGIN_SCREEN_EXTENSION: |
| return "TYPE_LOGIN_SCREEN_EXTENSION"; |
| case extensions::Manifest::TYPE_CHROMEOS_SYSTEM_EXTENSION: |
| return "TYPE_CHROMEOS_SYSTEM_EXTENSION"; |
| case extensions::Manifest::NUM_LOAD_TYPES: |
| break; |
| } |
| NOTREACHED(); |
| return ""; |
| } |
| |
| const char* LocationToString(ManifestLocation loc) { |
| switch (loc) { |
| case ManifestLocation::kInvalidLocation: |
| return "INVALID_LOCATION"; |
| case ManifestLocation::kInternal: |
| return "INTERNAL"; |
| case ManifestLocation::kExternalPref: |
| return "EXTERNAL_PREF"; |
| case ManifestLocation::kExternalRegistry: |
| return "EXTERNAL_REGISTRY"; |
| case ManifestLocation::kUnpacked: |
| return "UNPACKED"; |
| case ManifestLocation::kComponent: |
| return "COMPONENT"; |
| case ManifestLocation::kExternalPrefDownload: |
| return "EXTERNAL_PREF_DOWNLOAD"; |
| case ManifestLocation::kExternalPolicyDownload: |
| return "EXTERNAL_POLICY_DOWNLOAD"; |
| case ManifestLocation::kCommandLine: |
| return "COMMAND_LINE"; |
| case ManifestLocation::kExternalPolicy: |
| return "EXTERNAL_POLICY"; |
| case ManifestLocation::kExternalComponent: |
| return "EXTERNAL_COMPONENT"; |
| } |
| NOTREACHED(); |
| return ""; |
| } |
| |
| base::Value::List CreationFlagsToList(int creation_flags) { |
| base::Value::List flags_value; |
| if (creation_flags == extensions::Extension::NO_FLAGS) |
| flags_value.Append("NO_FLAGS"); |
| if (creation_flags & extensions::Extension::REQUIRE_KEY) |
| flags_value.Append("REQUIRE_KEY"); |
| if (creation_flags & extensions::Extension::REQUIRE_MODERN_MANIFEST_VERSION) |
| flags_value.Append("REQUIRE_MODERN_MANIFEST_VERSION"); |
| if (creation_flags & extensions::Extension::ALLOW_FILE_ACCESS) |
| flags_value.Append("ALLOW_FILE_ACCESS"); |
| if (creation_flags & extensions::Extension::FROM_WEBSTORE) |
| flags_value.Append("FROM_WEBSTORE"); |
| if (creation_flags & extensions::Extension::FOLLOW_SYMLINKS_ANYWHERE) |
| flags_value.Append("FOLLOW_SYMLINKS_ANYWHERE"); |
| if (creation_flags & extensions::Extension::ERROR_ON_PRIVATE_KEY) |
| flags_value.Append("ERROR_ON_PRIVATE_KEY"); |
| if (creation_flags & extensions::Extension::WAS_INSTALLED_BY_DEFAULT) |
| flags_value.Append("WAS_INSTALLED_BY_DEFAULT"); |
| if (creation_flags & extensions::Extension::REQUIRE_PERMISSIONS_CONSENT) |
| flags_value.Append("REQUIRE_PERMISSIONS_CONSENT"); |
| if (creation_flags & extensions::Extension::IS_EPHEMERAL) |
| flags_value.Append("IS_EPHEMERAL"); |
| if (creation_flags & extensions::Extension::WAS_INSTALLED_BY_OEM) |
| flags_value.Append("WAS_INSTALLED_BY_OEM"); |
| if (creation_flags & extensions::Extension::MAY_BE_UNTRUSTED) |
| flags_value.Append("MAY_BE_UNTRUSTED"); |
| if (creation_flags & extensions::Extension::WITHHOLD_PERMISSIONS) |
| flags_value.Append("WITHHOLD_PERMISSIONS"); |
| return flags_value; |
| } |
| |
| base::Value::List DisableReasonsToList(int disable_reasons) { |
| static_assert(extensions::disable_reason::DISABLE_REASON_LAST == 1 << 22, |
| "Please add your new disable reason here."); |
| |
| base::Value::List disable_reasons_value; |
| if (disable_reasons & extensions::disable_reason::DISABLE_USER_ACTION) { |
| disable_reasons_value.Append("DISABLE_USER_ACTION"); |
| } |
| if (disable_reasons & |
| extensions::disable_reason::DISABLE_PERMISSIONS_INCREASE) { |
| disable_reasons_value.Append("DISABLE_PERMISSIONS_INCREASE"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_RELOAD) { |
| disable_reasons_value.Append("DISABLE_RELOAD"); |
| } |
| if (disable_reasons & |
| extensions::disable_reason::DISABLE_UNSUPPORTED_REQUIREMENT) { |
| disable_reasons_value.Append("DISABLE_UNSUPPORTED_REQUIREMENT"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_SIDELOAD_WIPEOUT) { |
| disable_reasons_value.Append("DISABLE_SIDELOAD_WIPEOUT"); |
| } |
| if (disable_reasons & |
| extensions::disable_reason::DEPRECATED_DISABLE_UNKNOWN_FROM_SYNC) { |
| disable_reasons_value.Append("DEPRECATED_DISABLE_UNKNOWN_FROM_SYNC"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_NOT_VERIFIED) { |
| disable_reasons_value.Append("DISABLE_NOT_VERIFIED"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_GREYLIST) { |
| disable_reasons_value.Append("DISABLE_GREYLIST"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_CORRUPTED) { |
| disable_reasons_value.Append("DISABLE_CORRUPTED"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_REMOTE_INSTALL) { |
| disable_reasons_value.Append("DISABLE_REMOTE_INSTALL"); |
| } |
| if (disable_reasons & |
| extensions::disable_reason::DISABLE_EXTERNAL_EXTENSION) { |
| disable_reasons_value.Append("DISABLE_EXTERNAL_EXTENSION"); |
| } |
| if (disable_reasons & |
| extensions::disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY) { |
| disable_reasons_value.Append("DISABLE_UPDATE_REQUIRED_BY_POLICY"); |
| } |
| if (disable_reasons & |
| extensions::disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED) { |
| disable_reasons_value.Append("DISABLE_CUSTODIAN_APPROVAL_REQUIRED"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_BLOCKED_BY_POLICY) { |
| disable_reasons_value.Append("DISABLE_BLOCKED_BY_POLICY"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_REINSTALL) { |
| disable_reasons_value.Append("DISABLE_REINSTALL"); |
| } |
| if (disable_reasons & extensions::disable_reason::DISABLE_NOT_ALLOWLISTED) { |
| disable_reasons_value.Append("DISABLE_NOT_ALLOWLISTED"); |
| } |
| if (disable_reasons & |
| extensions::disable_reason::DISABLE_NOT_ASH_KEEPLISTED) { |
| disable_reasons_value.Append("DISABLE_NOT_ASH_KEEPLISTED"); |
| } |
| |
| return disable_reasons_value; |
| } |
| |
| // The JSON we generate looks like this: |
| // Note: |
| // - tab_specific permissions can have 0 or more DICT entries with each tab id |
| // pointing to the api, explicit_host, manifest and scriptable_host permission |
| // lists. |
| // - In some cases manifest or api permissions rather than just being a STRING |
| // can be a DICT with the name keying a more complex object with detailed |
| // information. This is the case for subclasses of ManifestPermission and |
| // APIPermission which override the ToValue function. |
| // |
| // [ { |
| // "creation_flags": [ "ALLOW_FILE_ACCESS", "FROM_WEBSTORE" ], |
| // "disable_reasons": ["DISABLE_USER_ACTION"], |
| // "event_listeners": { |
| // "count": 2, |
| // "events": [ { |
| // "name": "runtime.onInstalled" |
| // }, { |
| // "name": "runtime.onSuspend" |
| // } ] |
| // }, |
| // "id": "bhloflhklmhfpedakmangadcdofhnnoh", |
| // "keepalive": { |
| // "activities": [ { |
| // "extra_data": "render-frame", |
| // "type": "PROCESS_MANAGER" |
| // } ], |
| // "count": 1 |
| // }, |
| // "location": "INTERNAL", |
| // "manifest_version": 2, |
| // "name": "Earth View from Google Earth", |
| // "path": "/user/Extensions/bhloflhklmhfpedakmangadcdofhnnoh/2.18.5_0", |
| // "permissions": { |
| // "active": { |
| // "api": [ ], |
| // "explicit_hosts": [ ], |
| // "manifest": [ ], |
| // "scriptable_hosts": [ ] |
| // }, |
| // "optional": { |
| // "api": [ ], |
| // "explicit_hosts": [ ], |
| // "manifest": [ ], |
| // "scriptable_hosts": [ ] |
| // }, |
| // "tab_specific": { |
| // "4": { |
| // "api": [ ], |
| // "explicit_hosts": [ ], |
| // "manifest": [ ], |
| // "scriptable_hosts": [ ] |
| // } |
| // }, |
| // "withheld": { |
| // "api": [ ], |
| // "explicit_hosts": [ ], |
| // "manifest": [ ], |
| // "scriptable_hosts": [ ] |
| // }, |
| // "type": "TYPE_EXTENSION", |
| // "version": "2.18.5" |
| // } ] |
| // |
| // Which is: |
| // |
| // LIST |
| // DICT |
| // "creation_flags": LIST |
| // STRING |
| // "disable_reasons": LIST |
| // STRING |
| // "event_listeners": DICT |
| // "count": INT |
| // "listeners": LIST |
| // DICT |
| // "event_name": STRING |
| // "filter": DICT |
| // "is_for_service_worker": STRING |
| // "is_lazy": STRING |
| // "url": STRING |
| // "id": STRING |
| // "keepalive": DICT |
| // "activities": LIST |
| // DICT |
| // "extra_data": STRING |
| // "type": STRING |
| // "count": INT |
| // "location": STRING |
| // "manifest_version": INT |
| // "name": STRING |
| // "path": STRING |
| // "permissions": DICT |
| // "active": DICT |
| // "api": LIST |
| // STRING |
| // (see note above for edge cases on all "api" and "manfest" entries) |
| // "explicit_hosts": LIST |
| // STRING |
| // "manifest": LIST |
| // STRING |
| // "scriptable_hosts": LIST |
| // STRING |
| // "optional": DICT |
| // "api": LIST |
| // STRING |
| // "explicit_hosts": LIST |
| // STRING |
| // "manifest": LIST |
| // STRING |
| // "scriptable_hosts": LIST |
| // STRING |
| // "tab_specific": DICT |
| // (see note above for details) |
| // "withheld": DICT |
| // "api": LIST |
| // STRING |
| // "explicit_hosts": LIST |
| // STRING |
| // "manifest": LIST |
| // STRING |
| // "scriptable_hosts": LIST |
| // STRING |
| // "type": STRING |
| // "version": STRING |
| |
| constexpr base::StringPiece kActivitesKey = "activites"; |
| constexpr base::StringPiece kCountKey = "count"; |
| constexpr base::StringPiece kEventNameKey = "event_name"; |
| constexpr base::StringPiece kEventsListenersKey = "event_listeners"; |
| constexpr base::StringPiece kExtraDataKey = "extra_data"; |
| constexpr base::StringPiece kFilterKey = "filter"; |
| constexpr base::StringPiece kInternalsCreationFlagsKey = "creation_flags"; |
| constexpr base::StringPiece kInternalsDisableReasonsKey = "disable_reasons"; |
| constexpr base::StringPiece kInternalsIdKey = "id"; |
| constexpr base::StringPiece kInternalsNameKey = "name"; |
| constexpr base::StringPiece kInternalsVersionKey = "version"; |
| constexpr base::StringPiece kIsForServiceWorkerKey = "is_for_service_worker"; |
| constexpr base::StringPiece kIsLazyKey = "is_lazy"; |
| constexpr base::StringPiece kListenersKey = "listeners"; |
| constexpr base::StringPiece kKeepaliveKey = "keepalive"; |
| constexpr base::StringPiece kListenerUrlKey = "url"; |
| constexpr base::StringPiece kLocationKey = "location"; |
| constexpr base::StringPiece kManifestVersionKey = "manifest_version"; |
| constexpr base::StringPiece kPathKey = "path"; |
| constexpr base::StringPiece kPermissionsKey = "permissions"; |
| constexpr base::StringPiece kPermissionsActiveKey = "active"; |
| constexpr base::StringPiece kPermissionsOptionalKey = "optional"; |
| constexpr base::StringPiece kPermissionsTabSpecificKey = "tab_specific"; |
| constexpr base::StringPiece kPermissionsWithheldKey = "withheld"; |
| constexpr base::StringPiece kPermissionsApiKey = "api"; |
| constexpr base::StringPiece kPermissionsManifestKey = "manifest"; |
| constexpr base::StringPiece kPermissionsExplicitHostsKey = "explicit_hosts"; |
| constexpr base::StringPiece kPermissionsScriptableHostsKey = "scriptable_hosts"; |
| constexpr base::StringPiece kTypeKey = "type"; |
| |
| base::Value::Dict FormatKeepaliveData( |
| extensions::ProcessManager* process_manager, |
| const extensions::Extension* extension) { |
| base::Value::Dict keepalive_data; |
| keepalive_data.Set(kCountKey, |
| process_manager->GetLazyKeepaliveCount(extension)); |
| const extensions::ProcessManager::ActivitiesMultiset activities = |
| process_manager->GetLazyKeepaliveActivities(extension); |
| base::Value::List activities_data; |
| for (const auto& activity : activities) { |
| base::Value::Dict activities_entry; |
| activities_entry.Set(kTypeKey, |
| extensions::Activity::ToString(activity.first)); |
| activities_entry.Set(kExtraDataKey, activity.second); |
| activities_data.Append(std::move(activities_entry)); |
| } |
| keepalive_data.Set(kActivitesKey, std::move(activities_data)); |
| return keepalive_data; |
| } |
| |
| // Formats API and Manifest permissions, which can have details that we add as a |
| // dictionary rather than just the string name |
| template <typename T> |
| base::Value::List FormatDetailedPermissionSet(const T& permissions) { |
| base::Value::List value_list; |
| for (const auto& permission : permissions) { |
| if (auto detail = permission->ToValue()) { |
| base::Value::Dict tmp; |
| tmp.Set(permission->name(), |
| base::Value::FromUniquePtrValue(std::move(detail))); |
| value_list.Append(std::move(tmp)); |
| } else { |
| value_list.Append(permission->name()); |
| } |
| } |
| return value_list; |
| } |
| |
| base::Value::Dict FormatPermissionSet( |
| const extensions::PermissionSet& permission_set) { |
| base::Value::Dict value; |
| |
| value.Set(kPermissionsExplicitHostsKey, |
| permission_set.explicit_hosts().ToValue()); |
| value.Set(kPermissionsScriptableHostsKey, |
| permission_set.scriptable_hosts().ToValue()); |
| value.Set(kPermissionsManifestKey, |
| FormatDetailedPermissionSet(permission_set.manifest_permissions())); |
| value.Set(kPermissionsApiKey, |
| FormatDetailedPermissionSet(permission_set.apis())); |
| |
| return value; |
| } |
| |
| base::Value::Dict FormatPermissionsData( |
| const extensions::Extension& extension) { |
| const extensions::PermissionsData& permissions = |
| *extension.permissions_data(); |
| base::Value::Dict permissions_data; |
| |
| const extensions::PermissionSet& active_permissions = |
| permissions.active_permissions(); |
| permissions_data.Set(kPermissionsActiveKey, |
| FormatPermissionSet(active_permissions)); |
| |
| const extensions::PermissionSet& withheld_permissions = |
| permissions.withheld_permissions(); |
| permissions_data.Set(kPermissionsWithheldKey, |
| FormatPermissionSet(withheld_permissions)); |
| |
| base::Value::Dict tab_specific; |
| for (const auto& tab : permissions.tab_specific_permissions()) { |
| tab_specific.Set(base::NumberToString(tab.first), |
| FormatPermissionSet(*tab.second)); |
| } |
| permissions_data.Set(kPermissionsTabSpecificKey, std::move(tab_specific)); |
| |
| const extensions::PermissionSet& optional_permissions = |
| extensions::PermissionsParser::GetOptionalPermissions(&extension); |
| permissions_data.Set(kPermissionsOptionalKey, |
| FormatPermissionSet(optional_permissions)); |
| |
| return permissions_data; |
| } |
| |
| void AddEventListenerData(extensions::EventRouter* event_router, |
| base::Value::List* data) { |
| // A map of extension ID to the listener data for that extension, |
| // which is of type LIST of DICTIONARY. |
| std::unordered_map<base::StringPiece, base::Value::List, |
| base::StringPieceHash> |
| listeners_map; |
| |
| // Build the map of extension IDs to the list of events. |
| for (const auto& entry : event_router->listeners().listeners()) { |
| for (const auto& listener_entry : entry.second) { |
| auto& listeners_list = listeners_map[listener_entry->extension_id()]; |
| // The data for each listener is a dictionary. |
| base::Value::Dict listener_data; |
| listener_data.Set(kEventNameKey, listener_entry->event_name()); |
| listener_data.Set(kIsForServiceWorkerKey, |
| listener_entry->is_for_service_worker()); |
| listener_data.Set(kIsLazyKey, listener_entry->IsLazy()); |
| listener_data.Set(kListenerUrlKey, listener_entry->listener_url().spec()); |
| // Add the filter if one exists. |
| const base::Value::Dict* const filter = listener_entry->filter(); |
| if (filter) { |
| listener_data.Set(kFilterKey, filter->Clone()); |
| } |
| listeners_list.Append(std::move(listener_data)); |
| } |
| } |
| |
| // Move all of the entries from the map into the output data. |
| for (auto& output_entry : *data) { |
| const base::Value* const value = |
| output_entry.GetDict().Find(kInternalsIdKey); |
| CHECK(value && value->is_string()); |
| const auto it = listeners_map.find(value->GetString()); |
| base::Value::Dict event_listeners; |
| if (it == listeners_map.end()) { |
| // We didn't find any events, so initialize an empty dictionary. |
| event_listeners.Set(kCountKey, 0); |
| event_listeners.Set(kListenersKey, base::Value::List()); |
| } else { |
| // Set the count and the events values. |
| event_listeners.Set(kCountKey, |
| base::checked_cast<int>(it->second.size())); |
| event_listeners.Set(kListenersKey, std::move(it->second)); |
| } |
| output_entry.GetDict().Set(kEventsListenersKey, std::move(event_listeners)); |
| } |
| } |
| |
| } // namespace |
| |
| ExtensionsInternalsSource::ExtensionsInternalsSource(Profile* profile) |
| : profile_(profile) {} |
| |
| ExtensionsInternalsSource::~ExtensionsInternalsSource() = default; |
| |
| std::string ExtensionsInternalsSource::GetSource() { |
| return chrome::kChromeUIExtensionsInternalsHost; |
| } |
| |
| std::string ExtensionsInternalsSource::GetMimeType(const GURL& url) { |
| return "text/plain"; |
| } |
| |
| void ExtensionsInternalsSource::StartDataRequest( |
| const GURL& url, |
| const content::WebContents::Getter& wc_getter, |
| content::URLDataSource::GotDataCallback callback) { |
| std::string json = WriteToString(); |
| std::move(callback).Run( |
| base::MakeRefCounted<base::RefCountedString>(std::move(json))); |
| } |
| |
| std::string ExtensionsInternalsSource::WriteToString() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| std::unique_ptr<extensions::ExtensionSet> extensions = |
| extensions::ExtensionRegistry::Get(profile_) |
| ->GenerateInstalledExtensionsSet(); |
| extensions::ProcessManager* process_manager = |
| extensions::ProcessManager::Get(profile_); |
| extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_); |
| base::Value::List data; |
| for (const auto& extension : *extensions) { |
| base::Value::Dict extension_data; |
| extension_data.Set(kInternalsIdKey, extension->id()); |
| extension_data.Set(kInternalsCreationFlagsKey, |
| CreationFlagsToList(extension->creation_flags())); |
| extension_data.Set( |
| kInternalsDisableReasonsKey, |
| DisableReasonsToList(prefs->GetDisableReasons(extension->id()))); |
| extension_data.Set(kKeepaliveKey, |
| FormatKeepaliveData(process_manager, extension.get())); |
| extension_data.Set(kLocationKey, LocationToString(extension->location())); |
| extension_data.Set(kManifestVersionKey, extension->manifest_version()); |
| extension_data.Set(kInternalsNameKey, extension->name()); |
| extension_data.Set(kPathKey, extension->path().LossyDisplayName()); |
| extension_data.Set(kTypeKey, TypeToString(extension->GetType())); |
| extension_data.Set(kInternalsVersionKey, extension->GetVersionForDisplay()); |
| extension_data.Set(kPermissionsKey, FormatPermissionsData(*extension)); |
| data.Append(std::move(extension_data)); |
| } |
| |
| // Aggregate and add the data for the registered event listeners. |
| AddEventListenerData(extensions::EventRouter::Get(profile_), &data); |
| |
| std::string json; |
| base::JSONWriter::WriteWithOptions( |
| data, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); |
| |
| return json; |
| } |