Skip to content

Commit 21a5777

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

5 files changed

Lines changed: 217 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: 77 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,88 @@ 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+
// Reflection avoids a bytecode ref to MultivaluedMap (javax→jakarta in RESTEasy 6)
108+
Method getHeadersMethod = null;
109+
try {
110+
getHeadersMethod = InputPart.class.getMethod("getHeaders");
111+
} catch (NoSuchMethodException ignored) {
112+
}
113+
if (getHeadersMethod != null) {
114+
for (Map.Entry<String, List<InputPart>> e : ret.getFormDataMap().entrySet()) {
115+
for (InputPart inputPart : e.getValue()) {
116+
List<String> cdHeaders;
117+
try {
118+
@SuppressWarnings("unchecked")
119+
Map<String, List<String>> headers =
120+
(Map<String, List<String>>) getHeadersMethod.invoke(inputPart);
121+
cdHeaders = headers != null ? headers.get("Content-Disposition") : null;
122+
} catch (Exception ignored) {
123+
continue;
124+
}
125+
if (cdHeaders == null || cdHeaders.isEmpty()) {
126+
continue;
127+
}
128+
String cd = cdHeaders.get(0);
129+
for (String token : cd.split(";")) {
130+
token = token.trim();
131+
if (token.startsWith("filename=")) {
132+
String filename = token.substring(9).trim();
133+
if (filename.startsWith("\"") && filename.endsWith("\"")) {
134+
filename = filename.substring(1, filename.length() - 1);
135+
}
136+
if (!filename.isEmpty()) {
137+
filenames.add(filename);
138+
}
139+
break;
140+
}
141+
}
142+
}
143+
}
144+
}
145+
if (!filenames.isEmpty()) {
146+
Flow<Void> filenamesFlow = filenamesCallback.apply(reqCtx, filenames);
147+
Flow.Action filenamesAction = filenamesFlow.getAction();
148+
if (t == null && filenamesAction instanceof Flow.Action.RequestBlockingAction) {
149+
Flow.Action.RequestBlockingAction rba =
150+
(Flow.Action.RequestBlockingAction) filenamesAction;
151+
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
152+
if (blockResponseFunction != null) {
153+
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
154+
t = new BlockingException("Blocked request (multipart file upload)");
155+
reqCtx.getTraceSegment().effectivelyBlocked();
156+
}
157+
}
97158
}
98159
}
99160
}

0 commit comments

Comments
 (0)