Skip to content

Commit 02f1d71

Browse files
jansupolsenivam
authored andcommitted
Prevent leaking of annotation in a reused response.
Signed-off-by: jansupol <[email protected]>
1 parent d1e3e11 commit 02f1d71

File tree

2 files changed

+101
-12
lines changed

2 files changed

+101
-12
lines changed

core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2022 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -21,7 +21,6 @@
2121
import java.lang.reflect.ParameterizedType;
2222
import java.lang.reflect.Type;
2323
import java.util.ArrayList;
24-
import java.util.Arrays;
2524
import java.util.Collection;
2625
import java.util.Collections;
2726
import java.util.LinkedList;
@@ -280,13 +279,8 @@ protected void configure() {
280279
model.getPriority(ContainerResponseFilter.class)));
281280
}
282281
}
283-
284-
_readerInterceptors.addAll(
285-
StreamSupport.stream(processingProviders.getGlobalReaderInterceptors().spliterator(), false)
286-
.collect(Collectors.toList()));
287-
_writerInterceptors.addAll(
288-
StreamSupport.stream(processingProviders.getGlobalWriterInterceptors().spliterator(), false)
289-
.collect(Collectors.toList()));
282+
processingProviders.getGlobalReaderInterceptors().forEach(_readerInterceptors::add);
283+
processingProviders.getGlobalWriterInterceptors().forEach(_writerInterceptors::add);
290284

291285
if (resourceMethod != null) {
292286
addNameBoundFiltersAndInterceptors(
@@ -458,9 +452,7 @@ private Response invoke(final RequestProcessingContext context, final Object res
458452
if (entityAnn.length == 0) {
459453
response.setEntityAnnotations(methodAnnotations);
460454
} else {
461-
final Annotation[] mergedAnn = Arrays.copyOf(methodAnnotations,
462-
methodAnnotations.length + entityAnn.length);
463-
System.arraycopy(entityAnn, 0, mergedAnn, methodAnnotations.length, entityAnn.length);
455+
final Annotation[] mergedAnn = mergeDistinctAnnotations(methodAnnotations, entityAnn);
464456
response.setEntityAnnotations(mergedAnn);
465457
}
466458
}
@@ -487,6 +479,22 @@ private Response invoke(final RequestProcessingContext context, final Object res
487479
return jaxrsResponse;
488480
}
489481

482+
private static Annotation[] mergeDistinctAnnotations(Annotation[] existing, Annotation[] newOnes) {
483+
List<Annotation> merged = new ArrayList<>(existing.length + newOnes.length);
484+
Collections.addAll(merged, existing);
485+
486+
newOnesLoop:
487+
for (Annotation n : newOnes) {
488+
for (Annotation ex : existing) {
489+
if (ex == n) {
490+
continue newOnesLoop;
491+
}
492+
}
493+
merged.add(n);
494+
}
495+
return merged.toArray(new Annotation[0]);
496+
}
497+
490498
private Type unwrapInvocableResponseType(ContainerRequest request, Type entityType) {
491499
if (isCompletionStageResponseType
492500
&& request.resolveProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.FALSE)) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
17+
package org.glassfish.jersey.tests.api;
18+
19+
import org.glassfish.jersey.server.ContainerResponse;
20+
import org.glassfish.jersey.server.ResourceConfig;
21+
import org.glassfish.jersey.test.JerseyTest;
22+
import org.junit.jupiter.api.Test;
23+
24+
import javax.ws.rs.GET;
25+
import javax.ws.rs.Path;
26+
import javax.ws.rs.container.ContainerRequestContext;
27+
import javax.ws.rs.container.ContainerResponseContext;
28+
import javax.ws.rs.container.ContainerResponseFilter;
29+
import javax.ws.rs.core.Application;
30+
import javax.ws.rs.core.Response;
31+
import java.io.IOException;
32+
import java.lang.annotation.Annotation;
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
36+
import static org.junit.jupiter.api.Assertions.assertEquals;
37+
38+
/**
39+
* Check the reused response does not leak by additional Annotations (ContainerResponse#setEntityAnnottaions.
40+
*/
41+
public class Jersey5939Test extends JerseyTest {
42+
43+
private final List<ContainerResponse> capturedResponses = new ArrayList<>();
44+
45+
@Override
46+
protected Application configure() {
47+
return new ResourceConfig(Restlet.class).register(new ContainerResponseFilter() {
48+
@Override
49+
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
50+
throws IOException {
51+
if (responseContext instanceof ContainerResponse) {
52+
capturedResponses.add((ContainerResponse) responseContext);
53+
}
54+
}
55+
});
56+
}
57+
58+
@Test
59+
public void testIssue5939() {
60+
for (int i = 0; i < 10; i++) {
61+
Response response = target("foo/bar").request().get();
62+
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
63+
ContainerResponse containerResponse = capturedResponses.get(i);
64+
Annotation[] annotations = containerResponse.getEntityAnnotations();
65+
// [@javax.ws.rs.GET(), @javax.ws.rs.Path("/bar")]
66+
assertEquals(2, annotations.length, "Found " + annotations.length + " annotations, in iteration " + i);
67+
}
68+
}
69+
70+
@Path("/foo")
71+
public static class Restlet {
72+
73+
private static final Response RESPONSE_204 = Response.noContent().build();
74+
75+
@GET
76+
@Path("/bar")
77+
public Response fooBar() {
78+
return RESPONSE_204;
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)