Commit 8aa718ed authored by Nick Carter's avatar Nick Carter Committed by Commit Bot

Reconcile SecurityOrigin and url::Origin nonstandard-scheme behaviors.

Create opaque SecurityOrigins for nonstandard schemes, unless
they are local. Allow creation of url::Origins for local,
nonstandard schemes.

The root problem here was inconsistency between url::Origin and
SecurityOrigin -- url::SchemeHostPort (and thus url::Origin)
rejects anything with a nonstandard protocol, but
SecurityOrigin::Create(KURL) would allow this creation to
proceed as a tuple origin ("protocol", "", 0).

The allowance of local+nonstandard schemes seems to be
necessary, at least temporarily, to avoid breaking the
Android-only "content:" scheme, and the ChromeOS-only
"externalfile:" scheme.

Other nonstandard schemes shouldn't generally commit, but this does
seem to be possible via LoadDataWithBaseUrl; for example in the test
"NavigationControllerBrowserTest.CrossDomainResourceRequestLoadDataWithBaseUrl"

BUG=862282

Change-Id: I351a067521cd846d2c77cca2498e58580613b4e2
Reviewed-on: https://chromium-review.googlesource.com/1208811
Commit-Queue: Nick Carter <nick@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589671}
parent aa091d69
...@@ -588,6 +588,23 @@ void ChromeContentClient::AddContentDecryptionModules( ...@@ -588,6 +588,23 @@ void ChromeContentClient::AddContentDecryptionModules(
#endif #endif
} }
// New schemes by which content can be retrieved should almost certainly be
// marked as "standard" schemes, even if they're internal, chrome-only schemes.
// "Standard" here just means that its URLs behave like 'normal' URL do.
// - Standard schemes get canonicalized like "new-scheme://hostname/[path]"
// - Whereas "new-scheme:hostname" is a valid nonstandard URL.
// - Thus, hostnames can't be extracted from non-standard schemes.
// - The presence of hostnames enables the same-origin policy. Resources like
// "new-scheme://foo/" are kept separate from "new-scheme://bar/". For
// a nonstandard scheme, every resource loaded from that scheme could
// have access to every other resource.
// - The same-origin policy is very important if webpages can be
// loaded via the scheme. Try to organize the URL space of any new scheme
// such that hostnames provide meaningful compartmentalization of
// privileges.
//
// Example standard schemes: https://, chrome-extension://, chrome://, file://
// Example nonstandard schemes: mailto:, data:, javascript:, about:
static const char* const kChromeStandardURLSchemes[] = { static const char* const kChromeStandardURLSchemes[] = {
extensions::kExtensionScheme, extensions::kExtensionScheme,
chrome::kChromeNativeScheme, chrome::kChromeNativeScheme,
......
...@@ -4,7 +4,7 @@ Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': ...@@ -4,7 +4,7 @@ Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window':
Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin 'asdf' in a call to 'postMessage'." while posting message to 'asdf'. Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin 'asdf' in a call to 'postMessage'." while posting message to 'asdf'.
Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin '/tmp/foo' in a call to 'postMessage'." while posting message to '/tmp/foo'. Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin '/tmp/foo' in a call to 'postMessage'." while posting message to '/tmp/foo'.
Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin '//localhost' in a call to 'postMessage'." while posting message to '//localhost'. Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin '//localhost' in a call to 'postMessage'." while posting message to '//localhost'.
Posted message to 'asdf:' without any exceptions. Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin 'asdf:' in a call to 'postMessage'." while posting message to 'asdf:'.
Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin 'http:' in a call to 'postMessage'." while posting message to 'http:'. Encountered exception "SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin 'http:' in a call to 'postMessage'." while posting message to 'http:'.
Received message: data="Received message: data="done" origin="http://127.0.0.1:8000"" origin="http://localhost:8000" Received message: data="Received message: data="done" origin="http://127.0.0.1:8000"" origin="http://localhost:8000"
...@@ -3,10 +3,10 @@ CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The targ ...@@ -3,10 +3,10 @@ CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The targ
CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://127.0.0.1') does not match the recipient window's origin ('http://127.0.0.1:8000'). CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://127.0.0.1') does not match the recipient window's origin ('http://127.0.0.1:8000').
CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://127.0.0.1:8000') does not match the recipient window's origin ('http://127.0.0.1:8000'). CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://127.0.0.1:8000') does not match the recipient window's origin ('http://127.0.0.1:8000').
CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://www.example.com') does not match the recipient window's origin ('http://127.0.0.1:8000'). CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://www.example.com') does not match the recipient window's origin ('http://127.0.0.1:8000').
CONSOLE ERROR: line 16: Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('asdf://') does not match the recipient window's origin ('http://127.0.0.1:8000').
window.location.href = http://127.0.0.1:8000/security/postMessage/target-origin-same-site.html window.location.href = http://127.0.0.1:8000/security/postMessage/target-origin-same-site.html
waiting... waiting...
Error sending message to asdf:. SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin 'asdf:' in a call to 'postMessage'.
Error sending message to . SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin '' in a call to 'postMessage'. Error sending message to . SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin '' in a call to 'postMessage'.
Error sending message to asdf. SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin 'asdf' in a call to 'postMessage'. Error sending message to asdf. SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin 'asdf' in a call to 'postMessage'.
Error sending message to /tmp/foo. SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin '/tmp/foo' in a call to 'postMessage'. Error sending message to /tmp/foo. SyntaxError: Failed to execute 'postMessage' on 'Window': Invalid target origin '/tmp/foo' in a call to 'postMessage'.
......
...@@ -589,6 +589,8 @@ TEST_F(DocumentTest, EnforceSandboxFlags) { ...@@ -589,6 +589,8 @@ TEST_F(DocumentTest, EnforceSandboxFlags) {
// A unique origin does not bypass secure context checks unless it // A unique origin does not bypass secure context checks unless it
// is also potentially trustworthy. // is also potentially trustworthy.
url::AddStandardScheme("very-special-scheme",
url::SchemeType::SCHEME_WITH_HOST);
SchemeRegistry::RegisterURLSchemeBypassingSecureContextCheck( SchemeRegistry::RegisterURLSchemeBypassingSecureContextCheck(
"very-special-scheme"); "very-special-scheme");
origin = origin =
......
...@@ -95,18 +95,11 @@ void PluginData::UpdatePluginList(const SecurityOrigin* main_frame_origin) { ...@@ -95,18 +95,11 @@ void PluginData::UpdatePluginList(const SecurityOrigin* main_frame_origin) {
ResetPluginData(); ResetPluginData();
main_frame_origin_ = main_frame_origin; main_frame_origin_ = main_frame_origin;
// TODO(jbroman): Remove this. It is intended only as a minimal fix to restore
// previous behavior about origins that url::Origin rejects. This triggers the
// code which maps such origins to url::Origin(), a unique origin.
// https://crbug.com/862282
scoped_refptr<const SecurityOrigin> legacy_origin =
SecurityOrigin::CreateFromUrlOrigin(main_frame_origin->ToUrlOrigin());
mojom::blink::PluginRegistryPtr registry; mojom::blink::PluginRegistryPtr registry;
Platform::Current()->GetInterfaceProvider()->GetInterface( Platform::Current()->GetInterfaceProvider()->GetInterface(
mojo::MakeRequest(&registry)); mojo::MakeRequest(&registry));
Vector<mojom::blink::PluginInfoPtr> plugins; Vector<mojom::blink::PluginInfoPtr> plugins;
registry->GetPlugins(false, legacy_origin, &plugins); registry->GetPlugins(false, main_frame_origin_, &plugins);
for (const auto& plugin : plugins) { for (const auto& plugin : plugins) {
auto* plugin_info = auto* plugin_info =
new PluginInfo(plugin->name, FilePathToWebString(plugin->filename), new PluginInfo(plugin->name, FilePathToWebString(plugin->filename),
......
...@@ -10,8 +10,10 @@ ...@@ -10,8 +10,10 @@
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/plugins/plugin_registry.mojom-blink.h" #include "third_party/blink/public/mojom/plugins/plugin_registry.mojom-blink.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h" #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h" #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/functional.h" #include "third_party/blink/renderer/platform/wtf/functional.h"
#include "url/url_util.h"
using testing::Eq; using testing::Eq;
using testing::Property; using testing::Property;
...@@ -31,9 +33,12 @@ class MockPluginRegistry : public mojom::blink::PluginRegistry { ...@@ -31,9 +33,12 @@ class MockPluginRegistry : public mojom::blink::PluginRegistry {
MOCK_METHOD2(DidGetPlugins, void(bool, const SecurityOrigin&)); MOCK_METHOD2(DidGetPlugins, void(bool, const SecurityOrigin&));
}; };
// This behavior may be temporary, as it's odd for such origins to be treated as // Regression test for https://crbug.com/862282
// non-unique in Blink and unique in the browser. https://crbug.com/862282
TEST(PluginDataTest, NonStandardUrlSchemeRequestsPluginsWithUniqueOrigin) { TEST(PluginDataTest, NonStandardUrlSchemeRequestsPluginsWithUniqueOrigin) {
// Create a scheme that's local but nonstandard, as in bug 862282.
url::AddLocalScheme("nonstandard-862282");
SchemeRegistry::RegisterURLSchemeAsLocal("nonstandard-862282");
base::test::ScopedTaskEnvironment scoped_task_environment; base::test::ScopedTaskEnvironment scoped_task_environment;
MockPluginRegistry mock_plugin_registry; MockPluginRegistry mock_plugin_registry;
mojo::Binding<mojom::blink::PluginRegistry> registry_binding( mojo::Binding<mojom::blink::PluginRegistry> registry_binding(
...@@ -52,10 +57,10 @@ TEST(PluginDataTest, NonStandardUrlSchemeRequestsPluginsWithUniqueOrigin) { ...@@ -52,10 +57,10 @@ TEST(PluginDataTest, NonStandardUrlSchemeRequestsPluginsWithUniqueOrigin) {
EXPECT_CALL( EXPECT_CALL(
mock_plugin_registry, mock_plugin_registry,
DidGetPlugins(false, Property(&SecurityOrigin::IsOpaque, Eq(true)))); DidGetPlugins(false, Property(&SecurityOrigin::IsOpaque, Eq(false))));
scoped_refptr<SecurityOrigin> non_standard_origin = scoped_refptr<SecurityOrigin> non_standard_origin =
SecurityOrigin::CreateFromString("nonstandard:foo/bar"); SecurityOrigin::CreateFromString("nonstandard-862282:foo/bar");
EXPECT_FALSE(non_standard_origin->IsOpaque()); EXPECT_FALSE(non_standard_origin->IsOpaque());
auto* plugin_data = PluginData::Create(); auto* plugin_data = PluginData::Create();
plugin_data->UpdatePluginList(non_standard_origin.get()); plugin_data->UpdatePluginList(non_standard_origin.get());
......
...@@ -135,9 +135,13 @@ class PLATFORM_EXPORT KURL { ...@@ -135,9 +135,13 @@ class PLATFORM_EXPORT KURL {
bool HasPath() const; bool HasPath() const;
// Returns true if you can set the host and port for the URL. // Returns true if you can set the host and port for the URL.
// Non-hierarchical URLs don't have a host and port. // Non-hierarchical URLs don't have a host and port. This is equivalent to
// GURL::IsStandard().
//
// Note: this returns true for "filesystem" and false for "blob" currently,
// due to peculiarities of how schemes are registered in url/ -- neither
// of these schemes can have hostnames on the outer URL.
bool CanSetHostOrPort() const { return IsHierarchical(); } bool CanSetHostOrPort() const { return IsHierarchical(); }
bool CanSetPathname() const { return IsHierarchical(); } bool CanSetPathname() const { return IsHierarchical(); }
bool IsHierarchical() const; bool IsHierarchical() const;
......
...@@ -776,11 +776,43 @@ TEST(KURLTest, LastPathComponent) { ...@@ -776,11 +776,43 @@ TEST(KURLTest, LastPathComponent) {
} }
TEST(KURLTest, IsHierarchical) { TEST(KURLTest, IsHierarchical) {
const KURL url1("http://host/path/to/file.txt"); // Note that it's debatable whether "filesystem" URLs are or hierarchical.
EXPECT_TRUE(url1.IsHierarchical()); // They're currently registered in the url lib as standard; but the parsed
// url never has a valid hostname (the inner URL does)."
const char* standard_urls[] = {
"http://host/path/to/file.txt",
"ftp://andrew.cmu.edu/foo",
"file:///path/to/resource",
"file://hostname/etc/"
"filesystem:http://www.google.com/type/",
"filesystem:http://user:pass@google.com:21/blah#baz",
};
const KURL invalid_utf8("http://a@9%aa%:/path/to/file.txt"); for (const char* input : standard_urls) {
EXPECT_FALSE(invalid_utf8.IsHierarchical()); SCOPED_TRACE(input);
KURL url(input);
EXPECT_TRUE(url.IsHierarchical());
EXPECT_TRUE(url.CanSetHostOrPort());
EXPECT_TRUE(url.CanSetPathname());
}
const char* nonstandard_urls[] = {
"blob:null/guid-goes-here",
"blob:http://example.com/guid-goes-here",
"http://a@9%aa%:/path/to/file.txt",
"about:blank://hostname",
"about:blank",
"javascript:void(0);",
"data:text/html,greetings",
};
for (const char* input : nonstandard_urls) {
SCOPED_TRACE(input);
KURL url(input);
EXPECT_FALSE(url.IsHierarchical());
EXPECT_FALSE(url.CanSetHostOrPort());
EXPECT_FALSE(url.CanSetPathname());
}
} }
TEST(KURLTest, PathAfterLastSlash) { TEST(KURLTest, PathAfterLastSlash) {
......
...@@ -110,6 +110,16 @@ static bool ShouldTreatAsOpaqueOrigin(const KURL& url) { ...@@ -110,6 +110,16 @@ static bool ShouldTreatAsOpaqueOrigin(const KURL& url) {
if (SchemeRegistry::ShouldTreatURLSchemeAsNoAccess(relevant_url.Protocol())) if (SchemeRegistry::ShouldTreatURLSchemeAsNoAccess(relevant_url.Protocol()))
return true; return true;
// Nonstandard schemes and unregistered schemes aren't known to contain hosts
// and/or ports, so they'll usually be placed in opaque origins. An exception
// is made for non-standard local schemes.
// TODO: Migrate "content:" and "externalfile:" to be standard schemes, and
// remove the local scheme exception.
if (!relevant_url.CanSetHostOrPort() &&
!SchemeRegistry::ShouldTreatURLSchemeAsLocal(relevant_url.Protocol())) {
return true;
}
// This is the common case. // This is the common case.
return false; return false;
} }
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "third_party/blink/renderer/platform/blob/blob_url.h" #include "third_party/blink/renderer/platform/blob/blob_url.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h" #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
...@@ -396,12 +397,18 @@ TEST_F(SecurityOriginTest, CanonicalizeHost) { ...@@ -396,12 +397,18 @@ TEST_F(SecurityOriginTest, CanonicalizeHost) {
} }
TEST_F(SecurityOriginTest, UrlOriginConversions) { TEST_F(SecurityOriginTest, UrlOriginConversions) {
url::AddLocalScheme("nonstandard-but-local");
SchemeRegistry::RegisterURLSchemeAsLocal("nonstandard-but-local");
struct TestCases { struct TestCases {
const char* const url; const char* const url;
const char* const scheme; const char* const scheme;
const char* const host; const char* const host;
uint16_t port; uint16_t port;
bool opaque = false;
} cases[] = { } cases[] = {
// Nonstandard scheme registered as local scheme
{"nonstandard-but-local:really?really", "nonstandard-but-local", "", 0},
// IP Addresses // IP Addresses
{"http://192.168.9.1/", "http", "192.168.9.1", 80}, {"http://192.168.9.1/", "http", "192.168.9.1", 80},
{"http://[2001:db8::1]/", "http", "[2001:db8::1]", 80}, {"http://[2001:db8::1]/", "http", "[2001:db8::1]", 80},
...@@ -420,6 +427,11 @@ TEST_F(SecurityOriginTest, UrlOriginConversions) { ...@@ -420,6 +427,11 @@ TEST_F(SecurityOriginTest, UrlOriginConversions) {
{"https://example.com/#1234", "https", "example.com", 443}, {"https://example.com/#1234", "https", "example.com", 443},
{"https://u:p@example.com:123/?query#1234", "https", "example.com", 123}, {"https://u:p@example.com:123/?query#1234", "https", "example.com", 123},
// Nonstandard schemes.
{"unrecognized-scheme://localhost/", "", "", 0, true},
{"mailto:localhost/", "", "", 0, true},
{"about:blank", "", "", 0, true},
// Registered URLs // Registered URLs
{"ftp://example.com/", "ftp", "example.com", 21}, {"ftp://example.com/", "ftp", "example.com", 21},
// crbug.com/781342 // crbug.com/781342
...@@ -447,19 +459,61 @@ TEST_F(SecurityOriginTest, UrlOriginConversions) { ...@@ -447,19 +459,61 @@ TEST_F(SecurityOriginTest, UrlOriginConversions) {
}; };
for (const auto& test_case : cases) { for (const auto& test_case : cases) {
url::Origin url_origin1 = url::Origin::Create(GURL(test_case.url)); SCOPED_TRACE(test_case.url);
GURL gurl(test_case.url);
KURL kurl(String::FromUTF8(test_case.url));
EXPECT_TRUE(gurl.is_valid());
EXPECT_TRUE(kurl.IsValid());
url::Origin origin_via_gurl = url::Origin::Create(gurl);
scoped_refptr<const SecurityOrigin> security_origin_via_kurl =
SecurityOrigin::Create(kurl);
EXPECT_EQ(origin_via_gurl.scheme(), test_case.scheme);
// Test CreateFromUrlOrigin // Test CreateFromUrlOrigin
scoped_refptr<const SecurityOrigin> security_origin = scoped_refptr<const SecurityOrigin> security_origin_via_gurl =
SecurityOrigin::CreateFromUrlOrigin(url_origin1); SecurityOrigin::CreateFromUrlOrigin(origin_via_gurl);
EXPECT_TRUE(security_origin->IsSameSchemeHostPort( EXPECT_EQ(test_case.scheme, security_origin_via_gurl->Protocol());
SecurityOrigin::Create(test_case.scheme, test_case.host, test_case.port) EXPECT_EQ(test_case.scheme, security_origin_via_kurl->Protocol());
.get())); EXPECT_EQ(test_case.host, security_origin_via_gurl->Host());
EXPECT_EQ(test_case.host, security_origin_via_kurl->Host());
EXPECT_EQ(security_origin_via_gurl->Port(),
security_origin_via_kurl->Port());
EXPECT_EQ(test_case.port, security_origin_via_gurl->EffectivePort());
EXPECT_EQ(test_case.port, security_origin_via_kurl->EffectivePort());
EXPECT_EQ(test_case.opaque, security_origin_via_gurl->IsOpaque());
EXPECT_EQ(test_case.opaque, security_origin_via_kurl->IsOpaque());
EXPECT_EQ(!test_case.opaque, security_origin_via_kurl->IsSameSchemeHostPort(
security_origin_via_gurl.get()));
EXPECT_EQ(!test_case.opaque, security_origin_via_gurl->IsSameSchemeHostPort(
security_origin_via_kurl.get()));
if (!test_case.opaque) {
scoped_refptr<const SecurityOrigin> security_origin =
SecurityOrigin::Create(test_case.scheme, test_case.host,
test_case.port);
EXPECT_TRUE(security_origin->IsSameSchemeHostPort(
security_origin_via_gurl.get()));
EXPECT_TRUE(security_origin->IsSameSchemeHostPort(
security_origin_via_kurl.get()));
EXPECT_TRUE(security_origin_via_gurl->IsSameSchemeHostPort(
security_origin.get()));
EXPECT_TRUE(security_origin_via_kurl->IsSameSchemeHostPort(
security_origin.get()));
}
// Test ToUrlOrigin // Test ToUrlOrigin
url::Origin url_origin2 = security_origin->ToUrlOrigin(); url::Origin origin_roundtrip_via_kurl =
EXPECT_TRUE(url_origin1.IsSameOriginWith(url_origin2)) security_origin_via_kurl->ToUrlOrigin();
<< test_case.url << " : " << url_origin2.Serialize(); url::Origin origin_roundtrip_via_gurl =
security_origin_via_gurl->ToUrlOrigin();
EXPECT_EQ(test_case.opaque, origin_roundtrip_via_kurl.unique());
EXPECT_EQ(test_case.opaque, origin_roundtrip_via_gurl.unique());
if (!test_case.opaque) {
EXPECT_EQ(origin_via_gurl, origin_roundtrip_via_kurl);
EXPECT_EQ(origin_roundtrip_via_kurl, origin_roundtrip_via_gurl);
EXPECT_EQ(origin_roundtrip_via_gurl, origin_via_gurl);
}
} }
} }
......
...@@ -329,14 +329,19 @@ TEST(GURLTest, GetOrigin) { ...@@ -329,14 +329,19 @@ TEST(GURLTest, GetOrigin) {
const char* input; const char* input;
const char* expected; const char* expected;
} cases[] = { } cases[] = {
{"http://www.google.com", "http://www.google.com/"}, {"http://www.google.com", "http://www.google.com/"},
{"javascript:window.alert(\"hello,world\");", ""}, {"javascript:window.alert(\"hello,world\");", ""},
{"http://user:pass@www.google.com:21/blah#baz", "http://www.google.com:21/"}, {"http://user:pass@www.google.com:21/blah#baz",
{"http://user@www.google.com", "http://www.google.com/"}, "http://www.google.com:21/"},
{"http://:pass@www.google.com", "http://www.google.com/"}, {"http://user@www.google.com", "http://www.google.com/"},
{"http://:@www.google.com", "http://www.google.com/"}, {"http://:pass@www.google.com", "http://www.google.com/"},
{"filesystem:http://www.google.com/temp/foo?q#b", "http://www.google.com/"}, {"http://:@www.google.com", "http://www.google.com/"},
{"filesystem:http://user:pass@google.com:21/blah#baz", "http://google.com:21/"}, {"filesystem:http://www.google.com/temp/foo?q#b",
"http://www.google.com/"},
{"filesystem:http://user:pass@google.com:21/blah#baz",
"http://google.com:21/"},
{"blob:null/guid-goes-here", ""},
{"blob:http://origin/guid-goes-here", "" /* should be http://origin/ */},
}; };
for (size_t i = 0; i < arraysize(cases); i++) { for (size_t i = 0; i < arraysize(cases); i++) {
GURL url(cases[i].input); GURL url(cases[i].input);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <string.h> #include <string.h>
#include "base/logging.h" #include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "url/gurl.h" #include "url/gurl.h"
#include "url/url_canon.h" #include "url/url_canon.h"
...@@ -20,7 +21,7 @@ namespace url { ...@@ -20,7 +21,7 @@ namespace url {
Origin::Origin() {} Origin::Origin() {}
Origin Origin::Create(const GURL& url) { Origin Origin::Create(const GURL& url) {
if (!url.is_valid() || (!url.IsStandard() && !url.SchemeIsBlob())) if (!url.is_valid())
return Origin(); return Origin();
SchemeHostPort tuple; SchemeHostPort tuple;
...@@ -35,18 +36,22 @@ Origin Origin::Create(const GURL& url) { ...@@ -35,18 +36,22 @@ Origin Origin::Create(const GURL& url) {
tuple = SchemeHostPort(GURL(url.GetContent())); tuple = SchemeHostPort(GURL(url.GetContent()));
} else { } else {
tuple = SchemeHostPort(url); tuple = SchemeHostPort(url);
// It's SchemeHostPort's responsibility to filter out unrecognized schemes;
// sanity check that this is happening.
DCHECK(tuple.IsInvalid() || url.IsStandard() ||
base::ContainsValue(GetLocalSchemes(), url.scheme_piece()));
} }
if (tuple.IsInvalid()) if (tuple.IsInvalid())
return Origin(); return Origin();
return Origin(std::move(tuple)); return Origin(std::move(tuple));
} }
// Note: this is very similar to Create(const GURL&), but opaque origins are // Note: this is very similar to Create(const GURL&), but opaque origins are
// created with CreateUniqueOpaque() rather than the default constructor. // created with CreateUniqueOpaque() rather than the default constructor.
Origin Origin::CreateCanonical(const GURL& url) { Origin Origin::CreateCanonical(const GURL& url) {
if (!url.is_valid() || (!url.IsStandard() && !url.SchemeIsBlob())) if (!url.is_valid())
return CreateUniqueOpaque(); return CreateUniqueOpaque();
SchemeHostPort tuple; SchemeHostPort tuple;
...@@ -61,6 +66,11 @@ Origin Origin::CreateCanonical(const GURL& url) { ...@@ -61,6 +66,11 @@ Origin Origin::CreateCanonical(const GURL& url) {
tuple = SchemeHostPort(GURL(url.GetContent())); tuple = SchemeHostPort(GURL(url.GetContent()));
} else { } else {
tuple = SchemeHostPort(url); tuple = SchemeHostPort(url);
// It's SchemeHostPort's responsibility to filter out unrecognized schemes;
// sanity check that this is happening.
DCHECK(tuple.IsInvalid() || url.IsStandard() ||
base::ContainsValue(GetLocalSchemes(), url.scheme_piece()));
} }
if (tuple.IsInvalid()) if (tuple.IsInvalid())
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h" #include "url/gurl.h"
#include "url/origin.h" #include "url/origin.h"
#include "url/url_util.h"
namespace url { namespace url {
...@@ -37,6 +38,24 @@ void ExpectParsedUrlsEqual(const GURL& a, const GURL& b) { ...@@ -37,6 +38,24 @@ void ExpectParsedUrlsEqual(const GURL& a, const GURL& b) {
class OriginTest : public ::testing::Test { class OriginTest : public ::testing::Test {
protected: protected:
void SetUp() override {
// Add two schemes which are local but nonstandard.
AddLocalScheme("local-but-nonstandard");
AddLocalScheme("also-local-but-nonstandard");
// Add a scheme that's both local and standard.
AddStandardScheme("local-and-standard", SchemeType::SCHEME_WITH_HOST);
AddLocalScheme("local-and-standard");
// Add a scheme that's standard but no-access. We still want these to
// form valid SchemeHostPorts, even though they always commit as opaque
// origins, so that they can represent the source of the resource even if
// it's not committable as a non-opaque origin.
AddStandardScheme("standard-but-noaccess", SchemeType::SCHEME_WITH_HOST);
AddNoAccessScheme("standard-but-noaccess");
}
void TearDown() override { url::Shutdown(); }
Origin CreateUniqueOpaque() { return Origin::CreateUniqueOpaque(); } Origin CreateUniqueOpaque() { return Origin::CreateUniqueOpaque(); }
Origin CreateCanonical(const GURL& url) { Origin CreateCanonical(const GURL& url) {
...@@ -66,12 +85,35 @@ TEST_F(OriginTest, OpaqueOriginComparison) { ...@@ -66,12 +85,35 @@ TEST_F(OriginTest, OpaqueOriginComparison) {
// cross origin to each other. // cross origin to each other.
EXPECT_FALSE(opaque_origin.IsSameOriginWith(unique_origin)); EXPECT_FALSE(opaque_origin.IsSameOriginWith(unique_origin));
const char* const urls[] = {"data:text/html,Hello!", const char* const urls[] = {
"javascript:alert(1)", "data:text/html,Hello!",
"about:blank", "javascript:alert(1)",
"file://example.com:443/etc/passwd", "about:blank",
"yay", "file://example.com:443/etc/passwd",
"http::///invalid.example.com/"}; "unknown-scheme:foo",
"unknown-scheme://bar",
"http",
"http:",
"http:/",
"http://",
"http://:",
"http://:1",
"yay",
"http::///invalid.example.com/",
"blob:null/foo", // blob:null (actually a valid URL)
"blob:data:foo", // blob + data (which is nonstandard)
"blob:about://blank/", // blob + about (which is nonstandard)
"blob:about:blank/", // blob + about (which is nonstandard)
"filesystem:http://example.com/", // Invalid (missing /type/)
"filesystem:local-but-nonstandard:baz./type/", // fs requires standard
"filesystem:local-but-nonstandard://hostname/type/",
"filesystem:unknown-scheme://hostname/type/",
"local-but-nonstandar:foo", // Prefix of registered scheme.
"but-nonstandard:foo", // Suffix of registered scheme.
"local-and-standard:", // Standard scheme needs a hostname.
"standard-but-noaccess:", // Standard scheme needs a hostname.
"blob:blob:http://www.example.com/guid-goes-here", // Double blob.
};
for (auto* test_url : urls) { for (auto* test_url : urls) {
SCOPED_TRACE(test_url); SCOPED_TRACE(test_url);
...@@ -169,6 +211,9 @@ TEST_F(OriginTest, ConstructFromGURL) { ...@@ -169,6 +211,9 @@ TEST_F(OriginTest, ConstructFromGURL) {
// IP Addresses // IP Addresses
{"http://192.168.9.1/", "http", "192.168.9.1", 80}, {"http://192.168.9.1/", "http", "192.168.9.1", 80},
{"http://[2001:db8::1]/", "http", "[2001:db8::1]", 80}, {"http://[2001:db8::1]/", "http", "[2001:db8::1]", 80},
{"http://1/", "http", "0.0.0.1", 80},
{"http://1:1/", "http", "0.0.0.1", 1},
{"http://3232237825/", "http", "192.168.9.1", 80},
// Punycode // Punycode
{"http://☃.net/", "http", "xn--n3h.net", 80}, {"http://☃.net/", "http", "xn--n3h.net", 80},
...@@ -189,6 +234,34 @@ TEST_F(OriginTest, ConstructFromGURL) { ...@@ -189,6 +234,34 @@ TEST_F(OriginTest, ConstructFromGURL) {
{"gopher://example.com/", "gopher", "example.com", 70}, {"gopher://example.com/", "gopher", "example.com", 70},
{"ws://example.com/", "ws", "example.com", 80}, {"ws://example.com/", "ws", "example.com", 80},
{"wss://example.com/", "wss", "example.com", 443}, {"wss://example.com/", "wss", "example.com", 443},
{"wss://user:pass@example.com/", "wss", "example.com", 443},
// Scheme (registered in SetUp()) that's both local and standard.
// TODO: Is it really appropriate to do network-host canonicalization of
// schemes without ports?
{"local-and-standard:20", "local-and-standard", "0.0.0.20", 0},
{"local-and-standard:20.", "local-and-standard", "0.0.0.20", 0},
{"local-and-standard:↑↑↓↓←→←→ba.↑↑↓↓←→←→ba.0.bg", "local-and-standard",
"xn--ba-rzuadaibfa.xn--ba-rzuadaibfa.0.bg", 0},
{"local-and-standard:foo", "local-and-standard", "foo", 0},
{"local-and-standard://bar:20", "local-and-standard", "bar", 0},
{"local-and-standard:baz.", "local-and-standard", "baz.", 0},
{"local-and-standard:baz..", "local-and-standard", "baz..", 0},
{"local-and-standard:baz..bar", "local-and-standard", "baz..bar", 0},
{"local-and-standard:baz...", "local-and-standard", "baz...", 0},
// Scheme (registered in SetUp()) that's local but nonstandard. These
// always have empty hostnames, but are allowed to be url::Origins.
{"local-but-nonstandard:", "local-but-nonstandard", "", 0},
{"local-but-nonstandard:foo", "local-but-nonstandard", "", 0},
{"local-but-nonstandard://bar", "local-but-nonstandard", "", 0},
{"also-local-but-nonstandard://bar", "also-local-but-nonstandard", "", 0},
// Scheme (registered in SetUp()) that's standard but marked as noaccess.
// url::Origin doesn't currently take the noaccess property into account,
// so these aren't expected to result in opaque origins.
{"standard-but-noaccess:foo", "standard-but-noaccess", "foo", 0},
{"standard-but-noaccess://bar", "standard-but-noaccess", "bar", 0},
// file: URLs // file: URLs
{"file:///etc/passwd", "file", "", 0}, {"file:///etc/passwd", "file", "", 0},
...@@ -199,10 +272,13 @@ TEST_F(OriginTest, ConstructFromGURL) { ...@@ -199,10 +272,13 @@ TEST_F(OriginTest, ConstructFromGURL) {
{"filesystem:http://example.com:123/type/", "http", "example.com", 123}, {"filesystem:http://example.com:123/type/", "http", "example.com", 123},
{"filesystem:https://example.com/type/", "https", "example.com", 443}, {"filesystem:https://example.com/type/", "https", "example.com", 443},
{"filesystem:https://example.com:123/type/", "https", "example.com", 123}, {"filesystem:https://example.com:123/type/", "https", "example.com", 123},
{"filesystem:local-and-standard:baz./type/", "local-and-standard", "baz.",
0},
// Blob: // Blob:
{"blob:http://example.com/guid-goes-here", "http", "example.com", 80}, {"blob:http://example.com/guid-goes-here", "http", "example.com", 80},
{"blob:http://example.com:123/guid-goes-here", "http", "example.com", 123}, {"blob:http://example.com:123/guid-goes-here", "http", "example.com",
123},
{"blob:https://example.com/guid-goes-here", "https", "example.com", 443}, {"blob:https://example.com/guid-goes-here", "https", "example.com", 443},
{"blob:http://u:p@example.com/guid-goes-here", "http", "example.com", 80}, {"blob:http://u:p@example.com/guid-goes-here", "http", "example.com", 80},
}; };
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "url/gurl.h" #include "url/gurl.h"
#include "url/third_party/mozilla/url_parse.h" #include "url/third_party/mozilla/url_parse.h"
...@@ -56,8 +57,18 @@ bool IsValidInput(const base::StringPiece& scheme, ...@@ -56,8 +57,18 @@ bool IsValidInput(const base::StringPiece& scheme,
scheme.data(), scheme.data(),
Component(0, base::checked_cast<int>(scheme.length())), Component(0, base::checked_cast<int>(scheme.length())),
&scheme_type); &scheme_type);
if (!is_standard) if (!is_standard) {
// To be consistent with blink, local non-standard schemes are currently
// allowed to be tuple origins. Nonstandard schemes don't have hostnames,
// so their tuple is just ("protocol", "", 0).
//
// TODO: Migrate "content:" and "externalfile:" to be standard schemes, and
// remove this local scheme exception.
if (base::ContainsValue(GetLocalSchemes(), scheme) && host.empty() &&
port == 0)
return true;
return false; return false;
}
switch (scheme_type) { switch (scheme_type) {
case SCHEME_WITH_HOST_AND_PORT: case SCHEME_WITH_HOST_AND_PORT:
...@@ -116,12 +127,15 @@ SchemeHostPort::SchemeHostPort(std::string scheme, ...@@ -116,12 +127,15 @@ SchemeHostPort::SchemeHostPort(std::string scheme,
uint16_t port, uint16_t port,
ConstructPolicy policy) ConstructPolicy policy)
: port_(0) { : port_(0) {
if (!IsValidInput(scheme, host, port, policy)) if (!IsValidInput(scheme, host, port, policy)) {
DCHECK(IsInvalid());
return; return;
}
scheme_ = std::move(scheme); scheme_ = std::move(scheme);
host_ = std::move(host); host_ = std::move(host);
port_ = port; port_ = port;
DCHECK(!IsInvalid());
} }
SchemeHostPort::SchemeHostPort(base::StringPiece scheme, SchemeHostPort::SchemeHostPort(base::StringPiece scheme,
...@@ -159,7 +173,11 @@ SchemeHostPort::SchemeHostPort(const GURL& url) : port_(0) { ...@@ -159,7 +173,11 @@ SchemeHostPort::SchemeHostPort(const GURL& url) : port_(0) {
SchemeHostPort::~SchemeHostPort() = default; SchemeHostPort::~SchemeHostPort() = default;
bool SchemeHostPort::IsInvalid() const { bool SchemeHostPort::IsInvalid() const {
return scheme_.empty() && host_.empty() && !port_; // It suffices to just check |scheme_| for emptiness; the other fields are
// never present without it.
DCHECK(!scheme_.empty() || host_.empty());
DCHECK(!scheme_.empty() || port_ == 0);
return scheme_.empty();
} }
std::string SchemeHostPort::Serialize() const { std::string SchemeHostPort::Serialize() const {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment