Validate and return the absolute path. ``root`` is the configured path for the `StaticFileHandler`, and ``path`` is the result of `get_absolute_path` This is an instance method called during request processing, so it may raise `HTTPError` or use methods like
(self, root: str, absolute_path: str)
| 2954 | return abspath |
| 2955 | |
| 2956 | def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: |
| 2957 | """Validate and return the absolute path. |
| 2958 | |
| 2959 | ``root`` is the configured path for the `StaticFileHandler`, |
| 2960 | and ``path`` is the result of `get_absolute_path` |
| 2961 | |
| 2962 | This is an instance method called during request processing, |
| 2963 | so it may raise `HTTPError` or use methods like |
| 2964 | `RequestHandler.redirect` (return None after redirecting to |
| 2965 | halt further processing). This is where 404 errors for missing files |
| 2966 | are generated. |
| 2967 | |
| 2968 | This method may modify the path before returning it, but note that |
| 2969 | any such modifications will not be understood by `make_static_url`. |
| 2970 | |
| 2971 | In instance methods, this method's result is available as |
| 2972 | ``self.absolute_path``. |
| 2973 | |
| 2974 | .. versionadded:: 3.1 |
| 2975 | """ |
| 2976 | # os.path.abspath strips a trailing /. |
| 2977 | # We must add it back to `root` so that we only match files |
| 2978 | # in a directory named `root` instead of files starting with |
| 2979 | # that prefix. |
| 2980 | root = os.path.abspath(root) |
| 2981 | if not root.endswith(os.path.sep): |
| 2982 | # abspath always removes a trailing slash, except when |
| 2983 | # root is '/'. This is an unusual case, but several projects |
| 2984 | # have independently discovered this technique to disable |
| 2985 | # Tornado's path validation and (hopefully) do their own, |
| 2986 | # so we need to support it. |
| 2987 | root += os.path.sep |
| 2988 | # The trailing slash also needs to be temporarily added back |
| 2989 | # the requested path so a request to root/ will match. |
| 2990 | if not (absolute_path + os.path.sep).startswith(root): |
| 2991 | raise HTTPError(403, "%s is not in root static directory", self.path) |
| 2992 | if os.path.isdir(absolute_path) and self.default_filename is not None: |
| 2993 | # need to look at the request.path here for when path is empty |
| 2994 | # but there is some prefix to the path that was already |
| 2995 | # trimmed by the routing |
| 2996 | if not self.request.path.endswith("/"): |
| 2997 | if self.request.path.startswith("//"): |
| 2998 | # A redirect with two initial slashes is a "protocol-relative" URL. |
| 2999 | # This means the next path segment is treated as a hostname instead |
| 3000 | # of a part of the path, making this effectively an open redirect. |
| 3001 | # Reject paths starting with two slashes to prevent this. |
| 3002 | # This is only reachable under certain configurations. |
| 3003 | raise HTTPError( |
| 3004 | 403, "cannot redirect path with two initial slashes" |
| 3005 | ) |
| 3006 | self.redirect(self.request.path + "/", permanent=True) |
| 3007 | return None |
| 3008 | absolute_path = os.path.join(absolute_path, self.default_filename) |
| 3009 | if not os.path.exists(absolute_path): |
| 3010 | raise HTTPError(404) |
| 3011 | if not os.path.isfile(absolute_path): |
| 3012 | raise HTTPError(403, "%s is not a file", self.path) |
| 3013 | return absolute_path |