Skip to content

Commit 8bec00f

Browse files
committed
Add server.request.body.filenames support for Jersey and RESTEasy
1 parent 081af53 commit 8bec00f

5 files changed

Lines changed: 221 additions & 72 deletions

File tree

dd-java-agent/instrumentation/jersey/jersey-2.0/src/jersey2JettyTest/groovy/datadog/trace/instrumentation/jersey2/Jersey2JettyTest.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ class Jersey2JettyTest extends HttpServerTest<JettyServer> {
5454
true
5555
}
5656

57+
@Override
58+
boolean testBodyFilenames() {
59+
true
60+
}
61+
5762
@Override
5863
boolean testBodyJson() {
5964
true

dd-java-agent/instrumentation/jersey/jersey-2.0/src/jersey3JettyTest/groovy/datadog/trace/instrumentation/jersey3/Jersey3JettyTest.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ class Jersey3JettyTest extends HttpServerTest<JettyServer> {
5353
true
5454
}
5555

56+
@Override
57+
boolean testBodyFilenames() {
58+
true
59+
}
60+
5661
@Override
5762
boolean testBodyJson() {
5863
true

dd-java-agent/instrumentation/jersey/jersey-appsec/jersey-appsec-2.0/src/main/java/datadog/trace/instrumentation/jersey2/MultiPartReaderServerSideInstrumentation.java

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import net.bytebuddy.asm.Advice;
2828
import org.glassfish.jersey.media.multipart.BodyPart;
2929
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
30+
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
3031
import org.glassfish.jersey.media.multipart.MultiPart;
3132
import org.glassfish.jersey.message.internal.MediaTypes;
3233

@@ -72,42 +73,78 @@ static void after(
7273
CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
7374
BiFunction<RequestContext, Object, Flow<Void>> callback =
7475
cbp.getCallback(EVENTS.requestBodyProcessed());
75-
if (callback == null) {
76+
BiFunction<RequestContext, List<String>, Flow<Void>> filenamesCallback =
77+
cbp.getCallback(EVENTS.requestFilesFilenames());
78+
if (callback == null && filenamesCallback == null) {
7679
return;
7780
}
7881

79-
Map<String, List<String>> map = new HashMap<>();
80-
for (BodyPart bodyPart : ret.getBodyParts()) {
81-
if (!(bodyPart instanceof FormDataBodyPart)) {
82-
continue;
83-
}
84-
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
85-
if (!MediaTypes.typeEqual(MediaType.TEXT_PLAIN_TYPE, dataBodyPart.getMediaType())) {
86-
continue;
87-
}
88-
// if the type of dataBodyPart.getEntity() is BodyPartEntity, it is safe to read the part
89-
// more than once. So we're not depriving the application of the data by consuming it here
90-
String v = dataBodyPart.getValue();
82+
if (callback != null) {
83+
Map<String, List<String>> map = new HashMap<>();
84+
for (BodyPart bodyPart : ret.getBodyParts()) {
85+
if (!(bodyPart instanceof FormDataBodyPart)) {
86+
continue;
87+
}
88+
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
89+
if (!MediaTypes.typeEqual(MediaType.TEXT_PLAIN_TYPE, dataBodyPart.getMediaType())) {
90+
continue;
91+
}
92+
// if the type of dataBodyPart.getEntity() is BodyPartEntity, it is safe to read the part
93+
// more than once. So we're not depriving the application of the data by consuming it here
94+
String v = dataBodyPart.getValue();
95+
96+
String name = dataBodyPart.getName();
97+
List<String> values = map.get(name);
98+
if (values == null) {
99+
values = new ArrayList<>();
100+
map.put(name, values);
101+
}
91102

92-
String name = dataBodyPart.getName();
93-
List<String> values = map.get(name);
94-
if (values == null) {
95-
values = new ArrayList<>();
96-
map.put(name, values);
103+
values.add(v);
97104
}
98105

99-
values.add(v);
106+
Flow<Void> flow = callback.apply(reqCtx, map);
107+
Flow.Action action = flow.getAction();
108+
if (action instanceof Flow.Action.RequestBlockingAction) {
109+
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
110+
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
111+
if (blockResponseFunction != null) {
112+
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
113+
t = new BlockingException("Blocked request (for MultiPartReaderClientSide/readFrom)");
114+
reqCtx.getTraceSegment().effectivelyBlocked();
115+
}
116+
}
100117
}
101118

102-
Flow<Void> flow = callback.apply(reqCtx, map);
103-
Flow.Action action = flow.getAction();
104-
if (action instanceof Flow.Action.RequestBlockingAction) {
105-
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
106-
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
107-
if (blockResponseFunction != null) {
108-
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
109-
t = new BlockingException("Blocked request (for MultiPartReaderClientSide/readFrom)");
110-
reqCtx.getTraceSegment().effectivelyBlocked();
119+
if (filenamesCallback != null) {
120+
List<String> filenames = new ArrayList<>();
121+
for (BodyPart bodyPart : ret.getBodyParts()) {
122+
if (!(bodyPart instanceof FormDataBodyPart)) {
123+
continue;
124+
}
125+
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
126+
FormDataContentDisposition cd = dataBodyPart.getFormDataContentDisposition();
127+
if (cd == null) {
128+
continue;
129+
}
130+
String filename = cd.getFileName();
131+
if (filename != null && !filename.isEmpty()) {
132+
filenames.add(filename);
133+
}
134+
}
135+
if (!filenames.isEmpty()) {
136+
Flow<Void> filenamesFlow = filenamesCallback.apply(reqCtx, filenames);
137+
Flow.Action filenamesAction = filenamesFlow.getAction();
138+
if (t == null && filenamesAction instanceof Flow.Action.RequestBlockingAction) {
139+
Flow.Action.RequestBlockingAction rba =
140+
(Flow.Action.RequestBlockingAction) filenamesAction;
141+
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
142+
if (blockResponseFunction != null) {
143+
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
144+
t = new BlockingException("Blocked request (multipart file upload)");
145+
reqCtx.getTraceSegment().effectivelyBlocked();
146+
}
147+
}
111148
}
112149
}
113150
}

dd-java-agent/instrumentation/jersey/jersey-appsec/jersey-appsec-3.0/src/main/java/datadog/trace/instrumentation/jersey3/MultiPartReaderServerSideInstrumentation.java

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import net.bytebuddy.asm.Advice;
2828
import org.glassfish.jersey.media.multipart.BodyPart;
2929
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
30+
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
3031
import org.glassfish.jersey.media.multipart.MultiPart;
3132
import org.glassfish.jersey.message.internal.MediaTypes;
3233

@@ -72,42 +73,78 @@ static void after(
7273
CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
7374
BiFunction<RequestContext, Object, Flow<Void>> callback =
7475
cbp.getCallback(EVENTS.requestBodyProcessed());
75-
if (callback == null) {
76+
BiFunction<RequestContext, List<String>, Flow<Void>> filenamesCallback =
77+
cbp.getCallback(EVENTS.requestFilesFilenames());
78+
if (callback == null && filenamesCallback == null) {
7679
return;
7780
}
7881

79-
Map<String, List<String>> map = new HashMap<>();
80-
for (BodyPart bodyPart : ret.getBodyParts()) {
81-
if (!(bodyPart instanceof FormDataBodyPart)) {
82-
continue;
83-
}
84-
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
85-
if (!MediaTypes.typeEqual(MediaType.TEXT_PLAIN_TYPE, dataBodyPart.getMediaType())) {
86-
continue;
87-
}
88-
// if the type of dataBodyPart.getEntity() is BodyPartEntity, it is safe to read the part
89-
// more than once. So we're not depriving the application of the data by consuming it here
90-
String v = dataBodyPart.getValue();
82+
if (callback != null) {
83+
Map<String, List<String>> map = new HashMap<>();
84+
for (BodyPart bodyPart : ret.getBodyParts()) {
85+
if (!(bodyPart instanceof FormDataBodyPart)) {
86+
continue;
87+
}
88+
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
89+
if (!MediaTypes.typeEqual(MediaType.TEXT_PLAIN_TYPE, dataBodyPart.getMediaType())) {
90+
continue;
91+
}
92+
// if the type of dataBodyPart.getEntity() is BodyPartEntity, it is safe to read the part
93+
// more than once. So we're not depriving the application of the data by consuming it here
94+
String v = dataBodyPart.getValue();
95+
96+
String name = dataBodyPart.getName();
97+
List<String> values = map.get(name);
98+
if (values == null) {
99+
values = new ArrayList<>();
100+
map.put(name, values);
101+
}
91102

92-
String name = dataBodyPart.getName();
93-
List<String> values = map.get(name);
94-
if (values == null) {
95-
values = new ArrayList<>();
96-
map.put(name, values);
103+
values.add(v);
97104
}
98105

99-
values.add(v);
106+
Flow<Void> flow = callback.apply(reqCtx, map);
107+
Flow.Action action = flow.getAction();
108+
if (action instanceof Flow.Action.RequestBlockingAction) {
109+
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
110+
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
111+
if (blockResponseFunction != null) {
112+
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
113+
t = new BlockingException("Blocked request (for MultiPartReaderClientSide/readFrom)");
114+
reqCtx.getTraceSegment().effectivelyBlocked();
115+
}
116+
}
100117
}
101118

102-
Flow<Void> flow = callback.apply(reqCtx, map);
103-
Flow.Action action = flow.getAction();
104-
if (action instanceof Flow.Action.RequestBlockingAction) {
105-
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
106-
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
107-
if (blockResponseFunction != null) {
108-
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
109-
t = new BlockingException("Blocked request (for MultiPartReaderClientSide/readFrom)");
110-
reqCtx.getTraceSegment().effectivelyBlocked();
119+
if (filenamesCallback != null) {
120+
List<String> filenames = new ArrayList<>();
121+
for (BodyPart bodyPart : ret.getBodyParts()) {
122+
if (!(bodyPart instanceof FormDataBodyPart)) {
123+
continue;
124+
}
125+
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
126+
FormDataContentDisposition cd = dataBodyPart.getFormDataContentDisposition();
127+
if (cd == null) {
128+
continue;
129+
}
130+
String filename = cd.getFileName();
131+
if (filename != null && !filename.isEmpty()) {
132+
filenames.add(filename);
133+
}
134+
}
135+
if (!filenames.isEmpty()) {
136+
Flow<Void> filenamesFlow = filenamesCallback.apply(reqCtx, filenames);
137+
Flow.Action filenamesAction = filenamesFlow.getAction();
138+
if (t == null && filenamesAction instanceof Flow.Action.RequestBlockingAction) {
139+
Flow.Action.RequestBlockingAction rba =
140+
(Flow.Action.RequestBlockingAction) filenamesAction;
141+
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
142+
if (blockResponseFunction != null) {
143+
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
144+
t = new BlockingException("Blocked request (multipart file upload)");
145+
reqCtx.getTraceSegment().effectivelyBlocked();
146+
}
147+
}
111148
}
112149
}
113150
}

dd-java-agent/instrumentation/resteasy/resteasy-appsec-3.0/src/main/java/datadog/trace/instrumentation/resteasy/MultipartFormDataReaderInstrumentation.java

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import datadog.trace.api.gateway.RequestContextSlot;
1919
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
2020
import java.io.IOException;
21+
import java.lang.reflect.Method;
2122
import java.util.ArrayList;
2223
import java.util.HashMap;
2324
import java.util.List;
@@ -72,28 +73,92 @@ static void after(
7273
CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
7374
BiFunction<RequestContext, Object, Flow<Void>> callback =
7475
cbp.getCallback(EVENTS.requestBodyProcessed());
75-
if (callback == null) {
76+
BiFunction<RequestContext, List<String>, Flow<Void>> filenamesCallback =
77+
cbp.getCallback(EVENTS.requestFilesFilenames());
78+
if (callback == null && filenamesCallback == null) {
7679
return;
7780
}
7881

79-
Map<String, List<String>> m = new HashMap<>();
80-
for (Map.Entry<String, List<InputPart>> e : ret.getFormDataMap().entrySet()) {
81-
List<String> strings = new ArrayList<>();
82-
m.put(e.getKey(), strings);
83-
for (InputPart inputPart : e.getValue()) {
84-
strings.add(inputPart.getBodyAsString());
82+
if (callback != null) {
83+
Map<String, List<String>> m = new HashMap<>();
84+
for (Map.Entry<String, List<InputPart>> e : ret.getFormDataMap().entrySet()) {
85+
List<String> strings = new ArrayList<>();
86+
m.put(e.getKey(), strings);
87+
for (InputPart inputPart : e.getValue()) {
88+
strings.add(inputPart.getBodyAsString());
89+
}
90+
}
91+
92+
Flow<Void> flow = callback.apply(reqCtx, m);
93+
Flow.Action action = flow.getAction();
94+
if (action instanceof Flow.Action.RequestBlockingAction) {
95+
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
96+
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
97+
if (blockResponseFunction != null) {
98+
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
99+
t = new BlockingException("Blocked request (for MultipartFormDataInput/readFrom)");
100+
reqCtx.getTraceSegment().effectivelyBlocked();
101+
}
85102
}
86103
}
87104

88-
Flow<Void> flow = callback.apply(reqCtx, m);
89-
Flow.Action action = flow.getAction();
90-
if (action instanceof Flow.Action.RequestBlockingAction) {
91-
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
92-
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
93-
if (blockResponseFunction != null) {
94-
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
95-
t = new BlockingException("Blocked request (for MultipartFormDataInput/readFrom)");
96-
reqCtx.getTraceSegment().effectivelyBlocked();
105+
if (filenamesCallback != null) {
106+
List<String> filenames = new ArrayList<>();
107+
// Use reflection to call getHeaders() to avoid a bytecode reference to
108+
// javax.ws.rs.core.MultivaluedMap, which changed to jakarta.ws.rs.core.MultivaluedMap in
109+
// RESTEasy 6. Both return MultivaluedMap<String,String> which extends
110+
// Map<String,List<String>>.
111+
Method getHeadersMethod = null;
112+
try {
113+
getHeadersMethod = InputPart.class.getMethod("getHeaders");
114+
} catch (NoSuchMethodException ignored) {
115+
}
116+
for (Map.Entry<String, List<InputPart>> e : ret.getFormDataMap().entrySet()) {
117+
for (InputPart inputPart : e.getValue()) {
118+
if (getHeadersMethod == null) {
119+
continue;
120+
}
121+
List<String> cdHeaders;
122+
try {
123+
@SuppressWarnings("unchecked")
124+
Map<String, List<String>> headers =
125+
(Map<String, List<String>>) getHeadersMethod.invoke(inputPart);
126+
cdHeaders = headers != null ? headers.get("Content-Disposition") : null;
127+
} catch (Exception ignored) {
128+
continue;
129+
}
130+
if (cdHeaders == null || cdHeaders.isEmpty()) {
131+
continue;
132+
}
133+
String cd = cdHeaders.get(0);
134+
for (String token : cd.split(";")) {
135+
token = token.trim();
136+
if (token.startsWith("filename=")) {
137+
String filename = token.substring(9).trim();
138+
if (filename.startsWith("\"") && filename.endsWith("\"")) {
139+
filename = filename.substring(1, filename.length() - 1);
140+
}
141+
if (!filename.isEmpty()) {
142+
filenames.add(filename);
143+
}
144+
break;
145+
}
146+
}
147+
}
148+
}
149+
if (!filenames.isEmpty()) {
150+
Flow<Void> filenamesFlow = filenamesCallback.apply(reqCtx, filenames);
151+
Flow.Action filenamesAction = filenamesFlow.getAction();
152+
if (t == null && filenamesAction instanceof Flow.Action.RequestBlockingAction) {
153+
Flow.Action.RequestBlockingAction rba =
154+
(Flow.Action.RequestBlockingAction) filenamesAction;
155+
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
156+
if (blockResponseFunction != null) {
157+
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
158+
t = new BlockingException("Blocked request (multipart file upload)");
159+
reqCtx.getTraceSegment().effectivelyBlocked();
160+
}
161+
}
97162
}
98163
}
99164
}

0 commit comments

Comments
 (0)