/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi.hotspot2; import android.text.TextUtils; import com.android.server.wifi.hotspot2.Utils; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Utility class for matching domain names. */ public class DomainMatcher { public static final int MATCH_NONE = 0; public static final int MATCH_PRIMARY = 1; public static final int MATCH_SECONDARY = 2; /** * The root of the Label tree. */ private final Label mRoot; /** * Label tree representation for the domain name. Labels are delimited by "." in the domain * name. * * For example, the tree representation of "android.google.com" as a primary domain: * [com, None] -> [google, None] -> [android, Primary] * */ private static class Label { private final Map mSubDomains; private int mMatch; Label(int match) { mMatch = match; mSubDomains = new HashMap(); } /** * Add sub-domains to this label. * * @param labels The iterator of domain label strings * @param match The match status of the domain */ public void addDomain(Iterator labels, int match) { String labelName = labels.next(); // Create the Label object if it doesn't exist yet. Label subLabel = mSubDomains.get(labelName); if (subLabel == null) { subLabel = new Label(MATCH_NONE); mSubDomains.put(labelName, subLabel); } if (labels.hasNext()) { // Adding sub-domain. subLabel.addDomain(labels, match); } else { // End of the domain, update the match status. subLabel.mMatch = match; } } /** * Return the Label for the give label string. * @param labelString The label string to look for * @return {@link Label} */ public Label getSubLabel(String labelString) { return mSubDomains.get(labelString); } /** * Return the match status * * @return The match status */ public int getMatch() { return mMatch; } private void toString(StringBuilder sb) { if (mSubDomains != null) { sb.append(".{"); for (Map.Entry entry : mSubDomains.entrySet()) { sb.append(entry.getKey()); entry.getValue().toString(sb); } sb.append('}'); } else { sb.append('=').append(mMatch); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); toString(sb); return sb.toString(); } } public DomainMatcher(String primaryDomain, List secondaryDomains) { // Create the root label. mRoot = new Label(MATCH_NONE); // Add secondary domains. if (secondaryDomains != null) { for (String domain : secondaryDomains) { if (!TextUtils.isEmpty(domain)) { List secondaryLabel = Utils.splitDomain(domain); mRoot.addDomain(secondaryLabel.iterator(), MATCH_SECONDARY); } } } // Add primary domain, primary overwrites secondary. if (!TextUtils.isEmpty(primaryDomain)) { List primaryLabel = Utils.splitDomain(primaryDomain); mRoot.addDomain(primaryLabel.iterator(), MATCH_PRIMARY); } } /** * Check if domain is either the same or a sub-domain of any of the domains in the * domain tree in this matcher, i.e. all or a sub-set of the labels in domain matches * a path in the tree. * * This will have precedence for matching primary domain over secondary domain if both * are found. * * For example, with primary domain set to "test.google.com" and secondary domain set to * "google.com": * "test2.test.google.com" -> Match.Primary * "test1.google.com" -> Match.Secondary * * @param domainName Domain name to be checked. * @return The match status */ public int isSubDomain(String domainName) { if (TextUtils.isEmpty(domainName)) { return MATCH_NONE; } List domainLabels = Utils.splitDomain(domainName); Label label = mRoot; int match = MATCH_NONE; for (String labelString : domainLabels) { label = label.getSubLabel(labelString); if (label == null) { break; } else if (label.getMatch() != MATCH_NONE) { match = label.getMatch(); if (match == MATCH_PRIMARY) { break; } } } return match; } /** * Check if domain2 is a sub-domain of domain1. * * @param domain1 The string of the first domain * @param domain2 The string of the second domain * @return true if the second domain is the sub-domain of the first */ public static boolean arg2SubdomainOfArg1(String domain1, String domain2) { if (TextUtils.isEmpty(domain1) || TextUtils.isEmpty(domain2)) { return false; } List labels1 = Utils.splitDomain(domain1); List labels2 = Utils.splitDomain(domain2); // domain2 must be the same or longer than domain1 in order to be a sub-domain. if (labels2.size() < labels1.size()) { return false; } Iterator l1 = labels1.iterator(); Iterator l2 = labels2.iterator(); while(l1.hasNext()) { if (!TextUtils.equals(l1.next(), l2.next())) { return false; } } return true; } @Override public String toString() { return "Domain matcher " + mRoot; } }