Skip to content

Commit 4125a33

Browse files
agherardijansupol
authored andcommitted
Patch for client connection leak when using digest authentication (#3860)
Signed-off-by: agherardi <alessandro.gherardi@yahoo.com>
1 parent ae9ca1f commit 4125a33

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
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
@@ -41,6 +41,7 @@
4141
import org.apache.http.auth.AuthScope;
4242
import org.apache.http.auth.UsernamePasswordCredentials;
4343
import org.apache.http.client.CredentialsProvider;
44+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
4445
import org.junit.Ignore;
4546
import org.junit.Test;
4647
import static org.junit.Assert.assertEquals;
@@ -147,6 +148,19 @@ public String getFilter(@Context HttpHeaders h) {
147148
return "GET";
148149
}
149150

151+
@GET
152+
@Path("digest")
153+
public String getDigest(@Context HttpHeaders h) {
154+
String value = h.getRequestHeaders().getFirst("Authorization");
155+
if (value == null) {
156+
throw new WebApplicationException(
157+
Response.status(401).header("WWW-Authenticate", "Digest realm=\"WallyWorld\"")
158+
.entity("Forbidden").build());
159+
}
160+
161+
return "GET";
162+
}
163+
150164
@POST
151165
public String post(@Context HttpHeaders h, String e) {
152166
requestCount++;
@@ -281,6 +295,24 @@ public void testAuthGetWithClientFilter() {
281295
assertEquals("GET", r.request().get(String.class));
282296
}
283297

298+
@Test
299+
public void testAuthGetWithDigestFilter() {
300+
ClientConfig cc = new ClientConfig();
301+
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
302+
cc.connectorProvider(new ApacheConnectorProvider());
303+
cc.property(ApacheClientProperties.CONNECTION_MANAGER, cm);
304+
Client client = ClientBuilder.newClient(cc);
305+
client.register(HttpAuthenticationFeature.universal("name", "password"));
306+
WebTarget r = client.target(getBaseUri()).path("test/digest");
307+
308+
assertEquals("GET", r.request().get(String.class));
309+
310+
// Verify the connection that was used for the request is available for reuse
311+
// and no connections are leased
312+
assertEquals(cm.getTotalStats().getAvailable(), 1);
313+
assertEquals(cm.getTotalStats().getLeased(), 0);
314+
}
315+
284316
@Test
285317
@Ignore("JERSEY-1750: Cannot retry request with a non-repeatable request entity. How to buffer the entity?"
286318
+ " Allow repeatable write in jersey?")

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.glassfish.jersey.client.authentication;
1818

19+
import java.io.IOException;
20+
import java.io.InputStream;
1921
import java.net.URI;
2022
import java.net.URISyntaxException;
2123

@@ -25,6 +27,25 @@
2527
* Common authentication utilities
2628
*/
2729
class AuthenticationUtil {
30+
static void discardInputAndClose(InputStream is) {
31+
byte[] buf = new byte[4096];
32+
try {
33+
while (true) {
34+
if (is.read(buf) <= 0) {
35+
break;
36+
}
37+
}
38+
} catch (IOException ex) {
39+
// ignore
40+
} finally {
41+
try {
42+
is.close();
43+
} catch (IOException ex) {
44+
// ignore
45+
}
46+
}
47+
}
48+
2849
static URI getCacheKey(ClientRequestContext request) {
2950
URI requestUri = request.getUri();
3051
if (requestUri.getRawQuery() != null) {

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,16 @@ private void updateCache(ClientRequestContext request, boolean success, Type ope
268268
* {@code false} otherwise).
269269
*/
270270
static boolean repeatRequest(ClientRequestContext request, ClientResponseContext response, String newAuthorizationHeader) {
271-
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+
AuthenticationUtil.discardInputAndClose(response.getEntityStream());
277+
response.setEntityStream(null);
278+
}
272279

280+
Client client = request.getClient();
273281
String method = request.getMethod();
274282
MediaType mediaType = request.getMediaType();
275283
URI lUri = request.getUri();
@@ -292,6 +300,12 @@ static boolean repeatRequest(ClientRequestContext request, ClientResponseContext
292300

293301
builder.property(REQUEST_PROPERTY_FILTER_REUSED, "true");
294302

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+
295309
Invocation invocation;
296310
if (request.getEntity() == null) {
297311
invocation = builder.build(method);

0 commit comments

Comments
 (0)