summaryrefslogtreecommitdiff
path: root/django/forms
diff options
context:
space:
mode:
Diffstat (limited to 'django/forms')
-rw-r--r--django/forms/formsets.py30
-rw-r--r--django/forms/models.py8
2 files changed, 25 insertions, 13 deletions
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 81b75f2796..98ae3205fe 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -33,6 +33,9 @@ class ManagementForm(Form):
def __init__(self, *args, **kwargs):
self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
+ # MAX_NUM_FORM_COUNT is output with the rest of the management form,
+ # but only for the convenience of client-side code. The POST
+ # value of MAX_NUM_FORM_COUNT returned from the client is not checked.
self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs)
@@ -94,7 +97,11 @@ class BaseFormSet(object):
def total_form_count(self):
"""Returns the total number of forms in this FormSet."""
if self.is_bound:
- return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
+ # return absolute_max if it is lower than the actual total form
+ # count in the data; this is DoS protection to prevent clients
+ # from forcing the server to instantiate arbitrary numbers of
+ # forms
+ return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max)
else:
initial_forms = self.initial_form_count()
total_forms = initial_forms + self.extra
@@ -113,14 +120,13 @@ class BaseFormSet(object):
else:
# Use the length of the inital data if it's there, 0 otherwise.
initial_forms = self.initial and len(self.initial) or 0
- if initial_forms > self.max_num >= 0:
- initial_forms = self.max_num
return initial_forms
def _construct_forms(self):
# instantiate all the forms and put them in self.forms
self.forms = []
- for i in xrange(min(self.total_form_count(), self.absolute_max)):
+ # DoS protection is included in total_form_count()
+ for i in xrange(self.total_form_count()):
self.forms.append(self._construct_form(i))
def _construct_form(self, i, **kwargs):
@@ -168,7 +174,6 @@ class BaseFormSet(object):
self.add_fields(form, None)
return form
- # Maybe this should just go away?
@property
def cleaned_data(self):
"""
@@ -294,8 +299,11 @@ class BaseFormSet(object):
for i in range(0, self.total_form_count()):
form = self.forms[i]
self._errors.append(form.errors)
- # Give self.clean() a chance to do cross-form validation.
try:
+ if (self.validate_max and self.total_form_count() > self.max_num) or \
+ self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max:
+ raise ValidationError(_("Please submit %s or fewer forms." % self.max_num))
+ # Give self.clean() a chance to do cross-form validation.
self.clean()
except ValidationError as e:
self._non_form_errors = self.error_class(e.messages)
@@ -367,16 +375,18 @@ class BaseFormSet(object):
return mark_safe('\n'.join([six.text_type(self.management_form), forms]))
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
- can_delete=False, max_num=None):
+ can_delete=False, max_num=None, validate_max=False):
"""Return a FormSet for the given form class."""
if max_num is None:
max_num = DEFAULT_MAX_NUM
# hard limit on forms instantiated, to prevent memory-exhaustion attacks
- # limit defaults to DEFAULT_MAX_NUM, but developer can increase it via max_num
- absolute_max = max(DEFAULT_MAX_NUM, max_num)
+ # limit is simply max_num + DEFAULT_MAX_NUM (which is 2*DEFAULT_MAX_NUM
+ # if max_num is None in the first place)
+ absolute_max = max_num + DEFAULT_MAX_NUM
attrs = {'form': form, 'extra': extra,
'can_order': can_order, 'can_delete': can_delete,
- 'max_num': max_num, 'absolute_max': absolute_max}
+ 'max_num': max_num, 'absolute_max': absolute_max,
+ 'validate_max' : validate_max}
return type(form.__name__ + str('FormSet'), (formset,), attrs)
def all_valid(formsets):
diff --git a/django/forms/models.py b/django/forms/models.py
index 272f1ddee6..0672bafc47 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -682,7 +682,7 @@ class BaseModelFormSet(BaseFormSet):
def modelformset_factory(model, form=ModelForm, formfield_callback=None,
formset=BaseModelFormSet, extra=1, can_delete=False,
can_order=False, max_num=None, fields=None,
- exclude=None, widgets=None):
+ exclude=None, widgets=None, validate_max=False):
"""
Returns a FormSet class for the given Django model class.
"""
@@ -690,7 +690,8 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
formfield_callback=formfield_callback,
widgets=widgets)
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
- can_order=can_order, can_delete=can_delete)
+ can_order=can_order, can_delete=can_delete,
+ validate_max=validate_max)
FormSet.model = model
return FormSet
@@ -826,7 +827,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
formset=BaseInlineFormSet, fk_name=None,
fields=None, exclude=None,
extra=3, can_order=False, can_delete=True, max_num=None,
- formfield_callback=None, widgets=None):
+ formfield_callback=None, widgets=None, validate_max=False):
"""
Returns an ``InlineFormSet`` for the given kwargs.
@@ -848,6 +849,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
'exclude': exclude,
'max_num': max_num,
'widgets': widgets,
+ 'validate_max': validate_max,
}
FormSet = modelformset_factory(model, **kwargs)
FormSet.fk = fk