chromium/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TrustedCdn.java

// Copyright 2019 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.tab;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.jni_zero.NativeMethods;

import org.chromium.base.UnownedUserData;
import org.chromium.base.UnownedUserDataKey;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.components.security_state.SecurityStateModel;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.url.GURL;

/** Provides a trusted CDN publisher URL for the current web contents in a Tab. */
public class TrustedCdn extends TabWebContentsUserData {
    @VisibleForTesting public static final Class<TrustedCdn> USER_DATA_KEY = TrustedCdn.class;

    private final Tab mTab;
    private final long mNativeTrustedCdn;

    /**
     * UnownedUserData shared across all tabs to get the publisher url visibility.
     * This hangs off of an activity via WindowAndroid.
     */
    public static interface PublisherUrlVisibility extends UnownedUserData {
        /** The key for accessing this object on an {@link UnownedUserDataHost}. */
        public static final UnownedUserDataKey<PublisherUrlVisibility> KEY =
                new UnownedUserDataKey<>(PublisherUrlVisibility.class);

        /**
         * Get the Activity's {@link PublisherUrlVisibility} from the provided
         * {@link WindowAndroid}.
         * @param window The window to get the validator from.
         * @return The Activity's {@link PublisherUrlVisibility}.
         */
        public static @Nullable PublisherUrlVisibility from(WindowAndroid window) {
            return KEY.retrieveDataFromHost(window.getUnownedUserDataHost());
        }

        /**
         * Make this instance of PublisherUrlVisibility available through the activity's window.
         * @param window A {@link WindowAndroid} to attach to.
         * @param validator The {@link PublisherUrlVisibility} to attach.
         */
        public static void attach(WindowAndroid window, PublisherUrlVisibility validator) {
            KEY.attachToHost(window.getUnownedUserDataHost(), validator);
        }

        /**
         * Detach the provided PublisherUrlVisibility from any host it is associated with.
         * @param validator The {@link PublisherUrlVisibility} to detach.
         */
        public static void detach(PublisherUrlVisibility validator) {
            KEY.detachFromAllHosts(validator);
        }

        /**
         * Whether the encomapssing activity can show the publisher URL from a trusted CDN.
         * @param tab Tab object currently being shown.
         * @return {@code true} if the publisher URL from a trusted CDN can be shown.
         */
        boolean canShowPublisherUrl(Tab tab);
    }

    /**
     * @return The publisher URL if the current page is hosted on a trusted CDN, or null otherwise
     */
    public static @Nullable GURL getPublisherUrl(@Nullable Tab tab) {
        TrustedCdn cdn = get(tab);
        return cdn != null ? cdn.getPublisherUrl() : null;
    }

    /**
     * @param tab Tab object currently being shown.
     * @return The name of the publisher of the content if it can be reliably extracted, or null
     *         otherwise.
     */
    public static String getContentPublisher(Tab tab) {
        if (tab == null) return null;

        GURL publisherUrl = TrustedCdn.getPublisherUrl(tab);
        if (publisherUrl != null) {
            return UrlUtilities.extractPublisherFromPublisherUrl(publisherUrl);
        }

        return null;
    }

    static TrustedCdn from(@NonNull Tab tab) {
        TrustedCdn trustedCdn = get(tab);
        if (trustedCdn == null) {
            trustedCdn = tab.getUserDataHost().setUserData(USER_DATA_KEY, new TrustedCdn(tab));
        }
        return trustedCdn;
    }

    public static void initForTesting(@NonNull Tab tab) {
        from(tab);
    }

    private static TrustedCdn get(@Nullable Tab tab) {
        return tab != null ? tab.getUserDataHost().getUserData(USER_DATA_KEY) : null;
    }

    private TrustedCdn(Tab tab) {
        super(tab);
        mTab = tab;
        mNativeTrustedCdn = TrustedCdnJni.get().init(TrustedCdn.this);
    }

    @Override
    public void initWebContents(WebContents webContents) {
        TrustedCdnJni.get().setWebContents(mNativeTrustedCdn, TrustedCdn.this, webContents);
    }

    @Override
    public void cleanupWebContents(WebContents webContents) {
        TrustedCdnJni.get().resetWebContents(mNativeTrustedCdn, TrustedCdn.this);
    }

    @Override
    public void destroyInternal() {
        TrustedCdnJni.get().onDestroyed(mNativeTrustedCdn, TrustedCdn.this);
    }

    @Nullable
    @VisibleForTesting
    public GURL getPublisherUrl() {
        WebContents webContents = mTab.getWebContents();
        if (webContents == null) return null;

        WindowAndroid windowAndroid = webContents.getTopLevelNativeWindow();
        if (windowAndroid == null) return null;

        PublisherUrlVisibility publisherUrlVisibility = PublisherUrlVisibility.from(windowAndroid);
        if (publisherUrlVisibility == null || !publisherUrlVisibility.canShowPublisherUrl(mTab)) {
            return null;
        }
        int level = SecurityStateModel.getSecurityLevelForWebContents(mTab.getWebContents());
        if (level == ConnectionSecurityLevel.DANGEROUS) return null;
        GURL publisherUrl = TrustedCdnJni.get().getPublisherUrl(mNativeTrustedCdn);
        return publisherUrl.isValid() ? publisherUrl : null;
    }

    @NativeMethods
    public interface Natives {
        long init(TrustedCdn caller);

        void onDestroyed(long nativeTrustedCdn, TrustedCdn caller);

        void setWebContents(long nativeTrustedCdn, TrustedCdn caller, WebContents webContents);

        void resetWebContents(long nativeTrustedCdn, TrustedCdn caller);

        GURL getPublisherUrl(long nativeTrustedCdn);
    }
}