001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027package org.apache.http.conn.util; 028 029import java.net.IDN; 030import java.util.Collection; 031import java.util.List; 032import java.util.Locale; 033import java.util.Map; 034import java.util.concurrent.ConcurrentHashMap; 035 036import org.apache.http.annotation.Contract; 037import org.apache.http.annotation.ThreadingBehavior; 038import org.apache.http.util.Args; 039 040/** 041 * Utility class that can test if DNS names match the content of the Public Suffix List. 042 * <p> 043 * An up-to-date list of suffixes can be obtained from 044 * <a href="http://publicsuffix.org/">publicsuffix.org</a> 045 * 046 * @see org.apache.http.conn.util.PublicSuffixList 047 * 048 * @since 4.4 049 */ 050@Contract(threading = ThreadingBehavior.SAFE) 051public final class PublicSuffixMatcher { 052 053 private final Map<String, DomainType> rules; 054 private final Map<String, DomainType> exceptions; 055 056 public PublicSuffixMatcher(final Collection<String> rules, final Collection<String> exceptions) { 057 this(DomainType.UNKNOWN, rules, exceptions); 058 } 059 060 /** 061 * @since 4.5 062 */ 063 public PublicSuffixMatcher( 064 final DomainType domainType, final Collection<String> rules, final Collection<String> exceptions) { 065 Args.notNull(domainType, "Domain type"); 066 Args.notNull(rules, "Domain suffix rules"); 067 this.rules = new ConcurrentHashMap<String, DomainType>(rules.size()); 068 for (final String rule: rules) { 069 this.rules.put(rule, domainType); 070 } 071 this.exceptions = new ConcurrentHashMap<String, DomainType>(); 072 if (exceptions != null) { 073 for (final String exception: exceptions) { 074 this.exceptions.put(exception, domainType); 075 } 076 } 077 } 078 079 /** 080 * @since 4.5 081 */ 082 public PublicSuffixMatcher(final Collection<PublicSuffixList> lists) { 083 Args.notNull(lists, "Domain suffix lists"); 084 this.rules = new ConcurrentHashMap<String, DomainType>(); 085 this.exceptions = new ConcurrentHashMap<String, DomainType>(); 086 for (final PublicSuffixList list: lists) { 087 final DomainType domainType = list.getType(); 088 final List<String> rules = list.getRules(); 089 for (final String rule: rules) { 090 this.rules.put(rule, domainType); 091 } 092 final List<String> exceptions = list.getExceptions(); 093 if (exceptions != null) { 094 for (final String exception: exceptions) { 095 this.exceptions.put(exception, domainType); 096 } 097 } 098 } 099 } 100 101 private static boolean hasEntry(final Map<String, DomainType> map, final String rule, final DomainType expectedType) { 102 if (map == null) { 103 return false; 104 } 105 final DomainType domainType = map.get(rule); 106 if (domainType == null) { 107 return false; 108 } else { 109 return expectedType == null || domainType.equals(expectedType); 110 } 111 } 112 113 private boolean hasRule(final String rule, final DomainType expectedType) { 114 return hasEntry(this.rules, rule, expectedType); 115 } 116 117 private boolean hasException(final String exception, final DomainType expectedType) { 118 return hasEntry(this.exceptions, exception, expectedType); 119 } 120 121 /** 122 * Returns registrable part of the domain for the given domain name or {@code null} 123 * if given domain represents a public suffix. 124 * 125 * @param domain 126 * @return domain root 127 */ 128 public String getDomainRoot(final String domain) { 129 return getDomainRoot(domain, null); 130 } 131 132 /** 133 * Returns registrable part of the domain for the given domain name or {@code null} 134 * if given domain represents a public suffix. 135 * 136 * @param domain 137 * @param expectedType expected domain type or {@code null} if any. 138 * @return domain root 139 * 140 * @since 4.5 141 */ 142 public String getDomainRoot(final String domain, final DomainType expectedType) { 143 if (domain == null) { 144 return null; 145 } 146 if (domain.startsWith(".")) { 147 return null; 148 } 149 String domainName = null; 150 String segment = domain.toLowerCase(Locale.ROOT); 151 while (segment != null) { 152 153 // An exception rule takes priority over any other matching rule. 154 if (hasException(IDN.toUnicode(segment), expectedType)) { 155 return segment; 156 } 157 158 if (hasRule(IDN.toUnicode(segment), expectedType)) { 159 break; 160 } 161 162 final int nextdot = segment.indexOf('.'); 163 final String nextSegment = nextdot != -1 ? segment.substring(nextdot + 1) : null; 164 165 if (nextSegment != null) { 166 if (hasRule("*." + IDN.toUnicode(nextSegment), expectedType)) { 167 break; 168 } 169 } 170 if (nextdot != -1) { 171 domainName = segment; 172 } 173 segment = nextSegment; 174 } 175 return domainName; 176 } 177 178 /** 179 * Tests whether the given domain matches any of entry from the public suffix list. 180 */ 181 public boolean matches(final String domain) { 182 return matches(domain, null); 183 } 184 185 /** 186 * Tests whether the given domain matches any of entry from the public suffix list. 187 * 188 * @param domain 189 * @param expectedType expected domain type or {@code null} if any. 190 * @return {@code true} if the given domain matches any of the public suffixes. 191 * 192 * @since 4.5 193 */ 194 public boolean matches(final String domain, final DomainType expectedType) { 195 if (domain == null) { 196 return false; 197 } 198 final String domainRoot = getDomainRoot( 199 domain.startsWith(".") ? domain.substring(1) : domain, expectedType); 200 return domainRoot == null; 201 } 202 203}