| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.components.browser_ui.site_settings; |
| |
| import android.net.Uri; |
| |
| import androidx.annotation.Nullable; |
| |
| import org.chromium.components.embedder_support.util.UrlConstants; |
| import org.chromium.components.embedder_support.util.UrlUtilities; |
| import org.chromium.components.url_formatter.SchemeDisplay; |
| import org.chromium.components.url_formatter.UrlFormatter; |
| |
| import java.io.Serializable; |
| |
| /** |
| * A pattern that matches a certain set of URLs used in content settings rules. The pattern can be |
| * a fully specified origin, or just a host, or a domain name pattern. |
| * |
| * This is roughly equivalent to C++'s ContentSettingsPattern, though more limited. |
| */ |
| public class WebsiteAddress implements Comparable<WebsiteAddress>, Serializable { |
| private final String mOriginOrHostPattern; |
| private final String mOrigin; |
| private final String mScheme; |
| private final String mHost; |
| private final boolean mOmitProtocolAndPort; |
| private String mDomainAndRegistry; |
| |
| private static final String SCHEME_SUFFIX = "://"; |
| static final String ANY_SUBDOMAIN_PATTERN = "[*.]"; |
| |
| /** |
| * Creates a new WebsiteAddress from |originOrHostOrPattern|. |
| * |
| * @return A new WebsiteAddress, or null if |originOrHostOrPattern| was null or empty. |
| */ |
| @Nullable |
| public static WebsiteAddress create(String originOrHostOrPattern) { |
| // TODO(mvanouwerkerk): Define the behavior of this method if a url with path, query, or |
| // fragment is passed in. |
| |
| if (originOrHostOrPattern == null || originOrHostOrPattern.isEmpty()) { |
| return null; |
| } |
| |
| // Pattern |
| if (originOrHostOrPattern.startsWith(ANY_SUBDOMAIN_PATTERN)) { |
| String origin = null; |
| String scheme = null; |
| String host = originOrHostOrPattern.substring(ANY_SUBDOMAIN_PATTERN.length()); |
| boolean omitProtocolAndPort = true; |
| return new WebsiteAddress( |
| originOrHostOrPattern, origin, scheme, host, omitProtocolAndPort); |
| } |
| |
| // Origin |
| if (originOrHostOrPattern.indexOf(SCHEME_SUFFIX) != -1) { |
| Uri uri = Uri.parse(originOrHostOrPattern); |
| String origin = trimTrailingBackslash(originOrHostOrPattern); |
| boolean omitProtocolAndPort = UrlConstants.HTTP_SCHEME.equals(uri.getScheme()) |
| && (uri.getPort() == -1 || uri.getPort() == 80); |
| return new WebsiteAddress(originOrHostOrPattern, origin, uri.getScheme(), uri.getHost(), |
| omitProtocolAndPort); |
| } |
| |
| // Host |
| String origin = null; |
| String scheme = null; |
| boolean omitProtocolAndPort = true; |
| return new WebsiteAddress( |
| originOrHostOrPattern, origin, scheme, originOrHostOrPattern, omitProtocolAndPort); |
| } |
| |
| private WebsiteAddress(String originOrHostPattern, String origin, String scheme, String host, |
| boolean omitProtocolAndPort) { |
| mOriginOrHostPattern = originOrHostPattern; |
| mOrigin = origin; |
| mScheme = scheme; |
| mHost = host; |
| mOmitProtocolAndPort = omitProtocolAndPort; |
| } |
| |
| public String getOrigin() { |
| // aaa:80 and aaa must return the same origin string. |
| if (mHost != null && mOmitProtocolAndPort) { |
| return UrlConstants.HTTP_URL_PREFIX + mHost; |
| } else { |
| return mOrigin; |
| } |
| } |
| |
| public String getHost() { |
| return mHost; |
| } |
| |
| public boolean getIsAnySubdomainPattern() { |
| return mOriginOrHostPattern.startsWith(ANY_SUBDOMAIN_PATTERN); |
| } |
| |
| public String getTitle() { |
| if (mOrigin == null) return mHost; |
| return UrlFormatter.formatUrlForSecurityDisplay(mOrigin, |
| mOmitProtocolAndPort ? SchemeDisplay.OMIT_HTTP_AND_HTTPS : SchemeDisplay.SHOW); |
| } |
| |
| /** |
| * Returns true if {@code url} matches this WebsiteAddress's origin or host pattern. |
| */ |
| public boolean matches(String url) { |
| return WebsitePreferenceBridgeJni.get().urlMatchesContentSettingsPattern( |
| url, mOriginOrHostPattern); |
| } |
| |
| /** |
| * @return Domain and registry if those are defined; origin/host otherwise (for things like IP |
| *. addresses and "localhost") with the scheme omitted. |
| */ |
| public String getDomainAndRegistry() { |
| if (mDomainAndRegistry == null) { |
| // getDomainAndRegistry works better having a protocol prefix. |
| mDomainAndRegistry = UrlUtilities.getDomainAndRegistry( |
| (mOrigin != null) ? mOrigin : UrlConstants.HTTP_URL_PREFIX + mHost, false); |
| if (mDomainAndRegistry == null || mDomainAndRegistry.isEmpty()) { |
| mDomainAndRegistry = mHost; |
| } |
| } |
| return mDomainAndRegistry; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof WebsiteAddress) { |
| WebsiteAddress other = (WebsiteAddress) obj; |
| return compareTo(other) == 0; |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 17; |
| hash = hash * 31 + (mOrigin == null ? 0 : mOrigin.hashCode()); |
| hash = hash * 31 + (mScheme == null ? 0 : mScheme.hashCode()); |
| hash = hash * 31 + (mHost == null ? 0 : mHost.hashCode()); |
| return hash; |
| } |
| |
| @Override |
| public int compareTo(WebsiteAddress to) { |
| if (this == to) return 0; |
| String domainAndRegistry1 = getDomainAndRegistry(); |
| String domainAndRegistry2 = to.getDomainAndRegistry(); |
| int domainComparison = domainAndRegistry1.compareTo(domainAndRegistry2); |
| if (domainComparison != 0) return domainComparison; |
| // The same domain. Compare by scheme for grouping sites by scheme. |
| if ((mScheme == null) != (to.mScheme == null)) return mScheme == null ? -1 : 1; |
| if (mScheme != null) { // && to.mScheme != null |
| int schemesComparison = mScheme.compareTo(to.mScheme); |
| if (schemesComparison != 0) return schemesComparison; |
| } |
| // Now extract subdomains and compare them RTL. |
| String[] subdomains1 = getSubdomainsList(); |
| String[] subdomains2 = to.getSubdomainsList(); |
| int position1 = subdomains1.length - 1; |
| int position2 = subdomains2.length - 1; |
| while (position1 >= 0 && position2 >= 0) { |
| int subdomainComparison = subdomains1[position1--].compareTo(subdomains2[position2--]); |
| if (subdomainComparison != 0) return subdomainComparison; |
| } |
| return position1 - position2; |
| } |
| |
| private String[] getSubdomainsList() { |
| int startIndex; |
| String mAddress; |
| if (mOrigin != null) { |
| startIndex = mOrigin.indexOf(SCHEME_SUFFIX); |
| if (startIndex == -1) return new String[0]; |
| startIndex += SCHEME_SUFFIX.length(); |
| mAddress = mOrigin; |
| } else { |
| startIndex = 0; |
| mAddress = mHost; |
| } |
| int endIndex = mAddress.indexOf(getDomainAndRegistry()); |
| return --endIndex > startIndex ? mAddress.substring(startIndex, endIndex).split("\\.") |
| : new String[0]; |
| } |
| |
| private static String trimTrailingBackslash(String origin) { |
| return (origin.endsWith("/")) ? origin.substring(0, origin.length() - 1) : origin; |
| } |
| } |