Skip to content
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
24 changes: 23 additions & 1 deletion formtools/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import pickle

from django.core.files.uploadedfile import TemporaryUploadedFile
from django.db.models import QuerySet
from django.utils.crypto import salted_hmac


def sanitise(obj):
if isinstance(obj, list):
return [sanitise(o) for o in obj]
elif isinstance(obj, tuple):
return tuple([sanitise(o) for o in obj])
elif isinstance(obj, QuerySet):
return [sanitise(o) for o in list(obj)]
try:
od = obj.__dict__
nd = {'_class': obj.__class__}
for key, val in od.items():
if not key.startswith('_'):
# ignore Django internal attributes
nd[key] = sanitise(val)
return nd
except Exception:
pass
return obj


def form_hmac(form):
"""
Calculates a security hash for the given Form instance.
Expand All @@ -22,6 +43,7 @@ def form_hmac(form):
value = value.read()
data.append((bf.name, value))

pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
sanitised_data = sanitise(data)
pickled = pickle.dumps(sanitised_data, pickle.HIGHEST_PROTOCOL)
key_salt = 'django.contrib.formtools'
return salted_hmac(key_salt, pickled).hexdigest()
28 changes: 28 additions & 0 deletions tests/forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
from django import forms
from django.db import models


class ManyModel(models.Model):
name = models.CharField(max_length=100)

class Meta:
app_label = 'formtools'

def __str__(self):
return self.name


class OtherModel(models.Model):
name = models.CharField(max_length=100)
manymodels = models.ManyToManyField(ManyModel)

class Meta:
app_label = 'formtools'

def __str__(self):
return self.name + " with " + ", ".join(map(str, self.manymodels.all()))


class OtherModelForm(forms.ModelForm):
class Meta:
model = OtherModel
fields = ["name", "manymodels"]


class TestForm(forms.Form):
Expand Down
28 changes: 27 additions & 1 deletion tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from formtools import preview, utils

from .forms import (
HashTestBlankForm, HashTestForm, HashTestFormWithFile, TestForm,
HashTestBlankForm, HashTestForm, HashTestFormWithFile, ManyModel,
OtherModelForm, TestForm,
)

success_string = "Done was called!"
Expand Down Expand Up @@ -214,3 +215,28 @@ def test_hash_with_file(self):
hash1 = utils.form_hmac(f1)
hash2 = utils.form_hmac(f2)
self.assertNotEqual(hash1, hash2)


class PicklingTests(unittest.TestCase):

def setUp(self):
super().setUp()
ManyModel.objects.create(name="jane")

def test_queryset_hash(self):
"""
Regression test for #10034: the hash generation function should ignore
leading/trailing whitespace so as to be friendly to broken browsers that
submit it (usually in textareas).
"""

qs1 = ManyModel.objects.all()
qs2 = ManyModel.objects.all()

qs1._prefetch_done = True
qs2._prefetch_done = False
f1 = OtherModelForm({'name': 'joe', 'manymodels': qs1})
f2 = OtherModelForm({'name': 'joe', 'manymodels': qs2})
hash1 = utils.form_hmac(f1)
hash2 = utils.form_hmac(f2)
self.assertEqual(hash1, hash2)