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