chromium/chrome/android/javatests/src/org/chromium/chrome/browser/IntentHandlerNativeTest.java

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser;

import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser;

import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.browserservices.verification.ChromeOriginVerifier;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.CustomTabsIntentTestUtils;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.content_public.browser.test.NativeLibraryTestUtils;

/** Unit tests for IntentHandler. These tests require use of the native library. */
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class IntentHandlerNativeTest {
    private static final String GOOGLE_URL = "https://www.google.com";

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);

    @Before
    public void setUp() {
        NativeLibraryTestUtils.loadNativeLibraryNoBrowserProcess();
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testReferrerUrl_extraReferrer() {
        // Check that EXTRA_REFERRER is not accepted with a random URL.
        Intent foreignIntent = new Intent(Intent.ACTION_VIEW);
        foreignIntent.putExtra(Intent.EXTRA_REFERRER, GOOGLE_URL);
        Assert.assertNull(IntentHandler.getReferrerUrlIncludingExtraHeaders(foreignIntent));

        // Check that EXTRA_REFERRER with android-app URL works.
        String appUrl = "android-app://com.application/http/www.application.com";
        Intent appIntent = new Intent(Intent.ACTION_VIEW);
        appIntent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(appUrl));
        Assert.assertEquals(appUrl, IntentHandler.getReferrerUrlIncludingExtraHeaders(appIntent));

        // Ditto, with EXTRA_REFERRER_NAME.
        Intent nameIntent = new Intent(Intent.ACTION_VIEW);
        nameIntent.putExtra(Intent.EXTRA_REFERRER_NAME, appUrl);
        Assert.assertEquals(appUrl, IntentHandler.getReferrerUrlIncludingExtraHeaders(nameIntent));

        // Check that EXTRA_REFERRER with an empty host android-app URL doesn't work.
        appUrl = "android-app:///www.application.com";
        appIntent = new Intent(Intent.ACTION_VIEW);
        appIntent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(appUrl));
        Assert.assertNull(IntentHandler.getReferrerUrlIncludingExtraHeaders(appIntent));

        // Ditto, with EXTRA_REFERRER_NAME.
        nameIntent = new Intent(Intent.ACTION_VIEW);
        nameIntent.putExtra(Intent.EXTRA_REFERRER_NAME, appUrl);
        Assert.assertNull(IntentHandler.getReferrerUrlIncludingExtraHeaders(nameIntent));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testReferrerUrl_extraHeadersInclReferer() {
        // Check that invalid header specified in EXTRA_HEADERS isn't used.
        Bundle bundle = new Bundle();
        bundle.putString("Accept", "application/xhtml+xml");
        bundle.putString("Referer", GOOGLE_URL);
        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
        Assert.assertEquals(
                "Accept: application/xhtml+xml",
                IntentHandler.getExtraHeadersFromIntent(headersIntent));
        Assert.assertNull(IntentHandler.getReferrerUrlIncludingExtraHeaders(headersIntent));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testReferrerUrl_extraHeadersInclRefererMultiple() {
        // Check that invalid header specified in EXTRA_HEADERS isn't used.
        Bundle bundle = new Bundle();
        bundle.putString("Accept", "application/xhtml+xml");
        bundle.putString("Content-Language", "de-DE, en-CA");
        bundle.putString("Referer", GOOGLE_URL);
        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
        Assert.assertEquals(
                "Content-Language: de-DE, en-CA\nAccept: application/xhtml+xml",
                IntentHandler.getExtraHeadersFromIntent(headersIntent));
        Assert.assertNull(IntentHandler.getReferrerUrlIncludingExtraHeaders(headersIntent));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testReferrerUrl_extraHeadersOnlyReferer() {
        // Check that invalid header specified in EXTRA_HEADERS isn't used.
        Bundle bundle = new Bundle();
        bundle.putString("Referer", GOOGLE_URL);
        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
        Assert.assertNull(IntentHandler.getReferrerUrlIncludingExtraHeaders(headersIntent));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testReferrerUrl_extraHeadersAndExtraReferrer() {
        String validReferer = "android-app://package/http/url";
        Bundle bundle = new Bundle();
        bundle.putString("Referer", GOOGLE_URL);
        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
        headersIntent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(validReferer));
        Assert.assertEquals(
                validReferer, IntentHandler.getReferrerUrlIncludingExtraHeaders(headersIntent));
        Assert.assertNull(IntentHandler.getExtraHeadersFromIntent(headersIntent));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testReferrerUrl_extraHeadersValidReferrer() {
        String validReferer = "android-app://package/http/url";
        Bundle bundle = new Bundle();
        bundle.putString("Referer", validReferer);
        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
        Assert.assertEquals(
                validReferer, IntentHandler.getReferrerUrlIncludingExtraHeaders(headersIntent));
        Assert.assertNull(IntentHandler.getExtraHeadersFromIntent(headersIntent));
    }

    @Test
    @SmallTest
    public void testExtraHeadersVerifiedOrigin() throws Exception {
        // Check that non-allowlisted headers from extras are passed
        // when origin is verified.
        Context context = ApplicationProvider.getApplicationContext();
        Intent headersIntent =
                CustomTabsIntentTestUtils.createMinimalCustomTabIntent(
                        context, "https://www.google.com/");

        Bundle headers = new Bundle();
        headers.putString("bearer-token", "Some token");
        headers.putString("redirect-url", "https://www.google.com");
        headersIntent.putExtra(Browser.EXTRA_HEADERS, headers);

        CustomTabsSessionToken token =
                CustomTabsSessionToken.getSessionTokenFromIntent(headersIntent);
        CustomTabsConnection connection = CustomTabsConnection.getInstance();
        connection.newSession(token);
        connection.overridePackageNameForSessionForTesting(token, "app1");
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        ChromeOriginVerifier.addVerificationOverride(
                                "app1",
                                Origin.create(headersIntent.getData()),
                                CustomTabsService.RELATION_USE_AS_ORIGIN));

        String extraHeaders = IntentHandler.getExtraHeadersFromIntent(headersIntent);
        assertTrue(extraHeaders.contains("bearer-token: Some token"));
        assertTrue(extraHeaders.contains("redirect-url: https://www.google.com"));
        ThreadUtils.runOnUiThreadBlocking(
                () -> ChromeOriginVerifier.clearCachedVerificationsForTesting());
    }

    @Test
    @SmallTest
    public void testExtraHeadersNonVerifiedOrigin() throws Exception {
        // Check that non-allowlisted headers from extras are passed
        // when origin is verified.
        Context context = ApplicationProvider.getApplicationContext();
        Intent headersIntent =
                CustomTabsIntentTestUtils.createMinimalCustomTabIntent(
                        context, "https://www.google.com/");

        Bundle headers = new Bundle();
        headers.putString("bearer-token", "Some token");
        headers.putString("redirect-url", "https://www.google.com");
        headersIntent.putExtra(Browser.EXTRA_HEADERS, headers);

        CustomTabsSessionToken token =
                CustomTabsSessionToken.getSessionTokenFromIntent(headersIntent);
        CustomTabsConnection connection = CustomTabsConnection.getInstance();
        connection.newSession(token);
        connection.overridePackageNameForSessionForTesting(token, "app1");
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        ChromeOriginVerifier.addVerificationOverride(
                                "app2",
                                Origin.create(headersIntent.getData()),
                                CustomTabsService.RELATION_USE_AS_ORIGIN));

        String extraHeaders = IntentHandler.getExtraHeadersFromIntent(headersIntent);
        assertNull(extraHeaders);
        ThreadUtils.runOnUiThreadBlocking(
                () -> ChromeOriginVerifier.clearCachedVerificationsForTesting());
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testRemoveChromeCustomHeaderFromExtraIntentHeaders() {
        Bundle bundle = new Bundle();
        bundle.putString("X-Chrome-intent-type", "X-custom-value");
        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
        Assert.assertNull(IntentHandler.getExtraHeadersFromIntent(headersIntent));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testStripNonCorsSafelistedCustomHeader() {
        Bundle bundle = new Bundle();
        bundle.putString("X-Some-Header", "1");
        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
        Assert.assertNull(IntentHandler.getExtraHeadersFromIntent(headersIntent));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Android-AppBase"})
    public void testIgnoreHeaderNewLineInValue() {
        Bundle bundle = new Bundle();
        bundle.putString("sec-ch-ua-full", "\nCookie: secret=cookie");
        Intent headersIntent = new Intent(Intent.ACTION_VIEW);
        headersIntent.putExtra(Browser.EXTRA_HEADERS, bundle);
        Assert.assertNull(IntentHandler.getExtraHeadersFromIntent(headersIntent));
    }
}