types/src/main/java/com/google/common/html/types/SafeUrls.java
private static boolean isSafeUrl(String url, Set<CustomSafeUrlScheme> extraAllowedSchemes) {
    String lowerCased = url.toLowerCase();

    // If some Unicode character lower cases to something that ends up matching these ASCII ones,
    // it's harmless.
    for (String scheme : DEFAULT_SAFE_SCHEMES) {
      if (lowerCased.startsWith(scheme + ":")) {
        return true;
      }
    }

    for (CustomSafeUrlScheme scheme : extraAllowedSchemes) {
      /**
       * For "-" in a custom URL scheme, it's not possible to write a proto enum with "-" in the
       * field name. In proto, it has to be "_". But we can safely convert all "_" in the proto name
       * to "-", since according to the URL Living Standard, a URL-scheme string must be one ASCII
       * alpha, followed by zero or more of ASCII alphanumeric, "+", "-", and ".".
       *
       * @see https://url.spec.whatwg.org/#url-syntax
       */
      if (lowerCased.startsWith(scheme.name().toLowerCase().replace('_', '-') + ":")) {
        return true;
      }
    }

    for (int i = 0; i < url.length(); i++) {
      switch (url.charAt(i)) {
        case '/':
        case '?':
        case '#':
          // After this the string can end or contain anything else, it won't be interpreted
          // as the scheme.
          return true;
        case ':':
          // This character is not allowed before seeing one of the above characters.
          return false;
        default:
          // Other characters ok.
          continue;
      }
    }
    return true;
  }
private static boolean isSafeUrl(String url, Set<CustomSafeUrlScheme> extraAllowedSchemes) {
    String lowerCased = url.toLowerCase();

    // If some Unicode character lower cases to something that ends up matching these ASCII ones,
    // it's harmless.
    for (String scheme : DEFAULT_SAFE_SCHEMES) {
      if (lowerCased.startsWith(scheme + ":")) {
        return true;
      }
    }

    for (CustomSafeUrlScheme scheme : extraAllowedSchemes) {
      /**
       * For "-" in a custom URL scheme, it's not possible to write a proto enum with "-" in the
       * field name. In proto, it has to be "_". But we can safely convert all "_" in the proto name
       * to "-", since according to the URL Living Standard, a URL-scheme string must be one ASCII
       * alpha, followed by zero or more of ASCII alphanumeric, "+", "-", and ".".
       *
       * @see https://url.spec.whatwg.org/#url-syntax
       */
      if (lowerCased.startsWith(scheme.name().toLowerCase().replace('_', '-') + ":")) {
        return true;
      }
    }

    for (int i = 0; i < url.length(); i++) {
      switch (url.charAt(i)) {
        case '/':
        case '?':
        case '#':
          // After this the string can end or contain anything else, it won't be interpreted
          // as the scheme.
          return true;
        case ':':
          // This character is not allowed before seeing one of the above characters.
          return false;
        default:
          // Other characters ok.
          continue;
      }
    }
    return true;
  }
Matches a subset of URLs that will not cause script execution if used in URL context within a
HTML document. Specifically, this method returns true if the {@code url}:
<ul>
<li>Starts with a default safe protocol (http, https, ftp, mailto) or one of the schemes
specified in {@code extraAllowedSchemes}.
<li>Contains no protocol.  A protocol must be followed by a colon and colons are allowed
only after one of the characters [/?#].
A colon after a hash (#) must be in the fragment.
Otherwise, a colon after a (?) must be in a query.
Otherwise, a colon after a single solidus (/) must be in a path.
Otherwise, a colon after a double solidus (*) must be in the authority (before port).
</ul>

<p>We don't use a regex so that we don't need to depend on GWT, which does not support Java's
Pattern and requires using its RegExp class.