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