1 /*
2  * Copyright (C) 2018 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 android.provider;
18 
19 import android.annotation.Nullable;
20 import android.content.ComponentName;
21 import android.net.Uri;
22 
23 import com.android.internal.util.ArrayUtils;
24 
25 import java.util.Locale;
26 
27 /**
28  * This class provides both interface for validation and common validators
29  * used to ensure Settings have meaningful values.
30  *
31  * @hide
32  */
33 public class SettingsValidators {
34 
35     public static final Validator BOOLEAN_VALIDATOR =
36             new DiscreteValueValidator(new String[] {"0", "1"});
37 
38     public static final Validator ANY_STRING_VALIDATOR = new Validator() {
39         @Override
40         public boolean validate(@Nullable String value) {
41             return true;
42         }
43     };
44 
45     public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
46         @Override
47         public boolean validate(@Nullable String value) {
48             try {
49                 return Integer.parseInt(value) >= 0;
50             } catch (NumberFormatException e) {
51                 return false;
52             }
53         }
54     };
55 
56     public static final Validator ANY_INTEGER_VALIDATOR = new Validator() {
57         @Override
58         public boolean validate(@Nullable String value) {
59             try {
60                 Integer.parseInt(value);
61                 return true;
62             } catch (NumberFormatException e) {
63                 return false;
64             }
65         }
66     };
67 
68     public static final Validator URI_VALIDATOR = new Validator() {
69         @Override
70         public boolean validate(@Nullable String value) {
71             try {
72                 Uri.decode(value);
73                 return true;
74             } catch (IllegalArgumentException e) {
75                 return false;
76             }
77         }
78     };
79 
80     /**
81      * Does not allow a setting to have a null {@link ComponentName}. Use {@link
82      * SettingsValidators#NULLABLE_COMPONENT_NAME_VALIDATOR} instead if a setting can have a
83      * nullable {@link ComponentName}.
84      */
85     public static final Validator COMPONENT_NAME_VALIDATOR = new Validator() {
86         @Override
87         public boolean validate(@Nullable String value) {
88             return value != null && ComponentName.unflattenFromString(value) != null;
89         }
90     };
91 
92     /**
93      * Allows a setting to have a null {@link ComponentName}.
94      */
95     public static final Validator NULLABLE_COMPONENT_NAME_VALIDATOR = new Validator() {
96         @Override
97         public boolean validate(@Nullable String value) {
98             return value == null || COMPONENT_NAME_VALIDATOR.validate(value);
99         }
100     };
101 
102     public static final Validator PACKAGE_NAME_VALIDATOR = new Validator() {
103         @Override
104         public boolean validate(@Nullable String value) {
105             return value != null && isStringPackageName(value);
106         }
107 
108         private boolean isStringPackageName(String value) {
109             // The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers,
110             // and underscores ('_'). However, individual package name parts may only
111             // start with letters.
112             // (https://developer.android.com/guide/topics/manifest/manifest-element.html#package)
113             if (value == null) {
114                 return false;
115             }
116             String[] subparts = value.split("\\.");
117             boolean isValidPackageName = true;
118             for (String subpart : subparts) {
119                 isValidPackageName &= isSubpartValidForPackageName(subpart);
120                 if (!isValidPackageName) break;
121             }
122             return isValidPackageName;
123         }
124 
125         private boolean isSubpartValidForPackageName(String subpart) {
126             if (subpart.length() == 0) return false;
127             boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
128             for (int i = 1; i < subpart.length(); i++) {
129                 isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i))
130                                 || (subpart.charAt(i) == '_'));
131                 if (!isValidSubpart) break;
132             }
133             return isValidSubpart;
134         }
135     };
136 
137     public static final Validator LENIENT_IP_ADDRESS_VALIDATOR = new Validator() {
138         private static final int MAX_IPV6_LENGTH = 45;
139 
140         @Override
141         public boolean validate(@Nullable String value) {
142             if (value == null) {
143                 return false;
144             }
145             return value.length() <= MAX_IPV6_LENGTH;
146         }
147     };
148 
149     public static final Validator LOCALE_VALIDATOR = new Validator() {
150         @Override
151         public boolean validate(@Nullable String value) {
152             if (value == null) {
153                 return false;
154             }
155             Locale[] validLocales = Locale.getAvailableLocales();
156             for (Locale locale : validLocales) {
157                 if (value.equals(locale.toString())) {
158                     return true;
159                 }
160             }
161             return false;
162         }
163     };
164 
165     public interface Validator {
166         /**
167          * Returns whether the input value is valid. Subclasses should handle the case where the
168          * input value is {@code null}.
169          */
validate(@ullable String value)170         boolean validate(@Nullable String value);
171     }
172 
173     public static final class DiscreteValueValidator implements Validator {
174         private final String[] mValues;
175 
DiscreteValueValidator(String[] values)176         public DiscreteValueValidator(String[] values) {
177             mValues = values;
178         }
179 
180         @Override
validate(@ullable String value)181         public boolean validate(@Nullable String value) {
182             return ArrayUtils.contains(mValues, value);
183         }
184     }
185 
186     public static final class InclusiveIntegerRangeValidator implements Validator {
187         private final int mMin;
188         private final int mMax;
189 
InclusiveIntegerRangeValidator(int min, int max)190         public InclusiveIntegerRangeValidator(int min, int max) {
191             mMin = min;
192             mMax = max;
193         }
194 
195         @Override
validate(@ullable String value)196         public boolean validate(@Nullable String value) {
197             try {
198                 final int intValue = Integer.parseInt(value);
199                 return intValue >= mMin && intValue <= mMax;
200             } catch (NumberFormatException e) {
201                 return false;
202             }
203         }
204     }
205 
206     public static final class InclusiveFloatRangeValidator implements Validator {
207         private final float mMin;
208         private final float mMax;
209 
InclusiveFloatRangeValidator(float min, float max)210         public InclusiveFloatRangeValidator(float min, float max) {
211             mMin = min;
212             mMax = max;
213         }
214 
215         @Override
validate(@ullable String value)216         public boolean validate(@Nullable String value) {
217             try {
218                 final float floatValue = Float.parseFloat(value);
219                 return floatValue >= mMin && floatValue <= mMax;
220             } catch (NumberFormatException | NullPointerException e) {
221                 return false;
222             }
223         }
224     }
225 
226     public static final class ComponentNameListValidator implements Validator {
227         private final String mSeparator;
228 
ComponentNameListValidator(String separator)229         public ComponentNameListValidator(String separator) {
230             mSeparator = separator;
231         }
232 
233         @Override
validate(@ullable String value)234         public boolean validate(@Nullable String value) {
235             if (value == null) {
236                 return false;
237             }
238             String[] elements = value.split(mSeparator);
239             for (String element : elements) {
240                 if (!COMPONENT_NAME_VALIDATOR.validate(element)) {
241                     return false;
242                 }
243             }
244             return true;
245         }
246     }
247 
248     public static final class PackageNameListValidator implements Validator {
249         private final String mSeparator;
250 
PackageNameListValidator(String separator)251         public PackageNameListValidator(String separator) {
252             mSeparator = separator;
253         }
254 
255         @Override
validate(@ullable String value)256         public boolean validate(@Nullable String value) {
257             if (value == null) {
258                 return false;
259             }
260             String[] elements = value.split(mSeparator);
261             for (String element : elements) {
262                 if (!PACKAGE_NAME_VALIDATOR.validate(element)) {
263                     return false;
264                 }
265             }
266             return true;
267         }
268     }
269 }
270