Skip to content

Commit 398ea51

Browse files
committed
Extract filename extraction to MultipartHelper in jetty-appsec-9.2 + unit tests
Move the getSubmittedFileName() loop from GetFilenamesAdvice into a new MultipartHelper helper class (injected via helperClassNames) so it can be unit tested in isolation. Add 8 Spock test cases covering null/empty collections, null/empty filenames, multiple parts, and special characters.
1 parent 20f8bb4 commit 398ea51

File tree

3 files changed

+109
-8
lines changed

3 files changed

+109
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package datadog.trace.instrumentation.jetty92;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
import java.util.Collections;
6+
import java.util.List;
7+
import javax.servlet.http.Part;
8+
9+
public class MultipartHelper {
10+
11+
private MultipartHelper() {}
12+
13+
/**
14+
* Extracts non-null, non-empty filenames from a collection of multipart {@link Part}s using
15+
* {@link Part#getSubmittedFileName()} (Servlet 3.1+).
16+
*
17+
* @return list of filenames; never {@code null}, may be empty
18+
*/
19+
public static List<String> extractFilenames(Collection<Part> parts) {
20+
if (parts == null || parts.isEmpty()) {
21+
return Collections.emptyList();
22+
}
23+
List<String> filenames = new ArrayList<>();
24+
for (Part part : parts) {
25+
String name = part.getSubmittedFileName();
26+
if (name != null && !name.isEmpty()) {
27+
filenames.add(name);
28+
}
29+
}
30+
return filenames;
31+
}
32+
}

dd-java-agent/instrumentation/jetty/jetty-appsec/jetty-appsec-9.2/src/main/java/datadog/trace/instrumentation/jetty92/RequestExtractContentParametersInstrumentation.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import datadog.trace.api.gateway.RequestContextSlot;
2020
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
2121
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
22-
import java.util.ArrayList;
2322
import java.util.Collection;
2423
import java.util.List;
2524
import java.util.function.BiFunction;
@@ -42,6 +41,11 @@ public String instrumentedType() {
4241
return "org.eclipse.jetty.server.Request";
4342
}
4443

44+
@Override
45+
public String[] helperClassNames() {
46+
return new String[] {packageName + ".MultipartHelper"};
47+
}
48+
4549
@Override
4650
public void methodAdvice(MethodTransformer transformer) {
4751
transformer.applyAdvice(
@@ -159,13 +163,7 @@ static void after(
159163
if (!proceed || t != null || parts == null || parts.isEmpty()) {
160164
return;
161165
}
162-
List<String> filenames = new ArrayList<>();
163-
for (Part part : parts) {
164-
String name = part.getSubmittedFileName();
165-
if (name != null && !name.isEmpty()) {
166-
filenames.add(name);
167-
}
168-
}
166+
List<String> filenames = MultipartHelper.extractFilenames(parts);
169167
if (filenames.isEmpty()) {
170168
return;
171169
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package datadog.trace.instrumentation.jetty92
2+
3+
import javax.servlet.http.Part
4+
import spock.lang.Specification
5+
6+
class MultipartHelperTest extends Specification {
7+
8+
def "returns empty list for null collection"() {
9+
expect:
10+
MultipartHelper.extractFilenames(null) == []
11+
}
12+
13+
def "returns empty list for empty collection"() {
14+
expect:
15+
MultipartHelper.extractFilenames([]) == []
16+
}
17+
18+
def "returns empty list when all parts have null filename"() {
19+
given:
20+
def parts = [part(null), part(null)]
21+
22+
expect:
23+
MultipartHelper.extractFilenames(parts) == []
24+
}
25+
26+
def "returns empty list when all parts have empty filename"() {
27+
given:
28+
def parts = [part(''), part('')]
29+
30+
expect:
31+
MultipartHelper.extractFilenames(parts) == []
32+
}
33+
34+
def "extracts filename from single part"() {
35+
given:
36+
def parts = [part('photo.jpg')]
37+
38+
expect:
39+
MultipartHelper.extractFilenames(parts) == ['photo.jpg']
40+
}
41+
42+
def "extracts filenames from multiple parts"() {
43+
given:
44+
def parts = [part('a.jpg'), part('b.png'), part('c.pdf')]
45+
46+
expect:
47+
MultipartHelper.extractFilenames(parts) == ['a.jpg', 'b.png', 'c.pdf']
48+
}
49+
50+
def "skips parts with null or empty filename and keeps valid ones"() {
51+
given:
52+
def parts = [part(null), part('valid.txt'), part(''), part('other.zip')]
53+
54+
expect:
55+
MultipartHelper.extractFilenames(parts) == ['valid.txt', 'other.zip']
56+
}
57+
58+
def "preserves filenames with spaces and special characters"() {
59+
given:
60+
def parts = [part('my file.tar.gz'), part('résumé.pdf')]
61+
62+
expect:
63+
MultipartHelper.extractFilenames(parts) == ['my file.tar.gz', 'résumé.pdf']
64+
}
65+
66+
private Part part(String submittedFileName) {
67+
Part p = Stub(Part)
68+
p.getSubmittedFileName() >> submittedFileName
69+
return p
70+
}
71+
}

0 commit comments

Comments
 (0)