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.internal.pm.pkg.component;
18 
19 import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
20 
21 import android.annotation.FlaggedApi;
22 import android.annotation.NonNull;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.UriRelativeFilter;
26 import android.content.UriRelativeFilterGroup;
27 import android.content.pm.Flags;
28 import android.content.pm.parsing.result.ParseInput;
29 import android.content.pm.parsing.result.ParseResult;
30 import android.content.res.Resources;
31 import android.content.res.TypedArray;
32 import android.content.res.XmlResourceParser;
33 import android.os.PatternMatcher;
34 import android.util.Slog;
35 import android.util.TypedValue;
36 
37 import com.android.internal.R;
38 import com.android.internal.pm.pkg.parsing.ParsingPackage;
39 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
40 import com.android.internal.pm.pkg.parsing.ParsingUtils;
41 
42 import org.xmlpull.v1.XmlPullParser;
43 import org.xmlpull.v1.XmlPullParserException;
44 
45 import java.io.IOException;
46 import java.util.Iterator;
47 
48 /** @hide */
49 public class ParsedIntentInfoUtils {
50 
51     private static final String TAG = ParsingUtils.TAG;
52 
53     public static final boolean DEBUG = false;
54 
55     @NonNull
parseIntentInfo(String className, ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs, boolean allowAutoVerify, ParseInput input)56     public static ParseResult<ParsedIntentInfoImpl> parseIntentInfo(String className,
57             ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
58             boolean allowAutoVerify, ParseInput input)
59             throws XmlPullParserException, IOException {
60         ParsedIntentInfoImpl intentInfo = new ParsedIntentInfoImpl();
61         IntentFilter intentFilter = intentInfo.getIntentFilter();
62         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestIntentFilter);
63         try {
64             intentFilter.setPriority(
65                     sa.getInt(R.styleable.AndroidManifestIntentFilter_priority, 0));
66             intentFilter.setOrder(sa.getInt(R.styleable.AndroidManifestIntentFilter_order, 0));
67 
68             TypedValue v = sa.peekValue(R.styleable.AndroidManifestIntentFilter_label);
69             if (v != null) {
70                 intentInfo.setLabelRes(v.resourceId);
71                 if (v.resourceId == 0) {
72                     intentInfo.setNonLocalizedLabel(v.coerceToString());
73                 }
74             }
75 
76             if (ParsingPackageUtils.sUseRoundIcon) {
77                 intentInfo.setIcon(sa.getResourceId(
78                         R.styleable.AndroidManifestIntentFilter_roundIcon, 0));
79             }
80 
81             if (intentInfo.getIcon() == 0) {
82                 intentInfo.setIcon(
83                         sa.getResourceId(R.styleable.AndroidManifestIntentFilter_icon, 0));
84             }
85 
86             if (allowAutoVerify) {
87                 intentFilter.setAutoVerify(sa.getBoolean(
88                         R.styleable.AndroidManifestIntentFilter_autoVerify,
89                         false));
90             }
91         } finally {
92             sa.recycle();
93         }
94         final int depth = parser.getDepth();
95         int type;
96         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
97                 && (type != XmlPullParser.END_TAG
98                 || parser.getDepth() > depth)) {
99             if (type != XmlPullParser.START_TAG) {
100                 continue;
101             }
102             if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
103                 continue;
104             }
105 
106             final ParseResult result;
107             String nodeName = parser.getName();
108             switch (nodeName) {
109                 case "action": {
110                     String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
111                     if (value == null) {
112                         result = input.error("No value supplied for <android:name>");
113                     } else if (value.isEmpty()) {
114                         intentFilter.addAction(value);
115                         // Prior to R, this was not a failure
116                         result = input.deferError("No value supplied for <android:name>",
117                                 ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
118                     } else {
119                         intentFilter.addAction(value);
120                         result = input.success(null);
121                     }
122                     break;
123                 }
124                 case "category": {
125                     String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
126                     if (value == null) {
127                         result = input.error("No value supplied for <android:name>");
128                     } else if (value.isEmpty()) {
129                         intentFilter.addCategory(value);
130                         // Prior to R, this was not a failure
131                         result = input.deferError("No value supplied for <android:name>",
132                                 ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
133                     } else {
134                         intentFilter.addCategory(value);
135                         result = input.success(null);
136                     }
137                     break;
138                 }
139                 case "data":
140                     result = parseData(intentInfo, res, parser, allowGlobs, input);
141                     break;
142                 case "uri-relative-filter-group":
143                     if (Flags.relativeReferenceIntentFilters()) {
144                         result = parseRelRefGroup(intentInfo, pkg, res, parser, allowGlobs, input);
145                         break;
146                     }
147                 default:
148                     result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input);
149                     break;
150             }
151 
152             if (result.isError()) {
153                 return input.error(result);
154             }
155         }
156 
157         intentInfo.setHasDefault(intentFilter.hasCategory(Intent.CATEGORY_DEFAULT));
158 
159         if (DEBUG) {
160             final StringBuilder cats = new StringBuilder("Intent d=");
161             cats.append(intentInfo.isHasDefault());
162             cats.append(", cat=");
163 
164             final Iterator<String> it = intentFilter.categoriesIterator();
165             if (it != null) {
166                 while (it.hasNext()) {
167                     cats.append(' ');
168                     cats.append(it.next());
169                 }
170             }
171             Slog.d(TAG, cats.toString());
172         }
173 
174         return input.success(intentInfo);
175     }
176 
177     @NonNull
178     @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
parseRelRefGroup(ParsedIntentInfo intentInfo, ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs, ParseInput input)179     private static ParseResult<ParsedIntentInfo> parseRelRefGroup(ParsedIntentInfo intentInfo,
180             ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
181             ParseInput input) throws XmlPullParserException, IOException {
182         IntentFilter intentFilter = intentInfo.getIntentFilter();
183         TypedArray sa = res.obtainAttributes(parser,
184                 R.styleable.AndroidManifestUriRelativeFilterGroup);
185         UriRelativeFilterGroup group;
186         try {
187             int action = UriRelativeFilterGroup.ACTION_ALLOW;
188             if (!sa.getBoolean(R.styleable.AndroidManifestUriRelativeFilterGroup_allow, true)) {
189                 action = UriRelativeFilterGroup.ACTION_BLOCK;
190             }
191             group = new UriRelativeFilterGroup(action);
192         } finally {
193             sa.recycle();
194         }
195         final int depth = parser.getDepth();
196         int type;
197         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
198                 && (type != XmlPullParser.END_TAG
199                 || parser.getDepth() > depth)) {
200             if (type != XmlPullParser.START_TAG) {
201                 continue;
202             }
203             if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
204                 continue;
205             }
206 
207             final ParseResult result;
208             String nodeName = parser.getName();
209             switch (nodeName) {
210                 case "data":
211                     result = parseRelRefGroupData(group, res, parser, allowGlobs, input);
212                     break;
213                 default:
214                     result = ParsingUtils.unknownTag("<uri-relative-filter-group>",
215                             pkg, parser, input);
216                     break;
217             }
218 
219             if (result.isError()) {
220                 return input.error(result);
221             }
222         }
223 
224         if (group.getUriRelativeFilters().size() > 0) {
225             intentFilter.addUriRelativeFilterGroup(group);
226         }
227         return input.success(null);
228     }
229 
230     @NonNull
231     @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
parseRelRefGroupData(UriRelativeFilterGroup group, Resources res, XmlResourceParser parser, boolean allowGlobs, ParseInput input)232     private static ParseResult<ParsedIntentInfo> parseRelRefGroupData(UriRelativeFilterGroup group,
233             Resources res, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
234         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestData);
235         try {
236             String str = sa.getNonConfigurationString(
237                     R.styleable.AndroidManifestData_path, 0);
238             if (str != null) {
239                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
240                         PatternMatcher.PATTERN_LITERAL, str));
241             }
242 
243             str = sa.getNonConfigurationString(
244                     R.styleable.AndroidManifestData_pathPrefix, 0);
245             if (str != null) {
246                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
247                         PatternMatcher.PATTERN_PREFIX, str));
248             }
249 
250             str = sa.getNonConfigurationString(
251                     R.styleable.AndroidManifestData_pathPattern, 0);
252             if (str != null) {
253                 if (!allowGlobs) {
254                     return input.error(
255                             "pathPattern not allowed here; path must be literal");
256                 }
257                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
258                         PatternMatcher.PATTERN_SIMPLE_GLOB, str));
259             }
260 
261             str = sa.getNonConfigurationString(
262                     R.styleable.AndroidManifestData_pathAdvancedPattern, 0);
263             if (str != null) {
264                 if (!allowGlobs) {
265                     return input.error(
266                             "pathAdvancedPattern not allowed here; path must be literal");
267                 }
268                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
269                         PatternMatcher.PATTERN_ADVANCED_GLOB, str));
270             }
271 
272             str = sa.getNonConfigurationString(
273                     R.styleable.AndroidManifestData_pathSuffix, 0);
274             if (str != null) {
275                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
276                         PatternMatcher.PATTERN_SUFFIX, str));
277             }
278 
279             str = sa.getNonConfigurationString(
280                     R.styleable.AndroidManifestData_fragment, 0);
281             if (str != null) {
282                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
283                         PatternMatcher.PATTERN_LITERAL, str));
284             }
285 
286             str = sa.getNonConfigurationString(
287                     R.styleable.AndroidManifestData_fragmentPrefix, 0);
288             if (str != null) {
289                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
290                         PatternMatcher.PATTERN_PREFIX, str));
291             }
292 
293             str = sa.getNonConfigurationString(
294                     R.styleable.AndroidManifestData_fragmentPattern, 0);
295             if (str != null) {
296                 if (!allowGlobs) {
297                     return input.error(
298                             "fragmentPattern not allowed here; fragment must be literal");
299                 }
300                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
301                         PatternMatcher.PATTERN_SIMPLE_GLOB, str));
302             }
303 
304             str = sa.getNonConfigurationString(
305                     R.styleable.AndroidManifestData_fragmentAdvancedPattern, 0);
306             if (str != null) {
307                 if (!allowGlobs) {
308                     return input.error(
309                             "fragmentAdvancedPattern not allowed here; fragment must be literal");
310                 }
311                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
312                         PatternMatcher.PATTERN_ADVANCED_GLOB, str));
313             }
314 
315             str = sa.getNonConfigurationString(
316                     R.styleable.AndroidManifestData_fragmentSuffix, 0);
317             if (str != null) {
318                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
319                         PatternMatcher.PATTERN_SUFFIX, str));
320             }
321 
322             str = sa.getNonConfigurationString(
323                     R.styleable.AndroidManifestData_query, 0);
324             if (str != null) {
325                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
326                         PatternMatcher.PATTERN_LITERAL, str));
327             }
328 
329             str = sa.getNonConfigurationString(
330                     R.styleable.AndroidManifestData_queryPrefix, 0);
331             if (str != null) {
332                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
333                         PatternMatcher.PATTERN_PREFIX, str));
334             }
335 
336             str = sa.getNonConfigurationString(
337                     R.styleable.AndroidManifestData_queryPattern, 0);
338             if (str != null) {
339                 if (!allowGlobs) {
340                     return input.error(
341                             "queryPattern not allowed here; query must be literal");
342                 }
343                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
344                         PatternMatcher.PATTERN_SIMPLE_GLOB, str));
345             }
346 
347             str = sa.getNonConfigurationString(
348                     R.styleable.AndroidManifestData_queryAdvancedPattern, 0);
349             if (str != null) {
350                 if (!allowGlobs) {
351                     return input.error(
352                             "queryAdvancedPattern not allowed here; query must be literal");
353                 }
354                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
355                         PatternMatcher.PATTERN_ADVANCED_GLOB, str));
356             }
357 
358             str = sa.getNonConfigurationString(
359                     R.styleable.AndroidManifestData_querySuffix, 0);
360             if (str != null) {
361                 group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
362                         PatternMatcher.PATTERN_SUFFIX, str));
363             }
364 
365             return input.success(null);
366         } finally {
367             sa.recycle();
368         }
369     }
370 
371     @NonNull
parseData(ParsedIntentInfo intentInfo, Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input)372     private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo,
373             Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
374         IntentFilter intentFilter = intentInfo.getIntentFilter();
375         TypedArray sa = resources.obtainAttributes(parser, R.styleable.AndroidManifestData);
376         try {
377             String str = sa.getNonConfigurationString(
378                     R.styleable.AndroidManifestData_mimeType, 0);
379             if (str != null) {
380                 try {
381                     intentFilter.addDataType(str);
382                 } catch (IntentFilter.MalformedMimeTypeException e) {
383                     return input.error(e.toString());
384                 }
385             }
386 
387             str = sa.getNonConfigurationString(
388                     R.styleable.AndroidManifestData_mimeGroup, 0);
389             if (str != null) {
390                 intentFilter.addMimeGroup(str);
391             }
392 
393             str = sa.getNonConfigurationString(
394                     R.styleable.AndroidManifestData_scheme, 0);
395             if (str != null) {
396                 intentFilter.addDataScheme(str);
397             }
398 
399             str = sa.getNonConfigurationString(
400                     R.styleable.AndroidManifestData_ssp, 0);
401             if (str != null) {
402                 intentFilter.addDataSchemeSpecificPart(str,
403                         PatternMatcher.PATTERN_LITERAL);
404             }
405 
406             str = sa.getNonConfigurationString(
407                     R.styleable.AndroidManifestData_sspPrefix, 0);
408             if (str != null) {
409                 intentFilter.addDataSchemeSpecificPart(str,
410                         PatternMatcher.PATTERN_PREFIX);
411             }
412 
413             str = sa.getNonConfigurationString(
414                     R.styleable.AndroidManifestData_sspPattern, 0);
415             if (str != null) {
416                 if (!allowGlobs) {
417                     return input.error(
418                             "sspPattern not allowed here; ssp must be literal");
419                 }
420                 intentFilter.addDataSchemeSpecificPart(str,
421                         PatternMatcher.PATTERN_SIMPLE_GLOB);
422             }
423 
424             str = sa.getNonConfigurationString(
425                     R.styleable.AndroidManifestData_sspAdvancedPattern, 0);
426             if (str != null) {
427                 if (!allowGlobs) {
428                     return input.error(
429                             "sspAdvancedPattern not allowed here; ssp must be literal");
430                 }
431                 intentFilter.addDataSchemeSpecificPart(str,
432                         PatternMatcher.PATTERN_ADVANCED_GLOB);
433             }
434 
435             str = sa.getNonConfigurationString(
436                     R.styleable.AndroidManifestData_sspSuffix, 0);
437             if (str != null) {
438                 intentFilter.addDataSchemeSpecificPart(str,
439                         PatternMatcher.PATTERN_SUFFIX);
440             }
441 
442 
443             String host = sa.getNonConfigurationString(
444                     R.styleable.AndroidManifestData_host, 0);
445             String port = sa.getNonConfigurationString(
446                     R.styleable.AndroidManifestData_port, 0);
447             if (host != null) {
448                 intentFilter.addDataAuthority(host, port);
449             }
450 
451             str = sa.getNonConfigurationString(
452                     R.styleable.AndroidManifestData_path, 0);
453             if (str != null) {
454                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
455             }
456 
457             str = sa.getNonConfigurationString(
458                     R.styleable.AndroidManifestData_pathPrefix, 0);
459             if (str != null) {
460                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
461             }
462 
463             str = sa.getNonConfigurationString(
464                     R.styleable.AndroidManifestData_pathPattern, 0);
465             if (str != null) {
466                 if (!allowGlobs) {
467                     return input.error(
468                             "pathPattern not allowed here; path must be literal");
469                 }
470                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
471             }
472 
473             str = sa.getNonConfigurationString(
474                     R.styleable.AndroidManifestData_pathAdvancedPattern, 0);
475             if (str != null) {
476                 if (!allowGlobs) {
477                     return input.error(
478                             "pathAdvancedPattern not allowed here; path must be literal");
479                 }
480                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
481             }
482 
483             str = sa.getNonConfigurationString(
484                     R.styleable.AndroidManifestData_pathSuffix, 0);
485             if (str != null) {
486                 intentFilter.addDataPath(str, PatternMatcher.PATTERN_SUFFIX);
487             }
488 
489 
490             return input.success(null);
491         } finally {
492             sa.recycle();
493         }
494     }
495 }
496