Ticket #9886: 9886.6.diff
File 9886.6.diff, 17.3 KB (added by , 14 years ago) |
---|
-
django/core/handlers/modpython.py
=== modified file 'django/core/handlers/modpython.py'
42 42 # naughty, but also pretty harmless. 43 43 self.path_info = u'/' 44 44 self._post_parse_error = False 45 self._stream = self._req 46 self._read_started = False 45 47 46 48 def __repr__(self): 47 49 # Since this is called as part of error handling, we need to be very … … 81 83 # mod_python < 3.2.10 doesn't have req.is_https(). 82 84 return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1') 83 85 84 def _load_post_and_files(self):85 "Populates self._post and self._files"86 if self.method != 'POST':87 self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()88 return89 90 if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):91 self._raw_post_data = ''92 try:93 self._post, self._files = self.parse_file_upload(self.META, self._req)94 except:95 # See django.core.handlers.wsgi.WSGIHandler for an explanation96 # of what's going on here.97 self._post = http.QueryDict('')98 self._files = datastructures.MultiValueDict()99 self._post_parse_error = True100 raise101 else:102 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()103 104 86 def _get_request(self): 105 87 if not hasattr(self, '_request'): 106 88 self._request = datastructures.MergeDict(self.POST, self.GET) … … 162 144 self._meta[key] = value 163 145 return self._meta 164 146 165 def _get_raw_post_data(self):166 try:167 return self._raw_post_data168 except AttributeError:169 self._raw_post_data = self._req.read()170 return self._raw_post_data171 172 147 def _get_method(self): 173 148 return self.META['REQUEST_METHOD'].upper() 174 149 … … 178 153 FILES = property(_get_files) 179 154 META = property(_get_meta) 180 155 REQUEST = property(_get_request) 181 raw_post_data = property(_get_raw_post_data)182 156 method = property(_get_method) 183 157 184 158 class ModPythonHandler(BaseHandler): -
django/core/handlers/wsgi.py
=== modified file 'django/core/handlers/wsgi.py'
5 5 from cStringIO import StringIO 6 6 except ImportError: 7 7 from StringIO import StringIO 8 import socket 8 9 9 10 from django import http 10 11 from django.core import signals … … 62 63 505: 'HTTP VERSION NOT SUPPORTED', 63 64 } 64 65 65 def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0): 66 """ 67 A version of shutil.copyfileobj that will not read more than 'size' bytes. 68 This makes it safe from clients sending more than CONTENT_LENGTH bytes of 69 data in the body. 70 """ 71 if not size: 72 return 73 while size > 0: 74 buf = fsrc.read(min(length, size)) 75 if not buf: 76 break 77 fdst.write(buf) 78 size -= len(buf) 66 class LimitedStream(object): 67 ''' 68 LimitedStream wraps another stream in order to not allow reading from it 69 past specified amount of bytes. 70 ''' 71 def __init__(self, stream, limit, buf_size=64 * 1024 * 1024): 72 self.stream = stream 73 self.remaining = limit 74 self.buffer = '' 75 self.buf_size = buf_size 76 77 def _read_limited(self, size=None): 78 if size is None or size > self.remaining: 79 size = self.remaining 80 if size == 0: 81 return '' 82 result = self.stream.read(size) 83 self.remaining -= len(result) 84 return result 85 86 def read(self, size=None): 87 if size is None: 88 result = self.buffer + self._read_limited() 89 self.buffer = '' 90 elif size < len(self.buffer): 91 result = self.buffer[:size] 92 self.buffer = self.buffer[size:] 93 else: # size >= len(self.buffer) 94 result = self.buffer + self._read_limited(size - len(self.buffer)) 95 self.buffer = '' 96 return result 97 98 def readline(self, size=None): 99 while '\n' not in self.buffer or \ 100 (size is not None and len(self.buffer) < size): 101 chunk = self._read_limited(self.buf_size) 102 if not chunk: 103 break 104 self.buffer += chunk 105 sio = StringIO(self.buffer) 106 line = sio.readline() 107 self.buffer = sio.read() 108 return line 79 109 80 110 class WSGIRequest(http.HttpRequest): 81 111 def __init__(self, environ): … … 98 128 self.META['SCRIPT_NAME'] = script_name 99 129 self.method = environ['REQUEST_METHOD'].upper() 100 130 self._post_parse_error = False 131 if isinstance(self.environ['wsgi.input'], socket._fileobject): 132 # Under development server 'wsgi.input' is an instance of 133 # socket._fileobject which hangs indefinitely on reading bytes past 134 # available count. To prevent this it's wrapped in LimitedStream 135 # that doesn't read past Content-Length bytes. 136 # 137 # This is not done for other kinds of inputs (like flup's FastCGI 138 # streams) beacuse they don't suffer from this problem and we can 139 # avoid using another wrapper with its own .read and .readline 140 # implementation. 141 try: 142 content_length = int(self.environ.get('CONTENT_LENGTH', 0)) 143 except (ValueError, TypeError): 144 content_length = 0 145 self._stream = LimitedStream(self.environ['wsgi.input'], content_length) 146 else: 147 self._stream = self.environ['wsgi.input'] 148 self._read_started = False 101 149 102 150 def __repr__(self): 103 151 # Since this is called as part of error handling, we need to be very … … 133 181 return 'wsgi.url_scheme' in self.environ \ 134 182 and self.environ['wsgi.url_scheme'] == 'https' 135 183 136 def _load_post_and_files(self):137 # Populates self._post and self._files138 if self.method == 'POST':139 if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):140 self._raw_post_data = ''141 try:142 self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])143 except:144 # An error occured while parsing POST data. Since when145 # formatting the error the request handler might access146 # self.POST, set self._post and self._file to prevent147 # attempts to parse POST data again.148 self._post = http.QueryDict('')149 self._files = datastructures.MultiValueDict()150 # Mark that an error occured. This allows self.__repr__ to151 # be explicit about it instead of simply representing an152 # empty POST153 self._post_parse_error = True154 raise155 else:156 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()157 else:158 self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()159 160 184 def _get_request(self): 161 185 if not hasattr(self, '_request'): 162 186 self._request = datastructures.MergeDict(self.POST, self.GET) … … 192 216 self._load_post_and_files() 193 217 return self._files 194 218 195 def _get_raw_post_data(self):196 try:197 return self._raw_post_data198 except AttributeError:199 buf = StringIO()200 try:201 # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)202 content_length = int(self.environ.get('CONTENT_LENGTH', 0))203 except (ValueError, TypeError):204 # If CONTENT_LENGTH was empty string or not an integer, don't205 # error out. We've also seen None passed in here (against all206 # specs, but see ticket #8259), so we handle TypeError as well.207 content_length = 0208 if content_length > 0:209 safe_copyfileobj(self.environ['wsgi.input'], buf,210 size=content_length)211 self._raw_post_data = buf.getvalue()212 buf.close()213 return self._raw_post_data214 215 219 GET = property(_get_get, _set_get) 216 220 POST = property(_get_post, _set_post) 217 221 COOKIES = property(_get_cookies, _set_cookies) 218 222 FILES = property(_get_files) 219 223 REQUEST = property(_get_request) 220 raw_post_data = property(_get_raw_post_data)221 224 222 225 class WSGIHandler(base.BaseHandler): 223 226 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: … … 132 136 parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) 133 137 return parser.parse() 134 138 139 def _get_raw_post_data(self): 140 if not hasattr(self, '_raw_post_data'): 141 if self._read_started: 142 raise Exception("You cannot access raw_post_data after reading from request's data stream") 143 self._raw_post_data = self.read() 144 self._stream = StringIO(self._raw_post_data) 145 return self._raw_post_data 146 raw_post_data = property(_get_raw_post_data) 147 148 def _mark_post_parse_error(self): 149 self._post = QueryDict('') 150 self._files = MultiValueDict() 151 self._post_parse_error = True 152 153 def _load_post_and_files(self): 154 # Populates self._post and self._files 155 if self.method != 'POST': 156 self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() 157 return 158 if self._read_started: 159 self._mark_post_parse_error() 160 return 161 162 if self.META.get('CONTENT_TYPE', '').startswith('multipart'): 163 self._raw_post_data = '' 164 try: 165 self._post, self._files = self.parse_file_upload(self.META, self) 166 except: 167 # An error occured while parsing POST data. Since when 168 # formatting the error the request handler might access 169 # self.POST, set self._post and self._file to prevent 170 # attempts to parse POST data again. 171 # Mark that an error occured. This allows self.__repr__ to 172 # be explicit about it instead of simply representing an 173 # empty POST 174 self._mark_post_parse_error() 175 raise 176 else: 177 self._post, self._files = QueryDict(self.raw_post_data, encoding=self._encoding), MultiValueDict() 178 179 ## File-like and iterator interface. 180 ## 181 ## Expects self._stream to be set to an appropriate source of bytes by 182 ## a corresponding request subclass (WSGIRequest or ModPythonRequest). 183 ## Also when request data has already been read by request.POST or 184 ## request.raw_post_data self._stream pooints to a StringIO instance 185 ## containing that data. 186 187 def read(self, *args, **kwargs): 188 self._read_started = True 189 return self._stream.read(*args, **kwargs) 190 191 def readline(self, *args, **kwargs): 192 self._read_started = True 193 return self._stream.readline(*args, **kwargs) 194 195 def xreadlines(self): 196 while True: 197 buf = self.readline() 198 if not buf: 199 break 200 yield buf 201 __iter__ = xreadlines 202 203 def readlines(self): 204 return list(iter(self)) 205 135 206 class QueryDict(MultiValueDict): 136 207 """ 137 208 A specialized MultiValueDict that takes a query string when initialized. … … 198 269 for key, value in dict.items(self): 199 270 dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo)) 200 271 return result 201 272 202 273 def setlist(self, key, list_): 203 274 self._assert_mutable() 204 275 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'])