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