// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.preferences.website;
import android.net.Uri;
import org.chromium.chrome.browser.util.UrlUtilities;
import java.io.Serializable;
import javax.annotation.Nullable;
/**
* 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 static final String HTTP_SCHEME = "http";
private static final String SCHEME_SUFFIX = "://";
private 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 = 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 (mOrigin != null && mOmitProtocolAndPort) {
return HTTP_SCHEME + SCHEME_SUFFIX + mHost;
} else {
return mOrigin;
}
}
public String getHost() {
return mHost;
}
public String getTitle() {
if (mOrigin == null || mOmitProtocolAndPort) return mHost;
return mOrigin;
}
/**
* Returns true if {@code url} matches this WebsiteAddress's origin or host pattern.
*/
public boolean matches(String url) {
return WebsitePreferenceBridge.nativeUrlMatchesContentSettingsPattern(url,
mOriginOrHostPattern);
}
private String getDomainAndRegistry() {
if (mOrigin != null) return UrlUtilities.getDomainAndRegistry(mOrigin, false);
// getDomainAndRegistry works better having a protocol prefix.
return UrlUtilities.getDomainAndRegistry(HTTP_SCHEME + SCHEME_SUFFIX + mHost, false);
}
@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;
}
}