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