Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

Commit 6f00c6a

Browse files
author
alessandro.gherardi
committed
Patch for client connection leak when using digest authentication
1 parent 7d4c8b0 commit 6f00c6a

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

connectors/apache-connector/src/test/java/org/glassfish/jersey/apache/connector/AuthTest.java

+32
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.apache.http.auth.AuthScope;
6565
import org.apache.http.auth.UsernamePasswordCredentials;
6666
import org.apache.http.client.CredentialsProvider;
67+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
6768
import org.junit.Ignore;
6869
import org.junit.Test;
6970
import static org.junit.Assert.assertEquals;
@@ -168,6 +169,19 @@ public String getFilter(@Context HttpHeaders h) {
168169
return "GET";
169170
}
170171

172+
@GET
173+
@Path("digest")
174+
public String getDigest(@Context HttpHeaders h) {
175+
String value = h.getRequestHeaders().getFirst("Authorization");
176+
if (value == null) {
177+
throw new WebApplicationException(
178+
Response.status(401).header("WWW-Authenticate", "Digest realm=\"WallyWorld\"")
179+
.entity("Forbidden").build());
180+
}
181+
182+
return "GET";
183+
}
184+
171185
@POST
172186
public String post(@Context HttpHeaders h, String e) {
173187
requestCount++;
@@ -259,6 +273,24 @@ public void testAuthGetWithClientFilter() {
259273
assertEquals("GET", r.request().get(String.class));
260274
}
261275

276+
@Test
277+
public void testAuthGetWithDigestFilter() {
278+
ClientConfig cc = new ClientConfig();
279+
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
280+
cc.connectorProvider(new ApacheConnectorProvider());
281+
cc.property(ApacheClientProperties.CONNECTION_MANAGER, cm);
282+
Client client = ClientBuilder.newClient(cc);
283+
client.register(HttpAuthenticationFeature.universal("name", "password"));
284+
WebTarget r = client.target(getBaseUri()).path("test/digest");
285+
286+
assertEquals("GET", r.request().get(String.class));
287+
288+
// Verify the connection that was used for the request is available for reuse
289+
// and no connections are leased
290+
assertEquals(cm.getTotalStats().getAvailable(), 1);
291+
assertEquals(cm.getTotalStats().getLeased(), 0);
292+
}
293+
262294
@Test
263295
@Ignore("JERSEY-1750: Cannot retry request with a non-repeatable request entity. How to buffer the entity?"
264296
+ " Allow repeatable write in jersey?")

core-client/src/main/java/org/glassfish/jersey/client/authentication/HttpAuthenticationFilter.java

+35-4
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@
4949
import java.util.List;
5050
import java.util.Map;
5151

52+
import javax.annotation.Priority;
5253
import javax.ws.rs.Priorities;
5354
import javax.ws.rs.client.Client;
54-
import javax.ws.rs.client.ClientBuilder;
5555
import javax.ws.rs.client.ClientRequestContext;
5656
import javax.ws.rs.client.ClientRequestFilter;
5757
import javax.ws.rs.client.ClientResponseContext;
@@ -66,8 +66,6 @@
6666
import javax.ws.rs.core.MultivaluedMap;
6767
import javax.ws.rs.core.Response;
6868

69-
import javax.annotation.Priority;
70-
7169
import org.glassfish.jersey.client.ClientProperties;
7270
import org.glassfish.jersey.client.internal.LocalizationMessages;
7371

@@ -294,8 +292,16 @@ private void updateCache(ClientRequestContext request, boolean success, Type ope
294292
* {@code false} otherwise).
295293
*/
296294
static boolean repeatRequest(ClientRequestContext request, ClientResponseContext response, String newAuthorizationHeader) {
297-
Client client = request.getClient();
295+
// If the failed response has an entity stream, close it. We must do this to avoid leaking a connection
296+
// when we replace the entity stream of the failed response with that of the repeated response (see below).
297+
// Notice that by closing the entity stream before sending the repeated request we allow the connection allocated
298+
// to the failed request to be reused, if possible, for the repeated request.
299+
if (response.hasEntity()) {
300+
discardInputAndClose(response.getEntityStream());
301+
response.setEntityStream(null);
302+
}
298303

304+
Client client = request.getClient();
299305
String method = request.getMethod();
300306
MediaType mediaType = request.getMediaType();
301307
URI lUri = request.getUri();
@@ -318,6 +324,12 @@ static boolean repeatRequest(ClientRequestContext request, ClientResponseContext
318324

319325
builder.property(REQUEST_PROPERTY_FILTER_REUSED, "true");
320326

327+
// Copy other properties, if any, from the original request
328+
for (String propertyName : request.getPropertyNames()) {
329+
Object propertyValue = request.getProperty(propertyName);
330+
builder.property(propertyName, propertyValue);
331+
}
332+
321333
Invocation invocation;
322334
if (request.getEntity() == null) {
323335
invocation = builder.build(method);
@@ -443,4 +455,23 @@ static Credentials getCredentials(ClientRequestContext request, Credentials defa
443455
return specificCredentials != null ? specificCredentials : defaultCredentials;
444456
}
445457
}
458+
459+
private static void discardInputAndClose(InputStream is) {
460+
byte[] buf = new byte[4096];
461+
try {
462+
while (true) {
463+
if (is.read(buf) <= 0) {
464+
break;
465+
}
466+
}
467+
} catch (IOException ex) {
468+
// ignore
469+
} finally {
470+
try {
471+
is.close();
472+
} catch (IOException ex) {
473+
// ignore
474+
}
475+
}
476+
}
446477
}

0 commit comments

Comments
 (0)