();
+ }
+ 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
+ * .
+ *
+ */
+
+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
+ * .
+ *
+ */
+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
+ *
+ * An up-to-date list of suffixes can be obtained from
+ * publicsuffix.org
+ *
+ * @since 4.4
+ */
+@Immutable
+public final class PublicSuffixList {
+
+ private final List rules;
+ private final List exceptions;
+
+ public PublicSuffixList(final List rules, final List exceptions) {
+ this.rules = Collections.unmodifiableList(Args.notNull(rules, "Domain suffix rules"));
+ this.exceptions = Collections.unmodifiableList(Args.notNull(exceptions, "Domain suffix exceptions"));
+ }
+
+ public List getRules() {
+ return rules;
+ }
+
+ public List 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
+ * .
+ *
+ */
+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 publicsuffix.org
+ * 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 rules = new ArrayList();
+ final List exceptions = new ArrayList();
+ 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
+ * .
+ *
+ */
+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.
+ *
+ * An up-to-date list of suffixes can be obtained from
+ * publicsuffix.org
+ *
+ * @see org.apache.http.conn.util.PublicSuffixList
+ *
+ * @since 4.4
+ */
+@ThreadSafe
+public final class PublicSuffixMatcher {
+
+ private final Map rules;
+ private final Map exceptions;
+
+ public PublicSuffixMatcher(final Collection rules, final Collection exceptions) {
+ Args.notNull(rules, "Domain suffix rules");
+ this.rules = new ConcurrentHashMap(rules.size());
+ for (String rule: rules) {
+ this.rules.put(rule, rule);
+ }
+ if (exceptions != null) {
+ this.exceptions = new ConcurrentHashMap(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
+ * .
+ *
+ */
+
+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 notNull(final T argument, final String name) {
+ if (argument == null) {
+ throw new IllegalArgumentException(name + " may not be null");
+ }
+ return argument;
+ }
+
+ public static 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 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 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 > 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;
+ }
+
+}