mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Improvements to scheme handling (issue #246).
- Break CefRegisterScheme into separate CefRegisterCustomScheme and CefRegisterSchemeHandlerFactory functions. - Allow registration of handlers for built-in schemes. - Supply scheme and request information to CefSchemeHandlerFactory::Create. - Add CrossOriginWhitelist functions for bypassing the same-origin policy with both built-in and custom standard schemes. git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@247 5089003a-bbd8-11dd-ad1f-f1f9622dbc98
This commit is contained in:
@ -1,11 +1,18 @@
|
||||
// Copyright (c) 2009 The Chromium Embedded Framework Authors.
|
||||
// Copyright (c) 2011 The Chromium Embedded Framework Authors.
|
||||
// Portions copyright (c) 2006-2009 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 "include/cef.h"
|
||||
#include "cef_context.h"
|
||||
#include "request_impl.h"
|
||||
#include "response_impl.h"
|
||||
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/string_util.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "googleurl/src/url_util.h"
|
||||
#include "net/base/completion_callback.h"
|
||||
#include "net/base/io_buffer.h"
|
||||
@ -13,43 +20,89 @@
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "net/http/http_util.h"
|
||||
#include "net/url_request/url_request.h"
|
||||
#include "net/url_request/url_request_about_job.h"
|
||||
#include "net/url_request/url_request_data_job.h"
|
||||
#include "net/url_request/url_request_error_job.h"
|
||||
#include "net/url_request/url_request_file_job.h"
|
||||
#include "net/url_request/url_request_filter.h"
|
||||
#include "net/url_request/url_request_ftp_job.h"
|
||||
#include "net/url_request/url_request_http_job.h"
|
||||
#include "net/url_request/url_request_job.h"
|
||||
|
||||
#include "include/cef.h"
|
||||
#include "tracker.h"
|
||||
#include "cef_context.h"
|
||||
#include "request_impl.h"
|
||||
#include "response_impl.h"
|
||||
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h"
|
||||
#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
using WebKit::WebSecurityPolicy;
|
||||
using WebKit::WebString;
|
||||
|
||||
// Memory manager.
|
||||
namespace {
|
||||
|
||||
base::LazyInstance<CefTrackManager> g_scheme_tracker(base::LINKER_INITIALIZED);
|
||||
|
||||
class TrackBase : public CefTrackObject
|
||||
bool IsStandardScheme(const std::string& scheme)
|
||||
{
|
||||
public:
|
||||
TrackBase(CefBase* base) { base_ = base; }
|
||||
url_parse::Component scheme_comp(0, scheme.length());
|
||||
return url_util::IsStandard(scheme.c_str(), scheme_comp);
|
||||
}
|
||||
|
||||
protected:
|
||||
CefRefPtr<CefBase> base_;
|
||||
void RegisterStandardScheme(const std::string& scheme)
|
||||
{
|
||||
REQUIRE_UIT();
|
||||
url_parse::Component scheme_comp(0, scheme.length());
|
||||
if (!url_util::IsStandard(scheme.c_str(), scheme_comp))
|
||||
url_util::AddStandardScheme(scheme.c_str());
|
||||
}
|
||||
|
||||
// Copied from net/url_request/url_request_job_manager.cc.
|
||||
struct SchemeToFactory {
|
||||
const char* scheme;
|
||||
net::URLRequest::ProtocolFactory* factory;
|
||||
};
|
||||
static const SchemeToFactory kBuiltinFactories[] = {
|
||||
{ "http", net::URLRequestHttpJob::Factory },
|
||||
{ "https", net::URLRequestHttpJob::Factory },
|
||||
{ "file", net::URLRequestFileJob::Factory },
|
||||
{ "ftp", net::URLRequestFtpJob::Factory },
|
||||
{ "about", net::URLRequestAboutJob::Factory },
|
||||
{ "data", net::URLRequestDataJob::Factory },
|
||||
};
|
||||
|
||||
static void TrackAdd(CefTrackObject* object)
|
||||
bool IsBuiltinScheme(const std::string& scheme)
|
||||
{
|
||||
g_scheme_tracker.Pointer()->Add(object);
|
||||
for (size_t i = 0; i < arraysize(kBuiltinFactories); ++i)
|
||||
if (LowerCaseEqualsASCII(scheme, kBuiltinFactories[i].scheme))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
net::URLRequestJob* GetBuiltinSchemeRequestJob(net::URLRequest* request,
|
||||
const std::string& scheme)
|
||||
{
|
||||
// See if the request should be handled by a built-in protocol factory.
|
||||
for (size_t i = 0; i < arraysize(kBuiltinFactories); ++i) {
|
||||
if (scheme == kBuiltinFactories[i].scheme) {
|
||||
net::URLRequestJob* job = (kBuiltinFactories[i].factory)(request, scheme);
|
||||
DCHECK(job); // The built-in factories are not expected to fail!
|
||||
return job;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::string ToLower(const std::string& str)
|
||||
{
|
||||
std::string str_lower = str;
|
||||
std::transform(str_lower.begin(), str_lower.end(), str_lower.begin(),
|
||||
towlower);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// net::URLRequestJob implementation.
|
||||
|
||||
class CefUrlRequestJob : public net::URLRequestJob {
|
||||
public:
|
||||
CefUrlRequestJob(net::URLRequest* request, CefRefPtr<CefSchemeHandler> handler)
|
||||
CefUrlRequestJob(net::URLRequest* request,
|
||||
CefRefPtr<CefSchemeHandler> handler)
|
||||
: net::URLRequestJob(request),
|
||||
handler_(handler),
|
||||
response_length_(0),
|
||||
@ -244,54 +297,64 @@ private:
|
||||
};
|
||||
|
||||
|
||||
// net::URLRequestFilter clone that manages the CefSchemeHandlerFactory pointers.
|
||||
|
||||
class CefUrlRequestFilter {
|
||||
// Class that manages the CefSchemeHandlerFactory instances.
|
||||
class CefUrlRequestManager {
|
||||
public:
|
||||
// scheme,hostname -> ProtocolFactory
|
||||
typedef std::map<std::pair<std::string, std::string>,
|
||||
CefSchemeHandlerFactory*> HandlerMap;
|
||||
CefUrlRequestManager() {}
|
||||
|
||||
// Singleton instance for use.
|
||||
static CefUrlRequestFilter* GetInstance()
|
||||
{
|
||||
if (!shared_instance_)
|
||||
shared_instance_ = new CefUrlRequestFilter;
|
||||
return shared_instance_;
|
||||
}
|
||||
// Retrieve the singleton instance.
|
||||
static CefUrlRequestManager* GetInstance();
|
||||
|
||||
static net::URLRequestJob* Factory(net::URLRequest* request,
|
||||
const std::string& scheme)
|
||||
bool AddFactory(const std::string& scheme,
|
||||
const std::string& domain,
|
||||
CefRefPtr<CefSchemeHandlerFactory> factory)
|
||||
{
|
||||
// Returning null here just means that the built-in handler will be used.
|
||||
return GetInstance()->FindRequestHandler(request, scheme);
|
||||
}
|
||||
if (!factory.get()) {
|
||||
RemoveFactory(scheme, domain);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddHostnameHandler(const std::string& scheme,
|
||||
const std::string& hostname,
|
||||
CefSchemeHandlerFactory* factory)
|
||||
{
|
||||
handler_map_[make_pair(scheme, hostname)] = factory;
|
||||
REQUIRE_IOT();
|
||||
|
||||
std::string scheme_lower = ToLower(scheme);
|
||||
std::string domain_lower = ToLower(domain);
|
||||
|
||||
// Hostname is only supported for standard schemes.
|
||||
if (!IsStandardScheme(scheme_lower))
|
||||
domain_lower.clear();
|
||||
|
||||
handler_map_[make_pair(scheme_lower, domain_lower)] = factory;
|
||||
|
||||
// Register with the ProtocolFactory.
|
||||
net::URLRequest::RegisterProtocolFactory(scheme,
|
||||
&CefUrlRequestFilter::Factory);
|
||||
net::URLRequest::RegisterProtocolFactory(scheme_lower,
|
||||
&CefUrlRequestManager::Factory);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveHostnameHandler(const std::string& scheme,
|
||||
const std::string& hostname)
|
||||
void RemoveFactory(const std::string& scheme,
|
||||
const std::string& domain)
|
||||
{
|
||||
REQUIRE_IOT();
|
||||
|
||||
std::string scheme_lower = ToLower(scheme);
|
||||
std::string domain_lower = ToLower(domain);
|
||||
|
||||
// Hostname is only supported for standard schemes.
|
||||
if (!IsStandardScheme(scheme_lower))
|
||||
domain_lower.clear();
|
||||
|
||||
HandlerMap::iterator iter =
|
||||
handler_map_.find(make_pair(scheme, hostname));
|
||||
DCHECK(iter != handler_map_.end());
|
||||
|
||||
handler_map_.erase(iter);
|
||||
handler_map_.find(make_pair(scheme_lower, domain_lower));
|
||||
if (iter != handler_map_.end())
|
||||
handler_map_.erase(iter);
|
||||
}
|
||||
|
||||
// Clear all the existing URL handlers and unregister with the
|
||||
// ProtocolFactory. Resets the hit count.
|
||||
void ClearHandlers()
|
||||
// Clear all the existing URL handlers and unregister the ProtocolFactory.
|
||||
void ClearFactories()
|
||||
{
|
||||
REQUIRE_IOT();
|
||||
|
||||
// Unregister with the ProtocolFactory.
|
||||
std::set<std::string> schemes;
|
||||
for (HandlerMap::const_iterator i = handler_map_.begin();
|
||||
@ -304,24 +367,73 @@ public:
|
||||
}
|
||||
|
||||
handler_map_.clear();
|
||||
hit_count_ = 0;
|
||||
}
|
||||
|
||||
CefSchemeHandlerFactory* FindRequestHandlerFactory(net::URLRequest* request,
|
||||
const std::string& scheme)
|
||||
// Check if a scheme has already been registered.
|
||||
bool HasRegisteredScheme(const std::string& scheme)
|
||||
{
|
||||
CefSchemeHandlerFactory* factory = NULL;
|
||||
if (request->url().is_valid()) {
|
||||
// Check for a map with a hostname first.
|
||||
const std::string& hostname = request->url().host();
|
||||
std::string scheme_lower = ToLower(scheme);
|
||||
|
||||
HandlerMap::iterator i = handler_map_.find(make_pair(scheme, hostname));
|
||||
// Don't register builtin schemes.
|
||||
if (IsBuiltinScheme(scheme_lower))
|
||||
return true;
|
||||
|
||||
scheme_set_lock_.Acquire();
|
||||
bool registered = (scheme_set_.find(scheme_lower) != scheme_set_.end());
|
||||
scheme_set_lock_.Release();
|
||||
return registered;
|
||||
}
|
||||
|
||||
// Register a scheme.
|
||||
bool RegisterScheme(const std::string& scheme,
|
||||
bool is_standard,
|
||||
bool is_local,
|
||||
bool is_display_isolated)
|
||||
{
|
||||
if (HasRegisteredScheme(scheme)) {
|
||||
NOTREACHED() << "Scheme already registered: " << scheme;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string scheme_lower = ToLower(scheme);
|
||||
|
||||
scheme_set_lock_.Acquire();
|
||||
scheme_set_.insert(scheme_lower);
|
||||
scheme_set_lock_.Release();
|
||||
|
||||
if (is_standard)
|
||||
RegisterStandardScheme(scheme_lower);
|
||||
if (is_local) {
|
||||
WebSecurityPolicy::registerURLSchemeAsLocal(
|
||||
WebString::fromUTF8(scheme_lower));
|
||||
}
|
||||
if (is_display_isolated) {
|
||||
WebSecurityPolicy::registerURLSchemeAsDisplayIsolated(
|
||||
WebString::fromUTF8(scheme_lower));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Retrieve the matching handler factory, if any. |scheme| will already be in
|
||||
// lower case.
|
||||
CefRefPtr<CefSchemeHandlerFactory> GetHandlerFactory(
|
||||
net::URLRequest* request, const std::string& scheme)
|
||||
{
|
||||
CefRefPtr<CefSchemeHandlerFactory> factory;
|
||||
|
||||
if (request->url().is_valid() && IsStandardScheme(scheme)) {
|
||||
// Check for a match with a domain first.
|
||||
const std::string& domain = request->url().host();
|
||||
|
||||
HandlerMap::iterator i = handler_map_.find(make_pair(scheme, domain));
|
||||
if (i != handler_map_.end())
|
||||
factory = i->second;
|
||||
}
|
||||
|
||||
if (!factory) {
|
||||
// Check for a map with no specified hostname.
|
||||
if (!factory.get()) {
|
||||
// Check for a match with no specified domain.
|
||||
HandlerMap::iterator i =
|
||||
handler_map_.find(make_pair(scheme, std::string()));
|
||||
if (i != handler_map_.end())
|
||||
@ -331,90 +443,71 @@ public:
|
||||
return factory;
|
||||
}
|
||||
|
||||
// Returns the number of times a handler was used to service a request.
|
||||
int hit_count() const { return hit_count_; }
|
||||
|
||||
protected:
|
||||
CefUrlRequestFilter() : hit_count_(0) { }
|
||||
|
||||
// Helper method that looks up the request in the handler_map_.
|
||||
net::URLRequestJob* FindRequestHandler(net::URLRequest* request,
|
||||
const std::string& scheme)
|
||||
// Create the job that will handle the request. |scheme| will already be in
|
||||
// lower case.
|
||||
net::URLRequestJob* GetRequestJob(net::URLRequest* request,
|
||||
const std::string& scheme)
|
||||
{
|
||||
net::URLRequestJob* job = NULL;
|
||||
CefSchemeHandlerFactory* factory =
|
||||
FindRequestHandlerFactory(request, scheme);
|
||||
CefRefPtr<CefSchemeHandlerFactory> factory =
|
||||
GetHandlerFactory(request, scheme);
|
||||
if (factory) {
|
||||
CefRefPtr<CefSchemeHandler> handler = factory->Create();
|
||||
// Call the handler factory to create the handler for the request.
|
||||
CefRefPtr<CefRequest> requestPtr(new CefRequestImpl());
|
||||
static_cast<CefRequestImpl*>(requestPtr.get())->Set(request);
|
||||
CefRefPtr<CefSchemeHandler> handler = factory->Create(scheme, requestPtr);
|
||||
if (handler.get())
|
||||
job = new CefUrlRequestJob(request, handler);
|
||||
}
|
||||
|
||||
if (job) {
|
||||
DLOG(INFO) << "net::URLRequestFilter hit for " << request->url().spec();
|
||||
hit_count_++;
|
||||
if (!job && IsBuiltinScheme(scheme)) {
|
||||
// Give the built-in scheme handler a chance to handle the request.
|
||||
job = GetBuiltinSchemeRequestJob(request, scheme);
|
||||
}
|
||||
|
||||
if (job)
|
||||
DLOG(INFO) << "CefUrlRequestManager hit for " << request->url().spec();
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
// Maps hostnames to factories. Hostnames take priority over URLs.
|
||||
// Factory method called by the ProtocolFactory. |scheme| will already be in
|
||||
// lower case.
|
||||
static net::URLRequestJob* Factory(net::URLRequest* request,
|
||||
const std::string& scheme)
|
||||
{
|
||||
REQUIRE_IOT();
|
||||
return GetInstance()->GetRequestJob(request, scheme);
|
||||
}
|
||||
|
||||
// Map (scheme, domain) to factories. This map will only be accessed on the IO
|
||||
// thread.
|
||||
typedef std::map<std::pair<std::string, std::string>,
|
||||
CefRefPtr<CefSchemeHandlerFactory> > HandlerMap;
|
||||
HandlerMap handler_map_;
|
||||
|
||||
int hit_count_;
|
||||
// Set of registered schemes. This set may be accessed from multiple threads.
|
||||
typedef std::set<std::string> SchemeSet;
|
||||
SchemeSet scheme_set_;
|
||||
base::Lock scheme_set_lock_;
|
||||
|
||||
private:
|
||||
// Singleton instance.
|
||||
static CefUrlRequestFilter* shared_instance_;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(CefUrlRequestFilter);
|
||||
DISALLOW_EVIL_CONSTRUCTORS(CefUrlRequestManager);
|
||||
};
|
||||
|
||||
CefUrlRequestFilter* CefUrlRequestFilter::shared_instance_ = NULL;
|
||||
base::LazyInstance<CefUrlRequestManager> g_manager(base::LINKER_INITIALIZED);
|
||||
|
||||
CefUrlRequestManager* CefUrlRequestManager::GetInstance()
|
||||
{
|
||||
return g_manager.Pointer();
|
||||
}
|
||||
|
||||
} // anonymous
|
||||
|
||||
|
||||
class SchemeRequestJobWrapper : public CefBase {
|
||||
public:
|
||||
SchemeRequestJobWrapper(const std::string& scheme_name,
|
||||
const std::string& host_name,
|
||||
bool is_standard,
|
||||
CefSchemeHandlerFactory* factory)
|
||||
: scheme_name_(scheme_name), host_name_(host_name),
|
||||
is_standard_(is_standard), factory_(factory)
|
||||
{
|
||||
// The reference will be released when the application exits.
|
||||
TrackAdd(new TrackBase(factory));
|
||||
}
|
||||
|
||||
void RegisterScheme()
|
||||
{
|
||||
if(is_standard_) {
|
||||
// Register the scheme as a standard scheme if it isn't already.
|
||||
url_parse::Component scheme(0, scheme_name_.length());
|
||||
if (!url_util::IsStandard(scheme_name_.c_str(), scheme))
|
||||
url_util::AddStandardScheme(scheme_name_.c_str());
|
||||
}
|
||||
|
||||
// we need to store the pointer of this handler because
|
||||
// we can't pass it as a parameter to the factory method
|
||||
CefUrlRequestFilter::GetInstance()->AddHostnameHandler(
|
||||
scheme_name_, host_name_, factory_);
|
||||
}
|
||||
|
||||
static bool ImplementsThreadSafeReferenceCounting() { return true; }
|
||||
|
||||
private:
|
||||
std::string scheme_name_;
|
||||
std::string host_name_;
|
||||
bool is_standard_;
|
||||
CefSchemeHandlerFactory* factory_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(SchemeRequestJobWrapper);
|
||||
};
|
||||
|
||||
bool CefRegisterScheme(const CefString& scheme_name,
|
||||
const CefString& host_name,
|
||||
bool is_standard,
|
||||
CefRefPtr<CefSchemeHandlerFactory> factory)
|
||||
bool CefRegisterCustomScheme(const CefString& scheme_name,
|
||||
bool is_standard,
|
||||
bool is_local,
|
||||
bool is_display_isolated)
|
||||
{
|
||||
// Verify that the context is in a valid state.
|
||||
if (!CONTEXT_STATE_VALID()) {
|
||||
@ -422,19 +515,60 @@ bool CefRegisterScheme(const CefString& scheme_name,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use a smart pointer for the wrapper object because
|
||||
// RunnableMethodTraits::RetainCallee() (originating from NewRunnableMethod)
|
||||
// will call AddRef() and Release() on the object in debug mode, resulting in
|
||||
// the object being deleted if it doesn't already have a reference.
|
||||
std::string hostNameStr;
|
||||
if (is_standard)
|
||||
hostNameStr = host_name;
|
||||
CefRefPtr<SchemeRequestJobWrapper> wrapper(
|
||||
new SchemeRequestJobWrapper(scheme_name, hostNameStr, is_standard,
|
||||
factory));
|
||||
if (CefThread::CurrentlyOn(CefThread::UI)) {
|
||||
// Must be executed on the UI thread because it calls WebKit APIs.
|
||||
return CefUrlRequestManager::GetInstance()->RegisterScheme(scheme_name,
|
||||
is_standard, is_local, is_display_isolated);
|
||||
} else {
|
||||
// Verify that the scheme has not already been registered.
|
||||
if (CefUrlRequestManager::GetInstance()->HasRegisteredScheme(scheme_name)) {
|
||||
NOTREACHED() << "Scheme already registered: " << scheme_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
CefThread::PostTask(CefThread::UI, FROM_HERE,
|
||||
NewRunnableFunction(&CefRegisterCustomScheme, scheme_name, is_standard,
|
||||
is_local, is_display_isolated));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(wrapper.get(),
|
||||
&SchemeRequestJobWrapper::RegisterScheme));
|
||||
bool CefRegisterSchemeHandlerFactory(const CefString& scheme_name,
|
||||
const CefString& domain_name,
|
||||
CefRefPtr<CefSchemeHandlerFactory> factory)
|
||||
{
|
||||
// Verify that the context is in a valid state.
|
||||
if (!CONTEXT_STATE_VALID()) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CefThread::CurrentlyOn(CefThread::IO)) {
|
||||
return CefUrlRequestManager::GetInstance()->AddFactory(scheme_name,
|
||||
domain_name,
|
||||
factory);
|
||||
} else {
|
||||
CefThread::PostTask(CefThread::IO, FROM_HERE,
|
||||
NewRunnableFunction(&CefRegisterSchemeHandlerFactory, scheme_name,
|
||||
domain_name, factory));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CefClearSchemeHandlerFactories()
|
||||
{
|
||||
// Verify that the context is in a valid state.
|
||||
if (!CONTEXT_STATE_VALID()) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CefThread::CurrentlyOn(CefThread::IO)) {
|
||||
CefUrlRequestManager::GetInstance()->ClearFactories();
|
||||
} else {
|
||||
CefThread::PostTask(CefThread::IO, FROM_HERE,
|
||||
NewRunnableFunction(&CefClearSchemeHandlerFactories));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
Reference in New Issue
Block a user