From aaae72cf8450b72eec921a6dd7ccc027c12fcfc6 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 25 Apr 2015 22:54:05 +0000 Subject: [PATCH] - Initial checkin of Apache classes from Apache HttpClient 4.4.1 (Apache 2.0 license) and one small class from HttpCore 4.4.1, unmodified as a baseline for future merges. Does not compile. See following checkins for more info --- .../conn/ssl/DefaultHostnameVerifier.java | 297 ++++++++++++++++++ .../http/conn/util/InetAddressUtils.java | 124 ++++++++ .../http/conn/util/PublicSuffixList.java | 63 ++++ .../conn/util/PublicSuffixListParser.java | 114 +++++++ .../http/conn/util/PublicSuffixMatcher.java | 121 +++++++ core/java/src/org/apache/http/util/Args.java | 127 ++++++++ 6 files changed, 846 insertions(+) create mode 100644 core/java/src/org/apache/http/conn/ssl/DefaultHostnameVerifier.java create mode 100644 core/java/src/org/apache/http/conn/util/InetAddressUtils.java create mode 100644 core/java/src/org/apache/http/conn/util/PublicSuffixList.java create mode 100644 core/java/src/org/apache/http/conn/util/PublicSuffixListParser.java create mode 100644 core/java/src/org/apache/http/conn/util/PublicSuffixMatcher.java create mode 100644 core/java/src/org/apache/http/util/Args.java diff --git a/core/java/src/org/apache/http/conn/ssl/DefaultHostnameVerifier.java b/core/java/src/org/apache/http/conn/ssl/DefaultHostnameVerifier.java new file mode 100644 index 0000000000..03a1edb918 --- /dev/null +++ b/core/java/src/org/apache/http/conn/ssl/DefaultHostnameVerifier.java @@ -0,0 +1,297 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.ssl; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.cert.Certificate; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.NoSuchElementException; + +import javax.naming.InvalidNameException; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.annotation.Immutable; +import org.apache.http.conn.util.InetAddressUtils; +import org.apache.http.conn.util.PublicSuffixMatcher; + +/** + * Default {@link javax.net.ssl.HostnameVerifier} implementation. + * + * @since 4.4 + */ +@Immutable +public final class DefaultHostnameVerifier implements HostnameVerifier { + + final static int DNS_NAME_TYPE = 2; + final static int IP_ADDRESS_TYPE = 7; + + private final Log log = LogFactory.getLog(getClass()); + + private final PublicSuffixMatcher publicSuffixMatcher; + + public DefaultHostnameVerifier(final PublicSuffixMatcher publicSuffixMatcher) { + this.publicSuffixMatcher = publicSuffixMatcher; + } + + public DefaultHostnameVerifier() { + this(null); + } + + @Override + public final boolean verify(final String host, final SSLSession session) { + try { + final Certificate[] certs = session.getPeerCertificates(); + final X509Certificate x509 = (X509Certificate) certs[0]; + verify(host, x509); + return true; + } catch(final SSLException ex) { + if (log.isDebugEnabled()) { + log.debug(ex.getMessage(), ex); + } + return false; + } + } + + public final void verify( + final String host, final X509Certificate cert) throws SSLException { + final boolean ipv4 = InetAddressUtils.isIPv4Address(host); + final boolean ipv6 = InetAddressUtils.isIPv6Address(host); + final int subjectType = ipv4 || ipv6 ? IP_ADDRESS_TYPE : DNS_NAME_TYPE; + final List<String> subjectAlts = extractSubjectAlts(cert, subjectType); + if (subjectAlts != null && !subjectAlts.isEmpty()) { + if (ipv4) { + matchIPAddress(host, subjectAlts); + } else if (ipv6) { + matchIPv6Address(host, subjectAlts); + } else { + matchDNSName(host, subjectAlts, this.publicSuffixMatcher); + } + } else { + // CN matching has been deprecated by rfc2818 and can be used + // as fallback only when no subjectAlts are available + final X500Principal subjectPrincipal = cert.getSubjectX500Principal(); + final String cn = extractCN(subjectPrincipal.getName(X500Principal.RFC2253)); + if (cn == null) { + throw new SSLException("Certificate subject for <" + host + "> doesn't contain " + + "a common name and does not have alternative names"); + } + matchCN(host, cn, this.publicSuffixMatcher); + } + } + + static void matchIPAddress(final String host, final List<String> subjectAlts) throws SSLException { + for (int i = 0; i < subjectAlts.size(); i++) { + final String subjectAlt = subjectAlts.get(i); + if (host.equals(subjectAlt)) { + return; + } + } + throw new SSLException("Certificate for <" + host + "> doesn't match any " + + "of the subject alternative names: " + subjectAlts); + } + + static void matchIPv6Address(final String host, final List<String> subjectAlts) throws SSLException { + final String normalisedHost = normaliseAddress(host); + for (int i = 0; i < subjectAlts.size(); i++) { + final String subjectAlt = subjectAlts.get(i); + final String normalizedSubjectAlt = normaliseAddress(subjectAlt); + if (normalisedHost.equals(normalizedSubjectAlt)) { + return; + } + } + throw new SSLException("Certificate for <" + host + "> doesn't match any " + + "of the subject alternative names: " + subjectAlts); + } + + static void matchDNSName(final String host, final List<String> subjectAlts, + final PublicSuffixMatcher publicSuffixMatcher) throws SSLException { + final String normalizedHost = host.toLowerCase(Locale.ROOT); + for (int i = 0; i < subjectAlts.size(); i++) { + final String subjectAlt = subjectAlts.get(i); + final String normalizedSubjectAlt = subjectAlt.toLowerCase(Locale.ROOT); + if (matchIdentityStrict(normalizedHost, normalizedSubjectAlt, publicSuffixMatcher)) { + return; + } + } + throw new SSLException("Certificate for <" + host + "> doesn't match any " + + "of the subject alternative names: " + subjectAlts); + } + + static void matchCN(final String host, final String cn, + final PublicSuffixMatcher publicSuffixMatcher) throws SSLException { + if (!matchIdentityStrict(host, cn, publicSuffixMatcher)) { + throw new SSLException("Certificate for <" + host + "> doesn't match " + + "common name of the certificate subject: " + cn); + } + } + + static boolean matchDomainRoot(final String host, final String domainRoot) { + if (domainRoot == null) { + return false; + } + return host.endsWith(domainRoot) && (host.length() == domainRoot.length() + || host.charAt(host.length() - domainRoot.length() - 1) == '.'); + } + + private static boolean matchIdentity(final String host, final String identity, + final PublicSuffixMatcher publicSuffixMatcher, + final boolean strict) { + if (publicSuffixMatcher != null && host.contains(".")) { + if (!matchDomainRoot(host, publicSuffixMatcher.getDomainRoot(identity))) { + return false; + } + } + + // RFC 2818, 3.1. Server Identity + // "...Names may contain the wildcard + // character * which is considered to match any single domain name + // component or component fragment..." + // Based on this statement presuming only singular wildcard is legal + final int asteriskIdx = identity.indexOf('*'); + if (asteriskIdx != -1) { + final String prefix = identity.substring(0, asteriskIdx); + final String suffix = identity.substring(asteriskIdx + 1); + if (!prefix.isEmpty() && !host.startsWith(prefix)) { + return false; + } + if (!suffix.isEmpty() && !host.endsWith(suffix)) { + return false; + } + // Additional sanity checks on content selected by wildcard can be done here + if (strict) { + final String remainder = host.substring( + prefix.length(), host.length() - suffix.length()); + if (remainder.contains(".")) { + return false; + } + } + return true; + } + return host.equalsIgnoreCase(identity); + } + + static boolean matchIdentity(final String host, final String identity, + final PublicSuffixMatcher publicSuffixMatcher) { + return matchIdentity(host, identity, publicSuffixMatcher, false); + } + + static boolean matchIdentity(final String host, final String identity) { + return matchIdentity(host, identity, null, false); + } + + static boolean matchIdentityStrict(final String host, final String identity, + final PublicSuffixMatcher publicSuffixMatcher) { + return matchIdentity(host, identity, publicSuffixMatcher, true); + } + + static boolean matchIdentityStrict(final String host, final String identity) { + return matchIdentity(host, identity, null, true); + } + + static String extractCN(final String subjectPrincipal) throws SSLException { + if (subjectPrincipal == null) { + return null; + } + try { + final LdapName subjectDN = new LdapName(subjectPrincipal); + final List<Rdn> rdns = subjectDN.getRdns(); + for (int i = rdns.size() - 1; i >= 0; i--) { + final Rdn rds = rdns.get(i); + final Attributes attributes = rds.toAttributes(); + final Attribute cn = attributes.get("cn"); + if (cn != null) { + try { + final Object value = cn.get(); + if (value != null) { + return value.toString(); + } + } catch (NoSuchElementException ignore) { + } catch (NamingException ignore) { + } + } + } + return null; + } catch (InvalidNameException e) { + throw new SSLException(subjectPrincipal + " is not a valid X500 distinguished name"); + } + } + + static List<String> extractSubjectAlts(final X509Certificate cert, final int subjectType) { + Collection<List<?>> c = null; + try { + c = cert.getSubjectAlternativeNames(); + } catch(final CertificateParsingException ignore) { + } + List<String> subjectAltList = null; + if (c != null) { + for (final List<?> aC : c) { + final List<?> list = aC; + final int type = ((Integer) list.get(0)).intValue(); + if (type == subjectType) { + final String s = (String) list.get(1); + if (subjectAltList == null) { + subjectAltList = new ArrayList<String>(); + } + subjectAltList.add(s); + } + } + } + return subjectAltList; + } + + /* + * Normalize IPv6 or DNS name. + */ + static String normaliseAddress(final String hostname) { + if (hostname == null) { + return hostname; + } + try { + final InetAddress inetAddress = InetAddress.getByName(hostname); + return inetAddress.getHostAddress(); + } catch (final UnknownHostException unexpected) { // Should not happen, because we check for IPv6 address above + return hostname; + } + } +} diff --git a/core/java/src/org/apache/http/conn/util/InetAddressUtils.java b/core/java/src/org/apache/http/conn/util/InetAddressUtils.java new file mode 100644 index 0000000000..acee8afa23 --- /dev/null +++ b/core/java/src/org/apache/http/conn/util/InetAddressUtils.java @@ -0,0 +1,124 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.util; + +import java.util.regex.Pattern; + +import org.apache.http.annotation.Immutable; + +/** + * A collection of utilities relating to InetAddresses. + * + * @since 4.0 + */ +@Immutable +public class InetAddressUtils { + + private InetAddressUtils() { + } + + private static final String IPV4_BASIC_PATTERN_STRING = + "(([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){1}" + // initial first field, 1-255 + "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){2}" + // following 2 fields, 0-255 followed by . + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"; // final field, 0-255 + + private static final Pattern IPV4_PATTERN = + Pattern.compile("^" + IPV4_BASIC_PATTERN_STRING + "$"); + + private static final Pattern IPV4_MAPPED_IPV6_PATTERN = // TODO does not allow for redundant leading zeros + Pattern.compile("^::[fF]{4}:" + IPV4_BASIC_PATTERN_STRING + "$"); + + private static final Pattern IPV6_STD_PATTERN = + Pattern.compile( + "^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$"); + + private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = + Pattern.compile( + "^(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)" + // 0-6 hex fields + "::" + + "(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)$"); // 0-6 hex fields + + /* + * The above pattern is not totally rigorous as it allows for more than 7 hex fields in total + */ + private static final char COLON_CHAR = ':'; + + // Must not have more than 7 colons (i.e. 8 fields) + private static final int MAX_COLON_COUNT = 7; + + /** + * Checks whether the parameter is a valid IPv4 address + * + * @param input the address string to check for validity + * @return true if the input parameter is a valid IPv4 address + */ + public static boolean isIPv4Address(final String input) { + return IPV4_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv4MappedIPv64Address(final String input) { + return IPV4_MAPPED_IPV6_PATTERN.matcher(input).matches(); + } + + /** + * Checks whether the parameter is a valid standard (non-compressed) IPv6 address + * + * @param input the address string to check for validity + * @return true if the input parameter is a valid standard (non-compressed) IPv6 address + */ + public static boolean isIPv6StdAddress(final String input) { + return IPV6_STD_PATTERN.matcher(input).matches(); + } + + /** + * Checks whether the parameter is a valid compressed IPv6 address + * + * @param input the address string to check for validity + * @return true if the input parameter is a valid compressed IPv6 address + */ + public static boolean isIPv6HexCompressedAddress(final String input) { + int colonCount = 0; + for(int i = 0; i < input.length(); i++) { + if (input.charAt(i) == COLON_CHAR) { + colonCount++; + } + } + return colonCount <= MAX_COLON_COUNT && IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); + } + + /** + * Checks whether the parameter is a valid IPv6 address (including compressed). + * + * @param input the address string to check for validity + * @return true if the input parameter is a valid standard or compressed IPv6 address + */ + public static boolean isIPv6Address(final String input) { + return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); + } + +} diff --git a/core/java/src/org/apache/http/conn/util/PublicSuffixList.java b/core/java/src/org/apache/http/conn/util/PublicSuffixList.java new file mode 100644 index 0000000000..ec15c9d40b --- /dev/null +++ b/core/java/src/org/apache/http/conn/util/PublicSuffixList.java @@ -0,0 +1,63 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.conn.util; + +import java.util.Collections; +import java.util.List; + +import org.apache.http.annotation.Immutable; +import org.apache.http.util.Args; + +/** + * Public suffix is a set of DNS names or wildcards concatenated with dots. It represents + * the part of a domain name which is not under the control of the individual registrant + * <p> + * An up-to-date list of suffixes can be obtained from + * <a href="http://publicsuffix.org/">publicsuffix.org</a> + * + * @since 4.4 + */ +@Immutable +public final class PublicSuffixList { + + private final List<String> rules; + private final List<String> exceptions; + + public PublicSuffixList(final List<String> rules, final List<String> exceptions) { + this.rules = Collections.unmodifiableList(Args.notNull(rules, "Domain suffix rules")); + this.exceptions = Collections.unmodifiableList(Args.notNull(exceptions, "Domain suffix exceptions")); + } + + public List<String> getRules() { + return rules; + } + + public List<String> getExceptions() { + return exceptions; + } + +} diff --git a/core/java/src/org/apache/http/conn/util/PublicSuffixListParser.java b/core/java/src/org/apache/http/conn/util/PublicSuffixListParser.java new file mode 100644 index 0000000000..84bbd182fc --- /dev/null +++ b/core/java/src/org/apache/http/conn/util/PublicSuffixListParser.java @@ -0,0 +1,114 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.conn.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.annotation.Immutable; + +/** + * Parses the list from <a href="http://publicsuffix.org/">publicsuffix.org</a> + * and configures a PublicSuffixFilter. + * + * @since 4.4 + */ +@Immutable +public final class PublicSuffixListParser { + + private static final int MAX_LINE_LEN = 256; + + public PublicSuffixListParser() { + } + + /** + * Parses the public suffix list format. When creating the reader from the file, make sure to + * use the correct encoding (the original list is in UTF-8). + * + * @param reader the data reader. The caller is responsible for closing the reader. + * @throws java.io.IOException on error while reading from list + */ + public PublicSuffixList parse(final Reader reader) throws IOException { + final List<String> rules = new ArrayList<String>(); + final List<String> exceptions = new ArrayList<String>(); + final BufferedReader r = new BufferedReader(reader); + final StringBuilder sb = new StringBuilder(256); + boolean more = true; + while (more) { + more = readLine(r, sb); + String line = sb.toString(); + if (line.isEmpty()) { + continue; + } + if (line.startsWith("//")) { + continue; //entire lines can also be commented using // + } + if (line.startsWith(".")) { + line = line.substring(1); // A leading dot is optional + } + // An exclamation mark (!) at the start of a rule marks an exception to a previous wildcard rule + final boolean isException = line.startsWith("!"); + if (isException) { + line = line.substring(1); + } + + if (isException) { + exceptions.add(line); + } else { + rules.add(line); + } + } + return new PublicSuffixList(rules, exceptions); + } + + private boolean readLine(final Reader r, final StringBuilder sb) throws IOException { + sb.setLength(0); + int b; + boolean hitWhitespace = false; + while ((b = r.read()) != -1) { + final char c = (char) b; + if (c == '\n') { + break; + } + // Each line is only read up to the first whitespace + if (Character.isWhitespace(c)) { + hitWhitespace = true; + } + if (!hitWhitespace) { + sb.append(c); + } + if (sb.length() > MAX_LINE_LEN) { + return false; // prevent excess memory usage + } + } + return (b != -1); + } + +} diff --git a/core/java/src/org/apache/http/conn/util/PublicSuffixMatcher.java b/core/java/src/org/apache/http/conn/util/PublicSuffixMatcher.java new file mode 100644 index 0000000000..02393aca4d --- /dev/null +++ b/core/java/src/org/apache/http/conn/util/PublicSuffixMatcher.java @@ -0,0 +1,121 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.conn.util; + +import java.net.IDN; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.http.annotation.ThreadSafe; +import org.apache.http.util.Args; + +/** + * Utility class that can test if DNS names match the content of the Public Suffix List. + * <p> + * An up-to-date list of suffixes can be obtained from + * <a href="http://publicsuffix.org/">publicsuffix.org</a> + * + * @see org.apache.http.conn.util.PublicSuffixList + * + * @since 4.4 + */ +@ThreadSafe +public final class PublicSuffixMatcher { + + private final Map<String, String> rules; + private final Map<String, String> exceptions; + + public PublicSuffixMatcher(final Collection<String> rules, final Collection<String> exceptions) { + Args.notNull(rules, "Domain suffix rules"); + this.rules = new ConcurrentHashMap<String, String>(rules.size()); + for (String rule: rules) { + this.rules.put(rule, rule); + } + if (exceptions != null) { + this.exceptions = new ConcurrentHashMap<String, String>(exceptions.size()); + for (String exception: exceptions) { + this.exceptions.put(exception, exception); + } + } else { + this.exceptions = null; + } + } + + /** + * Returns registrable part of the domain for the given domain name of {@code null} + * if given domain represents a public suffix. + * + * @param domain + * @return domain root + */ + public String getDomainRoot(final String domain) { + if (domain == null) { + return null; + } + if (domain.startsWith(".")) { + return null; + } + String domainName = null; + String segment = domain.toLowerCase(Locale.ROOT); + while (segment != null) { + + // An exception rule takes priority over any other matching rule. + if (this.exceptions != null && this.exceptions.containsKey(IDN.toUnicode(segment))) { + return segment; + } + + if (this.rules.containsKey(IDN.toUnicode(segment))) { + break; + } + + final int nextdot = segment.indexOf('.'); + final String nextSegment = nextdot != -1 ? segment.substring(nextdot + 1) : null; + + if (nextSegment != null) { + if (this.rules.containsKey("*." + IDN.toUnicode(nextSegment))) { + break; + } + } + if (nextdot != -1) { + domainName = segment; + } + segment = nextSegment; + } + return domainName; + } + + public boolean matches(final String domain) { + if (domain == null) { + return false; + } + final String domainRoot = getDomainRoot(domain.startsWith(".") ? domain.substring(1) : domain); + return domainRoot == null; + } + +} diff --git a/core/java/src/org/apache/http/util/Args.java b/core/java/src/org/apache/http/util/Args.java new file mode 100644 index 0000000000..9eb8a251ff --- /dev/null +++ b/core/java/src/org/apache/http/util/Args.java @@ -0,0 +1,127 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.util; + +import java.util.Collection; + +public class Args { + + public static void check(final boolean expression, final String message) { + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + public static void check(final boolean expression, final String message, final Object... args) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, args)); + } + } + + public static void check(final boolean expression, final String message, final Object arg) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, arg)); + } + } + + public static <T> T notNull(final T argument, final String name) { + if (argument == null) { + throw new IllegalArgumentException(name + " may not be null"); + } + return argument; + } + + public static <T extends CharSequence> T notEmpty(final T argument, final String name) { + if (argument == null) { + throw new IllegalArgumentException(name + " may not be null"); + } + if (TextUtils.isEmpty(argument)) { + throw new IllegalArgumentException(name + " may not be empty"); + } + return argument; + } + + public static <T extends CharSequence> T notBlank(final T argument, final String name) { + if (argument == null) { + throw new IllegalArgumentException(name + " may not be null"); + } + if (TextUtils.isBlank(argument)) { + throw new IllegalArgumentException(name + " may not be blank"); + } + return argument; + } + + public static <T extends CharSequence> T containsNoBlanks(final T argument, final String name) { + if (argument == null) { + throw new IllegalArgumentException(name + " may not be null"); + } + if (TextUtils.containsBlanks(argument)) { + throw new IllegalArgumentException(name + " may not contain blanks"); + } + return argument; + } + + public static <E, T extends Collection<E>> T notEmpty(final T argument, final String name) { + if (argument == null) { + throw new IllegalArgumentException(name + " may not be null"); + } + if (argument.isEmpty()) { + throw new IllegalArgumentException(name + " may not be empty"); + } + return argument; + } + + public static int positive(final int n, final String name) { + if (n <= 0) { + throw new IllegalArgumentException(name + " may not be negative or zero"); + } + return n; + } + + public static long positive(final long n, final String name) { + if (n <= 0) { + throw new IllegalArgumentException(name + " may not be negative or zero"); + } + return n; + } + + public static int notNegative(final int n, final String name) { + if (n < 0) { + throw new IllegalArgumentException(name + " may not be negative"); + } + return n; + } + + public static long notNegative(final long n, final String name) { + if (n < 0) { + throw new IllegalArgumentException(name + " may not be negative"); + } + return n; + } + +} -- GitLab