1 // Copyright 2015 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.app.PendingIntent; 8 import android.content.ActivityNotFoundException; 9 import android.content.ComponentName; 10 import android.content.Context; 11 import android.content.Intent; 12 import android.os.BadParcelableException; 13 import android.os.Binder; 14 import android.os.Build; 15 import android.os.Bundle; 16 import android.os.IBinder; 17 import android.os.Parcel; 18 import android.os.Parcelable; 19 import android.os.TransactionTooLargeException; 20 import android.text.TextUtils; 21 22 import androidx.annotation.Nullable; 23 import androidx.core.app.BundleCompat; 24 25 import org.chromium.base.compat.ApiHelperForM; 26 import org.chromium.base.compat.ApiHelperForS; 27 28 import java.io.Serializable; 29 import java.util.ArrayList; 30 31 /** 32 * Utilities dealing with extracting information from intents and creating common intents. 33 */ 34 public class IntentUtils { 35 private static final String TAG = "IntentUtils"; 36 37 /** 38 * The scheme for referrer coming from an application. 39 */ 40 public static final String ANDROID_APP_REFERRER_SCHEME = "android-app"; 41 42 /** 43 * Intent extra used to identify the sending application. 44 */ 45 public static final String TRUSTED_APPLICATION_CODE_EXTRA = "trusted_application_code_extra"; 46 47 /** 48 * Fake ComponentName used in constructing TRUSTED_APPLICATION_CODE_EXTRA. 49 */ 50 private static ComponentName sFakeComponentName; 51 private static final Object COMPONENT_NAME_LOCK = new Object(); 52 53 private static boolean sForceTrustedIntentForTesting; 54 55 /** 56 * Just like {@link Intent#hasExtra(String)} but doesn't throw exceptions. 57 */ safeHasExtra(Intent intent, String name)58 public static boolean safeHasExtra(Intent intent, String name) { 59 try { 60 return intent.hasExtra(name); 61 } catch (Throwable t) { 62 // Catches un-parceling exceptions. 63 Log.e(TAG, "hasExtra failed on intent " + intent); 64 return false; 65 } 66 } 67 68 /** 69 * Just like {@link Intent#removeExtra(String)} but doesn't throw exceptions. 70 */ safeRemoveExtra(Intent intent, String name)71 public static void safeRemoveExtra(Intent intent, String name) { 72 try { 73 intent.removeExtra(name); 74 } catch (Throwable t) { 75 // Catches un-parceling exceptions. 76 Log.e(TAG, "removeExtra failed on intent " + intent); 77 } 78 } 79 80 /** 81 * Just like {@link Intent#getBooleanExtra(String, boolean)} but doesn't throw exceptions. 82 */ safeGetBooleanExtra(Intent intent, String name, boolean defaultValue)83 public static boolean safeGetBooleanExtra(Intent intent, String name, boolean defaultValue) { 84 try { 85 return intent.getBooleanExtra(name, defaultValue); 86 } catch (Throwable t) { 87 // Catches un-parceling exceptions. 88 Log.e(TAG, "getBooleanExtra failed on intent " + intent); 89 return defaultValue; 90 } 91 } 92 93 /** 94 * Just like {@link Bundle#getBoolean(String, boolean)} but doesn't throw exceptions. 95 */ safeGetBoolean(Bundle bundle, String name, boolean defaultValue)96 public static boolean safeGetBoolean(Bundle bundle, String name, boolean defaultValue) { 97 try { 98 return bundle.getBoolean(name, defaultValue); 99 } catch (Throwable t) { 100 // Catches un-parceling exceptions. 101 Log.e(TAG, "getBoolean failed on bundle " + bundle); 102 return defaultValue; 103 } 104 } 105 106 /** 107 * Just like {@link Intent#getIntExtra(String, int)} but doesn't throw exceptions. 108 */ safeGetIntExtra(Intent intent, String name, int defaultValue)109 public static int safeGetIntExtra(Intent intent, String name, int defaultValue) { 110 try { 111 return intent.getIntExtra(name, defaultValue); 112 } catch (Throwable t) { 113 // Catches un-parceling exceptions. 114 Log.e(TAG, "getIntExtra failed on intent " + intent); 115 return defaultValue; 116 } 117 } 118 119 /** 120 * Just like {@link Bundle#getInt(String, int)} but doesn't throw exceptions. 121 */ safeGetInt(Bundle bundle, String name, int defaultValue)122 public static int safeGetInt(Bundle bundle, String name, int defaultValue) { 123 try { 124 return bundle.getInt(name, defaultValue); 125 } catch (Throwable t) { 126 // Catches un-parceling exceptions. 127 Log.e(TAG, "getInt failed on bundle " + bundle); 128 return defaultValue; 129 } 130 } 131 132 /** 133 * Just like {@link Intent#getIntArrayExtra(String)} but doesn't throw exceptions. 134 */ safeGetIntArrayExtra(Intent intent, String name)135 public static int[] safeGetIntArrayExtra(Intent intent, String name) { 136 try { 137 return intent.getIntArrayExtra(name); 138 } catch (Throwable t) { 139 // Catches un-parceling exceptions. 140 Log.e(TAG, "getIntArrayExtra failed on intent " + intent); 141 return null; 142 } 143 } 144 145 /** 146 * Just like {@link Bundle#getIntArray(String)} but doesn't throw exceptions. 147 */ safeGetIntArray(Bundle bundle, String name)148 public static int[] safeGetIntArray(Bundle bundle, String name) { 149 try { 150 return bundle.getIntArray(name); 151 } catch (Throwable t) { 152 // Catches un-parceling exceptions. 153 Log.e(TAG, "getIntArray failed on bundle " + bundle); 154 return null; 155 } 156 } 157 158 /** 159 * Just like {@link Bundle#getFloatArray(String)} but doesn't throw exceptions. 160 */ safeGetFloatArray(Bundle bundle, String name)161 public static float[] safeGetFloatArray(Bundle bundle, String name) { 162 try { 163 return bundle.getFloatArray(name); 164 } catch (Throwable t) { 165 // Catches un-parceling exceptions. 166 Log.e(TAG, "getFloatArray failed on bundle " + bundle); 167 return null; 168 } 169 } 170 171 /** 172 * Just like {@link Intent#getLongExtra(String, long)} but doesn't throw exceptions. 173 */ safeGetLongExtra(Intent intent, String name, long defaultValue)174 public static long safeGetLongExtra(Intent intent, String name, long defaultValue) { 175 try { 176 return intent.getLongExtra(name, defaultValue); 177 } catch (Throwable t) { 178 // Catches un-parceling exceptions. 179 Log.e(TAG, "getLongExtra failed on intent " + intent); 180 return defaultValue; 181 } 182 } 183 184 /** 185 * Just like {@link Intent#getStringExtra(String)} but doesn't throw exceptions. 186 */ safeGetStringExtra(Intent intent, String name)187 public static String safeGetStringExtra(Intent intent, String name) { 188 try { 189 return intent.getStringExtra(name); 190 } catch (Throwable t) { 191 // Catches un-parceling exceptions. 192 Log.e(TAG, "getStringExtra failed on intent " + intent); 193 return null; 194 } 195 } 196 197 /** 198 * Just like {@link Bundle#getString(String)} but doesn't throw exceptions. 199 */ safeGetString(Bundle bundle, String name)200 public static String safeGetString(Bundle bundle, String name) { 201 try { 202 return bundle.getString(name); 203 } catch (Throwable t) { 204 // Catches un-parceling exceptions. 205 Log.e(TAG, "getString failed on bundle " + bundle); 206 return null; 207 } 208 } 209 210 /** 211 * Just like {@link Intent#getBundleExtra(String)} but doesn't throw exceptions. 212 */ safeGetBundleExtra(Intent intent, String name)213 public static Bundle safeGetBundleExtra(Intent intent, String name) { 214 try { 215 return intent.getBundleExtra(name); 216 } catch (Throwable t) { 217 // Catches un-parceling exceptions. 218 Log.e(TAG, "getBundleExtra failed on intent " + intent); 219 return null; 220 } 221 } 222 223 /** 224 * Just like {@link Bundle#getBundle(String)} but doesn't throw exceptions. 225 */ safeGetBundle(Bundle bundle, String name)226 public static Bundle safeGetBundle(Bundle bundle, String name) { 227 try { 228 return bundle.getBundle(name); 229 } catch (Throwable t) { 230 // Catches un-parceling exceptions. 231 Log.e(TAG, "getBundle failed on bundle " + bundle); 232 return null; 233 } 234 } 235 236 /** 237 * Just like {@link Bundle#getParcelable(String)} but doesn't throw exceptions. 238 */ safeGetParcelable(Bundle bundle, String name)239 public static <T extends Parcelable> T safeGetParcelable(Bundle bundle, String name) { 240 try { 241 return bundle.getParcelable(name); 242 } catch (Throwable t) { 243 // Catches un-parceling exceptions. 244 Log.e(TAG, "getParcelable failed on bundle " + bundle); 245 return null; 246 } 247 } 248 249 /** 250 * Just like {@link Intent#getParcelableExtra(String)} but doesn't throw exceptions. 251 */ safeGetParcelableExtra(Intent intent, String name)252 public static <T extends Parcelable> T safeGetParcelableExtra(Intent intent, String name) { 253 try { 254 return intent.getParcelableExtra(name); 255 } catch (Throwable t) { 256 // Catches un-parceling exceptions. 257 Log.e(TAG, "getParcelableExtra failed on intent " + intent); 258 return null; 259 } 260 } 261 262 /** 263 * Just link {@link Intent#getParcelableArrayListExtra(String)} but doesn't throw exceptions. 264 */ getParcelableArrayListExtra( Intent intent, String name)265 public static <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra( 266 Intent intent, String name) { 267 try { 268 return intent.getParcelableArrayListExtra(name); 269 } catch (Throwable t) { 270 // Catches un-parceling exceptions. 271 Log.e(TAG, "getParcelableArrayListExtra failed on intent " + intent); 272 return null; 273 } 274 } 275 276 /** 277 * Just link {@link Bundle#getParcelableArrayList(String)} but doesn't throw exceptions. 278 */ safeGetParcelableArrayList( Bundle bundle, String name)279 public static <T extends Parcelable> ArrayList<T> safeGetParcelableArrayList( 280 Bundle bundle, String name) { 281 try { 282 return bundle.getParcelableArrayList(name); 283 } catch (Throwable t) { 284 // Catches un-parceling exceptions. 285 Log.e(TAG, "getParcelableArrayList failed on bundle " + bundle); 286 return null; 287 } 288 } 289 290 /** 291 * Just like {@link Intent#getParcelableArrayExtra(String)} but doesn't throw exceptions. 292 */ safeGetParcelableArrayExtra(Intent intent, String name)293 public static Parcelable[] safeGetParcelableArrayExtra(Intent intent, String name) { 294 try { 295 return intent.getParcelableArrayExtra(name); 296 } catch (Throwable t) { 297 Log.e(TAG, "getParcelableArrayExtra failed on intent " + intent); 298 return null; 299 } 300 } 301 302 /** 303 * Just like {@link Intent#getStringArrayListExtra(String)} but doesn't throw exceptions. 304 */ safeGetStringArrayListExtra(Intent intent, String name)305 public static ArrayList<String> safeGetStringArrayListExtra(Intent intent, String name) { 306 try { 307 return intent.getStringArrayListExtra(name); 308 } catch (Throwable t) { 309 // Catches un-parceling exceptions. 310 Log.e(TAG, "getStringArrayListExtra failed on intent " + intent); 311 return null; 312 } 313 } 314 315 /** 316 * Just like {@link Intent#getByteArrayExtra(String)} but doesn't throw exceptions. 317 */ safeGetByteArrayExtra(Intent intent, String name)318 public static byte[] safeGetByteArrayExtra(Intent intent, String name) { 319 try { 320 return intent.getByteArrayExtra(name); 321 } catch (Throwable t) { 322 // Catches un-parceling exceptions. 323 Log.e(TAG, "getByteArrayExtra failed on intent " + intent); 324 return null; 325 } 326 } 327 328 /** 329 * Just like {@link Intent#getSerializableExtra(String)} but doesn't throw exceptions. 330 */ 331 @SuppressWarnings("unchecked") safeGetSerializableExtra(Intent intent, String name)332 public static <T extends Serializable> T safeGetSerializableExtra(Intent intent, String name) { 333 try { 334 return (T) intent.getSerializableExtra(name); 335 } catch (ClassCastException ex) { 336 Log.e(TAG, "Invalide class for Serializable: " + name, ex); 337 return null; 338 } catch (Throwable t) { 339 // Catches un-serializable exceptions. 340 Log.e(TAG, "getSerializableExtra failed on intent " + intent); 341 return null; 342 } 343 } 344 345 /** 346 * Just like {@link BundleCompat#getBinder()}, but doesn't throw exceptions. 347 */ safeGetBinder(Bundle bundle, String name)348 public static IBinder safeGetBinder(Bundle bundle, String name) { 349 if (bundle == null) return null; 350 try { 351 return BundleCompat.getBinder(bundle, name); 352 } catch (Throwable t) { 353 // Catches un-parceling exceptions. 354 Log.e(TAG, "getBinder failed on bundle " + bundle); 355 return null; 356 } 357 } 358 359 /** 360 * @return a Binder from an Intent, or null. 361 * 362 * Creates a temporary copy of the extra Bundle, which is required as 363 * Intent#getBinderExtra() doesn't exist, but Bundle.getBinder() does. 364 */ safeGetBinderExtra(Intent intent, String name)365 public static IBinder safeGetBinderExtra(Intent intent, String name) { 366 if (!intent.hasExtra(name)) return null; 367 Bundle extras = intent.getExtras(); 368 return safeGetBinder(extras, name); 369 } 370 371 /** 372 * Inserts a {@link Binder} value into an Intent as an extra. 373 * 374 * Uses {@link BundleCompat#putBinder()}, but doesn't throw exceptions. 375 * 376 * @param intent Intent to put the binder into. 377 * @param name Key. 378 * @param binder Binder object. 379 */ safePutBinderExtra(Intent intent, String name, IBinder binder)380 public static void safePutBinderExtra(Intent intent, String name, IBinder binder) { 381 if (intent == null) return; 382 Bundle bundle = new Bundle(); 383 try { 384 BundleCompat.putBinder(bundle, name, binder); 385 } catch (Throwable t) { 386 // Catches parceling exceptions. 387 Log.e(TAG, "putBinder failed on bundle " + bundle); 388 } 389 intent.putExtras(bundle); 390 } 391 392 /** See {@link #safeStartActivity(Context, Intent, Bundle)}. */ safeStartActivity(Context context, Intent intent)393 public static boolean safeStartActivity(Context context, Intent intent) { 394 return safeStartActivity(context, intent, null); 395 } 396 397 /** 398 * Catches any failures to start an Activity. 399 * @param context Context to use when starting the Activity. 400 * @param intent Intent to fire. 401 * @param bundle Bundle of launch options. 402 * @return Whether or not Android accepted the Intent. 403 */ safeStartActivity( Context context, Intent intent, @Nullable Bundle bundle)404 public static boolean safeStartActivity( 405 Context context, Intent intent, @Nullable Bundle bundle) { 406 try { 407 context.startActivity(intent, bundle); 408 return true; 409 } catch (ActivityNotFoundException e) { 410 return false; 411 } 412 } 413 414 /** Returns whether the intent starts an activity in a new task or a new document. */ isIntentForNewTaskOrNewDocument(Intent intent)415 public static boolean isIntentForNewTaskOrNewDocument(Intent intent) { 416 int testFlags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 417 return (intent.getFlags() & testFlags) != 0; 418 } 419 420 /** 421 * Returns how large the Intent will be in Parcel form, which is helpful for gauging whether 422 * Android will deliver the Intent instead of throwing a TransactionTooLargeException. 423 * 424 * @param intent Intent to get the size of. 425 * @return Number of bytes required to parcel the Intent. 426 */ getParceledIntentSize(Intent intent)427 public static int getParceledIntentSize(Intent intent) { 428 Parcel parcel = Parcel.obtain(); 429 intent.writeToParcel(parcel, 0); 430 return parcel.dataSize(); 431 } 432 433 /** 434 * Given an exception, check whether it wrapped a {@link TransactionTooLargeException}. If it 435 * does, then log the underlying error. If not, throw the original exception again. 436 * 437 * @param e The caught RuntimeException. 438 * @param intent The intent that triggered the RuntimeException to be thrown. 439 */ logTransactionTooLargeOrRethrow(RuntimeException e, Intent intent)440 public static void logTransactionTooLargeOrRethrow(RuntimeException e, Intent intent) { 441 // See http://crbug.com/369574. 442 if (e.getCause() instanceof TransactionTooLargeException) { 443 Log.e(TAG, "Could not resolve Activity for intent " + intent.toString(), e); 444 } else { 445 throw e; 446 } 447 } 448 logInvalidIntent(Intent intent, Exception e)449 private static Intent logInvalidIntent(Intent intent, Exception e) { 450 Log.e(TAG, "Invalid incoming intent.", e); 451 return intent.replaceExtras((Bundle) null); 452 } 453 454 /** 455 * Sanitizes an intent. In case the intent cannot be unparcelled, all extras will be removed to 456 * make it safe to use. 457 * @return A safe to use version of this intent. 458 */ sanitizeIntent(final Intent incomingIntent)459 public static Intent sanitizeIntent(final Intent incomingIntent) { 460 // On Android T+, items are only deserialized when the items themselves are queried, so the 461 // code below is a no-op. 462 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return incomingIntent; 463 if (incomingIntent == null) return null; 464 try { 465 incomingIntent.getBooleanExtra("TriggerUnparcel", false); 466 return incomingIntent; 467 } catch (BadParcelableException e) { 468 return logInvalidIntent(incomingIntent, e); 469 } catch (RuntimeException e) { 470 if (e.getCause() instanceof ClassNotFoundException) { 471 return logInvalidIntent(incomingIntent, e); 472 } 473 throw e; 474 } 475 } 476 477 /** 478 * @return True if the intent is a MAIN intent a launcher would send. 479 */ isMainIntentFromLauncher(Intent intent)480 public static boolean isMainIntentFromLauncher(Intent intent) { 481 return intent != null && TextUtils.equals(intent.getAction(), Intent.ACTION_MAIN) 482 && intent.hasCategory(Intent.CATEGORY_LAUNCHER) 483 && 0 == (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); 484 } 485 486 /** 487 * Gets the PendingIntent flag for the specified mutability. 488 * PendingIntent.FLAG_IMMUTABLE was added in API level 23 (M), and FLAG_MUTABLE was added in 489 * Android S. 490 * 491 * Unless mutability is required, PendingIntents should always be marked as Immutable as this 492 * is the more secure default. 493 */ getPendingIntentMutabilityFlag(boolean mutable)494 public static int getPendingIntentMutabilityFlag(boolean mutable) { 495 if (!mutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 496 return ApiHelperForM.getPendingIntentImmutableFlag(); 497 } else if (mutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 498 return ApiHelperForS.getPendingIntentMutableFlag(); 499 } 500 return 0; 501 } 502 503 /** 504 * Determines whether this app is the only possible handler for this Intent. 505 * 506 * @param context Any context for this app. 507 * @param intent The intent to check. 508 * @return True if the intent targets this app. 509 */ intentTargetsSelf(Context context, Intent intent)510 public static boolean intentTargetsSelf(Context context, Intent intent) { 511 boolean hasPackage = !TextUtils.isEmpty(intent.getPackage()); 512 boolean matchesPackage = hasPackage && context.getPackageName().equals(intent.getPackage()); 513 boolean hasComponent = intent.getComponent() != null; 514 boolean matchesComponent = hasComponent 515 && context.getPackageName().equals(intent.getComponent().getPackageName()); 516 517 // Component takes precedence over PackageName when routing Intents if both are set, but to 518 // be on the safe side, ensure that if we have both package and component set, that they 519 // agree. 520 if (matchesComponent) { 521 if (hasPackage) { 522 // We should not create intents that disagree on package/component, but for security 523 // purposes we should handle this case. 524 assert matchesPackage; 525 return matchesPackage; 526 } 527 return true; 528 } 529 if (matchesPackage) { 530 assert !hasComponent; 531 return !hasComponent; 532 } 533 return false; 534 } 535 getFakeComponentName(String packageName)536 private static ComponentName getFakeComponentName(String packageName) { 537 synchronized (COMPONENT_NAME_LOCK) { 538 if (sFakeComponentName == null) { 539 sFakeComponentName = new ComponentName(packageName, "FakeClass"); 540 } 541 } 542 543 return sFakeComponentName; 544 } 545 getAuthenticationToken()546 private static PendingIntent getAuthenticationToken() { 547 Intent fakeIntent = new Intent(); 548 Context appContext = ContextUtils.getApplicationContext(); 549 fakeIntent.setComponent(getFakeComponentName(appContext.getPackageName())); 550 return PendingIntent.getActivity( 551 appContext, 0, fakeIntent, getPendingIntentMutabilityFlag(false)); 552 } 553 554 /** 555 * Sets TRUSTED_APPLICATION_CODE_EXTRA on the provided intent to identify it as coming from 556 * a trusted source. 557 * 558 * @param intent An Intent that targets either current package, or explicitly targets a 559 * component of the current package. 560 */ addTrustedIntentExtras(Intent intent)561 public static void addTrustedIntentExtras(Intent intent) { 562 // It is crucial that we never leak the authentication token to other packages, because 563 // then the other package could be used to impersonate us/do things as us. 564 boolean toSelf = 565 IntentUtils.intentTargetsSelf(ContextUtils.getApplicationContext(), intent); 566 assert toSelf; 567 // For security reasons we have to check the asserted condition anyways. 568 if (!toSelf) return; 569 570 // The PendingIntent functions as an authentication token --- it could only have come 571 // from us. Stash it in the real Intent as an extra we can validate upon receiving it. 572 intent.putExtra(TRUSTED_APPLICATION_CODE_EXTRA, getAuthenticationToken()); 573 } 574 575 /** 576 * @param intent An Intent to be checked. 577 * @return Whether an intent originates from the current app. 578 */ isTrustedIntentFromSelf(@ullable Intent intent)579 public static boolean isTrustedIntentFromSelf(@Nullable Intent intent) { 580 if (intent == null) return false; 581 582 if (sForceTrustedIntentForTesting) return true; 583 584 // Fetch the authentication token (a PendingIntent) created by 585 // addTrustedIntentExtras, if any. If anything goes wrong trying to retrieve the 586 // token (examples include BadParcelableException or ClassNotFoundException), fail closed. 587 PendingIntent token = 588 IntentUtils.safeGetParcelableExtra(intent, TRUSTED_APPLICATION_CODE_EXTRA); 589 if (token == null) return false; 590 591 // Fetch what should be a matching token. If the PendingIntents are equal, we know that the 592 // sender was us. 593 PendingIntent pending = getAuthenticationToken(); 594 return pending.equals(token); 595 } 596 setForceIsTrustedIntentForTesting(boolean isTrusted)597 public static void setForceIsTrustedIntentForTesting(boolean isTrusted) { 598 sForceTrustedIntentForTesting = isTrusted; 599 } 600 } 601