1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.sdksandbox.helpers;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.util.ArraySet;
22 
23 /**
24  * Helper class for String operations.
25  *
26  * @hide
27  */
28 public class StringHelper {
29     /**
30      * Checks if a given input string matches any of the given patterns. Each pattern can contain
31      * wildcards in the form of an asterisk. This wildcard should match 0 or more number of
32      * characters in the input string.
33      */
doesInputMatchAnyWildcardPattern( @onNull ArraySet<String> patterns, @Nullable String input)34     public static boolean doesInputMatchAnyWildcardPattern(
35             @NonNull ArraySet<String> patterns, @Nullable String input) {
36         for (int i = 0; i < patterns.size(); ++i) {
37             if (doesInputMatchWildcardPattern(
38                     patterns.valueAt(i), input, /*matchOnNullInput=*/ false)) {
39                 return true;
40             }
41         }
42         return false;
43     }
44 
45     /**
46      * Checks if a given input string matches the given pattern. The pattern can contain wildcards
47      * in the form of an asterisk. This wildcard should match 0 or more number of characters in the
48      * input string.
49      */
doesInputMatchWildcardPattern( @ullable String pattern, @Nullable String input, boolean matchOnNullInput)50     public static boolean doesInputMatchWildcardPattern(
51             @Nullable String pattern, @Nullable String input, boolean matchOnNullInput) {
52         if (matchOnNullInput && (pattern != null && pattern.equals("*"))) {
53             return true;
54         }
55         if (pattern == null || input == null) {
56             return false;
57         }
58 
59         /*
60          * We split the pattern by the wildcard. It is split with a non-negative limit, indicating
61          * that the pattern is applied as many times as possible e.g. if pattern = "*a*", the split
62          * would be ["","a",""].
63          */
64         // TODO(b/289197372): Optimize by splitting beforehand.
65         String[] patternSubstrings = pattern.split("\\*", -1);
66         int inputMatchStartIndex = 0;
67         for (int i = 0; i < patternSubstrings.length; ++i) {
68             if (i == 0) {
69                 // Verify that the input string starts with the characters present before the first
70                 // wildcard.
71                 if (!input.startsWith(patternSubstrings[i])) {
72                     return false;
73                 }
74                 inputMatchStartIndex = patternSubstrings[i].length();
75             } else if (i == patternSubstrings.length - 1) {
76                 // Verify that the input string (after the point where it's been matched so far)
77                 // matches with the characters after the last wildcard.
78                 if (!input.substring(inputMatchStartIndex).endsWith(patternSubstrings[i])) {
79                     return false;
80                 }
81                 inputMatchStartIndex = input.length();
82             } else {
83                 // For patterns between the first and last wildcard, greedily check if the input
84                 // (after the point where it's been matched so far) matches properly.
85                 int substringIndex = input.indexOf(patternSubstrings[i], inputMatchStartIndex);
86                 if (substringIndex == -1) {
87                     return false;
88                 }
89                 inputMatchStartIndex = substringIndex + patternSubstrings[i].length();
90             }
91         }
92 
93         // Verify that the whole input has been matched.
94         return inputMatchStartIndex >= input.length();
95     }
96 }
97