Просмотр исходного кода

Redirect GET and HEAD requests to sanitized path

Unrud 4 лет назад
Родитель
Сommit
b93842b10c
4 измененных файлов с 29 добавлено и 17 удалено
  1. 16 8
      radicale/app/__init__.py
  2. 3 6
      radicale/app/get.py
  3. 9 2
      radicale/tests/test_base.py
  4. 1 1
      radicale/web/internal_data/fn.js

+ 16 - 8
radicale/app/__init__.py

@@ -161,6 +161,9 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
             # Return response content
             return status_text, list(headers.items()), answers
 
+        time_begin = datetime.datetime.now()
+        request_method = environ["REQUEST_METHOD"].upper()
+        unsafe_path = environ.get("PATH_INFO", "")
         remote_host = "unknown"
         if environ.get("REMOTE_HOST"):
             remote_host = repr(environ["REMOTE_HOST"])
@@ -175,11 +178,9 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
         depthinfo = ""
         if environ.get("HTTP_DEPTH"):
             depthinfo = " with depth %r" % environ["HTTP_DEPTH"]
-        time_begin = datetime.datetime.now()
-        logger.info(
-            "%s request for %r%s received from %s%s",
-            environ["REQUEST_METHOD"], environ.get("PATH_INFO", ""), depthinfo,
-            remote_host, remote_useragent)
+        logger.info("%s request for %r%s received from %s%s",
+                    request_method, unsafe_path, depthinfo,
+                    remote_host, remote_useragent)
         logger.debug("Request headers:\n%s",
                      pprint.pformat(self._scrub_headers(environ)))
 
@@ -191,12 +192,19 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
         logger.debug("Base prefix: %r", base_prefix)
         # Sanitize request URI (a WSGI server indicates with an empty path,
         # that the URL targets the application root without a trailing slash)
-        path = pathutils.sanitize_path(environ.get("PATH_INFO", ""))
+        path = pathutils.sanitize_path(unsafe_path)
+        if unsafe_path != path and request_method in ["GET", "HEAD"]:
+            location = base_prefix + path
+            logger.info("Redirecting to sanitized path: %r ==> %r",
+                        base_prefix + unsafe_path, location)
+            return response(
+                client.MOVED_PERMANENTLY,
+                {"Location": location, "Content-Type": "text/plain"},
+                "Redirected to %s" % location)
         logger.debug("Sanitized path: %r", path)
 
         # Get function corresponding to method
-        function = getattr(
-            self, "do_%s" % environ["REQUEST_METHOD"].upper(), None)
+        function = getattr(self, "do_%s" % request_method, None)
         if not function:
             return response(*httputils.METHOD_NOT_ALLOWED)
 

+ 3 - 6
radicale/app/get.py

@@ -62,13 +62,10 @@ class ApplicationPartGet(ApplicationBase):
         """Manage GET request."""
         # Redirect to .web if the root URL is requested
         if not pathutils.strip_path(path):
-            web_path = ".web"
-            if not environ.get("PATH_INFO"):
-                web_path = posixpath.join(posixpath.basename(base_prefix),
-                                          web_path)
+            location = ".web"
             return (client.FOUND,
-                    {"Location": web_path, "Content-Type": "text/plain"},
-                    "Redirected to %s" % web_path)
+                    {"Location": location, "Content-Type": "text/plain"},
+                    "Redirected to %s" % location)
         # Dispatch .web URL to web module
         if path == "/.web" or path.startswith("/.web/"):
             return self._web.get(environ, base_prefix, path, user)

+ 9 - 2
radicale/tests/test_base.py

@@ -60,8 +60,15 @@ permissions: RrWw""")
         """GET request at "/" with SCRIPT_NAME."""
         _, answer = self.get("/", check=302, SCRIPT_NAME="/radicale")
         assert answer == "Redirected to .web"
-        _, answer = self.get("", check=302, SCRIPT_NAME="/radicale")
-        assert answer == "Redirected to radicale/.web"
+
+    def test_sanitized_path(self) -> None:
+        """GET request with unsanitized paths."""
+        for path, sane_path in [("//", "/"), ("", "/"), ("/a//b", "/a/b"),
+                                ("/a//b/", "/a/b/")]:
+            _, answer = self.get(path, check=301)
+            assert answer == "Redirected to %s" % sane_path
+            _, answer = self.get(path, check=301, SCRIPT_NAME="/radicale")
+            assert answer == "Redirected to /radicale%s" % sane_path
 
     def test_add_event(self) -> None:
         """Add an event."""

+ 1 - 1
radicale/web/internal_data/fn.js

@@ -28,7 +28,7 @@ const SERVER = location.origin;
  * @const
  * @type {string}
  */
-const ROOT_PATH = location.pathname.replace(new RegExp("/+[^/]+/*(/index\\.html?)?$"), "") + '/';
+const ROOT_PATH = (new URL("..", location.href)).pathname;
 
 /**
  * Regex to match and normalize color