-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
bpo-34866: Adding max_num_fields to cgi.FieldStorage #9660
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
Changes from all commits
d846e2b
21e5e48
27352e9
ab0eb93
7668322
60aee01
1fa59e4
20a77f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -311,7 +311,8 @@ class FieldStorage: | |
""" | ||
def __init__(self, fp=None, headers=None, outerboundary=b'', | ||
environ=os.environ, keep_blank_values=0, strict_parsing=0, | ||
limit=None, encoding='utf-8', errors='replace'): | ||
limit=None, encoding='utf-8', errors='replace', | ||
max_num_fields=None): | ||
"""Constructor. Read multipart/* until last part. | ||
|
||
Arguments, all optional: | ||
|
@@ -351,10 +352,14 @@ def __init__(self, fp=None, headers=None, outerboundary=b'', | |
for the page sending the form (content-type : meta http-equiv or | ||
header) | ||
|
||
max_num_fields: int. If set, then __init__ throws a ValueError | ||
if there are more than n fields read by parse_qsl(). | ||
|
||
""" | ||
method = 'GET' | ||
self.keep_blank_values = keep_blank_values | ||
self.strict_parsing = strict_parsing | ||
self.max_num_fields = max_num_fields | ||
if 'REQUEST_METHOD' in environ: | ||
method = environ['REQUEST_METHOD'].upper() | ||
self.qs_on_post = None | ||
|
@@ -578,12 +583,11 @@ def read_urlencoded(self): | |
qs = qs.decode(self.encoding, self.errors) | ||
if self.qs_on_post: | ||
qs += '&' + self.qs_on_post | ||
self.list = [] | ||
query = urllib.parse.parse_qsl( | ||
qs, self.keep_blank_values, self.strict_parsing, | ||
encoding=self.encoding, errors=self.errors) | ||
for key, value in query: | ||
self.list.append(MiniFieldStorage(key, value)) | ||
encoding=self.encoding, errors=self.errors, | ||
max_num_fields=self.max_num_fields) | ||
self.list = [MiniFieldStorage(key, value) for key, value in query] | ||
self.skip_lines() | ||
|
||
FieldStorageClass = None | ||
|
@@ -597,9 +601,9 @@ def read_multi(self, environ, keep_blank_values, strict_parsing): | |
if self.qs_on_post: | ||
query = urllib.parse.parse_qsl( | ||
self.qs_on_post, self.keep_blank_values, self.strict_parsing, | ||
encoding=self.encoding, errors=self.errors) | ||
for key, value in query: | ||
self.list.append(MiniFieldStorage(key, value)) | ||
encoding=self.encoding, errors=self.errors, | ||
max_num_fields=self.max_num_fields) | ||
self.list.extend(MiniFieldStorage(key, value) for key, value in query) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not an essential change, but the extend with generator seemed faster and cleaner than the |
||
|
||
klass = self.FieldStorageClass or self.__class__ | ||
first_line = self.fp.readline() # bytes | ||
|
@@ -633,11 +637,23 @@ def read_multi(self, environ, keep_blank_values, strict_parsing): | |
if 'content-length' in headers: | ||
del headers['content-length'] | ||
|
||
# Propagate max_num_fields into the sub class appropriately | ||
sub_max_num_fields = self.max_num_fields | ||
if sub_max_num_fields is not None: | ||
sub_max_num_fields -= len(self.list) | ||
|
||
part = klass(self.fp, headers, ib, environ, keep_blank_values, | ||
strict_parsing,self.limit-self.bytes_read, | ||
self.encoding, self.errors) | ||
self.encoding, self.errors, sub_max_num_fields) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tseaver I'm assuming here that if a user declares a custom |
||
|
||
max_num_fields = self.max_num_fields | ||
if max_num_fields is not None and part.list: | ||
max_num_fields -= len(part.list) | ||
|
||
self.bytes_read += part.bytes_read | ||
self.list.append(part) | ||
if max_num_fields is not None and max_num_fields < len(self.list): | ||
raise ValueError('Max number of fields exceeded') | ||
if part.done or self.bytes_read >= self.length > 0: | ||
break | ||
self.skip_lines() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Adding ``max_num_fields`` to ``cgi.FieldStorage`` to make DOS attacks harder by | ||
limiting the number of ``MiniFieldStorage`` objects created by ``FieldStorage``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not an essential change, but the list comprehension seemed faster and cleaner than the
append()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice improvement. Good catch!