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 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 String segment = DnsUtils.normalize(domain);
142 String result = null;
143 while (segment != null) {
144
145 final String key = IDN.toUnicode(segment);
146 final DomainType exceptionRule = findEntry(exceptions, key);
147 if (match(exceptionRule, expectedType)) {
148 return segment;
149 }
150 final DomainType domainRule = findEntry(rules, key);
151 if (match(domainRule, expectedType)) {
152 if (domainRule == DomainType.PRIVATE) {
153 return segment;
154 }
155 return result;
156 }
157
158 final int nextdot = segment.indexOf('.');
159 final String nextSegment = nextdot != -1 ? segment.substring(nextdot + 1) : null;
160
161 if (nextSegment != null) {
162 final DomainType wildcardDomainRule = findEntry(rules, "*." + IDN.toUnicode(nextSegment));
163 if (match(wildcardDomainRule, expectedType)) {
164 if (wildcardDomainRule == DomainType.PRIVATE) {
165 return segment;
166 }
167 return result;
168 }
169 }
170 result = segment;
171 segment = nextSegment;
172 }
173
174
175 if (expectedType == null || expectedType == DomainType.UNKNOWN) {
176 return result;
177 }
178
179
180 return null;
181 }
182
183
184
185
186 public boolean matches(final String domain) {
187 return matches(domain, null);
188 }
189
190
191
192
193
194
195
196
197
198
199 public boolean matches(final String domain, final DomainType expectedType) {
200 if (domain == null) {
201 return false;
202 }
203 final String domainRoot = getDomainRoot(
204 domain.startsWith(".") ? domain.substring(1) : domain, expectedType);
205 return domainRoot == null;
206 }
207
208 }