1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.http.conn.util;
28
29 import java.net.IDN;
30 import java.util.Collection;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.ConcurrentHashMap;
34
35 import org.apache.http.annotation.Contract;
36 import org.apache.http.annotation.ThreadingBehavior;
37 import org.apache.http.util.Args;
38
39
40
41
42
43
44
45
46
47
48
49 @Contract(threading = ThreadingBehavior.SAFE)
50 public final class PublicSuffixMatcher {
51
52 private final Map<String, DomainType> rules;
53 private final Map<String, DomainType> exceptions;
54
55 public PublicSuffixMatcher(final Collection<String> rules, final Collection<String> exceptions) {
56 this(DomainType.UNKNOWN, rules, exceptions);
57 }
58
59
60
61
62 public PublicSuffixMatcher(
63 final DomainType domainType, final Collection<String> rules, final Collection<String> exceptions) {
64 Args.notNull(domainType, "Domain type");
65 Args.notNull(rules, "Domain suffix rules");
66 this.rules = new ConcurrentHashMap<String, DomainType>(rules.size());
67 for (final String rule: rules) {
68 this.rules.put(rule, domainType);
69 }
70 this.exceptions = new ConcurrentHashMap<String, DomainType>();
71 if (exceptions != null) {
72 for (final String exception: exceptions) {
73 this.exceptions.put(exception, domainType);
74 }
75 }
76 }
77
78
79
80
81 public PublicSuffixMatcher(final Collection<PublicSuffixList> lists) {
82 Args.notNull(lists, "Domain suffix lists");
83 this.rules = new ConcurrentHashMap<String, DomainType>();
84 this.exceptions = new ConcurrentHashMap<String, DomainType>();
85 for (final PublicSuffixList list: lists) {
86 final DomainType domainType = list.getType();
87 final List<String> rules = list.getRules();
88 for (final String rule: rules) {
89 this.rules.put(rule, domainType);
90 }
91 final List<String> exceptions = list.getExceptions();
92 if (exceptions != null) {
93 for (final String exception: exceptions) {
94 this.exceptions.put(exception, domainType);
95 }
96 }
97 }
98 }
99
100 private static DomainType findEntry(final Map<String, DomainType> map, final String rule) {
101 if (map == null) {
102 return null;
103 }
104 return map.get(rule);
105 }
106
107 private static boolean match(final DomainTypepe.html#DomainType">DomainType domainType, final DomainType expectedType) {
108 return domainType != null && (expectedType == null || domainType.equals(expectedType));
109 }
110
111
112
113
114
115
116
117
118 public String getDomainRoot(final String domain) {
119 return getDomainRoot(domain, null);
120 }
121
122
123
124
125
126
127
128
129
130
131
132 public String getDomainRoot(final String domain, final DomainType expectedType) {
133 if (domain == null) {
134 return null;
135 }
136 if (domain.startsWith(".")) {
137 return null;
138 }
139 final String normalized = DnsUtils.normalize(domain);
140 String segment = normalized;
141 String result = null;
142 while (segment != null) {
143
144 final String key = IDN.toUnicode(segment);
145 final DomainType exceptionRule = findEntry(exceptions, key);
146 if (match(exceptionRule, expectedType)) {
147 return segment;
148 }
149 final DomainType domainRule = findEntry(rules, key);
150 if (match(domainRule, expectedType)) {
151 if (domainRule == DomainType.PRIVATE) {
152 return segment;
153 }
154 return result;
155 }
156
157 final int nextdot = segment.indexOf('.');
158 final String nextSegment = nextdot != -1 ? segment.substring(nextdot + 1) : null;
159
160 if (nextSegment != null) {
161 final DomainType wildcardDomainRule = findEntry(rules, "*." + IDN.toUnicode(nextSegment));
162 if (match(wildcardDomainRule, expectedType)) {
163 if (wildcardDomainRule == DomainType.PRIVATE) {
164 return segment;
165 }
166 return result;
167 }
168 }
169 result = segment;
170 segment = nextSegment;
171 }
172
173
174 if (expectedType == null || expectedType == DomainType.UNKNOWN) {
175 return result;
176 }
177
178
179 return null;
180 }
181
182
183
184
185 public boolean matches(final String domain) {
186 return matches(domain, null);
187 }
188
189
190
191
192
193
194
195
196
197
198 public boolean matches(final String domain, final DomainType expectedType) {
199 if (domain == null) {
200 return false;
201 }
202 final String domainRoot = getDomainRoot(
203 domain.startsWith(".") ? domain.substring(1) : domain, expectedType);
204 return domainRoot == null;
205 }
206
207 }