Ticket #9886: 9886.diff

File 9886.diff, 12.1 KB (added by Ivan Sagalaev, 15 years ago)

Patch for review

  • django/core/handlers/modpython.py

    === modified file 'django/core/handlers/modpython.py'
     
    3636            # naughty, but also pretty harmless.
    3737            self.path_info = u'/'
    3838        self._post_parse_error = False
     39        self._stream = self._req
     40        self._read_started = False
    3941
    4042    def __repr__(self):
    4143        # Since this is called as part of error handling, we need to be very
     
    7577            # mod_python < 3.2.10 doesn't have req.is_https().
    7678            return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1')
    7779
    78     def _load_post_and_files(self):
    79         "Populates self._post and self._files"
    80         if self.method != 'POST':
    81             self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()
    82             return
    83 
    84         if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
    85             self._raw_post_data = ''
    86             try:
    87                 self._post, self._files = self.parse_file_upload(self.META, self._req)
    88             except:
    89                 # See django.core.handlers.wsgi.WSGIHandler for an explanation
    90                 # of what's going on here.
    91                 self._post = http.QueryDict('')
    92                 self._files = datastructures.MultiValueDict()
    93                 self._post_parse_error = True
    94                 raise
    95         else:
    96             self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
    97 
    9880    def _get_request(self):
    9981        if not hasattr(self, '_request'):
    10082            self._request = datastructures.MergeDict(self.POST, self.GET)
     
    156138                self._meta[key] = value
    157139        return self._meta
    158140
    159     def _get_raw_post_data(self):
    160         try:
    161             return self._raw_post_data
    162         except AttributeError:
    163             self._raw_post_data = self._req.read()
    164             return self._raw_post_data
    165 
    166141    def _get_method(self):
    167142        return self.META['REQUEST_METHOD'].upper()
    168143
     
    172147    FILES = property(_get_files)
    173148    META = property(_get_meta)
    174149    REQUEST = property(_get_request)
    175     raw_post_data = property(_get_raw_post_data)
    176150    method = property(_get_method)
    177151
    178152class ModPythonHandler(BaseHandler):
  • django/core/handlers/wsgi.py

    === modified file 'django/core/handlers/wsgi.py'
     
    44    from cStringIO import StringIO
    55except ImportError:
    66    from StringIO import StringIO
     7import socket
    78
    89from django import http
    910from django.core import signals
     
    5758    505: 'HTTP VERSION NOT SUPPORTED',
    5859}
    5960
    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)
     61class LimitedInput(object):
     62    BUF_SIZE = 64 * 2**10
     63
     64    def __init__(self, stream, limit, buf_size=None):
     65        self.stream = stream
     66        self.remaining = limit
     67        self.buffer = ''
     68        self.buf_size = buf_size or self.BUF_SIZE
     69
     70    def _read_limited(self, size=None):
     71        if size is None or size > self.remaining:
     72            size = self.remaining
     73        if size == 0:
     74            return ''
     75        result = self.stream.read(size)
     76        self.remaining -= len(result)
     77        return result
     78
     79    def read(self, size=None):
     80        if size is None:
     81            result = self.buffer + self._read_limited()
     82            self.buffer = ''
     83        elif size < len(self.buffer):
     84            result = self.buffer[:size]
     85            self.buffer = self.buffer[size:]
     86        else: # size >= len(self.buffer)
     87            result = self.buffer + self._read_limited(size - len(self.buffer))
     88            self.buffer = ''
     89        return result
     90
     91    def readline(self, size=None):
     92        while '\n' not in self.buffer or \
     93              (size is not None and len(self.buffer) < size):
     94            chunk = self._read_limited(self.buf_size)
     95            if not chunk:
     96                break
     97            self.buffer += chunk
     98        sio = StringIO(self.buffer)
     99        line = sio.readline()
     100        self.buffer = sio.read()
     101        return line
    74102
    75103class WSGIRequest(http.HttpRequest):
    76104    def __init__(self, environ):
     
    93121        self.META['SCRIPT_NAME'] = script_name
    94122        self.method = environ['REQUEST_METHOD'].upper()
    95123        self._post_parse_error = False
     124        if isinstance(self.environ['wsgi.input'], socket._fileobject):
     125            # Under development server 'wsgi.input' is an instance of
     126            # socket._fileobject which hangs indefinitely on reading bytes past
     127            # available count. To prevent this it's wrapped in LimitedInput
     128            # that doesn't read past Content-Length bytes.
     129            #
     130            # This is not done for other kinds of inputs (like flup's FastCGI
     131            # streams) beacuse they don't suffer from this problem and we can
     132            # avoid using another wrapper with its own .read and .readline
     133            # implementation.
     134            try:
     135                content_length = int(self.environ.get('CONTENT_LENGTH', 0))
     136            except (ValueError, TypeError):
     137                content_length = 0
     138            self._stream = LimitedInput(self.environ['wsgi.input'], content_length)
     139        else:
     140            self._stream = self.environ['wsgi.input']
     141        self._read_started = False
    96142
    97143    def __repr__(self):
    98144        # Since this is called as part of error handling, we need to be very
     
    126172        return 'wsgi.url_scheme' in self.environ \
    127173            and self.environ['wsgi.url_scheme'] == 'https'
    128174
    129     def _load_post_and_files(self):
    130         # Populates self._post and self._files
    131         if self.method == 'POST':
    132             if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
    133                 self._raw_post_data = ''
    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
    148             else:
    149                 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
    150         else:
    151             self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()
    152 
    153175    def _get_request(self):
    154176        if not hasattr(self, '_request'):
    155177            self._request = datastructures.MergeDict(self.POST, self.GET)
     
    185207            self._load_post_and_files()
    186208        return self._files
    187209
    188     def _get_raw_post_data(self):
    189         try:
    190             return self._raw_post_data
    191         except AttributeError:
    192             buf = StringIO()
    193             try:
    194                 # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
    195                 content_length = int(self.environ.get('CONTENT_LENGTH', 0))
    196             except (ValueError, TypeError):
    197                 # If CONTENT_LENGTH was empty string or not an integer, don't
    198                 # error out. We've also seen None passed in here (against all
    199                 # specs, but see ticket #8259), so we handle TypeError as well.
    200                 content_length = 0
    201             if content_length > 0:
    202                 safe_copyfileobj(self.environ['wsgi.input'], buf,
    203                         size=content_length)
    204             self._raw_post_data = buf.getvalue()
    205             buf.close()
    206             return self._raw_post_data
    207 
    208210    GET = property(_get_get, _set_get)
    209211    POST = property(_get_post, _set_post)
    210212    COOKIES = property(_get_cookies, _set_cookies)
    211213    FILES = property(_get_files)
    212214    REQUEST = property(_get_request)
    213     raw_post_data = property(_get_raw_post_data)
    214215
    215216class WSGIHandler(base.BaseHandler):
    216217    initLock = Lock()
  • django/http/__init__.py

    === modified file 'django/http/__init__.py'
     
    55from urllib import urlencode
    66from urlparse import urljoin
    77try:
     8    from cStringIO import StringIO
     9except ImportError:
     10    from StringIO import StringIO
     11try:
    812    # The mod_python version is more efficient, so try importing it first.
    913    from mod_python.util import parse_qsl
    1014except ImportError:
     
    123127        parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
    124128        return parser.parse()
    125129
     130    def _get_raw_post_data(self):
     131        if not hasattr(self, '_raw_post_data'):
     132            if self._read_started:
     133                raise Exception("You cannot access raw_post_data after reading from request's data stream")
     134            self._raw_post_data = self.read()
     135            self._stream = StringIO(self._raw_post_data)
     136        return self._raw_post_data
     137    raw_post_data = property(_get_raw_post_data)
     138
     139    def _mark_post_parse_error(self):
     140        self._post = QueryDict('')
     141        self._files = MultiValueDict()
     142        self._post_parse_error = True
     143
     144    def _load_post_and_files(self):
     145        # Populates self._post and self._files
     146        if self.method != 'POST':
     147            self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
     148            return
     149        if self._read_started:
     150            self._mark_post_parse_error()
     151            return
     152
     153        if self.META.get('CONTENT_TYPE', '').startswith('multipart'):
     154            self._raw_post_data = ''
     155            try:
     156                self._post, self._files = self.parse_file_upload(self.META, self)
     157            except:
     158                # An error occured while parsing POST data.  Since when
     159                # formatting the error the request handler might access
     160                # self.POST, set self._post and self._file to prevent
     161                # attempts to parse POST data again.
     162                # Mark that an error occured.  This allows self.__repr__ to
     163                # be explicit about it instead of simply representing an
     164                # empty POST
     165                self._mark_post_parse_error()
     166                raise
     167        else:
     168            self._post, self._files = QueryDict(self.raw_post_data, encoding=self._encoding), MultiValueDict()
     169
     170    ## File-like and iterator interface.
     171    ##
     172    ## Expects self._stream to be set to an appropriate source of bytes by
     173    ## a corresponding request subclass (WSGIRequest or ModPythonRequest).
     174    ## Also when request data has already been read by request.POST or
     175    ## request.raw_post_data self._stream pooints to a StringIO instance
     176    ## containing that data.
     177
     178    def read(self, size=None):
     179        self._read_started = True
     180        return self._stream.read(size)
     181
     182    def readline(self, *args, **kwargs):
     183        self._read_started = True
     184        return self._stream.readline(*args, **kwargs)
     185
     186    def xreadlines(self):
     187        while True:
     188            buf = self.readline()
     189            if not buf:
     190                break
     191            yield buf
     192    __iter__ = xreadlines
     193
     194    def readlines(self):
     195        return list(iter(self))
     196
    126197class QueryDict(MultiValueDict):
    127198    """
    128199    A specialized MultiValueDict that takes a query string when initialized.
Back to Top