summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
blob: 20a7b95f4d622a5f333b0a861097f68b0aa58359 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/.
 */

package org.mozilla.gecko.util;

import android.annotation.TargetApi;
import android.content.Intent;
import android.net.Uri;
import java.net.URISyntaxException;
import java.util.Locale;

/** Utilities for Intents. */
public class IntentUtils {
  private IntentUtils() {}

  /**
   * Return a Uri instance which is equivalent to uri, but with a guaranteed-lowercase scheme as if
   * the API level 16 method Uri.normalizeScheme had been called.
   *
   * @param uri The URI string to normalize.
   * @return The corresponding normalized Uri.
   */
  private static Uri normalizeUriScheme(final Uri uri) {
    final String scheme = uri.getScheme();
    if (scheme == null) {
      return uri;
    }
    final String lower = scheme.toLowerCase(Locale.ROOT);
    if (lower.equals(scheme)) {
      return uri;
    }

    // Otherwise, return a new URI with a normalized scheme.
    return uri.buildUpon().scheme(lower).build();
  }

  /**
   * Return a normalized Uri instance that corresponds to the given URI string with cross-API-level
   * compatibility.
   *
   * @param aUri The URI string to normalize.
   * @return The corresponding normalized Uri.
   */
  public static Uri normalizeUri(final String aUri) {
    final Uri normUri =
        normalizeUriScheme(
            aUri.indexOf(':') >= 0 ? Uri.parse(aUri) : new Uri.Builder().scheme(aUri).build());
    return normUri;
  }

  public static boolean isUriSafeForScheme(final String aUri) {
    return isUriSafeForScheme(normalizeUri(aUri));
  }

  /**
   * Verify whether the given URI is considered safe to load in respect to its scheme. Unsafe URIs
   * should be blocked from further handling.
   *
   * @param aUri The URI instance to test.
   * @return Whether the provided URI is considered safe in respect to its scheme.
   */
  public static boolean isUriSafeForScheme(final Uri aUri) {
    final String scheme = aUri.getScheme();
    if ("tel".equals(scheme) || "sms".equals(scheme)) {
      // Bug 794034 - We don't want to pass MWI or USSD codes to the
      // dialer, and ensure the Uri class doesn't parse a URI
      // containing a fragment ('#')
      final String number = aUri.getSchemeSpecificPart();
      if (number.contains("#") || number.contains("*") || aUri.getFragment() != null) {
        return false;
      }
    }

    if (("intent".equals(scheme) || "android-app".equals(scheme))) {
      // Bug 1356893 - Rject intents with file data schemes.
      return getSafeIntent(aUri) != null;
    }

    return true;
  }

  /**
   * Create a safe intent for the given URI. Intents with file data schemes are considered unsafe.
   *
   * @param aUri The URI for the intent.
   * @return A safe intent for the given URI or null if URI is considered unsafe.
   */
  public static Intent getSafeIntent(final Uri aUri) {
    final Intent intent;
    try {
      intent = Intent.parseUri(aUri.toString(), 0);
    } catch (final URISyntaxException e) {
      return null;
    }

    final Uri data = intent.getData();
    if (data != null && "file".equals(normalizeUriScheme(data).getScheme())) {
      return null;
    }

    // Only open applications which can accept arbitrary data from a browser.
    intent.addCategory(Intent.CATEGORY_BROWSABLE);

    // Prevent site from explicitly opening our internal activities,
    // which can leak data.
    intent.setComponent(null);
    nullIntentSelector(intent);

    return intent;
  }

  // We create a separate method to better encapsulate the @TargetApi use.
  @TargetApi(15)
  private static void nullIntentSelector(final Intent intent) {
    intent.setSelector(null);
  }
}