=== modified file 'django/core/handlers/modpython.py'
--- django/core/handlers/modpython.py	2009-09-12 20:28:01 +0000
+++ django/core/handlers/modpython.py	2009-09-13 14:15:02 +0000
@@ -36,6 +36,8 @@
             # naughty, but also pretty harmless.
             self.path_info = u'/'
         self._post_parse_error = False
+        self._stream = self._req
+        self._read_started = False
 
     def __repr__(self):
         # Since this is called as part of error handling, we need to be very
@@ -75,26 +77,6 @@
             # mod_python < 3.2.10 doesn't have req.is_https().
             return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1')
 
-    def _load_post_and_files(self):
-        "Populates self._post and self._files"
-        if self.method != 'POST':
-            self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()
-            return
-
-        if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
-            self._raw_post_data = ''
-            try:
-                self._post, self._files = self.parse_file_upload(self.META, self._req)
-            except:
-                # See django.core.handlers.wsgi.WSGIHandler for an explanation
-                # of what's going on here.
-                self._post = http.QueryDict('')
-                self._files = datastructures.MultiValueDict()
-                self._post_parse_error = True
-                raise
-        else:
-            self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
-
     def _get_request(self):
         if not hasattr(self, '_request'):
             self._request = datastructures.MergeDict(self.POST, self.GET)
@@ -156,13 +138,6 @@
                 self._meta[key] = value
         return self._meta
 
-    def _get_raw_post_data(self):
-        try:
-            return self._raw_post_data
-        except AttributeError:
-            self._raw_post_data = self._req.read()
-            return self._raw_post_data
-
     def _get_method(self):
         return self.META['REQUEST_METHOD'].upper()
 
@@ -172,7 +147,6 @@
     FILES = property(_get_files)
     META = property(_get_meta)
     REQUEST = property(_get_request)
-    raw_post_data = property(_get_raw_post_data)
     method = property(_get_method)
 
 class ModPythonHandler(BaseHandler):

=== modified file 'django/core/handlers/wsgi.py'
--- django/core/handlers/wsgi.py	2009-03-08 09:58:40 +0000
+++ django/core/handlers/wsgi.py	2009-10-16 21:43:57 +0000
@@ -4,6 +4,7 @@
     from cStringIO import StringIO
 except ImportError:
     from StringIO import StringIO
+import socket
 
 from django import http
 from django.core import signals
@@ -57,20 +58,49 @@
     505: 'HTTP VERSION NOT SUPPORTED',
 }
 
-def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0):
-    """
-    A version of shutil.copyfileobj that will not read more than 'size' bytes.
-    This makes it safe from clients sending more than CONTENT_LENGTH bytes of
-    data in the body.
-    """
-    if not size:
-        return
-    while size > 0:
-        buf = fsrc.read(min(length, size))
-        if not buf:
-            break
-        fdst.write(buf)
-        size -= len(buf)
+class LimitedStream(object):
+    '''
+    LimitedStream wraps another stream in order to not allow reading from it
+    past specified amount of bytes.
+    '''
+    def __init__(self, stream, limit, buf_size=64 * 1024 * 1024):
+        self.stream = stream
+        self.remaining = limit
+        self.buffer = ''
+        self.buf_size = buf_size
+
+    def _read_limited(self, size=None):
+        if size is None or size > self.remaining:
+            size = self.remaining
+        if size == 0:
+            return ''
+        result = self.stream.read(size)
+        self.remaining -= len(result)
+        return result
+
+    def read(self, size=None):
+        if size is None:
+            result = self.buffer + self._read_limited()
+            self.buffer = ''
+        elif size < len(self.buffer):
+            result = self.buffer[:size]
+            self.buffer = self.buffer[size:]
+        else: # size >= len(self.buffer)
+            result = self.buffer + self._read_limited(size - len(self.buffer))
+            self.buffer = ''
+        return result
+
+    def readline(self, size=None):
+        while '\n' not in self.buffer or \
+              (size is not None and len(self.buffer) < size):
+            chunk = self._read_limited(self.buf_size)
+            if not chunk:
+                break
+            self.buffer += chunk
+        sio = StringIO(self.buffer)
+        line = sio.readline()
+        self.buffer = sio.read()
+        return line
 
 class WSGIRequest(http.HttpRequest):
     def __init__(self, environ):
@@ -93,6 +123,24 @@
         self.META['SCRIPT_NAME'] = script_name
         self.method = environ['REQUEST_METHOD'].upper()
         self._post_parse_error = False
+        if isinstance(self.environ['wsgi.input'], socket._fileobject):
+            # Under development server 'wsgi.input' is an instance of
+            # socket._fileobject which hangs indefinitely on reading bytes past
+            # available count. To prevent this it's wrapped in LimitedStream
+            # that doesn't read past Content-Length bytes.
+            #
+            # This is not done for other kinds of inputs (like flup's FastCGI
+            # streams) beacuse they don't suffer from this problem and we can
+            # avoid using another wrapper with its own .read and .readline
+            # implementation.
+            try:
+                content_length = int(self.environ.get('CONTENT_LENGTH', 0))
+            except (ValueError, TypeError):
+                content_length = 0
+            self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
+        else:
+            self._stream = self.environ['wsgi.input']
+        self._read_started = False
 
     def __repr__(self):
         # Since this is called as part of error handling, we need to be very
@@ -128,30 +176,6 @@
         return 'wsgi.url_scheme' in self.environ \
             and self.environ['wsgi.url_scheme'] == 'https'
 
-    def _load_post_and_files(self):
-        # Populates self._post and self._files
-        if self.method == 'POST':
-            if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
-                self._raw_post_data = ''
-                try:
-                    self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
-                except:
-                    # An error occured while parsing POST data.  Since when
-                    # formatting the error the request handler might access
-                    # self.POST, set self._post and self._file to prevent
-                    # attempts to parse POST data again.
-                    self._post = http.QueryDict('')
-                    self._files = datastructures.MultiValueDict()
-                    # Mark that an error occured.  This allows self.__repr__ to
-                    # be explicit about it instead of simply representing an
-                    # empty POST
-                    self._post_parse_error = True
-                    raise
-            else:
-                self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
-        else:
-            self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()
-
     def _get_request(self):
         if not hasattr(self, '_request'):
             self._request = datastructures.MergeDict(self.POST, self.GET)
@@ -187,32 +211,11 @@
             self._load_post_and_files()
         return self._files
 
-    def _get_raw_post_data(self):
-        try:
-            return self._raw_post_data
-        except AttributeError:
-            buf = StringIO()
-            try:
-                # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
-                content_length = int(self.environ.get('CONTENT_LENGTH', 0))
-            except (ValueError, TypeError):
-                # If CONTENT_LENGTH was empty string or not an integer, don't
-                # error out. We've also seen None passed in here (against all
-                # specs, but see ticket #8259), so we handle TypeError as well.
-                content_length = 0
-            if content_length > 0:
-                safe_copyfileobj(self.environ['wsgi.input'], buf,
-                        size=content_length)
-            self._raw_post_data = buf.getvalue()
-            buf.close()
-            return self._raw_post_data
-
     GET = property(_get_get, _set_get)
     POST = property(_get_post, _set_post)
     COOKIES = property(_get_cookies, _set_cookies)
     FILES = property(_get_files)
     REQUEST = property(_get_request)
-    raw_post_data = property(_get_raw_post_data)
 
 class WSGIHandler(base.BaseHandler):
     initLock = Lock()

=== modified file 'django/http/__init__.py'
--- django/http/__init__.py	2009-05-08 10:31:36 +0000
+++ django/http/__init__.py	2009-10-17 10:16:32 +0000
@@ -5,6 +5,10 @@
 from urllib import urlencode
 from urlparse import urljoin
 try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+try:
     # The mod_python version is more efficient, so try importing it first.
     from mod_python.util import parse_qsl
 except ImportError:
@@ -123,6 +127,73 @@
         parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
         return parser.parse()
 
+    def _get_raw_post_data(self):
+        if not hasattr(self, '_raw_post_data'):
+            if self._read_started:
+                raise Exception("You cannot access raw_post_data after reading from request's data stream")
+            self._raw_post_data = self.read()
+            self._stream = StringIO(self._raw_post_data)
+        return self._raw_post_data
+    raw_post_data = property(_get_raw_post_data)
+
+    def _mark_post_parse_error(self):
+        self._post = QueryDict('')
+        self._files = MultiValueDict()
+        self._post_parse_error = True
+
+    def _load_post_and_files(self):
+        # Populates self._post and self._files
+        if self.method != 'POST':
+            self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
+            return
+        if self._read_started:
+            self._mark_post_parse_error()
+            return
+
+        if self.META.get('CONTENT_TYPE', '').startswith('multipart'):
+            self._raw_post_data = ''
+            try:
+                self._post, self._files = self.parse_file_upload(self.META, self)
+            except:
+                # An error occured while parsing POST data.  Since when
+                # formatting the error the request handler might access
+                # self.POST, set self._post and self._file to prevent
+                # attempts to parse POST data again.
+                # Mark that an error occured.  This allows self.__repr__ to
+                # be explicit about it instead of simply representing an
+                # empty POST
+                self._mark_post_parse_error()
+                raise
+        else:
+            self._post, self._files = QueryDict(self.raw_post_data, encoding=self._encoding), MultiValueDict()
+
+    ## File-like and iterator interface.
+    ##
+    ## Expects self._stream to be set to an appropriate source of bytes by
+    ## a corresponding request subclass (WSGIRequest or ModPythonRequest).
+    ## Also when request data has already been read by request.POST or
+    ## request.raw_post_data self._stream pooints to a StringIO instance
+    ## containing that data.
+
+    def read(self, *args, **kwargs):
+        self._read_started = True
+        return self._stream.read(*args, **kwargs)
+
+    def readline(self, *args, **kwargs):
+        self._read_started = True
+        return self._stream.readline(*args, **kwargs)
+
+    def xreadlines(self):
+        while True:
+            buf = self.readline()
+            if not buf:
+                break
+            yield buf
+    __iter__ = xreadlines
+
+    def readlines(self):
+        return list(iter(self))
+
 class QueryDict(MultiValueDict):
     """
     A specialized MultiValueDict that takes a query string when initialized.
@@ -189,7 +260,7 @@
         for key, value in dict.items(self):
             dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo))
         return result
-    
+
     def setlist(self, key, list_):
         self._assert_mutable()
         key = str_to_unicode(key, self.encoding)

=== modified file 'docs/ref/request-response.txt'
--- docs/ref/request-response.txt	2009-09-12 21:51:16 +0000
+++ docs/ref/request-response.txt	2009-10-17 18:37:09 +0000
@@ -176,8 +176,14 @@
 
 .. attribute:: HttpRequest.raw_post_data
 
-    The raw HTTP POST data. This is only useful for advanced processing. Use
-    ``POST`` instead.
+    The raw HTTP POST data as a byte string. This is useful for processing
+    data in different formats than of conventional HTML forms: binary images,
+    XML payload etc. For processing form data use ``HttpRequest.POST``.
+
+    .. versionadded:: 1.2
+
+    You can also read from an HttpRequest using file-like interface, see
+    ``HttpRequest.read()``
 
 .. attribute:: HttpRequest.urlconf
 
@@ -236,6 +242,26 @@
    If you write your own XMLHttpRequest call (on the browser side), you'll
    have to set this header manually if you want ``is_ajax()`` to work.
 
+.. method:: HttpRequest.read(size=None)
+.. method:: HttpRequest.readline()
+.. method:: HttpRequest.readlines()
+.. method:: HttpRequest.xreadlines()
+.. method:: HttpRequest.__iter__()
+
+   .. versionadded:: 1.2
+
+   Methods implement file-like interface for reading from an HttpRequest
+   instance. This makes it possible to process an incoming request in a
+   streaming fashion. A common use-case is processing a big XML payload with
+   iterative parser without constructing a whole XML tree in memory.
+
+   Given this standard interface an HttpRequest instance can be passed directly
+   to an XML parser, for example ElementTree::
+
+       import xml.etree.ElementTree as ET
+       for element in ET.iterparse(request):
+           process(element)
+
 
 QueryDict objects
 -----------------

=== modified file 'tests/regressiontests/requests/tests.py'
--- tests/regressiontests/requests/tests.py	2008-08-23 16:28:12 +0000
+++ tests/regressiontests/requests/tests.py	2009-10-17 10:26:20 +0000
@@ -7,8 +7,9 @@
 COOKIES:{},
 META:{}>
 
+>>> from StringIO import StringIO
 >>> from django.core.handlers.wsgi import WSGIRequest
->>> print repr(WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus'}))
+>>> print repr(WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus', 'wsgi.input': StringIO()}))
 <WSGIRequest
 GET:<QueryDict: {}>,
 POST:<QueryDict: {}>,
@@ -44,4 +45,47 @@
 >>> request.path = ''
 >>> print request.build_absolute_uri(location="/path/with:colons")
 http://www.example.com/path/with:colons
+
+
+>>> from django.core.handlers.wsgi import LimitedStream
+>>> stream = LimitedStream(StringIO('test'), 2)
+>>> stream.read()
+'te'
+>>> stream = LimitedStream(StringIO('test'), 2)
+>>> stream.read(5)
+'te'
+
+
+Basic test of file-like reading from request
+>>> request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
+>>> request.read()
+'name=value'
+
+
+Reading from request is allowed after accessing request contents as POST or raw_post_data
+>>> request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
+>>> request.POST
+<QueryDict: {u'name': [u'value']}>
+>>> request.raw_post_data
+'name=value'
+>>> request.read()
+'name=value'
+
+Construction of POST or raw_post_data is not allowed after reading from request
+>>> request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
+>>> request.read(2)
+'na'
+>>> request.raw_post_data
+Traceback (most recent call last):
+  File "<stdin>", line 1, in ?
+    raise Exception("You cannot access raw_post_data after reading from request's data stream")
+Exception: You cannot access raw_post_data after reading from request's data stream
+>>> request.POST
+<QueryDict: {}>
+
+Request can be read by lines
+>>> request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
+>>> [l for l in request]
+['name=value']
+
 """

