From 14a0cfc09c80f64cf64ba34ff7556cde0ff7e9d7 Mon Sep 17 00:00:00 2001 From: Michael Gisi Date: Wed, 28 Nov 2018 23:05:49 -0500 Subject: [PATCH] Add server_tokens configuration option Fixes #825 --- docs/source/settings.rst | 17 +++++++++++++++++ gunicorn/config.py | 31 +++++++++++++++++++++++++++++- gunicorn/http/wsgi.py | 4 ++-- tests/test_config.py | 13 ++++++++++++- tests/test_http.py | 41 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 101 insertions(+), 5 deletions(-) diff --git a/docs/source/settings.rst b/docs/source/settings.rst index bdc62f1e7b..3579a1fadd 100644 --- a/docs/source/settings.rst +++ b/docs/source/settings.rst @@ -1095,6 +1095,23 @@ The variables are passed to the the PasteDeploy entrypoint. Example:: .. versionadded:: 19.7 +.. _server-tokens: + +server_tokens +~~~~~~~~~~~~~ + +* ``--server-tokens`` +* ``true`` + +Enable or disable emitting gunicorn version in the ``Server`` http response header. + +This setting may also be used to emit a custom ``Server`` header value +or disable the header entirely. + +A value of ``true`` will result in the default behavior. ``false`` will disable +emittance of gunicorn version. A string value will emit itself as a custom ``Server`` +header value, and an empty string will disable the ``Server`` response header. + Server Socket ------------- diff --git a/gunicorn/config.py b/gunicorn/config.py index a9b18afe85..401ff0ceda 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -17,7 +17,7 @@ import sys import textwrap -from gunicorn import __version__, util +from gunicorn import __version__, util, SERVER_SOFTWARE from gunicorn.errors import ConfigError from gunicorn.reloader import reloader_engines @@ -221,6 +221,17 @@ def paste_global_conf(self): return global_conf + @property + def server_tokens(self): + s = self.settings['server_tokens'].get() + if s == 'true': + return SERVER_SOFTWARE + + if s == 'false': + return 'gunicorn' + + return s + class SettingMeta(type): def __new__(cls, name, bases, attrs): @@ -1988,3 +1999,21 @@ class PasteGlobalConf(Setting): .. versionadded:: 19.7 """ + +class ServerTokens(Setting): + name = "server_tokens" + section = "Server Mechanics" + cli = ["--server-tokens"] + validator = validate_string + default = 'true' + desc = """\ + Enable or disable emitting gunicorn version in the ``Server`` http response header. + + This setting may also be used to emit a custom ``Server`` header value + or disable the header entirely. + + A value of ``true`` will result in the default behavior. ``false`` will disable + emittance of gunicorn version. A string value will emit itself as a custom ``Server`` + header value, and an empty string will disable the ``Server`` response header. + """ + diff --git a/gunicorn/http/wsgi.py b/gunicorn/http/wsgi.py index 593c8f24de..6ed203605b 100644 --- a/gunicorn/http/wsgi.py +++ b/gunicorn/http/wsgi.py @@ -194,7 +194,6 @@ class Response(object): def __init__(self, req, sock, cfg): self.req = req self.sock = sock - self.version = SERVER_SOFTWARE self.status = None self.chunked = False self.must_close = False @@ -300,10 +299,11 @@ def default_headers(self): headers = [ "HTTP/%s.%s %s\r\n" % (self.req.version[0], self.req.version[1], self.status), - "Server: %s\r\n" % self.version, "Date: %s\r\n" % util.http_date(), "Connection: %s\r\n" % connection ] + if self.cfg and self.cfg.server_tokens: + headers.append("Server: %s\r\n" % self.cfg.server_tokens) if self.chunked: headers.append("Transfer-Encoding: chunked\r\n") return headers diff --git a/tests/test_config.py b/tests/test_config.py index 98420bd0c2..218b623b87 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -8,7 +8,7 @@ import pytest -from gunicorn import config +from gunicorn import config, SERVER_SOFTWARE from gunicorn.app.base import Application from gunicorn.errors import ConfigError from gunicorn.workers.sync import SyncWorker @@ -435,3 +435,14 @@ def test_bind_fd(): with AltArgs(["prog_name", "-b", "fd://42"]): app = NoConfigApp() assert app.cfg.bind == ["fd://42"] + + +def test_server_tokens(): + c = config.Config() + assert c.server_tokens == SERVER_SOFTWARE + c.set('server_tokens', 'false') + assert c.server_tokens == 'gunicorn' + c.set('server_tokens', 'server software') + assert c.server_tokens == 'server software' + c.set('server_tokens', '') + assert c.server_tokens == '' diff --git a/tests/test_http.py b/tests/test_http.py index a91f4794c8..4aae8ba6c0 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -4,7 +4,7 @@ import t import pytest -from gunicorn import util +from gunicorn import util, config, SERVER_SOFTWARE from gunicorn.http.body import Body, LengthReader, EOFReader from gunicorn.http.wsgi import Response from gunicorn.http.unreader import Unreader, IterUnreader, SocketUnreader @@ -226,3 +226,42 @@ def test_eof_reader_read_invalid_size(): reader.read([100]) with pytest.raises(ValueError): reader.read(-100) + + +def test_http_server_header(): + """ tests whether the http server header is set correctly """ + + def get_server_header(headers): + for header in headers: + if header[:8] == 'Server: ': + return header[8:].strip() + return None + + mocked_socket = mock.MagicMock() + mocked_socket.sendall = mock.MagicMock() + + mocked_request = mock.MagicMock() + c = config.Config() + + # default server header + response = Response(mocked_request, mocked_socket, c) + headers = response.default_headers() + assert get_server_header(headers) == SERVER_SOFTWARE + + # server header w/o version number + c.set('server_tokens', 'false') + response = Response(mocked_request, mocked_socket, c) + headers = response.default_headers() + assert get_server_header(headers) == 'gunicorn' + + # custom server header + c.set('server_tokens', 'server software') + response = Response(mocked_request, mocked_socket, c) + headers = response.default_headers() + assert get_server_header(headers) == 'server software' + + # disabled server header + c.set('server_tokens', '') + response = Response(mocked_request, mocked_socket, c) + headers = response.default_headers() + assert get_server_header(headers) is None