diff options
30 files changed, 383 insertions, 108 deletions
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java index f5ab4de..ee49238 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java @@ -6,6 +6,7 @@ package org.chromium.chrome.browser; import android.content.Intent; import android.net.Uri; +import android.os.Bundle; import android.os.Environment; import android.provider.Browser; import android.test.FlakyTest; @@ -43,9 +44,9 @@ import java.util.concurrent.TimeoutException; * Test the behavior of tabs when opening a URL from an external app. */ public class TabsOpenedFromExternalAppTest extends ChromeTabbedActivityTestBase { - private static final String EXTERNAL_APP_1_ID = "app1"; private static final String EXTERNAL_APP_2_ID = "app2"; + private static final String ANDROID_APP_REFERRER = "android-app://com.my.great.great.app"; static class ElementFocusedCriteria extends Criteria { private final Tab mTab; @@ -116,6 +117,40 @@ public class TabsOpenedFromExternalAppTest extends ChromeTabbedActivityTestBase } } + private static class ReferrerCriteria extends Criteria { + private final Tab mTab; + private final String mExpectedReferrer; + private static final String GET_REFERRER_JS = + "(function() { return document.referrer; })();"; + + public ReferrerCriteria(Tab tab, String expectedReferrer) { + super("Referrer is not as expected."); + mTab = tab; + // Add quotes to match returned value from JS. + mExpectedReferrer = "\"" + expectedReferrer + "\""; + } + + @Override + public boolean isSatisfied() { + String referrer; + try { + String jsonText = JavaScriptUtils.executeJavaScriptAndWaitForResult( + mTab.getWebContents(), GET_REFERRER_JS); + if (jsonText.equalsIgnoreCase("null")) jsonText = ""; + referrer = jsonText; + } catch (InterruptedException e) { + e.printStackTrace(); + Assert.fail("InterruptedException was thrown"); + return false; + } catch (TimeoutException e) { + e.printStackTrace(); + Assert.fail("TimeoutException was thrown"); + return false; + } + return TextUtils.equals(mExpectedReferrer, referrer); + } + } + private EmbeddedTestServer mTestServer; public TabsOpenedFromExternalAppTest() { @@ -146,8 +181,8 @@ public class TabsOpenedFromExternalAppTest extends ChromeTabbedActivityTestBase * Returns when the URL has been navigated to. * @throws InterruptedException */ - private void launchUrlFromExternalApp(String url, String appId, boolean createNewTab) - throws InterruptedException { + private void launchUrlFromExternalApp(String url, String appId, boolean createNewTab, + Bundle extras) throws InterruptedException { final Intent intent = new Intent(Intent.ACTION_VIEW); if (appId != null) { intent.putExtra(Browser.EXTRA_APPLICATION_ID, appId); @@ -156,6 +191,7 @@ public class TabsOpenedFromExternalAppTest extends ChromeTabbedActivityTestBase intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true); } intent.setData(Uri.parse(url)); + if (extras != null) intent.putExtras(extras); final Tab originalTab = getActivity().getActivityTab(); ThreadUtils.runOnUiThreadBlocking(new Runnable() { @@ -175,6 +211,45 @@ public class TabsOpenedFromExternalAppTest extends ChromeTabbedActivityTestBase ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url); } + private void launchUrlFromExternalApp(String url, String appId, boolean createNewTab) + throws InterruptedException { + launchUrlFromExternalApp(url, appId, createNewTab, null); + } + + /** + * Tests that URLs opened from external apps can set an android-app scheme referrer. + * @throws InterruptedException + */ + @LargeTest + @Feature({"Navigation"}) + public void testReferrer() throws InterruptedException { + String url = mTestServer.getURL("/chrome/test/data/android/about.html"); + startMainActivityFromLauncher(); + Bundle extras = new Bundle(); + extras.putParcelable(Intent.EXTRA_REFERRER, Uri.parse(ANDROID_APP_REFERRER)); + launchUrlFromExternalApp(url, EXTERNAL_APP_1_ID, true, extras); + CriteriaHelper.pollForCriteria( + new ReferrerCriteria(getActivity().getActivityTab(), ANDROID_APP_REFERRER), 2000, + 200); + } + + /** + * Tests that URLs opened from external apps can set an android-app scheme referrer. + * @throws InterruptedException + */ + @LargeTest + @Feature({"Navigation"}) + public void testCannotSetArbitraryReferrer() throws InterruptedException { + String url = mTestServer.getURL("/chrome/test/data/android/about.html"); + startMainActivityFromLauncher(); + String referrer = "foobar://totally.legit.referrer"; + Bundle extras = new Bundle(); + extras.putParcelable(Intent.EXTRA_REFERRER, Uri.parse(referrer)); + launchUrlFromExternalApp(url, EXTERNAL_APP_1_ID, true, extras); + CriteriaHelper.pollForCriteria( + new ReferrerCriteria(getActivity().getActivityTab(), ""), 2000, 200); + } + /** * Tests that URLs opened from the same external app don't create new tabs. * @throws InterruptedException diff --git a/chrome/common/chrome_content_client.cc b/chrome/common/chrome_content_client.cc index 58564f2..779c8a5 100644 --- a/chrome/common/chrome_content_client.cc +++ b/chrome/common/chrome_content_client.cc @@ -539,10 +539,16 @@ static const url::SchemeWithType kChromeStandardURLSchemes[ void ChromeContentClient::AddAdditionalSchemes( std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* savable_schemes) { for (int i = 0; i < kNumChromeStandardURLSchemes; i++) standard_schemes->push_back(kChromeStandardURLSchemes[i]); +#if defined(OS_ANDROID) + referrer_schemes->push_back( + {chrome::kAndroidAppScheme, url::SCHEME_WITHOUT_PORT}); +#endif + savable_schemes->push_back(extensions::kExtensionScheme); savable_schemes->push_back(extensions::kExtensionResourceScheme); savable_schemes->push_back(chrome::kChromeSearchScheme); diff --git a/chrome/common/chrome_content_client.h b/chrome/common/chrome_content_client.h index 33515ea..21a8bbda 100644 --- a/chrome/common/chrome_content_client.h +++ b/chrome/common/chrome_content_client.h @@ -60,6 +60,7 @@ class ChromeContentClient : public content::ContentClient { void AddPepperPlugins( std::vector<content::PepperPluginInfo>* plugins) override; void AddAdditionalSchemes(std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* saveable_shemes) override; bool CanSendWhileSwappedOut(const IPC::Message* message) override; std::string GetProduct() const override; diff --git a/chrome/common/chrome_content_client_ios.mm b/chrome/common/chrome_content_client_ios.mm index bfca613..fb603f7 100644 --- a/chrome/common/chrome_content_client_ios.mm +++ b/chrome/common/chrome_content_client_ios.mm @@ -32,6 +32,7 @@ void ChromeContentClient::AddPepperPlugins( void ChromeContentClient::AddAdditionalSchemes( std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* saveable_shemes) { // No additional schemes for iOS. } diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc index ed60a71..4ae8e21 100644 --- a/chrome/common/url_constants.cc +++ b/chrome/common/url_constants.cc @@ -18,6 +18,10 @@ namespace chrome { const char kCrosScheme[] = "cros"; #endif +#if defined(OS_ANDROID) +const char kAndroidAppScheme[] = "android-app"; +#endif + // Add Chrome UI URLs as necessary, in alphabetical order. // Be sure to add the corresponding kChromeUI*Host constant below. // This is a WebUI page that lists other WebUI pages. diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h index 2e25824..28b7836 100644 --- a/chrome/common/url_constants.h +++ b/chrome/common/url_constants.h @@ -537,6 +537,10 @@ extern const char kChromeUIDiscardsURL[]; extern const char kCrosScheme[]; #endif +#if defined(OS_ANDROID) +extern const char kAndroidAppScheme[]; +#endif + // "Learn more" URL for the Cloud Print section under Options. extern const char kCloudPrintLearnMoreURL[]; diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc index 7d00445..dc9f4e7 100644 --- a/chrome/renderer/chrome_content_renderer_client.cc +++ b/chrome/renderer/chrome_content_renderer_client.cc @@ -79,6 +79,7 @@ #include "components/web_cache/renderer/web_cache_render_process_observer.h" #include "content/public/common/content_constants.h" #include "content/public/common/content_switches.h" +#include "content/public/common/url_constants.h" #include "content/public/renderer/plugin_instance_throttler.h" #include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_thread.h" @@ -393,8 +394,13 @@ void ChromeContentRendererClient::RenderThreadStarted() { WebSecurityPolicy::registerURLSchemeAsDisplayIsolated(dom_distiller_scheme); #if defined(OS_CHROMEOS) - WebString external_file_scheme(ASCIIToUTF16(content::kExternalFileScheme)); - WebSecurityPolicy::registerURLSchemeAsLocal(external_file_scheme); + WebSecurityPolicy::registerURLSchemeAsLocal( + WebString::fromUTF8(content::kExternalFileScheme)); +#endif + +#if defined(OS_ANDROID) + WebSecurityPolicy::registerURLSchemeAsAllowedForReferrer( + WebString::fromUTF8(chrome::kAndroidAppScheme)); #endif #if defined(ENABLE_IPC_FUZZER) diff --git a/chromecast/common/cast_content_client.cc b/chromecast/common/cast_content_client.cc index 2332ba6..302be4e 100644 --- a/chromecast/common/cast_content_client.cc +++ b/chromecast/common/cast_content_client.cc @@ -81,6 +81,7 @@ CastContentClient::~CastContentClient() { void CastContentClient::AddAdditionalSchemes( std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* savable_schemes) { standard_schemes->push_back(kChromeResourceSchemeWithType); } diff --git a/chromecast/common/cast_content_client.h b/chromecast/common/cast_content_client.h index f77a610..073debc 100644 --- a/chromecast/common/cast_content_client.h +++ b/chromecast/common/cast_content_client.h @@ -18,6 +18,7 @@ class CastContentClient : public content::ContentClient { // content::ContentClient implementation: void AddAdditionalSchemes(std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* saveable_shemes) override; std::string GetUserAgent() const override; base::string16 GetLocalizedString(int message_id) const override; diff --git a/content/common/url_schemes.cc b/content/common/url_schemes.cc index 6d4c58c..c985e85 100644 --- a/content/common/url_schemes.cc +++ b/content/common/url_schemes.cc @@ -18,35 +18,37 @@ namespace { -void AddStandardSchemeHelper(const url::SchemeWithType& scheme) { - url::AddStandardScheme(scheme.scheme, scheme.type); -} - } // namespace namespace content { -void RegisterContentSchemes(bool lock_standard_schemes) { +void RegisterContentSchemes(bool lock_schemes) { std::vector<url::SchemeWithType> additional_standard_schemes; + std::vector<url::SchemeWithType> additional_referrer_schemes; std::vector<std::string> additional_savable_schemes; + GetContentClient()->AddAdditionalSchemes(&additional_standard_schemes, + &additional_referrer_schemes, &additional_savable_schemes); url::AddStandardScheme(kChromeDevToolsScheme, url::SCHEME_WITHOUT_PORT); url::AddStandardScheme(kChromeUIScheme, url::SCHEME_WITHOUT_PORT); url::AddStandardScheme(kGuestScheme, url::SCHEME_WITHOUT_PORT); url::AddStandardScheme(kMetadataScheme, url::SCHEME_WITHOUT_AUTHORITY); - std::for_each(additional_standard_schemes.begin(), - additional_standard_schemes.end(), - AddStandardSchemeHelper); - - // Prevent future modification of the standard schemes list. This is to - // prevent accidental creation of data races in the program. AddStandardScheme - // isn't threadsafe so must be called when GURL isn't used on any other - // thread. This is really easy to mess up, so we say that all calls to - // AddStandardScheme in Chrome must be inside this function. - if (lock_standard_schemes) - url::LockStandardSchemes(); + + for (const url::SchemeWithType& scheme : additional_standard_schemes) + url::AddStandardScheme(scheme.scheme, scheme.type); + + for (const url::SchemeWithType& scheme : additional_referrer_schemes) + url::AddReferrerScheme(scheme.scheme, scheme.type); + + // Prevent future modification of the scheme lists. This is to prevent + // accidental creation of data races in the program. Add*Scheme aren't + // threadsafe so must be called when GURL isn't used on any other thread. This + // is really easy to mess up, so we say that all calls to Add*Scheme in Chrome + // must be inside this function. + if (lock_schemes) + url::LockSchemeRegistries(); // We rely on the above lock to protect this part from being invoked twice. if (!additional_savable_schemes.empty()) { diff --git a/content/common/url_schemes.h b/content/common/url_schemes.h index 571c660..1441c9c 100644 --- a/content/common/url_schemes.h +++ b/content/common/url_schemes.h @@ -10,16 +10,16 @@ namespace content { // Note: ContentMainRunner calls this method internally as part of main -// initialziation, so this function generally should not be called by +// initialization, so this function generally should not be called by // embedders. It's exported to facilitate test harnesses that do not // utilize ContentMainRunner and that do not wish to lock the set // of standard schemes at init time. // -// Called near the beginning of startup to register URL schemes that should -// be parsed as "standard" with the src/url/ library. Optionally, the set -// of standard schemes is locked down. The embedder can add additional -// schemes by overriding the ContentClient::AddAdditionalSchemes method. -CONTENT_EXPORT void RegisterContentSchemes(bool lock_standard_schemes); +// Called near the beginning of startup to register URL schemes that should be +// parsed as "standard" or "referrer" with the src/url/ library. Optionally, the +// sets of schemes are locked down. The embedder can add additional schemes by +// overriding the ContentClient::AddAdditionalSchemes method. +CONTENT_EXPORT void RegisterContentSchemes(bool lock_schemes); } // namespace content diff --git a/content/public/common/content_client.h b/content/public/common/content_client.h index 5bd865d..eabd3da 100644 --- a/content/public/common/content_client.h +++ b/content/public/common/content_client.h @@ -88,10 +88,11 @@ class CONTENT_EXPORT ContentClient { virtual void AddPepperPlugins( std::vector<content::PepperPluginInfo>* plugins) {} - // Gives the embedder a chance to register its own standard and saveable - // url schemes early on in the startup sequence. + // Gives the embedder a chance to register its own standard, referrer and + // saveable url schemes early on in the startup sequence. virtual void AddAdditionalSchemes( std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* savable_schemes) {} // Returns whether the given message should be sent in a swapped out renderer. diff --git a/content/public/common/referrer.cc b/content/public/common/referrer.cc index 183c950..bd145a2 100644 --- a/content/public/common/referrer.cc +++ b/content/public/common/referrer.cc @@ -14,7 +14,7 @@ Referrer Referrer::SanitizeForRequest(const GURL& request, Referrer sanitized_referrer(referrer.url.GetAsReferrer(), referrer.policy); if (!request.SchemeIsHTTPOrHTTPS() || - !sanitized_referrer.url.SchemeIsHTTPOrHTTPS()) { + !sanitized_referrer.url.SchemeIsValidForReferrer()) { sanitized_referrer.url = GURL(); return sanitized_referrer; } diff --git a/extensions/shell/common/shell_content_client.cc b/extensions/shell/common/shell_content_client.cc index 946fe83..e180da8 100644 --- a/extensions/shell/common/shell_content_client.cc +++ b/extensions/shell/common/shell_content_client.cc @@ -83,6 +83,7 @@ static const url::SchemeWithType kShellStandardURLSchemes[ void ShellContentClient::AddAdditionalSchemes( std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* savable_schemes) { for (int i = 0; i < kNumShellStandardURLSchemes; i++) standard_schemes->push_back(kShellStandardURLSchemes[i]); diff --git a/extensions/shell/common/shell_content_client.h b/extensions/shell/common/shell_content_client.h index f105511..471272a 100644 --- a/extensions/shell/common/shell_content_client.h +++ b/extensions/shell/common/shell_content_client.h @@ -20,6 +20,7 @@ class ShellContentClient : public content::ContentClient { void AddPepperPlugins( std::vector<content::PepperPluginInfo>* plugins) override; void AddAdditionalSchemes(std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* saveable_shemes) override; std::string GetUserAgent() const override; base::string16 GetLocalizedString(int message_id) const override; diff --git a/extensions/test/extensions_unittests_main.cc b/extensions/test/extensions_unittests_main.cc index bb9cc00..f0ea868 100644 --- a/extensions/test/extensions_unittests_main.cc +++ b/extensions/test/extensions_unittests_main.cc @@ -39,6 +39,7 @@ class ExtensionsContentClient : public content::ContentClient { // content::ContentClient overrides: void AddAdditionalSchemes( std::vector<url::SchemeWithType>* standard_schemes, + std::vector<url::SchemeWithType>* referrer_schemes, std::vector<std::string>* savable_schemes) override { for (int i = 0; i < kNumExtensionStandardURLSchemes; i++) standard_schemes->push_back(kExtensionStandardURLSchemes[i]); diff --git a/ios/web/public/url_schemes.h b/ios/web/public/url_schemes.h index ccbc99c..6532916 100644 --- a/ios/web/public/url_schemes.h +++ b/ios/web/public/url_schemes.h @@ -15,10 +15,10 @@ namespace web { // // Called near the beginning of startup to register URL schemes that // should be parsed as "standard" with the src/url/ library. The set -// of standard scheme is locked if |lock_standard_schemes| is true. +// of standard scheme is locked if |lock_schemes| is true. // The embedder can add additional schemes by overriding the // WebClient::AddAditionalSchemes method. -void RegisterWebSchemes(bool lock_standard_schemes); +void RegisterWebSchemes(bool lock_schemes); } // namespace web diff --git a/ios/web/public/url_schemes.mm b/ios/web/public/url_schemes.mm index 638ba7f..e501ee2 100644 --- a/ios/web/public/url_schemes.mm +++ b/ios/web/public/url_schemes.mm @@ -18,19 +18,19 @@ void AddStandardSchemeHelper(const url::SchemeWithType& scheme) { } } // namespace -void RegisterWebSchemes(bool lock_standard_schemes) { +void RegisterWebSchemes(bool lock_schemes) { std::vector<url::SchemeWithType> additional_standard_schemes; GetWebClient()->AddAdditionalSchemes(&additional_standard_schemes); std::for_each(additional_standard_schemes.begin(), additional_standard_schemes.end(), AddStandardSchemeHelper); - // Prevent future modification of the standard schemes list. This is to - // prevent accidental creation of data races in the program. AddStandardScheme - // isn't threadsafe so must be called when GURL isn't used on any other - // thread. This is really easy to mess up, so we say that all calls to - // AddStandardScheme in Chrome must be inside this function. - if (lock_standard_schemes) - url::LockStandardSchemes(); + // Prevent future modification of the schemes lists. This is to prevent + // accidental creation of data races in the program. Add*Scheme aren't + // threadsafe so must be called when GURL isn't used on any other thread. This + // is really easy to mess up, so we say that all calls to Add*Scheme in Chrome + // must be inside this function. + if (lock_schemes) + url::LockSchemeRegistries(); } } // namespace web diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc index 748b753..55f5235 100644 --- a/net/url_request/url_request_unittest.cc +++ b/net/url_request/url_request_unittest.cc @@ -6567,6 +6567,20 @@ TEST_F(URLRequestTestHTTP, RedirectJobWithReferenceFragment) { EXPECT_EQ(redirect_url, r->url()); } +TEST_F(URLRequestTestHTTP, UnsupportedReferrerScheme) { + ASSERT_TRUE(http_test_server()->Start()); + + const std::string referrer("foobar://totally.legit.referrer"); + TestDelegate d; + scoped_ptr<URLRequest> req(default_context_.CreateRequest( + http_test_server()->GetURL("/echoheader?Referer"), DEFAULT_PRIORITY, &d)); + req->SetReferrer(referrer); + req->Start(); + base::RunLoop().Run(); + + EXPECT_EQ(std::string("None"), d.data_received()); +} + TEST_F(URLRequestTestHTTP, NoUserPassInReferrer) { ASSERT_TRUE(http_test_server()->Start()); diff --git a/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.cpp b/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.cpp index 8489a85..491c651 100644 --- a/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.cpp +++ b/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.cpp @@ -194,6 +194,18 @@ static URLSchemesSet& secureContextBypassingSchemes() return secureContextBypassingSchemes; } +static URLSchemesSet& allowedInReferrerSchemes() +{ + DEFINE_STATIC_LOCAL_WITH_LOCK(URLSchemesSet, allowedInReferrerSchemes, ()); + + if (allowedInReferrerSchemes.isEmpty()) { + allowedInReferrerSchemes.add("http"); + allowedInReferrerSchemes.add("https"); + } + + return allowedInReferrerSchemes; +} + bool SchemeRegistry::shouldTreatURLSchemeAsLocal(const String& scheme) { if (scheme.isEmpty()) @@ -392,6 +404,26 @@ bool SchemeRegistry::shouldTreatURLSchemeAsFirstPartyWhenTopLevel(const String& return firstPartyWhenTopLevelSchemes().contains(scheme); } +void SchemeRegistry::registerURLSchemeAsAllowedForReferrer(const String& scheme) +{ + MutexLocker locker(mutex()); + allowedInReferrerSchemes().add(scheme); +} + +void SchemeRegistry::removeURLSchemeAsAllowedForReferrer(const String& scheme) +{ + MutexLocker locker(mutex()); + allowedInReferrerSchemes().remove(scheme); +} + +bool SchemeRegistry::shouldTreatURLSchemeAsAllowedForReferrer(const String& scheme) +{ + if (scheme.isEmpty()) + return false; + MutexLocker locker(mutex()); + return allowedInReferrerSchemes().contains(scheme); +} + void SchemeRegistry::registerURLSchemeAsBypassingContentSecurityPolicy(const String& scheme, PolicyAreas policyAreas) { MutexLocker locker(mutex()); diff --git a/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h b/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h index 153cf4a..a234c9b 100644 --- a/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h +++ b/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h @@ -101,6 +101,11 @@ public: static void registerURLSchemeAsFirstPartyWhenTopLevel(const String& scheme); static bool shouldTreatURLSchemeAsFirstPartyWhenTopLevel(const String& scheme); + // Schemes that can be used in a referrer. + static void registerURLSchemeAsAllowedForReferrer(const String& scheme); + static void removeURLSchemeAsAllowedForReferrer(const String& scheme); + static bool shouldTreatURLSchemeAsAllowedForReferrer(const String& scheme); + // Allow resources from some schemes to load on a page, regardless of its // Content Security Policy. // This enum should be kept in sync with public/web/WebSecurityPolicy.h. diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityPolicy.cpp b/third_party/WebKit/Source/platform/weborigin/SecurityPolicy.cpp index af82e51..3eb154a 100644 --- a/third_party/WebKit/Source/platform/weborigin/SecurityPolicy.cpp +++ b/third_party/WebKit/Source/platform/weborigin/SecurityPolicy.cpp @@ -31,6 +31,7 @@ #include "platform/RuntimeEnabledFeatures.h" #include "platform/weborigin/KURL.h" #include "platform/weborigin/OriginAccessEntry.h" +#include "platform/weborigin/SchemeRegistry.h" #include "platform/weborigin/SecurityOrigin.h" #include "wtf/HashMap.h" #include "wtf/HashSet.h" @@ -67,9 +68,10 @@ void SecurityPolicy::init() bool SecurityPolicy::shouldHideReferrer(const KURL& url, const String& referrer) { bool referrerIsSecureURL = protocolIs(referrer, "https"); - bool referrerIsWebURL = referrerIsSecureURL || protocolIs(referrer, "http"); + String scheme = KURL(KURL(), referrer).protocol(); + bool schemeIsAllowed = SchemeRegistry::shouldTreatURLSchemeAsAllowedForReferrer(scheme); - if (!referrerIsWebURL) + if (!schemeIsAllowed) return true; if (!referrerIsSecureURL) @@ -85,7 +87,8 @@ Referrer SecurityPolicy::generateReferrer(ReferrerPolicy referrerPolicy, const K if (referrer.isEmpty()) return Referrer(String(), referrerPolicy); - if (!(protocolIs(referrer, "https") || protocolIs(referrer, "http"))) + String scheme = KURL(KURL(), referrer).protocol(); + if (!SchemeRegistry::shouldTreatURLSchemeAsAllowedForReferrer(scheme)) return Referrer(String(), referrerPolicy); if (SecurityOrigin::shouldUseInnerURL(url)) diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityPolicyTest.cpp b/third_party/WebKit/Source/platform/weborigin/SecurityPolicyTest.cpp index 59a3603..e6ed820 100644 --- a/third_party/WebKit/Source/platform/weborigin/SecurityPolicyTest.cpp +++ b/third_party/WebKit/Source/platform/weborigin/SecurityPolicyTest.cpp @@ -31,14 +31,40 @@ #include "platform/weborigin/SecurityPolicy.h" #include "platform/weborigin/KURL.h" +#include "platform/weborigin/SchemeRegistry.h" #include "platform/weborigin/SecurityOrigin.h" #include "testing/gtest/include/gtest/gtest.h" namespace blink { -TEST(SecurityPolicyTest, ReferrerIsAlwaysAWebURL) +TEST(SecurityPolicyTest, EmptyReferrerForUnauthorizedScheme) { - EXPECT_TRUE(String() == SecurityPolicy::generateReferrer(ReferrerPolicyAlways, KURL(ParsedURLString, "http://example.com/"), String::fromUTF8("chrome://somepage/")).referrer); + const KURL exampleHttpUrl = KURL(ParsedURLString, "http://example.com/"); + EXPECT_TRUE(String() == SecurityPolicy::generateReferrer(ReferrerPolicyAlways, exampleHttpUrl, String::fromUTF8("chrome://somepage/")).referrer); +} + +TEST(SecurityPolicyTest, GenerateReferrerRespectsReferrerSchemesRegistry) +{ + const KURL exampleHttpUrl = KURL(ParsedURLString, "http://example.com/"); + const String foobarURL = String::fromUTF8("foobar://somepage/"); + const String foobarScheme = String::fromUTF8("foobar"); + + EXPECT_EQ(String(), SecurityPolicy::generateReferrer(ReferrerPolicyAlways, exampleHttpUrl, foobarURL).referrer); + SchemeRegistry::registerURLSchemeAsAllowedForReferrer(foobarScheme); + EXPECT_EQ(foobarURL, SecurityPolicy::generateReferrer(ReferrerPolicyAlways, exampleHttpUrl, foobarURL).referrer); + SchemeRegistry::removeURLSchemeAsAllowedForReferrer(foobarScheme); +} + +TEST(SecurityPolicyTest, ShouldHideReferrerRespectsReferrerSchemesRegistry) +{ + const KURL exampleHttpUrl = KURL(ParsedURLString, "http://example.com/"); + const String foobarURL = String::fromUTF8("foobar://somepage/"); + const String foobarScheme = String::fromUTF8("foobar"); + + EXPECT_TRUE(SecurityPolicy::shouldHideReferrer(exampleHttpUrl, foobarScheme)); + SchemeRegistry::registerURLSchemeAsAllowedForReferrer(foobarScheme); + EXPECT_FALSE(SecurityPolicy::shouldHideReferrer(exampleHttpUrl, foobarURL)); + SchemeRegistry::removeURLSchemeAsAllowedForReferrer(foobarScheme); } TEST(SecurityPolicyTest, GenerateReferrer) diff --git a/third_party/WebKit/Source/web/WebSecurityPolicy.cpp b/third_party/WebKit/Source/web/WebSecurityPolicy.cpp index ea67c1d..2c1ac0f 100644 --- a/third_party/WebKit/Source/web/WebSecurityPolicy.cpp +++ b/third_party/WebKit/Source/web/WebSecurityPolicy.cpp @@ -152,4 +152,9 @@ void WebSecurityPolicy::registerURLSchemeAsNotAllowingJavascriptURLs(const WebSt SchemeRegistry::registerURLSchemeAsNotAllowingJavascriptURLs(scheme); } +void WebSecurityPolicy::registerURLSchemeAsAllowedForReferrer(const WebString& scheme) +{ + SchemeRegistry::registerURLSchemeAsAllowedForReferrer(scheme); +} + } // namespace blink diff --git a/third_party/WebKit/public/web/WebSecurityPolicy.h b/third_party/WebKit/public/web/WebSecurityPolicy.h index fbe011d..eb2858f 100644 --- a/third_party/WebKit/public/web/WebSecurityPolicy.h +++ b/third_party/WebKit/public/web/WebSecurityPolicy.h @@ -125,6 +125,9 @@ public: // by bookmarklets or javascript: URLs typed in the omnibox. BLINK_EXPORT static void registerURLSchemeAsNotAllowingJavascriptURLs(const WebString&); + // Registers an URL scheme as allowed in referrers. + BLINK_EXPORT static void registerURLSchemeAsAllowedForReferrer(const WebString&); + private: WebSecurityPolicy(); }; diff --git a/url/gurl.cc b/url/gurl.cc index db8b277..2b67bee 100644 --- a/url/gurl.cc +++ b/url/gurl.cc @@ -332,7 +332,7 @@ GURL GURL::GetOrigin() const { } GURL GURL::GetAsReferrer() const { - if (!is_valid_ || !SchemeIsHTTPOrHTTPS()) + if (!SchemeIsValidForReferrer()) return GURL(); if (!has_ref() && !has_username() && !has_password()) @@ -386,6 +386,10 @@ bool GURL::SchemeIsHTTPOrHTTPS() const { return SchemeIs(url::kHttpScheme) || SchemeIs(url::kHttpsScheme); } +bool GURL::SchemeIsValidForReferrer() const { + return is_valid_ && IsReferrerScheme(spec_.data(), parsed_.scheme); +} + bool GURL::SchemeIsWSOrWSS() const { return SchemeIs(url::kWsScheme) || SchemeIs(url::kWssScheme); } @@ -215,6 +215,9 @@ class URL_EXPORT GURL { // Returns true if the scheme is "http" or "https". bool SchemeIsHTTPOrHTTPS() const; + // Returns true if the scheme is valid for use as a referrer. + bool SchemeIsValidForReferrer() const; + // Returns true is the scheme is "ws" or "wss". bool SchemeIsWSOrWSS() const; diff --git a/url/url_util.cc b/url/url_util.cc index cbb252f..c1e9979 100644 --- a/url/url_util.cc +++ b/url/url_util.cc @@ -34,13 +34,21 @@ const SchemeWithType kStandardURLSchemes[kNumStandardURLSchemes] = { {kFileSystemScheme, SCHEME_WITHOUT_AUTHORITY}, }; -// List of the currently installed standard schemes. This list is lazily -// initialized by InitStandardSchemes and is leaked on shutdown to prevent -// any destructors from being called that will slow us down or cause problems. -std::vector<SchemeWithType>* standard_schemes = NULL; +const int kNumReferrerURLSchemes = 2; +const SchemeWithType kReferrerURLSchemes[kNumReferrerURLSchemes] = { + {kHttpScheme, SCHEME_WITH_PORT}, + {kHttpsScheme, SCHEME_WITH_PORT}, +}; + +// Lists of the currently installed standard and referrer schemes. These lists +// are lazily initialized by InitStandardSchemes and InitReferrerSchemes and are +// leaked on shutdown to prevent any destructors from being called that will +// slow us down or cause problems. +std::vector<SchemeWithType>* standard_schemes = nullptr; +std::vector<SchemeWithType>* referrer_schemes = nullptr; -// See the LockStandardSchemes declaration in the header. -bool standard_schemes_locked = false; +// See the LockSchemeRegistries declaration in the header. +bool scheme_registries_locked = false; // This template converts a given character type to the corresponding // StringPiece type. @@ -53,14 +61,27 @@ template<> struct CharToStringPiece<base::char16> { typedef base::StringPiece16 Piece; }; -// Ensures that the standard_schemes list is initialized, does nothing if it -// already has values. -void InitStandardSchemes() { - if (standard_schemes) +void InitSchemes(std::vector<SchemeWithType>** schemes, + const SchemeWithType* initial_schemes, + size_t size) { + if (*schemes) return; - standard_schemes = new std::vector<SchemeWithType>; - for (int i = 0; i < kNumStandardURLSchemes; i++) - standard_schemes->push_back(kStandardURLSchemes[i]); + *schemes = new std::vector<SchemeWithType>(size); + for (size_t i = 0; i < size; i++) { + (*schemes)->push_back(initial_schemes[i]); + } +} + +// Ensures that the standard_schemes list is initialized, does nothing if +// it already has values. +void InitStandardSchemes() { + InitSchemes(&standard_schemes, kStandardURLSchemes, kNumStandardURLSchemes); +} + +// Ensures that the referrer_schemes list is initialized, does nothing if +// it already has values. +void InitReferrerSchemes() { + InitSchemes(&referrer_schemes, kReferrerURLSchemes, kNumReferrerURLSchemes); } // Given a string and a range inside the string, compares it to the given @@ -78,22 +99,20 @@ inline bool DoCompareSchemeComponent(const CHAR* spec, } // Returns true and sets |type| to the SchemeType of the given scheme -// identified by |scheme| within |spec| if the scheme is one of the registered -// "standard" schemes. +// identified by |scheme| within |spec| if in |schemes|. template<typename CHAR> -bool DoIsStandard(const CHAR* spec, - const Component& scheme, - SchemeType* type) { +bool DoIsInSchemes(const CHAR* spec, + const Component& scheme, + SchemeType* type, + const std::vector<SchemeWithType>& schemes) { if (!scheme.is_nonempty()) return false; // Empty or invalid schemes are non-standard. - InitStandardSchemes(); - for (size_t i = 0; i < standard_schemes->size(); i++) { - if (base::LowerCaseEqualsASCII( - typename CharToStringPiece<CHAR>::Piece( - &spec[scheme.begin], scheme.len), - standard_schemes->at(i).scheme)) { - *type = standard_schemes->at(i).type; + for (const SchemeWithType& scheme_with_type : schemes) { + if (base::LowerCaseEqualsASCII(typename CharToStringPiece<CHAR>::Piece( + &spec[scheme.begin], scheme.len), + scheme_with_type.scheme)) { + *type = scheme_with_type.type; return true; } } @@ -101,6 +120,13 @@ bool DoIsStandard(const CHAR* spec, } template<typename CHAR> +bool DoIsStandard(const CHAR* spec, const Component& scheme, SchemeType* type) { + InitStandardSchemes(); + return DoIsInSchemes(spec, scheme, type, *standard_schemes); +} + + +template<typename CHAR> bool DoFindAndCompareScheme(const CHAR* str, int str_len, const char* compare, @@ -364,31 +390,19 @@ bool DoReplaceComponents(const char* spec, return ReplacePathURL(spec, parsed, replacements, output, out_parsed); } -} // namespace - -void Initialize() { - InitStandardSchemes(); -} - -void Shutdown() { - if (standard_schemes) { - delete standard_schemes; - standard_schemes = NULL; - } -} - -void AddStandardScheme(const char* new_scheme, - SchemeType type) { - // If this assert triggers, it means you've called AddStandardScheme after - // LockStandardSchemes have been called (see the header file for - // LockStandardSchemes for more). +void DoAddScheme(const char* new_scheme, + SchemeType type, + std::vector<SchemeWithType>* schemes) { + DCHECK(schemes); + // If this assert triggers, it means you've called Add*Scheme after + // LockSchemeRegistries has been called (see the header file for + // LockSchemeRegistries for more). // - // This normally means you're trying to set up a new standard scheme too late - // in your application's init process. Locate where your app does this - // initialization and calls LockStandardSchemes, and add your new standard - // scheme there. - DCHECK(!standard_schemes_locked) << - "Trying to add a standard scheme after the list has been locked."; + // This normally means you're trying to set up a new scheme too late in your + // application's init process. Locate where your app does this initialization + // and calls LockSchemeRegistries, and add your new scheme there. + DCHECK(!scheme_registries_locked) + << "Trying to add a scheme after the lists have been locked."; size_t scheme_len = strlen(new_scheme); if (scheme_len == 0) @@ -400,15 +414,42 @@ void AddStandardScheme(const char* new_scheme, ANNOTATE_LEAKING_OBJECT_PTR(dup_scheme); memcpy(dup_scheme, new_scheme, scheme_len + 1); - InitStandardSchemes(); SchemeWithType scheme_with_type; scheme_with_type.scheme = dup_scheme; scheme_with_type.type = type; - standard_schemes->push_back(scheme_with_type); + schemes->push_back(scheme_with_type); +} + +} // namespace + +void Initialize() { + InitStandardSchemes(); + InitReferrerSchemes(); +} + +void Shutdown() { + if (standard_schemes) { + delete standard_schemes; + standard_schemes = NULL; + } + if (referrer_schemes) { + delete referrer_schemes; + referrer_schemes = NULL; + } } -void LockStandardSchemes() { - standard_schemes_locked = true; +void AddStandardScheme(const char* new_scheme, SchemeType type) { + InitStandardSchemes(); + DoAddScheme(new_scheme, type, standard_schemes); +} + +void AddReferrerScheme(const char* new_scheme, SchemeType type) { + InitReferrerSchemes(); + DoAddScheme(new_scheme, type, referrer_schemes); +} + +void LockSchemeRegistries() { + scheme_registries_locked = true; } bool IsStandard(const char* spec, const Component& scheme) { @@ -427,6 +468,12 @@ bool IsStandard(const base::char16* spec, const Component& scheme) { return DoIsStandard(spec, scheme, &unused_scheme_type); } +bool IsReferrerScheme(const char* spec, const Component& scheme) { + InitReferrerSchemes(); + SchemeType unused_scheme_type; + return DoIsInSchemes(spec, scheme, &unused_scheme_type, *referrer_schemes); +} + bool FindAndCompareScheme(const char* str, int str_len, const char* compare, diff --git a/url/url_util.h b/url/url_util.h index 04a6ef4..a209a61 100644 --- a/url/url_util.h +++ b/url/url_util.h @@ -20,7 +20,7 @@ namespace url { // Initialization is NOT required, it will be implicitly initialized when first // used. However, this implicit initialization is NOT threadsafe. If you are // using this library in a threaded environment and don't have a consistent -// "first call" (an example might be calling AddStandardScheme with your special +// "first call" (an example might be calling Add*Scheme with your special // application-specific schemes) then you will want to call initialize before // spawning any threads. // @@ -61,24 +61,33 @@ struct URL_EXPORT SchemeWithType { // URI syntax" (https://tools.ietf.org/html/rfc3986#section-3). // // This function is not threadsafe and can not be called concurrently with any -// other url_util function. It will assert if the list of standard schemes has -// been locked (see LockStandardSchemes). +// other url_util function. It will assert if the lists of schemes have +// been locked (see LockSchemeRegistries). URL_EXPORT void AddStandardScheme(const char* new_scheme, SchemeType scheme_type); -// Sets a flag to prevent future calls to AddStandardScheme from succeeding. +// Adds an application-defined scheme to the internal list of schemes allowed +// for referrers. +// +// This function is not threadsafe and can not be called concurrently with any +// other url_util function. It will assert if the lists of schemes have +// been locked (see LockSchemeRegistries). +URL_EXPORT void AddReferrerScheme(const char* new_scheme, + SchemeType scheme_type); + +// Sets a flag to prevent future calls to Add*Scheme from succeeding. // // This is designed to help prevent errors for multithreaded applications. -// Normal usage would be to call AddStandardScheme for your custom schemes at -// the beginning of program initialization, and then LockStandardSchemes. This -// prevents future callers from mistakenly calling AddStandardScheme when the +// Normal usage would be to call Add*Scheme for your custom schemes at +// the beginning of program initialization, and then LockSchemeRegistries. This +// prevents future callers from mistakenly calling Add*Scheme when the // program is running with multiple threads, where such usage would be // dangerous. // -// We could have had AddStandardScheme use a lock instead, but that would add +// We could have had Add*Scheme use a lock instead, but that would add // some platform-specific dependencies we don't otherwise have now, and is // overkill considering the normal usage is so simple. -URL_EXPORT void LockStandardSchemes(); +URL_EXPORT void LockSchemeRegistries(); // Locates the scheme in the given string and places it into |found_scheme|, // which may be NULL to indicate the caller does not care about the range. @@ -112,6 +121,10 @@ inline bool FindAndCompareScheme(const base::string16& str, URL_EXPORT bool IsStandard(const char* spec, const Component& scheme); URL_EXPORT bool IsStandard(const base::char16* spec, const Component& scheme); +// Returns true if the given scheme identified by |scheme| within |spec| is in +// the list of allowed schemes for referrers (see AddReferrerScheme). +URL_EXPORT bool IsReferrerScheme(const char* spec, const Component& scheme); + // Returns true and sets |type| to the SchemeType of the given scheme // identified by |scheme| within |spec| if the scheme is in the list of known // standard-format schemes (see AddStandardScheme). diff --git a/url/url_util_unittest.cc b/url/url_util_unittest.cc index 4143062..f0032c2 100644 --- a/url/url_util_unittest.cc +++ b/url/url_util_unittest.cc @@ -71,6 +71,21 @@ TEST(URLUtilTest, IsStandard) { EXPECT_FALSE(IsStandard(kFooScheme, Component(0, strlen(kFooScheme)))); } +TEST(URLUtilTest, IsReferrerScheme) { + const char kHTTPScheme[] = "http"; + EXPECT_TRUE(IsReferrerScheme(kHTTPScheme, Component(0, strlen(kHTTPScheme)))); + + const char kFooScheme[] = "foo"; + EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); +} + +TEST(URLUtilTest, AddReferrerScheme) { + const char kFooScheme[] = "foo"; + EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); + AddReferrerScheme(kFooScheme, url::SCHEME_WITHOUT_PORT); + EXPECT_TRUE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); +} + TEST(URLUtilTest, GetStandardSchemeType) { url::SchemeType scheme_type; |