1 /*
2  * Copyright (C) 2014 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.os;
18 
19 import static java.nio.charset.StandardCharsets.UTF_8;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.util.ArrayMap;
24 import android.util.Slog;
25 import android.util.Xml;
26 import android.util.proto.ProtoOutputStream;
27 
28 import com.android.internal.util.XmlUtils;
29 import com.android.modules.utils.TypedXmlPullParser;
30 import com.android.modules.utils.TypedXmlSerializer;
31 
32 import org.xmlpull.v1.XmlPullParser;
33 import org.xmlpull.v1.XmlPullParserException;
34 import org.xmlpull.v1.XmlSerializer;
35 
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.OutputStream;
39 import java.io.Serializable;
40 import java.util.ArrayList;
41 
42 /**
43  * A mapping from String keys to values of various types. The set of types
44  * supported by this class is purposefully restricted to simple objects that can
45  * safely be persisted to and restored from disk.
46  *
47  * <p><b>Warning:</b> Note that {@link PersistableBundle} is a lazy container and as such it does
48  * NOT implement {@link #equals(Object)} or {@link #hashCode()}.
49  *
50  * @see Bundle
51  */
52 public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
53         XmlUtils.WriteMapCallback {
54     private static final String TAG = "PersistableBundle";
55 
56     private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
57 
58     /** An unmodifiable {@code PersistableBundle} that is always {@link #isEmpty() empty}. */
59     public static final PersistableBundle EMPTY;
60 
61     static {
62         EMPTY = new PersistableBundle();
63         EMPTY.mMap = ArrayMap.EMPTY;
64     }
65 
66     /** @hide */
isValidType(Object value)67     public static boolean isValidType(Object value) {
68         return (value instanceof Integer) || (value instanceof Long) ||
69                 (value instanceof Double) || (value instanceof String) ||
70                 (value instanceof int[]) || (value instanceof long[]) ||
71                 (value instanceof double[]) || (value instanceof String[]) ||
72                 (value instanceof PersistableBundle) || (value == null) ||
73                 (value instanceof Boolean) || (value instanceof boolean[]);
74     }
75 
76     /**
77      * Constructs a new, empty PersistableBundle.
78      */
PersistableBundle()79     public PersistableBundle() {
80         super();
81         mFlags = FLAG_DEFUSABLE;
82     }
83 
84     /**
85      * Constructs a new, empty PersistableBundle sized to hold the given number of
86      * elements. The PersistableBundle will grow as needed.
87      *
88      * @param capacity the initial capacity of the PersistableBundle
89      */
PersistableBundle(int capacity)90     public PersistableBundle(int capacity) {
91         super(capacity);
92         mFlags = FLAG_DEFUSABLE;
93     }
94 
95     /**
96      * Constructs a PersistableBundle containing a copy of the mappings from the given
97      * PersistableBundle.  Does only a shallow copy of the original PersistableBundle -- see
98      * {@link #deepCopy()} if that is not what you want.
99      *
100      * @param b a PersistableBundle to be copied.
101      *
102      * @see #deepCopy()
103      */
PersistableBundle(PersistableBundle b)104     public PersistableBundle(PersistableBundle b) {
105         super(b);
106         mFlags = b.mFlags;
107     }
108 
109 
110     /**
111      * Constructs a PersistableBundle from a Bundle.  Does only a shallow copy of the Bundle.
112      *
113      * <p><b>Warning:</b> This method will deserialize every item on the bundle, including custom
114      * types such as {@link Parcelable} and {@link Serializable}, so only use this when you trust
115      * the source. Specifically don't use this method on app-provided bundles.
116      *
117      * @param b a Bundle to be copied.
118      *
119      * @throws IllegalArgumentException if any element of {@code b} cannot be persisted.
120      *
121      * @hide
122      */
PersistableBundle(Bundle b)123     public PersistableBundle(Bundle b) {
124         this(b, true);
125     }
126 
PersistableBundle(Bundle b, boolean throwException)127     private PersistableBundle(Bundle b, boolean throwException) {
128         this(b.getItemwiseMap(), throwException);
129     }
130 
131     /**
132      * Constructs a PersistableBundle containing the mappings passed in.
133      *
134      * @param map a Map containing only those items that can be persisted.
135      * @throws IllegalArgumentException if any element of #map cannot be persisted.
136      */
PersistableBundle(ArrayMap<String, Object> map, boolean throwException)137     private PersistableBundle(ArrayMap<String, Object> map, boolean throwException) {
138         super();
139         mFlags = FLAG_DEFUSABLE;
140 
141         // First stuff everything in.
142         putAll(map);
143 
144         // Now verify each item throwing an exception if there is a violation.
145         final int N = mMap.size();
146         for (int i = N - 1; i >= 0; --i) {
147             Object value = mMap.valueAt(i);
148             if (value instanceof ArrayMap) {
149                 // Fix up any Maps by replacing them with PersistableBundles.
150                 mMap.setValueAt(i,
151                         new PersistableBundle((ArrayMap<String, Object>) value, throwException));
152             } else if (value instanceof Bundle) {
153                 mMap.setValueAt(i, new PersistableBundle((Bundle) value, throwException));
154             } else if (!isValidType(value)) {
155                 final String errorMsg = "Bad value in PersistableBundle key="
156                         + mMap.keyAt(i) + " value=" + value;
157                 if (throwException) {
158                     throw new IllegalArgumentException(errorMsg);
159                 } else {
160                     Slog.wtfStack(TAG, errorMsg);
161                     mMap.removeAt(i);
162                 }
163             }
164         }
165     }
166 
PersistableBundle(Parcel parcelledData, int length)167     /* package */ PersistableBundle(Parcel parcelledData, int length) {
168         super(parcelledData, length);
169         mFlags = FLAG_DEFUSABLE;
170     }
171 
172     /**
173      * Constructs a {@link PersistableBundle} containing a copy of {@code from}.
174      *
175      * @param from The bundle to be copied.
176      * @param deep Whether is a deep or shallow copy.
177      *
178      * @hide
179      */
PersistableBundle(PersistableBundle from, boolean deep)180     PersistableBundle(PersistableBundle from, boolean deep) {
181         super(from, deep);
182     }
183 
184     /**
185      * Make a PersistableBundle for a single key/value pair.
186      *
187      * @hide
188      */
forPair(String key, String value)189     public static PersistableBundle forPair(String key, String value) {
190         PersistableBundle b = new PersistableBundle(1);
191         b.putString(key, value);
192         return b;
193     }
194 
195     /**
196      * Clones the current PersistableBundle. The internal map is cloned, but the keys and
197      * values to which it refers are copied by reference.
198      */
199     @Override
clone()200     public Object clone() {
201         return new PersistableBundle(this);
202     }
203 
204     /**
205      * Make a deep copy of the given bundle.  Traverses into inner containers and copies
206      * them as well, so they are not shared across bundles.  Will traverse in to
207      * {@link Bundle}, {@link PersistableBundle}, {@link ArrayList}, and all types of
208      * primitive arrays.  Other types of objects (such as Parcelable or Serializable)
209      * are referenced as-is and not copied in any way.
210      */
deepCopy()211     public PersistableBundle deepCopy() {
212         return new PersistableBundle(this, /* deep */ true);
213     }
214 
215     /**
216      * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
217      * any existing value for the given key.  Either key or value may be null.
218      *
219      * @param key a String, or null
220      * @param value a Bundle object, or null
221      */
putPersistableBundle(@ullable String key, @Nullable PersistableBundle value)222     public void putPersistableBundle(@Nullable String key, @Nullable PersistableBundle value) {
223         unparcel();
224         mMap.put(key, value);
225     }
226 
227     /**
228      * Returns the value associated with the given key, or null if
229      * no mapping of the desired type exists for the given key or a null
230      * value is explicitly associated with the key.
231      *
232      * @param key a String, or null
233      * @return a Bundle value, or null
234      */
235     @Nullable
getPersistableBundle(@ullable String key)236     public PersistableBundle getPersistableBundle(@Nullable String key) {
237         unparcel();
238         Object o = mMap.get(key);
239         if (o == null) {
240             return null;
241         }
242         try {
243             return (PersistableBundle) o;
244         } catch (ClassCastException e) {
245             typeWarning(key, o, "Bundle", e);
246             return null;
247         }
248     }
249 
250     public static final @android.annotation.NonNull Parcelable.Creator<PersistableBundle> CREATOR =
251             new Parcelable.Creator<PersistableBundle>() {
252                 @Override
253                 public PersistableBundle createFromParcel(Parcel in) {
254                     return in.readPersistableBundle();
255                 }
256 
257                 @Override
258                 public PersistableBundle[] newArray(int size) {
259                     return new PersistableBundle[size];
260                 }
261             };
262 
263     /** @hide */
264     @Override
writeUnknownObject(Object v, String name, TypedXmlSerializer out)265     public void writeUnknownObject(Object v, String name, TypedXmlSerializer out)
266             throws XmlPullParserException, IOException {
267         if (v instanceof PersistableBundle) {
268             out.startTag(null, TAG_PERSISTABLEMAP);
269             out.attribute(null, "name", name);
270             ((PersistableBundle) v).saveToXml(out);
271             out.endTag(null, TAG_PERSISTABLEMAP);
272         } else {
273             throw new XmlPullParserException("Unknown Object o=" + v);
274         }
275     }
276 
277     /** @hide */
saveToXml(XmlSerializer out)278     public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
279         saveToXml(XmlUtils.makeTyped(out));
280     }
281 
282     /** @hide */
saveToXml(TypedXmlSerializer out)283     public void saveToXml(TypedXmlSerializer out) throws IOException, XmlPullParserException {
284         unparcel();
285         // Explicitly drop invalid types an attacker may have added before persisting.
286         for (int i = mMap.size() - 1; i >= 0; --i) {
287             final Object value = mMap.valueAt(i);
288             if (!isValidType(value)) {
289                 Slog.e(TAG, "Dropping bad data before persisting: "
290                         + mMap.keyAt(i) + "=" + value);
291                 mMap.removeAt(i);
292             }
293         }
294         XmlUtils.writeMapXml(mMap, out, this);
295     }
296 
297     /**
298      * Checks whether all keys and values are within the given character limit.
299      * Note: Maximum character limit of String that can be saved to XML as part of bundle is 65535.
300      * Otherwise IOException is thrown.
301      * @param limit length of String keys and values in the PersistableBundle, including nested
302      *                    PersistableBundles to check against.
303      *
304      * @hide
305      */
isBundleContentsWithinLengthLimit(int limit)306     public boolean isBundleContentsWithinLengthLimit(int limit) {
307         unparcel();
308         if (mMap == null) {
309             return true;
310         }
311         for (int i = 0; i < mMap.size(); i++) {
312             if (mMap.keyAt(i) != null && mMap.keyAt(i).length() > limit) {
313                 return false;
314             }
315             final Object value = mMap.valueAt(i);
316             if (value instanceof String && ((String) value).length() > limit) {
317                 return false;
318             } else if (value instanceof String[]) {
319                 String[] stringArray =  (String[]) value;
320                 for (int j = 0; j < stringArray.length; j++) {
321                     if (stringArray[j] != null
322                             && stringArray[j].length() > limit) {
323                         return false;
324                     }
325                 }
326             } else if (value instanceof PersistableBundle
327                     && !((PersistableBundle) value).isBundleContentsWithinLengthLimit(limit)) {
328                 return false;
329             }
330         }
331         return true;
332     }
333 
334     /** @hide */
335     static class MyReadMapCallback implements  XmlUtils.ReadMapCallback {
336         @Override
readThisUnknownObjectXml(TypedXmlPullParser in, String tag)337         public Object readThisUnknownObjectXml(TypedXmlPullParser in, String tag)
338                 throws XmlPullParserException, IOException {
339             if (TAG_PERSISTABLEMAP.equals(tag)) {
340                 return restoreFromXml(in);
341             }
342             throw new XmlPullParserException("Unknown tag=" + tag);
343         }
344     }
345 
346     /**
347      * Report the nature of this Parcelable's contents
348      */
349     @Override
describeContents()350     public int describeContents() {
351         return 0;
352     }
353 
354     /**
355      * Writes the PersistableBundle contents to a Parcel, typically in order for
356      * it to be passed through an IBinder connection.
357      * @param parcel The parcel to copy this bundle to.
358      */
359     @Override
writeToParcel(Parcel parcel, int flags)360     public void writeToParcel(Parcel parcel, int flags) {
361         final boolean oldAllowFds = parcel.pushAllowFds(false);
362         try {
363             writeToParcelInner(parcel, flags);
364         } finally {
365             parcel.restoreAllowFds(oldAllowFds);
366         }
367     }
368 
369     /** @hide */
restoreFromXml(XmlPullParser in)370     public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
371             XmlPullParserException {
372         return restoreFromXml(XmlUtils.makeTyped(in));
373     }
374 
375     /** @hide */
restoreFromXml(TypedXmlPullParser in)376     public static PersistableBundle restoreFromXml(TypedXmlPullParser in) throws IOException,
377             XmlPullParserException {
378         final int outerDepth = in.getDepth();
379         final String startTag = in.getName();
380         final String[] tagName = new String[1];
381         int event;
382         while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
383                 (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
384             if (event == XmlPullParser.START_TAG) {
385                 // Don't throw an exception when restoring from XML since an attacker could try to
386                 // input invalid data in the persisted file.
387                 return new PersistableBundle((ArrayMap<String, Object>)
388                         XmlUtils.readThisArrayMapXml(in, startTag, tagName,
389                         new MyReadMapCallback()),
390                         /* throwException */ false);
391             }
392         }
393         return new PersistableBundle();  // An empty mutable PersistableBundle
394     }
395 
396     /**
397      * Returns a string representation of the {@link PersistableBundle} that may be suitable for
398      * debugging. It won't print the internal map if its content hasn't been unparcelled.
399      */
400     @Override
toString()401     public synchronized String toString() {
402         if (mParcelledData != null) {
403             if (isEmptyParcel()) {
404                 return "PersistableBundle[EMPTY_PARCEL]";
405             } else {
406                 return "PersistableBundle[mParcelledData.dataSize=" +
407                         mParcelledData.dataSize() + "]";
408             }
409         }
410         return "PersistableBundle[" + mMap.toString() + "]";
411     }
412 
413     /** @hide */
toShortString()414     synchronized public String toShortString() {
415         if (mParcelledData != null) {
416             if (isEmptyParcel()) {
417                 return "EMPTY_PARCEL";
418             } else {
419                 return "mParcelledData.dataSize=" + mParcelledData.dataSize();
420             }
421         }
422         return mMap.toString();
423     }
424 
425     /** @hide */
dumpDebug(ProtoOutputStream proto, long fieldId)426     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
427         final long token = proto.start(fieldId);
428 
429         if (mParcelledData != null) {
430             if (isEmptyParcel()) {
431                 proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, 0);
432             } else {
433                 proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, mParcelledData.dataSize());
434             }
435         } else {
436             proto.write(PersistableBundleProto.MAP_DATA, mMap.toString());
437         }
438 
439         proto.end(token);
440     }
441 
442     /**
443      * Writes the content of the {@link PersistableBundle} to a {@link OutputStream}.
444      *
445      * <p>The content can be read by a {@link #readFromStream}.
446      *
447      * @see #readFromStream
448      */
writeToStream(@onNull OutputStream outputStream)449     public void writeToStream(@NonNull OutputStream outputStream) throws IOException {
450         TypedXmlSerializer serializer = Xml.newFastSerializer();
451         serializer.setOutput(outputStream, UTF_8.name());
452         serializer.startTag(null, "bundle");
453         try {
454             saveToXml(serializer);
455         } catch (XmlPullParserException e) {
456             throw new IOException(e);
457         }
458         serializer.endTag(null, "bundle");
459         serializer.flush();
460     }
461 
462     /**
463      * Reads a {@link PersistableBundle} from an {@link InputStream}.
464      *
465      * <p>The stream must be generated by {@link #writeToStream}.
466      *
467      * @see #writeToStream
468      */
469     @NonNull
readFromStream(@onNull InputStream inputStream)470     public static PersistableBundle readFromStream(@NonNull InputStream inputStream)
471             throws IOException {
472         try {
473             TypedXmlPullParser parser = Xml.newFastPullParser();
474             parser.setInput(inputStream, UTF_8.name());
475             parser.next();
476             return PersistableBundle.restoreFromXml(parser);
477         } catch (XmlPullParserException e) {
478             throw new IOException(e);
479         }
480     }
481 }
482