Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #8622: accessing POST after a POST handling exception no longer…

… throws the server into an infinite loop. Thanks to vung for tracking this one down and fixing it.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@8748 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 15644cb255f673c07d9bafc3595edaaa4cdc3a90 1 parent 7c65a31
Jacob Kaplan-Moss authored August 30, 2008
20  django/core/handlers/modpython.py
@@ -35,6 +35,7 @@ def __init__(self, req):
35 35
             # a common start character for URL patterns. So this is a little
36 36
             # naughty, but also pretty harmless.
37 37
             self.path_info = u'/'
  38
+        self._post_parse_error = False
38 39
 
39 40
     def __repr__(self):
40 41
         # Since this is called as part of error handling, we need to be very
@@ -43,10 +44,13 @@ def __repr__(self):
43 44
             get = pformat(self.GET)
44 45
         except:
45 46
             get = '<could not parse>'
46  
-        try:
47  
-            post = pformat(self.POST)
48  
-        except:
  47
+        if self._post_parse_error:
49 48
             post = '<could not parse>'
  49
+        else:
  50
+            try:
  51
+                post = pformat(self.POST)
  52
+            except:
  53
+                post = '<could not parse>'
50 54
         try:
51 55
             cookies = pformat(self.COOKIES)
52 56
         except:
@@ -73,7 +77,15 @@ def _load_post_and_files(self):
73 77
         "Populates self._post and self._files"
74 78
         if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
75 79
             self._raw_post_data = ''
76  
-            self._post, self._files = self.parse_file_upload(self.META, self._req)
  80
+            try:
  81
+                self._post, self._files = self.parse_file_upload(self.META, self._req)
  82
+            except:
  83
+                # See django.core.handlers.wsgi.WSGIHandler for an explanation
  84
+                # of what's going on here.
  85
+                self._post = http.QueryDict('')
  86
+                self._files = datastructures.MultiValueDict()
  87
+                self._post_parse_error = True
  88
+                raise
77 89
         else:
78 90
             self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
79 91
 
25  django/core/handlers/wsgi.py
@@ -92,6 +92,7 @@ def __init__(self, environ):
92 92
         self.META['PATH_INFO'] = path_info
93 93
         self.META['SCRIPT_NAME'] = script_name
94 94
         self.method = environ['REQUEST_METHOD'].upper()
  95
+        self._post_parse_error = False
95 96
 
96 97
     def __repr__(self):
97 98
         # Since this is called as part of error handling, we need to be very
@@ -100,10 +101,13 @@ def __repr__(self):
100 101
             get = pformat(self.GET)
101 102
         except:
102 103
             get = '<could not parse>'
103  
-        try:
104  
-            post = pformat(self.POST)
105  
-        except:
  104
+        if self._post_parse_error:
106 105
             post = '<could not parse>'
  106
+        else:
  107
+            try:
  108
+                post = pformat(self.POST)
  109
+            except:
  110
+                post = '<could not parse>'
107 111
         try:
108 112
             cookies = pformat(self.COOKIES)
109 113
         except:
@@ -127,7 +131,20 @@ def _load_post_and_files(self):
127 131
         if self.method == 'POST':
128 132
             if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
129 133
                 self._raw_post_data = ''
130  
-                self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
  134
+                try:
  135
+                    self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
  136
+                except:
  137
+                    # An error occured while parsing POST data.  Since when
  138
+                    # formatting the error the request handler might access
  139
+                    # self.POST, set self._post and self._file to prevent
  140
+                    # attempts to parse POST data again.
  141
+                    self._post = http.QueryDict('')
  142
+                    self._files = datastructures.MultiValueDict()
  143
+                    # Mark that an error occured.  This allows self.__repr__ to
  144
+                    # be explicit about it instead of simply representing an
  145
+                    # empty POST
  146
+                    self._post_parse_error = True
  147
+                    raise
131 148
             else:
132 149
                 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
133 150
         else:
42  tests/regressiontests/file_uploads/tests.py
@@ -10,6 +10,7 @@
10 10
 from django.utils.hashcompat import sha_constructor
11 11
 
12 12
 from models import FileModel, temp_storage, UPLOAD_TO
  13
+import uploadhandler
13 14
 
14 15
 class FileUploadTests(TestCase):
15 16
     def test_simple_upload(self):
@@ -187,6 +188,47 @@ def test_fileupload_getlist(self):
187 188
         self.assertEqual(got.get('file1'), 1)
188 189
         self.assertEqual(got.get('file2'), 2)
189 190
 
  191
+    def test_file_error_blocking(self):
  192
+        """
  193
+        The server should not block when there are upload errors (bug #8622).
  194
+        This can happen if something -- i.e. an exception handler -- tries to
  195
+        access POST while handling an error in parsing POST. This shouldn't
  196
+        cause an infinite loop!
  197
+        """
  198
+        class POSTAccessingHandler(client.ClientHandler):
  199
+            """A handler that'll access POST during an exception."""
  200
+            def handle_uncaught_exception(self, request, resolver, exc_info):
  201
+                ret = super(POSTAccessingHandler, self).handle_uncaught_exception(request, resolver, exc_info)
  202
+                p = request.POST
  203
+                return ret
  204
+        
  205
+        post_data = {
  206
+            'name': 'Ringo',
  207
+            'file_field': open(__file__),
  208
+        }
  209
+        # Maybe this is a little more complicated that it needs to be; but if
  210
+        # the django.test.client.FakePayload.read() implementation changes then
  211
+        # this test would fail.  So we need to know exactly what kind of error
  212
+        # it raises when there is an attempt to read more than the available bytes:
  213
+        try:
  214
+            client.FakePayload('a').read(2)
  215
+        except Exception, reference_error:
  216
+            pass
  217
+
  218
+        # install the custom handler that tries to access request.POST
  219
+        self.client.handler = POSTAccessingHandler()
  220
+
  221
+        try:
  222
+            response = self.client.post('/file_uploads/upload_errors/', post_data)
  223
+        except reference_error.__class__, err:
  224
+            self.failIf(
  225
+                str(err) == str(reference_error), 
  226
+                "Caught a repeated exception that'll cause an infinite loop in file uploads."
  227
+            )
  228
+        except Exception, err:
  229
+            # CustomUploadError is the error that should have been raised
  230
+            self.assertEqual(err.__class__, uploadhandler.CustomUploadError)
  231
+
190 232
 class DirectoryCreationTests(unittest.TestCase):
191 233
     """
192 234
     Tests for error handling during directory creation
10  tests/regressiontests/file_uploads/uploadhandler.py
@@ -23,4 +23,12 @@ def receive_data_chunk(self, raw_data, start):
23 23
         return raw_data
24 24
             
25 25
     def file_complete(self, file_size):
26  
-        return None
  26
+        return None
  27
+
  28
+class CustomUploadError(Exception):
  29
+    pass
  30
+
  31
+class ErroringUploadHandler(FileUploadHandler):
  32
+    """A handler that raises an exception."""
  33
+    def receive_data_chunk(self, raw_data, start):
  34
+        raise CustomUploadError("Oops!")
1  tests/regressiontests/file_uploads/urls.py
@@ -8,4 +8,5 @@
8 8
     (r'^quota/$',           views.file_upload_quota),
9 9
     (r'^quota/broken/$',    views.file_upload_quota_broken),
10 10
     (r'^getlist_count/$',   views.file_upload_getlist_count),
  11
+    (r'^upload_errors/$',   views.file_upload_errors),
11 12
 )
6  tests/regressiontests/file_uploads/views.py
@@ -3,7 +3,7 @@
3 3
 from django.http import HttpResponse, HttpResponseServerError
4 4
 from django.utils import simplejson
5 5
 from models import FileModel
6  
-from uploadhandler import QuotaUploadHandler
  6
+from uploadhandler import QuotaUploadHandler, ErroringUploadHandler
7 7
 from django.utils.hashcompat import sha_constructor
8 8
 
9 9
 def file_upload_view(request):
@@ -84,3 +84,7 @@ def file_upload_getlist_count(request):
84 84
     for key in request.FILES.keys():
85 85
         file_counts[key] = len(request.FILES.getlist(key))
86 86
     return HttpResponse(simplejson.dumps(file_counts))
  87
+
  88
+def file_upload_errors(request):
  89
+    request.upload_handlers.insert(0, ErroringUploadHandler())
  90
+    return file_upload_echo(request)

0 notes on commit 15644cb

Please sign in to comment.
Something went wrong with that request. Please try again.