chromium/android_webview/java/src/org/chromium/android_webview/AwMediaIntegrityApiStatusConfig.java

// Copyright 2023 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.android_webview;

import android.net.Uri;

import org.chromium.android_webview.common.Lifetime;
import org.chromium.android_webview.common.MediaIntegrityApiStatus;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Stores configuration for the WebView Media Integrity API. Configuration is used to set permission
 * levels for origin sites through defaults and override rules. Origin site URIs are matched against
 * these override rules with an {@link AwContentsOriginMatcher}.
 */
@Lifetime.WebView
public class AwMediaIntegrityApiStatusConfig {

    // A URI may match multiple origin patterns but we must return the least permissive
    // option. Hence we look for matches in the following order.
    private static final int[] sStatusByPriority = {
        MediaIntegrityApiStatus.DISABLED,
        MediaIntegrityApiStatus.ENABLED_WITHOUT_APP_IDENTITY,
        MediaIntegrityApiStatus.ENABLED
    };
    private final AwContentsOriginMatcher mRuleValidationMatcher = new AwContentsOriginMatcher();
    private final Map<@MediaIntegrityApiStatus Integer, AwContentsOriginMatcher>
            mPermissionToMatcher;

    private @MediaIntegrityApiStatus int mDefaultStatus;
    private Map<String, @MediaIntegrityApiStatus Integer> mOverrideRulesToPermission;

    public AwMediaIntegrityApiStatusConfig() {
        mDefaultStatus = MediaIntegrityApiStatus.ENABLED;
        mOverrideRulesToPermission = Collections.emptyMap();
        Map<@MediaIntegrityApiStatus Integer, AwContentsOriginMatcher> matcherMap = new HashMap<>();
        for (@MediaIntegrityApiStatus int status : sStatusByPriority) {
            matcherMap.put(status, new AwContentsOriginMatcher());
        }
        mPermissionToMatcher = Collections.unmodifiableMap(matcherMap);
    }

    public void setApiAvailabilityRules(
            @MediaIntegrityApiStatus int defaultStatus,
            Map<String, @MediaIntegrityApiStatus Integer> permissionConfig) {
        mDefaultStatus = defaultStatus;
        String[] badRules =
                mRuleValidationMatcher.updateRuleList(new ArrayList<>(permissionConfig.keySet()));
        if (badRules.length > 0) {
            throw new IllegalArgumentException("Badly formed rules: " + Arrays.toString(badRules));
        }
        mOverrideRulesToPermission = permissionConfig;
        populateMatchersForLookup(permissionConfig);
    }

    @MediaIntegrityApiStatus
    public int getDefaultStatus() {
        return mDefaultStatus;
    }

    public Map<String, @MediaIntegrityApiStatus Integer> getOverrideRules() {
        return mOverrideRulesToPermission;
    }

    @MediaIntegrityApiStatus
    public int getStatusForUri(Uri uri) {
        for (@MediaIntegrityApiStatus int status : sStatusByPriority) {
            if (mPermissionToMatcher.get(status).matchesOrigin(uri)) {
                return status;
            }
        }
        // Uri does not match any override rules
        return mDefaultStatus;
    }

    private void populateMatchersForLookup(
            Map<String, @MediaIntegrityApiStatus Integer> permissionConfig) {
        Map<Integer, List<String>> newPatterns = new HashMap<>();
        for (int status : sStatusByPriority) {
            newPatterns.put(status, new ArrayList());
        }
        for (Map.Entry<String, @MediaIntegrityApiStatus Integer> entry :
                permissionConfig.entrySet()) {
            newPatterns.get(entry.getValue()).add(entry.getKey());
        }
        for (int status : sStatusByPriority) {
            mPermissionToMatcher.get(status).updateRuleList(newPatterns.get(status));
        }
    }
}