1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
from tests.api import SnekAPITestCase
class TestEvalResource(SnekAPITestCase):
PATH = "/eval"
def test_post_valid_200(self):
cases = [
{"args": ["-c", "print('output')"]},
{"input": "print('hello')"},
{"input": "print('hello')", "args": ["-c"]},
{"input": "print('hello')", "args": [""]},
{"input": "pass", "args": ["-m", "timeit"]},
]
for body in cases:
with self.subTest():
result = self.simulate_post(self.PATH, json=body)
self.assertEqual(result.status_code, 200)
self.assertEqual("output", result.json["stdout"])
self.assertEqual(0, result.json["returncode"])
def test_post_invalid_schema_400(self):
body = {"stuff": "foo"}
result = self.simulate_post(self.PATH, json=body)
self.assertEqual(result.status_code, 400)
expected = {
"title": "Request data failed validation",
"description": "{'stuff': 'foo'} is not valid under any of the given schemas",
}
self.assertEqual(expected, result.json)
def test_post_invalid_data_400(self):
bodies = ({"args": 400}, {"args": [], "files": [215]})
expects = ["400 is not of type 'array'", "215 is not of type 'object'"]
for body, expected in zip(bodies, expects):
with self.subTest():
result = self.simulate_post(self.PATH, json=body)
self.assertEqual(result.status_code, 400)
expected_json = {
"title": "Request data failed validation",
"description": expected,
}
self.assertEqual(expected_json, result.json)
def test_files_path(self):
"""Normal paths should work with 200."""
test_paths = [
"file.txt",
"./0.jpg",
"path/to/file",
"folder/../hm",
"folder/./to/./somewhere",
"traversal/but/../not/beyond/../root",
r"backslash\\okay",
r"backslash\okay",
"numbers/0123456789",
]
for path in test_paths:
with self.subTest(path=path):
body = {"args": ["test.py"], "files": [{"path": path}]}
result = self.simulate_post(self.PATH, json=body)
self.assertEqual(result.status_code, 200)
self.assertEqual("output", result.json["stdout"])
self.assertEqual(0, result.json["returncode"])
def test_files_illegal_path_traversal(self):
"""Traversal beyond root should be denied with 400 error."""
test_paths = [
"../secrets",
"../../dir",
"dir/../../secrets",
"dir/var/../../../file",
]
for path in test_paths:
with self.subTest(path=path):
body = {"args": ["test.py"], "files": [{"path": path}]}
result = self.simulate_post(self.PATH, json=body)
self.assertEqual(result.status_code, 400)
expected = {
"title": "Request file is invalid",
"description": f"File path '{path}' may not traverse beyond root",
}
self.assertEqual(expected, result.json)
def test_files_illegal_path_absolute(self):
"""Absolute file paths should 400-error at json schema validation stage."""
test_paths = [
"/",
"/etc",
"/etc/vars/secrets",
"/absolute",
"/file.bin",
]
for path in test_paths:
with self.subTest(path=path):
body = {"args": ["test.py"], "files": [{"path": path}]}
result = self.simulate_post(self.PATH, json=body)
self.assertEqual(result.status_code, 400)
self.assertEqual("Request data failed validation", result.json["title"])
self.assertIn("does not match", result.json["description"])
def test_files_illegal_path_null_byte(self):
"""Paths containing \0 should 400-error at json schema validation stage."""
test_paths = [
r"etc/passwd\0",
r"a\0b",
r"\0",
r"\\0",
r"var/\0/path",
]
for path in test_paths:
with self.subTest(path=path):
body = {"args": ["test.py"], "files": [{"path": path}]}
result = self.simulate_post(self.PATH, json=body)
self.assertEqual(result.status_code, 400)
self.assertEqual("Request data failed validation", result.json["title"])
self.assertIn("does not match", result.json["description"])
def test_post_invalid_content_type_415(self):
body = "{'input': 'foo'}"
headers = {"Content-Type": "application/xml"}
result = self.simulate_post(self.PATH, body=body, headers=headers)
self.assertEqual(result.status_code, 415)
expected = {
"title": "415 Unsupported Media Type",
"description": "application/xml is an unsupported media type.",
}
self.assertEqual(expected, result.json)
def test_disallowed_method_405(self):
result = self.simulate_get(self.PATH)
self.assertEqual(result.status_code, 405)
def test_options_allow_post_only(self):
result = self.simulate_options(self.PATH)
self.assertEqual(result.status_code, 200)
self.assertEqual(result.headers.get("Allow"), "POST")
|