Ticket #9886: 9886.5.diff
File 9886.5.diff, 17.3 KB (added by , 14 years ago) |
---|
-
django/core/handlers/modpython.py
=== modified file 'django/core/handlers/modpython.py'
37 37 # naughty, but also pretty harmless. 38 38 self.path_info = u'/' 39 39 self._post_parse_error = False 40 self._stream = self._req 41 self._read_started = False 40 42 41 43 def __repr__(self): 42 44 # Since this is called as part of error handling, we need to be very … … 76 78 # mod_python < 3.2.10 doesn't have req.is_https(). 77 79 return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1') 78 80 79 def _load_post_and_files(self):80 "Populates self._post and self._files"81 if self.method != 'POST':82 self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()83 return84 85 if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):86 self._raw_post_data = ''87 try:88 self._post, self._files = self.parse_file_upload(self.META, self._req)89 except:90 # See django.core.handlers.wsgi.WSGIHandler for an explanation91 # of what's going on here.92 self._post = http.QueryDict('')93 self._files = datastructures.MultiValueDict()94 self._post_parse_error = True95 raise96 else:97 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()98 99 81 def _get_request(self): 100 82 if not hasattr(self, '_request'): 101 83 self._request = datastructures.MergeDict(self.POST, self.GET) … … 157 139 self._meta[key] = value 158 140 return self._meta 159 141 160 def _get_raw_post_data(self):161 try:162 return self._raw_post_data163 except AttributeError:164 self._raw_post_data = self._req.read()165 return self._raw_post_data166 167 142 def _get_method(self): 168 143 return self.META['REQUEST_METHOD'].upper() 169 144 … … 173 148 FILES = property(_get_files) 174 149 META = property(_get_meta) 175 150 REQUEST = property(_get_request) 176 raw_post_data = property(_get_raw_post_data)177 151 method = property(_get_method) 178 152 179 153 class ModPythonHandler(BaseHandler): -
django/core/handlers/wsgi.py
=== modified file 'django/core/handlers/wsgi.py'
4 4 from cStringIO import StringIO 5 5 except ImportError: 6 6 from StringIO import StringIO 7 import socket 7 8 8 9 from django import http 9 10 from django.core import signals … … 57 58 505: 'HTTP VERSION NOT SUPPORTED', 58 59 } 59 60 60 def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0): 61 """ 62 A version of shutil.copyfileobj that will not read more than 'size' bytes. 63 This makes it safe from clients sending more than CONTENT_LENGTH bytes of 64 data in the body. 65 """ 66 if not size: 67 return 68 while size > 0: 69 buf = fsrc.read(min(length, size)) 70 if not buf: 71 break 72 fdst.write(buf) 73 size -= len(buf) 61 class LimitedStream(object): 62 ''' 63 LimitedStream wraps another stream in order to not allow reading from it 64 past specified amount of bytes. 65 ''' 66 def __init__(self, stream, limit, buf_size=64 * 1024 * 1024): 67 self.stream = stream 68 self.remaining = limit 69 self.buffer = '' 70 self.buf_size = buf_size 71 72 def _read_limited(self, size=None): 73 if size is None or size > self.remaining: 74 size = self.remaining 75 if size == 0: 76 return '' 77 result = self.stream.read(size) 78 self.remaining -= len(result) 79 return result 80 81 def read(self, size=None): 82 if size is None: 83 result = self.buffer + self._read_limited() 84 self.buffer = '' 85 elif size < len(self.buffer): 86 result = self.buffer[:size] 87 self.buffer = self.buffer[size:] 88 else: # size >= len(self.buffer) 89 result = self.buffer + self._read_limited(size - len(self.buffer)) 90 self.buffer = '' 91 return result 92 93 def readline(self, size=None): 94 while '\n' not in self.buffer or \ 95 (size is not None and len(self.buffer) < size): 96 chunk = self._read_limited(self.buf_size) 97 if not chunk: 98 break 99 self.buffer += chunk 100 sio = StringIO(self.buffer) 101 line = sio.readline() 102 self.buffer = sio.read() 103 return line 74 104 75 105 class WSGIRequest(http.HttpRequest): 76 106 def __init__(self, environ): … … 93 123 self.META['SCRIPT_NAME'] = script_name 94 124 self.method = environ['REQUEST_METHOD'].upper() 95 125 self._post_parse_error = False 126 if isinstance(self.environ['wsgi.input'], socket._fileobject): 127 # Under development server 'wsgi.input' is an instance of 128 # socket._fileobject which hangs indefinitely on reading bytes past 129 # available count. To prevent this it's wrapped in LimitedStream 130 # that doesn't read past Content-Length bytes. 131 # 132 # This is not done for other kinds of inputs (like flup's FastCGI 133 # streams) beacuse they don't suffer from this problem and we can 134 # avoid using another wrapper with its own .read and .readline 135 # implementation. 136 try: 137 content_length = int(self.environ.get('CONTENT_LENGTH', 0)) 138 except (ValueError, TypeError): 139 content_length = 0 140 self._stream = LimitedStream(self.environ['wsgi.input'], content_length) 141 else: 142 self._stream = self.environ['wsgi.input'] 143 self._read_started = False 96 144 97 145 def __repr__(self): 98 146 # Since this is called as part of error handling, we need to be very … … 128 176 return 'wsgi.url_scheme' in self.environ \ 129 177 and self.environ['wsgi.url_scheme'] == 'https' 130 178 131 def _load_post_and_files(self):132 # Populates self._post and self._files133 if self.method == 'POST':134 if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):135 self._raw_post_data = ''136 try:137 self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])138 except:139 # An error occured while parsing POST data. Since when140 # formatting the error the request handler might access141 # self.POST, set self._post and self._file to prevent142 # attempts to parse POST data again.143 self._post = http.QueryDict('')144 self._files = datastructures.MultiValueDict()145 # Mark that an error occured. This allows self.__repr__ to146 # be explicit about it instead of simply representing an147 # empty POST148 self._post_parse_error = True149 raise150 else:151 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()152 else:153 self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()154 155 179 def _get_request(self): 156 180 if not hasattr(self, '_request'): 157 181 self._request = datastructures.MergeDict(self.POST, self.GET) … … 187 211 self._load_post_and_files() 188 212 return self._files 189 213 190 def _get_raw_post_data(self):191 try:192 return self._raw_post_data193 except AttributeError:194 buf = StringIO()195 try:196 # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)197 content_length = int(self.environ.get('CONTENT_LENGTH', 0))198 except (ValueError, TypeError):199 # If CONTENT_LENGTH was empty string or not an integer, don't200 # error out. We've also seen None passed in here (against all201 # specs, but see ticket #8259), so we handle TypeError as well.202 content_length = 0203 if content_length > 0:204 safe_copyfileobj(self.environ['wsgi.input'], buf,205 size=content_length)206 self._raw_post_data = buf.getvalue()207 buf.close()208 return self._raw_post_data209 210 214 GET = property(_get_get, _set_get) 211 215 POST = property(_get_post, _set_post) 212 216 COOKIES = property(_get_cookies, _set_cookies) 213 217 FILES = property(_get_files) 214 218 REQUEST = property(_get_request) 215 raw_post_data = property(_get_raw_post_data)216 219 217 220 class WSGIHandler(base.BaseHandler): 218 221 initLock = Lock() -
django/http/__init__.py
=== modified file 'django/http/__init__.py'
7 7 from urllib import urlencode 8 8 from urlparse import urljoin 9 9 try: 10 from cStringIO import StringIO 11 except ImportError: 12 from StringIO import StringIO 13 try: 10 14 # The mod_python version is more efficient, so try importing it first. 11 15 from mod_python.util import parse_qsl 12 16 except ImportError: … … 126 130 parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) 127 131 return parser.parse() 128 132 133 def _get_raw_post_data(self): 134 if not hasattr(self, '_raw_post_data'): 135 if self._read_started: 136 raise Exception("You cannot access raw_post_data after reading from request's data stream") 137 self._raw_post_data = self.read() 138 self._stream = StringIO(self._raw_post_data) 139 return self._raw_post_data 140 raw_post_data = property(_get_raw_post_data) 141 142 def _mark_post_parse_error(self): 143 self._post = QueryDict('') 144 self._files = MultiValueDict() 145 self._post_parse_error = True 146 147 def _load_post_and_files(self): 148 # Populates self._post and self._files 149 if self.method != 'POST': 150 self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() 151 return 152 if self._read_started: 153 self._mark_post_parse_error() 154 return 155 156 if self.META.get('CONTENT_TYPE', '').startswith('multipart'): 157 self._raw_post_data = '' 158 try: 159 self._post, self._files = self.parse_file_upload(self.META, self) 160 except: 161 # An error occured while parsing POST data. Since when 162 # formatting the error the request handler might access 163 # self.POST, set self._post and self._file to prevent 164 # attempts to parse POST data again. 165 # Mark that an error occured. This allows self.__repr__ to 166 # be explicit about it instead of simply representing an 167 # empty POST 168 self._mark_post_parse_error() 169 raise 170 else: 171 self._post, self._files = QueryDict(self.raw_post_data, encoding=self._encoding), MultiValueDict() 172 173 ## File-like and iterator interface. 174 ## 175 ## Expects self._stream to be set to an appropriate source of bytes by 176 ## a corresponding request subclass (WSGIRequest or ModPythonRequest). 177 ## Also when request data has already been read by request.POST or 178 ## request.raw_post_data self._stream pooints to a StringIO instance 179 ## containing that data. 180 181 def read(self, *args, **kwargs): 182 self._read_started = True 183 return self._stream.read(*args, **kwargs) 184 185 def readline(self, *args, **kwargs): 186 self._read_started = True 187 return self._stream.readline(*args, **kwargs) 188 189 def xreadlines(self): 190 while True: 191 buf = self.readline() 192 if not buf: 193 break 194 yield buf 195 __iter__ = xreadlines 196 197 def readlines(self): 198 return list(iter(self)) 199 129 200 class QueryDict(MultiValueDict): 130 201 """ 131 202 A specialized MultiValueDict that takes a query string when initialized. … … 192 263 for key, value in dict.items(self): 193 264 dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo)) 194 265 return result 195 266 196 267 def setlist(self, key, list_): 197 268 self._assert_mutable() 198 269 key = str_to_unicode(key, self.encoding) -
docs/ref/request-response.txt
=== modified file 'docs/ref/request-response.txt'
189 189 190 190 .. attribute:: HttpRequest.raw_post_data 191 191 192 The raw HTTP POST data. This is only useful for advanced processing. Use 193 ``POST`` instead. 192 The raw HTTP POST data as a byte string. This is useful for processing 193 data in different formats than of conventional HTML forms: binary images, 194 XML payload etc. For processing form data use ``HttpRequest.POST``. 195 196 .. versionadded:: 1.3 197 198 You can also read from an HttpRequest using file-like interface, see 199 ``HttpRequest.read()`` 194 200 195 201 .. attribute:: HttpRequest.urlconf 196 202 … … 249 255 If you write your own XMLHttpRequest call (on the browser side), you'll 250 256 have to set this header manually if you want ``is_ajax()`` to work. 251 257 258 .. method:: HttpRequest.read(size=None) 259 .. method:: HttpRequest.readline() 260 .. method:: HttpRequest.readlines() 261 .. method:: HttpRequest.xreadlines() 262 .. method:: HttpRequest.__iter__() 263 264 .. versionadded:: 1.3 265 266 Methods implement file-like interface for reading from an HttpRequest 267 instance. This makes it possible to process an incoming request in a 268 streaming fashion. A common use-case is processing a big XML payload with 269 iterative parser without constructing a whole XML tree in memory. 270 271 Given this standard interface an HttpRequest instance can be passed directly 272 to an XML parser, for example ElementTree:: 273 274 import xml.etree.ElementTree as ET 275 for element in ET.iterparse(request): 276 process(element) 277 252 278 253 279 QueryDict objects 254 280 ----------------- -
tests/regressiontests/requests/tests.py
=== modified file 'tests/regressiontests/requests/tests.py'
1 1 from datetime import datetime, timedelta 2 2 import time 3 from StringIO import StringIO 3 4 import unittest 4 5 5 6 from django.http import HttpRequest, HttpResponse, parse_cookie 6 from django.core.handlers.wsgi import WSGIRequest 7 from django.core.handlers.wsgi import WSGIRequest, LimitedStream 7 8 from django.core.handlers.modpython import ModPythonRequest 8 9 from django.utils.http import cookie_date 9 10 … … 17 18 self.assertEqual(request.META.keys(), []) 18 19 19 20 def test_wsgirequest(self): 20 request = WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus' })21 request = WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus', 'wsgi.input': StringIO('')}) 21 22 self.assertEqual(request.GET.keys(), []) 22 23 self.assertEqual(request.POST.keys(), []) 23 24 self.assertEqual(request.COOKIES.keys(), []) 24 self.assertEqual(set(request.META.keys()), set(['PATH_INFO', 'REQUEST_METHOD', 'SCRIPT_NAME' ]))25 self.assertEqual(set(request.META.keys()), set(['PATH_INFO', 'REQUEST_METHOD', 'SCRIPT_NAME', 'wsgi.input'])) 25 26 self.assertEqual(request.META['PATH_INFO'], 'bogus') 26 27 self.assertEqual(request.META['REQUEST_METHOD'], 'bogus') 27 28 self.assertEqual(request.META['SCRIPT_NAME'], '') … … 88 89 max_age_cookie = response.cookies['max_age'] 89 90 self.assertEqual(max_age_cookie['max-age'], 10) 90 91 self.assertEqual(max_age_cookie['expires'], cookie_date(time.time()+10)) 92 93 def test_limited_stream(self): 94 stream = LimitedStream(StringIO('test'), 2) 95 self.assertEqual(stream.read(), 'te') 96 stream = LimitedStream(StringIO('test'), 2) 97 self.assertEqual(stream.read(5), 'te') 98 99 def test_stream(self): 100 request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) 101 self.assertEqual(request.read(), 'name=value') 102 103 def test_read_after_value(self): 104 """ 105 Reading from request is allowed after accessing request contents as 106 POST or raw_post_data. 107 """ 108 request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) 109 self.assertEqual(request.POST, {u'name': [u'value']}) 110 self.assertEqual(request.raw_post_data, 'name=value') 111 self.assertEqual(request.read(), 'name=value') 112 113 def test_value_after_read(self): 114 """ 115 Construction of POST or raw_post_data is not allowed after reading 116 from request. 117 """ 118 request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) 119 self.assertEqual(request.read(2), 'na') 120 self.assertRaises(Exception, lambda: request.raw_post_data) 121 self.assertEqual(request.POST, {}) 122 123 def test_read_by_lines(self): 124 request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) 125 self.assertEqual(list(request), ['name=value'])