blob: 745e2afd901b34226ec3d28568bf7973937fe984 [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/custom_handlers/protocol_handler_registry.h"
#include <utility>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/custom_handlers/register_protocol_handler_infobar_delegate.h"
#include "chrome/browser/net/chrome_url_request_context.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/custom_handlers/protocol_handler.h"
#include "chrome/common/pref_names.h"
#include "content/browser/browser_thread.h"
#include "content/browser/child_process_security_policy.h"
#include "content/common/notification_service.h"
#include "net/base/network_delegate.h"
#include "net/url_request/url_request_redirect_job.h"
// ProtocolHandlerRegistry -----------------------------------------------------
ProtocolHandlerRegistry::ProtocolHandlerRegistry(Profile* profile,
Delegate* delegate)
: profile_(profile),
delegate_(delegate),
enabled_(true),
enabled_io_(enabled_),
is_loading_(false) {
}
ProtocolHandlerRegistry::~ProtocolHandlerRegistry() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(default_client_observers_.empty());
}
void ProtocolHandlerRegistry::Finalize() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
delegate_.reset(NULL);
// We free these now in case there are any outstanding workers running. If
// we didn't free them they could respond to workers and try to update the
// protocol handler registry after it was deleted.
// Observers remove themselves from this list when they are deleted; so
// we delete the last item until none are left in the list.
while (!default_client_observers_.empty()) {
delete default_client_observers_.back();
}
}
const ProtocolHandlerRegistry::ProtocolHandlerList*
ProtocolHandlerRegistry::GetHandlerList(
const std::string& scheme) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ProtocolHandlerMultiMap::const_iterator p = protocol_handlers_.find(scheme);
if (p == protocol_handlers_.end()) {
return NULL;
}
return &p->second;
}
ProtocolHandlerRegistry::ProtocolHandlerList
ProtocolHandlerRegistry::GetHandlersFor(
const std::string& scheme) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ProtocolHandlerMultiMap::const_iterator p = protocol_handlers_.find(scheme);
if (p == protocol_handlers_.end()) {
return ProtocolHandlerList();
}
return p->second;
}
ProtocolHandlerRegistry::ProtocolHandlerList
ProtocolHandlerRegistry::GetIgnoredHandlers() {
return ignored_protocol_handlers_;
}
void ProtocolHandlerRegistry::RegisterProtocolHandler(
const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(CanSchemeBeOverridden(handler.protocol()));
DCHECK(!handler.IsEmpty());
if (IsRegistered(handler)) {
return;
}
if (enabled_ && !delegate_->IsExternalHandlerRegistered(handler.protocol()))
delegate_->RegisterExternalHandler(handler.protocol());
InsertHandler(handler);
}
void ProtocolHandlerRegistry::InsertHandler(const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ProtocolHandlerMultiMap::iterator p =
protocol_handlers_.find(handler.protocol());
if (p != protocol_handlers_.end()) {
p->second.push_back(handler);
return;
}
ProtocolHandlerList new_list;
new_list.push_back(handler);
protocol_handlers_[handler.protocol()] = new_list;
}
void ProtocolHandlerRegistry::IgnoreProtocolHandler(
const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ignored_protocol_handlers_.push_back(handler);
}
void ProtocolHandlerRegistry::Enable() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (enabled_) {
return;
}
enabled_ = true;
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this, &ProtocolHandlerRegistry::EnableIO));
ProtocolHandlerMap::const_iterator p;
for (p = default_handlers_.begin(); p != default_handlers_.end(); ++p) {
delegate_->RegisterExternalHandler(p->first);
}
Save();
NotifyChanged();
}
void ProtocolHandlerRegistry::Disable() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!enabled_) {
return;
}
enabled_ = false;
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this, &ProtocolHandlerRegistry::DisableIO));
ProtocolHandlerMap::const_iterator p;
for (p = default_handlers_.begin(); p != default_handlers_.end(); ++p) {
delegate_->DeregisterExternalHandler(p->first);
}
Save();
NotifyChanged();
}
std::vector<const DictionaryValue*>
ProtocolHandlerRegistry::GetHandlersFromPref(const char* pref_name) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
std::vector<const DictionaryValue*> result;
PrefService* prefs = profile_->GetPrefs();
if (!prefs->HasPrefPath(pref_name)) {
return result;
}
const ListValue* handlers = prefs->GetList(pref_name);
if (handlers) {
for (size_t i = 0; i < handlers->GetSize(); ++i) {
DictionaryValue* dict;
if (!handlers->GetDictionary(i, &dict))
continue;
if (ProtocolHandler::IsValidDict(dict)) {
result.push_back(dict);
}
}
}
return result;
}
namespace {
// If true default protocol handlers will be removed if the OS level
// registration for a protocol is no longer Chrome.
bool ShouldRemoveHandlersNotInOS() {
#if defined(OS_LINUX)
// We don't do this on Linux as the OS registration there is not reliable,
// and Chrome OS doesn't have any notion of OS registration.
// TODO(benwells): When Linux support is more reliable remove this
// difference (http://crbug.com/88255).
return false;
#else
const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
return ShellIntegration::CanSetAsDefaultProtocolClient() &&
!cmd_line.HasSwitch(switches::kDisableCustomProtocolOSCheck);
#endif
}
} // namespace
void ProtocolHandlerRegistry::Load() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
is_loading_ = true;
PrefService* prefs = profile_->GetPrefs();
if (prefs->HasPrefPath(prefs::kCustomHandlersEnabled)) {
enabled_ = prefs->GetBoolean(prefs::kCustomHandlersEnabled);
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this, enabled_ ? &ProtocolHandlerRegistry::EnableIO :
&ProtocolHandlerRegistry::DisableIO));
}
std::vector<const DictionaryValue*> registered_handlers =
GetHandlersFromPref(prefs::kRegisteredProtocolHandlers);
for (std::vector<const DictionaryValue*>::const_iterator p =
registered_handlers.begin();
p != registered_handlers.end(); ++p) {
ProtocolHandler handler = ProtocolHandler::CreateProtocolHandler(*p);
RegisterProtocolHandler(handler);
bool is_default = false;
if ((*p)->GetBoolean("default", &is_default) && is_default) {
SetDefault(handler);
}
}
std::vector<const DictionaryValue*> ignored_handlers =
GetHandlersFromPref(prefs::kIgnoredProtocolHandlers);
for (std::vector<const DictionaryValue*>::const_iterator p =
ignored_handlers.begin();
p != ignored_handlers.end(); ++p) {
IgnoreProtocolHandler(ProtocolHandler::CreateProtocolHandler(*p));
}
is_loading_ = false;
// For each default protocol handler, check that we are still registered
// with the OS as the default application.
if (ShouldRemoveHandlersNotInOS()) {
for (ProtocolHandlerMap::const_iterator p = default_handlers_.begin();
p != default_handlers_.end(); ++p) {
ProtocolHandler handler = p->second;
DefaultClientObserver* observer = delegate_->CreateShellObserver(this);
scoped_refptr<ShellIntegration::DefaultProtocolClientWorker> worker;
worker = delegate_->CreateShellWorker(observer, handler.protocol());
observer->SetWorker(worker);
default_client_observers_.push_back(observer);
worker->StartCheckIsDefault();
}
}
}
void ProtocolHandlerRegistry::Save() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (is_loading_) {
return;
}
scoped_ptr<Value> registered_protocol_handlers(EncodeRegisteredHandlers());
scoped_ptr<Value> ignored_protocol_handlers(EncodeIgnoredHandlers());
scoped_ptr<Value> enabled(Value::CreateBooleanValue(enabled_));
profile_->GetPrefs()->Set(prefs::kRegisteredProtocolHandlers,
*registered_protocol_handlers);
profile_->GetPrefs()->Set(prefs::kIgnoredProtocolHandlers,
*ignored_protocol_handlers);
profile_->GetPrefs()->Set(prefs::kCustomHandlersEnabled, *enabled);
profile_->GetPrefs()->ScheduleSavePersistentPrefs();
}
bool ProtocolHandlerRegistry::CanSchemeBeOverridden(
const std::string& scheme) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
const ProtocolHandlerList* handlers = GetHandlerList(scheme);
// If we already have a handler for this scheme, we can add more.
if (handlers != NULL && !handlers->empty())
return true;
// Don't override a scheme if it already has an external handler.
return !delegate_->IsExternalHandlerRegistered(scheme);
}
void ProtocolHandlerRegistry::GetRegisteredProtocols(
std::vector<std::string>* output) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ProtocolHandlerMultiMap::const_iterator p;
for (p = protocol_handlers_.begin(); p != protocol_handlers_.end(); ++p) {
if (!p->second.empty())
output->push_back(p->first);
}
}
void ProtocolHandlerRegistry::RemoveIgnoredHandler(
const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
bool should_notify = false;
ProtocolHandlerList::iterator p = std::find(
ignored_protocol_handlers_.begin(), ignored_protocol_handlers_.end(),
handler);
if (p != ignored_protocol_handlers_.end()) {
ignored_protocol_handlers_.erase(p);
Save();
should_notify = true;
}
if (should_notify)
NotifyChanged();
}
bool ProtocolHandlerRegistry::IsRegistered(
const ProtocolHandler& handler) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
const ProtocolHandlerList* handlers = GetHandlerList(handler.protocol());
if (!handlers) {
return false;
}
return std::find(handlers->begin(), handlers->end(), handler) !=
handlers->end();
}
bool ProtocolHandlerRegistry::IsIgnored(const ProtocolHandler& handler) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ProtocolHandlerList::const_iterator i;
for (i = ignored_protocol_handlers_.begin();
i != ignored_protocol_handlers_.end(); ++i) {
if (*i == handler) {
return true;
}
}
return false;
}
bool ProtocolHandlerRegistry::IsHandledProtocol(
const std::string& scheme) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
return enabled_ && !GetHandlerFor(scheme).IsEmpty();
}
void ProtocolHandlerRegistry::RemoveHandler(
const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ProtocolHandlerList& handlers = protocol_handlers_[handler.protocol()];
ProtocolHandlerList::iterator p =
std::find(handlers.begin(), handlers.end(), handler);
if (p != handlers.end()) {
handlers.erase(p);
}
ProtocolHandlerMap::iterator q = default_handlers_.find(handler.protocol());
if (q != default_handlers_.end() && q->second == handler) {
// Make the new top handler in the list the default.
if (!handlers.empty()) {
// NOTE We pass a copy because SetDefault() modifies handlers.
SetDefault(ProtocolHandler(handlers[0]));
} else {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &ProtocolHandlerRegistry::ClearDefaultIO,
q->second.protocol()));
default_handlers_.erase(q);
}
}
if (!IsHandledProtocol(handler.protocol())) {
delegate_->DeregisterExternalHandler(handler.protocol());
}
Save();
NotifyChanged();
}
void ProtocolHandlerRegistry::RemoveDefaultHandler(const std::string& scheme) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ProtocolHandler current_default = GetHandlerFor(scheme);
if (!current_default.IsEmpty())
RemoveHandler(current_default);
}
static const ProtocolHandler& LookupHandler(
const ProtocolHandlerRegistry::ProtocolHandlerMap& handler_map,
const std::string& scheme) {
ProtocolHandlerRegistry::ProtocolHandlerMap::const_iterator p =
handler_map.find(scheme);
if (p != handler_map.end()) {
return p->second;
}
return ProtocolHandler::EmptyProtocolHandler();
}
Value* ProtocolHandlerRegistry::EncodeRegisteredHandlers() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ListValue* protocol_handlers = new ListValue();
for (ProtocolHandlerMultiMap::iterator i = protocol_handlers_.begin();
i != protocol_handlers_.end(); ++i) {
for (ProtocolHandlerList::iterator j = i->second.begin();
j != i->second.end(); ++j) {
DictionaryValue* encoded = j->Encode();
if (IsDefault(*j)) {
encoded->Set("default", Value::CreateBooleanValue(true));
}
protocol_handlers->Append(encoded);
}
}
return protocol_handlers;
}
Value* ProtocolHandlerRegistry::EncodeIgnoredHandlers() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ListValue* handlers = new ListValue();
for (ProtocolHandlerList::iterator i = ignored_protocol_handlers_.begin();
i != ignored_protocol_handlers_.end(); ++i) {
handlers->Append(i->Encode());
}
return handlers;
}
void ProtocolHandlerRegistry::OnAcceptRegisterProtocolHandler(
const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RegisterProtocolHandler(handler);
SetDefault(handler);
Save();
NotifyChanged();
}
void ProtocolHandlerRegistry::OnDenyRegisterProtocolHandler(
const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RegisterProtocolHandler(handler);
Save();
NotifyChanged();
}
void ProtocolHandlerRegistry::OnIgnoreRegisterProtocolHandler(
const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
IgnoreProtocolHandler(handler);
Save();
NotifyChanged();
}
// static
void ProtocolHandlerRegistry::RegisterPrefs(PrefService* pref_service) {
pref_service->RegisterListPref(prefs::kRegisteredProtocolHandlers,
PrefService::UNSYNCABLE_PREF);
pref_service->RegisterListPref(prefs::kIgnoredProtocolHandlers,
PrefService::UNSYNCABLE_PREF);
pref_service->RegisterBooleanPref(prefs::kCustomHandlersEnabled, true,
PrefService::UNSYNCABLE_PREF);
}
void ProtocolHandlerRegistry::SetDefault(const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ProtocolHandlerMap::const_iterator p = default_handlers_.find(
handler.protocol());
// If we're not loading, and we are setting a default for a new protocol,
// register with the OS.
if (!is_loading_ && p == default_handlers_.end())
delegate_->RegisterWithOSAsDefaultClient(handler.protocol(), this);
default_handlers_.erase(handler.protocol());
default_handlers_.insert(std::make_pair(handler.protocol(), handler));
PromoteHandler(handler);
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this, &ProtocolHandlerRegistry::SetDefaultIO, handler));
}
void ProtocolHandlerRegistry::ClearDefault(const std::string& scheme) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
default_handlers_.erase(scheme);
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this,
&ProtocolHandlerRegistry::ClearDefaultIO, scheme));
Save();
NotifyChanged();
}
bool ProtocolHandlerRegistry::IsDefault(
const ProtocolHandler& handler) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
return GetHandlerFor(handler.protocol()) == handler;
}
const ProtocolHandler& ProtocolHandlerRegistry::GetHandlerFor(
const std::string& scheme) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
return LookupHandler(default_handlers_, scheme);
}
int ProtocolHandlerRegistry::GetHandlerIndex(const std::string& scheme) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
const ProtocolHandler& handler = GetHandlerFor(scheme);
if (handler.IsEmpty())
return -1;
const ProtocolHandlerList* handlers = GetHandlerList(scheme);
if (!handlers)
return -1;
ProtocolHandlerList::const_iterator p;
int i;
for (i = 0, p = handlers->begin(); p != handlers->end(); ++p, ++i) {
if (*p == handler)
return i;
}
return -1;
}
void ProtocolHandlerRegistry::PromoteHandler(const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(IsRegistered(handler));
ProtocolHandlerMultiMap::iterator p =
protocol_handlers_.find(handler.protocol());
ProtocolHandlerList& list = p->second;
list.erase(std::find(list.begin(), list.end(), handler));
list.insert(list.begin(), handler);
}
void ProtocolHandlerRegistry::NotifyChanged() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
NotificationService::current()->Notify(
chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED,
Source<Profile>(profile_),
NotificationService::NoDetails());
}
// IO thread methods -----------------------------------------------------------
void ProtocolHandlerRegistry::ClearDefaultIO(const std::string& scheme) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
default_handlers_io_.erase(scheme);
}
void ProtocolHandlerRegistry::SetDefaultIO(const ProtocolHandler& handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
ClearDefaultIO(handler.protocol());
default_handlers_io_.insert(std::make_pair(handler.protocol(), handler));
}
bool ProtocolHandlerRegistry::IsHandledProtocolIO(
const std::string& scheme) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
return enabled_io_ && !LookupHandler(default_handlers_io_, scheme).IsEmpty();
}
net::URLRequestJob* ProtocolHandlerRegistry::MaybeCreateJob(
net::URLRequest* request) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
ProtocolHandler handler = LookupHandler(default_handlers_io_,
request->url().scheme());
if (handler.IsEmpty()) {
return NULL;
}
GURL translated_url(handler.TranslateUrl(request->url()));
if (!translated_url.is_valid()) {
return NULL;
}
return new net::URLRequestRedirectJob(request, translated_url);
}
// Delegate --------------------------------------------------------------------
ProtocolHandlerRegistry::Delegate::~Delegate() {
}
void ProtocolHandlerRegistry::Delegate::RegisterExternalHandler(
const std::string& protocol) {
ChildProcessSecurityPolicy* policy =
ChildProcessSecurityPolicy::GetInstance();
if (!policy->IsWebSafeScheme(protocol)) {
policy->RegisterWebSafeScheme(protocol);
}
}
void ProtocolHandlerRegistry::Delegate::DeregisterExternalHandler(
const std::string& protocol) {
}
ShellIntegration::DefaultProtocolClientWorker*
ProtocolHandlerRegistry::Delegate::CreateShellWorker(
ShellIntegration::DefaultWebClientObserver* observer,
const std::string& protocol) {
return new ShellIntegration::DefaultProtocolClientWorker(observer, protocol);
}
ProtocolHandlerRegistry::DefaultClientObserver*
ProtocolHandlerRegistry::Delegate::CreateShellObserver(
ProtocolHandlerRegistry* registry) {
return new DefaultClientObserver(registry);
}
void ProtocolHandlerRegistry::Delegate::RegisterWithOSAsDefaultClient(
const std::string& protocol, ProtocolHandlerRegistry* registry) {
DefaultClientObserver* observer = CreateShellObserver(registry);
// The worker pointer is reference counted. While it is running the
// message loops of the FILE and UI thread will hold references to it
// and it will be automatically freed once all its tasks have finished.
scoped_refptr<ShellIntegration::DefaultProtocolClientWorker> worker;
worker = CreateShellWorker(observer, protocol);
observer->SetWorker(worker);
registry->default_client_observers_.push_back(observer);
worker->StartSetAsDefault();
}
bool ProtocolHandlerRegistry::Delegate::IsExternalHandlerRegistered(
const std::string& protocol) {
// NOTE(koz): This function is safe to call from any thread, despite living
// in ProfileIOData.
return ProfileIOData::IsHandledProtocol(protocol);
}
// DefaultClientObserver ------------------------------------------------------
ProtocolHandlerRegistry::DefaultClientObserver::DefaultClientObserver(
ProtocolHandlerRegistry* registry)
: worker_(NULL), registry_(registry) {
DCHECK(registry_);
}
ProtocolHandlerRegistry::DefaultClientObserver::~DefaultClientObserver() {
if (worker_) {
worker_->ObserverDestroyed();
};
DefaultClientObserverList::iterator iter = std::find(
registry_->default_client_observers_.begin(),
registry_->default_client_observers_.end(), this);
registry_->default_client_observers_.erase(iter);
}
void
ProtocolHandlerRegistry::DefaultClientObserver::SetDefaultWebClientUIState(
ShellIntegration::DefaultWebClientUIState state) {
if (worker_) {
if (ShouldRemoveHandlersNotInOS() &&
(state == ShellIntegration::STATE_NOT_DEFAULT)) {
registry_->ClearDefault(worker_->protocol());
}
} else {
NOTREACHED();
}
}
void ProtocolHandlerRegistry::DefaultClientObserver::SetWorker(
ShellIntegration::DefaultProtocolClientWorker* worker) {
worker_ = worker;
}