From 5c2a9ce5dbbba31bd5f3d9f2f1726c0d5e96843f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 26 May 2015 13:55:04 +0200 Subject: [PATCH] Allow downloading all uploaded files, not just gcode and stl Hidden files (starting with .) will produce a 404. This also excludes .metadata.yaml from being downloadable. The alternative of adding all extensions defined by plugins to the regex dynamically was not chosen since that would necessitate to make plugins implementing the "octoprint.filemanager.extension_tree" hook restart needing plugins in the lifecycle management for a minimal gain in perceived security. Solves #897 --- src/octoprint/server/__init__.py | 2 +- src/octoprint/server/util/tornado.py | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 4c2674db..495aca1d 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -766,7 +766,7 @@ class Server(): server_routes = self._router.urls + [ (r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, dict(path=settings().getBaseFolder("timelapse"), as_attachment=True)), - (r"/downloads/files/local/([^/]*\.(gco|gcode|g|stl))", util.tornado.LargeResponseHandler, dict(path=settings().getBaseFolder("uploads"), as_attachment=True)), + (r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, dict(path=settings().getBaseFolder("uploads"), as_attachment=True, path_validation=util.tornado.path_validation_factory(lambda path: not os.path.basename(path).startswith("."), status_code=404))), (r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, dict(path=settings().getBaseFolder("logs"), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.admin_validator))), (r"/downloads/camera/current", util.tornado.UrlForwardHandler, dict(url=settings().get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))), ] diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 0773ad4c..d350916d 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -746,7 +746,8 @@ class CustomHTTP1ConnectionParameters(tornado.http1connection.HTTP1ConnectionPar class LargeResponseHandler(tornado.web.StaticFileHandler): """ Customized `tornado.web.StaticFileHandler `_ - that allows delivery of the requested resource as attachment and access validation through an optional callback. + that allows delivery of the requested resource as attachment and access and request path validation through + optional callbacks. Note that access validation takes place before path validation. Arguments: path (str): The system path from which to serve files (this will be forwarded to the ``initialize`` method of @@ -760,16 +761,23 @@ class LargeResponseHandler(tornado.web.StaticFileHandler): be called with ``self.request`` as parameter which contains the full tornado request object. Should raise a ``tornado.web.HTTPError`` if access is not allowed in which case the request will not be further processed. Defaults to ``None`` and hence no access validation being performed. + path_validation (function): Callback to call in the ``get`` method to validate the requested path. Will be called + with the requested path as parameter. Should raise a ``tornado.web.HTTPError`` (e.g. an 404) if the requested + path does not pass validation in which case the request will not be further processed. + Defaults to ``None`` and hence no path validation being performed. """ - def initialize(self, path, default_filename=None, as_attachment=False, access_validation=None): + def initialize(self, path, default_filename=None, as_attachment=False, access_validation=None, path_validation=None): tornado.web.StaticFileHandler.initialize(self, os.path.abspath(path), default_filename) self._as_attachment = as_attachment self._access_validation = access_validation + self._path_validation = path_validation def get(self, path, include_body=True): if self._access_validation is not None: self._access_validation(self.request) + if self._path_validation is not None: + self._path_validation(path) result = tornado.web.StaticFileHandler.get(self, path, include_body=include_body) return result @@ -887,7 +895,7 @@ def access_validation_factory(app, login_manager, validator): Creates an access validation wrapper using the supplied validator. :param validator: the access validator to use inside the validation wrapper - :return: an access validation wrapper taking a request as parameter and performing the request validation + :return: an access validator taking a request as parameter and performing the request validation """ def f(request): """ @@ -904,3 +912,16 @@ def access_validation_factory(app, login_manager, validator): login_manager.reload_user() validator(flask.request) return f + +def path_validation_factory(path_filter, status_code=404): + """ + Creates a request path validation wrapper returning the defined status code if the supplied path_filter returns False. + + :param path_filter: the path filter to use on the requested path, should return False for requests that should + be responded with the provided error code. + :return: a request path validator taking a request path as parameter and performing the request validation + """ + def f(path): + if not path_filter(path): + raise tornado.web.HTTPError(status_code) + return f \ No newline at end of file