Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ class AbstractPlayServerTest extends HttpServerTest<Server> {
true
}

@Override
boolean testBodyFilenames() {
true
}

@Override
boolean testBlocking() {
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,80 @@ private static String handleText(String s) {
private static MultipartFormData<?> handleMultipartFormData(MultipartFormData<?> data) {
scala.collection.immutable.Map<String, Seq<String>> mpfd = data.asFormUrlEncoded();

if (mpfd == null || mpfd.isEmpty()) {
return data;
if (mpfd != null && !mpfd.isEmpty()) {
try {
Object conv = tryConvertingScalaContainers(mpfd, MAX_CONVERSION_DEPTH);
handleArbitraryPostData(conv, "multipartFormData");
} catch (Exception e) {
handleException(e, "Error handling result of multipartFormData BodyParser");
}
}

try {
Object conv = tryConvertingScalaContainers(mpfd, MAX_CONVERSION_DEPTH);
handleArbitraryPostData(conv, "multipartFormData");
// Use reflection to avoid a hard binary reference to files():Lscala/collection/Seq; —
// in Scala 2.13 (Play 2.7+) the return type became scala.collection.immutable.Seq,
// which would cause muzzle to disable the whole instrumentation for Play 2.7.
Object files = data.getClass().getMethod("files").invoke(data);
if (files instanceof scala.collection.Iterable) {
handleMultipartFilenames(((scala.collection.Iterable<?>) files).iterator());
}
} catch (Exception e) {
handleException(e, "Error handling result of multipartFormData BodyParser");
handleException(e, "Error handling multipartFormData filenames");
}

return data;
}

private static void handleMultipartFilenames(Iterator<?> iterator) {
AgentSpan span = activeSpan();
if (span == null) {
return;
}
RequestContext reqCtx = span.getRequestContext();
if (reqCtx == null || reqCtx.getData(RequestContextSlot.APPSEC) == null) {
return;
}

List<String> filenames = new ArrayList<>();
while (iterator.hasNext()) {
MultipartFormData.FilePart<?> part = (MultipartFormData.FilePart<?>) iterator.next();
String filename = part.filename();
if (filename != null && !filename.isEmpty()) {
filenames.add(filename);
}
}

if (filenames.isEmpty()) {
return;
}

CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
BiFunction<RequestContext, List<String>, Flow<Void>> callback =
cbp.getCallback(EVENTS.requestFilesFilenames());
if (callback == null) {
return;
}
executeFilenamesCallback(reqCtx, callback, filenames);
}

private static void executeFilenamesCallback(
RequestContext reqCtx,
BiFunction<RequestContext, List<String>, Flow<Void>> callback,
List<String> filenames) {
Flow<Void> flow = callback.apply(reqCtx, filenames);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction brf = reqCtx.getBlockResponseFunction();
if (brf != null) {
boolean success = brf.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
if (success) {
throw new BlockingException("Blocked request (multipart file upload)");
}
}
}
}

public static Function1<JsValue, JsValue> getHandleJsonF() {
return HANDLE_JSON;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ class PlayServerTest extends HttpServerTest<Server> {
true
}

@Override
boolean testBodyFilenames() {
true
}

@Override
boolean testBlocking() {
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,19 +117,80 @@ private static String handleText(String s) {
private static MultipartFormData<?> handleMultipartFormData(MultipartFormData<?> data) {
scala.collection.immutable.Map<String, Seq<String>> mpfd = data.asFormUrlEncoded();

if (mpfd == null || mpfd.isEmpty()) {
return data;
if (mpfd != null && !mpfd.isEmpty()) {
try {
Object conv = tryConvertingScalaContainers(mpfd, MAX_CONVERSION_DEPTH);
handleArbitraryPostData(conv, "multipartFormData");
} catch (Exception e) {
handleException(e, "Error handling result of multipartFormData BodyParser");
}
}

try {
Object conv = tryConvertingScalaContainers(mpfd, MAX_CONVERSION_DEPTH);
handleArbitraryPostData(conv, "multipartFormData");
// Use reflection to avoid a hard binary reference to files():Lscala/collection/Seq; —
// in Scala 2.13 (Play 2.7+) the return type became scala.collection.immutable.Seq,
// which would cause muzzle to disable the whole instrumentation for Play 2.7.
Object files = data.getClass().getMethod("files").invoke(data);
if (files instanceof scala.collection.Iterable) {
handleMultipartFilenames(((scala.collection.Iterable<?>) files).iterator());
}
} catch (Exception e) {
handleException(e, "Error handling result of multipartFormData BodyParser");
handleException(e, "Error handling multipartFormData filenames");
}

return data;
}

private static void handleMultipartFilenames(Iterator<?> iterator) {
AgentSpan span = activeSpan();
if (span == null) {
return;
}
RequestContext reqCtx = span.getRequestContext();
if (reqCtx == null || reqCtx.getData(RequestContextSlot.APPSEC) == null) {
return;
}

List<String> filenames = new ArrayList<>();
while (iterator.hasNext()) {
MultipartFormData.FilePart<?> part = (MultipartFormData.FilePart<?>) iterator.next();
String filename = part.filename();
if (filename != null && !filename.isEmpty()) {
filenames.add(filename);
}
}

if (filenames.isEmpty()) {
return;
}

CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
BiFunction<RequestContext, List<String>, Flow<Void>> callback =
cbp.getCallback(EVENTS.requestFilesFilenames());
if (callback == null) {
return;
}
executeFilenamesCallback(reqCtx, callback, filenames);
}

private static void executeFilenamesCallback(
RequestContext reqCtx,
BiFunction<RequestContext, List<String>, Flow<Void>> callback,
List<String> filenames) {
Flow<Void> flow = callback.apply(reqCtx, filenames);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction brf = reqCtx.getBlockResponseFunction();
if (brf != null) {
boolean success = brf.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
if (success) {
throw new BlockingException("Blocked request (multipart file upload)");
}
}
}
}

public static Function1<JsValue, JsValue> getHandleJsonF() {
return HANDLE_JSON;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ private Map<String, Collection<String>> getMap() {
Deque<FormData.FormValue> formValues = formData.get(key);
List<String> values = new ArrayList<>(formValues.size());
for (FormData.FormValue formValue : formValues) {
if (!formValue.isFile()) {
// In undertow 2.2+, isFile() returns false for in-memory file uploads; getFileName()
// correctly identifies all file uploads regardless of storage.
if (formValue.getFileName() == null) {
values.add(formValue.getValue());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.form.FormData;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import net.bytebuddy.asm.Advice;

Expand Down Expand Up @@ -81,25 +83,56 @@ static void after(
}

CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
BiFunction<RequestContext, Object, Flow<Void>> callback =
BiFunction<RequestContext, Object, Flow<Void>> bodyCallback =
cbp.getCallback(EVENTS.requestBodyProcessed());
if (callback == null) {
BiFunction<RequestContext, List<String>, Flow<Void>> filenamesCb =
cbp.getCallback(EVENTS.requestFilesFilenames());
if (bodyCallback == null && filenamesCb == null) {
return;
}
FormData attachment = exchange.getAttachment(FORM_DATA);
if (attachment == null) {
return;
}

Flow<Void> flow = callback.apply(reqCtx, new FormDataMap(attachment));
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
if (t == null) {
t = new BlockingException("Blocked request (for MultiPartUploadHandler/parseBlocking)");
if (bodyCallback != null) {
Flow<Void> flow = bodyCallback.apply(reqCtx, new FormDataMap(attachment));
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
if (t == null) {
t =
new BlockingException(
"Blocked request (for MultiPartUploadHandler/parseBlocking)");
}
}
}
}

if (filenamesCb != null) {
List<String> filenames = new ArrayList<>();
for (String key : attachment) {
for (FormData.FormValue formValue : attachment.get(key)) {
String filename = formValue.getFileName();
if (filename != null && !filename.isEmpty()) {
filenames.add(filename);
}
}
}
if (!filenames.isEmpty()) {
Flow<Void> filenamesFlow = filenamesCb.apply(reqCtx, filenames);
Flow.Action filenamesAction = filenamesFlow.getAction();
if (t == null && filenamesAction instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba =
(Flow.Action.RequestBlockingAction) filenamesAction;
BlockResponseFunction brf = reqCtx.getBlockResponseFunction();
if (brf != null) {
brf.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (multipart file upload)");
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ class UndertowServletAsyncTest extends HttpServerTest<Undertow> {
true
}

@Override
boolean testBodyFilenames() {
true
}

@Override
boolean testBlockingOnResponse() {
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ abstract class UndertowServletTest extends HttpServerTest<Undertow> {
true
}

@Override
boolean testBodyFilenames() {
true
}

@Override
boolean testBlockingOnResponse() {
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ class UndertowServletTest extends HttpServerTest<Undertow> {
true
}

@Override
boolean testBodyFilenames() {
true
}

boolean hasResponseSpan(ServerEndpoint endpoint) {
// FIXME: re-enable when jakarta servlet will be fully supported
// return endpoint == REDIRECT || endpoint == NOT_FOUND
Expand Down
Loading