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