View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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   * Utility class that can test if DNS names match the content of the Public Suffix List.
41   * <p>
42   * An up-to-date list of suffixes can be obtained from
43   * <a href="http://publicsuffix.org/">publicsuffix.org</a>
44   *
45   * @see org.apache.http.conn.util.PublicSuffixList
46   *
47   * @since 4.4
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       * @since 4.5
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       * @since 4.5
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      * Returns registrable part of the domain for the given domain name or {@code null}
113      * if given domain represents a public suffix.
114      *
115      * @param domain
116      * @return domain root
117      */
118     public String getDomainRoot(final String domain) {
119         return getDomainRoot(domain, null);
120     }
121 
122     /**
123      * Returns registrable part of the domain for the given domain name or {@code null}
124      * if given domain represents a public suffix.
125      *
126      * @param domain
127      * @param expectedType expected domain type or {@code null} if any.
128      * @return domain root
129      *
130      * @since 4.5
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             // An exception rule takes priority over any other matching rule.
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         // If no expectations then this result is good.
174         if (expectedType == null || expectedType == DomainType.UNKNOWN) {
175             return result;
176         }
177 
178         // If we did have expectations apparently there was no match
179         return null;
180     }
181 
182     /**
183      * Tests whether the given domain matches any of entry from the public suffix list.
184      */
185     public boolean matches(final String domain) {
186         return matches(domain, null);
187     }
188 
189     /**
190      * Tests whether the given domain matches any of entry from the public suffix list.
191      *
192      * @param domain
193      * @param expectedType expected domain type or {@code null} if any.
194      * @return {@code true} if the given domain matches any of the public suffixes.
195      *
196      * @since 4.5
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 }