1 /*
2  * Copyright (C) 2020 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.content.pm.parsing.component;
18 
19 import static android.content.pm.parsing.component.ComponentParseUtils.flag;
20 
21 import android.annotation.NonNull;
22 import android.content.pm.PackageParser;
23 import android.content.pm.PathPermission;
24 import android.content.pm.ProviderInfo;
25 import android.content.pm.parsing.ParsingPackage;
26 import android.content.pm.parsing.ParsingUtils;
27 import android.content.pm.parsing.result.ParseInput;
28 import android.content.pm.parsing.result.ParseResult;
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.content.res.XmlResourceParser;
32 import android.os.Build;
33 import android.os.PatternMatcher;
34 import android.util.Slog;
35 
36 import com.android.internal.R;
37 
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 
41 import java.io.IOException;
42 import java.util.Objects;
43 
44 /** @hide */
45 public class ParsedProviderUtils {
46 
47     private static final String TAG = ParsingUtils.TAG;
48 
49     @NonNull
parseProvider(String[] separateProcesses, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, boolean useRoundIcon, ParseInput input)50     public static ParseResult<ParsedProvider> parseProvider(String[] separateProcesses,
51             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
52             boolean useRoundIcon, ParseInput input)
53             throws IOException, XmlPullParserException {
54         String authority;
55         boolean visibleToEphemeral;
56 
57         final int targetSdkVersion = pkg.getTargetSdkVersion();
58         final String packageName = pkg.getPackageName();
59         final ParsedProvider provider = new ParsedProvider();
60         final String tag = parser.getName();
61 
62         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProvider);
63         try {
64             ParseResult<ParsedProvider> result =
65                     ParsedMainComponentUtils.parseMainComponent(provider, tag, separateProcesses,
66                     pkg, sa, flags, useRoundIcon, input,
67                     R.styleable.AndroidManifestProvider_banner,
68                     R.styleable.AndroidManifestProvider_description,
69                     R.styleable.AndroidManifestProvider_directBootAware,
70                     R.styleable.AndroidManifestProvider_enabled,
71                     R.styleable.AndroidManifestProvider_icon,
72                     R.styleable.AndroidManifestProvider_label,
73                     R.styleable.AndroidManifestProvider_logo,
74                     R.styleable.AndroidManifestProvider_name,
75                     R.styleable.AndroidManifestProvider_process,
76                     R.styleable.AndroidManifestProvider_roundIcon,
77                     R.styleable.AndroidManifestProvider_splitName,
78                     R.styleable.AndroidManifestProvider_attributionTags);
79             if (result.isError()) {
80                 return result;
81             }
82 
83             authority = sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_authorities, 0);
84 
85             // For compatibility, applications targeting API level 16 or lower
86             // should have their content providers exported by default, unless they
87             // specify otherwise.
88             provider.exported = sa.getBoolean(R.styleable.AndroidManifestProvider_exported,
89                     targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1);
90 
91             provider.syncable = sa.getBoolean(R.styleable.AndroidManifestProvider_syncable, false);
92 
93             String permission = sa.getNonConfigurationString(
94                     R.styleable.AndroidManifestProvider_permission, 0);
95             String readPermission = sa.getNonConfigurationString(
96                     R.styleable.AndroidManifestProvider_readPermission, 0);
97             if (readPermission == null) {
98                 readPermission = permission;
99             }
100             if (readPermission == null) {
101                 provider.setReadPermission(pkg.getPermission());
102             } else {
103                 provider.setReadPermission(readPermission);
104             }
105             String writePermission = sa.getNonConfigurationString(
106                     R.styleable.AndroidManifestProvider_writePermission, 0);
107             if (writePermission == null) {
108                 writePermission = permission;
109             }
110             if (writePermission == null) {
111                 provider.setWritePermission(pkg.getPermission());
112             } else {
113                 provider.setWritePermission(writePermission);
114             }
115 
116             provider.grantUriPermissions = sa.getBoolean(R.styleable.AndroidManifestProvider_grantUriPermissions, false);
117             provider.forceUriPermissions = sa.getBoolean(R.styleable.AndroidManifestProvider_forceUriPermissions, false);
118             provider.multiProcess = sa.getBoolean(R.styleable.AndroidManifestProvider_multiprocess, false);
119             provider.initOrder = sa.getInt(R.styleable.AndroidManifestProvider_initOrder, 0);
120 
121             provider.flags |= flag(ProviderInfo.FLAG_SINGLE_USER, R.styleable.AndroidManifestProvider_singleUser, sa);
122 
123             visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
124             if (visibleToEphemeral) {
125                 provider.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP;
126                 pkg.setVisibleToInstantApps(true);
127             }
128         } finally {
129             sa.recycle();
130         }
131 
132         if (pkg.isCantSaveState()) {
133             // A heavy-weight application can not have providers in its main process
134             if (Objects.equals(provider.getProcessName(), packageName)) {
135                 return input.error("Heavy-weight applications can not have providers"
136                         + " in main process");
137             }
138         }
139 
140         if (authority == null) {
141             return input.error("<provider> does not include authorities attribute");
142         }
143         if (authority.length() <= 0) {
144             return input.error("<provider> has empty authorities attribute");
145         }
146         provider.setAuthority(authority);
147 
148         return parseProviderTags(pkg, tag, res, parser, visibleToEphemeral, provider, input);
149     }
150 
151     @NonNull
parseProviderTags(ParsingPackage pkg, String tag, Resources res, XmlResourceParser parser, boolean visibleToEphemeral, ParsedProvider provider, ParseInput input)152     private static ParseResult<ParsedProvider> parseProviderTags(ParsingPackage pkg, String tag,
153             Resources res, XmlResourceParser parser, boolean visibleToEphemeral,
154             ParsedProvider provider, ParseInput input)
155             throws XmlPullParserException, IOException {
156         final int depth = parser.getDepth();
157         int type;
158         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
159                 && (type != XmlPullParser.END_TAG
160                 || parser.getDepth() > depth)) {
161             if (type != XmlPullParser.START_TAG) {
162                 continue;
163             }
164 
165             String name = parser.getName();
166             final ParseResult result;
167             switch (name) {
168                 case "intent-filter":
169                     ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
170                             .parseIntentFilter(provider, pkg, res, parser, visibleToEphemeral,
171                                     true /*allowGlobs*/, false /*allowAutoVerify*/,
172                                     false /*allowImplicitEphemeralVisibility*/,
173                                     false /*failOnNoActions*/, input);
174                     result = intentResult;
175                     if (intentResult.isSuccess()) {
176                         ParsedIntentInfo intent = intentResult.getResult();
177                         provider.order = Math.max(intent.getOrder(), provider.order);
178                         provider.addIntent(intent);
179                     }
180                     break;
181                 case "meta-data":
182                     result = ParsedComponentUtils.addMetaData(provider, pkg, res, parser, input);
183                     break;
184                 case "property":
185                     result = ParsedComponentUtils.addProperty(provider, pkg, res, parser, input);
186                     break;
187                 case "grant-uri-permission": {
188                     result = parseGrantUriPermission(provider, pkg, res, parser, input);
189                     break;
190                 }
191                 case "path-permission": {
192                     result = parsePathPermission(provider, pkg, res, parser, input);
193                     break;
194                 }
195                 default:
196                     result = ParsingUtils.unknownTag(tag, pkg, parser, input);
197                     break;
198             }
199 
200             if (result.isError()) {
201                 return input.error(result);
202             }
203         }
204 
205         return input.success(provider);
206     }
207 
208     @NonNull
parseGrantUriPermission(ParsedProvider provider, ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input)209     private static ParseResult<ParsedProvider> parseGrantUriPermission(ParsedProvider provider,
210             ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) {
211         TypedArray sa = resources.obtainAttributes(parser,
212                 R.styleable.AndroidManifestGrantUriPermission);
213         try {
214             String name = parser.getName();
215             // Pattern has priority over pre/suffix over literal path
216             PatternMatcher pa = null;
217             String str = sa.getNonConfigurationString(
218                     R.styleable.AndroidManifestGrantUriPermission_pathAdvancedPattern, 0);
219             if (str != null) {
220                 pa = new PatternMatcher(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
221             } else {
222                 str = sa.getNonConfigurationString(
223                         R.styleable.AndroidManifestGrantUriPermission_pathPattern, 0);
224                 if (str != null) {
225                     pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
226                 } else {
227                     str = sa.getNonConfigurationString(
228                             R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 0);
229                     if (str != null) {
230                         pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX);
231                     } else {
232                         str = sa.getNonConfigurationString(
233                                 R.styleable.AndroidManifestGrantUriPermission_pathSuffix, 0);
234                         if (str != null) {
235                             pa = new PatternMatcher(str, PatternMatcher.PATTERN_SUFFIX);
236                         } else {
237                             str = sa.getNonConfigurationString(
238                                     R.styleable.AndroidManifestGrantUriPermission_path, 0);
239                             if (str != null) {
240                                 pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL);
241                             }
242                         }
243                     }
244                 }
245             }
246 
247             if (pa != null) {
248                 if (provider.uriPermissionPatterns == null) {
249                     provider.uriPermissionPatterns = new PatternMatcher[1];
250                     provider.uriPermissionPatterns[0] = pa;
251                 } else {
252                     final int N = provider.uriPermissionPatterns.length;
253                     PatternMatcher[] newp = new PatternMatcher[N + 1];
254                     System.arraycopy(provider.uriPermissionPatterns, 0, newp, 0, N);
255                     newp[N] = pa;
256                     provider.uriPermissionPatterns = newp;
257                 }
258                 provider.grantUriPermissions = true;
259             } else {
260                 if (PackageParser.RIGID_PARSER) {
261                     return input.error("No path, pathPrefix, or pathPattern for <path-permission>");
262                 }
263 
264                 Slog.w(TAG, "Unknown element under <path-permission>: " + name + " at "
265                         + pkg.getBaseApkPath() + " " + parser.getPositionDescription());
266             }
267 
268             return input.success(provider);
269         } finally {
270             sa.recycle();
271         }
272     }
273 
274     @NonNull
parsePathPermission(ParsedProvider provider, ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input)275     private static ParseResult<ParsedProvider> parsePathPermission(ParsedProvider provider,
276             ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) {
277         TypedArray sa = resources.obtainAttributes(parser,
278                 R.styleable.AndroidManifestPathPermission);
279         try {
280             String name = parser.getName();
281 
282             String permission = sa.getNonConfigurationString(
283                     R.styleable.AndroidManifestPathPermission_permission, 0);
284             String readPermission = sa.getNonConfigurationString(
285                     R.styleable.AndroidManifestPathPermission_readPermission, 0);
286             if (readPermission == null) {
287                 readPermission = permission;
288             }
289             String writePermission = sa.getNonConfigurationString(
290                     R.styleable.AndroidManifestPathPermission_writePermission, 0);
291             if (writePermission == null) {
292                 writePermission = permission;
293             }
294 
295             boolean havePerm = false;
296             if (readPermission != null) {
297                 readPermission = readPermission.intern();
298                 havePerm = true;
299             }
300             if (writePermission != null) {
301                 writePermission = writePermission.intern();
302                 havePerm = true;
303             }
304 
305             if (!havePerm) {
306                 if (PackageParser.RIGID_PARSER) {
307                     return input.error(
308                             "No readPermission or writePermission for <path-permission>");
309                 }
310                 Slog.w(TAG, "No readPermission or writePermission for <path-permission>: "
311                         + name + " at " + pkg.getBaseApkPath() + " "
312                         + parser.getPositionDescription());
313                 return input.success(provider);
314             }
315 
316             // Advanced has priority over simply over prefix over literal
317             PathPermission pa = null;
318             String path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_pathAdvancedPattern, 0);
319             if (path != null) {
320                 pa = new PathPermission(path, PatternMatcher.PATTERN_ADVANCED_GLOB, readPermission,
321                         writePermission);
322             } else {
323                 path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_pathPattern, 0);
324                 if (path != null) {
325                     pa = new PathPermission(path, PatternMatcher.PATTERN_SIMPLE_GLOB,
326                             readPermission, writePermission);
327                 } else {
328                     path = sa.getNonConfigurationString(
329                             R.styleable.AndroidManifestPathPermission_pathPrefix, 0);
330                     if (path != null) {
331                         pa = new PathPermission(path, PatternMatcher.PATTERN_PREFIX, readPermission,
332                                 writePermission);
333                     } else {
334                         path = sa.getNonConfigurationString(
335                                 R.styleable.AndroidManifestPathPermission_pathSuffix, 0);
336                         if (path != null) {
337                             pa = new PathPermission(path, PatternMatcher.PATTERN_SUFFIX,
338                                     readPermission, writePermission);
339                         } else {
340                             path = sa.getNonConfigurationString(
341                                     R.styleable.AndroidManifestPathPermission_path, 0);
342                             if (path != null) {
343                                 pa = new PathPermission(path, PatternMatcher.PATTERN_LITERAL,
344                                         readPermission, writePermission);
345                             }
346                         }
347                     }
348                 }
349             }
350 
351             if (pa != null) {
352                 if (provider.pathPermissions == null) {
353                     provider.pathPermissions = new PathPermission[1];
354                     provider.pathPermissions[0] = pa;
355                 } else {
356                     final int N = provider.pathPermissions.length;
357                     PathPermission[] newp = new PathPermission[N + 1];
358                     System.arraycopy(provider.pathPermissions, 0, newp, 0, N);
359                     newp[N] = pa;
360                     provider.pathPermissions = newp;
361                 }
362             } else {
363                 if (PackageParser.RIGID_PARSER) {
364                     return input.error(
365                             "No path, pathPrefix, or pathPattern for <path-permission>");
366                 }
367 
368                 Slog.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>: "
369                         + name + " at " + pkg.getBaseApkPath()
370                         + " "
371                         + parser.getPositionDescription());
372             }
373 
374             return input.success(provider);
375         } finally {
376             sa.recycle();
377         }
378     }
379 }
380