Expand Up @@ -26,6 +26,7 @@
*/
package org.apache.http.impl.client.cache;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
Expand All @@ -38,7 +39,6 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
Expand Down Expand Up @@ -72,25 +72,182 @@ public void canSerializeEntriesWithVariantMaps() throws Exception {
readWriteVerify(makeCacheEntryWithVariantMap());
}

@Test(expected = HttpCacheEntrySerializationException.class)
public void throwExceptionIfUnsafeDeserialization() throws IOException {
impl.readFrom(new ByteArrayInputStream(serializeProhibitedObject()));
@Test
public void isAllowedClassNameStringTrue() {
assertIsAllowedClassNameTrue(String.class.getName());
}

@Test(expected = HttpCacheEntrySerializationException.class)
public void allowClassesToBeDeserialized() throws IOException {
impl = new DefaultHttpCacheEntrySerializer(
Pattern.compile("javax.sql.rowset.BaseRowSet"),
Pattern.compile("com.sun.rowset.JdbcRowSetImpl"));
readVerify(serializeProhibitedObject());
@Test
public void isAllowedClassNameStringArrayTrue() {
assertIsAllowedClassNameTrue("[L" + String.class.getName());
}

@Test
public void isAllowedClassNameStringArrayArrayTrue() {
assertIsAllowedClassNameTrue("[[L" + String.class.getName());
}

@Test
public void isAllowedClassNameDataTrue() {
assertIsAllowedClassNameTrue(Date.class.getName());
}

@Test
public void isAllowedClassNameStatusLineTrue() {
assertIsAllowedClassNameTrue(StatusLine.class.getName());
}

@Test
public void isAllowedClassNameResourceTrue() {
assertIsAllowedClassNameTrue(Resource.class.getName());
}

@Test
public void isAllowedClassNameByteArrayTrue() {
assertIsAllowedClassNameTrue("[B");
}

@Test
public void isAllowedClassNameByteArrayArrayTrue() {
assertIsAllowedClassNameTrue("[[B");
}

@Test
public void isAllowedClassNameCharArrayTrue() {
assertIsAllowedClassNameTrue("[C");
}

@Test
public void isAllowedClassNameCharArrayArrayTrue() {
assertIsAllowedClassNameTrue("[[C");
}

@Test
public void isAllowedClassNameDoubleArrayTrue() {
assertIsAllowedClassNameTrue("[D");
}

@Test
public void isAllowedClassNameDoubleArrayArrayTrue() {
assertIsAllowedClassNameTrue("[[D");
}

@Test
public void isAllowedClassNameFloatArrayTrue() {
assertIsAllowedClassNameTrue("[F");
}

@Test
public void isAllowedClassNameFloatArrayArrayTrue() {
assertIsAllowedClassNameTrue("[[F");
}

@Test
public void isAllowedClassNameIntArrayTrue() {
assertIsAllowedClassNameTrue("[I");
}

@Test
public void isAllowedClassNameIntArrayArrayTrue() {
assertIsAllowedClassNameTrue("[[I");
}

@Test
public void isAllowedClassNameLongArrayTrue() {
assertIsAllowedClassNameTrue("[J");
}

@Test
public void isAllowedClassNameLongArrayArrayTrue() {
assertIsAllowedClassNameTrue("[[J");
}

@Test
public void isAllowedClassNameShortArrayTrue() {
assertIsAllowedClassNameTrue("[S");
}

@Test
public void isAllowedClassNameShortArrayArrayTrue() {
assertIsAllowedClassNameTrue("[[S");
}

@Test
public void isAllowedClassNameCollectionsInvokerTransformerFalse() {
assertIsAllowedClassNameFalse("org.apache.commons.collections.functors.InvokerTransformer");
}

@Test
public void isAllowedClassNameCollections4InvokerTransformerFalse() {
assertIsAllowedClassNameFalse("org.apache.commons.collections4.functors.InvokerTransformer");
}

@Test
public void isAllowedClassNameCollectionsInstantiateTransformerFalse() {
assertIsAllowedClassNameFalse("org.apache.commons.collections.functors.InstantiateTransformer");
}

@Test
public void isAllowedClassNameCollections4InstantiateTransformerFalse() {
assertIsAllowedClassNameFalse("org.apache.commons.collections4.functors.InstantiateTransformer");
}

@Test
public void isAllowedClassNameGroovyConvertedClosureFalse() {
assertIsAllowedClassNameFalse("org.codehaus.groovy.runtime.ConvertedClosure");
}

@Test
public void isAllowedClassNameGroovyMethodClosureFalse() {
assertIsAllowedClassNameFalse("org.codehaus.groovy.runtime.MethodClosure");
}

@Test
public void isAllowedClassNameSpringObjectFactoryFalse() {
assertIsAllowedClassNameFalse("org.springframework.beans.factory.ObjectFactory");
}

@Test
public void isAllowedClassNameCalanTemplatesImplFalse() {
assertIsAllowedClassNameFalse("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
}

@Test
public void isAllowedClassNameCalanTemplatesImplArrayFalse() {
assertIsAllowedClassNameFalse("[Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
}

@Test
public void isAllowedClassNameJavaRmiRegistryFalse() {
assertIsAllowedClassNameFalse("java.rmi.registry.Registry");
}

@Test
public void isAllowedClassNameJavaRmiServerRemoteObjectInvocationHandlerFalse() {
assertIsAllowedClassNameFalse("java.rmi.server.RemoteObjectInvocationHandler");
}

@Test
public void isAllowedClassNameJavaxXmlTransformTemplatesFalse() {
assertIsAllowedClassNameFalse("javax.xml.transform.Templates");
}

@Test
public void isAllowedClassNameJavaxManagementMBeanServerInvocationHandlerFalse() {
assertIsAllowedClassNameFalse("javax.management.MBeanServerInvocationHandler");
}

private static void assertIsAllowedClassNameTrue(final String className) {
assertTrue(DefaultHttpCacheEntrySerializer.RestrictedObjectInputStream.isAllowedClassName(className));
}

private static void assertIsAllowedClassNameFalse(final String className) {
assertFalse(DefaultHttpCacheEntrySerializer.RestrictedObjectInputStream.isAllowedClassName(className));
}

@Test(expected = HttpCacheEntrySerializationException.class)
public void allowClassesToBeDeserializedByRegex() throws IOException {
impl = new DefaultHttpCacheEntrySerializer(
Pattern.compile(("^com\\.sun\\.rowset\\.(.*)")),
Pattern.compile("^javax\\.sql\\.rowset\\.BaseRowSet$"));
readVerify(serializeProhibitedObject());
public void throwExceptionIfUnsafeDeserialization() throws IOException {
impl.readFrom(new ByteArrayInputStream(serializeProhibitedObject()));
}

private byte[] serializeProhibitedObject() throws IOException {
Expand All @@ -105,11 +262,7 @@ private byte[] serializeProhibitedObject() throws IOException {
return baos.toByteArray();
}

private void readVerify(final byte[] data) throws IOException {
impl.readFrom(new ByteArrayInputStream(data));
}

public void readWriteVerify(final HttpCacheEntry writeEntry) throws IOException {
private void readWriteVerify(final HttpCacheEntry writeEntry) throws IOException {
// write the entry
final ByteArrayOutputStream out = new ByteArrayOutputStream();
impl.writeTo(writeEntry, out);
Expand Down
2 changes: 1 addition & 1 deletion httpclient-osgi/pom.xml
Expand Up @@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
<version>4.5.10-SNAPSHOT</version>
<version>4.5.13</version>
</parent>
<artifactId>httpclient-osgi</artifactId>
<name>Apache HttpClient OSGi bundle</name>
Expand Down
2 changes: 1 addition & 1 deletion httpclient-win/pom.xml
Expand Up @@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
<version>4.5.10-SNAPSHOT</version>
<version>4.5.13</version>
</parent>
<artifactId>httpclient-win</artifactId>
<name>Apache HttpClient Windows features</name>
Expand Down
2 changes: 1 addition & 1 deletion httpclient/pom.xml
Expand Up @@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
<version>4.5.10-SNAPSHOT</version>
<version>4.5.13</version>
</parent>
<artifactId>httpclient</artifactId>
<name>Apache HttpClient</name>
Expand Down
Expand Up @@ -34,6 +34,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.http.Consts;
import org.apache.http.NameValuePair;
import org.apache.http.conn.util.InetAddressUtils;
Expand Down Expand Up @@ -78,16 +79,34 @@ public URIBuilder() {
* @throws URISyntaxException if the input is not a valid URI
*/
public URIBuilder(final String string) throws URISyntaxException {
super();
digestURI(new URI(string));
this(new URI(string), null);
}

/**
* Construct an instance from the provided URI.
* @param uri
*/
public URIBuilder(final URI uri) {
this(uri, null);
}

/**
* Construct an instance from the string which must be a valid URI.
*
* @param string a valid URI in string form
* @throws URISyntaxException if the input is not a valid URI
*/
public URIBuilder(final String string, final Charset charset) throws URISyntaxException {
this(new URI(string), charset);
}

/**
* Construct an instance from the provided URI.
* @param uri
*/
public URIBuilder(final URI uri, final Charset charset) {
super();
setCharset(charset);
digestURI(uri);
}

Expand Down Expand Up @@ -497,7 +516,7 @@ public boolean isAbsolute() {
* @since 4.3
*/
public boolean isOpaque() {
return isPathEmpty();
return this.pathSegments == null && this.encodedPath == null;
}

public String getScheme() {
Expand Down
69 changes: 28 additions & 41 deletions httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java
Expand Up @@ -419,56 +419,43 @@ public static HttpHost extractHost(final URI uri) {
if (uri == null) {
return null;
}
HttpHost target = null;
if (uri.isAbsolute()) {
int port = uri.getPort(); // may be overridden later
String host = uri.getHost();
if (host == null) { // normal parse failed; let's do it ourselves
if (uri.getHost() == null) { // normal parse failed; let's do it ourselves
// authority does not seem to care about the valid character-set for host names
host = uri.getAuthority();
if (host != null) {
if (uri.getAuthority() != null) {
String content = uri.getAuthority();
// Strip off any leading user credentials
final int at = host.indexOf('@');
if (at >= 0) {
if (host.length() > at+1 ) {
host = host.substring(at+1);
} else {
host = null; // @ on its own
}
int at = content.indexOf('@');
if (at != -1) {
content = content.substring(at + 1);
}
// Extract the port suffix, if present
if (host != null) {
final int colon = host.indexOf(':');
if (colon >= 0) {
final int pos = colon + 1;
int len = 0;
for (int i = pos; i < host.length(); i++) {
if (Character.isDigit(host.charAt(i))) {
len++;
} else {
break;
}
}
if (len > 0) {
try {
port = Integer.parseInt(host.substring(pos, pos + len));
} catch (final NumberFormatException ex) {
}
}
host = host.substring(0, colon);
final String scheme = uri.getScheme();
final String hostname;
final int port;
at = content.indexOf(":");
if (at != -1) {
hostname = content.substring(0, at);
try {
final String portText = content.substring(at + 1);
port = !TextUtils.isEmpty(portText) ? Integer.parseInt(portText) : -1;
} catch (final NumberFormatException ex) {
return null;
}
} else {
hostname = content;
port = -1;
}
try {
return new HttpHost(hostname, port, scheme);
} catch (final IllegalArgumentException ex) {
return null;
}
}
}
final String scheme = uri.getScheme();
if (!TextUtils.isBlank(host)) {
try {
target = new HttpHost(host, port, scheme);
} catch (final IllegalArgumentException ignore) {
}
} else {
return new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
}
}
return target;
return null;
}

/**
Expand Down
Expand Up @@ -36,7 +36,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;

import javax.naming.InvalidNameException;
Expand All @@ -55,6 +54,7 @@
import org.apache.commons.logging.LogFactory;
import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.conn.util.DnsUtils;
import org.apache.http.conn.util.DomainType;
import org.apache.http.conn.util.InetAddressUtils;
import org.apache.http.conn.util.PublicSuffixMatcher;
Expand Down Expand Up @@ -164,11 +164,11 @@ static void matchIPv6Address(final String host, final List<SubjectName> subjectA

static void matchDNSName(final String host, final List<SubjectName> subjectAlts,
final PublicSuffixMatcher publicSuffixMatcher) throws SSLException {
final String normalizedHost = host.toLowerCase(Locale.ROOT);
final String normalizedHost = DnsUtils.normalize(host);
for (int i = 0; i < subjectAlts.size(); i++) {
final SubjectName subjectAlt = subjectAlts.get(i);
if (subjectAlt.getType() == SubjectName.DNS) {
final String normalizedSubjectAlt = subjectAlt.getValue().toLowerCase(Locale.ROOT);
final String normalizedSubjectAlt = DnsUtils.normalize(subjectAlt.getValue());
if (matchIdentityStrict(normalizedHost, normalizedSubjectAlt, publicSuffixMatcher)) {
return;
}
Expand All @@ -180,8 +180,8 @@ static void matchDNSName(final String host, final List<SubjectName> subjectAlts,

static void matchCN(final String host, final String cn,
final PublicSuffixMatcher publicSuffixMatcher) throws SSLException {
final String normalizedHost = host.toLowerCase(Locale.ROOT);
final String normalizedCn = cn.toLowerCase(Locale.ROOT);
final String normalizedHost = DnsUtils.normalize(host);
final String normalizedCn = DnsUtils.normalize(cn);
if (!matchIdentityStrict(normalizedHost, normalizedCn, publicSuffixMatcher)) {
throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match " +
"common name of the certificate subject: " + cn);
Expand All @@ -198,9 +198,10 @@ static boolean matchDomainRoot(final String host, final String domainRoot) {

private static boolean matchIdentity(final String host, final String identity,
final PublicSuffixMatcher publicSuffixMatcher,
final DomainType domainType,
final boolean strict) {
if (publicSuffixMatcher != null && host.contains(".")) {
if (!matchDomainRoot(host, publicSuffixMatcher.getDomainRoot(identity, DomainType.ICANN))) {
if (!matchDomainRoot(host, publicSuffixMatcher.getDomainRoot(identity, domainType))) {
return false;
}
}
Expand Down Expand Up @@ -235,20 +236,32 @@ private static boolean matchIdentity(final String host, final String identity,

static boolean matchIdentity(final String host, final String identity,
final PublicSuffixMatcher publicSuffixMatcher) {
return matchIdentity(host, identity, publicSuffixMatcher, false);
return matchIdentity(host, identity, publicSuffixMatcher, null, false);
}

static boolean matchIdentity(final String host, final String identity) {
return matchIdentity(host, identity, null, false);
return matchIdentity(host, identity, null, null, false);
}

static boolean matchIdentityStrict(final String host, final String identity,
final PublicSuffixMatcher publicSuffixMatcher) {
return matchIdentity(host, identity, publicSuffixMatcher, true);
return matchIdentity(host, identity, publicSuffixMatcher, null, true);
}

static boolean matchIdentityStrict(final String host, final String identity) {
return matchIdentity(host, identity, null, true);
return matchIdentity(host, identity, null, null, true);
}

static boolean matchIdentity(final String host, final String identity,
final PublicSuffixMatcher publicSuffixMatcher,
final DomainType domainType) {
return matchIdentity(host, identity, publicSuffixMatcher, domainType, false);
}

static boolean matchIdentityStrict(final String host, final String identity,
final PublicSuffixMatcher publicSuffixMatcher,
final DomainType domainType) {
return matchIdentity(host, identity, publicSuffixMatcher, domainType, true);
}

static String extractCN(final String subjectPrincipal) throws SSLException {
Expand Down
76 changes: 76 additions & 0 deletions httpclient/src/main/java/org/apache/http/conn/util/DnsUtils.java
@@ -0,0 +1,76 @@
/*
* ====================================================================
* 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;

/**
* A collection of utilities relating to Domain Name System.
*
* @since 4.5
*/
public class DnsUtils {

private DnsUtils() {
}

private static boolean isUpper(final char c) {
return c >= 'A' && c <= 'Z';
}

public static String normalize(final String s) {
if (s == null) {
return null;
}
int pos = 0;
int remaining = s.length();
while (remaining > 0) {
if (isUpper(s.charAt(pos))) {
break;
}
pos++;
remaining--;
}
if (remaining > 0) {
final StringBuilder buf = new StringBuilder(s.length());
buf.append(s, 0, pos);
while (remaining > 0) {
final char c = s.charAt(pos);
if (isUpper(c)) {
buf.append((char) (c + ('a' - 'A')));
} else {
buf.append(c);
}
pos++;
remaining--;
}
return buf.toString();
} else {
return s;
}
}

}
Expand Up @@ -29,7 +29,6 @@
import java.net.IDN;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

Expand Down Expand Up @@ -98,20 +97,15 @@ public PublicSuffixMatcher(final Collection<PublicSuffixList> lists) {
}
}

private static boolean hasEntry(final Map<String, DomainType> map, final String rule, final DomainType expectedType) {
private static DomainType findEntry(final Map<String, DomainType> map, final String rule) {
if (map == null) {
return false;
return null;
}
final DomainType domainType = map.get(rule);
return domainType == null ? false : expectedType == null || domainType.equals(expectedType);
}

private boolean hasRule(final String rule, final DomainType expectedType) {
return hasEntry(this.rules, rule, expectedType);
return map.get(rule);
}

private boolean hasException(final String exception, final DomainType expectedType) {
return hasEntry(this.exceptions, exception, expectedType);
private static boolean match(final DomainType domainType, final DomainType expectedType) {
return domainType != null && (expectedType == null || domainType.equals(expectedType));
}

/**
Expand Down Expand Up @@ -142,31 +136,47 @@ public String getDomainRoot(final String domain, final DomainType expectedType)
if (domain.startsWith(".")) {
return null;
}
final String normalized = domain.toLowerCase(Locale.ROOT);
final String normalized = DnsUtils.normalize(domain);
String segment = normalized;
String result = null;
while (segment != null) {
// An exception rule takes priority over any other matching rule.
final String key = IDN.toUnicode(segment);
if (hasException(key, expectedType)) {
final DomainType exceptionRule = findEntry(exceptions, key);
if (match(exceptionRule, expectedType)) {
return segment;
}
if (hasRule(key, expectedType)) {
final DomainType domainRule = findEntry(rules, key);
if (match(domainRule, expectedType)) {
if (domainRule == DomainType.PRIVATE) {
return segment;
}
return result;
}

final int nextdot = segment.indexOf('.');
final String nextSegment = nextdot != -1 ? segment.substring(nextdot + 1) : null;

if (nextSegment != null) {
if (hasRule("*." + IDN.toUnicode(nextSegment), expectedType)) {
final DomainType wildcardDomainRule = findEntry(rules, "*." + IDN.toUnicode(nextSegment));
if (match(wildcardDomainRule, expectedType)) {
if (wildcardDomainRule == DomainType.PRIVATE) {
return segment;
}
return result;
}
}
result = segment;
segment = nextSegment;
}
return result;

// If no expectations then this result is good.
if (expectedType == null || expectedType == DomainType.UNKNOWN) {
return result;
}

// If we did have expectations apparently there was no match
return null;
}

/**
Expand Down
Expand Up @@ -95,7 +95,7 @@ public static PublicSuffixMatcher getDefault() {
}
}
} else {
DEFAULT_INSTANCE = new PublicSuffixMatcher(Arrays.asList("com"), null);
DEFAULT_INSTANCE = new PublicSuffixMatcher(DomainType.ICANN, Arrays.asList("com"), null);
}
}
}
Expand Down
Expand Up @@ -172,7 +172,8 @@ static String getType1Message(final String host, final String domain) {
* the 8 byte array the server sent.
* @return The type 3 message.
* @throws NTLMEngineException
* If {@encrypt(byte[],byte[])} fails.
* If {@link #Type3Message
* (String, String, String, String, byte[], int, String, byte[])} fails.
*/
static String getType3Message(final String user, final String password, final String host, final String domain,
final byte[] nonce, final int type2Flags, final String target, final byte[] targetInformation)
Expand All @@ -199,7 +200,8 @@ static String getType3Message(final String user, final String password, final St
* the 8 byte array the server sent.
* @return The type 3 message.
* @throws NTLMEngineException
* If {@encrypt(byte[],byte[])} fails.
* If {@link #Type3Message
* (String, String, String, String, byte[], int, String, byte[], Certificate, byte[], byte[])} fails.
*/
static String getType3Message(final String user, final String password, final String host, final String domain,
final byte[] nonce, final int type2Flags, final String target, final byte[] targetInformation,
Expand Down
Expand Up @@ -49,7 +49,8 @@ public boolean shouldBackoff(final Throwable t) {

@Override
public boolean shouldBackoff(final HttpResponse resp) {
return resp.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE;
return resp.getStatusLine().getStatusCode() == 429 ||
resp.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE;
}

}
Expand Up @@ -74,6 +74,8 @@ public class DefaultRedirectStrategy implements RedirectStrategy {

private final Log log = LogFactory.getLog(getClass());

public static final int SC_PERMANENT_REDIRECT = 308;

/**
* @deprecated (4.3) use {@link org.apache.http.client.protocol.HttpClientContext#REDIRECT_LOCATIONS}.
*/
Expand Down Expand Up @@ -120,6 +122,7 @@ public boolean isRedirected(
return isRedirectable(method) && locationHeader != null;
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_TEMPORARY_REDIRECT:
case SC_PERMANENT_REDIRECT:
return isRedirectable(method);
case HttpStatus.SC_SEE_OTHER:
return true;
Expand Down Expand Up @@ -225,7 +228,7 @@ public HttpUriRequest getRedirect(
return new HttpGet(uri);
} else {
final int status = response.getStatusLine().getStatusCode();
return status == HttpStatus.SC_TEMPORARY_REDIRECT
return (status == HttpStatus.SC_TEMPORARY_REDIRECT || status == SC_PERMANENT_REDIRECT)
? RequestBuilder.copy(request).setUri(uri).build()
: new HttpGet(uri);
}
Expand Down
Expand Up @@ -362,7 +362,19 @@ public synchronized void closeIdleConnections(final long idletime, final TimeUni

@Override
public void shutdown() {
close();
if (this.isShutdown.compareAndSet(false, true)) {
if (this.conn != null) {
this.log.debug("Shutting down connection");
try {
this.conn.shutdown();
} catch (final IOException iox) {
if (this.log.isDebugEnabled()) {
this.log.debug("I/O exception shutting down connection", iox);
}
}
this.conn = null;
}
}
}

}
Expand Up @@ -63,6 +63,7 @@
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.pool.ConnFactory;
import org.apache.http.pool.ConnPoolControl;
import org.apache.http.pool.PoolEntry;
import org.apache.http.pool.PoolEntryCallback;
import org.apache.http.pool.PoolStats;
import org.apache.http.protocol.HttpContext;
Expand Down Expand Up @@ -227,7 +228,7 @@ private String formatStats(final HttpRoute route) {
final StringBuilder buf = new StringBuilder();
final PoolStats totals = this.pool.getTotalStats();
final PoolStats stats = this.pool.getStats(route);
buf.append("[total kept alive: ").append(totals.getAvailable()).append("; ");
buf.append("[total available: ").append(totals.getAvailable()).append("; ");
buf.append("route allocated: ").append(stats.getLeased() + stats.getAvailable());
buf.append(" of ").append(stats.getMax()).append("; ");
buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable());
Expand Down Expand Up @@ -265,6 +266,7 @@ public ConnectionRequest requestConnection(
if (this.log.isDebugEnabled()) {
this.log.debug("Connection request: " + format(route, state) + formatStats(route));
}
Asserts.check(!this.isShutDown.get(), "Connection pool shut down");
final Future<CPoolEntry> future = this.pool.lease(route, state, null);
return new ConnectionRequest() {

Expand Down Expand Up @@ -408,6 +410,23 @@ public void shutdown() {
if (this.isShutDown.compareAndSet(false, true)) {
this.log.debug("Connection manager is shutting down");
try {
this.pool.enumLeased(new PoolEntryCallback<HttpRoute, ManagedHttpClientConnection>() {

@Override
public void process(final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry) {
final ManagedHttpClientConnection connection = entry.getConnection();
if (connection != null) {
try {
connection.shutdown();
} catch (final IOException iox) {
if (log.isDebugEnabled()) {
log.debug("I/O exception shutting down connection", iox);
}
}
}
}

});
this.pool.shutdown();
} catch (final IOException ex) {
this.log.debug("I/O exception shutting down connection manager", ex);
Expand Down
Expand Up @@ -45,11 +45,11 @@
public class BasicExpiresHandler extends AbstractCookieAttributeHandler implements CommonCookieAttributeHandler {

/** Valid date patterns */
private final String[] datepatterns;
private final String[] datePatterns;

public BasicExpiresHandler(final String[] datepatterns) {
Args.notNull(datepatterns, "Array of date patterns");
this.datepatterns = datepatterns;
public BasicExpiresHandler(final String[] datePatterns) {
Args.notNull(datePatterns, "Array of date patterns");
this.datePatterns = datePatterns.clone();
}

@Override
Expand All @@ -59,7 +59,7 @@ public void parse(final SetCookie cookie, final String value)
if (value == null) {
throw new MalformedCookieException("Missing value for 'expires' attribute");
}
final Date expiry = DateUtils.parseDate(value, this.datepatterns);
final Date expiry = DateUtils.parseDate(value, this.datePatterns);
if (expiry == null) {
throw new MalformedCookieException("Invalid 'expires' attribute: "
+ value);
Expand Down
Expand Up @@ -43,6 +43,7 @@
import org.apache.http.cookie.SetCookie;
import org.apache.http.message.ParserCursor;
import org.apache.http.util.Args;
import org.apache.http.util.TextUtils;

/**
*
Expand Down Expand Up @@ -105,6 +106,9 @@ public LaxExpiresHandler() {
@Override
public void parse(final SetCookie cookie, final String value) throws MalformedCookieException {
Args.notNull(cookie, "Cookie");
if (TextUtils.isBlank(value)) {
return;
}
final ParserCursor cursor = new ParserCursor(0, value.length());
final StringBuilder content = new StringBuilder();

Expand Down
Expand Up @@ -112,7 +112,12 @@ public CloseableHttpResponse execute(
try {
if (config.isRedirectsEnabled() &&
this.redirectStrategy.isRedirected(currentRequest.getOriginal(), response, context)) {

if (!RequestEntityProxy.isRepeatable(currentRequest)) {
if (log.isDebugEnabled()) {
log.debug("Cannot redirect non-repeatable request");
}
return response;
}
if (redirectCount >= maxRedirects) {
throw new RedirectException("Maximum redirects ("+ maxRedirects + ") exceeded");
}
Expand Down
Expand Up @@ -348,4 +348,11 @@ public void testPathNoLeadingSlash() throws Exception {
Assert.assertThat(uri, CoreMatchers.equalTo(URI.create("ftp:/blah")));
}

@Test
public void testOpaque() throws Exception {
final URIBuilder uriBuilder = new URIBuilder("http://host.com");
final URI uri = uriBuilder.build();
Assert.assertThat(uriBuilder.isOpaque(), CoreMatchers.equalTo(uri.isOpaque()));
}

}
Expand Up @@ -273,14 +273,16 @@ public void testExtractHost() throws Exception {

Assert.assertEquals(new HttpHost("localhost",8080),
URIUtils.extractHost(new URI("http://localhost:8080/;sessionid=stuff/abcd")));
Assert.assertEquals(new HttpHost("localhost",8080),
Assert.assertEquals(null,
URIUtils.extractHost(new URI("http://localhost:8080;sessionid=stuff/abcd")));
Assert.assertEquals(new HttpHost("localhost",-1),
Assert.assertEquals(null,
URIUtils.extractHost(new URI("http://localhost:;sessionid=stuff/abcd")));
Assert.assertEquals(null,
URIUtils.extractHost(new URI("http://:80/robots.txt")));
Assert.assertEquals(null,
URIUtils.extractHost(new URI("http://some%20domain:80/robots.txt")));
Assert.assertEquals(null,
URIUtils.extractHost(new URI("http://blah@goggle.com:80@google.com/")));
}

@Test
Expand Down
Expand Up @@ -28,14 +28,21 @@
package org.apache.http.conn.ssl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.net.ssl.SSLException;

import org.apache.http.conn.util.DomainType;
import org.apache.http.conn.util.PublicSuffixList;
import org.apache.http.conn.util.PublicSuffixListParser;
import org.apache.http.conn.util.PublicSuffixMatcher;
import org.junit.Assert;
import org.junit.Before;
Expand All @@ -50,10 +57,22 @@ public class TestDefaultHostnameVerifier {
private PublicSuffixMatcher publicSuffixMatcher;
private DefaultHostnameVerifier implWithPublicSuffixCheck;

private static final String PUBLIC_SUFFIX_MATCHER_SOURCE_FILE = "suffixlistmatcher.txt";

private static final Charset UTF_8 = Charset.forName("UTF-8");

@Before
public void setup() {
public void setup() throws IOException {
impl = new DefaultHostnameVerifier();
publicSuffixMatcher = new PublicSuffixMatcher(DomainType.ICANN, Arrays.asList("com", "co.jp", "gov.uk"), null);

// Load the test PublicSuffixMatcher
final ClassLoader classLoader = getClass().getClassLoader();
final InputStream in = classLoader.getResourceAsStream(PUBLIC_SUFFIX_MATCHER_SOURCE_FILE);
Assert.assertNotNull(in);
final List<PublicSuffixList> lists = new PublicSuffixListParser().parseByType(
new InputStreamReader(in, UTF_8));
publicSuffixMatcher = new PublicSuffixMatcher(lists);

implWithPublicSuffixCheck = new DefaultHostnameVerifier(publicSuffixMatcher);
}

Expand Down Expand Up @@ -275,16 +294,87 @@ public void testHTTPCLIENT_1255() {
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict("mail.a.b.c.com", "m*.a.b.c.com"));
}


@Test
public void testHTTPCLIENT_1997() {
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity(
"service.apps.dev.b.cloud.a", "*.apps.dev.b.cloud.a"));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(
"service.apps.dev.b.cloud.a", "*.apps.dev.b.cloud.a"));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity(
"service.apps.dev.b.cloud.a", "*.apps.dev.b.cloud.a", publicSuffixMatcher));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict(
"service.apps.dev.b.cloud.a", "*.apps.dev.b.cloud.a", publicSuffixMatcher));
public void testHTTPCLIENT_1997_ANY() { // Only True on all domains
String domain;
// Unknown
domain = "dev.b.cloud.a";
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));

// ICANN
domain = "dev.b.cloud.com";
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));

// PRIVATE
domain = "dev.b.cloud.lan";
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher));
}

@Test
public void testHTTPCLIENT_1997_ICANN() { // Only True on ICANN domains
String domain;
// Unknown
domain = "dev.b.cloud.a";
Assert.assertFalse(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
Assert.assertFalse(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));

// ICANN
domain = "dev.b.cloud.com";
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));

// PRIVATE
domain = "dev.b.cloud.lan";
Assert.assertFalse(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
Assert.assertFalse(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.ICANN));
}

@Test
public void testHTTPCLIENT_1997_PRIVATE() { // Only True on PRIVATE domains
String domain;
// Unknown
domain = "dev.b.cloud.a";
Assert.assertFalse(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
Assert.assertFalse(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));

// ICANN
domain = "dev.b.cloud.com";
Assert.assertFalse(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
Assert.assertFalse(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));

// PRIVATE
domain = "dev.b.cloud.lan";
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.PRIVATE));
}

@Test
public void testHTTPCLIENT_1997_UNKNOWN() { // Only True on all domains (same as ANY)
String domain;
// Unknown
domain = "dev.b.cloud.a";
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));

// ICANN
domain = "dev.b.cloud.com";
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));

// PRIVATE
domain = "dev.b.cloud.lan";
Assert.assertTrue(DefaultHostnameVerifier.matchIdentity( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
Assert.assertTrue(DefaultHostnameVerifier.matchIdentityStrict( "service.apps." + domain, "*.apps." + domain, publicSuffixMatcher, DomainType.UNKNOWN));
}

@Test // Check compressed IPv6 hostname matching
Expand Down Expand Up @@ -329,4 +419,28 @@ public void testExtractCN() throws Exception {
}
}

@Test
public void testMatchDNSName() throws Exception {
DefaultHostnameVerifier.matchDNSName(
"host.domain.com",
Collections.singletonList(SubjectName.DNS("*.domain.com")),
publicSuffixMatcher);
DefaultHostnameVerifier.matchDNSName(
"host.xx",
Collections.singletonList(SubjectName.DNS("*.xx")),
publicSuffixMatcher);
DefaultHostnameVerifier.matchDNSName(
"host.appspot.com",
Collections.singletonList(SubjectName.DNS("*.appspot.com")),
publicSuffixMatcher);
DefaultHostnameVerifier.matchDNSName(
"demo-s3-bucket.s3.eu-central-1.amazonaws.com",
Collections.singletonList(SubjectName.DNS("*.s3.eu-central-1.amazonaws.com")),
publicSuffixMatcher);
DefaultHostnameVerifier.matchDNSName(
"hostname-workspace-1.local",
Collections.singletonList(SubjectName.DNS("hostname-workspace-1.local")),
publicSuffixMatcher);
}

}
@@ -0,0 +1,51 @@
/*
* ====================================================================
* 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 org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Test;

/**
* Unit tests for DnsUtils.
*/
public class TesDnsUtils {

@Test
public void testNormalize() {
Assert.assertThat(DnsUtils.normalize(null), CoreMatchers.equalTo(null));
Assert.assertThat(DnsUtils.normalize(""), CoreMatchers.equalTo(""));
Assert.assertThat(DnsUtils.normalize("blah"), CoreMatchers.equalTo("blah"));
Assert.assertThat(DnsUtils.normalize("BLAH"), CoreMatchers.equalTo("blah"));
Assert.assertThat(DnsUtils.normalize("blAh"), CoreMatchers.equalTo("blah"));
Assert.assertThat(DnsUtils.normalize("blaH"), CoreMatchers.equalTo("blah"));
Assert.assertThat(DnsUtils.normalize("blaH"), CoreMatchers.equalTo("blah"));
Assert.assertThat(DnsUtils.normalize("hac\u212A!!!"), CoreMatchers.equalTo("hac\u212A!!!"));
}

}
Expand Up @@ -29,62 +29,111 @@

import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.List;

import org.apache.http.Consts;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class TestPublicSuffixMatcher {

private static final String SOURCE_FILE = "suffixlist.txt";
private static final String SOURCE_FILE = "suffixlistmatcher.txt";

private PublicSuffixMatcher matcher;

private static final Charset UTF_8 = Charset.forName("UTF-8");

@Before
public void setUp() throws Exception {
final ClassLoader classLoader = getClass().getClassLoader();
final InputStream in = classLoader.getResourceAsStream(SOURCE_FILE);
Assert.assertNotNull(in);
final PublicSuffixList suffixList;
try {
final PublicSuffixListParser parser = new PublicSuffixListParser();
suffixList = parser.parse(new InputStreamReader(in, Consts.UTF_8));
} finally {
in.close();
}
matcher = new PublicSuffixMatcher(suffixList.getRules(), suffixList.getExceptions());
final List<PublicSuffixList> lists = new PublicSuffixListParser().parseByType(
new InputStreamReader(in, UTF_8));
matcher = new PublicSuffixMatcher(lists);
}

@Test
public void testGetDomainRoot() throws Exception {
Assert.assertEquals("example.xx", matcher.getDomainRoot("example.XX"));
Assert.assertEquals("example.xx", matcher.getDomainRoot("www.example.XX"));
Assert.assertEquals("example.xx", matcher.getDomainRoot("www.blah.blah.example.XX"));
Assert.assertEquals(null, matcher.getDomainRoot("xx"));
public void testGetDomainRootAnyType() {
// Private
Assert.assertEquals("xx", matcher.getDomainRoot("example.XX"));
Assert.assertEquals("xx", matcher.getDomainRoot("www.example.XX"));
Assert.assertEquals("xx", matcher.getDomainRoot("www.blah.blah.example.XX"));
Assert.assertEquals("appspot.com", matcher.getDomainRoot("example.appspot.com"));
// Too short
Assert.assertEquals(null, matcher.getDomainRoot("jp"));
Assert.assertEquals(null, matcher.getDomainRoot("ac.jp"));
Assert.assertEquals(null, matcher.getDomainRoot("any.tokyo.jp"));
// ICANN
Assert.assertEquals("metro.tokyo.jp", matcher.getDomainRoot("metro.tokyo.jp"));
Assert.assertEquals("blah.blah.tokyo.jp", matcher.getDomainRoot("blah.blah.tokyo.jp"));
Assert.assertEquals("blah.ac.jp", matcher.getDomainRoot("blah.blah.ac.jp"));
// Unknown
Assert.assertEquals("garbage", matcher.getDomainRoot("garbage"));
Assert.assertEquals("garbage", matcher.getDomainRoot("garbage.garbage"));
Assert.assertEquals("garbage", matcher.getDomainRoot("*.garbage.garbage"));
Assert.assertEquals("garbage", matcher.getDomainRoot("*.garbage.garbage.garbage"));
}

@Test
public void testMatch() throws Exception {
public void testGetDomainRootOnlyPRIVATE() {
// Private
Assert.assertEquals("xx", matcher.getDomainRoot("example.XX", DomainType.PRIVATE));
Assert.assertEquals("xx", matcher.getDomainRoot("www.example.XX", DomainType.PRIVATE));
Assert.assertEquals("xx", matcher.getDomainRoot("www.blah.blah.example.XX", DomainType.PRIVATE));
Assert.assertEquals("appspot.com", matcher.getDomainRoot("example.appspot.com"));
// Too short
Assert.assertEquals(null, matcher.getDomainRoot("jp", DomainType.PRIVATE));
Assert.assertEquals(null, matcher.getDomainRoot("ac.jp", DomainType.PRIVATE));
Assert.assertEquals(null, matcher.getDomainRoot("any.tokyo.jp", DomainType.PRIVATE));
// ICANN
Assert.assertEquals(null, matcher.getDomainRoot("metro.tokyo.jp", DomainType.PRIVATE));
Assert.assertEquals(null, matcher.getDomainRoot("blah.blah.tokyo.jp", DomainType.PRIVATE));
Assert.assertEquals(null, matcher.getDomainRoot("blah.blah.ac.jp", DomainType.PRIVATE));
// Unknown
Assert.assertEquals(null, matcher.getDomainRoot("garbage", DomainType.PRIVATE));
Assert.assertEquals(null, matcher.getDomainRoot("garbage.garbage", DomainType.PRIVATE));
Assert.assertEquals(null, matcher.getDomainRoot("*.garbage.garbage", DomainType.PRIVATE));
Assert.assertEquals(null, matcher.getDomainRoot("*.garbage.garbage.garbage", DomainType.PRIVATE));
}

@Test
public void testGetDomainRootOnlyICANN() {
// Private
Assert.assertEquals(null, matcher.getDomainRoot("example.XX", DomainType.ICANN));
Assert.assertEquals(null, matcher.getDomainRoot("www.example.XX", DomainType.ICANN));
Assert.assertEquals(null, matcher.getDomainRoot("www.blah.blah.example.XX", DomainType.ICANN));
// Too short
Assert.assertEquals(null, matcher.getDomainRoot("xx", DomainType.ICANN));
Assert.assertEquals(null, matcher.getDomainRoot("jp", DomainType.ICANN));
Assert.assertEquals(null, matcher.getDomainRoot("ac.jp", DomainType.ICANN));
Assert.assertEquals(null, matcher.getDomainRoot("any.tokyo.jp", DomainType.ICANN));
// ICANN
Assert.assertEquals("metro.tokyo.jp", matcher.getDomainRoot("metro.tokyo.jp", DomainType.ICANN));
Assert.assertEquals("blah.blah.tokyo.jp", matcher.getDomainRoot("blah.blah.tokyo.jp", DomainType.ICANN));
Assert.assertEquals("blah.ac.jp", matcher.getDomainRoot("blah.blah.ac.jp", DomainType.ICANN));
// Unknown
Assert.assertEquals(null, matcher.getDomainRoot("garbage", DomainType.ICANN));
Assert.assertEquals(null, matcher.getDomainRoot("garbage.garbage", DomainType.ICANN));
Assert.assertEquals(null, matcher.getDomainRoot("*.garbage.garbage", DomainType.ICANN));
Assert.assertEquals(null, matcher.getDomainRoot("*.garbage.garbage.garbage", DomainType.ICANN));
}


@Test
public void testMatch() {
Assert.assertTrue(matcher.matches(".jp"));
Assert.assertTrue(matcher.matches(".ac.jp"));
Assert.assertTrue(matcher.matches(".any.tokyo.jp"));
// exception
Assert.assertFalse(matcher.matches(".metro.tokyo.jp"));
Assert.assertFalse(matcher.matches(".xx"));
Assert.assertFalse(matcher.matches(".appspot.com"));
}

@Test
public void testMatchUnicode() throws Exception {
public void testMatchUnicode() {
Assert.assertTrue(matcher.matches(".h\u00E5.no")); // \u00E5 is <aring>
Assert.assertTrue(matcher.matches(".xn--h-2fa.no"));
Assert.assertTrue(matcher.matches(".h\u00E5.no"));
Expand Down
Expand Up @@ -79,9 +79,16 @@ public void backsOffForServiceUnavailable() {
}

@Test
public void doesNotBackOffForNon503StatusCodes() {
public void backsOffForTooManyRequests() {
final HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
429, "Too Many Requests");
assertTrue(impl.shouldBackoff(resp));
}

@Test
public void doesNotBackOffForNon429And503StatusCodes() {
for(int i = 100; i <= 599; i++) {
if (i == HttpStatus.SC_SERVICE_UNAVAILABLE) {
if (i== 429|| i == HttpStatus.SC_SERVICE_UNAVAILABLE) {
continue;
}
final HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
Expand Down
Expand Up @@ -112,6 +112,18 @@ public void testIsRedirectedTemporaryRedirect() throws Exception {
Assert.assertFalse(redirectStrategy.isRedirected(httppost, response, context));
}

@Test
public void testIsRedirectedPermanentRedirect() throws Exception {
final DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
final HttpClientContext context = HttpClientContext.create();
final HttpGet httpget = new HttpGet("http://localhost/");
final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
DefaultRedirectStrategy.SC_PERMANENT_REDIRECT, "Redirect");
Assert.assertTrue(redirectStrategy.isRedirected(httpget, response, context));
final HttpPost httppost = new HttpPost("http://localhost/");
Assert.assertFalse(redirectStrategy.isRedirected(httppost, response, context));
}

@Test
public void testIsRedirectedSeeOther() throws Exception {
final DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
Expand Down Expand Up @@ -385,6 +397,27 @@ public void testGetRedirectRequestForTemporaryRedirect() throws Exception {
Assert.assertSame(entity, ((HttpEntityEnclosingRequest) redirect2).getEntity());
}

@Test
public void testGetRedirectRequestForPermanentRedirect() throws Exception {
final DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
DefaultRedirectStrategy.SC_PERMANENT_REDIRECT, "Permanent Redirect");
response.addHeader("Location", "http://localhost/stuff");
final HttpContext context1 = new BasicHttpContext();
final HttpUriRequest redirect1 = redirectStrategy.getRedirect(
new HttpTrace("http://localhost/"), response, context1);
Assert.assertEquals("TRACE", redirect1.getMethod());
final HttpContext context2 = new BasicHttpContext();
final HttpPost httppost = new HttpPost("http://localhost/");
final HttpEntity entity = new BasicHttpEntity();
httppost.setEntity(entity);
final HttpUriRequest redirect2 = redirectStrategy.getRedirect(
httppost, response, context2);
Assert.assertEquals("POST", redirect2.getMethod());
Assert.assertTrue(redirect2 instanceof HttpEntityEnclosingRequest);
Assert.assertSame(entity, ((HttpEntityEnclosingRequest) redirect2).getEntity());
}

@Test(expected=ProtocolException.class)
public void testCreateLocationURIInvalid() throws Exception {
final DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
Expand Down
Expand Up @@ -26,11 +26,13 @@
*/
package org.apache.http.impl.client.integration;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.List;

import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
Expand All @@ -49,14 +51,18 @@
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.cookie.SM;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.localserver.EchoHandler;
import org.apache.http.localserver.LocalServerTestBase;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpExpectationVerifier;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.UriHttpRequestHandlerMapper;
import org.apache.http.util.EntityUtils;
Expand Down Expand Up @@ -105,6 +111,37 @@ public void handle(

}

private static class RedirectExpectationVerifier implements HttpExpectationVerifier {

private final int statuscode;

public RedirectExpectationVerifier(final int statuscode) {
super();
this.statuscode = statuscode > 0 ? statuscode : HttpStatus.SC_MOVED_TEMPORARILY;
}

public RedirectExpectationVerifier() {
this(-1);
}

@Override
public void verify(
final HttpRequest request, final HttpResponse response, final HttpContext context) throws HttpException {
final HttpInetConnection conn = (HttpInetConnection) context.getAttribute(HttpCoreContext.HTTP_CONNECTION);
final int port = conn.getLocalPort();
final String uri = request.getRequestLine().getUri();
if (uri.equals("/oldlocation/")) {
response.setStatusCode(this.statuscode);
response.addHeader(new BasicHeader("Location",
"http://localhost:" + port + "/newlocation/"));
response.addHeader(new BasicHeader("Connection", "close"));
} else {
response.setStatusCode(HttpStatus.SC_CONTINUE);
}
}

}

private static class CircularRedirectService implements HttpRequestHandler {

public CircularRedirectService() {
Expand Down Expand Up @@ -586,6 +623,133 @@ public void testPostRedirectSeeOther() throws Exception {
Assert.assertEquals("GET", reqWrapper.getRequestLine().getMethod());
}

@Test
public void testPostRedirectMovedPermatently() throws Exception {
this.serverBootstrap.registerHandler("*", new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY));

final HttpHost target = start();

final HttpClientContext context = HttpClientContext.create();

final HttpPost httppost = new HttpPost("/oldlocation/");
httppost.setEntity(new StringEntity("stuff"));

final HttpResponse response = this.httpclient.execute(target, httppost, context);
EntityUtils.consume(response.getEntity());
Assert.assertEquals(HttpStatus.SC_MOVED_PERMANENTLY, response.getStatusLine().getStatusCode());
}

@Test
public void testPostRedirectMovedPermatentlyLaxStrategy() throws Exception {
this.serverBootstrap.registerHandler("*", new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY));
this.clientBuilder.setRedirectStrategy(LaxRedirectStrategy.INSTANCE);

final HttpHost target = start();

final HttpClientContext context = HttpClientContext.create();

final HttpPost httppost = new HttpPost("/oldlocation/");
httppost.setEntity(new StringEntity("stuff"));

final HttpResponse response = this.httpclient.execute(target, httppost, context);
EntityUtils.consume(response.getEntity());

final HttpRequest reqWrapper = context.getRequest();

Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
Assert.assertEquals("/newlocation/", reqWrapper.getRequestLine().getUri());
Assert.assertEquals("GET", reqWrapper.getRequestLine().getMethod());
}

@Test
public void testPostTemporaryRedirectLaxStrategy() throws Exception {
this.serverBootstrap.registerHandler("*", new BasicRedirectService(HttpStatus.SC_TEMPORARY_REDIRECT));
this.clientBuilder.setRedirectStrategy(LaxRedirectStrategy.INSTANCE);

final HttpHost target = start();

final HttpClientContext context = HttpClientContext.create();

final HttpPost httppost = new HttpPost("/oldlocation/");
httppost.setEntity(new StringEntity("stuff"));

final HttpResponse response = this.httpclient.execute(target, httppost, context);
EntityUtils.consume(response.getEntity());

final HttpRequest reqWrapper = context.getRequest();

Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
Assert.assertEquals("/newlocation/", reqWrapper.getRequestLine().getUri());
Assert.assertEquals("POST", reqWrapper.getRequestLine().getMethod());
}

@Test
public void testNonRepeatablePostTemporaryRedirectLaxStrategy() throws Exception {
this.serverBootstrap.registerHandler("*", new BasicRedirectService(HttpStatus.SC_TEMPORARY_REDIRECT));
this.clientBuilder.setRedirectStrategy(LaxRedirectStrategy.INSTANCE);

final HttpHost target = start();

final HttpClientContext context = HttpClientContext.create();

final HttpPost httppost = new HttpPost("/oldlocation/");
final byte[] content = "stuff".getBytes(Consts.ASCII);
httppost.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), content.length));

final HttpResponse response = this.httpclient.execute(target, httppost, context);
EntityUtils.consume(response.getEntity());
Assert.assertEquals(HttpStatus.SC_TEMPORARY_REDIRECT, response.getStatusLine().getStatusCode());
}

@Test
public void testNonRepeatablePostTemporaryWithExpectContinueRedirectLaxStrategy() throws Exception {
this.serverBootstrap.registerHandler("*", new BasicRedirectService(HttpStatus.SC_TEMPORARY_REDIRECT));
this.clientBuilder.setRedirectStrategy(LaxRedirectStrategy.INSTANCE);

final HttpHost target = start();

final HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(RequestConfig.custom()
.setExpectContinueEnabled(true)
.build());

final HttpPost httppost = new HttpPost("/oldlocation/");
final byte[] content = "stuff".getBytes(Consts.ASCII);
httppost.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), content.length));

final HttpResponse response = this.httpclient.execute(target, httppost, context);
EntityUtils.consume(response.getEntity());
Assert.assertEquals(HttpStatus.SC_TEMPORARY_REDIRECT, response.getStatusLine().getStatusCode());
}

@Test
public void testNonRepeatablePostTemporaryWithExpectContinueExpectVerifierRedirectLaxStrategy() throws Exception {
this.serverBootstrap.registerHandler("*", new EchoHandler());
this.serverBootstrap.setExpectationVerifier(new RedirectExpectationVerifier(HttpStatus.SC_TEMPORARY_REDIRECT));

this.clientBuilder.setRedirectStrategy(LaxRedirectStrategy.INSTANCE);

final HttpHost target = start();

final HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(RequestConfig.custom()
.setExpectContinueEnabled(true)
.build());

final HttpPost httppost = new HttpPost("/oldlocation/");
final byte[] content = "stuff".getBytes(Consts.ASCII);
httppost.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), content.length));

final HttpResponse response = this.httpclient.execute(target, httppost, context);
EntityUtils.consume(response.getEntity());

final HttpRequest reqWrapper = context.getRequest();

Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
Assert.assertEquals("/newlocation/", reqWrapper.getRequestLine().getUri());
Assert.assertEquals("POST", reqWrapper.getRequestLine().getMethod());
}

@Test
public void testRelativeRedirect() throws Exception {
this.serverBootstrap.registerHandler("*", new RelativeRedirectService());
Expand Down
Expand Up @@ -278,7 +278,7 @@ public void testShutdown() throws Exception {

mgr.shutdown();

Mockito.verify(conn, Mockito.times(1)).close();
Mockito.verify(conn, Mockito.times(1)).shutdown();

try {
final ConnectionRequest connRequest2 = mgr.requestConnection(route, null);
Expand All @@ -292,7 +292,7 @@ public void testShutdown() throws Exception {
mgr.closeIdleConnections(0L, TimeUnit.MILLISECONDS);
mgr.shutdown();

Mockito.verify(conn, Mockito.times(1)).close();
Mockito.verify(conn, Mockito.times(1)).shutdown();
}

@Test
Expand Down
Expand Up @@ -115,6 +115,14 @@ public void testParseExpiry() throws Exception {
Assert.assertEquals(0, c.get(Calendar.MILLISECOND));
}

@Test
public void testParseExpiryInvalidTime0() throws Exception {
final BasicClientCookie cookie = new BasicClientCookie("name", "value");
final CookieAttributeHandler h = new LaxExpiresHandler();
h.parse(cookie, null);
Assert.assertNull(cookie.getExpiryDate());
}

@Test(expected = MalformedCookieException.class)
public void testParseExpiryInvalidTime1() throws Exception {
final BasicClientCookie cookie = new BasicClientCookie("name", "value");
Expand Down
48 changes: 48 additions & 0 deletions httpclient/src/test/resources/suffixlistmatcher.txt
@@ -0,0 +1,48 @@
// ====================================================================
// 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/>.
//

// ===BEGIN PRIVATE DOMAINS===
xx
lan
appspot.com
s3.eu-central-1.amazonaws.com
// ===END PRIVATE DOMAINS===

// ===BEGIN ICANN DOMAINS===

jp
ac.jp
*.tokyo.jp
!metro.tokyo.jp

com
co.jp
gov.uk

// unicode
no
hå.no

// ===END ICANN DOMAINS===
2 changes: 1 addition & 1 deletion httpmime/pom.xml
Expand Up @@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
<version>4.5.10-SNAPSHOT</version>
<version>4.5.13</version>
</parent>
<artifactId>httpmime</artifactId>
<name>Apache HttpClient Mime</name>
Expand Down
6 changes: 3 additions & 3 deletions pom.xml
Expand Up @@ -32,7 +32,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>httpcomponents-client</artifactId>
<name>Apache HttpComponents Client</name>
<version>4.5.10-SNAPSHOT</version>
<version>4.5.13</version>
<description>Apache HttpComponents Client is a library of components for building client side HTTP services</description>
<url>http://hc.apache.org/httpcomponents-client-ga/</url>
<inceptionYear>1999</inceptionYear>
Expand Down Expand Up @@ -60,13 +60,13 @@
<connection>scm:git:https://git-wip-us.apache.org/repos/asf/httpcomponents-client.git</connection>
<developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/httpcomponents-client.git</developerConnection>
<url>https://github.com/apache/httpcomponents-client/tree/${project.scm.tag}</url>
<tag>4.5.10-SNAPSHOT</tag>
<tag>4.5.13</tag>
</scm>

<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<httpcore.version>4.4.12</httpcore.version>
<httpcore.version>4.4.13</httpcore.version>
<commons-logging.version>1.2</commons-logging.version>
<commons-codec.version>1.11</commons-codec.version>
<ehcache.version>2.6.11</ehcache.version>
Expand Down