Code

Ticket #2070: 2070_revision7359.diff

File 2070_revision7359.diff, 76.9 KB (added by axiak, 6 years ago)

New diff...some style changes and new documentation.

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