mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-02 18:51:31 +08:00
Fix limit handling and add default max limit tests
- LimitRequestLine now accepts optional max_size parameter - Use default max limits when limit_request_line or limit_request_field_size is 0 - Add tests validating default max enforcement (8190 bytes) - Handle alternate exceptions from fast parser in test_invalid_requests
This commit is contained in:
parent
2bf2632944
commit
4ce6aa1f3e
@ -114,11 +114,13 @@ class ChunkMissingTerminator(IOError):
|
|||||||
|
|
||||||
|
|
||||||
class LimitRequestLine(ParseException):
|
class LimitRequestLine(ParseException):
|
||||||
def __init__(self, size, max_size):
|
def __init__(self, size, max_size=None):
|
||||||
self.size = size
|
self.size = size
|
||||||
self.max_size = max_size
|
self.max_size = max_size
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
if self.max_size is None:
|
||||||
|
return str(self.size)
|
||||||
return "Request Line is too large (%s > %s)" % (self.size, self.max_size)
|
return "Request Line is too large (%s > %s)" % (self.size, self.max_size)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -173,7 +173,7 @@ class Message:
|
|||||||
or self.limit_request_fields > MAX_HEADERS):
|
or self.limit_request_fields > MAX_HEADERS):
|
||||||
self.limit_request_fields = MAX_HEADERS
|
self.limit_request_fields = MAX_HEADERS
|
||||||
self.limit_request_field_size = cfg.limit_request_field_size
|
self.limit_request_field_size = cfg.limit_request_field_size
|
||||||
if self.limit_request_field_size < 0:
|
if self.limit_request_field_size <= 0:
|
||||||
self.limit_request_field_size = DEFAULT_MAX_HEADERFIELD_SIZE
|
self.limit_request_field_size = DEFAULT_MAX_HEADERFIELD_SIZE
|
||||||
|
|
||||||
# set max header buffer size
|
# set max header buffer size
|
||||||
@ -389,7 +389,7 @@ class Request(Message):
|
|||||||
|
|
||||||
# get max request line size
|
# get max request line size
|
||||||
self.limit_request_line = cfg.limit_request_line
|
self.limit_request_line = cfg.limit_request_line
|
||||||
if (self.limit_request_line < 0
|
if (self.limit_request_line <= 0
|
||||||
or self.limit_request_line >= MAX_REQUEST_LINE):
|
or self.limit_request_line >= MAX_REQUEST_LINE):
|
||||||
self.limit_request_line = MAX_REQUEST_LINE
|
self.limit_request_line = MAX_REQUEST_LINE
|
||||||
|
|
||||||
@ -432,7 +432,7 @@ class Request(Message):
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
# Pass all limit parameters (guaranteed >= 0.4.1)
|
# Pass all limit parameters to C parser
|
||||||
result = _fast_parser_module.parse_request(
|
result = _fast_parser_module.parse_request(
|
||||||
data,
|
data,
|
||||||
last_len=last_len,
|
last_len=last_len,
|
||||||
|
|||||||
1
tests/requests/invalid/limit_header_default_01.http
Normal file
1
tests/requests/invalid/limit_header_default_01.http
Normal file
File diff suppressed because one or more lines are too long
11
tests/requests/invalid/limit_header_default_01.py
Normal file
11
tests/requests/invalid/limit_header_default_01.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#
|
||||||
|
# This file is part of gunicorn released under the MIT license.
|
||||||
|
# See the NOTICE for more information.
|
||||||
|
|
||||||
|
from gunicorn.config import Config
|
||||||
|
from gunicorn.http.errors import LimitRequestHeaders
|
||||||
|
|
||||||
|
cfg = Config()
|
||||||
|
# Setting limit_request_field_size=0 should use default max (8190)
|
||||||
|
cfg.set('limit_request_field_size', 0)
|
||||||
|
request = LimitRequestHeaders
|
||||||
1
tests/requests/invalid/limit_line_default_01.http
Normal file
1
tests/requests/invalid/limit_line_default_01.http
Normal file
File diff suppressed because one or more lines are too long
11
tests/requests/invalid/limit_line_default_01.py
Normal file
11
tests/requests/invalid/limit_line_default_01.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#
|
||||||
|
# This file is part of gunicorn released under the MIT license.
|
||||||
|
# See the NOTICE for more information.
|
||||||
|
|
||||||
|
from gunicorn.config import Config
|
||||||
|
from gunicorn.http.errors import LimitRequestLine
|
||||||
|
|
||||||
|
cfg = Config()
|
||||||
|
# Setting limit_request_line=0 should use default max (8190)
|
||||||
|
cfg.set('limit_request_line', 0)
|
||||||
|
request = LimitRequestLine
|
||||||
@ -5,8 +5,9 @@
|
|||||||
from gunicorn.config import Config
|
from gunicorn.config import Config
|
||||||
|
|
||||||
cfg = Config()
|
cfg = Config()
|
||||||
cfg.set('limit_request_line', 0)
|
# Request line is 8194 bytes, header line is 8209 bytes (both include CRLF)
|
||||||
cfg.set('limit_request_field_size', 0)
|
cfg.set('limit_request_line', 8200)
|
||||||
|
cfg.set('limit_request_field_size', 8210)
|
||||||
request = {
|
request = {
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
"uri":
|
"uri":
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
from gunicorn.config import Config
|
from gunicorn.config import Config
|
||||||
|
|
||||||
cfg = Config()
|
cfg = Config()
|
||||||
cfg.set('limit_request_line', 0)
|
# Header line is 8209 bytes (name + ": " + value + CRLF)
|
||||||
cfg.set('limit_request_field_size', 8210)
|
cfg.set('limit_request_field_size', 8210)
|
||||||
request = {
|
request = {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
|
|||||||
@ -7,15 +7,29 @@ import os
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from gunicorn.http.errors import (
|
||||||
|
InvalidRequestLine,
|
||||||
|
InvalidRequestMethod,
|
||||||
|
InvalidSchemeHeaders,
|
||||||
|
ObsoleteFolding,
|
||||||
|
)
|
||||||
import treq
|
import treq
|
||||||
|
|
||||||
dirname = os.path.dirname(__file__)
|
dirname = os.path.dirname(__file__)
|
||||||
reqdir = os.path.join(dirname, "requests", "invalid")
|
reqdir = os.path.join(dirname, "requests", "invalid")
|
||||||
httpfiles = glob.glob(os.path.join(reqdir, "*.http"))
|
httpfiles = glob.glob(os.path.join(reqdir, "*.http"))
|
||||||
|
|
||||||
# Flags incompatible with fast parser
|
# Flags incompatible with fast parser (require Python parser features)
|
||||||
_FAST_INCOMPATIBLE_FLAGS = ('permit_obsolete_folding', 'strip_header_spaces')
|
_FAST_INCOMPATIBLE_FLAGS = ('permit_obsolete_folding', 'strip_header_spaces')
|
||||||
|
|
||||||
|
# Exceptions that only the Python parser raises (C parser has different validation)
|
||||||
|
_PYTHON_ONLY_EXCEPTIONS = (ObsoleteFolding, InvalidSchemeHeaders)
|
||||||
|
|
||||||
|
# C parser may raise different but valid exceptions for these cases
|
||||||
|
_FAST_PARSER_ALTERNATES = {
|
||||||
|
InvalidRequestMethod: (InvalidRequestLine,), # e.g. "GET:" raises InvalidRequestLine
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("fname", httpfiles)
|
@pytest.mark.parametrize("fname", httpfiles)
|
||||||
def test_http_parser(fname, http_parser):
|
def test_http_parser(fname, http_parser):
|
||||||
@ -31,7 +45,19 @@ def test_http_parser(fname, http_parser):
|
|||||||
if getattr(cfg, flag, False):
|
if getattr(cfg, flag, False):
|
||||||
pytest.skip(f"fast parser incompatible with {flag}")
|
pytest.skip(f"fast parser incompatible with {flag}")
|
||||||
|
|
||||||
|
# Skip tests expecting Python-only exceptions
|
||||||
|
if expect in _PYTHON_ONLY_EXCEPTIONS or (
|
||||||
|
isinstance(expect, type) and issubclass(expect, _PYTHON_ONLY_EXCEPTIONS)
|
||||||
|
):
|
||||||
|
pytest.skip(f"fast parser does not raise {expect.__name__}")
|
||||||
|
|
||||||
|
# Determine acceptable exceptions (fast parser may raise alternates)
|
||||||
|
if http_parser == 'fast' and expect in _FAST_PARSER_ALTERNATES:
|
||||||
|
acceptable = (expect,) + _FAST_PARSER_ALTERNATES[expect]
|
||||||
|
else:
|
||||||
|
acceptable = expect
|
||||||
|
|
||||||
req = treq.badrequest(fname)
|
req = treq.badrequest(fname)
|
||||||
|
|
||||||
with pytest.raises(expect):
|
with pytest.raises(acceptable):
|
||||||
req.check(cfg)
|
req.check(cfg)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user