// Copyright (c) 2011 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 "chrome/browser/extensions/extension_management_api.h"
#include <map>
#include <string>
#include "base/basictypes.h"
#include "base/json/json_writer.h"
#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "chrome/browser/extensions/extension_event_names.h"
#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_updater.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/webui/extension_icon_source.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "chrome/common/extensions/extension_icon_set.h"
#include "chrome/common/extensions/url_pattern.h"
#include "content/common/notification_service.h"
#include "content/common/notification_type.h"
using base::IntToString;
namespace events = extension_event_names;
namespace {
const char kAppLaunchUrlKey[] = "appLaunchUrl";
const char kDescriptionKey[] = "description";
const char kEnabledKey[] = "enabled";
const char kHomepageURLKey[] = "homepageUrl";
const char kIconsKey[] = "icons";
const char kIdKey[] = "id";
const char kIsAppKey[] = "isApp";
const char kNameKey[] = "name";
const char kOptionsUrlKey[] = "optionsUrl";
const char kPermissionsKey[] = "permissions";
const char kMayDisableKey[] = "mayDisable";
const char kSizeKey[] = "size";
const char kUrlKey[] = "url";
const char kVersionKey[] = "version";
const char kNoExtensionError[] = "No extension with id *";
const char kNotAnAppError[] = "Extension * is not an App";
const char kUserCantDisableError[] = "Extension * can not be disabled by user";
}
ExtensionService* ExtensionManagementFunction::service() {
return profile()->GetExtensionService();
}
static DictionaryValue* CreateExtensionInfo(const Extension& extension,
bool enabled) {
DictionaryValue* info = new DictionaryValue();
info->SetString(kIdKey, extension.id());
info->SetBoolean(kIsAppKey, extension.is_app());
info->SetString(kNameKey, extension.name());
info->SetBoolean(kEnabledKey, enabled);
info->SetBoolean(kMayDisableKey,
Extension::UserMayDisable(extension.location()));
info->SetString(kVersionKey, extension.VersionString());
info->SetString(kDescriptionKey, extension.description());
info->SetString(kOptionsUrlKey,
extension.options_url().possibly_invalid_spec());
info->SetString(kHomepageURLKey,
extension.GetHomepageURL().possibly_invalid_spec());
if (extension.is_app())
info->SetString(kAppLaunchUrlKey,
extension.GetFullLaunchURL().possibly_invalid_spec());
const ExtensionIconSet::IconMap& icons = extension.icons().map();
if (!icons.empty()) {
ListValue* icon_list = new ListValue();
std::map<int, std::string>::const_iterator icon_iter;
for (icon_iter = icons.begin(); icon_iter != icons.end(); ++icon_iter) {
DictionaryValue* icon_info = new DictionaryValue();
Extension::Icons size = static_cast<Extension::Icons>(icon_iter->first);
GURL url = ExtensionIconSource::GetIconURL(
&extension, size, ExtensionIconSet::MATCH_EXACTLY, false);
icon_info->SetInteger(kSizeKey, icon_iter->first);
icon_info->SetString(kUrlKey, url.spec());
icon_list->Append(icon_info);
}
info->Set("icons", icon_list);
}
const std::set<std::string> perms = extension.api_permissions();
ListValue* permission_list = new ListValue();
if (!perms.empty()) {
std::set<std::string>::const_iterator perms_iter;
for (perms_iter = perms.begin(); perms_iter != perms.end(); ++perms_iter) {
StringValue* permission_name = new StringValue(*perms_iter);
permission_list->Append(permission_name);
}
}
info->Set("permissions", permission_list);
ListValue* host_permission_list = new ListValue();
if (!extension.is_hosted_app()) {
// Skip host permissions for hosted apps.
const URLPatternList host_perms = extension.host_permissions();
if (!host_perms.empty()) {
std::vector<URLPattern>::const_iterator host_perms_iter;
for (host_perms_iter = host_perms.begin();
host_perms_iter != host_perms.end();
++host_perms_iter) {
StringValue* name = new StringValue(host_perms_iter->GetAsString());
host_permission_list->Append(name);
}
}
}
info->Set("hostPermissions", host_permission_list);
return info;
}
static void AddExtensionInfo(ListValue* list,
const ExtensionList& extensions,
bool enabled) {
for (ExtensionList::const_iterator i = extensions.begin();
i != extensions.end(); ++i) {
const Extension& extension = **i;
if (extension.location() == Extension::COMPONENT)
continue; // Skip built-in extensions.
list->Append(CreateExtensionInfo(extension, enabled));
}
}
bool GetAllExtensionsFunction::RunImpl() {
ListValue* result = new ListValue();
result_.reset(result);
AddExtensionInfo(result, *service()->extensions(), true);
AddExtensionInfo(result, *service()->disabled_extensions(), false);
return true;
}
bool GetExtensionByIdFunction::RunImpl() {
std::string extension_id;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &extension_id));
const Extension* extension = service()->GetExtensionById(extension_id, true);
if (!extension) {
error_ = ExtensionErrorUtils::FormatErrorMessage(kNoExtensionError,
extension_id);
return false;
}
bool enabled = service()->extension_prefs()->
GetExtensionState(extension_id) == Extension::ENABLED;
DictionaryValue* result = CreateExtensionInfo(*extension, enabled);
result_.reset(result);
return true;
}
bool LaunchAppFunction::RunImpl() {
std::string extension_id;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &extension_id));
const Extension* extension = service()->GetExtensionById(extension_id, true);
if (!extension) {
error_ = ExtensionErrorUtils::FormatErrorMessage(kNoExtensionError,
extension_id);
return false;
}
if (!extension->is_app()) {
error_ = ExtensionErrorUtils::FormatErrorMessage(kNotAnAppError,
extension_id);
return false;
}
// Look at prefs to find the right launch container.
// |default_pref_value| is set to LAUNCH_REGULAR so that if
// the user has not set a preference, we open the app in a tab.
extension_misc::LaunchContainer launch_container =
service()->extension_prefs()->GetLaunchContainer(
extension, ExtensionPrefs::LAUNCH_DEFAULT);
Browser::OpenApplication(profile(), extension, launch_container, NULL);
UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram,
extension_misc::APP_LAUNCH_EXTENSION_API,
extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
return true;
}
bool SetEnabledFunction::RunImpl() {
std::string extension_id;
bool enable;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &extension_id));
EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &enable));
if (!service()->GetExtensionById(extension_id, true)) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
kNoExtensionError, extension_id);
return false;
}
ExtensionPrefs* prefs = service()->extension_prefs();
Extension::State state = prefs->GetExtensionState(extension_id);
if (!Extension::UserMayDisable(
prefs->GetInstalledExtensionInfo(extension_id)->extension_location)) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
kUserCantDisableError, extension_id);
return false;
}
if (state == Extension::DISABLED && enable) {
service()->EnableExtension(extension_id);
} else if (state == Extension::ENABLED && !enable) {
service()->DisableExtension(extension_id);
}
return true;
}
bool UninstallFunction::RunImpl() {
std::string extension_id;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &extension_id));
if (!service()->GetExtensionById(extension_id, true)) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
kNoExtensionError, extension_id);
return false;
}
ExtensionPrefs* prefs = service()->extension_prefs();
if (!Extension::UserMayDisable(
prefs->GetInstalledExtensionInfo(extension_id)->extension_location)) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
kUserCantDisableError, extension_id);
return false;
}
service()->UninstallExtension(extension_id, false /* external_uninstall */,
NULL);
return true;
}
// static
ExtensionManagementEventRouter* ExtensionManagementEventRouter::GetInstance() {
return Singleton<ExtensionManagementEventRouter>::get();
}
ExtensionManagementEventRouter::ExtensionManagementEventRouter() {}
ExtensionManagementEventRouter::~ExtensionManagementEventRouter() {}
void ExtensionManagementEventRouter::Init() {
NotificationType::Type types[] = {
NotificationType::EXTENSION_INSTALLED,
NotificationType::EXTENSION_UNINSTALLED,
NotificationType::EXTENSION_LOADED,
NotificationType::EXTENSION_UNLOADED
};
// Don't re-init (eg in the case of multiple profiles).
if (registrar_.IsEmpty()) {
for (size_t i = 0; i < arraysize(types); i++) {
registrar_.Add(this,
types[i],
NotificationService::AllSources());
}
}
}
void ExtensionManagementEventRouter::Observe(
NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
const char* event_name = NULL;
switch (type.value) {
case NotificationType::EXTENSION_INSTALLED:
event_name = events::kOnExtensionInstalled;
break;
case NotificationType::EXTENSION_UNINSTALLED:
event_name = events::kOnExtensionUninstalled;
break;
case NotificationType::EXTENSION_LOADED:
event_name = events::kOnExtensionEnabled;
break;
case NotificationType::EXTENSION_UNLOADED:
event_name = events::kOnExtensionDisabled;
break;
default:
NOTREACHED();
return;
}
Profile* profile = Source<Profile>(source).ptr();
CHECK(profile);
ListValue args;
if (event_name == events::kOnExtensionUninstalled) {
const std::string& extension_id =
Details<UninstalledExtensionInfo>(details).ptr()->extension_id;
args.Append(Value::CreateStringValue(extension_id));
} else {
const Extension* extension = NULL;
if (event_name == events::kOnExtensionDisabled) {
extension = Details<UnloadedExtensionInfo>(details)->extension;
} else {
extension = Details<const Extension>(details).ptr();
}
CHECK(extension);
ExtensionService* service = profile->GetExtensionService();
bool enabled = service->GetExtensionById(extension->id(), false) != NULL;
args.Append(CreateExtensionInfo(*extension, enabled));
}
std::string args_json;
base::JSONWriter::Write(&args, false /* pretty_print */, &args_json);
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
event_name, args_json, NULL, GURL());
}