blob: 91721d9dbafd09ea91cd014d1283ba8b074dd85e [file] [log] [blame]
// 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.base.shared_preferences;
import android.text.TextUtils;
import org.chromium.build.annotations.CheckDiscard;
import java.util.Arrays;
import java.util.regex.Pattern;
/**
* Class that checks if given Strings are valid SharedPreferences keys to use.
*
* Checks that:
* 1. Keys are registered as "in use".
* 2. The key format is valid, either:
* - "Chrome.[Feature].[Key]"
* - "Chrome.[Feature].[KeyPrefix].[Suffix]"
* - Legacy key prior to this restriction
*/
@CheckDiscard("Validation is performed in tests and in debug builds.")
class StrictPreferenceKeyChecker implements PreferenceKeyChecker {
// The dynamic part cannot be empty, but otherwise it is anything that does not contain
// stars.
private static final Pattern DYNAMIC_PART_PATTERN = Pattern.compile("[^\\*]+");
private final PreferenceKeyRegistry mRegistry;
StrictPreferenceKeyChecker(PreferenceKeyRegistry registry) {
mRegistry = registry;
}
/**
* Check that the |key| passed is in use.
* @throws RuntimeException if the key is not in use.
*/
@Override
public void checkIsKeyInUse(String key) {
if (!isKeyInUse(key)) {
throw new RuntimeException(
"SharedPreferences key \""
+ key
+ "\" is not registered in PreferenceKeyRegistry.mKeysInUse");
}
KnownPreferenceKeyRegistries.onRegistryUsed(mRegistry);
}
/**
* @return Whether |key| is in use.
*/
private boolean isKeyInUse(String key) {
// For non-dynamic legacy keys, a simple map check is enough.
if (mRegistry.mLegacyFormatKeys.contains(key)) {
return true;
}
// For dynamic legacy keys, each legacy prefix has to be checked.
for (KeyPrefix prefix : mRegistry.mLegacyPrefixes) {
if (prefix.hasGenerated(key)) {
return true;
}
}
// If not a format-legacy key, assume it follows the format and find out if it is
// a prefixed key.
String[] parts = key.split("\\.", 4);
if (parts.length < 3) return false;
boolean isPrefixed = parts.length >= 4;
if (isPrefixed) {
// Key with prefix in format "Chrome.[Feature].[KeyPrefix].[Suffix]".
// Check if its prefix is registered in |mKeysInUse|.
String prefixFormat =
TextUtils.join(".", Arrays.asList(parts[0], parts[1], parts[2], "*"));
if (!mRegistry.mKeysInUse.contains(prefixFormat)) return false;
// Check if the dynamic part is correctly formed.
String dynamicPart = parts[3];
return DYNAMIC_PART_PATTERN.matcher(dynamicPart).matches();
} else {
// Regular key in format "Chrome.[Feature].[Key]" which was not present in |mKeysInUse|.
// Just check if it is in [keys in use].
return mRegistry.mKeysInUse.contains(key);
}
}
@Override
public void checkIsPrefixInUse(KeyPrefix prefix) {
if (mRegistry.mLegacyPrefixes.contains(prefix)) {
return;
}
if (mRegistry.mKeysInUse.contains(prefix.pattern())) {
return;
}
throw new RuntimeException(
"SharedPreferences KeyPrefix \""
+ prefix.pattern()
+ "\" is not registered in PreferenceKeyRegistry.mKeysInUse()");
}
}