// Copyright (c) 2012 The Chromium Embedded Framework Authors. // Portions copyright (c) 2012 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 "libcef/browser/net/chrome_scheme_handler.h" #include #include #include #include #include "include/cef_version.h" #include "include/cef_web_plugin.h" #include "libcef/browser/extensions/chrome_api_registration.h" #include "libcef/browser/frame_host_impl.h" #include "libcef/browser/net/internal_scheme_handler.h" #include "libcef/browser/thread_util.h" #include "libcef/common/app_manager.h" #include "base/command_line.h" #include "base/files/file_util.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/path_service.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "cef/grit/cef_resources.h" #include "chrome/browser/browser_about_handler.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h" #include "chrome/browser/ui/webui/theme_source.h" #include "chrome/common/url_constants.h" #include "content/browser/frame_host/debug_urls.h" #include "content/browser/webui/content_web_ui_controller_factory.h" #include "content/public/browser/browser_url_handler.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/url_data_source.h" #include "content/public/browser/web_ui_controller.h" #include "content/public/common/url_constants.h" #include "content/public/common/url_utils.h" #include "content/public/common/user_agent.h" #include "ipc/ipc_channel.h" #include "ui/base/resource/resource_bundle.h" #include "v8/include/v8.h" using extensions::api::cef::kSupportedAPIs; namespace scheme { const char kChromeURL[] = "chrome://"; namespace { const char kChromeUIExtensionsSupportHost[] = "extensions-support"; const char kChromeUILicenseHost[] = "license"; const char kChromeUIWebUIHostsHost[] = "webui-hosts"; // TODO(network): Consider handling content::kChromeDevToolsScheme via WebUI // (DevToolsUI class) with the following changes: // 1. Add an entry for content::kChromeDevToolsScheme in // AlloyContentBrowserClient::GetAdditionalWebUISchemes. // 2. Allow the scheme in CefWebUIControllerFactory::AllowWebUIForURL. // 3. Add an entry for chrome::kChromeUIDevToolsHost in kAllowedWebUIHosts and // kUnlistedHosts. // 4. Remove scheme::RegisterInternalHandlers and related plumbing. // Chrome hosts implemented by WebUI. // Some WebUI handlers have Chrome dependencies that may fail in CEF without // additional changes. Do not add new hosts to this list without also manually // testing all related functionality in CEF. const char* kAllowedWebUIHosts[] = { content::kChromeUIAppCacheInternalsHost, chrome::kChromeUIAccessibilityHost, content::kChromeUIBlobInternalsHost, chrome::kChromeUIChromeURLsHost, chrome::kChromeUICreditsHost, kChromeUIExtensionsSupportHost, content::kChromeUIGpuHost, content::kChromeUIHistogramHost, content::kChromeUIIndexedDBInternalsHost, kChromeUILicenseHost, content::kChromeUIMediaInternalsHost, chrome::kChromeUINetExportHost, chrome::kChromeUINetInternalsHost, content::kChromeUINetworkErrorHost, content::kChromeUINetworkErrorsListingHost, chrome::kChromeUIPrintHost, content::kChromeUIProcessInternalsHost, content::kChromeUIResourcesHost, content::kChromeUIServiceWorkerInternalsHost, chrome::kChromeUISystemInfoHost, chrome::kChromeUIThemeHost, content::kChromeUITracingHost, chrome::kChromeUIVersionHost, content::kChromeUIWebRTCInternalsHost, kChromeUIWebUIHostsHost, }; // Hosts that don't have useful output when linked directly. They'll be excluded // from the "chrome://webui-hosts" listing. const char* kUnlistedHosts[] = { content::kChromeUINetworkErrorHost, content::kChromeUIResourcesHost, chrome::kChromeUIThemeHost, }; enum ChromeHostId { CHROME_UNKNOWN = 0, CHROME_EXTENSIONS_SUPPORT, CHROME_LICENSE, CHROME_VERSION, CHROME_WEBUI_HOSTS, }; // Chrome hosts implemented by CEF. const struct { const char* host; ChromeHostId host_id; } kAllowedCefHosts[] = { {chrome::kChromeUIChromeURLsHost, CHROME_WEBUI_HOSTS}, {kChromeUIExtensionsSupportHost, CHROME_EXTENSIONS_SUPPORT}, {kChromeUILicenseHost, CHROME_LICENSE}, {chrome::kChromeUIVersionHost, CHROME_VERSION}, {kChromeUIWebUIHostsHost, CHROME_WEBUI_HOSTS}, }; ChromeHostId GetChromeHostId(const std::string& host) { for (size_t i = 0; i < sizeof(kAllowedCefHosts) / sizeof(kAllowedCefHosts[0]); ++i) { if (base::EqualsCaseInsensitiveASCII(kAllowedCefHosts[i].host, host.c_str())) { return kAllowedCefHosts[i].host_id; } } return CHROME_UNKNOWN; } // Returns WebUI hosts. Does not include chrome debug hosts (for crashing, etc). void GetAllowedHosts(std::vector* hosts) { // Explicitly whitelisted WebUI hosts. for (size_t i = 0; i < sizeof(kAllowedWebUIHosts) / sizeof(kAllowedWebUIHosts[0]); ++i) { hosts->push_back(kAllowedWebUIHosts[i]); } } // Returns true if a host should not be listed on "chrome://webui-hosts". bool IsUnlistedHost(const std::string& host) { for (size_t i = 0; i < sizeof(kUnlistedHosts) / sizeof(kUnlistedHosts[0]); ++i) { if (host == kUnlistedHosts[i]) return true; } return false; } // Returns true if a host is WebUI and should be allowed to load. bool IsAllowedWebUIHost(const std::string& host) { // Explicitly whitelisted WebUI hosts. for (size_t i = 0; i < sizeof(kAllowedWebUIHosts) / sizeof(kAllowedWebUIHosts[0]); ++i) { if (base::EqualsCaseInsensitiveASCII(kAllowedWebUIHosts[i], host.c_str())) { return true; } } return false; } // Additional debug URLs that are not included in chrome::kChromeDebugURLs. const char* kAllowedDebugURLs[] = { content::kChromeUIBrowserCrashURL, }; void GetDebugURLs(std::vector* urls) { for (size_t i = 0; i < chrome::kNumberOfChromeDebugURLs; ++i) { urls->push_back(chrome::kChromeDebugURLs[i]); } for (size_t i = 0; i < sizeof(kAllowedDebugURLs) / sizeof(kAllowedDebugURLs[0]); ++i) { urls->push_back(kAllowedDebugURLs[i]); } } std::string GetOSType() { #if defined(OS_WIN) return "Windows"; #elif defined(OS_MAC) return "Mac OS X"; #elif defined(OS_CHROMEOS) return "Chromium OS"; #elif defined(OS_ANDROID) return "Android"; #elif defined(OS_LINUX) return "Linux"; #elif defined(OS_FREEBSD) return "FreeBSD"; #elif defined(OS_OPENBSD) return "OpenBSD"; #elif defined(OS_SOLARIS) return "Solaris"; #else return "Unknown"; #endif } std::string GetCommandLine() { #if defined(OS_WIN) return base::WideToUTF8( base::CommandLine::ForCurrentProcess()->GetCommandLineString()); #elif defined(OS_POSIX) std::string command_line = ""; typedef std::vector ArgvList; const ArgvList& argv = base::CommandLine::ForCurrentProcess()->argv(); for (ArgvList::const_iterator iter = argv.begin(); iter != argv.end(); iter++) command_line += " " + *iter; // TODO(viettrungluu): |command_line| could really have any encoding, whereas // below we assumes it's UTF-8. return command_line; #endif } std::string GetModulePath() { base::FilePath path; if (base::PathService::Get(base::FILE_MODULE, &path)) return CefString(path.value()); return std::string(); } class TemplateParser { public: TemplateParser() : ident_start_("$$"), ident_end_("$$") {} TemplateParser(const std::string& ident_start, const std::string& ident_end) : ident_start_(ident_start), ident_end_(ident_end) {} void Add(const std::string& key, const std::string& value) { values_.insert(std::make_pair(key, value)); } void Parse(std::string* tmpl) { int start_pos, end_pos = 0; int ident_start_len = ident_start_.length(); int ident_end_len = ident_end_.length(); while (true) { start_pos = tmpl->find(ident_start_, end_pos); if (start_pos >= 0) { end_pos = tmpl->find(ident_end_, start_pos + ident_start_len); if (end_pos >= 0) { // Found an identifier. Check if a substitution exists. std::string key = tmpl->substr(start_pos + ident_start_len, end_pos - start_pos - ident_start_len); KeyMap::const_iterator it = values_.find(key); if (it != values_.end()) { // Peform the substitution. tmpl->replace(start_pos, end_pos + ident_end_len - start_pos, it->second); end_pos = start_pos + it->second.length(); } else { // Leave the unknown identifier in place. end_pos += ident_end_len; } if (end_pos >= static_cast(tmpl->length()) - ident_start_len - ident_end_len) { // Not enough room remaining for more identifiers. break; } } else { // No end identifier found. break; } } else { // No start identifier found. break; } } } private: typedef std::map KeyMap; KeyMap values_; std::string ident_start_; std::string ident_end_; }; bool OnExtensionsSupportUI(std::string* mime_type, std::string* output) { static const char kDevURL[] = "https://developer.chrome.com/extensions/"; std::string html = "\nExtensions Support\n" "

Supported Chrome Extensions " "APIs

\nFollow issue #1947 for development progress.\n
    \n"; bool has_top_level_name = false; for (size_t i = 0; kSupportedAPIs[i] != nullptr; ++i) { const std::string& api_name = kSupportedAPIs[i]; if (api_name.find("Private") != std::string::npos) { // Don't list private APIs. continue; } const size_t dot_pos = api_name.find('.'); if (dot_pos == std::string::npos) { if (has_top_level_name) { // End the previous top-level API entry. html += "
\n"; } else { has_top_level_name = true; } // Start a new top-level API entry. html += "
  • " + api_name + "
      \n"; } else { // Function name. const std::string& group_name = api_name.substr(0, dot_pos); const std::string& function_name = api_name.substr(dot_pos + 1); html += "\t
    • " + api_name + "
    • \n"; } } if (has_top_level_name) { // End the last top-level API entry. html += "
  • \n"; } html += "\n\n"; *mime_type = "text/html"; *output = html; return true; } bool OnLicenseUI(std::string* mime_type, std::string* output) { std::string piece = ui::ResourceBundle::GetSharedInstance().LoadDataResourceString( IDR_CEF_LICENSE_TXT); if (piece.empty()) { NOTREACHED() << "Failed to load license txt resource."; return false; } *mime_type = "text/html"; *output = "License
    " + piece +
                "
    "; return true; } bool OnVersionUI(Profile* profile, std::string* mime_type, std::string* output) { std::string tmpl = ui::ResourceBundle::GetSharedInstance().LoadDataResourceString( IDR_CEF_VERSION_HTML); if (tmpl.empty()) { NOTREACHED() << "Failed to load version html resource."; return false; } TemplateParser parser; parser.Add("YEAR", MAKE_STRING(COPYRIGHT_YEAR)); parser.Add("CEF", CEF_VERSION); parser.Add("CHROMIUM", base::StringPrintf("%d.%d.%d.%d", CHROME_VERSION_MAJOR, CHROME_VERSION_MINOR, CHROME_VERSION_BUILD, CHROME_VERSION_PATCH)); parser.Add("OS", GetOSType()); parser.Add("WEBKIT", content::GetWebKitVersion()); parser.Add("JAVASCRIPT", v8::V8::GetVersion()); parser.Add("FLASH", std::string()); // Value populated asynchronously. parser.Add( "USERAGENT", CefAppManager::Get()->GetContentClient()->browser()->GetUserAgent()); parser.Add("COMMANDLINE", GetCommandLine()); parser.Add("MODULEPATH", GetModulePath()); parser.Add("CACHEPATH", CefString(profile->GetPath().value())); parser.Parse(&tmpl); *mime_type = "text/html"; *output = tmpl; return true; } bool OnWebUIHostsUI(std::string* mime_type, std::string* output) { std::string html = "\nChrome URLs\n" "

    List of Chrome URLs

    \n
      \n"; std::vector list; GetAllowedHosts(&list); std::sort(list.begin(), list.end()); for (size_t i = 0U; i < list.size(); ++i) { if (IsUnlistedHost(list[i])) continue; html += "
    • chrome://" + list[i] + "
    • \n"; } list.clear(); GetDebugURLs(&list); std::sort(list.begin(), list.end()); html += "
    \n

    For Debug

    \n" "

    The following pages are for debugging purposes only. Because they " "crash or hang the renderer, they're not linked directly; you can type " "them into the address bar if you need them.

    \n
      \n"; for (size_t i = 0U; i < list.size(); ++i) { html += "
    • " + std::string(list[i]) + "
    • \n"; } html += "
    \n"; html += "\n"; *mime_type = "text/html"; *output = html; return true; } const content::WebUI::TypeID kCefWebUITypeID = &kCefWebUITypeID; class CefURLDataSource : public content::URLDataSource { public: CefURLDataSource(const std::string& host, ChromeHostId host_id, Profile* profile) : host_(host), host_id_(host_id), profile_(profile) { CEF_REQUIRE_UIT(); output_ = new base::RefCountedString(); bool handled = false; switch (host_id_) { case CHROME_EXTENSIONS_SUPPORT: handled = OnExtensionsSupportUI(&mime_type_, &output_->data()); break; case CHROME_LICENSE: handled = OnLicenseUI(&mime_type_, &output_->data()); break; case CHROME_VERSION: handled = OnVersionUI(profile_, &mime_type_, &output_->data()); break; case CHROME_WEBUI_HOSTS: handled = OnWebUIHostsUI(&mime_type_, &output_->data()); break; default: break; } DCHECK(handled) << "Unhandled WebUI host: " << host; } ~CefURLDataSource() override = default; // content::URLDataSource implementation. std::string GetSource() override { return host_; } void StartDataRequest( const GURL& path, const content::WebContents::Getter& wc_getter, content::URLDataSource::GotDataCallback callback) override { std::move(callback).Run(output_); } std::string GetMimeType(const std::string& path) override { return mime_type_; } bool AllowCaching() override { return false; } private: const std::string host_; const ChromeHostId host_id_; Profile* const profile_; std::string mime_type_; scoped_refptr output_; DISALLOW_COPY_AND_ASSIGN(CefURLDataSource); }; class CefWebUIController : public content::WebUIController { public: CefWebUIController(content::WebUI* web_ui, const std::string& host, ChromeHostId host_id) : content::WebUIController(web_ui) { Profile* profile = Profile::FromWebUI(web_ui); content::URLDataSource::Add( profile, std::make_unique(host, host_id, profile)); } ~CefWebUIController() override = default; private: DISALLOW_COPY_AND_ASSIGN(CefWebUIController); }; // Intercepts all WebUI calls and either blocks them or forwards them to the // Content or Chrome WebUI factory as appropriate. class CefWebUIControllerFactory : public content::WebUIControllerFactory { public: // Returns true if WebUI is allowed to handle the specified |url|. static bool AllowWebUIForURL(const GURL& url) { if (!url.SchemeIs(content::kChromeUIScheme)) return false; if (IsAllowedWebUIHost(url.host())) return true; return false; } // Returns true if WebUI is allowed to make network requests. static bool IsWebUIAllowedToMakeNetworkRequests(const url::Origin& origin) { if (!AllowWebUIForURL(origin.GetURL())) return false; if (ChromeWebUIControllerFactory::IsWebUIAllowedToMakeNetworkRequests( origin)) { return true; } return false; } std::unique_ptr CreateWebUIControllerForURL( content::WebUI* web_ui, const GURL& url) override { std::unique_ptr controller; if (!AllowWebUIForURL(url)) return controller; // Set up the chrome://theme/ source. These URLs are referenced from many // places (WebUI and chrome://resources which live in //ui). WebUI code // can live in both //content and //chrome. Since ThemeSource lives in // //chrome the WebUI from //content is not performing this setup despite // the fact that it's needed for proper handling of theme resource requests. // See https://crbug.com/1011280. Profile* profile = Profile::FromWebUI(web_ui); content::URLDataSource::Add(profile, std::make_unique(profile)); const auto host_id = GetChromeHostId(url.host()); if (host_id != CHROME_UNKNOWN) { return std::make_unique(web_ui, url.host(), host_id); } controller = content::ContentWebUIControllerFactory::GetInstance() ->CreateWebUIControllerForURL(web_ui, url); if (controller.get()) return controller; return ChromeWebUIControllerFactory::GetInstance() ->CreateWebUIControllerForURL(web_ui, url); } content::WebUI::TypeID GetWebUIType(content::BrowserContext* browser_context, const GURL& url) override { content::WebUI::TypeID type = content::WebUI::kNoWebUI; if (!AllowWebUIForURL(url)) return type; const auto host_id = GetChromeHostId(url.host()); if (host_id != CHROME_UNKNOWN) { return kCefWebUITypeID; } type = content::ContentWebUIControllerFactory::GetInstance()->GetWebUIType( browser_context, url); if (type != content::WebUI::kNoWebUI) return type; type = ChromeWebUIControllerFactory::GetInstance()->GetWebUIType( browser_context, url); if (type != content::WebUI::kNoWebUI) return type; return content::WebUI::kNoWebUI; } bool UseWebUIForURL(content::BrowserContext* browser_context, const GURL& url) override { if (!AllowWebUIForURL(url)) return false; const auto host_id = GetChromeHostId(url.host()); if (host_id != CHROME_UNKNOWN) { return true; } if (content::ContentWebUIControllerFactory::GetInstance()->UseWebUIForURL( browser_context, url) || ChromeWebUIControllerFactory::GetInstance()->UseWebUIForURL( browser_context, url)) { return true; } return false; } bool UseWebUIBindingsForURL(content::BrowserContext* browser_context, const GURL& url) override { if (!AllowWebUIForURL(url)) return false; const auto host_id = GetChromeHostId(url.host()); if (host_id != CHROME_UNKNOWN) { // TODO(network): Use WebUI bindings to implement DidFinishChromeLoad. return false; } if (content::ContentWebUIControllerFactory::GetInstance() ->UseWebUIBindingsForURL(browser_context, url) || ChromeWebUIControllerFactory::GetInstance()->UseWebUIBindingsForURL( browser_context, url)) { return true; } return false; } static void BrowserURLHandlerCreated(content::BrowserURLHandler* handler) { // about: handler. Must come before chrome: handler, since it will // rewrite about: urls to chrome: URLs and then expect chrome: to // actually handle them. Also relies on a preliminary fixup phase. handler->SetFixupHandler(&FixupBrowserAboutURL); handler->AddHandlerPair(&WillHandleBrowserAboutURL, content::BrowserURLHandler::null_handler()); // chrome: & friends. handler->AddHandlerPair(&HandleWebUI, &HandleWebUIReverse); } static CefWebUIControllerFactory* GetInstance(); protected: CefWebUIControllerFactory() {} ~CefWebUIControllerFactory() override {} private: friend struct base::LazyInstanceTraitsBase; // From chrome/browser/chrome_content_browser_client.cc // Handles rewriting Web UI URLs. static bool HandleWebUI(GURL* url, content::BrowserContext* browser_context) { if (!GetInstance()->UseWebUIForURL(browser_context, *url)) return false; return true; } // Reverse URL handler for Web UI. static bool HandleWebUIReverse(GURL* url, content::BrowserContext* browser_context) { // No need to actually reverse-rewrite the URL. return false; } DISALLOW_COPY_AND_ASSIGN(CefWebUIControllerFactory); }; base::LazyInstance::Leaky g_web_ui_controller_factory = LAZY_INSTANCE_INITIALIZER; // static CefWebUIControllerFactory* CefWebUIControllerFactory::GetInstance() { return &g_web_ui_controller_factory.Get(); } void DidFinishChromeVersionLoad(CefRefPtr frame) { // Retieve Flash version information and update asynchronously. class Visitor : public CefWebPluginInfoVisitor { public: Visitor(CefRefPtr frame) : frame_(frame) {} bool Visit(CefRefPtr info, int count, int total) override { std::string name = info->GetName(); if (name == "Shockwave Flash") { if (frame_->IsValid()) { std::string version = info->GetVersion(); frame_->ExecuteJavaScript( "document.getElementById('flash').innerText = '" + version + "';", std::string(), 0); } return false; } return true; } private: CefRefPtr frame_; IMPLEMENT_REFCOUNTING(Visitor); }; CefVisitWebPluginInfo(new Visitor(frame)); } } // namespace void RegisterWebUIControllerFactory() { // Channel all WebUI handling through CefWebUIControllerFactory. content::WebUIControllerFactory::UnregisterFactoryForTesting( content::ContentWebUIControllerFactory::GetInstance()); content::WebUIControllerFactory::RegisterFactory( CefWebUIControllerFactory::GetInstance()); } void BrowserURLHandlerCreated(content::BrowserURLHandler* handler) { CefWebUIControllerFactory::BrowserURLHandlerCreated(handler); } void DidFinishChromeLoad(CefRefPtr frame, const GURL& validated_url) { ChromeHostId host_id = GetChromeHostId(validated_url.host()); switch (host_id) { case CHROME_VERSION: DidFinishChromeVersionLoad(frame); break; default: break; } } bool IsWebUIAllowedToMakeNetworkRequests(const url::Origin& origin) { return CefWebUIControllerFactory::IsWebUIAllowedToMakeNetworkRequests(origin); } } // namespace scheme