瀏覽代碼

Fix crash on malformed request paths

Zed 3 月之前
父節點
當前提交
fb9107cff6
共有 2 個文件被更改,包括 67 次插入2 次删除
  1. 7 2
      src/nitter.nim
  2. 60 0
      tests/test_security.py

+ 7 - 2
src/nitter.nim

@@ -2,7 +2,7 @@
 import asyncdispatch, strformat, logging
 from net import Port
 from htmlgen import a
-from os import getEnv
+from os import getEnv, normalizedPath
 
 import jester
 
@@ -63,12 +63,17 @@ createDebugRouter(cfg)
 
 settings:
   port = Port(cfg.port)
-  staticDir = cfg.staticDir
+  staticDir = normalizedPath(cfg.staticDir)
   bindAddr = cfg.address
   reusePort = true
+  maxBody = 64 * 1024
 
 routes:
   before:
+    # Reject malformed paths
+    if request.path.len == 0 or request.path[0] != '/':
+      halt Http400
+
     # skip all file URLs
     cond "." notin request.path
     applyUrlPrefs()

+ 60 - 0
tests/test_security.py

@@ -0,0 +1,60 @@
+import subprocess
+from parameterized import parameterized
+
+BASE_URL = 'http://localhost:8080'
+
+
+def curl_status(url):
+    """Get HTTP status code using curl to avoid URL normalization by Python libs."""
+    result = subprocess.run(
+        ['curl', '-s', '-o', '/dev/null', '-w', '%{http_code}', url],
+        capture_output=True, text=True, timeout=10
+    )
+    return int(result.stdout)
+
+
+class TestMalformedPaths:
+    """Test that malformed paths don't crash the server.
+
+    URLs like //foo are parsed as having 'foo' as the authority (host),
+    resulting in an empty path. Empty paths previously crashed jester's
+    static file handler. Now they return 400.
+
+    URLs like //foo/bar are parsed as authority='foo', path='/bar',
+    so they route normally (not empty path).
+    """
+
+    @parameterized.expand([
+        # These parse to empty paths -> 400
+        ('//lefty_rae', 400),
+        ('//test', 400),
+        ('//anyuser', 400),
+    ])
+    def test_empty_path_returns_400(self, path, expected_status):
+        """URLs that parse to empty paths should return 400, not crash."""
+        status = curl_status(f'{BASE_URL}{path}')
+        assert status == expected_status, \
+            f'Expected {expected_status} for {path}, got {status}'
+
+    @parameterized.expand([
+        ('/jack', 200),
+        ('/about', 200),
+        ('/', 200),
+    ])
+    def test_normal_paths_work(self, path, expected_status):
+        """Normal paths should still work."""
+        status = curl_status(f'{BASE_URL}{path}')
+        assert status == expected_status, \
+            f'Expected {expected_status} for {path}, got {status}'
+
+    def test_server_survives_malformed_requests(self):
+        """Server should handle malformed requests without crashing."""
+        # These all parse to empty paths
+        malformed_paths = ['//a', '//b', '//c', '//user', '//test']
+        for path in malformed_paths:
+            status = curl_status(f'{BASE_URL}{path}')
+            assert status == 400, f'Expected 400 for {path}, got {status}'
+
+        # Verify server is still responding after malformed requests
+        status = curl_status(f'{BASE_URL}/')
+        assert status == 200, 'Server should still be alive'