1 /*
2  * Copyright (C) 2022 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.pm.pkg.component;
18 
19 import static com.android.server.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
20 
21 import android.annotation.NonNull;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.pm.parsing.result.ParseInput;
25 import android.content.pm.parsing.result.ParseResult;
26 import android.content.res.Resources;
27 import android.content.res.TypedArray;
28 import android.content.res.XmlResourceParser;
29 import android.os.PatternMatcher;
30 import android.util.Slog;
31 import android.util.TypedValue;
32 
33 import com.android.internal.R;
34 import com.android.server.pm.pkg.parsing.ParsingPackage;
35 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
36 import com.android.server.pm.pkg.parsing.ParsingUtils;
37 
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 
41 import java.io.IOException;
42 import java.util.Iterator;
43 
44 /** @hide */
45 public class ParsedIntentInfoUtils {
46 
47     private static final String TAG = ParsingUtils.TAG;
48 
49     public static final boolean DEBUG = false;
50 
51     @NonNull
parseIntentInfo(String className, ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs, boolean allowAutoVerify, ParseInput input)52     public static ParseResult<ParsedIntentInfoImpl> parseIntentInfo(String className,
53             ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
54             boolean allowAutoVerify, ParseInput input)
55             throws XmlPullParserException, IOException {
56         ParsedIntentInfoImpl intentInfo = new ParsedIntentInfoImpl();
57         IntentFilter intentFilter = intentInfo.getIntentFilter();
58         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestIntentFilter);
59         try {
60             intentFilter.setPriority(
61                     sa.getInt(R.styleable.AndroidManifestIntentFilter_priority, 0));
62             intentFilter.setOrder(sa.getInt(R.styleable.AndroidManifestIntentFilter_order, 0));
63 
64             TypedValue v = sa.peekValue(R.styleable.AndroidManifestIntentFilter_label);
65             if (v != null) {
66                 intentInfo.setLabelRes(v.resourceId);
67                 if (v.resourceId == 0) {
68                     intentInfo.setNonLocalizedLabel(v.coerceToString());
69                 }
70             }
71 
72             if (ParsingPackageUtils.sUseRoundIcon) {
73                 intentInfo.setIcon(sa.getResourceId(
74                         R.styleable.AndroidManifestIntentFilter_roundIcon, 0));
75             }
76 
77             if (intentInfo.getIcon() == 0) {
78                 intentInfo.setIcon(
79                         sa.getResourceId(R.styleable.AndroidManifestIntentFilter_icon, 0));
80             }
81 
82             if (allowAutoVerify) {
83                 intentFilter.setAutoVerify(sa.getBoolean(
84                         R.styleable.AndroidManifestIntentFilter_autoVerify,
85                         false));
86             }
87         } finally {
88             sa.recycle();
89         }
90         final int depth = parser.getDepth();
91         int type;
92         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
93                 && (type != XmlPullParser.END_TAG
94                 || parser.getDepth() > depth)) {
95             if (type != XmlPullParser.START_TAG) {
96                 continue;
97             }
98 
99             final ParseResult result;
100             String nodeName = parser.getName();
101             switch (nodeName) {
102                 case "action": {
103                     String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
104                     if (value == null) {
105                         result = input.error("No value supplied for <android:name>");
106                     } else if (value.isEmpty()) {
107                         intentFilter.addAction(value);
108                         // Prior to R, this was not a failure
109                         result = input.deferError("No value supplied for <android:name>",
110                                 ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
111                     } else {
112                         intentFilter.addAction(value);
113                         result = input.success(null);
114                     }
115                     break;
116                 }
117                 case "category": {
118                     String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
119                     if (value == null) {
120                         result = input.error("No value supplied for <android:name>");
121                     } else if (value.isEmpty()) {
122                         intentFilter.addCategory(value);
123                         // Prior to R, this was not a failure
124                         result = input.deferError("No value supplied for <android:name>",
125                                 ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
126                     } else {
127                         intentFilter.addCategory(value);
128                         result = input.success(null);
129                     }
130                     break;
131                 }
132                 case "data":
133                     result = parseData(intentInfo, res, parser, allowGlobs, input);
134                     break;
135                 default:
136                     result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input);
137                     break;
138             }
139 
140             if (result.isError()) {
141                 return input.error(result);
142             }
143         }
144 
145         intentInfo.setHasDefault(intentFilter.hasCategory(Intent.CATEGORY_DEFAULT));
146 
147         if (DEBUG) {
148             final StringBuilder cats = new StringBuilder("Intent d=");
149             cats.append(intentInfo.isHasDefault());
150             cats.append(", cat=");
151 
152             final Iterator<String> it = intentFilter.categoriesIterator();
153             if (it != null) {
154                 while (it.hasNext()) {
155                     cats.append(' ');
156                     cats.append(it.next());
157                 }
158             }
159             Slog.d(TAG, cats.toString());
160         }
161 
162         return input.success(intentInfo);
163     }
164 
165     @NonNull
parseData(ParsedIntentInfo intentInfo, Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input)166     private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo,
167             Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
168         IntentFilter intentFilter = intentInfo.getIntentFilter();
169         TypedArray sa = resources.obtainAttributes(parser, R.styleable.AndroidManifestData);
170         try {
171             String str = sa.getNonConfigurationString(
172                     R.styleable.AndroidManifestData_mimeType, 0);
173             if (str != null) {
174                 try {
175                     intentFilter.addDataType(str);
176                 } catch (IntentFilter.MalformedMimeTypeException e) {
177                     return input.error(e.toString());
178                 }
179             }
180 
181             str = sa.getNonConfigurationString(
182                     R.styleable.AndroidManifestData_mimeGroup, 0);
183             if (str != null) {
184                 intentFilter.addMimeGroup(str);
185             }
186 
187             str = sa.getNonConfigurationString(
188                     R.styleable.AndroidManifestData_scheme, 0);
189             if (str != null) {
190                 intentFilter.addDataScheme(str);
191             }
192 
193             str = sa.getNonConfigurationString(
194                     R.styleable.AndroidManifestData_ssp, 0);
195             if (str != null) {
196                 intentFilter.addDataSchemeSpecificPart(str,
197                         PatternMatcher.PATTERN_LITERAL);
198             }
199 
200             str = sa.getNonConfigurationString(
201                     R.styleable.AndroidManifestData_sspPrefix, 0);
202             if (str != null) {
203                 intentFilter.addDataSchemeSpecificPart(str,
204                         PatternMatcher.PATTERN_PREFIX);
205             }
206 
207             str = sa.getNonConfigurationString(
208                     R.styleable.AndroidManifestData_sspPattern, 0);
209             if (str != null) {
210                 if (!allowGlobs) {
211                     return input.error(
212                             "sspPattern not allowed here; ssp must be literal");
213                 }
214                 intentFilter.addDataSchemeSpecificPart(str,
215                         PatternMatcher.PATTERN_SIMPLE_GLOB);
216             }
217 
218             str = sa.getNonConfigurationString(
219                     R.styleable.AndroidManifestData_sspAdvancedPattern, 0);
220             if (str != null) {
221                 if (!allowGlobs) {
222                     return input.error(
223                             "sspAdvancedPattern not allowed here; ssp must be literal");
224                 }
225                 intentFilter.addDataSchemeSpecificPart(str,
226                         PatternMatcher.PATTERN_ADVANCED_GLOB);
227             }
228 
229             str = sa.getNonConfigurationString(
230                     R.styleable.AndroidManifestData_sspSuffix, 0);
231             if (str != null) {
232                 intentFilter.addDataSchemeSpecificPart(str,
233                         PatternMatcher.PATTERN_SUFFIX);
234             }
235 
236 
237             String host = sa.getNonConfigurationString(
238                     R.styleable.AndroidManifestData_host, 0);
239             String port = sa.getNonConfigurationString(
240                     R.styleable.AndroidManifestData_port, 0);
241             if (host != null) {
242                 intentFilter.addDataAuthority(host, port);
243             }
244 
245             str = sa.getNonConfigurationString(
246                     R.styleable.AndroidManifestData_path, 0);
247             if (str != null) {
248                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
249             }
250 
251             str = sa.getNonConfigurationString(
252                     R.styleable.AndroidManifestData_pathPrefix, 0);
253             if (str != null) {
254                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
255             }
256 
257             str = sa.getNonConfigurationString(
258                     R.styleable.AndroidManifestData_pathPattern, 0);
259             if (str != null) {
260                 if (!allowGlobs) {
261                     return input.error(
262                             "pathPattern not allowed here; path must be literal");
263                 }
264                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
265             }
266 
267             str = sa.getNonConfigurationString(
268                     R.styleable.AndroidManifestData_pathAdvancedPattern, 0);
269             if (str != null) {
270                 if (!allowGlobs) {
271                     return input.error(
272                             "pathAdvancedPattern not allowed here; path must be literal");
273                 }
274                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
275             }
276 
277             str = sa.getNonConfigurationString(
278                     R.styleable.AndroidManifestData_pathSuffix, 0);
279             if (str != null) {
280                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_SUFFIX);
281             }
282 
283 
284             return input.success(null);
285         } finally {
286             sa.recycle();
287         }
288     }
289 }
290