From fbd7747e2d802572544fbc635a46f095f5825ff6 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Thu, 20 Jan 2022 15:28:48 +0200 Subject: [PATCH] [fenix] Add missing request interceptors to androidTest AppRequestInterceptor --- .../mozilla/fenix/AppRequestInterceptor.kt | 175 +++++++++++++++++- 1 file changed, 174 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/AppRequestInterceptor.kt b/app/src/androidTest/java/org/mozilla/fenix/AppRequestInterceptor.kt index 8fefecae2..ef0986a8a 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/AppRequestInterceptor.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/AppRequestInterceptor.kt @@ -5,10 +5,16 @@ package org.mozilla.fenix import android.content.Context +import android.net.ConnectivityManager +import androidx.core.content.getSystemService import androidx.navigation.NavController +import mozilla.components.browser.errorpages.ErrorPages +import mozilla.components.browser.errorpages.ErrorType import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.request.RequestInterceptor +import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.isOnline import org.mozilla.fenix.helpers.TestHelper.appContext import java.lang.ref.WeakReference @@ -35,8 +41,175 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor { isDirectNavigation: Boolean, isSubframeRequest: Boolean ): RequestInterceptor.InterceptionResponse? { + + interceptFxaRequest( + engineSession, + uri, + lastUri, + hasUserGesture, + isSameDomain, + isRedirect, + isDirectNavigation, + isSubframeRequest + )?.let { response -> + return response + } + + interceptAmoRequest(uri, isSameDomain, hasUserGesture)?.let { response -> + return response + } + + return context.components.services.appLinksInterceptor + .onLoadRequest( + engineSession, + uri, + lastUri, + hasUserGesture, + isSameDomain, + isRedirect, + isDirectNavigation, + isSubframeRequest + ) + } + + override fun onErrorRequest( + session: EngineSession, + errorType: ErrorType, + uri: String? + ): RequestInterceptor.ErrorResponse? { + val improvedErrorType = improveErrorType(errorType) + val riskLevel = getRiskLevel(improvedErrorType) + + context.components.analytics.metrics.track(Event.ErrorPageVisited(improvedErrorType)) + + val errorPageUri = ErrorPages.createUrlEncodedErrorPage( + context = context, + errorType = improvedErrorType, + uri = uri, + htmlResource = riskLevel.htmlRes + ) + + return RequestInterceptor.ErrorResponse(errorPageUri) + } + + /** + * Checks if the provided [uri] is a request to install an add-on from addons.mozilla.org and + * redirects to Add-ons Manager to trigger installation if needed. + * + * @return [RequestInterceptor.InterceptionResponse.Deny] when installation was triggered and + * the original request can be skipped, otherwise null to continue loading the page. + */ + private fun interceptAmoRequest( + uri: String, + isSameDomain: Boolean, + hasUserGesture: Boolean + ): RequestInterceptor.InterceptionResponse? { + // First we execute a quick check to see if this is a request we're interested in i.e. a + // request triggered by the user and coming from AMO. + if (hasUserGesture && isSameDomain && uri.startsWith(AMO_BASE_URL)) { + + // Check if this is a request to install an add-on. + val matchResult = AMO_INSTALL_URL_REGEX.toRegex().matchEntire(uri) + if (matchResult != null) { + + // Navigate and trigger add-on installation. + matchResult.groupValues.getOrNull(1)?.let { addonId -> + navController?.get()?.navigate( + NavGraphDirections.actionGlobalAddonsManagementFragment(addonId) + ) + + // We've redirected to the add-ons management fragment, skip original request. + return RequestInterceptor.InterceptionResponse.Deny + } + } + } + + // In all other case we let the original request proceed. + return null + } + + @Suppress("LongParameterList") + private fun interceptFxaRequest( + engineSession: EngineSession, + uri: String, + lastUri: String?, + hasUserGesture: Boolean, + isSameDomain: Boolean, + isRedirect: Boolean, + isDirectNavigation: Boolean, + isSubframeRequest: Boolean + ): RequestInterceptor.InterceptionResponse? { return appContext.components.services.accountsAuthFeature.interceptor.onLoadRequest( - engineSession, uri, lastUri, hasUserGesture, isSameDomain, isRedirect, isDirectNavigation, isSubframeRequest + engineSession, + uri, + lastUri, + hasUserGesture, + isSameDomain, + isRedirect, + isDirectNavigation, + isSubframeRequest ) } + + /** + * Where possible, this will make the error type more accurate by including information not + * available to AC. + */ + private fun improveErrorType(errorType: ErrorType): ErrorType { + // This is not an ideal solution. For context, see: + // https://github.com/mozilla-mobile/android-components/pull/5068#issuecomment-558415367 + + val isConnected: Boolean = context.getSystemService()!!.isOnline() + + return when { + errorType == ErrorType.ERROR_UNKNOWN_HOST && !isConnected -> ErrorType.ERROR_NO_INTERNET + else -> errorType + } + } + + private fun getRiskLevel(errorType: ErrorType): RiskLevel = when (errorType) { + ErrorType.UNKNOWN, + ErrorType.ERROR_NET_INTERRUPT, + ErrorType.ERROR_NET_TIMEOUT, + ErrorType.ERROR_CONNECTION_REFUSED, + ErrorType.ERROR_UNKNOWN_SOCKET_TYPE, + ErrorType.ERROR_REDIRECT_LOOP, + ErrorType.ERROR_OFFLINE, + ErrorType.ERROR_NET_RESET, + ErrorType.ERROR_UNSAFE_CONTENT_TYPE, + ErrorType.ERROR_CORRUPTED_CONTENT, + ErrorType.ERROR_CONTENT_CRASHED, + ErrorType.ERROR_INVALID_CONTENT_ENCODING, + ErrorType.ERROR_UNKNOWN_HOST, + ErrorType.ERROR_MALFORMED_URI, + ErrorType.ERROR_FILE_NOT_FOUND, + ErrorType.ERROR_FILE_ACCESS_DENIED, + ErrorType.ERROR_PROXY_CONNECTION_REFUSED, + ErrorType.ERROR_UNKNOWN_PROXY_HOST, + ErrorType.ERROR_NO_INTERNET, + ErrorType.ERROR_HTTPS_ONLY, + ErrorType.ERROR_UNKNOWN_PROTOCOL -> RiskLevel.Low + + ErrorType.ERROR_SECURITY_BAD_CERT, + ErrorType.ERROR_SECURITY_SSL, + ErrorType.ERROR_PORT_BLOCKED -> RiskLevel.Medium + + ErrorType.ERROR_SAFEBROWSING_HARMFUL_URI, + ErrorType.ERROR_SAFEBROWSING_MALWARE_URI, + ErrorType.ERROR_SAFEBROWSING_PHISHING_URI, + ErrorType.ERROR_SAFEBROWSING_UNWANTED_URI -> RiskLevel.High + } + + internal enum class RiskLevel(val htmlRes: String) { + Low(LOW_AND_MEDIUM_RISK_ERROR_PAGES), + Medium(LOW_AND_MEDIUM_RISK_ERROR_PAGES), + High(HIGH_RISK_ERROR_PAGES), + } + + companion object { + internal const val LOW_AND_MEDIUM_RISK_ERROR_PAGES = "low_and_medium_risk_error_pages.html" + internal const val HIGH_RISK_ERROR_PAGES = "high_risk_error_pages.html" + internal const val AMO_BASE_URL = BuildConfig.AMO_BASE_URL + internal const val AMO_INSTALL_URL_REGEX = "$AMO_BASE_URL/android/downloads/file/([^\\s]+)/([^\\s]+\\.xpi)" + } }