Skip to content

Commit 3b7c9ff

Browse files
committed
fix(appsec/jetty): replace bytecode injection in jetty-appsec-8.1.3 with exit-advice
Replace the brittle GetPartsMethodVisitor ASM bytecode injection (intercepting MultiMap.add() calls inside getParts()) with a clean ByteBuddy exit-advice approach: - Remove ParameterCollector, GetPartsAdvice, GetPartsVisitorWrapper, RequestClassVisitor, and GetPartsMethodVisitor; drop HasTypeAdvice from the instrumentation module. - Add PartHelper with extractFormFields() (reads InputStream per part) and extractFilenames() (parses Content-Disposition manually, since Part.getSubmittedFileName() is Servlet 3.1+ and not available in Jetty 8.x). - Add GetFilenamesAdvice (@RequiresRequestContext / @ActiveRequestContext) on getParts():Collection that fires requestBodyProcessed() with form fields and requestFilesFilenames() with upload filenames. Uses CallDepthThreadLocalMap to guard against reentrant calls. - Jetty8LatestDepForkedTest: set testBodyFilenamesCalledOnce() and testBodyFilenamesCalledOnceCombined() to false because Jetty 8.x has no _multiParts / _contentParameters field guards to suppress duplicate firings.
1 parent 22596a2 commit 3b7c9ff

4 files changed

Lines changed: 176 additions & 209 deletions

File tree

dd-java-agent/instrumentation/jetty/jetty-appsec/jetty-appsec-8.1.3/src/main/java/datadog/trace/instrumentation/jetty8/ParameterCollector.java

Lines changed: 0 additions & 64 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package datadog.trace.instrumentation.jetty8;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.util.ArrayList;
7+
import java.util.Collection;
8+
import java.util.Collections;
9+
import java.util.LinkedHashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
import javax.servlet.http.Part;
13+
14+
/**
15+
* Helper for extracting filenames and form-field values from Servlet 3.0 {@link Part} objects.
16+
*
17+
* <p>{@code Part.getSubmittedFileName()} was added in Servlet 3.1 (Jetty 9.1+); for Jetty 8.x we
18+
* must parse the {@code Content-Disposition} header manually.
19+
*/
20+
public class PartHelper {
21+
22+
private PartHelper() {}
23+
24+
/**
25+
* Returns filenames found in {@code parts} by parsing each part's {@code Content-Disposition}
26+
* header for a {@code filename=} parameter.
27+
*/
28+
public static List<String> extractFilenames(Collection<?> parts) {
29+
if (parts == null || parts.isEmpty()) {
30+
return Collections.emptyList();
31+
}
32+
List<String> filenames = new ArrayList<>();
33+
for (Object obj : parts) {
34+
String filename = filenameFromPart((Part) obj);
35+
if (filename != null && !filename.isEmpty()) {
36+
filenames.add(filename);
37+
}
38+
}
39+
return filenames;
40+
}
41+
42+
/**
43+
* Returns a name→values map of form-field parts (those without a {@code filename=} parameter).
44+
* File-upload parts are skipped to avoid reading potentially large content.
45+
*/
46+
public static Map<String, List<String>> extractFormFields(Collection<?> parts) {
47+
if (parts == null || parts.isEmpty()) {
48+
return Collections.emptyMap();
49+
}
50+
Map<String, List<String>> result = new LinkedHashMap<>();
51+
for (Object obj : parts) {
52+
Part part = (Part) obj;
53+
if (filenameFromPart(part) != null) {
54+
continue; // file-upload part — skip
55+
}
56+
String name = part.getName();
57+
if (name == null) {
58+
continue;
59+
}
60+
String value = readPartContent(part);
61+
if (value == null) {
62+
continue;
63+
}
64+
List<String> values = result.get(name);
65+
if (values == null) {
66+
values = new ArrayList<>();
67+
result.put(name, values);
68+
}
69+
values.add(value);
70+
}
71+
return result;
72+
}
73+
74+
/**
75+
* Extracts the {@code filename} value from a {@code Content-Disposition} header, or {@code null}
76+
* if the part has no filename (i.e. it is a plain form field).
77+
*/
78+
static String filenameFromPart(Part part) {
79+
String cd = part.getHeader("Content-Disposition");
80+
if (cd == null) {
81+
return null;
82+
}
83+
for (String token : cd.split(";")) {
84+
token = token.trim();
85+
if (token.startsWith("filename=")) {
86+
String name = token.substring("filename=".length()).trim();
87+
if (name.length() >= 2 && name.charAt(0) == '"' && name.charAt(name.length() - 1) == '"') {
88+
name = name.substring(1, name.length() - 1);
89+
}
90+
return name.isEmpty() ? null : name;
91+
}
92+
}
93+
return null;
94+
}
95+
96+
private static String readPartContent(Part part) {
97+
try {
98+
InputStream is = part.getInputStream();
99+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
100+
byte[] buf = new byte[4096];
101+
int read;
102+
while ((read = is.read(buf)) != -1) {
103+
baos.write(buf, 0, read);
104+
}
105+
return baos.toString("UTF-8");
106+
} catch (IOException e) {
107+
return null;
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)