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