diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/external_intents/android/java | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/external_intents/android/java')
4 files changed, 195 insertions, 65 deletions
diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java index 156128517d4..dd8a259c4f4 100644 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java +++ b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java @@ -9,11 +9,13 @@ import android.content.Intent; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.WebContents; import org.chromium.ui.base.WindowAndroid; +import org.chromium.url.Origin; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -133,8 +135,12 @@ public interface ExternalNavigationDelegate { */ void maybeAdjustInstantAppExtras(Intent intent, boolean isIntentToInstantApp); - /** Invoked for intents with user gestures and records the user gesture if desired. */ - void maybeSetUserGesture(Intent intent); + /** + * Invoked for intents with request metadata such as user gesture, whether request is renderer + * initiated and the initiator origin. Records the information if desired. + */ + void maybeSetRequestMetadata(Intent intent, boolean hasUserGesture, boolean isRendererInitiated, + @Nullable Origin initiatorOrigin); /** * Records the pending incognito URL if desired. Called only if the diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java index b1b9bffa4b1..69f43cc8cfa 100644 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java +++ b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java @@ -160,6 +160,17 @@ public class ExternalNavigationHandler { @VisibleForTesting static final String INTENT_ACTION_HISTOGRAM = "Android.Intent.OverrideUrlLoadingIntentAction"; + // Helper class to return a boolean by reference. + private static class MutableBoolean { + private Boolean mValue = null; + public void set(boolean value) { + mValue = value; + } + public Boolean get() { + return mValue; + } + } + private final ExternalNavigationDelegate mDelegate; /** @@ -217,9 +228,16 @@ public class ExternalNavigationHandler { && !UrlUtilities.isValidForIntentFallbackNavigation(browserFallbackUrl)) { browserFallbackUrl = null; } + + // TODO(https://crbug.com/1096099): Refactor shouldOverrideUrlLoadingInternal, splitting it + // up to separate out the notions wanting to fire an external intent vs being able to. + MutableBoolean canLaunchExternalFallbackResult = new MutableBoolean(); + long time = SystemClock.elapsedRealtime(); @OverrideUrlLoadingResult - int result = shouldOverrideUrlLoadingInternal(params, targetIntent, browserFallbackUrl); + int result = shouldOverrideUrlLoadingInternal( + params, targetIntent, browserFallbackUrl, canLaunchExternalFallbackResult); + assert canLaunchExternalFallbackResult.get() != null; RecordHistogram.recordTimesHistogram( "Android.StrictMode.OverrideUrlLoadingTime", SystemClock.elapsedRealtime() - time); @@ -236,31 +254,64 @@ public class ExternalNavigationHandler { && (params.getRedirectHandler() == null // For instance, if this is a chained fallback URL, we ignore it. || !params.getRedirectHandler().shouldNotOverrideUrlLoading())) { - result = handleFallbackUrl(params, targetIntent, browserFallbackUrl); + result = handleFallbackUrl(params, targetIntent, browserFallbackUrl, + canLaunchExternalFallbackResult.get()); } if (DEBUG) printDebugShouldOverrideUrlLoadingResult(result); return result; } - private @OverrideUrlLoadingResult int handleFallbackUrl( - ExternalNavigationParams params, Intent targetIntent, String browserFallbackUrl) { + private @OverrideUrlLoadingResult int handleFallbackUrl(ExternalNavigationParams params, + Intent targetIntent, String browserFallbackUrl, boolean canLaunchExternalFallback) { if (mDelegate.isIntentToInstantApp(targetIntent)) { RecordHistogram.recordEnumeratedHistogram("Android.InstantApps.DirectInstantAppsIntent", AiaIntent.FALLBACK_USED, AiaIntent.NUM_ENTRIES); } - // Launch WebAPK if it can handle the URL. - try { - Intent intent = Intent.parseUri(browserFallbackUrl, Intent.URI_INTENT_SCHEME); - sanitizeQueryIntentActivitiesIntent(intent); - List<ResolveInfo> resolvingInfos = queryIntentActivities(intent); - if (!isAlreadyInTargetWebApk(resolvingInfos, params) - && launchWebApkIfSoleIntentHandler(resolvingInfos, intent)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; + + if (canLaunchExternalFallback) { + if (shouldBlockAllExternalAppLaunches(params) || params.isIncognito()) { + throw new SecurityException("Context is not allowed to launch an external app."); } - } catch (Exception e) { - if (DEBUG) Log.i(TAG, "Could not parse fallback url as intent"); + // Launch WebAPK if it can handle the URL. + try { + Intent intent = Intent.parseUri(browserFallbackUrl, Intent.URI_INTENT_SCHEME); + sanitizeQueryIntentActivitiesIntent(intent); + List<ResolveInfo> resolvingInfos = queryIntentActivities(intent); + if (!isAlreadyInTargetWebApk(resolvingInfos, params) + && launchWebApkIfSoleIntentHandler(resolvingInfos, intent)) { + return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; + } + } catch (Exception e) { + if (DEBUG) Log.i(TAG, "Could not parse fallback url as intent"); + } + + // If the fallback URL is a link to Play Store, send the user to Play Store app + // instead: crbug.com/638672. + Pair<String, String> appInfo = maybeGetPlayStoreAppIdAndReferrer(browserFallbackUrl); + if (appInfo != null) { + String marketReferrer = TextUtils.isEmpty(appInfo.second) + ? ContextUtils.getApplicationContext().getPackageName() + : appInfo.second; + return sendIntentToMarket(appInfo.first, marketReferrer, params); + } + } + + // For subframes, we don't support fallback url for now. + // http://crbug.com/364522. + if (!params.isMainFrame()) { + if (DEBUG) Log.i(TAG, "Don't support fallback url in subframes"); + return OverrideUrlLoadingResult.NO_OVERRIDE; } - return clobberCurrentTabWithFallbackUrl(browserFallbackUrl, params); + + // NOTE: any further redirection from fall-back URL should not override URL loading. + // Otherwise, it can be used in chain for fingerprinting multiple app installation + // status in one shot. In order to prevent this scenario, we notify redirection + // handler that redirection from the current navigation should stay in this app. + if (params.getRedirectHandler() != null) { + params.getRedirectHandler().setShouldNotOverrideUrlLoadingOnCurrentRedirectChain(); + } + if (DEBUG) Log.i(TAG, "clobberCurrentTab called"); + return clobberCurrentTab(browserFallbackUrl, params.getReferrerUrl()); } private void printDebugShouldOverrideUrlLoadingResult(int result) { @@ -300,6 +351,19 @@ public class ExternalNavigationHandler { } /** + * https://crbug.com/1094442: Don't allow any external navigation on AUTO_SUBFRAME navigation + * (eg. initial ad frame navigation). + */ + private boolean blockExternalNavFromAutoSubframe(ExternalNavigationParams params) { + int pageTransitionCore = params.getPageTransition() & PageTransition.CORE_MASK; + if (pageTransitionCore == PageTransition.AUTO_SUBFRAME) { + if (DEBUG) Log.i(TAG, "Auto navigation in subframe"); + return true; + } + return false; + } + + /** * http://crbug.com/441284 : Disallow firing external intent while the app is in the background. */ private boolean blockExternalNavWhileBackgrounded(ExternalNavigationParams params) { @@ -651,6 +715,30 @@ public class ExternalNavigationHandler { } /** + * Intent URIs leads to creating intents that chrome would use for firing external navigations + * via Android. Android throws an exception [1] when an application exposes a file:// Uri to + * another app. + * + * This method checks if the |targetIntent| contains the file:// scheme in its data. + * + * [1]: https://developer.android.com/reference/android/os/FileUriExposedException + */ + private boolean hasFileSchemeInIntentURI(Intent targetIntent, boolean hasIntentScheme) { + // We are only concerned with targetIntent that was generated due to intent:// schemes only. + if (!hasIntentScheme) return false; + + Uri data = targetIntent.getData(); + + if (data == null || data.getScheme() == null) return false; + + if (data.getScheme().equalsIgnoreCase(UrlConstants.FILE_SCHEME)) { + if (DEBUG) Log.i(TAG, "Intent navigation to file: URI"); + return true; + } + return false; + } + + /** * Special case - It makes no sense to use an external application for a YouTube * pairing code URL, since these match the current tab with a device (Chromecast * or similar) it is supposed to be controlling. Using a different application @@ -851,7 +939,8 @@ public class ExternalNavigationHandler { AiaIntent.SERP, AiaIntent.NUM_ENTRIES); } - if (params.hasUserGesture()) mDelegate.maybeSetUserGesture(targetIntent); + mDelegate.maybeSetRequestMetadata(targetIntent, params.hasUserGesture(), + params.isRendererInitiated(), params.getInitiatorOrigin()); } private @OverrideUrlLoadingResult int handleExternalIncognitoIntent(Intent targetIntent, @@ -940,15 +1029,20 @@ public class ExternalNavigationHandler { return false; } + // Check if we're navigating under conditions that should never launch an external app. + private boolean shouldBlockAllExternalAppLaunches(ExternalNavigationParams params) { + return blockExternalNavFromAutoSubframe(params) || blockExternalNavWhileBackgrounded(params) + || blockExternalNavFromBackgroundTab(params) || ignoreBackForwardNav(params); + } + private @OverrideUrlLoadingResult int shouldOverrideUrlLoadingInternal( ExternalNavigationParams params, Intent targetIntent, - @Nullable String browserFallbackUrl) { + @Nullable String browserFallbackUrl, MutableBoolean canLaunchExternalFallbackResult) { sanitizeQueryIntentActivitiesIntent(targetIntent); + // Don't allow external fallback URLs by default. + canLaunchExternalFallbackResult.set(false); - if (blockExternalNavWhileBackgrounded(params) || blockExternalNavFromBackgroundTab(params) - || ignoreBackForwardNav(params)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } + if (shouldBlockAllExternalAppLaunches(params)) return OverrideUrlLoadingResult.NO_OVERRIDE; if (handleWithAutofillAssistant(params, targetIntent, browserFallbackUrl)) { return OverrideUrlLoadingResult.NO_OVERRIDE; @@ -1016,6 +1110,10 @@ public class ExternalNavigationHandler { return OverrideUrlLoadingResult.NO_OVERRIDE; } + if (hasFileSchemeInIntentURI(targetIntent, hasIntentScheme)) { + return OverrideUrlLoadingResult.NO_OVERRIDE; + } + if (isYoutubePairingCode(params)) return OverrideUrlLoadingResult.NO_OVERRIDE; if (shouldStayInIncognito(params, isExternalProtocol)) { @@ -1026,6 +1124,10 @@ public class ExternalNavigationHandler { if (hasIntentScheme) recordIntentActionMetrics(targetIntent); + // From this point on, we have determined it is safe to launch an External App from a + // fallback URL, provided the user isn't in incognito. + if (!params.isIncognito()) canLaunchExternalFallbackResult.set(true); + Intent debugIntent = new Intent(targetIntent); List<ResolveInfo> resolvingInfos = queryIntentActivities(targetIntent); if (resolvingInfos.isEmpty()) { @@ -1154,44 +1256,6 @@ public class ExternalNavigationHandler { } /** - * Clobber the current tab with fallback URL. - * - * @param browserFallbackUrl The fallback URL. - * @param params The external navigation params. - * @return {@link OverrideUrlLoadingResult} if the tab was clobbered, or we launched an - * intent. - */ - private @OverrideUrlLoadingResult int clobberCurrentTabWithFallbackUrl( - String browserFallbackUrl, ExternalNavigationParams params) { - // If the fallback URL is a link to Play Store, send the user to Play Store app - // instead: crbug.com/638672. - Pair<String, String> appInfo = maybeGetPlayStoreAppIdAndReferrer(browserFallbackUrl); - if (appInfo != null) { - String marketReferrer = TextUtils.isEmpty(appInfo.second) - ? ContextUtils.getApplicationContext().getPackageName() - : appInfo.second; - return sendIntentToMarket(appInfo.first, marketReferrer, params); - } - - // For subframes, we don't support fallback url for now. - // http://crbug.com/364522. - if (!params.isMainFrame()) { - if (DEBUG) Log.i(TAG, "Don't support fallback url in subframes"); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - // NOTE: any further redirection from fall-back URL should not override URL loading. - // Otherwise, it can be used in chain for fingerprinting multiple app installation - // status in one shot. In order to prevent this scenario, we notify redirection - // handler that redirection from the current navigation should stay in this app. - if (params.getRedirectHandler() != null) { - params.getRedirectHandler().setShouldNotOverrideUrlLoadingOnCurrentRedirectChain(); - } - if (DEBUG) Log.i(TAG, "clobberCurrentTab called"); - return clobberCurrentTab(browserFallbackUrl, params.getReferrerUrl()); - } - - /** * If the given URL is to Google Play, extracts the package name and referrer tracking code * from the {@param url} and returns as a Pair in that order. Otherwise returns null. */ diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java index f4d40512867..fe172dadec4 100644 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java +++ b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java @@ -4,6 +4,10 @@ package org.chromium.components.external_intents; +import androidx.annotation.Nullable; + +import org.chromium.url.Origin; + /** * A container object for passing navigation parameters to {@link ExternalNavigationHandler}. */ @@ -53,12 +57,22 @@ public class ExternalNavigationParams { */ private final boolean mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; + /** + * Whether the navigation is initiated by the renderer. + */ + private boolean mIsRendererInitiated; + + /** + * The origin that initiates the navigation, could be null. + */ + private Origin mInitiatorOrigin; + private ExternalNavigationParams(String url, boolean isIncognito, String referrerUrl, int pageTransition, boolean isRedirect, boolean appMustBeInForeground, RedirectHandler redirectHandler, boolean openInNewTab, boolean isBackgroundTabNavigation, boolean isMainFrame, String nativeClientPackageName, - boolean hasUserGesture, - boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent) { + boolean hasUserGesture, boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent, + boolean isRendererInitiated, @Nullable Origin initiatorOrigin) { mUrl = url; mIsIncognito = isIncognito; mPageTransition = pageTransition; @@ -73,6 +87,8 @@ public class ExternalNavigationParams { mHasUserGesture = hasUserGesture; mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent = shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; + mIsRendererInitiated = isRendererInitiated; + mInitiatorOrigin = initiatorOrigin; } /** @return The URL to potentially open externally. */ @@ -149,6 +165,21 @@ public class ExternalNavigationParams { return mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; } + /** + * @return Whether the navigation is initiated by renderer. + */ + public boolean isRendererInitiated() { + return mIsRendererInitiated; + } + + /** + * @return The origin that initiates the navigation. + */ + @Nullable + public Origin getInitiatorOrigin() { + return mInitiatorOrigin; + } + /** The builder for {@link ExternalNavigationParams} objects. */ public static class Builder { /** The URL which we are navigating to. */ @@ -196,6 +227,16 @@ public class ExternalNavigationParams { */ private boolean mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; + /** + * Whether the navigation is initiated by the renderer. + */ + private boolean mIsRendererInitiated; + + /** + * The origin that initiates the navigation, could be null. + */ + private Origin mInitiatorOrigin; + public Builder(String url, boolean isIncognito) { mUrl = url; mIsIncognito = isIncognito; @@ -261,12 +302,29 @@ public class ExternalNavigationParams { return this; } + /** + * Sets whether the navigation is initiated by renderer. + */ + public Builder setIsRendererInitiated(boolean v) { + mIsRendererInitiated = v; + return this; + } + + /** + * Sets the origin that initiates the navigation. + */ + public Builder setInitiatorOrigin(@Nullable Origin v) { + mInitiatorOrigin = v; + return this; + } + /** @return A fully constructed {@link ExternalNavigationParams} object. */ public ExternalNavigationParams build() { return new ExternalNavigationParams(mUrl, mIsIncognito, mReferrerUrl, mPageTransition, mIsRedirect, mApplicationMustBeInForeground, mRedirectHandler, mOpenInNewTab, mIsBackgroundTabNavigation, mIsMainFrame, mNativeClientPackageName, - mHasUserGesture, mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent); + mHasUserGesture, mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent, + mIsRendererInitiated, mInitiatorOrigin); } } } diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java index dad8bbfe811..686721169fa 100644 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java +++ b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java @@ -180,7 +180,9 @@ public class InterceptNavigationDelegateImpl implements InterceptNavigationDeleg .setIsMainFrame(navigationParams.isMainFrame) .setHasUserGesture(navigationParams.hasUserGesture) .setShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent( - shouldCloseTab && navigationParams.isMainFrame); + shouldCloseTab && navigationParams.isMainFrame) + .setIsRendererInitiated(navigationParams.isRendererInitiated) + .setInitiatorOrigin(navigationParams.initiatorOrigin); } /** |