Skip to content

Commit e43466f

Browse files
committed
test(appsec/jetty): add unit tests for PartHelper (jetty-appsec-8.1.3)
1 parent 3b7c9ff commit e43466f

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

  • dd-java-agent/instrumentation/jetty/jetty-appsec/jetty-appsec-8.1.3/src/test/groovy/datadog/trace/instrumentation/jetty8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package datadog.trace.instrumentation.jetty8
2+
3+
import javax.servlet.http.Part
4+
import spock.lang.Specification
5+
6+
class PartHelperTest extends Specification {
7+
8+
// ── extractFilenames ────────────────────────────────────────────────────────
9+
10+
def "extractFilenames returns empty list for null collection"() {
11+
expect:
12+
PartHelper.extractFilenames(null) == []
13+
}
14+
15+
def "extractFilenames returns empty list for empty collection"() {
16+
expect:
17+
PartHelper.extractFilenames([]) == []
18+
}
19+
20+
def "extractFilenames returns empty list when no parts have a filename"() {
21+
given:
22+
def parts = [field('a', 'x'), field('b', 'y')]
23+
24+
expect:
25+
PartHelper.extractFilenames(parts) == []
26+
}
27+
28+
def "extractFilenames extracts filename from a single file part"() {
29+
given:
30+
def parts = [filePart('photo.jpg')]
31+
32+
expect:
33+
PartHelper.extractFilenames(parts) == ['photo.jpg']
34+
}
35+
36+
def "extractFilenames extracts filenames from multiple file parts"() {
37+
given:
38+
def parts = [filePart('a.jpg'), filePart('b.png'), filePart('c.pdf')]
39+
40+
expect:
41+
PartHelper.extractFilenames(parts) == ['a.jpg', 'b.png', 'c.pdf']
42+
}
43+
44+
def "extractFilenames skips form-field parts and keeps file parts"() {
45+
given:
46+
def parts = [field('x', 'v'), filePart('upload.zip'), field('y', 'w')]
47+
48+
expect:
49+
PartHelper.extractFilenames(parts) == ['upload.zip']
50+
}
51+
52+
def "extractFilenames preserves filenames with spaces and special characters"() {
53+
given:
54+
def parts = [filePart('my file.tar.gz'), filePart('résumé.pdf')]
55+
56+
expect:
57+
PartHelper.extractFilenames(parts) == ['my file.tar.gz', 'résumé.pdf']
58+
}
59+
60+
// ── filenameFromPart ────────────────────────────────────────────────────────
61+
62+
def "filenameFromPart returns null when Content-Disposition header is absent"() {
63+
given:
64+
Part p = Stub(Part) { getHeader('Content-Disposition') >> null }
65+
66+
expect:
67+
PartHelper.filenameFromPart(p) == null
68+
}
69+
70+
def "filenameFromPart returns null when there is no filename parameter"() {
71+
given:
72+
Part p = Stub(Part) { getHeader('Content-Disposition') >> 'form-data; name="field"' }
73+
74+
expect:
75+
PartHelper.filenameFromPart(p) == null
76+
}
77+
78+
def "filenameFromPart extracts unquoted filename"() {
79+
given:
80+
Part p = Stub(Part) { getHeader('Content-Disposition') >> 'form-data; name="file"; filename=photo.jpg' }
81+
82+
expect:
83+
PartHelper.filenameFromPart(p) == 'photo.jpg'
84+
}
85+
86+
def "filenameFromPart strips quotes from filename"() {
87+
given:
88+
Part p = Stub(Part) { getHeader('Content-Disposition') >> 'form-data; name="file"; filename="photo.jpg"' }
89+
90+
expect:
91+
PartHelper.filenameFromPart(p) == 'photo.jpg'
92+
}
93+
94+
def "filenameFromPart returns null for empty quoted filename"() {
95+
given:
96+
Part p = Stub(Part) { getHeader('Content-Disposition') >> 'form-data; name="file"; filename=""' }
97+
98+
expect:
99+
PartHelper.filenameFromPart(p) == null
100+
}
101+
102+
def "filenameFromPart returns null for empty unquoted filename"() {
103+
given:
104+
Part p = Stub(Part) { getHeader('Content-Disposition') >> 'form-data; name="file"; filename=' }
105+
106+
expect:
107+
PartHelper.filenameFromPart(p) == null
108+
}
109+
110+
// ── extractFormFields ───────────────────────────────────────────────────────
111+
112+
def "extractFormFields returns empty map for null collection"() {
113+
expect:
114+
PartHelper.extractFormFields(null) == [:]
115+
}
116+
117+
def "extractFormFields returns empty map for empty collection"() {
118+
expect:
119+
PartHelper.extractFormFields([]) == [:]
120+
}
121+
122+
def "extractFormFields skips file-upload parts"() {
123+
given:
124+
def parts = [filePart('evil.php')]
125+
126+
expect:
127+
PartHelper.extractFormFields(parts) == [:]
128+
}
129+
130+
def "extractFormFields extracts single form field"() {
131+
given:
132+
def parts = [field('username', 'alice')]
133+
134+
expect:
135+
PartHelper.extractFormFields(parts) == [username: ['alice']]
136+
}
137+
138+
def "extractFormFields groups multiple values under the same name"() {
139+
given:
140+
def parts = [field('tag', 'foo'), field('tag', 'bar')]
141+
142+
expect:
143+
PartHelper.extractFormFields(parts) == [tag: ['foo', 'bar']]
144+
}
145+
146+
def "extractFormFields mixes fields and skips files"() {
147+
given:
148+
def parts = [field('a', 'x'), filePart('upload.bin'), field('b', 'y')]
149+
150+
expect:
151+
PartHelper.extractFormFields(parts) == [a: ['x'], b: ['y']]
152+
}
153+
154+
// ── helpers ─────────────────────────────────────────────────────────────────
155+
156+
/** Creates a stub Part that looks like a plain form field (no filename). */
157+
private Part field(String name, String value) {
158+
Part p = Stub(Part)
159+
p.getHeader('Content-Disposition') >> "form-data; name=\"${name}\""
160+
p.getName() >> name
161+
p.getInputStream() >> new ByteArrayInputStream(value.getBytes('UTF-8'))
162+
return p
163+
}
164+
165+
/** Creates a stub Part that looks like a file upload with the given filename. */
166+
private Part filePart(String filename) {
167+
Part p = Stub(Part)
168+
p.getHeader('Content-Disposition') >> "form-data; name=\"file\"; filename=\"${filename}\""
169+
return p
170+
}
171+
}

0 commit comments

Comments
 (0)