1 /*
2  * Copyright (C) 2023 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.parsing;
18 
19 import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.pm.parsing.result.ParseInput;
24 import android.content.pm.parsing.result.ParseResult;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.content.res.XmlResourceParser;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.util.Pair;
31 import android.util.Slog;
32 
33 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
34 import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl;
35 import com.android.internal.util.Parcelling;
36 import com.android.internal.util.XmlUtils;
37 
38 import org.xmlpull.v1.XmlPullParserException;
39 
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.Set;
44 
45 /** @hide **/
46 public class ParsingUtils {
47 
48     public static final String TAG = "PackageParsing";
49 
50     public static final String ANDROID_RES_NAMESPACE = "http://schemas.android.com/apk/res/android";
51 
52     public static final int DEFAULT_MIN_SDK_VERSION = 1;
53     public static final int DEFAULT_MAX_SDK_VERSION = Integer.MAX_VALUE;
54     public static final int DEFAULT_TARGET_SDK_VERSION = 0;
55 
56     public static final int NOT_SET = -1;
57 
58     @Nullable
buildClassName(String pkg, CharSequence clsSeq)59     public static String buildClassName(String pkg, CharSequence clsSeq) {
60         if (clsSeq == null || clsSeq.length() <= 0) {
61             return null;
62         }
63         String cls = clsSeq.toString();
64         char c = cls.charAt(0);
65         if (c == '.') {
66             return pkg + cls;
67         }
68         if (cls.indexOf('.') < 0) {
69             StringBuilder b = new StringBuilder(pkg);
70             b.append('.');
71             b.append(cls);
72             return b.toString();
73         }
74         return cls;
75     }
76 
77     @NonNull
unknownTag(String parentTag, ParsingPackage pkg, XmlResourceParser parser, ParseInput input)78     public static ParseResult unknownTag(String parentTag, ParsingPackage pkg,
79             XmlResourceParser parser, ParseInput input) throws IOException, XmlPullParserException {
80         if (RIGID_PARSER) {
81             return input.error("Bad element under " + parentTag + ": " + parser.getName());
82         }
83         Slog.w(TAG, "Unknown element under " + parentTag + ": "
84                 + parser.getName() + " at " + pkg.getBaseApkPath() + " "
85                 + parser.getPositionDescription());
86         XmlUtils.skipCurrentTag(parser);
87         return input.success(null); // Type doesn't matter
88     }
89 
90     /**
91      * Use with {@link Parcel#writeTypedList(List)} or
92      * {@link #writeInterfaceAsImplList(Parcel, List)}
93      *
94      * @see Parcel#createTypedArrayList(Parcelable.Creator)
95      */
96     @NonNull
createTypedInterfaceList( @onNull Parcel parcel, @NonNull Parcelable.Creator<Impl> creator)97     public static <Interface, Impl extends Interface> List<Interface> createTypedInterfaceList(
98             @NonNull Parcel parcel, @NonNull Parcelable.Creator<Impl> creator) {
99         int size = parcel.readInt();
100         if (size < 0) {
101             return new ArrayList<>();
102         }
103         ArrayList<Interface> list = new ArrayList<Interface>(size);
104         while (size > 0) {
105             list.add(parcel.readTypedObject(creator));
106             size--;
107         }
108         return list;
109     }
110 
111     /**
112      * Use with {@link #createTypedInterfaceList(Parcel, Parcelable.Creator)}.
113      *
114      * Writes a list that can be cast as Parcelable types at runtime.
115      * TODO: Remove with ImmutableList multi-casting support
116      *
117      * @see Parcel#writeTypedList(List)
118      */
119     @NonNull
writeParcelableList(@onNull Parcel parcel, List<?> list)120     public static void writeParcelableList(@NonNull Parcel parcel, List<?> list) {
121         if (list == null) {
122             parcel.writeInt(-1);
123             return;
124         }
125         int size = list.size();
126         int index = 0;
127         parcel.writeInt(size);
128         while (index < size) {
129             parcel.writeTypedObject((Parcelable) list.get(index), 0);
130             index++;
131         }
132     }
133 
134     public static class StringPairListParceler implements
135             Parcelling<List<Pair<String, ParsedIntentInfo>>> {
136 
137         @Override
parcel(List<Pair<String, ParsedIntentInfo>> item, Parcel dest, int parcelFlags)138         public void parcel(List<Pair<String, ParsedIntentInfo>> item, Parcel dest,
139                 int parcelFlags) {
140             if (item == null) {
141                 dest.writeInt(-1);
142                 return;
143             }
144 
145             final int size = item.size();
146             dest.writeInt(size);
147 
148             for (int index = 0; index < size; index++) {
149                 Pair<String, ParsedIntentInfo> pair = item.get(index);
150                 dest.writeString(pair.first);
151                 dest.writeParcelable((Parcelable) pair.second, parcelFlags);
152             }
153         }
154 
155         @Override
unparcel(Parcel source)156         public List<Pair<String, ParsedIntentInfo>> unparcel(Parcel source) {
157             int size = source.readInt();
158             if (size == -1) {
159                 return null;
160             }
161 
162             if (size == 0) {
163                 return new ArrayList<>(0);
164             }
165 
166             final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size);
167             for (int i = 0; i < size; ++i) {
168                 list.add(Pair.create(source.readString(), source.readParcelable(
169                         ParsedIntentInfoImpl.class.getClassLoader(), ParsedIntentInfo.class)));
170             }
171 
172             return list;
173         }
174     }
175 
176     /**
177      * Parse the {@link android.R.attr#knownActivityEmbeddingCerts} attribute, if available.
178      */
179     @NonNull
parseKnownActivityEmbeddingCerts(@onNull TypedArray sa, @NonNull Resources res, int resourceId, @NonNull ParseInput input)180     public static ParseResult<Set<String>> parseKnownActivityEmbeddingCerts(@NonNull TypedArray sa,
181             @NonNull Resources res, int resourceId, @NonNull ParseInput input) {
182         if (!sa.hasValue(resourceId)) {
183             return input.success(null);
184         }
185 
186         final int knownActivityEmbeddingCertsResource = sa.getResourceId(resourceId, 0);
187         if (knownActivityEmbeddingCertsResource != 0) {
188             // The knownCerts attribute supports both a string array resource as well as a
189             // string resource for the case where the permission should only be granted to a
190             // single known signer.
191             Set<String> knownEmbeddingCertificates = null;
192             final String resourceType = res.getResourceTypeName(
193                     knownActivityEmbeddingCertsResource);
194             if (resourceType.equals("array")) {
195                 final String[] knownCerts = res.getStringArray(knownActivityEmbeddingCertsResource);
196                 if (knownCerts != null) {
197                     knownEmbeddingCertificates = Set.of(knownCerts);
198                 }
199             } else {
200                 final String knownCert = res.getString(knownActivityEmbeddingCertsResource);
201                 if (knownCert != null) {
202                     knownEmbeddingCertificates = Set.of(knownCert);
203                 }
204             }
205             if (knownEmbeddingCertificates == null || knownEmbeddingCertificates.isEmpty()) {
206                 return input.error("Defined a knownActivityEmbeddingCerts attribute but the "
207                         + "provided resource is null");
208             }
209             return input.success(knownEmbeddingCertificates);
210         }
211 
212         // If the knownCerts resource ID is null - the app specified a string value for the
213         // attribute representing a single trusted signer.
214         final String knownCert = sa.getString(resourceId);
215         if (knownCert == null || knownCert.isEmpty()) {
216             return input.error("Defined a knownActivityEmbeddingCerts attribute but the provided "
217                     + "string is empty");
218         }
219         return input.success(Set.of(knownCert));
220     }
221 }
222