diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py
index 6b3c8cfc..64a4c0b6 100644
--- a/src/octoprint/server/__init__.py
+++ b/src/octoprint/server/__init__.py
@@ -164,7 +164,7 @@ class Server(object):
global safe_mode
from tornado.ioloop import IOLoop
- from tornado.web import Application, RequestHandler
+ from tornado.web import Application
debug = self._debug
safe_mode = self._safe_mode
@@ -437,9 +437,22 @@ class Server(object):
self._logger.debug("Adding additional route {route} handled by handler {handler} and with additional arguments {kwargs!r}".format(**locals()))
server_routes.append((route, handler, kwargs))
- server_routes.append((r".*", util.tornado.UploadStorageFallbackHandler, dict(fallback=util.tornado.WsgiInputContainer(app.wsgi_app), file_prefix="octoprint-file-upload-", file_suffix=".tmp", suffixes=upload_suffixes)))
+ headers = {"X-Robots-Tag": "noindex, nofollow, noimageindex"}
+ removed_headers = ["Server"]
- self._tornado_app = Application(server_routes)
+ server_routes.append((r".*", util.tornado.UploadStorageFallbackHandler, dict(fallback=util.tornado.WsgiInputContainer(app.wsgi_app,
+ headers=headers,
+ removed_headers=removed_headers),
+ file_prefix="octoprint-file-upload-",
+ file_suffix=".tmp",
+ suffixes=upload_suffixes)))
+
+ transforms = [util.tornado.GlobalHeaderTransform.for_headers("OctoPrintGlobalHeaderTransform",
+ headers=headers,
+ removed_headers=removed_headers)]
+
+ self._tornado_app = Application(handlers=server_routes,
+ transforms=transforms)
max_body_sizes = [
("POST", r"/api/files/([^/]*)", self._settings.getInt(["server", "uploads", "maxSize"])),
("POST", r"/api/languages", 5 * 1024 * 1024)
diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py
index 12ba3b9b..6373733e 100644
--- a/src/octoprint/server/util/tornado.py
+++ b/src/octoprint/server/util/tornado.py
@@ -531,9 +531,20 @@ class WsgiInputContainer(object):
methods have been adjusted to allow for an optionally supplied ``body`` argument which is then used for ``wsgi.input``.
"""
- def __init__(self, wsgi_application):
+ def __init__(self, wsgi_application, headers=None, forced_headers=None, removed_headers=None):
self.wsgi_application = wsgi_application
+ if headers is None:
+ headers = dict()
+ if forced_headers is None:
+ forced_headers = dict()
+ if removed_headers is None:
+ removed_headers = []
+
+ self.headers = headers
+ self.forced_headers = forced_headers
+ self.removed_headers = removed_headers
+
def __call__(self, request, body=None):
"""
Wraps the call against the WSGI app, deriving the WSGI environment from the supplied Tornado ``HTTPServerRequest``.
@@ -569,8 +580,14 @@ class WsgiInputContainer(object):
headers.append(("Content-Length", str(len(body))))
if "content-type" not in header_set:
headers.append(("Content-Type", "text/html; charset=UTF-8"))
- if "server" not in header_set:
- headers.append(("Server", "TornadoServer/%s" % tornado.version))
+
+ header_set = set(k.lower() for (k, v) in headers)
+ for header, value in self.headers.items():
+ if header.lower() not in header_set:
+ headers.append((header, value))
+ for header, value in self.forced_headers.items():
+ headers.append((header, value))
+ headers = [(header, value) for header, value in headers if not header.lower() in self.removed_headers]
parts = [tornado.escape.utf8("HTTP/1.1 " + data["status"] + "\r\n")]
for key, value in headers:
@@ -1025,6 +1042,39 @@ class StaticDataHandler(tornado.web.RequestHandler):
self.finish()
+class GlobalHeaderTransform(tornado.web.OutputTransform):
+
+ HEADERS = dict()
+ FORCED_HEADERS = dict()
+ REMOVED_HEADERS = []
+
+ @classmethod
+ def for_headers(cls, name, headers=None, forced_headers=None, removed_headers=None):
+ if headers is None:
+ headers = dict()
+ if forced_headers is None:
+ forced_headers = dict()
+ if removed_headers is None:
+ removed_headers = []
+
+ return type(name, (GlobalHeaderTransform,), dict(HEADERS=headers,
+ FORCED_HEADERS=forced_headers,
+ REMOVED_HEADERS=removed_headers))
+
+ def __init__(self, request):
+ tornado.web.OutputTransform.__init__(self, request)
+
+ def transform_first_chunk(self, status_code, headers, chunk, finishing):
+ for header, value in self.HEADERS.items():
+ if not header in headers:
+ headers[header] = value
+ for header, value in self.FORCED_HEADERS.items():
+ headers[header] = value
+ for header in self.REMOVED_HEADERS:
+ del headers[header]
+ return status_code, headers, chunk
+
+
#~~ Factory method for creating Flask access validation wrappers from the Tornado request context
diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2
index 826804a1..d2107598 100644
--- a/src/octoprint/templates/index.jinja2
+++ b/src/octoprint/templates/index.jinja2
@@ -8,6 +8,8 @@
+
+
{% include 'stylesheets.jinja2' %}
{% include 'initscript.jinja2' %}