Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unhandled exception via invalid configuration #3197

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions gunicorn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def __str__(self):

def __getattr__(self, name):
if name not in self.settings:
raise AttributeError("No configuration setting for: %s" % name)
raise AttributeError("No configuration setting for: %s" % (name, ))
return self.settings[name].get()

def __setattr__(self, name, value):
Expand All @@ -73,7 +73,7 @@ def __setattr__(self, name, value):

def set(self, name, value):
if name not in self.settings:
raise AttributeError("No configuration setting for: %s" % name)
raise AttributeError("No configuration setting for: %s" % (name, ))
self.settings[name].set(value)

def get_cmd_args_from_env(self):
Expand Down Expand Up @@ -193,7 +193,7 @@ def env(self):
try:
k, v = s.split('=', 1)
except ValueError:
raise RuntimeError("environment setting %r invalid" % s)
raise RuntimeError("environment setting %r invalid" % (s, ))

env[k] = v

Expand Down Expand Up @@ -226,7 +226,7 @@ def paste_global_conf(self):
try:
k, v = re.split(r'(?<!\\)=', s, 1)
except ValueError:
raise RuntimeError("environment setting %r invalid" % s)
raise RuntimeError("environment setting %r invalid" % (s, ))
k = k.replace('\\=', '=')
v = v.replace('\\=', '=')
global_conf[k] = v
Expand Down Expand Up @@ -312,7 +312,7 @@ def get(self):

def set(self, val):
if not callable(self.validator):
raise TypeError('Invalid validator: %s' % self.name)
raise TypeError('Invalid validator: %s' % (self.name, ))
self.value = self.validator(val)

def __lt__(self, other):
Expand Down Expand Up @@ -350,7 +350,7 @@ def validate_bool(val):

def validate_dict(val):
if not isinstance(val, dict):
raise TypeError("Value is not a dictionary: %s " % val)
raise TypeError("Value is not a dictionary: %s " % (val, ))
return val


Expand All @@ -361,7 +361,7 @@ def validate_pos_int(val):
# Booleans are ints!
val = int(val)
if val < 0:
raise ValueError("Value must be positive: %s" % val)
raise ValueError("Value must be positive: %s" % (val, ))
return val


Expand All @@ -375,15 +375,15 @@ def validate_string(val):
if val is None:
return None
if not isinstance(val, str):
raise TypeError("Not a string: %s" % val)
raise TypeError("Not a string: %s" % (val, ))
return val.strip()


def validate_file_exists(val):
if val is None:
return None
if not os.path.exists(val):
raise ValueError("File %s does not exists." % val)
raise ValueError("File %s does not exists." % (val, ))
return val


Expand Down Expand Up @@ -426,7 +426,7 @@ def _validate_callable(val):
mod_name, obj_name = val.rsplit(".", 1)
except ValueError:
raise TypeError("Value '%s' is not import string. "
"Format: module[.submodules...].object" % val)
"Format: module[.submodules...].object" % (val, ))
try:
mod = __import__(mod_name, fromlist=[obj_name])
val = getattr(mod, obj_name)
Expand All @@ -436,9 +436,9 @@ def _validate_callable(val):
raise TypeError("Can not load '%s' from '%s'"
"" % (obj_name, mod_name))
if not callable(val):
raise TypeError("Value is not callable: %s" % val)
raise TypeError("Value is not callable: %s" % (val, ))
if arity != -1 and arity != util.get_arity(val):
raise TypeError("Value must have an arity of: %s" % arity)
raise TypeError("Value must have an arity of: %s" % (arity, ))
return val
return _validate_callable

Expand All @@ -454,7 +454,7 @@ def validate_user(val):
try:
return pwd.getpwnam(val).pw_uid
except KeyError:
raise ConfigError("No such user: '%s'" % val)
raise ConfigError("No such user: '%s'" % (val, ))


def validate_group(val):
Expand All @@ -469,7 +469,7 @@ def validate_group(val):
try:
return grp.getgrnam(val).gr_gid
except KeyError:
raise ConfigError("No such group: '%s'" % val)
raise ConfigError("No such group: '%s'" % (val, ))


def validate_post_request(val):
Expand All @@ -495,7 +495,7 @@ def validate_chdir(val):

# test if the path exists
if not os.path.exists(path):
raise ConfigError("can't chdir to %r" % val)
raise ConfigError("can't chdir to %r" % (val, ))

return path

Expand All @@ -522,8 +522,10 @@ def validate_statsd_address(val):


def validate_reload_engine(val):
val = validate_string(val)

if val not in reloader_engines:
raise ConfigError("Invalid reload_engine: %r" % val)
raise ConfigError("Invalid reload_engine: %r" % (val, ))

return val

Expand Down Expand Up @@ -2336,15 +2338,15 @@ def validate_header_map_behaviour(val):
return

if not isinstance(val, str):
raise TypeError("Invalid type for casting: %s" % val)
raise TypeError("Invalid type for casting: %s" % (val, ))
if val.lower().strip() == "drop":
return "drop"
elif val.lower().strip() == "refuse":
return "refuse"
elif val.lower().strip() == "dangerous":
return "dangerous"
else:
raise ValueError("Invalid header map behaviour: %s" % val)
raise ValueError("Invalid header map behaviour: %s" % (val, ))


class HeaderMap(Setting):
Expand Down
24 changes: 12 additions & 12 deletions gunicorn/http/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, buf=None):
self.buf = buf

def __str__(self):
return "No more data after: %r" % self.buf
return "No more data after: %r" % (self.buf, )


class ConfigurationProblem(ParseException):
Expand All @@ -28,7 +28,7 @@ def __init__(self, info):
self.code = 500

def __str__(self):
return "Configuration problem: %s" % self.info
return "Configuration problem: %s" % (self.info, )


class InvalidRequestLine(ParseException):
Expand All @@ -37,23 +37,23 @@ def __init__(self, req):
self.code = 400

def __str__(self):
return "Invalid HTTP request line: %r" % self.req
return "Invalid HTTP request line: %r" % (self.req, )


class InvalidRequestMethod(ParseException):
def __init__(self, method):
self.method = method

def __str__(self):
return "Invalid HTTP method: %r" % self.method
return "Invalid HTTP method: %r" % (self.method, )


class InvalidHTTPVersion(ParseException):
def __init__(self, version):
self.version = version

def __str__(self):
return "Invalid HTTP Version: %r" % (self.version,)
return "Invalid HTTP Version: %r" % (self.version, )


class InvalidHeader(ParseException):
Expand All @@ -62,15 +62,15 @@ def __init__(self, hdr, req=None):
self.req = req

def __str__(self):
return "Invalid HTTP Header: %r" % self.hdr
return "Invalid HTTP Header: %r" % (self.hdr, )


class InvalidHeaderName(ParseException):
def __init__(self, hdr):
self.hdr = hdr

def __str__(self):
return "Invalid HTTP header name: %r" % self.hdr
return "Invalid HTTP header name: %r" % (self.hdr, )


class UnsupportedTransferCoding(ParseException):
Expand All @@ -79,23 +79,23 @@ def __init__(self, hdr):
self.code = 501

def __str__(self):
return "Unsupported transfer coding: %r" % self.hdr
return "Unsupported transfer coding: %r" % (self.hdr, )


class InvalidChunkSize(IOError):
def __init__(self, data):
self.data = data

def __str__(self):
return "Invalid chunk size: %r" % self.data
return "Invalid chunk size: %r" % (self.data, )


class ChunkMissingTerminator(IOError):
def __init__(self, term):
self.term = term

def __str__(self):
return "Invalid chunk terminator is not '\\r\\n': %r" % self.term
return "Invalid chunk terminator is not '\\r\\n': %r" % (self.term, )


class LimitRequestLine(ParseException):
Expand All @@ -121,7 +121,7 @@ def __init__(self, line):
self.code = 400

def __str__(self):
return "Invalid PROXY line: %r" % self.line
return "Invalid PROXY line: %r" % (self.line, )


class ForbiddenProxyRequest(ParseException):
Expand All @@ -130,7 +130,7 @@ def __init__(self, host):
self.code = 403

def __str__(self):
return "Proxy request from %r not allowed" % self.host
return "Proxy request from %r not allowed" % (self.host, )


class InvalidSchemeHeaders(ParseException):
Expand Down
2 changes: 1 addition & 1 deletion gunicorn/http/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ def parse_request_line(self, line_bytes):
if not (1, 0) <= self.version < (2, 0):
# if ever relaxing this, carefully review Content-Encoding processing
if not self.cfg.permit_unconventional_http_version:
raise InvalidHTTPVersion(self.version)
raise InvalidHTTPVersion(bits[2])

def set_body_reader(self):
super().set_body_reader()
Expand Down
2 changes: 1 addition & 1 deletion gunicorn/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ def to_bytestring(value, encoding="utf8"):
if isinstance(value, bytes):
return value
if not isinstance(value, str):
raise TypeError('%r is not a string' % value)
raise TypeError('%r is not a string' % (value, ))

return value.encode(encoding)

Expand Down
2 changes: 2 additions & 0 deletions tests/requests/invalid/version_03.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GET /foo HTTP/0.9\r\n
\r\n
2 changes: 2 additions & 0 deletions tests/requests/invalid/version_03.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from gunicorn.http.errors import InvalidHTTPVersion
request = InvalidHTTPVersion
21 changes: 21 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ def test_bool_validation():
assert c.preload_app is False
pytest.raises(ValueError, c.set, "preload_app", "zilch")
pytest.raises(TypeError, c.set, "preload_app", 4)
pytest.raises(TypeError, c.set, "preload_app", [])
pytest.raises(TypeError, c.set, "preload_app", lambda x: True)
pytest.raises(TypeError, c.set, "preload_app", tuple())


def test_pos_int_validation():
Expand Down Expand Up @@ -176,6 +179,17 @@ def test_str_to_list_validation():
pytest.raises(TypeError, c.set, "forwarded_allow_ips", 1)


def test_dict_validation():
c = config.Config()
c.set("secure_scheme_headers", {"X-FORWARDED_PROTO": "ssl"})
assert c.secure_scheme_headers
pytest.raises(TypeError, c.set, "secure_scheme_headers", set())
pytest.raises(TypeError, c.set, "secure_scheme_headers", lambda x: True)
pytest.raises(TypeError, c.set, "secure_scheme_headers", [])
pytest.raises(TypeError, c.set, "secure_scheme_headers", tuple())
pytest.raises(TypeError, c.set, "secure_scheme_headers", (1,2,3))


def test_callable_validation():
c = config.Config()
def func(a, b):
Expand All @@ -184,6 +198,9 @@ def func(a, b):
assert c.pre_fork == func
pytest.raises(TypeError, c.set, "pre_fork", 1)
pytest.raises(TypeError, c.set, "pre_fork", lambda x: True)
pytest.raises(TypeError, c.set, "pre_fork", [])
pytest.raises(TypeError, c.set, "pre_fork", tuple())
pytest.raises(TypeError, c.set, "pre_fork", (1,2,3))


def test_reload_engine_validation():
Expand All @@ -195,6 +212,10 @@ def test_reload_engine_validation():
assert c.reload_engine == 'poll'

pytest.raises(ConfigError, c.set, "reload_engine", "invalid")
pytest.raises(TypeError, c.set, "reload_engine", tuple())
pytest.raises(TypeError, c.set, "reload_engine", (1,2,3))
pytest.raises(TypeError, c.set, "reload_engine", [])
pytest.raises(TypeError, c.set, "reload_engine", 0)


def test_callable_validation_for_string():
Expand Down
3 changes: 2 additions & 1 deletion tests/test_invalid_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ def test_http_parser(fname):
cfg = env["cfg"]
req = treq.badrequest(fname)

with pytest.raises(expect):
# telling pytest to match the exception string validates its __str__()
with pytest.raises(expect, match="."):
req.check(cfg)
Loading