Code

Ticket #2070: 2070_latest7354.diff

File 2070_latest7354.diff, 68.8 KB (added by axiak, 6 years ago)

The latest #2070 patch. Small changes...see comment.

Line 
1Index: django/test/client.py
2===================================================================
3--- django/test/client.py       (revision 7354)
4+++ django/test/client.py       (working copy)
5@@ -18,6 +18,25 @@
6 BOUNDARY = 'BoUnDaRyStRiNg'
7 MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
8 
9+class FakePayload(object):
10+    """
11+    A wrapper around StringIO that restricts what can be read,
12+    since data from the network can't be seeked and cannot
13+    be read outside of its content length (or else we hang).
14+    """
15+    def __init__(self, content):
16+        self.__content = StringIO(content)
17+        self.__len = len(content)
18+
19+    def read(self, num_bytes=None):
20+        if num_bytes is None:
21+            num_bytes = self.__len or 1
22+        assert self.__len >= num_bytes, "Cannot read more than the available bytes from the HTTP incoming data."
23+        content = self.__content.read(num_bytes)
24+        self.__len -= num_bytes
25+        return content
26+
27+
28 class ClientHandler(BaseHandler):
29     """
30     A HTTP Handler that can be used for testing purposes.
31@@ -230,7 +249,7 @@
32             'CONTENT_TYPE':   content_type,
33             'PATH_INFO':      urllib.unquote(path),
34             'REQUEST_METHOD': 'POST',
35-            'wsgi.input':     StringIO(post_data),
36+            'wsgi.input':     FakePayload(post_data),
37         }
38         r.update(extra)
39 
40Index: django/http/multipartparser.py
41===================================================================
42--- django/http/multipartparser.py      (revision 0)
43+++ django/http/multipartparser.py      (revision 0)
44@@ -0,0 +1,585 @@
45+"""
46+MultiPart parsing for file uploads.
47+
48+This object will take the file upload headers
49+and the file upload handler and chunk the upload
50+data for the handler to deal with.
51+"""
52+from django.utils.datastructures import MultiValueDict
53+from django.utils.encoding import force_unicode
54+
55+__all__ = ('MultiPartParser','MultiPartParserError','InputStreamExhausted')
56+
57+class MultiPartParserError(Exception):
58+    pass
59+
60+class InputStreamExhausted(Exception):
61+    """ No more reads are allowed from this device. """
62+    pass
63+
64+class MultiPartParser(object):
65+    """
66+    A rfc2388 multipart/form-data parser.
67+
68+    parse() reads the input stream in chunk_size chunks and returns a
69+    tuple of (POST MultiValueDict, FILES MultiValueDict). If
70+    file_upload_dir is defined files will be streamed to temporary
71+    files in the specified directory.
72+    """
73+    def __init__(self, META, input_data, upload_handlers, encoding=None):
74+        """
75+        Initialize the MultiPartParser object.
76+
77+        *META* -- The standard META dictionary in Django request objects.
78+        *input_data* -- The raw post data, as a bytestring.
79+        *upload_handler* -- An object of type UploadHandler
80+                            that performs operations on the uploaded
81+                            data.
82+        *encoding* -- The encoding with which to treat the incoming data.
83+        """
84+        # Import cgi utilities for (near) future use.
85+        global parse_header, valid_boundary, settings
86+        from django.conf import settings
87+        from cgi import valid_boundary, parse_header
88+
89+        #######
90+        # Check basic headers
91+        #######
92+
93+        #
94+        # Content-Type should containt multipart and the boundary information.
95+        ####
96+
97+        content_type = META.get('HTTP_CONTENT_TYPE', META.get('CONTENT_TYPE', ''))
98+        if not content_type.startswith('multipart/'):
99+            raise MultiPartParserError('Invalid Content-Type: %s' %
100+                                       content_type)
101+
102+        # Parse the header to get the boundary to split the parts.
103+        ctypes, opts = parse_header(content_type)
104+        boundary = opts.get('boundary')
105+        if not boundary or not valid_boundary(boundary):
106+            raise MultiPartParserError('Invalid boundary in multipart: %s' %
107+                                       boundary)
108+
109+
110+        #
111+        # Content-Length should contain the length of the body we are about
112+        # to receive.
113+        ####
114+        try:
115+            content_length = int(META.get('HTTP_CONTENT_LENGTH',
116+                                          META.get('CONTENT_LENGTH',0)))
117+        except (ValueError, TypeError):
118+            # For now set it to 0...we'll try again later on down.
119+            content_length = 0
120+
121+        # If we have better knowledge of how much
122+        # data is remaining in the request stream,
123+        # we should use that. (modpython for instance)
124+        #try:
125+        #    remaining = input_data.remaining
126+        #    if remaining is not None and \
127+        #            (content_length is None or remaining < content_length):
128+        #        content_length = remaining
129+        #except AttributeError:
130+        #    pass
131+
132+        if not content_length:
133+            # This means we shouldn't continue...raise an error.
134+            raise MultiPartParserError("Invalid content length: %r" % content_length)
135+
136+        self._boundary = boundary
137+        self._input_data = input_data
138+
139+        # For compatibility with low-level network APIs (with 32-bit integers),
140+        # the chunk size should be < 2^31:
141+        self._chunk_size = min(2147483647, *[x.chunk_size for x in upload_handlers
142+                                            if x.chunk_size])
143+
144+        self._meta = META
145+        self._encoding = encoding or settings.DEFAULT_CHARSET
146+        self._content_length = content_length
147+        self._upload_handlers = upload_handlers
148+
149+    def parse(self):
150+        """
151+        Parse the POST data and break it into a FILES MultiValueDict
152+        and a POST MultiValueDict.
153+
154+           *returns* -- A tuple containing the POST and FILES dictionary,
155+                        respectively.
156+        """
157+        from django.core.files.fileuploadhandler import StopUpload, SkipFile
158+        from django.http import QueryDict
159+
160+        encoding = self._encoding
161+        handlers = self._upload_handlers
162+
163+        limited_input_data = LimitBytes(self._input_data, self._content_length)
164+
165+        # See if the handler will want to take care of the parsing.
166+        # This allows overriding everything if somebody wants it.
167+        for handler in handlers:
168+            result = handler.handle_raw_input(limited_input_data,
169+                                              self._meta,
170+                                              self._content_length,
171+                                              self._boundary,
172+                                              encoding)
173+            if result is not None:
174+                return result[0], result[1]
175+
176+        # Create the data structures to be used later.
177+        self._post = QueryDict('', mutable=True)
178+        self._files = MultiValueDict()
179+
180+        # Instantiate the parser and stream:
181+        stream = LazyStream(ChunkIter(limited_input_data, self._chunk_size))
182+        for item_type, meta_data, stream in Parser(stream, self._boundary):
183+            try:
184+                disposition = meta_data['content-disposition'][1]
185+                field_name = disposition['name'].strip()
186+            except (KeyError, IndexError, AttributeError):
187+                continue
188+
189+            transfer_encoding = meta_data.get('content-transfer-encoding')
190+
191+            field_name = force_unicode(field_name, encoding, errors='replace')
192+
193+            if item_type == 'FIELD':
194+                # This is a post field, we can just set it in the post
195+                if transfer_encoding == 'base64':
196+                    raw_data = stream.read()
197+                    try:
198+                        data = str(raw_data).decode('base64')
199+                    except:
200+                        data = raw_data
201+                else:
202+                    data = stream.read()
203+
204+                self._post.appendlist(field_name,
205+                                      force_unicode(data, encoding, errors='replace'))
206+            elif item_type == 'FILE':
207+                # This is a file, use the handler...
208+                file_successful = True
209+                file_name = self.IE_sanitize(disposition.get('filename'))
210+                if not file_name:
211+                    continue
212+
213+                file_name = force_unicode(file_name, encoding, errors='replace')
214+
215+                content_type = meta_data.get('content-type', ('',))[0].strip()
216+                try:
217+                    charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
218+                except:
219+                    charset = None
220+
221+                try:
222+                    content_length = int(meta_data.get('content-length')[0])
223+                except (IndexError, TypeError, ValueError):
224+                    content_length = None
225+
226+                counter = 0
227+                try:
228+                    for handler in handlers:
229+                        retval = handler.new_file(field_name, file_name,
230+                                                  content_type, content_length,
231+                                                  charset)
232+                        if retval:
233+                            break
234+
235+                    for chunk in stream:
236+                        if transfer_encoding == 'base64':
237+                            # We only special-case base64 transfer encoding
238+                            try:
239+                                chunk = str(chunk).decode('base64')
240+                            except Exception, e:
241+                                # Since this is only a chunk, any error is an unfixable error.
242+                                raise MultiValueParseError("Could not decode base64 data: %r" % e)
243+
244+                        chunk_length = len(chunk)
245+                        counter += chunk_length
246+                        for handler in handlers:
247+                            chunk = handler.receive_data_chunk(chunk,
248+                                                               counter - chunk_length,
249+                                                               counter)
250+                            if chunk is None:
251+                                break
252+
253+                except (StopUpload, SkipFile), e:
254+                    file_successful = False
255+                    if isinstance(e, SkipFile):
256+                        # Just use up the rest of this file...
257+                        stream.exhaust()
258+                    elif isinstance(e, StopUpload):
259+                        # Abort the parsing and break
260+                        parser.abort()
261+                        break
262+                else:
263+                    # Only do this if the handler didn't raise an abort error
264+                    for handler in handlers:
265+                        file_obj = handler.file_complete(counter)
266+                        if file_obj:
267+                            # If it returns a file object, then set the files dict.
268+                            self._files.appendlist(force_unicode(field_name,
269+                                                                 encoding,
270+                                                                 errors='replace'),
271+                                                   file_obj)
272+                            break
273+            else:
274+                # If this is neither a FIELD or a FILE, just exhaust the stream.
275+                stream.exhuast()
276+
277+        # Make sure that the request data is all fed
278+        limited_input_data.exhaust()
279+
280+        # Signal that the upload has completed.
281+        for handler in handlers:
282+            retval = handler.upload_complete()
283+            if retval:
284+                break
285+
286+        return self._post, self._files
287+
288+    def IE_sanitize(self, filename):
289+        """cleanup filename from IE full paths"""
290+        return filename and filename[filename.rfind("\\")+1:].strip()
291+
292+
293+class LazyStream(object):
294+    def __init__(self, producer, length=None):
295+        """
296+        Every LazyStream must have a producer when instantiated.
297+
298+        A producer is an iterable that returns a string each time it
299+        is called.
300+        """
301+        self._producer = producer
302+        self._empty = False
303+        self._leftover = ''
304+        self.length = length
305+        self.position = 0
306+        self._remaining = length
307+
308+    def tell(self):
309+        return self.position
310+
311+    def read(self, size=None):
312+        def parts():
313+            remaining = (size is not None and [size] or [self._remaining])[0]
314+            # do the whole thing in one shot if no limit was provided.
315+            if remaining is None:
316+                yield ''.join(self)
317+                return
318+
319+            # otherwise do some bookkeeping to return exactly enough
320+            # of the stream and stashing any extra content we get from
321+            # the producer
322+            while remaining != 0:
323+                assert remaining > 0, 'remaining bytes to read should never go negative'
324+
325+                chunk = self.next()
326+
327+                emitting = chunk[:remaining]
328+                self.unget(chunk[remaining:])
329+                remaining -= len(emitting)
330+                yield emitting
331+
332+        out = ''.join(parts())
333+        self.position += len(out)
334+        return out
335+
336+    def next(self):
337+        """
338+        Used when the exact number of bytes to read is unimportant.
339+
340+        This procedure just returns whatever is chunk is conveniently
341+        returned from the iterator instead. Useful to avoid
342+        unnecessary bookkeeping if performance is an issue.
343+        """
344+        if self._leftover:
345+            output = self._leftover
346+            self.position += len(output)
347+            self._leftover = ''
348+            return output
349+        else:
350+            output = self._producer.next()
351+            self.position += len(output)
352+            return output
353+
354+    def close(self):
355+        """
356+        Used to invalidate/disable this lazy stream.
357+
358+        Replaces the producer with an empty list. Any leftover bytes
359+        that have already been read will still be reported upon read()
360+        and/or next().
361+        """
362+        self._producer = []
363+
364+    def __iter__(self):
365+        return self
366+
367+    def unget(self, bytes):
368+        """
369+        Places bytes back onto the front of the lazy stream.
370+
371+        Future calls to read() will return those bytes first. The
372+        stream position and thus tell() will be rewound.
373+        """
374+        self.position -= len(bytes)
375+        self._leftover = ''.join([bytes, self._leftover])
376+
377+    def exhaust(self):
378+        """
379+        Exhausts the entire underlying stream.
380+
381+        Useful for skipping and advancing sections.
382+        """
383+        for thing in self:
384+            pass
385+
386+
387+class ChunkIter(object):
388+    def __init__(self, flo, chunk_size=1024**2):
389+        self.flo = flo
390+        self.chunk_size = chunk_size
391+
392+    def next(self):
393+        try:
394+            data = self.flo.read(self.chunk_size)
395+        except InputStreamExhausted:
396+            raise StopIteration
397+        if data:
398+            return data
399+        else:
400+            raise StopIteration
401+
402+    def __iter__(self):
403+        return self
404+
405+
406+class LimitBytes(object):
407+    """ Limit bytes for a file object. """
408+    def __init__(self, fileobject, length):
409+        self._file = fileobject
410+        self.remaining = length
411+
412+    def read(self, num_bytes=None):
413+        """
414+        Read data from the underlying file.
415+        If you ask for too much or there isn't anything left,
416+        this will raise an InputStreamExhausted error.
417+        """
418+        if self.remaining <= 0:
419+            raise InputStreamExhausted()
420+        if num_bytes is None:
421+            num_bytes = self.remaining
422+        else:
423+            num_bytes = min(num_bytes, self.remaining)
424+        self.remaining -= num_bytes
425+        return self._file.read(num_bytes)
426+
427+    def exhaust(self):
428+        """
429+        Exhaust this file until all of the bytes it was limited by
430+        have been read.
431+        """
432+        while self.remaining > 0:
433+            num_bytes = min(self.remaining, 16384)
434+            __ = self._file.read(num_bytes)
435+            self.remaining -= num_bytes
436+
437+
438+class InterBoundaryIter(object):
439+    """
440+    A Producer that will iterate over boundaries.
441+    """
442+    def __init__(self, stream, boundary):
443+        self._stream = stream
444+        self._boundary = boundary
445+
446+    def __iter__(self):
447+        return self
448+
449+    def next(self):
450+        try:
451+            return LazyStream(BoundaryIter(self._stream, self._boundary))
452+        except InputStreamExhausted:
453+            raise StopIteration
454+
455+class BoundaryIter(object):
456+    """
457+    A Producer that is sensitive to boundaries.
458+
459+    Will happily yield bytes until a boundary is found. Will yield the
460+    bytes before the boundary, throw away the boundary bytes
461+    themselves, and push the post-boundary bytes back on the stream.
462+
463+    The future calls to .next() after locating the boundary will raise
464+    a StopIteration exception.
465+    """
466+    def __init__(self, stream, boundary):
467+        self._stream = stream
468+        self._boundary = boundary
469+        self._done = False
470+        # rollback an additional six bytes because the format is like
471+        # this: CRLF<boundary>[--CRLF]
472+        self._rollback = len(boundary) + 6
473+
474+        # Try to use mx fast string search if available. Otherwise
475+        # use Python find. Wrap the latter for consistency.
476+        unused_char = self._stream.read(1)
477+        if not unused_char:
478+            raise InputStreamExhausted
479+        self._stream.unget(unused_char)
480+        try:
481+            from mx.TextTools import FS
482+            self._fs = FS(boundary).find
483+        except ImportError:
484+            self._fs = lambda data: data.find(boundary)
485+
486+    def __iter__(self):
487+        return self
488+
489+    def next(self):
490+        if self._done:
491+            raise StopIteration
492+
493+        stream = self._stream
494+        rollback = self._rollback
495+
496+        bytes_read = 0
497+        chunks = []
498+        for bytes in stream:
499+            bytes_read += len(bytes)
500+            chunks.append(bytes)
501+            if bytes_read > rollback:
502+                break
503+            if not bytes:
504+                break
505+        else:
506+            self._done = True
507+
508+        if not chunks:
509+            raise StopIteration
510+
511+        chunk = ''.join(chunks)
512+
513+        boundary = self._find_boundary(chunk, len(chunk) < self._rollback)
514+
515+
516+        if boundary:
517+            end, next = boundary
518+            stream.unget(chunk[next:])
519+            self._done = True
520+            return chunk[:end]
521+        else:
522+            # make sure we dont treat a partial boundary (and
523+            # its separators) as data
524+            if not chunk[:-rollback]:# and len(chunk) >= (len(self._boundary) + 6):
525+                # There's nothing left, we should just return and mark as done.
526+                self._done = True
527+                return chunk
528+            else:
529+                stream.unget(chunk[-rollback:])
530+                return chunk[:-rollback]
531+
532+    def _find_boundary(self, data, eof = False):
533+        """
534+        Finds a multipart boundary in data.
535+
536+        Should no boundry exist in the data None is returned
537+        instead. Otherwise a tuple containing
538+        the indices of the following are returned:
539+
540+         * the end of current encapsulation
541+
542+         * the start of the next encapsulation
543+        """
544+        index = self._fs(data)
545+        if index < 0:
546+            return None
547+        else:
548+            end = index
549+            next = index + len(self._boundary)
550+            data_len = len(data) - 1
551+            # backup over CRLF
552+            if data[max(0,end-1)] == '\n': end -= 1
553+            if data[max(0,end-1)] == '\r': end -= 1
554+            # skip over --CRLF
555+            if data[min(data_len,next)] == '-': next += 1
556+            if data[min(data_len,next)] == '-': next += 1
557+            if data[min(data_len,next)] == '\r': next += 1
558+            if data[min(data_len,next)] == '\n': next += 1
559+            return end, next
560+
561+def ParseBoundaryStream(stream, max_header_size):
562+        """
563+        Parses one and exactly one stream that encapsulates a boundary.
564+        """
565+        # Stream at beginning of header, look for end of header
566+        # and parse it if found. The header must fit within one
567+        # chunk.
568+        chunk = stream.read(max_header_size)
569+        # 'find' returns the top of these four bytes, so we'll
570+        # need to munch them later to prevent them from polluting
571+        # the payload.
572+        header_end = chunk.find('\r\n\r\n')
573+
574+        def parse_header(line):
575+            from cgi import parse_header
576+            main_value_pair, params = parse_header(line)
577+            try:
578+                name, value = main_value_pair.split(':', 1)
579+            except:
580+                raise ValueError("Invalid header: %r" % line)
581+            return name, (value, params)
582+
583+        if header_end == -1:
584+            # we find no header, so we just mark this fact and pass on
585+            # the stream verbatim
586+            stream.unget(chunk)
587+            return ('RAW', {}, stream)
588+
589+        header = chunk[:header_end]
590+
591+        # here we place any excess chunk back onto the stream, as
592+        # well as throwing away the CRLFCRLF bytes from above.
593+        stream.unget(chunk[header_end + 4:])
594+
595+        is_file_field = False
596+        outdict = {}
597+
598+        # eliminate blank lines
599+        for line in header.split('\r\n'):
600+            # This terminology ("main value" and "dictionary of
601+            # parameters") is from the Python docs.
602+            name, (value, params) = parse_header(line)
603+            if name == 'content-disposition' and params.get('filename'):
604+                is_file_field = True
605+
606+            outdict[name] = value, params
607+
608+        if is_file_field:
609+            return ('FILE', outdict, stream)
610+        else:
611+            return ('FIELD', outdict, stream)
612+
613+
614+class Parser(object):
615+    def __init__(self, stream, boundary):
616+        self._stream = stream
617+        self._separator = '--' + boundary
618+
619+    def __iter__(self):
620+
621+        boundarystream = InterBoundaryIter(self._stream,
622+                                           self._separator)
623+
624+        for sub_stream in boundarystream:
625+            # Iterate over each part
626+            yield ParseBoundaryStream(sub_stream, 1024)
627+
628+
629+
630Index: django/http/__init__.py
631===================================================================
632--- django/http/__init__.py     (revision 7354)
633+++ django/http/__init__.py     (working copy)
634@@ -11,7 +11,7 @@
635 
636 from django.utils.datastructures import MultiValueDict, FileDict
637 from django.utils.encoding import smart_str, iri_to_uri, force_unicode
638-
639+from django.http.multipartparser import MultiPartParser
640 from utils import *
641 
642 RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
643@@ -30,6 +30,7 @@
644         self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
645         self.path = ''
646         self.method = None
647+        self._upload_handlers = []
648 
649     def __repr__(self):
650         return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
651@@ -102,40 +103,35 @@
652 
653     encoding = property(_get_encoding, _set_encoding)
654 
655-def parse_file_upload(header_dict, post_data):
656-    """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
657-    import email, email.Message
658-    from cgi import parse_header
659-    raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
660-    raw_message += '\r\n\r\n' + post_data
661-    msg = email.message_from_string(raw_message)
662-    POST = QueryDict('', mutable=True)
663-    FILES = MultiValueDict()
664-    for submessage in msg.get_payload():
665-        if submessage and isinstance(submessage, email.Message.Message):
666-            name_dict = parse_header(submessage['Content-Disposition'])[1]
667-            # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
668-            # or {'name': 'blah'} for POST fields
669-            # We assume all uploaded files have a 'filename' set.
670-            if 'filename' in name_dict:
671-                assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
672-                if not name_dict['filename'].strip():
673-                    continue
674-                # IE submits the full path, so trim everything but the basename.
675-                # (We can't use os.path.basename because that uses the server's
676-                # directory separator, which may not be the same as the
677-                # client's one.)
678-                filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
679-                FILES.appendlist(name_dict['name'], FileDict({
680-                    'filename': filename,
681-                    'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None,
682-                    'content': submessage.get_payload(),
683-                }))
684-            else:
685-                POST.appendlist(name_dict['name'], submessage.get_payload())
686-    return POST, FILES
687+    def _set_upload_handlers(self, upload_handlers):
688+        """
689+        Set the upload handler to the new handler given in the parameter.
690+        """
691+        if hasattr(self, '_files'):
692+            raise AttributeError("You cannot set the upload handler after the upload has been processed.")
693+        self._upload_handlers = upload_handlers
694 
695+    def _get_upload_handlers(self):
696+        return self._upload_handlers
697 
698+    upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
699+
700+
701+    def parse_file_upload(self, META, post_data):
702+        """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
703+        from django.core.files.fileuploadhandler import TemporaryFileUploadHandler, MemoryFileUploadHandler
704+        if not self.upload_handlers:
705+            # Order here is *very* important.
706+            self.upload_handlers = (MemoryFileUploadHandler(),
707+                                    TemporaryFileUploadHandler())
708+        else:
709+            self.upload_handlers = tuple(self.upload_handlers)
710+
711+        parser = MultiPartParser(META, post_data, self.upload_handlers,
712+                                 self.encoding)
713+        return parser.parse()
714+
715+
716 class QueryDict(MultiValueDict):
717     """
718     A specialized MultiValueDict that takes a query string when initialized.
719Index: django/oldforms/__init__.py
720===================================================================
721--- django/oldforms/__init__.py (revision 7354)
722+++ django/oldforms/__init__.py (working copy)
723@@ -680,18 +680,23 @@
724         self.field_name, self.is_required = field_name, is_required
725         self.validator_list = [self.isNonEmptyFile] + validator_list
726 
727-    def isNonEmptyFile(self, field_data, all_data):
728-        try:
729-            content = field_data['content']
730-        except TypeError:
731-            raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.")
732-        if not content:
733+    def isNonEmptyFile(self, new_data, all_data):
734+        if hasattr(new_data, 'upload_errors'):
735+            upload_errors = new_data.upload_errors()
736+            if upload_errors:
737+                raise validators.CriticalValidationError, upload_errors
738+        if not new_data.file_size:
739             raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
740 
741     def render(self, data):
742         return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
743             (self.get_id(), self.__class__.__name__, self.field_name))
744 
745+    def prepare(self, new_data):
746+        if hasattr(new_data, 'upload_errors'):
747+            upload_errors = new_data.upload_errors()
748+            new_data[self.field_name] = { '_file_upload_error': upload_errors }
749+
750     def html2python(data):
751         if data is None:
752             raise EmptyValue
753Index: django/db/models/base.py
754===================================================================
755--- django/db/models/base.py    (revision 7354)
756+++ django/db/models/base.py    (working copy)
757@@ -13,6 +13,7 @@
758 from django.utils.datastructures import SortedDict
759 from django.utils.functional import curry
760 from django.utils.encoding import smart_str, force_unicode, smart_unicode
761+from django.core.files.filemove import file_move_safe
762 from django.conf import settings
763 from itertools import izip
764 import types
765@@ -384,12 +385,16 @@
766     def _get_FIELD_size(self, field):
767         return os.path.getsize(self._get_FIELD_filename(field))
768 
769-    def _save_FIELD_file(self, field, filename, raw_contents, save=True):
770+    def _save_FIELD_file(self, field, filename, raw_field, save=True):
771         directory = field.get_directory_name()
772         try: # Create the date-based directory if it doesn't exist.
773             os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
774         except OSError: # Directory probably already exists.
775             pass
776+
777+        if filename is None:
778+            filename = raw_field.file_name
779+
780         filename = field.get_filename(filename)
781 
782         # If the filename already exists, keep adding an underscore to the name of
783@@ -406,9 +411,18 @@
784         setattr(self, field.attname, filename)
785 
786         full_filename = self._get_FIELD_filename(field)
787-        fp = open(full_filename, 'wb')
788-        fp.write(raw_contents)
789-        fp.close()
790+        if hasattr(raw_field, 'temporary_file_path'):
791+            raw_field.close()
792+            file_move_safe(raw_field.temporary_file_path(), full_filename)
793+        else:
794+            from django.core.files import filelocks
795+            fp = open(full_filename, 'wb')
796+            # exclusive lock
797+            filelocks.lock(fp, filelocks.LOCK_EX)
798+            # Stream it into the file, from where it is.
799+            for chunk in raw_field.chunk(65535):
800+                fp.write(chunk)
801+            fp.close()
802 
803         # Save the width and/or height, if applicable.
804         if isinstance(field, ImageField) and (field.width_field or field.height_field):
805Index: django/db/models/fields/__init__.py
806===================================================================
807--- django/db/models/fields/__init__.py (revision 7354)
808+++ django/db/models/fields/__init__.py (working copy)
809@@ -785,7 +785,8 @@
810         setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
811         setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
812         setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
813-        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
814+        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
815+        setattr(cls, 'move_%s_file' % self.name, lambda instance, raw_field, save=True: instance._save_FIELD_file(self, None, raw_field, save))
816         dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
817 
818     def delete_file(self, instance):
819@@ -808,9 +809,9 @@
820         if new_data.get(upload_field_name, False):
821             func = getattr(new_object, 'save_%s_file' % self.name)
822             if rel:
823-                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
824+                func(new_data[upload_field_name][0].file_name, new_data[upload_field_name][0], save)
825             else:
826-                func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
827+                func(new_data[upload_field_name].file_name, new_data[upload_field_name], save)
828 
829     def get_directory_name(self):
830         return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
831@@ -823,7 +824,7 @@
832     def save_form_data(self, instance, data):
833         from django.newforms.fields import UploadedFile
834         if data and isinstance(data, UploadedFile):
835-            getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)
836+            getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False)
837 
838     def formfield(self, **kwargs):
839         defaults = {'form_class': forms.FileField}
840Index: django/conf/global_settings.py
841===================================================================
842--- django/conf/global_settings.py      (revision 7354)
843+++ django/conf/global_settings.py      (working copy)
844@@ -224,6 +224,11 @@
845 # Example: "http://media.lawrence.com"
846 MEDIA_URL = ''
847 
848+# Directory to upload streamed files temporarily.
849+# A value of `None` means that it will use the default temporary
850+# directory for the server's operating system.
851+FILE_UPLOAD_TEMP_DIR = None
852+
853 # Default formatting for date objects. See all available format strings here:
854 # http://www.djangoproject.com/documentation/templates/#now
855 DATE_FORMAT = 'N j, Y'
856Index: django/core/handlers/wsgi.py
857===================================================================
858--- django/core/handlers/wsgi.py        (revision 7354)
859+++ django/core/handlers/wsgi.py        (working copy)
860@@ -78,6 +78,7 @@
861         self.path = force_unicode(environ['PATH_INFO'])
862         self.META = environ
863         self.method = environ['REQUEST_METHOD'].upper()
864+        self._upload_handlers = []
865 
866     def __repr__(self):
867         # Since this is called as part of error handling, we need to be very
868@@ -112,9 +113,8 @@
869         # Populates self._post and self._files
870         if self.method == 'POST':
871             if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
872-                header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
873-                header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
874-                self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
875+                self._raw_post_data = ''
876+                self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
877             else:
878                 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
879         else:
880Index: django/core/handlers/modpython.py
881===================================================================
882--- django/core/handlers/modpython.py   (revision 7354)
883+++ django/core/handlers/modpython.py   (working copy)
884@@ -16,6 +16,7 @@
885     def __init__(self, req):
886         self._req = req
887         self.path = force_unicode(req.uri)
888+        self._upload_handlers = []
889 
890     def __repr__(self):
891         # Since this is called as part of error handling, we need to be very
892@@ -53,7 +54,8 @@
893     def _load_post_and_files(self):
894         "Populates self._post and self._files"
895         if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
896-            self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
897+            self._raw_post_data = ''
898+            self._post, self._files = self.parse_file_upload(self.META, self._req)
899         else:
900             self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
901 
902Index: django/core/files/filelocks.py
903===================================================================
904--- django/core/files/filelocks.py      (revision 0)
905+++ django/core/files/filelocks.py      (revision 0)
906@@ -0,0 +1,68 @@
907+"""
908+Locking portability based partially on example by
909+Jonathan Feignberg <jdf@pobox.com> in python cookbook.
910+
911+Example Usage::
912+
913+    from django.utils import file_locks
914+
915+    f = open('./file', 'wb')
916+
917+    file_locks.lock(f, file_locks.LOCK_EX)
918+    f.write('Django')
919+    f.close()
920+"""
921+
922+__all__ = ('LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock')
923+
924+system_type = None
925+
926+try:
927+       import win32con
928+       import win32file
929+       import pywintypes
930+       LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
931+       LOCK_SH = 0
932+       LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
933+       __overlapped = pywintypes.OVERLAPPED()
934+       system_type = 'nt'
935+except (ImportError, AttributeError):
936+       pass
937+
938+try:
939+       import fcntl
940+       LOCK_EX = fcntl.LOCK_EX
941+       LOCK_SH = fcntl.LOCK_SH
942+       LOCK_NB = fcntl.LOCK_NB
943+       system_type = 'posix'
944+except (ImportError, AttributeError):
945+       pass
946+
947+
948+
949+if system_type == 'nt':
950+       def lock(file, flags):
951+               hfile = win32file._get_osfhandle(file.fileno())
952+               win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
953+
954+       def unlock(file):
955+               hfile = win32file._get_osfhandle(file.fileno())
956+               win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
957+
958+elif system_type =='posix':
959+       def lock(file, flags):
960+               fcntl.flock(file.fileno(), flags)
961+
962+       def unlock(file):
963+               fcntl.flock(file.fileno(), fcntl.LOCK_UN)
964+
965+else:
966+       # File locking is not supported.
967+       LOCK_EX = LOCK_SH = LOCK_NB = None
968+       
969+       # Dummy functions that don't do anything.
970+       def lock(file, flags):
971+               pass
972+
973+       def unlock(file):
974+               pass
975Index: django/core/files/uploadedfile.py
976===================================================================
977--- django/core/files/uploadedfile.py   (revision 0)
978+++ django/core/files/uploadedfile.py   (revision 0)
979@@ -0,0 +1,191 @@
980+"""
981+The uploaded file objects for Django.
982+This contains the base UploadedFile and the TemporaryUploadedFile
983+derived class.
984+"""
985+
986+__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile')
987+
988+class UploadedFile(object):
989+    """
990+    The UploadedFile object behaves somewhat like a file
991+    object and represents some data that the user submitted
992+    and is stored in some form.
993+    """
994+    DEFAULT_CHUNK_SIZE = 64 * 2**10
995+
996+    def __init__(self):
997+        self.file_size = None
998+        self.file_name = None
999+        self.content_type = None
1000+        self.charset = None
1001+        pass
1002+
1003+    def file_size(self):
1004+        return self.file_size
1005+
1006+    def chunk(self, chunk_size=None):
1007+        """
1008+        Read the file to generate chunks of chunk_size bytes.
1009+        """
1010+        if not chunk_size:
1011+            chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
1012+
1013+        if hasattr(self, 'seek'):
1014+            self.seek(0)
1015+        # Assume the pointer is at zero...
1016+        counter = self.file_size()
1017+
1018+        while counter > 0:
1019+            yield self.read(chunk_size)
1020+            counter -= chunk_size
1021+
1022+
1023+    def multiple_chunks(self, chunk_size=None):
1024+        """
1025+        Return True if you can expect multiple chunks, False otherwise.
1026+        Note: If a particular file representation is in memory, then
1027+              override this to return False.
1028+        """
1029+        if not chunk_size:
1030+            chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
1031+        return self.file_size() < chunk_size
1032+       
1033+
1034+    def read(self, num_bytes=None):
1035+        """
1036+        Read from the file in whatever representation it has.
1037+        """
1038+        raise NotImplementedError()
1039+
1040+    def open(self):
1041+        """
1042+        Open the file, if one needs to.
1043+        """
1044+        pass
1045+
1046+
1047+    def close(self):
1048+        """
1049+        Close the file, if one needs to.
1050+        """
1051+        pass
1052+
1053+    def __getitem__(self, key):
1054+        """
1055+        This maintains backwards compatibility.
1056+        """
1057+        import warnings
1058+        warnings.warn("The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.", DeprecationWarning)
1059+        # Dictionary to translate labels
1060+        # for backwards compatbility.
1061+        # Should be removed at some point.
1062+        backwards_translate = {
1063+            'filename': 'file_name',
1064+            'content-type': 'content_type',
1065+            }
1066+
1067+        if key == 'content':
1068+            return self.read()
1069+        else:
1070+            return getattr(self, backwards_translate.get(key, key))
1071+
1072+    def __repr__(self):
1073+        """
1074+        This representation could be anything and can be overridden.
1075+        This is mostly done to make it look somewhat useful.
1076+        """
1077+        _dict = {
1078+            'file_name': self.file_name,
1079+            'content_type': self.content_type,
1080+            'content': '<omitted>',
1081+            }
1082+        return repr(_dict)
1083+
1084+
1085+class TemporaryUploadedFile(UploadedFile):
1086+    """
1087+    Upload a file to a temporary file.
1088+    """
1089+
1090+    def __init__(self, file, file_name, content_type, file_size, charset):
1091+        self.file = file
1092+        self.file_name = file_name
1093+        self.path = file.name
1094+        self.content_type = content_type
1095+        self.file_size = file_size
1096+        self.charset = charset
1097+        self.file.seek(0)
1098+
1099+    def temporary_file_path(self):
1100+        """
1101+        Return the full path of this file.
1102+        """
1103+        return self.path
1104+
1105+    def read(self, *args, **kwargs):
1106+        return self.file.read(*args, **kwargs)
1107+
1108+    def open(self):
1109+        """
1110+        Assume the person meant to seek.
1111+        """
1112+        self.seek(0)
1113+
1114+    def seek(self, *args, **kwargs):
1115+        self.file.seek(*args, **kwargs)
1116+
1117+
1118+class InMemoryUploadedFile(UploadedFile):
1119+    """
1120+    Upload a file into memory.
1121+    """
1122+    def __init__(self, file, field_name, file_name, content_type, charset):
1123+        self.file = file
1124+        self.field_name = field_name
1125+        self.file_name = file_name
1126+        self.content_type = content_type
1127+        self.charset = charset
1128+        self.file.seek(0)
1129+
1130+    def seek(self, *args, **kwargs):
1131+        self.file.seek(*args, **kwargs)
1132+
1133+    def open(self):
1134+        self.seek(0)
1135+
1136+    def read(self, *args, **kwargs):
1137+        return self.file.read(*args, **kwargs)
1138+
1139+    def chunk(self, chunk_size=None):
1140+        """
1141+        Return the entirety of the data regardless.
1142+        """
1143+        self.file.seek(0)
1144+        return self.read()
1145+
1146+    def multiple_chunks(self, chunk_size=None):
1147+        """
1148+        Since it's in memory, we'll never have multiple chunks.
1149+        """
1150+        return False
1151+
1152+
1153+class SimpleUploadedFile(InMemoryUploadedFile):
1154+    """
1155+    A simple representation of a file, which
1156+    just has content, size, and a name.
1157+    """
1158+    def __init__(self, name, content, content_type='text/plain'):
1159+        try:
1160+            from cStringIO import StringIO
1161+        except ImportError:
1162+            from StringIO import StringIO
1163+        self.file = StringIO(content or '')
1164+        self.file_name = name
1165+        self.field_name = None
1166+        self.file_size = len(content or '')
1167+        self.content_type = content_type
1168+        self.charset = None
1169+        self.file.seek(0)
1170+
1171Index: django/core/files/__init__.py
1172===================================================================
1173--- django/core/files/__init__.py       (revision 0)
1174+++ django/core/files/__init__.py       (revision 0)
1175@@ -0,0 +1 @@
1176+
1177Index: django/core/files/fileuploadhandler.py
1178===================================================================
1179--- django/core/files/fileuploadhandler.py      (revision 0)
1180+++ django/core/files/fileuploadhandler.py      (revision 0)
1181@@ -0,0 +1,243 @@
1182+""" A fileuploadhandler base and default subclass for handling file uploads.
1183+"""
1184+import os
1185+try:
1186+    from cStringIO import StringIO
1187+except ImportError:
1188+    from StringIO import StringIO
1189+
1190+from django.utils.encoding import force_unicode
1191+from django.utils.datastructures import MultiValueDict
1192+
1193+from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
1194+
1195+__all__ = ('UploadFileException','StopUpload', 'SkipFile',
1196+           'FileUploadHandler', 'TemporaryFileUploadHandler',
1197+           'MemoryFileUploadHandler')
1198+
1199+
1200+class UploadFileException(Exception):
1201+    """ Any error having to do with Uploading Files. """
1202+    pass
1203+
1204+class StopUpload(UploadFileException):
1205+    """ This exception is raised when an upload must abort. """
1206+    pass
1207+
1208+class SkipFile(UploadFileException):
1209+    """ This exception is raised when a file needs to be skipped. """
1210+    pass
1211+
1212+
1213+class FileUploadHandler(object):
1214+    """ FileUploadHandler will take data and handle file uploads
1215+    in a streamed fashion.
1216+    """
1217+    chunk_size = 64 * 2 ** 10 #: The default chunk size is 64 KB.
1218+
1219+    def __init__(self):
1220+        " Initialize some local variables. "
1221+        self.file_name = None
1222+        self.content_type = None
1223+        self.content_length = None
1224+        self.charset = None
1225+
1226+    def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
1227+        """
1228+        Handle the raw input from the client.
1229+        Parameters:
1230+          *input_data* -- An object that supports reading via .read().
1231+          *content_length* -- The (integer) value of the Content-Length header from the client.
1232+          *boundary* -- The boundary from the Content-Type header. Be sure to prepend two '--'.
1233+        """
1234+        pass
1235+
1236+    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
1237+        """
1238+        Signal that a new file has been started.
1239+       
1240+        Warning: Do not trust content_length, if you get it at all.
1241+        """
1242+        self.field_name = field_name
1243+        self.file_name = file_name
1244+        self.content_type = content_type
1245+        self.content_length = content_length
1246+        self.charset = charset
1247+
1248+    def receive_data_chunk(self, raw_data, start, stop):
1249+        """
1250+        Receive data from the streamed upload parser.
1251+        Start and stop are the positions in the file.
1252+        This equality should always be true::
1253+            len(raw_data) = stop - start
1254+        """
1255+        raise NotImplementedError()
1256+
1257+    def file_complete(self, file_size):
1258+        """
1259+        Signal that a file has completed.
1260+        File size corresponds to the actual size accumulated
1261+        by all the chunks.
1262+
1263+        This should return a valid UploadedFile object.
1264+        """
1265+        raise NotImplementedError()
1266+
1267+    def upload_complete(self):
1268+        """
1269+        Signal that the upload is complete.
1270+        Do any cleanup that is necessary for this handler.
1271+        """
1272+        pass
1273+
1274+
1275+
1276+class TemporaryFileUploadHandler(FileUploadHandler):
1277+    """
1278+    Upload the streaming data into a temporary file.
1279+    """
1280+    def __init__(self, *args, **kwargs):
1281+        """ Import settings for later. """
1282+        super(TemporaryFileUploadHandler, self).__init__(*args, **kwargs)
1283+        global settings
1284+        from django.conf import settings
1285+
1286+    def new_file(self, file_name, *args, **kwargs):
1287+        """
1288+        Create the file object to append to as data is coming in.
1289+        """
1290+        super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs)
1291+        self.file = TemporaryFile(settings.FILE_UPLOAD_TEMP_DIR)
1292+        self.write = self.file.write
1293+
1294+    def receive_data_chunk(self, raw_data, start, stop):
1295+        """
1296+        Once we get the data, we will save it to our file.
1297+        """
1298+        self.write(raw_data)
1299+
1300+    def file_complete(self, file_size):
1301+        """
1302+        Signal that a file has completed.
1303+        File size corresponds to the actual size accumulated
1304+        by all the chunks.
1305+
1306+        This should return a valid UploadedFile object.
1307+        """
1308+        self.file.seek(0)
1309+        return TemporaryUploadedFile(self.file, self.file_name,
1310+                                     self.content_type, file_size,
1311+                                     self.charset)
1312+
1313+
1314+class TemporaryFile(object):
1315+    """
1316+    A temporary file that tries to delete itself when garbage collected.
1317+    """
1318+    def __init__(self, dir):
1319+        import tempfile
1320+        if not dir:
1321+            dir = tempfile.gettempdir()
1322+        try:
1323+            (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir)
1324+            self.file = os.fdopen(fd, 'w+b')
1325+        except (OSError, IOError):
1326+            raise OSError, "Could not create temporary file for uploading, have you set settings.FILE_UPLOAD_TEMP_DIR correctly?"
1327+        self.name = name
1328+
1329+    def __getattr__(self, name):
1330+        a = getattr(self.__dict__['file'], name)
1331+        if type(a) != type(0):
1332+            setattr(self, name, a)
1333+        return a
1334+
1335+    def __del__(self):
1336+        try:
1337+            os.unlink(self.name)
1338+        except OSError:
1339+            pass
1340+
1341+
1342+class MemoryFileUploadHandler(FileUploadHandler):
1343+    """
1344+    The MemoryFileUploadHandler will place the data directly into memory.
1345+    """
1346+
1347+    def __init__(self):
1348+        pass
1349+
1350+
1351+    def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
1352+        """
1353+        Parse the input data in-memory.
1354+        """
1355+        if content_length > 2621440:
1356+            # If the post is greater than 2.5 MB, do nothing.
1357+            return
1358+
1359+        from django.http import QueryDict
1360+        import email, email.Message
1361+        from cgi import parse_header
1362+
1363+        #####
1364+        # Get the headers from the META information.
1365+        headers = []
1366+        if 'HTTP_CONTENT_TYPE' not in META:
1367+            headers.append('Content-Type: %s' % (META.get('CONTENT_TYPE', '')))
1368+
1369+        if 'HTTP_CONTENT_LENGTH' not in META:
1370+            headers.append('Content-Length: %s' % (META.get('CONTENT_LENGTH', '0')))
1371+
1372+        for key, value in META.items():
1373+            if key.startswith('HTTP_'):
1374+                headers.append('%s: %s' % (key[5:].replace('_','-').title(), value))
1375+
1376+        raw_message = '\r\n'.join(headers)
1377+        raw_message += '\r\n\r\n' + input_data.read()
1378+
1379+        msg = email.message_from_string(raw_message)
1380+        POST = QueryDict('', mutable=True)
1381+        FILES = MultiValueDict()
1382+        for submessage in msg.get_payload():
1383+            if submessage and isinstance(submessage, email.Message.Message):
1384+                name_dict = parse_header(submessage['Content-Disposition'])[1]
1385+                field_name = force_unicode(name_dict['name'], encoding, errors='replace')
1386+
1387+                if 'filename' in name_dict:
1388+                    assert not isinstance(submessage.get_payload(), list), "Nested MIME messages are not supported"
1389+                    if not name_dict['filename'].strip():
1390+                        continue
1391+
1392+                    filename = force_unicode(name_dict['filename'][name_dict['filename'].rfind("\\")+1:],
1393+                                             encoding, errors='replace')
1394+                    content_type = 'Content-Type' in submessage and submessage['Content-Type'] or None
1395+
1396+                    file_obj = InMemoryUploadedFile(StringIO(submessage.get_payload()),
1397+                                                    field_name, filename, content_type, None)
1398+
1399+                    FILES.appendlist(field_name, file_obj)
1400+                else:
1401+                    content = force_unicode(submessage.get_payload(), encoding, errors='replace')
1402+                    POST.appendlist(field_name, content)
1403+
1404+        return POST, FILES
1405+
1406+
1407+    def new_file(self, field_name, file_name, content_type, content_length, charset):
1408+        """
1409+        Do Nothing.
1410+        """
1411+        return
1412+
1413+    def receive_data_chunk(self, raw_data, start, stop):
1414+        """
1415+        Do nothing.
1416+        """
1417+        return raw_data
1418+
1419+    def file_complete(self, file_size):
1420+        """
1421+        Do nothing.
1422+        """
1423+        return
1424+
1425Index: django/core/files/filemove.py
1426===================================================================
1427--- django/core/files/filemove.py       (revision 0)
1428+++ django/core/files/filemove.py       (revision 0)
1429@@ -0,0 +1,52 @@
1430+import os
1431+
1432+__all__ = ('file_move_safe',)
1433+
1434+try:
1435+    import shutil
1436+    file_move = shutil.move
1437+except ImportError:
1438+    file_move = os.rename
1439+
1440+def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
1441+    """
1442+    Moves a file from one location to another in the safest way possible.
1443+   
1444+    First, it tries using shutils.move, which is OS-dependent but doesn't
1445+    break with change of filesystems. Then it tries os.rename, which will
1446+    break if it encounters a change in filesystems. Lastly, it streams
1447+    it manually from one file to another in python.
1448+
1449+    Without ``allow_overwrite``, if the destination file exists, the
1450+    file will raise an IOError.
1451+    """
1452+
1453+    from django.core.files import filelocks
1454+
1455+    if old_file_name == new_file_name:
1456+        # No file moving takes place.
1457+        return
1458+
1459+    if not allow_overwrite and os.path.exists(new_file_name):
1460+        raise IOError, "Django does not allow overwriting files."
1461+
1462+    try:
1463+        file_move(old_file_name, new_file_name)
1464+        return
1465+    except OSError: # moving to another filesystem
1466+        pass
1467+
1468+    new_file = open(new_file_name, 'wb')
1469+    # exclusive lock
1470+    filelocks.lock(new_file, filelocks.LOCK_EX)
1471+    old_file = open(old_file_name, 'rb')
1472+    current_chunk = None
1473+
1474+    while current_chunk != '':
1475+        current_chunk = old_file.read(chunk_size)
1476+        new_file.write(current_chunk)
1477+
1478+    new_file.close()
1479+    old_file.close()
1480+
1481+    os.remove(old_file_name)
1482Index: django/newforms/fields.py
1483===================================================================
1484--- django/newforms/fields.py   (revision 7354)
1485+++ django/newforms/fields.py   (working copy)
1486@@ -416,9 +416,9 @@
1487 
1488 class UploadedFile(StrAndUnicode):
1489     "A wrapper for files uploaded in a FileField"
1490-    def __init__(self, filename, content):
1491+    def __init__(self, filename, data):
1492         self.filename = filename
1493-        self.content = content
1494+        self.data = data
1495 
1496     def __unicode__(self):
1497         """
1498@@ -445,12 +445,10 @@
1499         elif not data and initial:
1500             return initial
1501         try:
1502-            f = UploadedFile(data['filename'], data['content'])
1503-        except TypeError:
1504+            f = UploadedFile(data.file_name, data)
1505+        except (TypeError, AttributeError):
1506             raise ValidationError(self.error_messages['invalid'])
1507-        except KeyError:
1508-            raise ValidationError(self.error_messages['missing'])
1509-        if not f.content:
1510+        if not f.data.file_size:
1511             raise ValidationError(self.error_messages['empty'])
1512         return f
1513 
1514@@ -470,15 +468,26 @@
1515         elif not data and initial:
1516             return initial
1517         from PIL import Image
1518-        from cStringIO import StringIO
1519+
1520+        # We need to get the file, it either has a path
1521+        # or we have to read it all into memory...
1522+        if hasattr(data, 'temporary_file_path'):
1523+            file = data.temporary_file_path()
1524+        else:
1525+            try:
1526+                from cStringIO import StringIO
1527+            except ImportError:
1528+                from StringIO import StringIO
1529+            file = StringIO(data.read())
1530+
1531         try:
1532             # load() is the only method that can spot a truncated JPEG,
1533             #  but it cannot be called sanely after verify()
1534-            trial_image = Image.open(StringIO(f.content))
1535+            trial_image = Image.open(file)
1536             trial_image.load()
1537             # verify() is the only method that can spot a corrupt PNG,
1538             #  but it must be called immediately after the constructor
1539-            trial_image = Image.open(StringIO(f.content))
1540+            trial_image = Image.open(file)
1541             trial_image.verify()
1542         except Exception: # Python Imaging Library doesn't recognize it as an image
1543             raise ValidationError(self.error_messages['invalid_image'])
1544Index: tests/modeltests/model_forms/models.py
1545===================================================================
1546--- tests/modeltests/model_forms/models.py      (revision 7354)
1547+++ tests/modeltests/model_forms/models.py      (working copy)
1548@@ -75,6 +75,7 @@
1549 __test__ = {'API_TESTS': """
1550 >>> from django import newforms as forms
1551 >>> from django.newforms.models import ModelForm
1552+>>> from django.core.files.uploadedfile import SimpleUploadedFile
1553 
1554 The bare bones, absolutely nothing custom, basic case.
1555 
1556@@ -792,7 +793,7 @@
1557 
1558 # Upload a file and ensure it all works as expected.
1559 
1560->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world'}})
1561+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')})
1562 >>> f.is_valid()
1563 True
1564 >>> type(f.cleaned_data['file'])
1565@@ -819,7 +820,7 @@
1566 
1567 # Override the file by uploading a new one.
1568 
1569->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}, instance=instance)
1570+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance)
1571 >>> f.is_valid()
1572 True
1573 >>> instance = f.save()
1574@@ -838,7 +839,7 @@
1575 >>> instance.file
1576 ''
1577 
1578->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
1579+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
1580 >>> f.is_valid()
1581 True
1582 >>> instance = f.save()
1583@@ -858,7 +859,7 @@
1584 
1585 >>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png")).read()
1586 
1587->>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data}})
1588+>>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)})
1589 >>> f.is_valid()
1590 True
1591 >>> type(f.cleaned_data['image'])
1592@@ -885,7 +886,7 @@
1593 
1594 # Override the file by uploading a new one.
1595 
1596->>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}}, instance=instance)
1597+>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)}, instance=instance)
1598 >>> f.is_valid()
1599 True
1600 >>> instance = f.save()
1601@@ -904,7 +905,7 @@
1602 >>> instance.image
1603 ''
1604 
1605->>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
1606+>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance)
1607 >>> f.is_valid()
1608 True
1609 >>> instance = f.save()
1610Index: tests/regressiontests/bug639/tests.py
1611===================================================================
1612--- tests/regressiontests/bug639/tests.py       (revision 7354)
1613+++ tests/regressiontests/bug639/tests.py       (working copy)
1614@@ -9,6 +9,7 @@
1615 from regressiontests.bug639.models import Photo
1616 from django.http import QueryDict
1617 from django.utils.datastructures import MultiValueDict
1618+from django.core.files.uploadedfile import SimpleUploadedFile
1619 
1620 class Bug639Test(unittest.TestCase):
1621         
1622@@ -21,11 +22,7 @@
1623         
1624         # Fake a request query dict with the file
1625         qd = QueryDict("title=Testing&image=", mutable=True)
1626-        qd["image_file"] = {
1627-            "filename" : "test.jpg",
1628-            "content-type" : "image/jpeg",
1629-            "content" : img
1630-        }
1631+        qd["image_file"] = SimpleUploadedFile('test.jpg', img, 'image/jpeg')
1632         
1633         manip = Photo.AddManipulator()
1634         manip.do_html2python(qd)
1635@@ -39,4 +36,4 @@
1636         Make sure to delete the "uploaded" file to avoid clogging /tmp.
1637         """
1638         p = Photo.objects.get()
1639-        os.unlink(p.get_image_filename())
1640\ No newline at end of file
1641+        os.unlink(p.get_image_filename())
1642Index: tests/regressiontests/forms/error_messages.py
1643===================================================================
1644--- tests/regressiontests/forms/error_messages.py       (revision 7354)
1645+++ tests/regressiontests/forms/error_messages.py       (working copy)
1646@@ -1,6 +1,7 @@
1647 # -*- coding: utf-8 -*-
1648 tests = r"""
1649 >>> from django.newforms import *
1650+>>> from django.core.files.uploadedfile import SimpleUploadedFile
1651 
1652 # CharField ###################################################################
1653 
1654@@ -214,11 +215,11 @@
1655 Traceback (most recent call last):
1656 ...
1657 ValidationError: [u'INVALID']
1658->>> f.clean({})
1659+>>> f.clean(SimpleUploadedFile('name', None))
1660 Traceback (most recent call last):
1661 ...
1662-ValidationError: [u'MISSING']
1663->>> f.clean({'filename': 'name', 'content':''})
1664+ValidationError: [u'EMPTY FILE']
1665+>>> f.clean(SimpleUploadedFile('name', ''))
1666 Traceback (most recent call last):
1667 ...
1668 ValidationError: [u'EMPTY FILE']
1669Index: tests/regressiontests/forms/fields.py
1670===================================================================
1671--- tests/regressiontests/forms/fields.py       (revision 7354)
1672+++ tests/regressiontests/forms/fields.py       (working copy)
1673@@ -2,6 +2,7 @@
1674 tests = r"""
1675 >>> from django.newforms import *
1676 >>> from django.newforms.widgets import RadioFieldRenderer
1677+>>> from django.core.files.uploadedfile import SimpleUploadedFile
1678 >>> import datetime
1679 >>> import time
1680 >>> import re
1681@@ -773,12 +774,12 @@
1682 >>> f.clean({})
1683 Traceback (most recent call last):
1684 ...
1685-ValidationError: [u'No file was submitted.']
1686+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
1687 
1688 >>> f.clean({}, '')
1689 Traceback (most recent call last):
1690 ...
1691-ValidationError: [u'No file was submitted.']
1692+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
1693 
1694 >>> f.clean({}, 'files/test3.pdf')
1695 'files/test3.pdf'
1696@@ -788,20 +789,20 @@
1697 ...
1698 ValidationError: [u'No file was submitted. Check the encoding type on the form.']
1699 
1700->>> f.clean({'filename': 'name', 'content': None})
1701+>>> f.clean(SimpleUploadedFile('name', None))
1702 Traceback (most recent call last):
1703 ...
1704 ValidationError: [u'The submitted file is empty.']
1705 
1706->>> f.clean({'filename': 'name', 'content': ''})
1707+>>> f.clean(SimpleUploadedFile('name', ''))
1708 Traceback (most recent call last):
1709 ...
1710 ValidationError: [u'The submitted file is empty.']
1711 
1712->>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}))
1713+>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
1714 <class 'django.newforms.fields.UploadedFile'>
1715 
1716->>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}, 'files/test4.pdf'))
1717+>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))
1718 <class 'django.newforms.fields.UploadedFile'>
1719 
1720 # URLField ##################################################################
1721Index: tests/regressiontests/forms/forms.py
1722===================================================================
1723--- tests/regressiontests/forms/forms.py        (revision 7354)
1724+++ tests/regressiontests/forms/forms.py        (working copy)
1725@@ -1,6 +1,7 @@
1726 # -*- coding: utf-8 -*-
1727 tests = r"""
1728 >>> from django.newforms import *
1729+>>> from django.core.files.uploadedfile import SimpleUploadedFile
1730 >>> import datetime
1731 >>> import time
1732 >>> import re
1733@@ -1465,7 +1466,7 @@
1734 >>> print f
1735 <tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
1736 
1737->>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)
1738+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False)
1739 >>> print f
1740 <tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>
1741 
1742@@ -1473,7 +1474,7 @@
1743 >>> print f
1744 <tr><th>File1:</th><td><ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul><input type="file" name="file1" /></td></tr>
1745 
1746->>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)
1747+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False)
1748 >>> print f
1749 <tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
1750 >>> f.is_valid()
1751Index: tests/regressiontests/test_client_regress/views.py
1752===================================================================
1753--- tests/regressiontests/test_client_regress/views.py  (revision 7354)
1754+++ tests/regressiontests/test_client_regress/views.py  (working copy)
1755@@ -1,5 +1,6 @@
1756 from django.contrib.auth.decorators import login_required
1757 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
1758+import sha
1759 
1760 def no_template_view(request):
1761     "A simple view that expects a GET request, and returns a rendered template"
1762@@ -10,13 +11,36 @@
1763     Check that a file upload can be updated into the POST dictionary without
1764     going pear-shaped.
1765     """
1766+    from django.core.files.uploadedfile import UploadedFile
1767     form_data = request.POST.copy()
1768     form_data.update(request.FILES)
1769-    if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
1770+    if isinstance(form_data['file_field'], UploadedFile) and isinstance(form_data['name'], unicode):
1771         return HttpResponse('')
1772     else:
1773         return HttpResponseServerError()
1774 
1775+def file_upload_view_verify(request):
1776+    """
1777+    Use the sha digest hash to verify the uploaded contents.
1778+    """
1779+    from django.core.files.uploadedfile import UploadedFile
1780+    form_data = request.POST.copy()
1781+    form_data.update(request.FILES)
1782+    for key, value in form_data.items():
1783+        if key.endswith('_hash'):
1784+            continue
1785+        if key + '_hash' not in form_data:
1786+            continue
1787+        submitted_hash = form_data[key + '_hash']
1788+        if isinstance(value, UploadedFile):
1789+            new_hash = sha.new(value.read()).hexdigest()
1790+        else:
1791+            new_hash = sha.new(value).hexdigest()
1792+        if new_hash != submitted_hash:
1793+            return HttpResponseServerError()
1794+
1795+    return HttpResponse('')
1796+
1797 def get_view(request):
1798     "A simple login protected view"
1799     return HttpResponse("Hello world")
1800@@ -37,4 +61,4 @@
1801 def login_protected_redirect_view(request):
1802     "A view that redirects all requests to the GET view"
1803     return HttpResponseRedirect('/test_client_regress/get_view/')
1804-login_protected_redirect_view = login_required(login_protected_redirect_view)
1805\ No newline at end of file
1806+login_protected_redirect_view = login_required(login_protected_redirect_view)
1807Index: tests/regressiontests/test_client_regress/models.py
1808===================================================================
1809--- tests/regressiontests/test_client_regress/models.py (revision 7354)
1810+++ tests/regressiontests/test_client_regress/models.py (working copy)
1811@@ -5,6 +5,7 @@
1812 from django.test import Client, TestCase
1813 from django.core.urlresolvers import reverse
1814 import os
1815+import sha
1816 
1817 class AssertContainsTests(TestCase):
1818     def test_contains(self):
1819@@ -243,6 +244,44 @@
1820         response = self.client.post('/test_client_regress/file_upload/', post_data)
1821         self.assertEqual(response.status_code, 200)
1822 
1823+    def test_large_upload(self):
1824+        import tempfile
1825+        dir = tempfile.gettempdir()
1826+
1827+        (fd, name1) = tempfile.mkstemp(suffix='.file1', dir=dir)
1828+        file1 = os.fdopen(fd, 'w+b')
1829+        file1.write('a' * (2 ** 21))
1830+        file1.seek(0)
1831+
1832+        (fd, name2) = tempfile.mkstemp(suffix='.file2', dir=dir)
1833+        file2 = os.fdopen(fd, 'w+b')
1834+        file2.write('a' * (10 * 2 ** 20))
1835+        file2.seek(0)
1836+
1837+        post_data = {
1838+            'name': 'Ringo',
1839+            'file_field1': file1,
1840+            'file_field2': file2,
1841+            }
1842+
1843+        for key in post_data.keys():
1844+            try:
1845+                post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest()
1846+                post_data[key].seek(0)
1847+            except AttributeError:
1848+                post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest()
1849+
1850+        response = self.client.post('/test_client_regress/file_upload_verify/', post_data)
1851+
1852+        for name in (name1, name2):
1853+            try:
1854+                os.unlink(name)
1855+            except:
1856+                pass
1857+
1858+        self.assertEqual(response.status_code, 200)
1859+
1860+
1861 class LoginTests(TestCase):
1862     fixtures = ['testdata']
1863 
1864Index: tests/regressiontests/test_client_regress/urls.py
1865===================================================================
1866--- tests/regressiontests/test_client_regress/urls.py   (revision 7354)
1867+++ tests/regressiontests/test_client_regress/urls.py   (working copy)
1868@@ -4,6 +4,7 @@
1869 urlpatterns = patterns('',
1870     (r'^no_template_view/$', views.no_template_view),
1871     (r'^file_upload/$', views.file_upload_view),
1872+    (r'^file_upload_verify/$', views.file_upload_view_verify),
1873     (r'^get_view/$', views.get_view),
1874     url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
1875     (r'^login_protected_redirect_view/$', views.login_protected_redirect_view)
1876Index: AUTHORS
1877===================================================================
1878--- AUTHORS     (revision 7354)
1879+++ AUTHORS     (working copy)
1880@@ -58,7 +58,7 @@
1881     Jökull Sólberg Auðunsson <jokullsolberg@gmail.com>
1882     Arthur <avandorp@gmail.com>
1883     David Avsajanishvili <avsd05@gmail.com>
1884-    axiak@mit.edu
1885+    Mike Axiak <axiak@mit.edu>
1886     Niran Babalola <niran@niran.org>
1887     Morten Bagai <m@bagai.com>
1888     Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
1889@@ -135,6 +135,7 @@
1890     Marc Fargas <telenieko@telenieko.com>
1891     Szilveszter Farkas <szilveszter.farkas@gmail.com>
1892     favo@exoweb.net
1893+    fdr <drfarina@gmail.com>
1894     Dmitri Fedortchenko <zeraien@gmail.com>
1895     Bill Fenner <fenner@gmail.com>
1896     Stefane Fermgier <sf@fermigier.com>
1897Index: docs/settings.txt
1898===================================================================
1899--- docs/settings.txt   (revision 7354)
1900+++ docs/settings.txt   (working copy)
1901@@ -513,6 +513,15 @@
1902 The character encoding used to decode any files read from disk. This includes
1903 template files and initial SQL data files.
1904 
1905+FILE_UPLOAD_TEMP_DIR
1906+--------------------
1907+
1908+**New in Django development version**
1909+
1910+Default: ``None``
1911+
1912+The directory to store data temporarily while uploading files. If ``None``, Django will use the standard temporary directory for the operating system. For example, this will default to '/tmp' on *nix-style operating systems.
1913+
1914 FIXTURE_DIRS
1915 -------------
1916