• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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