Skip to content

Commit 9b4ccf2

Browse files
committed
add more tests
1 parent dea22af commit 9b4ccf2

2 files changed

Lines changed: 272 additions & 0 deletions

File tree

tests/python/tests/http_server/php/index.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,21 @@ public function work(string $output) {
243243
echo "name : " . $_POST["name"] . "\n";
244244
echo "role : " . $_POST["role"] . "\n";
245245
break;
246+
case "quoted_boundary":
247+
echo "name : " . $_POST["name"] . "\n";
248+
break;
249+
case "boundary_with_charset":
250+
echo "data : " . $_POST["data"] . "\n";
251+
break;
252+
case "empty_value":
253+
echo "empty_field : " . $_POST["empty_field"] . "\n";
254+
echo "non_empty : " . $_POST["non_empty"] . "\n";
255+
break;
256+
case "special_chars_in_name":
257+
echo "underscore : " . $_POST["name_with_underscore"] . "\n";
258+
echo "dash : " . $_POST["name-with-dash"] . "\n";
259+
echo "dots : " . $_POST["name.with.dots"] . "\n";
260+
break;
246261
case "simple_file_attribute":
247262
echo "filename : " . $_FILES["file"]['name'] . "\n";
248263
$tmp_name = $_FILES["file"]['tmp_name'];
@@ -259,6 +274,19 @@ public function work(string $output) {
259274
$file_first_line = file($second_file)[0];
260275
echo "content-2 : " . $file_first_line;
261276
break;
277+
case "mixed_files_and_fields":
278+
echo "text : " . $_POST["text_field"] . "\n";
279+
echo "filename : " . $_FILES["upload"]['name'] . "\n";
280+
echo "another : " . $_POST["another_field"] . "\n";
281+
break;
282+
case "file_without_content_type":
283+
echo "filename : " . $_FILES["file"]['name'] . "\n";
284+
echo "type : " . $_FILES["file"]['type'] . "\n";
285+
break;
286+
case "binary_file":
287+
echo "filename : " . $_FILES["binary"]['name'] . "\n";
288+
echo "size : " . $_FILES["binary"]['size'] . "\n";
289+
break;
262290
case "name_urlencoded_attribute":
263291
echo $_POST["form"]['name'] . "\n";
264292
echo $_POST["form"]['note'] . "\n";

tests/python/tests/http_server/test_multipart.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,131 @@
66

77
class TestMultipartContentType(WebServerAutoTestCase):
88

9+
def test_multipart_quoted_boundary(self):
10+
"""Test that quoted boundary in Content-Type header is handled correctly."""
11+
boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
12+
13+
data = (f'--{boundary}\r\n'
14+
'Content-Disposition: form-data; name="name"\r\n'
15+
'\r\n'
16+
'John\r\n'
17+
f'--{boundary}--\r\n'
18+
).encode('utf-8')
19+
20+
# Boundary is quoted in Content-Type header
21+
headers = {
22+
'Accept': '*/*',
23+
'Content-Type': f'multipart/form-data; boundary="{boundary}"',
24+
'Content-Length': str(len(data)),
25+
}
26+
27+
response = self.web_server.http_request(
28+
uri='/test_multipart?type=quoted_boundary',
29+
method='POST',
30+
headers=headers,
31+
data=data,
32+
)
33+
34+
self.assertEqual(200, response.status_code)
35+
self.assertTrue(response.content.find(b'name : John') != -1)
36+
37+
def test_multipart_boundary_with_charset(self):
38+
"""Test that boundary with additional params (charset) is parsed correctly."""
39+
boundary = '------------------------d74496d66958873e'
40+
41+
data = (f'--{boundary}\r\n'
42+
'Content-Disposition: form-data; name="data"\r\n'
43+
'\r\n'
44+
'test value\r\n'
45+
f'--{boundary}--\r\n'
46+
).encode('utf-8')
47+
48+
# Boundary with charset after it
49+
headers = {
50+
'Accept': '*/*',
51+
'Content-Type': f'multipart/form-data; boundary={boundary}; charset=UTF-8',
52+
'Content-Length': str(len(data)),
53+
}
54+
55+
response = self.web_server.http_request(
56+
uri='/test_multipart?type=boundary_with_charset',
57+
method='POST',
58+
headers=headers,
59+
data=data,
60+
)
61+
62+
self.assertEqual(200, response.status_code)
63+
self.assertTrue(response.content.find(b'data : test value') != -1)
64+
65+
def test_multipart_empty_value(self):
66+
"""Test that empty form field values are handled correctly."""
67+
boundary = '------------------------d74496d66958873e'
68+
69+
data = (f'--{boundary}\r\n'
70+
'Content-Disposition: form-data; name="empty_field"\r\n'
71+
'\r\n'
72+
'\r\n' # Empty value
73+
f'--{boundary}\r\n'
74+
'Content-Disposition: form-data; name="non_empty"\r\n'
75+
'\r\n'
76+
'value\r\n'
77+
f'--{boundary}--\r\n'
78+
).encode('utf-8')
79+
80+
headers = {
81+
'Accept': '*/*',
82+
'Content-Type': f'multipart/form-data; boundary={boundary}',
83+
'Content-Length': str(len(data)),
84+
}
85+
86+
response = self.web_server.http_request(
87+
uri='/test_multipart?type=empty_value',
88+
method='POST',
89+
headers=headers,
90+
data=data,
91+
)
92+
93+
self.assertEqual(200, response.status_code)
94+
self.assertTrue(response.content.find(b'empty_field :') != -1)
95+
self.assertTrue(response.content.find(b'non_empty : value') != -1)
96+
97+
def test_multipart_special_chars_in_name(self):
98+
"""Test form field names with special characters."""
99+
boundary = '------------------------d74496d66958873e'
100+
101+
data = (f'--{boundary}\r\n'
102+
'Content-Disposition: form-data; name="name_with_underscore"\r\n'
103+
'\r\n'
104+
'value1\r\n'
105+
f'--{boundary}\r\n'
106+
'Content-Disposition: form-data; name="name-with-dash"\r\n'
107+
'\r\n'
108+
'value2\r\n'
109+
f'--{boundary}\r\n'
110+
'Content-Disposition: form-data; name="name.with.dots"\r\n'
111+
'\r\n'
112+
'value3\r\n'
113+
f'--{boundary}--\r\n'
114+
).encode('utf-8')
115+
116+
headers = {
117+
'Accept': '*/*',
118+
'Content-Type': f'multipart/form-data; boundary={boundary}',
119+
'Content-Length': str(len(data)),
120+
}
121+
122+
response = self.web_server.http_request(
123+
uri='/test_multipart?type=special_chars_in_name',
124+
method='POST',
125+
headers=headers,
126+
data=data,
127+
)
128+
129+
self.assertEqual(200, response.status_code)
130+
self.assertTrue(response.content.find(b'value1') != -1)
131+
self.assertTrue(response.content.find(b'value2') != -1)
132+
self.assertTrue(response.content.find(b'value3') != -1)
133+
9134
def test_multipart_name_attributes(self):
10135
boundary = "------------------------d74496d66958873e"
11136

@@ -184,6 +309,125 @@ def test_multipart_superglobal_modify(self):
184309
# check that script delete tmp files at the end
185310
self.assertEqual(sorted(tmp_files), sorted(tmp_files_after_script))
186311

312+
def test_multipart_mixed_files_and_fields(self):
313+
"""Test mixing files and regular form fields in the same request."""
314+
tmp_files = os.listdir("/tmp/")
315+
boundary = '------------------------d74496d66958873e'
316+
317+
file_bytes = b'File content here\n'
318+
319+
data = (f'--{boundary}\r\n'
320+
'Content-Disposition: form-data; name="text_field"\r\n'
321+
'\r\n'
322+
'text value\r\n'
323+
f'--{boundary}\r\n'
324+
'Content-Disposition: form-data; name="upload"; filename="test.txt"\r\n'
325+
'Content-Type: text/plain\r\n'
326+
'\r\n'
327+
).encode('utf-8') + file_bytes + (
328+
f'\r\n--{boundary}\r\n'
329+
'Content-Disposition: form-data; name="another_field"\r\n'
330+
'\r\n'
331+
'another value\r\n'
332+
f'--{boundary}--\r\n'
333+
).encode('utf-8')
334+
335+
headers = {
336+
'Accept': '*/*',
337+
'Content-Type': f'multipart/form-data; boundary={boundary}',
338+
'Content-Length': str(len(data)),
339+
}
340+
341+
response = self.web_server.http_request(
342+
uri='/test_multipart?type=mixed_files_and_fields',
343+
method='POST',
344+
headers=headers,
345+
data=data,
346+
)
347+
348+
self.assertEqual(200, response.status_code)
349+
self.assertTrue(response.content.find(b'text : text value') != -1)
350+
self.assertTrue(response.content.find(b'filename : test.txt') != -1)
351+
self.assertTrue(response.content.find(b'another : another value') != -1)
352+
353+
tmp_files_after_script = os.listdir("/tmp/")
354+
# check that script delete tmp files at the end
355+
self.assertEqual(sorted(tmp_files), sorted(tmp_files_after_script))
356+
357+
def test_multipart_file_without_content_type(self):
358+
"""Test file upload without explicit Content-Type header."""
359+
tmp_files = os.listdir("/tmp/")
360+
boundary = '------------------------d74496d66958873e'
361+
362+
file_bytes = b'File without content type\n'
363+
364+
# No Content-Type header for the file part
365+
data = (f'--{boundary}\r\n'
366+
'Content-Disposition: form-data; name="file"; filename="no_type.txt"\r\n'
367+
'\r\n'
368+
).encode('utf-8') + file_bytes + (
369+
f'\r\n--{boundary}--\r\n'
370+
).encode('utf-8')
371+
372+
headers = {
373+
'Accept': '*/*',
374+
'Content-Type': f'multipart/form-data; boundary={boundary}',
375+
'Content-Length': str(len(data)),
376+
}
377+
378+
response = self.web_server.http_request(
379+
uri='/test_multipart?type=file_without_content_type',
380+
method='POST',
381+
headers=headers,
382+
data=data,
383+
)
384+
385+
self.assertEqual(200, response.status_code)
386+
self.assertTrue(response.content.find(b'filename : no_type.txt') != -1)
387+
# Should default to text/plain
388+
self.assertTrue(response.content.find(b'type : text/plain') != -1)
389+
390+
tmp_files_after_script = os.listdir("/tmp/")
391+
# check that script delete tmp files at the end
392+
self.assertEqual(sorted(tmp_files), sorted(tmp_files_after_script))
393+
394+
def test_multipart_binary_file(self):
395+
"""Test uploading binary file content."""
396+
tmp_files = os.listdir("/tmp/")
397+
boundary = '------------------------d74496d66958873e'
398+
399+
# Binary content with null bytes
400+
file_bytes = b'\x00\x01\x02\x03\xff\xfe\xfd\xfc'
401+
402+
data = (f'--{boundary}\r\n'
403+
'Content-Disposition: form-data; name="binary"; filename="data.bin"\r\n'
404+
'Content-Type: application/octet-stream\r\n'
405+
'\r\n'
406+
).encode('utf-8') + file_bytes + (
407+
f'\r\n--{boundary}--\r\n'
408+
).encode('utf-8')
409+
410+
headers = {
411+
'Accept': '*/*',
412+
'Content-Type': f'multipart/form-data; boundary={boundary}',
413+
'Content-Length': str(len(data)),
414+
}
415+
416+
response = self.web_server.http_request(
417+
uri='/test_multipart?type=binary_file',
418+
method='POST',
419+
headers=headers,
420+
data=data,
421+
)
422+
423+
self.assertEqual(200, response.status_code)
424+
self.assertTrue(response.content.find(b'size : 8') != -1)
425+
self.assertTrue(response.content.find(b'filename : data.bin') != -1)
426+
427+
tmp_files_after_script = os.listdir("/tmp/")
428+
# check that script delete tmp files at the end
429+
self.assertEqual(sorted(tmp_files), sorted(tmp_files_after_script))
430+
187431
def test_multipart_name_urlencoded_attribute(self):
188432
boundary = "------------------------d74496d66958873e"
189433

0 commit comments

Comments
 (0)