From b06b598252791a5c548826f2d930c7e7e8034413 Mon Sep 17 00:00:00 2001 From: James Willcox Date: Wed, 24 Jul 2019 15:48:00 +0000 Subject: [PATCH] Bug 1561079 - Add a `GeckoSession.loadUri()` overload that takes a referring `GeckoSession` r=geckoview-reviewers,ckerschb,esawin,agi Differential Revision: https://phabricator.services.mozilla.com/D36526 --HG-- extra : moz-landing-system : lando --- .../geckoview/test/NavigationDelegateTest.kt | 34 ++++++++ .../org/mozilla/geckoview/GeckoSession.java | 36 +++++++-- .../modules/geckoview/GeckoViewNavigation.jsm | 80 ++++++++++++++----- 3 files changed, 122 insertions(+), 28 deletions(-) diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt index 144ba439f72d..c9e752e993b2 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt @@ -1174,6 +1174,40 @@ class NavigationDelegateTest : BaseSessionTest() { equalTo(referrer)) } + @Test fun loadUriReferrerSession() { + val uri = "https://example.com/bar" + val referrer = "https://example.org/foo" + + sessionRule.session.loadUri(referrer) + sessionRule.session.waitForPageStop() + + val newSession = sessionRule.createOpenSession() + newSession.loadUri(uri, sessionRule.session, GeckoSession.LOAD_FLAGS_NONE) + newSession.waitForPageStop() + + assertThat("Referrer should match", + newSession.evaluateJS("document.referrer") as String, + equalTo(referrer)) + } + + @Test fun loadUriReferrerSessionFileUrl() { + val uri = "file:///system/etc/fonts.xml" + val referrer = "https://example.org" + + sessionRule.session.loadUri(referrer) + sessionRule.session.waitForPageStop() + + val newSession = sessionRule.createOpenSession() + newSession.loadUri(uri, sessionRule.session, GeckoSession.LOAD_FLAGS_NONE) + newSession.waitUntilCalled(object : Callbacks.NavigationDelegate { + @AssertCalled + override fun onLoadError(session: GeckoSession, uri: String?, error: WebRequestError): GeckoResult? { + return null + } + }) + } + + @Test(expected = GeckoResult.UncaughtException::class) fun onNewSession_doesNotAllowOpened() { // Disable popup blocker. diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index f4522f16061f..680046005b58 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -1534,7 +1534,7 @@ public class GeckoSession implements Parcelable { */ @AnyThread public void loadUri(final @NonNull String uri) { - loadUri(uri, null, LOAD_FLAGS_NONE); + loadUri(uri, (GeckoSession)null, LOAD_FLAGS_NONE); } /** @@ -1545,7 +1545,7 @@ public class GeckoSession implements Parcelable { */ @AnyThread public void loadUri(final @NonNull String uri, final @LoadFlags int flags) { - loadUri(uri, null, flags); + loadUri(uri, (GeckoSession)null, flags); } /** @@ -1563,7 +1563,29 @@ public class GeckoSession implements Parcelable { msg.putInt("flags", flags); if (referrer != null) { - msg.putString("referrer", referrer); + msg.putString("referrerUri", referrer); + } + mEventDispatcher.dispatch("GeckoView:LoadUri", msg); + } + + /** + * Load the given URI with the specified referrer and load type. This method will also do any + * applicable checks to ensure that the specified URI is both safe and allowable + * according to the referring GeckoSession. + * + * @param uri the URI to load + * @param referrer the referring GeckoSession, may be null + * @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*} + */ + @AnyThread + public void loadUri(final @NonNull String uri, final @Nullable GeckoSession referrer, + final @LoadFlags int flags) { + final GeckoBundle msg = new GeckoBundle(); + msg.putString("uri", uri); + msg.putInt("flags", flags); + + if (referrer != null) { + msg.putString("referrerSessionId", referrer.mId); } mEventDispatcher.dispatch("GeckoView:LoadUri", msg); } @@ -1584,7 +1606,7 @@ public class GeckoSession implements Parcelable { */ @AnyThread public void loadUri(final @NonNull Uri uri, final @LoadFlags int flags) { - loadUri(uri.toString(), null, flags); + loadUri(uri.toString(), (GeckoSession)null, flags); } /** @@ -1613,7 +1635,7 @@ public class GeckoSession implements Parcelable { throw new IllegalArgumentException("data cannot be null"); } - loadUri(createDataUri(data, mimeType), null, LOAD_FLAGS_NONE); + loadUri(createDataUri(data, mimeType), (GeckoSession)null, LOAD_FLAGS_NONE); } /** @@ -1628,8 +1650,8 @@ public class GeckoSession implements Parcelable { if (bytes == null) { throw new IllegalArgumentException("data cannot be null"); } - - loadUri(createDataUri(bytes, mimeType), null, LOAD_FLAGS_FORCE_ALLOW_DATA_URI); + + loadUri(createDataUri(bytes, mimeType), (GeckoSession)null, LOAD_FLAGS_FORCE_ALLOW_DATA_URI); } /** diff --git a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm index 656bfa80086f..3221912d5c38 100644 --- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm +++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm @@ -105,7 +105,7 @@ class GeckoViewNavigation extends GeckoViewModule { this.browser.gotoIndex(aData.index); break; case "GeckoView:LoadUri": - const { uri, referrer, flags } = aData; + const { uri, referrerUri, referrerSessionId, flags } = aData; let navFlags = 0; @@ -138,34 +138,72 @@ class GeckoViewNavigation extends GeckoViewModule { this.moduleManager.updateRemoteTypeForURI(uri); } - let parsedUri; - let triggeringPrincipal; - try { - parsedUri = Services.io.newURI(uri); - if ( - parsedUri.schemeIs("about") || - parsedUri.schemeIs("data") || - parsedUri.schemeIs("file") || - parsedUri.schemeIs("resource") || - parsedUri.schemeIs("moz-extension") - ) { - // Only allow privileged loading for certain URIs. - triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal( - parsedUri, - {} - ); - } - } catch (ignored) {} + let triggeringPrincipal, referrerInfo, csp; + if (referrerSessionId) { + const referrerWindow = Services.ww.getWindowByName( + referrerSessionId, + this.window + ); + triggeringPrincipal = referrerWindow.browser.contentPrincipal; + csp = referrerWindow.browser.csp; + + const referrerPolicy = referrerWindow.browser.referrerInfo + ? referrerWindow.browser.referrerInfo.referrerPolicy + : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET; + + referrerInfo = new ReferrerInfo( + referrerPolicy, + true, + referrerWindow.browser.documentURI + ); + } else { + try { + const parsedUri = Services.io.newURI(uri); + if ( + parsedUri.schemeIs("about") || + parsedUri.schemeIs("data") || + parsedUri.schemeIs("file") || + parsedUri.schemeIs("resource") || + parsedUri.schemeIs("moz-extension") + ) { + // Only allow privileged loading for certain URIs. + triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal( + parsedUri, + {} + ); + } + } catch (ignored) {} + + referrerInfo = createReferrerInfo(referrerUri); + } + if (!triggeringPrincipal) { triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal( {} ); } - this.browser.loadURI(parsedUri ? parsedUri.spec : uri, { + // For any navigation here, we should have an appropriate triggeringPrincipal: + // + // 1) If we have a referring session, triggeringPrincipal is the contentPrincipal from the + // referring document. + // 2) For certain URI schemes listed above, we will have a codebase principal. + // 3) In all other cases, we create a NullPrincipal. + // + // The navigation flags are driven by the app. We purposely do not propagate these from + // the referring document, but expect that the app will in most cases. + // + // The referrerInfo is derived from the referring document, if present, by propagating any + // referrer policy. If we only have the referrerUri from the app, we create a referrerInfo + // with the specified URI and no policy set. If no referrerUri is present and we have no + // referring session, the referrerInfo is null. + // + // csp is only present if we have a referring document, null otherwise. + this.browser.loadURI(uri, { flags: navFlags, - referrerInfo: createReferrerInfo(referrer), + referrerInfo, triggeringPrincipal, + csp, }); break; case "GeckoView:Reload":