1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi.hotspot2; 18 19 import android.text.TextUtils; 20 21 import com.android.server.wifi.hotspot2.Utils; 22 23 import java.util.HashMap; 24 import java.util.Iterator; 25 import java.util.List; 26 import java.util.Map; 27 28 /** 29 * Utility class for matching domain names. 30 */ 31 public class DomainMatcher { 32 public static final int MATCH_NONE = 0; 33 public static final int MATCH_PRIMARY = 1; 34 public static final int MATCH_SECONDARY = 2; 35 36 /** 37 * The root of the Label tree. 38 */ 39 private final Label mRoot; 40 41 /** 42 * Label tree representation for the domain name. Labels are delimited by "." in the domain 43 * name. 44 * 45 * For example, the tree representation of "android.google.com" as a primary domain: 46 * [com, None] -> [google, None] -> [android, Primary] 47 * 48 */ 49 private static class Label { 50 private final Map<String, Label> mSubDomains; 51 private int mMatch; 52 Label(int match)53 Label(int match) { 54 mMatch = match; 55 mSubDomains = new HashMap<String, Label>(); 56 } 57 58 /** 59 * Add sub-domains to this label. 60 * 61 * @param labels The iterator of domain label strings 62 * @param match The match status of the domain 63 */ addDomain(Iterator<String> labels, int match)64 public void addDomain(Iterator<String> labels, int match) { 65 String labelName = labels.next(); 66 // Create the Label object if it doesn't exist yet. 67 Label subLabel = mSubDomains.get(labelName); 68 if (subLabel == null) { 69 subLabel = new Label(MATCH_NONE); 70 mSubDomains.put(labelName, subLabel); 71 } 72 73 if (labels.hasNext()) { 74 // Adding sub-domain. 75 subLabel.addDomain(labels, match); 76 } else { 77 // End of the domain, update the match status. 78 subLabel.mMatch = match; 79 } 80 } 81 82 /** 83 * Return the Label for the give label string. 84 * @param labelString The label string to look for 85 * @return {@link Label} 86 */ getSubLabel(String labelString)87 public Label getSubLabel(String labelString) { 88 return mSubDomains.get(labelString); 89 } 90 91 /** 92 * Return the match status 93 * 94 * @return The match status 95 */ getMatch()96 public int getMatch() { 97 return mMatch; 98 } 99 toString(StringBuilder sb)100 private void toString(StringBuilder sb) { 101 if (mSubDomains != null) { 102 sb.append(".{"); 103 for (Map.Entry<String, Label> entry : mSubDomains.entrySet()) { 104 sb.append(entry.getKey()); 105 entry.getValue().toString(sb); 106 } 107 sb.append('}'); 108 } else { 109 sb.append('=').append(mMatch); 110 } 111 } 112 113 @Override toString()114 public String toString() { 115 StringBuilder sb = new StringBuilder(); 116 toString(sb); 117 return sb.toString(); 118 } 119 } 120 DomainMatcher(String primaryDomain, List<String> secondaryDomains)121 public DomainMatcher(String primaryDomain, List<String> secondaryDomains) { 122 // Create the root label. 123 mRoot = new Label(MATCH_NONE); 124 125 // Add secondary domains. 126 if (secondaryDomains != null) { 127 for (String domain : secondaryDomains) { 128 if (!TextUtils.isEmpty(domain)) { 129 List<String> secondaryLabel = Utils.splitDomain(domain); 130 mRoot.addDomain(secondaryLabel.iterator(), MATCH_SECONDARY); 131 } 132 } 133 } 134 135 // Add primary domain, primary overwrites secondary. 136 if (!TextUtils.isEmpty(primaryDomain)) { 137 List<String> primaryLabel = Utils.splitDomain(primaryDomain); 138 mRoot.addDomain(primaryLabel.iterator(), MATCH_PRIMARY); 139 } 140 } 141 142 /** 143 * Check if domain is either the same or a sub-domain of any of the domains in the 144 * domain tree in this matcher, i.e. all or a sub-set of the labels in domain matches 145 * a path in the tree. 146 * 147 * This will have precedence for matching primary domain over secondary domain if both 148 * are found. 149 * 150 * For example, with primary domain set to "test.google.com" and secondary domain set to 151 * "google.com": 152 * "test2.test.google.com" -> Match.Primary 153 * "test1.google.com" -> Match.Secondary 154 * 155 * @param domainName Domain name to be checked. 156 * @return The match status 157 */ isSubDomain(String domainName)158 public int isSubDomain(String domainName) { 159 if (TextUtils.isEmpty(domainName)) { 160 return MATCH_NONE; 161 } 162 List<String> domainLabels = Utils.splitDomain(domainName); 163 164 Label label = mRoot; 165 int match = MATCH_NONE; 166 for (String labelString : domainLabels) { 167 label = label.getSubLabel(labelString); 168 if (label == null) { 169 break; 170 } else if (label.getMatch() != MATCH_NONE) { 171 match = label.getMatch(); 172 if (match == MATCH_PRIMARY) { 173 break; 174 } 175 } 176 } 177 return match; 178 } 179 180 /** 181 * Check if domain2 is a sub-domain of domain1. 182 * 183 * @param domain1 The string of the first domain 184 * @param domain2 The string of the second domain 185 * @return true if the second domain is the sub-domain of the first 186 */ arg2SubdomainOfArg1(String domain1, String domain2)187 public static boolean arg2SubdomainOfArg1(String domain1, String domain2) { 188 if (TextUtils.isEmpty(domain1) || TextUtils.isEmpty(domain2)) { 189 return false; 190 } 191 192 List<String> labels1 = Utils.splitDomain(domain1); 193 List<String> labels2 = Utils.splitDomain(domain2); 194 195 // domain2 must be the same or longer than domain1 in order to be a sub-domain. 196 if (labels2.size() < labels1.size()) { 197 return false; 198 } 199 200 Iterator<String> l1 = labels1.iterator(); 201 Iterator<String> l2 = labels2.iterator(); 202 203 while(l1.hasNext()) { 204 if (!TextUtils.equals(l1.next(), l2.next())) { 205 return false; 206 } 207 } 208 return true; 209 } 210 211 @Override toString()212 public String toString() { 213 return "Domain matcher " + mRoot; 214 } 215 } 216