1 // Copyright 2023 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.shared_preferences; 6 7 import android.text.TextUtils; 8 9 import org.chromium.build.annotations.CheckDiscard; 10 11 import java.util.Arrays; 12 import java.util.regex.Pattern; 13 14 /** 15 * Class that checks if given Strings are valid SharedPreferences keys to use. 16 * 17 * Checks that: 18 * 1. Keys are registered as "in use". 19 * 2. The key format is valid, either: 20 * - "Chrome.[Feature].[Key]" 21 * - "Chrome.[Feature].[KeyPrefix].[Suffix]" 22 * - Legacy key prior to this restriction 23 */ 24 @CheckDiscard("Validation is performed in tests and in debug builds.") 25 class StrictPreferenceKeyChecker implements PreferenceKeyChecker { 26 // The dynamic part cannot be empty, but otherwise it is anything that does not contain 27 // stars. 28 private static final Pattern DYNAMIC_PART_PATTERN = Pattern.compile("[^\\*]+"); 29 30 private final PreferenceKeyRegistry mRegistry; 31 StrictPreferenceKeyChecker(PreferenceKeyRegistry registry)32 StrictPreferenceKeyChecker(PreferenceKeyRegistry registry) { 33 mRegistry = registry; 34 } 35 36 /** 37 * Check that the |key| passed is in use. 38 * @throws RuntimeException if the key is not in use. 39 */ 40 @Override checkIsKeyInUse(String key)41 public void checkIsKeyInUse(String key) { 42 if (!isKeyInUse(key)) { 43 throw new RuntimeException( 44 "SharedPreferences key \"" 45 + key 46 + "\" is not registered in PreferenceKeyRegistry.mKeysInUse"); 47 } 48 KnownPreferenceKeyRegistries.onRegistryUsed(mRegistry); 49 } 50 51 /** 52 * @return Whether |key| is in use. 53 */ isKeyInUse(String key)54 private boolean isKeyInUse(String key) { 55 // For non-dynamic legacy keys, a simple map check is enough. 56 if (mRegistry.mLegacyFormatKeys.contains(key)) { 57 return true; 58 } 59 60 // For dynamic legacy keys, each legacy prefix has to be checked. 61 for (KeyPrefix prefix : mRegistry.mLegacyPrefixes) { 62 if (prefix.hasGenerated(key)) { 63 return true; 64 } 65 } 66 67 // If not a format-legacy key, assume it follows the format and find out if it is 68 // a prefixed key. 69 String[] parts = key.split("\\.", 4); 70 if (parts.length < 3) return false; 71 boolean isPrefixed = parts.length >= 4; 72 73 if (isPrefixed) { 74 // Key with prefix in format "Chrome.[Feature].[KeyPrefix].[Suffix]". 75 76 // Check if its prefix is registered in |mKeysInUse|. 77 String prefixFormat = 78 TextUtils.join(".", Arrays.asList(parts[0], parts[1], parts[2], "*")); 79 if (!mRegistry.mKeysInUse.contains(prefixFormat)) return false; 80 81 // Check if the dynamic part is correctly formed. 82 String dynamicPart = parts[3]; 83 return DYNAMIC_PART_PATTERN.matcher(dynamicPart).matches(); 84 } else { 85 // Regular key in format "Chrome.[Feature].[Key]" which was not present in |mKeysInUse|. 86 // Just check if it is in [keys in use]. 87 return mRegistry.mKeysInUse.contains(key); 88 } 89 } 90 91 @Override checkIsPrefixInUse(KeyPrefix prefix)92 public void checkIsPrefixInUse(KeyPrefix prefix) { 93 if (mRegistry.mLegacyPrefixes.contains(prefix)) { 94 return; 95 } 96 97 if (mRegistry.mKeysInUse.contains(prefix.pattern())) { 98 return; 99 } 100 101 throw new RuntimeException( 102 "SharedPreferences KeyPrefix \"" 103 + prefix.pattern() 104 + "\" is not registered in PreferenceKeyRegistry.mKeysInUse()"); 105 } 106 } 107