RequestInterceptor.kt

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.concept.engine.request

import android.content.Intent
import mozilla.components.browser.errorpages.ErrorType
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.LoadUrlFlags

/**
 * Interface for classes that want to intercept load requests to allow custom behavior.
 */
interface RequestInterceptor {

    /**
     * An alternative response for an intercepted request.
     */
    sealed class InterceptionResponse {
        data class Content(
            val data: String,
            val mimeType: String = "text/html",
            val encoding: String = "UTF-8",
        ) : InterceptionResponse()

        /**
         * The intercepted request URL to load.
         *
         * @param url The URL of the request.
         * @param flags The [LoadUrlFlags] to use when loading the provided [url].
         * @param additionalHeaders The extra headers to use when loading the provided [url].
         */
        data class Url(
            val url: String,
            val flags: LoadUrlFlags = LoadUrlFlags.select(
                LoadUrlFlags.EXTERNAL,
                LoadUrlFlags.LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE,
            ),
            val additionalHeaders: Map<String, String>? = null,
        ) : InterceptionResponse()

        data class AppIntent(val appIntent: Intent, val url: String) : InterceptionResponse()

        /**
         * Deny request without further action.
         */
        object Deny : InterceptionResponse()
    }

    /**
     * An alternative response for an error request.
     * Used to load an encoded URI directly.
     */
    data class ErrorResponse(val uri: String)

    /**
     * A request to open an URI. This is called before each page load to allow
     * providing custom behavior.
     *
     * @param engineSession The engine session that initiated the callback.
     * @param uri The URI of the request.
     * @param lastUri The URI of the last request.
     * @param hasUserGesture If the request is triggered by the user then true, else false.
     * @param isSameDomain If the request is the same domain as the current URL then true, else false.
     * @param isRedirect If the request is due to a redirect then true, else false.
     * @param isDirectNavigation If the request is due to a direct navigation then true, else false.
     * @param isSubframeRequest If the request is coming from a subframe then true, else false.
     * @return An [InterceptionResponse] object containing alternative content
     * or an alternative URL. Null if the original request should continue to
     * be loaded.
     */
    @Suppress("LongParameterList")
    fun onLoadRequest(
        engineSession: EngineSession,
        uri: String,
        lastUri: String?,
        hasUserGesture: Boolean,
        isSameDomain: Boolean,
        isRedirect: Boolean,
        isDirectNavigation: Boolean,
        isSubframeRequest: Boolean,
    ): InterceptionResponse? = null

    /**
     * A request that the engine wasn't able to handle that resulted in an error.
     *
     * @param session The engine session that initiated the callback.
     * @param errorType The error that was provided by the engine related to the
     * type of error caused.
     * @param uri The uri that resulted in the error.
     * @return An [ErrorResponse] object containing content to display for the
     * provided error type.
     */
    fun onErrorRequest(session: EngineSession, errorType: ErrorType, uri: String?): ErrorResponse? = null

    /**
     * Returns whether or not this [RequestInterceptor] should intercept load
     * requests initiated by the app (via direct calls to [EngineSession.loadUrl]).
     * All other requests triggered by users interacting with web content
     * (e.g. following links) or redirects will always be intercepted.
     *
     * @return true if app initiated requests should be intercepted,
     * otherwise false. Defaults to false.
     */
    fun interceptsAppInitiatedRequests() = false
}