blob: 5c810a9f9858661c542742bb39cfa6e1d42491fe [file] [log] [blame]
// 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_webstore_private_api.h"
#include <string>
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
#include "chrome/browser/extensions/extension_install_dialog.h"
#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/net/gaia/token_service.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "chrome/common/extensions/extension_l10n_util.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
const char kAppInstallBubbleKey[] = "appInstallBubble";
const char kIconDataKey[] = "iconData";
const char kIconUrlKey[] = "iconUrl";
const char kIdKey[] = "id";
const char kLocalizedNameKey[] = "localizedName";
const char kLoginKey[] = "login";
const char kManifestKey[] = "manifest";
const char kTokenKey[] = "token";
const char kCannotSpecifyIconDataAndUrlError[] =
"You cannot specify both icon data and an icon url";
const char kInvalidIconUrlError[] = "Invalid icon url";
const char kInvalidIdError[] = "Invalid id";
const char kInvalidManifestError[] = "Invalid manifest";
const char kNoPreviousBeginInstallError[] =
"* does not match a previous call to beginInstall";
const char kUserCancelledError[] = "User cancelled install";
const char kUserGestureRequiredError[] =
"This function must be called during a user gesture";
ProfileSyncService* test_sync_service = NULL;
bool ignore_user_gesture_for_tests = false;
// Returns either the test sync service, or the real one from |profile|.
ProfileSyncService* GetSyncService(Profile* profile) {
if (test_sync_service)
return test_sync_service;
else
return profile->GetProfileSyncService();
}
bool IsWebStoreURL(Profile* profile, const GURL& url) {
ExtensionService* service = profile->GetExtensionService();
const Extension* store = service->GetWebStoreApp();
if (!store) {
NOTREACHED();
return false;
}
return (service->GetExtensionByWebExtent(url) == store);
}
// Helper to create a dictionary with login and token properties set from
// the appropriate values in the passed-in |profile|.
DictionaryValue* CreateLoginResult(Profile* profile) {
DictionaryValue* dictionary = new DictionaryValue();
std::string username = profile->GetPrefs()->GetString(
prefs::kGoogleServicesUsername);
dictionary->SetString(kLoginKey, username);
if (!username.empty()) {
CommandLine* cmdline = CommandLine::ForCurrentProcess();
TokenService* token_service = profile->GetTokenService();
if (cmdline->HasSwitch(switches::kAppsGalleryReturnTokens) &&
token_service->HasTokenForService(GaiaConstants::kGaiaService)) {
dictionary->SetString(kTokenKey,
token_service->GetTokenForService(
GaiaConstants::kGaiaService));
}
}
return dictionary;
}
} // namespace
// static
void WebstorePrivateApi::SetTestingProfileSyncService(
ProfileSyncService* service) {
test_sync_service = service;
}
// static
void BeginInstallFunction::SetIgnoreUserGestureForTests(bool ignore) {
ignore_user_gesture_for_tests = ignore;
}
bool BeginInstallFunction::RunImpl() {
if (!IsWebStoreURL(profile_, source_url()))
return false;
std::string id;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id));
if (!Extension::IdIsValid(id)) {
error_ = kInvalidIdError;
return false;
}
if (!user_gesture() && !ignore_user_gesture_for_tests) {
error_ = kUserGestureRequiredError;
return false;
}
// This gets cleared in CrxInstaller::ConfirmInstall(). TODO(asargent) - in
// the future we may also want to add time-based expiration, where a whitelist
// entry is only valid for some number of minutes.
CrxInstaller::SetWhitelistedInstallId(id);
return true;
}
BeginInstallWithManifestFunction::BeginInstallWithManifestFunction()
: use_app_installed_bubble_(false) {}
BeginInstallWithManifestFunction::~BeginInstallWithManifestFunction() {}
bool BeginInstallWithManifestFunction::RunImpl() {
if (!IsWebStoreURL(profile_, source_url())) {
SetResult(PERMISSION_DENIED);
return false;
}
if (!user_gesture() && !ignore_user_gesture_for_tests) {
SetResult(NO_GESTURE);
error_ = kUserGestureRequiredError;
return false;
}
DictionaryValue* details = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details));
CHECK(details);
EXTENSION_FUNCTION_VALIDATE(details->GetString(kIdKey, &id_));
if (!Extension::IdIsValid(id_)) {
SetResult(INVALID_ID);
error_ = kInvalidIdError;
return false;
}
EXTENSION_FUNCTION_VALIDATE(details->GetString(kManifestKey, &manifest_));
if (details->HasKey(kIconDataKey) && details->HasKey(kIconUrlKey)) {
SetResult(ICON_ERROR);
error_ = kCannotSpecifyIconDataAndUrlError;
return false;
}
if (details->HasKey(kIconDataKey))
EXTENSION_FUNCTION_VALIDATE(details->GetString(kIconDataKey, &icon_data_));
GURL icon_url;
if (details->HasKey(kIconUrlKey)) {
std::string tmp_url;
EXTENSION_FUNCTION_VALIDATE(details->GetString(kIconUrlKey, &tmp_url));
icon_url = source_url().Resolve(tmp_url);
if (!icon_url.is_valid()) {
SetResult(INVALID_ICON_URL);
error_ = kInvalidIconUrlError;
return false;
}
}
if (details->HasKey(kLocalizedNameKey))
EXTENSION_FUNCTION_VALIDATE(details->GetString(kLocalizedNameKey,
&localized_name_));
if (details->HasKey(kAppInstallBubbleKey))
EXTENSION_FUNCTION_VALIDATE(details->GetBoolean(
kAppInstallBubbleKey, &use_app_installed_bubble_));
net::URLRequestContextGetter* context_getter = NULL;
if (!icon_url.is_empty())
context_getter = profile()->GetRequestContext();
scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
this, manifest_, icon_data_, icon_url, context_getter);
// The helper will call us back via OnWebstoreParseSuccess or
// OnWebstoreParseFailure.
helper->Start();
// Matched with a Release in OnWebstoreParseSuccess/OnWebstoreParseFailure.
AddRef();
// The response is sent asynchronously in OnWebstoreParseSuccess/
// OnWebstoreParseFailure.
return true;
}
void BeginInstallWithManifestFunction::SetResult(ResultCode code) {
switch (code) {
case ERROR_NONE:
result_.reset(Value::CreateStringValue(""));
break;
case UNKNOWN_ERROR:
result_.reset(Value::CreateStringValue("unknown_error"));
break;
case USER_CANCELLED:
result_.reset(Value::CreateStringValue("user_cancelled"));
break;
case MANIFEST_ERROR:
result_.reset(Value::CreateStringValue("manifest_error"));
break;
case ICON_ERROR:
result_.reset(Value::CreateStringValue("icon_error"));
break;
case INVALID_ID:
result_.reset(Value::CreateStringValue("invalid_id"));
break;
case PERMISSION_DENIED:
result_.reset(Value::CreateStringValue("permission_denied"));
break;
case NO_GESTURE:
result_.reset(Value::CreateStringValue("no_gesture"));
break;
case INVALID_ICON_URL:
result_.reset(Value::CreateStringValue("invalid_icon_url"));
break;
default:
CHECK(false);
}
}
// static
void BeginInstallWithManifestFunction::SetIgnoreUserGestureForTests(
bool ignore) {
ignore_user_gesture_for_tests = ignore;
}
void BeginInstallWithManifestFunction::OnWebstoreParseSuccess(
const SkBitmap& icon, DictionaryValue* parsed_manifest) {
CHECK(parsed_manifest);
icon_ = icon;
parsed_manifest_.reset(parsed_manifest);
ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INSTALL_PROMPT);
ShowExtensionInstallDialogForManifest(
profile(),
this,
parsed_manifest,
id_,
localized_name_,
"", // no localized description
&icon_,
prompt,
&dummy_extension_);
if (!dummy_extension_.get()) {
OnWebstoreParseFailure(WebstoreInstallHelper::Delegate::MANIFEST_ERROR,
kInvalidManifestError);
return;
}
// Control flow finishes up in InstallUIProceed or InstallUIAbort.
}
void BeginInstallWithManifestFunction::OnWebstoreParseFailure(
WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code,
const std::string& error_message) {
// Map from WebstoreInstallHelper's result codes to ours.
switch (result_code) {
case WebstoreInstallHelper::Delegate::UNKNOWN_ERROR:
SetResult(UNKNOWN_ERROR);
break;
case WebstoreInstallHelper::Delegate::ICON_ERROR:
SetResult(ICON_ERROR);
break;
case WebstoreInstallHelper::Delegate::MANIFEST_ERROR:
SetResult(MANIFEST_ERROR);
break;
default:
CHECK(false);
}
error_ = error_message;
SendResponse(false);
// Matches the AddRef in RunImpl().
Release();
}
void BeginInstallWithManifestFunction::InstallUIProceed() {
CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry;
entry->parsed_manifest.reset(parsed_manifest_.release());
entry->localized_name = localized_name_;
entry->use_app_installed_bubble = use_app_installed_bubble_;
CrxInstaller::SetWhitelistEntry(id_, entry);
SetResult(ERROR_NONE);
SendResponse(true);
// The Permissions_Install histogram is recorded from the ExtensionService
// for all extension installs, so we only need to record the web store
// specific histogram here.
ExtensionService::RecordPermissionMessagesHistogram(
dummy_extension_, "Extensions.Permissions_WebStoreInstall");
// Matches the AddRef in RunImpl().
Release();
}
void BeginInstallWithManifestFunction::InstallUIAbort(bool user_initiated) {
error_ = kUserCancelledError;
SetResult(USER_CANCELLED);
SendResponse(false);
// The web store install histograms are a subset of the install histograms.
// We need to record both histograms here since CrxInstaller::InstallUIAbort
// is never called for web store install cancellations
std::string histogram_name = user_initiated ?
"Extensions.Permissions_WebStoreInstallCancel" :
"Extensions.Permissions_WebStoreInstallAbort";
ExtensionService::RecordPermissionMessagesHistogram(
dummy_extension_, histogram_name.c_str());
histogram_name = user_initiated ?
"Extensions.Permissions_InstallCancel" :
"Extensions.Permissions_InstallAbort";
ExtensionService::RecordPermissionMessagesHistogram(
dummy_extension_, histogram_name.c_str());
// Matches the AddRef in RunImpl().
Release();
}
bool CompleteInstallFunction::RunImpl() {
if (!IsWebStoreURL(profile_, source_url()))
return false;
std::string id;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id));
if (!Extension::IdIsValid(id)) {
error_ = kInvalidIdError;
return false;
}
if (!CrxInstaller::IsIdWhitelisted(id) &&
!CrxInstaller::GetWhitelistEntry(id)) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
kNoPreviousBeginInstallError, id);
return false;
}
GURL install_url(extension_urls::GetWebstoreInstallUrl(
id, g_browser_process->GetApplicationLocale()));
// The download url for the given |id| is now contained in |url|. We
// navigate the current (calling) tab to this url which will result in a
// download starting. Once completed it will go through the normal extension
// install flow. The above call to SetWhitelistedInstallId will bypass the
// normal permissions install dialog.
NavigationController& controller =
dispatcher()->delegate()->GetAssociatedTabContents()->controller();
controller.LoadURL(install_url, source_url(), PageTransition::LINK);
return true;
}
bool GetBrowserLoginFunction::RunImpl() {
if (!IsWebStoreURL(profile_, source_url()))
return false;
result_.reset(CreateLoginResult(profile_->GetOriginalProfile()));
return true;
}
bool GetStoreLoginFunction::RunImpl() {
if (!IsWebStoreURL(profile_, source_url()))
return false;
ExtensionService* service = profile_->GetExtensionService();
ExtensionPrefs* prefs = service->extension_prefs();
std::string login;
if (prefs->GetWebStoreLogin(&login)) {
result_.reset(Value::CreateStringValue(login));
} else {
result_.reset(Value::CreateStringValue(std::string()));
}
return true;
}
bool SetStoreLoginFunction::RunImpl() {
if (!IsWebStoreURL(profile_, source_url()))
return false;
std::string login;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &login));
ExtensionService* service = profile_->GetExtensionService();
ExtensionPrefs* prefs = service->extension_prefs();
prefs->SetWebStoreLogin(login);
return true;
}