Chromium Code Reviews| Index: chrome/browser/devtools/devtools_ui_bindings.cc |
| diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc |
| index 4c20eb075419620072d9b34c633245d027c39eda..2d45f92cfc1b4c7258490bcac019b8eb7227ab3f 100644 |
| --- a/chrome/browser/devtools/devtools_ui_bindings.cc |
| +++ b/chrome/browser/devtools/devtools_ui_bindings.cc |
| @@ -17,6 +17,7 @@ |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| +#include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| @@ -26,6 +27,7 @@ |
| #include "chrome/browser/devtools/devtools_file_watcher.h" |
| #include "chrome/browser/devtools/devtools_protocol.h" |
| #include "chrome/browser/devtools/global_confirm_info_bar.h" |
| +#include "chrome/browser/devtools/url_constants.h" |
| #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h" |
| #include "chrome/browser/infobars/infobar_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| @@ -62,8 +64,10 @@ |
| #include "extensions/common/constants.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "ipc/ipc_channel.h" |
| +#include "net/base/escape.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| +#include "net/base/url_util.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/url_request/url_fetcher.h" |
| @@ -299,6 +303,132 @@ int ResponseWriter::Finish(int net_error, |
| return net::OK; |
| } |
| +GURL SanitizeFrontendURL( |
| + const GURL& url, |
| + const std::string& scheme, |
| + const std::string& host, |
| + const std::string& path, |
| + bool allow_query); |
| + |
| +std::string SanitizeRevision(const std::string& revision) { |
| + for (size_t i = 0; i < revision.length(); i++) { |
| + if (!(revision[i] == '@' && i == 0) |
| + && !(revision[i] >= '0' && revision[i] <= '9') |
| + && !(revision[i] >= 'a' && revision[i] <= 'z') |
| + && !(revision[i] >= 'A' && revision[i] <= 'Z')) { |
| + return std::string(); |
| + } |
| + } |
| + return revision; |
| +} |
| + |
| +std::string SanitizeFrontendPath(const std::string& path) { |
| + for (size_t i = 0; i < path.length(); i++) { |
| + if (path[i] != '/' && path[i] != '-' && path[i] != '_' |
| + && path[i] != '.' && path[i] != '@' |
| + && !(path[i] >= '0' && path[i] <= '9') |
| + && !(path[i] >= 'a' && path[i] <= 'z') |
| + && !(path[i] >= 'A' && path[i] <= 'Z')) { |
| + return std::string(); |
| + } |
| + } |
| + return path; |
| +} |
| + |
| +std::string SanitizeEndpoint(const std::string& value) { |
| + if (value.find('&') != std::string::npos |
| + || value.find('?') != std::string::npos) |
| + return std::string(); |
| + return value; |
| +} |
| + |
| +std::string SanitizeRemoteBase(const std::string& value) { |
| + GURL url(value); |
| + std::string path = url.path(); |
| + std::vector<std::string> parts = base::SplitString( |
| + path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| + std::string revision = parts.size() > 2 ? parts[2] : ""; |
| + revision = SanitizeRevision(revision); |
| + path = base::StringPrintf("/%s/%s/", kRemoteFrontendPath, revision.c_str()); |
| + return SanitizeFrontendURL(url, url::kHttpsScheme, |
| + kRemoteFrontendDomain, path, false).spec(); |
| +} |
| + |
| +std::string SanitizeRemoteFrontendURL(const std::string& value) { |
| + GURL url(net::UnescapeURLComponent(value, |
| + net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS | |
| + net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | |
| + net::UnescapeRule::REPLACE_PLUS_WITH_SPACE)); |
| + std::string path = url.path(); |
| + std::vector<std::string> parts = base::SplitString( |
| + path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| + std::string revision = parts.size() > 2 ? parts[2] : ""; |
| + revision = SanitizeRevision(revision); |
| + std::string filename = parts.size() ? parts[parts.size() - 1] : ""; |
| + if (filename != "devtools.html") |
| + filename = "inspector.html"; |
| + path = base::StringPrintf("/serve_rev/%s/%s", |
| + revision.c_str(), filename.c_str()); |
| + std::string sanitized = SanitizeFrontendURL(url, url::kHttpsScheme, |
| + kRemoteFrontendDomain, path, true).spec(); |
| + return net::EscapeQueryParamValue(sanitized, false); |
| +} |
| + |
| +std::string SanitizeFrontendQueryParam( |
| + const std::string& key, |
| + const std::string& value) { |
| + // Convert boolean flags to true. |
| + if (key == "can_dock" || key == "debugFrontend" || key == "experiments" || |
| + key == "isSharedWorker" || key == "v8only" || key == "remoteFrontend") |
| + return "true"; |
| + |
| + // Pass connection endpoints as is. |
| + if (key == "ws" || key == "service-backend") |
| + return SanitizeEndpoint(value); |
| + |
| + // Only support undocked for old frontends. |
| + if (key == "dockSide" && value == "undocked") |
| + return value; |
| + |
| + if (key == "panel" && (value == "elements" || value == "console")) |
| + return value; |
| + |
| + if (key == "remoteBase") |
| + return SanitizeRemoteBase(value); |
| + |
| + if (key == "remoteFrontendUrl") |
| + return SanitizeRemoteFrontendURL(value); |
| + |
| + return std::string(); |
| +} |
| + |
| +GURL SanitizeFrontendURL( |
| + const GURL& url, |
| + const std::string& scheme, |
| + const std::string& host, |
| + const std::string& path, |
| + bool allow_query) { |
| + std::vector<std::string> query_parts; |
| + if (allow_query) { |
| + for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) { |
| + std::string value = SanitizeFrontendQueryParam(it.GetKey(), |
| + it.GetValue()); |
| + if (!value.empty()) { |
| + query_parts.push_back( |
| + base::StringPrintf("%s=%s", it.GetKey().c_str(), value.c_str())); |
| + } |
| + } |
| + } |
| + std::string query = |
| + query_parts.empty() ? "" : "?" + base::JoinString(query_parts, "&"); |
| + std::string constructed = base::StringPrintf("%s://%s%s%s", |
| + scheme.c_str(), host.c_str(), path.c_str(), query.c_str()); |
| + GURL result = GURL(constructed); |
| + if (!result.is_valid()) |
| + return GURL(); |
| + return result; |
| +} |
| + |
| } // namespace |
| // DevToolsUIBindings::FrontendWebContentsObserver ---------------------------- |
| @@ -335,6 +465,12 @@ DevToolsUIBindings::FrontendWebContentsObserver:: |
| ~FrontendWebContentsObserver() { |
| } |
| +// static |
| +GURL DevToolsUIBindings::SanitizeFrontendURL(const GURL& url) { |
| + return ::SanitizeFrontendURL(url, content::kChromeDevToolsScheme, |
| + chrome::kChromeUIDevToolsHost, SanitizeFrontendPath(url.path()), true); |
| +} |
| + |
| void DevToolsUIBindings::FrontendWebContentsObserver::RenderProcessGone( |
| base::TerminationStatus status) { |
| bool crashed = true; |
| @@ -359,11 +495,7 @@ void DevToolsUIBindings::FrontendWebContentsObserver::RenderProcessGone( |
| void DevToolsUIBindings::FrontendWebContentsObserver:: |
| DidStartNavigationToPendingEntry(const GURL& url, |
| content::ReloadType reload_type) { |
| - devtools_bindings_->frontend_host_.reset( |
| - content::DevToolsFrontendHost::Create( |
| - web_contents()->GetMainFrame(), |
| - base::Bind(&DevToolsUIBindings::HandleMessageFromDevToolsFrontend, |
| - base::Unretained(devtools_bindings_)))); |
| + devtools_bindings_->UpdateFrontendHost(); |
| } |
| void DevToolsUIBindings::FrontendWebContentsObserver:: |
| @@ -418,11 +550,7 @@ DevToolsUIBindings::DevToolsUIBindings(content::WebContents* web_contents) |
| // Register on-load actions. |
| embedder_message_dispatcher_.reset( |
| DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this)); |
| - |
| - frontend_host_.reset(content::DevToolsFrontendHost::Create( |
| - web_contents_->GetMainFrame(), |
| - base::Bind(&DevToolsUIBindings::HandleMessageFromDevToolsFrontend, |
| - base::Unretained(this)))); |
| + UpdateFrontendHost(); |
| } |
| DevToolsUIBindings::~DevToolsUIBindings() { |
| @@ -883,6 +1011,10 @@ void DevToolsUIBindings::DispatchProtocolMessageFromDevToolsFrontend( |
| void DevToolsUIBindings::RecordEnumeratedHistogram(const std::string& name, |
| int sample, |
| int boundary_value) { |
| + if (!frontend_host_) { |
| + NOTREACHED(); |
|
dgozman
2016/12/29 01:16:50
This could actually happen, because navigation is
|
| + return; |
| + } |
| if (!(boundary_value >= 0 && boundary_value <= 100 && sample >= 0 && |
| sample < boundary_value)) { |
| // TODO(nick): Replace with chrome::bad_message::ReceivedBadMessage(). |
| @@ -1068,6 +1200,20 @@ void DevToolsUIBindings::ShowDevToolsConfirmInfoBar( |
| GlobalConfirmInfoBar::Show(std::move(delegate)); |
| } |
| +void DevToolsUIBindings::UpdateFrontendHost() { |
| + GURL url = web_contents_->GetVisibleURL(); |
|
dgozman
2016/12/29 01:16:50
Wrong indent.
|
| + if (url.spec() != SanitizeFrontendURL(url).spec()) { |
| + LOG(ERROR) << "Attempt to navigate to an invalid DevTools front-end URL: " |
| + << url.spec(); |
| + frontend_host_.reset(); |
| + return; |
| + } |
| + frontend_host_.reset(content::DevToolsFrontendHost::Create( |
| + web_contents_->GetMainFrame(), |
| + base::Bind(&DevToolsUIBindings::HandleMessageFromDevToolsFrontend, |
| + base::Unretained(this)))); |
| +} |
| + |
| void DevToolsUIBindings::AddDevToolsExtensionsToClient() { |
| const extensions::ExtensionRegistry* registry = |
| extensions::ExtensionRegistry::Get(profile_->GetOriginalProfile()); |
| @@ -1141,6 +1287,9 @@ void DevToolsUIBindings::CallClientFunction(const std::string& function_name, |
| const base::Value* arg3) { |
| if (!web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme)) |
| return; |
| + // If we're not exposing bindings, we shouldn't call functions either. |
| + if (!frontend_host_) |
| + return; |
| std::string javascript = function_name + "("; |
| if (arg1) { |
| std::string json; |