Skip to content

Commit fb9107c

Browse files
committed
Fix crash on malformed request paths
1 parent 60cb102 commit fb9107c

2 files changed

Lines changed: 67 additions & 2 deletions

File tree

src/nitter.nim

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import asyncdispatch, strformat, logging
33
from net import Port
44
from htmlgen import a
5-
from os import getEnv
5+
from os import getEnv, normalizedPath
66

77
import jester
88

@@ -63,12 +63,17 @@ createDebugRouter(cfg)
6363

6464
settings:
6565
port = Port(cfg.port)
66-
staticDir = cfg.staticDir
66+
staticDir = normalizedPath(cfg.staticDir)
6767
bindAddr = cfg.address
6868
reusePort = true
69+
maxBody = 64 * 1024
6970

7071
routes:
7172
before:
73+
# Reject malformed paths
74+
if request.path.len == 0 or request.path[0] != '/':
75+
halt Http400
76+
7277
# skip all file URLs
7378
cond "." notin request.path
7479
applyUrlPrefs()

tests/test_security.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import subprocess
2+
from parameterized import parameterized
3+
4+
BASE_URL = 'http://localhost:8080'
5+
6+
7+
def curl_status(url):
8+
"""Get HTTP status code using curl to avoid URL normalization by Python libs."""
9+
result = subprocess.run(
10+
['curl', '-s', '-o', '/dev/null', '-w', '%{http_code}', url],
11+
capture_output=True, text=True, timeout=10
12+
)
13+
return int(result.stdout)
14+
15+
16+
class TestMalformedPaths:
17+
"""Test that malformed paths don't crash the server.
18+
19+
URLs like //foo are parsed as having 'foo' as the authority (host),
20+
resulting in an empty path. Empty paths previously crashed jester's
21+
static file handler. Now they return 400.
22+
23+
URLs like //foo/bar are parsed as authority='foo', path='/bar',
24+
so they route normally (not empty path).
25+
"""
26+
27+
@parameterized.expand([
28+
# These parse to empty paths -> 400
29+
('//lefty_rae', 400),
30+
('//test', 400),
31+
('//anyuser', 400),
32+
])
33+
def test_empty_path_returns_400(self, path, expected_status):
34+
"""URLs that parse to empty paths should return 400, not crash."""
35+
status = curl_status(f'{BASE_URL}{path}')
36+
assert status == expected_status, \
37+
f'Expected {expected_status} for {path}, got {status}'
38+
39+
@parameterized.expand([
40+
('/jack', 200),
41+
('/about', 200),
42+
('/', 200),
43+
])
44+
def test_normal_paths_work(self, path, expected_status):
45+
"""Normal paths should still work."""
46+
status = curl_status(f'{BASE_URL}{path}')
47+
assert status == expected_status, \
48+
f'Expected {expected_status} for {path}, got {status}'
49+
50+
def test_server_survives_malformed_requests(self):
51+
"""Server should handle malformed requests without crashing."""
52+
# These all parse to empty paths
53+
malformed_paths = ['//a', '//b', '//c', '//user', '//test']
54+
for path in malformed_paths:
55+
status = curl_status(f'{BASE_URL}{path}')
56+
assert status == 400, f'Expected 400 for {path}, got {status}'
57+
58+
# Verify server is still responding after malformed requests
59+
status = curl_status(f'{BASE_URL}/')
60+
assert status == 200, 'Server should still be alive'

0 commit comments

Comments
 (0)