Skip to content

Commit bed3e46

Browse files
agherardialessandro.gherardi
authored andcommitted
Patch for client connection leak when using digest authentication
Signed-off-by: agherardi <alessandro.gherardi@yahoo.com>
1 parent 02f2cb6 commit bed3e46

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.apache.http.auth.AuthScope;
4141
import org.apache.http.auth.UsernamePasswordCredentials;
4242
import org.apache.http.client.CredentialsProvider;
43+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
4344
import org.junit.Ignore;
4445
import org.junit.Test;
4546
import static org.junit.Assert.assertEquals;
@@ -144,6 +145,19 @@ public String getFilter(@Context HttpHeaders h) {
144145
return "GET";
145146
}
146147

148+
@GET
149+
@Path("digest")
150+
public String getDigest(@Context HttpHeaders h) {
151+
String value = h.getRequestHeaders().getFirst("Authorization");
152+
if (value == null) {
153+
throw new WebApplicationException(
154+
Response.status(401).header("WWW-Authenticate", "Digest realm=\"WallyWorld\"")
155+
.entity("Forbidden").build());
156+
}
157+
158+
return "GET";
159+
}
160+
147161
@POST
148162
public String post(@Context HttpHeaders h, String e) {
149163
requestCount++;
@@ -254,6 +268,24 @@ public void testAuthGetWithClientFilter() {
254268
assertEquals("GET", r.request().get(String.class));
255269
}
256270

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

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

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
import java.util.List;
2626
import java.util.Map;
2727

28+
import javax.annotation.Priority;
2829
import javax.ws.rs.Priorities;
2930
import javax.ws.rs.client.Client;
30-
import javax.ws.rs.client.ClientBuilder;
3131
import javax.ws.rs.client.ClientRequestContext;
3232
import javax.ws.rs.client.ClientRequestFilter;
3333
import javax.ws.rs.client.ClientResponseContext;
@@ -42,8 +42,6 @@
4242
import javax.ws.rs.core.MultivaluedMap;
4343
import javax.ws.rs.core.Response;
4444

45-
import javax.annotation.Priority;
46-
4745
import org.glassfish.jersey.client.ClientProperties;
4846
import org.glassfish.jersey.client.internal.LocalizationMessages;
4947

@@ -270,8 +268,16 @@ private void updateCache(ClientRequestContext request, boolean success, Type ope
270268
* {@code false} otherwise).
271269
*/
272270
static boolean repeatRequest(ClientRequestContext request, ClientResponseContext response, String newAuthorizationHeader) {
273-
Client client = request.getClient();
271+
// If the failed response has an entity stream, close it. We must do this to avoid leaking a connection
272+
// when we replace the entity stream of the failed response with that of the repeated response (see below).
273+
// Notice that by closing the entity stream before sending the repeated request we allow the connection allocated
274+
// to the failed request to be reused, if possible, for the repeated request.
275+
if (response.hasEntity()) {
276+
discardInputAndClose(response.getEntityStream());
277+
response.setEntityStream(null);
278+
}
274279

280+
Client client = request.getClient();
275281
String method = request.getMethod();
276282
MediaType mediaType = request.getMediaType();
277283
URI lUri = request.getUri();
@@ -294,6 +300,12 @@ static boolean repeatRequest(ClientRequestContext request, ClientResponseContext
294300

295301
builder.property(REQUEST_PROPERTY_FILTER_REUSED, "true");
296302

303+
// Copy other properties, if any, from the original request
304+
for (String propertyName : request.getPropertyNames()) {
305+
Object propertyValue = request.getProperty(propertyName);
306+
builder.property(propertyName, propertyValue);
307+
}
308+
297309
Invocation invocation;
298310
if (request.getEntity() == null) {
299311
invocation = builder.build(method);
@@ -419,4 +431,23 @@ static Credentials getCredentials(ClientRequestContext request, Credentials defa
419431
return specificCredentials != null ? specificCredentials : defaultCredentials;
420432
}
421433
}
434+
435+
private static void discardInputAndClose(InputStream is) {
436+
byte[] buf = new byte[4096];
437+
try {
438+
while (true) {
439+
if (is.read(buf) <= 0) {
440+
break;
441+
}
442+
}
443+
} catch (IOException ex) {
444+
// ignore
445+
} finally {
446+
try {
447+
is.close();
448+
} catch (IOException ex) {
449+
// ignore
450+
}
451+
}
452+
}
422453
}

0 commit comments

Comments
 (0)