Code

Ticket #2070: 2070_revision7484.diff

File 2070_revision7484.diff, 99.2 KB (added by axiak, 6 years ago)

Updated diff per Jacob's requests and useful feedback.

Line 
1Index: django/test/client.py
2===================================================================
3--- django/test/client.py       (revision 7484)
4+++ django/test/client.py       (working copy)
5@@ -18,6 +18,26 @@
6 BOUNDARY = 'BoUnDaRyStRiNg'
7 MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
8 
9+
10+class FakePayload(object):
11+    """
12+    A wrapper around StringIO that restricts what can be read,
13+    since data from the network can't be seeked and cannot
14+    be read outside of its content length (or else we hang).
15+    """
16+    def __init__(self, content):
17+        self.__content = StringIO(content)
18+        self.__len = len(content)
19+
20+    def read(self, num_bytes=None):
21+        if num_bytes is None:
22+            num_bytes = self.__len or 1
23+        assert self.__len >= num_bytes, "Cannot read more than the available bytes from the HTTP incoming data."
24+        content = self.__content.read(num_bytes)
25+        self.__len -= num_bytes
26+        return content
27+
28+
29 class ClientHandler(BaseHandler):
30     """
31     A HTTP Handler that can be used for testing purposes.
32@@ -230,7 +250,7 @@
33             'CONTENT_TYPE':   content_type,
34             'PATH_INFO':      urllib.unquote(path),
35             'REQUEST_METHOD': 'POST',
36-            'wsgi.input':     StringIO(post_data),
37+            'wsgi.input':     FakePayload(post_data),
38         }
39         r.update(extra)
40 
41Index: django/http/multipartparser.py
42===================================================================
43--- django/http/multipartparser.py      (revision 0)
44+++ django/http/multipartparser.py      (revision 0)
45@@ -0,0 +1,683 @@
46+"""
47+MultiPart parsing for file uploads.
48+
49+This object will take the file upload headers
50+and the file upload handler and chunk the upload
51+data for the handler to deal with.
52+"""
53+from django.utils.datastructures import MultiValueDict
54+from django.utils.encoding import force_unicode
55+from django.utils.text import unescape_entities
56+
57+__all__ = ('MultiPartParser','MultiPartParserError','InputStreamExhausted')
58+
59+class MultiPartParserError(Exception):
60+    pass
61+
62+class InputStreamExhausted(Exception):
63+    """ No more reads are allowed from this device. """
64+    pass
65+
66+
67+class FieldType(object):
68+    """
69+    Denotes what type of field a given section of the multipart
70+    stream is.
71+
72+    The types can be one of ("FILE", "RAW", "FIELD")
73+    """
74+
75+    def __init__(self, name):
76+        self.name = name
77+
78+    def __str__(self):
79+        return self.name
80+
81+RAW = FieldType('RAW')
82+FILE = FieldType('FILE')
83+FIELD = FieldType('FIELD')
84+
85+
86+class MultiPartParser(object):
87+    """
88+    A rfc2388 multipart/form-data parser.
89+
90+    parse() reads the input stream in chunk_size chunks and returns a
91+    tuple of (POST MultiValueDict, FILES MultiValueDict). If
92+    file_upload_dir is defined files will be streamed to temporary
93+    files in the specified directory.
94+    """
95+    def __init__(self, META, input_data, upload_handlers, encoding=None):
96+        """
97+        Initialize the MultiPartParser object.
98+
99+        *META* -- The standard META dictionary in Django request objects.
100+        *input_data* -- The raw post data, as a bytestring.
101+        *upload_handler* -- An object of type UploadHandler
102+                            that performs operations on the uploaded
103+                            data.
104+        *encoding* -- The encoding with which to treat the incoming data.
105+        """
106+        # Import cgi utilities for (near) future use.
107+        global valid_boundary, settings
108+        from django.conf import settings
109+        from cgi import valid_boundary
110+
111+        #
112+        # Content-Type should containt multipart and the boundary information.
113+        #
114+
115+        content_type = META.get('HTTP_CONTENT_TYPE', META.get('CONTENT_TYPE', ''))
116+        if not content_type.startswith('multipart/'):
117+            raise MultiPartParserError('Invalid Content-Type: %s' %
118+                                       content_type)
119+
120+        # Parse the header to get the boundary to split the parts.
121+        ctypes, opts = parse_header(content_type)
122+        boundary = opts.get('boundary')
123+        if not boundary or not valid_boundary(boundary):
124+            raise MultiPartParserError('Invalid boundary in multipart: %s' %
125+                                       boundary)
126+
127+
128+        #
129+        # Content-Length should contain the length of the body we are about
130+        # to receive.
131+        #
132+        try:
133+            content_length = int(META.get('HTTP_CONTENT_LENGTH',
134+                                          META.get('CONTENT_LENGTH',0)))
135+        except (ValueError, TypeError):
136+            # For now set it to 0...we'll try again later on down.
137+            content_length = 0
138+
139+        if content_length <= 0:
140+            # This means we shouldn't continue...raise an error.
141+            raise MultiPartParserError("Invalid content length: %r" % content_length)
142+
143+        self._boundary = boundary
144+        self._input_data = input_data
145+
146+        # For compatibility with low-level network APIs (with 32-bit integers),
147+        # the chunk size should be < 2^31, but still divisible by 4.
148+        self._chunk_size = min(2147483644, *[x.chunk_size for x in upload_handlers
149+                                            if x.chunk_size])
150+
151+        self._meta = META
152+        self._encoding = encoding or settings.DEFAULT_CHARSET
153+        self._content_length = content_length
154+        self._upload_handlers = upload_handlers
155+
156+
157+    def parse(self):
158+        """
159+        Parse the POST data and break it into a FILES MultiValueDict
160+        and a POST MultiValueDict.
161+
162+           *returns* -- A tuple containing the POST and FILES dictionary,
163+                        respectively.
164+        """
165+        from django.core.files.uploadhandler import StopUpload, SkipFile
166+        from django.http import QueryDict
167+
168+        encoding = self._encoding
169+        handlers = self._upload_handlers
170+
171+        limited_input_data = LimitBytes(self._input_data, self._content_length)
172+
173+        # See if the handler will want to take care of the parsing.
174+        # This allows overriding everything if somebody wants it.
175+        for handler in handlers:
176+            result = handler.handle_raw_input(limited_input_data,
177+                                              self._meta,
178+                                              self._content_length,
179+                                              self._boundary,
180+                                              encoding)
181+            if result is not None:
182+                return result[0], result[1]
183+
184+        # Create the data structures to be used later.
185+        self._post = QueryDict('', mutable=True)
186+        self._files = MultiValueDict()
187+
188+        # Instantiate the parser and stream:
189+        stream = LazyStream(ChunkIter(limited_input_data, self._chunk_size))
190+
191+        # Whether or not to signal a file-completion at the beginning of the loop.
192+        old_field_name = None
193+        counters = [0] * len(handlers)
194+
195+        try:
196+            for item_type, meta_data, field_stream in Parser(stream, self._boundary):
197+                if old_field_name:
198+                    # We run this at the beginning of the next loop
199+                    # since we cannot be sure a file is complete until
200+                    # we hit the next boundary/part of the multipart content.
201+                    self.handle_file_complete(old_field_name, counters)
202+
203+                try:
204+                    disposition = meta_data['content-disposition'][1]
205+                    field_name = disposition['name'].strip()
206+                except (KeyError, IndexError, AttributeError):
207+                    continue
208+
209+                transfer_encoding = meta_data.get('content-transfer-encoding')
210+                field_name = force_unicode(field_name, encoding, errors='replace')
211+
212+                if item_type is FIELD:
213+                    # This is a post field, we can just set it in the post
214+                    if transfer_encoding == 'base64':
215+                        raw_data = field_stream.read()
216+                        try:
217+                            data = str(raw_data).decode('base64')
218+                        except:
219+                            data = raw_data
220+                    else:
221+                        data = field_stream.read()
222+
223+                    self._post.appendlist(field_name,
224+                                          force_unicode(data, encoding, errors='replace'))
225+                elif item_type is FILE:
226+                    # This is a file, use the handler...
227+                    file_successful = True
228+                    file_name = disposition.get('filename')
229+                    if not file_name:
230+                        continue
231+                    file_name = force_unicode(file_name, encoding, errors='replace')
232+                    file_name = self.IE_sanitize(unescape_entities(file_name))
233+
234+
235+                    content_type = meta_data.get('content-type', ('',))[0].strip()
236+                    try:
237+                        charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
238+                    except:
239+                        charset = None
240+
241+                    try:
242+                        content_length = int(meta_data.get('content-length')[0])
243+                    except (IndexError, TypeError, ValueError):
244+                        content_length = None
245+
246+                    counters = [0] * len(handlers)
247+                    try:
248+                        for handler in handlers:
249+                            retval = handler.new_file(field_name, file_name,
250+                                                      content_type, content_length,
251+                                                      charset)
252+                            if retval:
253+                                break
254+
255+                        for chunk in field_stream:
256+                            if transfer_encoding == 'base64':
257+                                # We only special-case base64 transfer encoding
258+                                try:
259+                                    chunk = str(chunk).decode('base64')
260+                                except Exception, e:
261+                                    # Since this is only a chunk, any error is an unfixable error.
262+                                    raise MultiValueParseError("Could not decode base64 data: %r" % e)
263+
264+                            for i, handler in enumerate(handlers):
265+                                chunk_length = len(chunk)
266+                                chunk = handler.receive_data_chunk(chunk,
267+                                                                   counters[i])
268+                                counters[i] += chunk_length
269+                                if chunk is None:
270+                                    # If the chunk received by the handler is None, then don't continue.
271+                                    break
272+
273+                    except SkipFile, e:
274+                        file_successful = False
275+                        # Just use up the rest of this file...
276+                        exhaust(field_stream)
277+                    else:
278+                        # Handle file upload completions on next iteration.
279+                        old_field_name = field_name
280+                else:
281+                    # If this is neither a FIELD or a FILE, just exhaust the stream.
282+                    exhaust(stream)
283+        except StopUpload, e:
284+            if not e.connection_reset:
285+                exhaust(limited_input_data)
286+        else:
287+            # Make sure that the request data is all fed
288+            exhaust(limited_input_data)
289+
290+        # Signal that the upload has completed.
291+        for handler in handlers:
292+            retval = handler.upload_complete()
293+            if retval:
294+                break
295+
296+        return self._post, self._files
297+
298+    def handle_file_complete(self, old_field_name, counters):
299+        """
300+        Handle all the signalling that takes place when a file is complete.
301+        """
302+        for i, handler in enumerate(self._upload_handlers):
303+            file_obj = handler.file_complete(counters[i])
304+            if file_obj:
305+                # If it returns a file object, then set the files dict.
306+                self._files.appendlist(force_unicode(old_field_name,
307+                                                     self._encoding,
308+                                                     errors='replace'),
309+                                       file_obj)
310+                break
311+
312+    def IE_sanitize(self, filename):
313+        """cleanup filename from IE full paths"""
314+        return filename and filename[filename.rfind("\\")+1:].strip()
315+
316+
317+class LazyStream(object):
318+    """
319+    The LazyStream wrapper allows one to pull and "unget" bytes from a stream.
320+
321+    Given a producer object (an iterator that yields bytestrings), the
322+    LazyStream object will support iteration, reading, and keeping a
323+    "look-back" variable in case you need to "unget" some bytes.
324+    """
325+    def __init__(self, producer, length=None):
326+        """
327+        Every LazyStream must have a producer when instantiated.
328+
329+        A producer is an iterable that returns a string each time it
330+        is called.
331+        """
332+        self._producer = producer
333+        self._empty = False
334+        self._leftover = ''
335+        self.length = length
336+        self._position = 0
337+        self._remaining = length
338+
339+        # These fields are to do sanity checking to make sure we don't
340+        # have infinite loops getting/ungetting from the stream. The
341+        # purpose overall is to raise an exception if we perform lots
342+        # of stream get/unget gymnastics without getting
343+        # anywhere. Naturally this is not sound, but most probably
344+        # would indicate a bug if the exception is raised.
345+
346+        # largest position tell us how far this lazystream has ever
347+        # been advanced
348+        self._largest_position = 0
349+
350+        # "modifications since" will start at zero and increment every
351+        # time the position is modified but a new largest position is
352+        # not achieved.
353+        self._modifications_since = 0
354+
355+    def tell(self):
356+        return self.position
357+
358+    def read(self, size=None):
359+        def parts():
360+            remaining = (size is not None and [size] or [self._remaining])[0]
361+            # do the whole thing in one shot if no limit was provided.
362+            if remaining is None:
363+                yield ''.join(self)
364+                return
365+
366+            # otherwise do some bookkeeping to return exactly enough
367+            # of the stream and stashing any extra content we get from
368+            # the producer
369+            while remaining != 0:
370+                assert remaining > 0, 'remaining bytes to read should never go negative'
371+
372+                chunk = self.next()
373+
374+                emitting = chunk[:remaining]
375+                self.unget(chunk[remaining:])
376+                remaining -= len(emitting)
377+                yield emitting
378+
379+        out = ''.join(parts())
380+        return out
381+
382+    def next(self):
383+        """
384+        Used when the exact number of bytes to read is unimportant.
385+
386+        This procedure just returns whatever is chunk is conveniently
387+        returned from the iterator instead. Useful to avoid
388+        unnecessary bookkeeping if performance is an issue.
389+        """
390+        if self._leftover:
391+            output = self._leftover
392+            self._leftover = ''
393+        else:
394+            output = self._producer.next()
395+        self.position += len(output)
396+        return output
397+
398+    def close(self):
399+        """
400+        Used to invalidate/disable this lazy stream.
401+
402+        Replaces the producer with an empty list. Any leftover bytes
403+        that have already been read will still be reported upon read()
404+        and/or next().
405+        """
406+        self._producer = []
407+
408+    def __iter__(self):
409+        return self
410+
411+    def unget(self, bytes):
412+        """
413+        Places bytes back onto the front of the lazy stream.
414+
415+        Future calls to read() will return those bytes first. The
416+        stream position and thus tell() will be rewound.
417+        """
418+        self.position -= len(bytes)
419+        self._leftover = ''.join([bytes, self._leftover])
420+
421+    def _set_position(self, value):
422+        if value > self._largest_position:
423+            self._modifications_since = 0
424+            self._largest_position = value
425+        else:
426+            self._modifications_since += 1
427+            if self._modifications_since > 500:
428+                raise MultiPartParserError("LazyStream thinks it is somehow stuck. Report this to the Django developers\n" + repr(vars(self)))
429+
430+        self._position = value
431+
432+    position = property(lambda self: self._position, _set_position)
433+
434+class ChunkIter(object):
435+    """
436+    An iterable that will yield chunks of data.
437+    Given a file-like object as the constructor,
438+    this object will yield chunks of read operations
439+    from that object.
440+    """
441+    def __init__(self, flo, chunk_size=64 * 1024):
442+        self.flo = flo
443+        self.chunk_size = chunk_size
444+
445+    def next(self):
446+        try:
447+            data = self.flo.read(self.chunk_size)
448+        except InputStreamExhausted:
449+            raise StopIteration()
450+        if data:
451+            return data
452+        else:
453+            raise StopIteration()
454+
455+    def __iter__(self):
456+        return self
457+
458+
459+class LimitBytes(object):
460+    """ Limit bytes for a file object. """
461+    def __init__(self, fileobject, length):
462+        self._file = fileobject
463+        self.remaining = length
464+
465+    def read(self, num_bytes=None):
466+        """
467+        Read data from the underlying file.
468+        If you ask for too much or there isn't anything left,
469+        this will raise an InputStreamExhausted error.
470+        """
471+        if self.remaining <= 0:
472+            raise InputStreamExhausted()
473+        if num_bytes is None:
474+            num_bytes = self.remaining
475+        else:
476+            num_bytes = min(num_bytes, self.remaining)
477+        self.remaining -= num_bytes
478+        return self._file.read(num_bytes)
479+
480+class InterBoundaryIter(object):
481+    """
482+    A Producer that will iterate over boundaries.
483+    """
484+    def __init__(self, stream, boundary):
485+        self._stream = stream
486+        self._boundary = boundary
487+
488+    def __iter__(self):
489+        return self
490+
491+    def next(self):
492+        try:
493+            return LazyStream(BoundaryIter(self._stream, self._boundary))
494+        except InputStreamExhausted:
495+            raise StopIteration()
496+
497+
498+class BoundaryIter(object):
499+    """
500+    A Producer that is sensitive to boundaries.
501+
502+    Will happily yield bytes until a boundary is found. Will yield the
503+    bytes before the boundary, throw away the boundary bytes
504+    themselves, and push the post-boundary bytes back on the stream.
505+
506+    The future calls to .next() after locating the boundary will raise
507+    a StopIteration exception.
508+    """
509+
510+    def __init__(self, stream, boundary):
511+        self._stream = stream
512+        self._boundary = boundary
513+        self._done = False
514+        # rollback an additional six bytes because the format is like
515+        # this: CRLF<boundary>[--CRLF]
516+        self._rollback = len(boundary) + 6
517+
518+        # Try to use mx fast string search if available. Otherwise
519+        # use Python find. Wrap the latter for consistency.
520+        unused_char = self._stream.read(1)
521+        if not unused_char:
522+            raise InputStreamExhausted()
523+        self._stream.unget(unused_char)
524+        try:
525+            from mx.TextTools import FS
526+            self._fs = FS(boundary).find
527+        except ImportError:
528+            self._fs = lambda data: data.find(boundary)
529+
530+    def __iter__(self):
531+        return self
532+
533+    def next(self):
534+        if self._done:
535+            raise StopIteration()
536+
537+        stream = self._stream
538+        rollback = self._rollback
539+
540+        bytes_read = 0
541+        chunks = []
542+        for bytes in stream:
543+            bytes_read += len(bytes)
544+            chunks.append(bytes)
545+            if bytes_read > rollback:
546+                break
547+            if not bytes:
548+                break
549+        else:
550+            self._done = True
551+
552+        if not chunks:
553+            raise StopIteration()
554+
555+        chunk = ''.join(chunks)
556+        boundary = self._find_boundary(chunk, len(chunk) < self._rollback)
557+
558+        if boundary:
559+            end, next = boundary
560+            stream.unget(chunk[next:])
561+            self._done = True
562+            return chunk[:end]
563+        else:
564+            # make sure we dont treat a partial boundary (and
565+            # its separators) as data
566+            if not chunk[:-rollback]:# and len(chunk) >= (len(self._boundary) + 6):
567+                # There's nothing left, we should just return and mark as done.
568+                self._done = True
569+                return chunk
570+            else:
571+                stream.unget(chunk[-rollback:])
572+                return chunk[:-rollback]
573+
574+    def _find_boundary(self, data, eof = False):
575+        """
576+        Finds a multipart boundary in data.
577+
578+        Should no boundry exist in the data None is returned
579+        instead. Otherwise a tuple containing
580+        the indices of the following are returned:
581+
582+         * the end of current encapsulation
583+
584+         * the start of the next encapsulation
585+        """
586+        index = self._fs(data)
587+        if index < 0:
588+            return None
589+        else:
590+            end = index
591+            next = index + len(self._boundary)
592+            data_len = len(data) - 1
593+            # backup over CRLF
594+            if data[max(0,end-1)] == '\n':
595+                end -= 1
596+            if data[max(0,end-1)] == '\r':
597+                end -= 1
598+            # skip over --CRLF
599+            #if data[min(data_len,next)] == '-':
600+            #    next += 1
601+            #if data[min(data_len,next)] == '-':
602+            #    next += 1
603+            #if data[min(data_len,next)] == '\r':
604+            #    next += 1
605+            #if data[min(data_len,next)] == '\n':
606+            #    next += 1
607+            return end, next
608+
609+def exhaust(stream_or_iterable):
610+    """
611+    Completely exhausts an iterator or stream.
612+
613+    Raise a MultiPartParserError if the argument is not a stream or an
614+    iterable.
615+    """
616+    iterator = None
617+    try:
618+        iterator = iter(stream_or_iterable)
619+    except TypeError:
620+        iterator = ChunkIter(stream_or_iterable, 16384)
621+
622+    if iterator is None:
623+        raise MultiPartParserError('multipartparser.exhaust() was passed a non-iterable or stream parameter')
624+
625+    for __ in iterator:
626+        pass
627+
628+def ParseBoundaryStream(stream, max_header_size):
629+        """
630+        Parses one and exactly one stream that encapsulates a boundary.
631+        """
632+        # Stream at beginning of header, look for end of header
633+        # and parse it if found. The header must fit within one
634+        # chunk.
635+        chunk = stream.read(max_header_size)
636+        # 'find' returns the top of these four bytes, so we'll
637+        # need to munch them later to prevent them from polluting
638+        # the payload.
639+        header_end = chunk.find('\r\n\r\n')
640+
641+        def _parse_header(line):
642+            main_value_pair, params = parse_header(line)
643+            try:
644+                name, value = main_value_pair.split(':', 1)
645+            except:
646+                raise ValueError("Invalid header: %r" % line)
647+            return name, (value, params)
648+
649+        if header_end == -1:
650+            # we find no header, so we just mark this fact and pass on
651+            # the stream verbatim
652+            stream.unget(chunk)
653+            return (RAW, {}, stream)
654+
655+        header = chunk[:header_end]
656+
657+        # here we place any excess chunk back onto the stream, as
658+        # well as throwing away the CRLFCRLF bytes from above.
659+        stream.unget(chunk[header_end + 4:])
660+
661+        TYPE = RAW
662+        outdict = {}
663+
664+        # Eliminate blank lines
665+        for line in header.split('\r\n'):
666+            # This terminology ("main value" and "dictionary of
667+            # parameters") is from the Python docs.
668+            try:
669+                name, (value, params) = _parse_header(line)
670+            except:
671+                continue
672+
673+            if name == 'content-disposition':
674+                TYPE = FIELD
675+                if params.get('filename'):
676+                    TYPE = FILE
677+
678+            outdict[name] = value, params
679+
680+        if TYPE == RAW:
681+            stream.unget(chunk)
682+
683+        return (TYPE, outdict, stream)
684+
685+
686+class Parser(object):
687+    def __init__(self, stream, boundary):
688+        self._stream = stream
689+        self._separator = '--' + boundary
690+
691+    def __iter__(self):
692+
693+        boundarystream = InterBoundaryIter(self._stream,
694+                                           self._separator)
695+
696+        for sub_stream in boundarystream:
697+            # Iterate over each part
698+            yield ParseBoundaryStream(sub_stream, 1024)
699+
700+def parse_header(line):
701+    """ Parse the header into a key-value. """
702+    plist = _parse_header_params(';' + line)
703+    key = plist.pop(0).lower()
704+    pdict = {}
705+    for p in plist:
706+        i = p.find('=')
707+        if i >= 0:
708+            name = p[:i].strip().lower()
709+            value = p[i+1:].strip()
710+            if len(value) >= 2 and value[0] == value[-1] == '"':
711+                value = value[1:-1]
712+                value = value.replace('\\\\', '\\').replace('\\"', '"')
713+            pdict[name] = value
714+    return key, pdict
715+
716+def _parse_header_params(s):
717+    plist = []
718+    while s[:1] == ';':
719+        s = s[1:]
720+        end = s.find(';')
721+        while end > 0 and s.count('"', 0, end) % 2:
722+            end = s.find(';', end + 1)
723+        if end < 0:
724+            end = len(s)
725+        f = s[:end]
726+        plist.append(f.strip())
727+        s = s[end:]
728+    return plist
729Index: django/http/__init__.py
730===================================================================
731--- django/http/__init__.py     (revision 7484)
732+++ django/http/__init__.py     (working copy)
733@@ -9,9 +9,9 @@
734 except ImportError:
735     from cgi import parse_qsl
736 
737-from django.utils.datastructures import MultiValueDict, FileDict
738+from django.utils.datastructures import MultiValueDict, ImmutableList
739 from django.utils.encoding import smart_str, iri_to_uri, force_unicode
740-
741+from django.http.multipartparser import MultiPartParser
742 from utils import *
743 
744 RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
745@@ -25,6 +25,7 @@
746 
747     # The encoding used in GET/POST dicts. None means use default setting.
748     _encoding = None
749+    _upload_handlers = ()
750 
751     def __init__(self):
752         self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
753@@ -102,40 +103,41 @@
754 
755     encoding = property(_get_encoding, _set_encoding)
756 
757-def parse_file_upload(header_dict, post_data):
758-    """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
759-    import email, email.Message
760-    from cgi import parse_header
761-    raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
762-    raw_message += '\r\n\r\n' + post_data
763-    msg = email.message_from_string(raw_message)
764-    POST = QueryDict('', mutable=True)
765-    FILES = MultiValueDict()
766-    for submessage in msg.get_payload():
767-        if submessage and isinstance(submessage, email.Message.Message):
768-            name_dict = parse_header(submessage['Content-Disposition'])[1]
769-            # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
770-            # or {'name': 'blah'} for POST fields
771-            # We assume all uploaded files have a 'filename' set.
772-            if 'filename' in name_dict:
773-                assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
774-                if not name_dict['filename'].strip():
775-                    continue
776-                # IE submits the full path, so trim everything but the basename.
777-                # (We can't use os.path.basename because that uses the server's
778-                # directory separator, which may not be the same as the
779-                # client's one.)
780-                filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
781-                FILES.appendlist(name_dict['name'], FileDict({
782-                    'filename': filename,
783-                    'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None,
784-                    'content': submessage.get_payload(),
785-                }))
786-            else:
787-                POST.appendlist(name_dict['name'], submessage.get_payload())
788-    return POST, FILES
789+    def _initialize_handlers(self):
790+        from django.conf import settings
791+        from django.core.files.uploadhandler import load_handler
792+        handlers = []
793+        # We go through each handler in the settings variable
794+        # and instantiate the handler by calling HandlerClass(request).
795+        for handler in settings.FILE_UPLOAD_HANDLERS:
796+            handlers.append(load_handler(handler, self))
797+        self._upload_handlers = handlers
798 
799+    def _set_upload_handlers(self, upload_handlers):
800+        """
801+        Set the upload handler to the new handler given in the parameter.
802+        """
803+        if hasattr(self, '_files'):
804+            raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
805+        self._upload_handlers = upload_handlers
806 
807+    def _get_upload_handlers(self):
808+        if not self._upload_handlers:
809+            # If thre are no upload handlers defined, initialize them from settings.
810+            self._initialize_handlers()
811+        return self._upload_handlers
812+
813+    upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
814+
815+    def parse_file_upload(self, META, post_data):
816+        """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
817+        self.upload_handlers = ImmutableList(self.upload_handlers,
818+                                             warning="You cannot alter the upload handlers after the upload has been processed.")
819+        parser = MultiPartParser(META, post_data, self.upload_handlers,
820+                                 self.encoding)
821+        return parser.parse()
822+
823+
824 class QueryDict(MultiValueDict):
825     """
826     A specialized MultiValueDict that takes a query string when initialized.
827Index: django/oldforms/__init__.py
828===================================================================
829--- django/oldforms/__init__.py (revision 7484)
830+++ django/oldforms/__init__.py (working copy)
831@@ -680,18 +680,27 @@
832         self.field_name, self.is_required = field_name, is_required
833         self.validator_list = [self.isNonEmptyFile] + validator_list
834 
835-    def isNonEmptyFile(self, field_data, all_data):
836+    def isNonEmptyFile(self, new_data, all_data):
837+        if hasattr(new_data, 'upload_errors'):
838+            upload_errors = new_data.upload_errors()
839+            if upload_errors:
840+                raise validators.CriticalValidationError, upload_errors
841         try:
842-            content = field_data['content']
843-        except TypeError:
844-            raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.")
845-        if not content:
846+            file_size = new_data.file_size
847+        except AttributeError:
848+            file_size = len(new_data['content'])
849+        if not file_size:
850             raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
851 
852     def render(self, data):
853         return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
854             (self.get_id(), self.__class__.__name__, self.field_name))
855 
856+    def prepare(self, new_data):
857+        if hasattr(new_data, 'upload_errors'):
858+            upload_errors = new_data.upload_errors()
859+            new_data[self.field_name] = { '_file_upload_error': upload_errors }
860+
861     def html2python(data):
862         if data is None:
863             raise EmptyValue
864Index: django/db/models/base.py
865===================================================================
866--- django/db/models/base.py    (revision 7484)
867+++ django/db/models/base.py    (working copy)
868@@ -19,6 +19,7 @@
869 from django.utils.datastructures import SortedDict
870 from django.utils.functional import curry
871 from django.utils.encoding import smart_str, force_unicode, smart_unicode
872+from django.core.files.move import file_move_safe
873 from django.conf import settings
874 
875 try:
876@@ -471,12 +472,29 @@
877     def _get_FIELD_size(self, field):
878         return os.path.getsize(self._get_FIELD_filename(field))
879 
880-    def _save_FIELD_file(self, field, filename, raw_contents, save=True):
881+    def _save_FIELD_file(self, field, filename, raw_field, save=True):
882         directory = field.get_directory_name()
883         try: # Create the date-based directory if it doesn't exist.
884             os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
885         except OSError: # Directory probably already exists.
886             pass
887+
888+        # Put the deprecation warning first since there are multiple
889+        # locations where we use the new and old interface.
890+        if isinstance(raw_field, dict):
891+            import warnings
892+            from django.core.files.uploadedfile import SimpleUploadedFile
893+            raw_field = SimpleUploadedFile.from_dict(raw_field)
894+            warnings.warn("The dictionary usage for files is deprecated. Use the new object interface instead.", DeprecationWarning)
895+        elif isinstance(raw_field, basestring):
896+            import warnings
897+            from django.core.files.uploadedfile import SimpleUploadedFile
898+            raw_field = SimpleUploadedFile(filename, raw_field)
899+            warnings.warn("The string interface for save_FIELD_file is deprecated.", DeprecationWarning)
900+
901+        if filename is None:
902+            filename = raw_field.file_name
903+
904         filename = field.get_filename(filename)
905 
906         # If the filename already exists, keep adding an underscore to the name of
907@@ -493,10 +511,22 @@
908         setattr(self, field.attname, filename)
909 
910         full_filename = self._get_FIELD_filename(field)
911-        fp = open(full_filename, 'wb')
912-        fp.write(raw_contents)
913-        fp.close()
914+        if hasattr(raw_field, 'temporary_file_path'):
915+            # This file has a file path that we can move.
916+            raw_field.close()
917+            file_move_safe(raw_field.temporary_file_path(), full_filename)
918+        else:
919+            from django.core.files import locks
920+            fp = open(full_filename, 'wb')
921+            # exclusive lock
922+            locks.lock(fp, locks.LOCK_EX)
923+            # This is a normal uploadedfile that we can stream.
924+            for chunk in raw_field.chunk(65535):
925+                fp.write(chunk)
926+            locks.unlock(fp)
927+            fp.close()
928 
929+
930         # Save the width and/or height, if applicable.
931         if isinstance(field, ImageField) and (field.width_field or field.height_field):
932             from django.utils.images import get_image_dimensions
933Index: django/db/models/fields/__init__.py
934===================================================================
935--- django/db/models/fields/__init__.py (revision 7484)
936+++ django/db/models/fields/__init__.py (working copy)
937@@ -802,7 +802,7 @@
938         setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
939         setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
940         setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
941-        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
942+        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
943         dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
944 
945     def delete_file(self, instance):
946@@ -825,10 +825,16 @@
947         if new_data.get(upload_field_name, False):
948             func = getattr(new_object, 'save_%s_file' % self.name)
949             if rel:
950-                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
951+                file = new_data[upload_field_name][0]
952             else:
953-                func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
954+                file = new_data[upload_field_name]
955 
956+            try:
957+                file_name = file.file_name
958+            except AttributeError:
959+                file_name = file['filename']
960+            func(file_name, file, save)
961+
962     def get_directory_name(self):
963         return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
964 
965@@ -840,7 +846,7 @@
966     def save_form_data(self, instance, data):
967         from django.newforms.fields import UploadedFile
968         if data and isinstance(data, UploadedFile):
969-            getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)
970+            getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False)
971 
972     def formfield(self, **kwargs):
973         defaults = {'form_class': forms.FileField}
974Index: django/conf/global_settings.py
975===================================================================
976--- django/conf/global_settings.py      (revision 7484)
977+++ django/conf/global_settings.py      (working copy)
978@@ -224,6 +224,22 @@
979 # Example: "http://media.lawrence.com"
980 MEDIA_URL = ''
981 
982+# A tuple that enumerates the upload handlers
983+# in order.
984+FILE_UPLOAD_HANDLERS = (
985+    'django.core.files.uploadhandler.MemoryFileUploadHandler',
986+    'django.core.files.uploadhandler.TemporaryFileUploadHandler',
987+)
988+
989+# Number of bytes the length of the request can be before it is
990+# streamed to the file system instead of parsed entirely in memory.
991+FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440
992+
993+# Directory to upload streamed files temporarily.
994+# A value of `None` means that it will use the default temporary
995+# directory for the server's operating system.
996+FILE_UPLOAD_TEMP_DIR = None
997+
998 # Default formatting for date objects. See all available format strings here:
999 # http://www.djangoproject.com/documentation/templates/#now
1000 DATE_FORMAT = 'N j, Y'
1001Index: django/core/handlers/wsgi.py
1002===================================================================
1003--- django/core/handlers/wsgi.py        (revision 7484)
1004+++ django/core/handlers/wsgi.py        (working copy)
1005@@ -112,9 +112,8 @@
1006         # Populates self._post and self._files
1007         if self.method == 'POST':
1008             if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
1009-                header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
1010-                header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
1011-                self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
1012+                self._raw_post_data = ''
1013+                self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
1014             else:
1015                 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
1016         else:
1017Index: django/core/handlers/modpython.py
1018===================================================================
1019--- django/core/handlers/modpython.py   (revision 7484)
1020+++ django/core/handlers/modpython.py   (working copy)
1021@@ -53,7 +53,8 @@
1022     def _load_post_and_files(self):
1023         "Populates self._post and self._files"
1024         if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
1025-            self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
1026+            self._raw_post_data = ''
1027+            self._post, self._files = self.parse_file_upload(self.META, self._req)
1028         else:
1029             self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
1030 
1031Index: django/core/files/locks.py
1032===================================================================
1033--- django/core/files/locks.py  (revision 0)
1034+++ django/core/files/locks.py  (revision 0)
1035@@ -0,0 +1,66 @@
1036+"""
1037+Locking portability based partially on example by
1038+Jonathan Feignberg <jdf@pobox.com> in python cookbook:
1039+    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
1040+Licensed under the Python Software License.
1041+
1042+Example Usage::
1043+
1044+    from django.core.files import locks
1045+
1046+    f = open('./file', 'wb')
1047+
1048+    locks.lock(f, locks.LOCK_EX)
1049+    f.write('Django')
1050+    f.close()
1051+"""
1052+
1053+__all__ = ('LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock')
1054+
1055+system_type = None
1056+
1057+try:
1058+    import win32con
1059+    import win32file
1060+    import pywintypes
1061+    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
1062+    LOCK_SH = 0
1063+    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
1064+    __overlapped = pywintypes.OVERLAPPED()
1065+    system_type = 'nt'
1066+except (ImportError, AttributeError):
1067+    pass
1068+
1069+try:
1070+    import fcntl
1071+    LOCK_EX = fcntl.LOCK_EX
1072+    LOCK_SH = fcntl.LOCK_SH
1073+    LOCK_NB = fcntl.LOCK_NB
1074+    system_type = 'posix'
1075+except (ImportError, AttributeError):
1076+    pass
1077+
1078+if system_type == 'nt':
1079+    def lock(file, flags):
1080+               hfile = win32file._get_osfhandle(file.fileno())
1081+               win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
1082+
1083+    def unlock(file):
1084+               hfile = win32file._get_osfhandle(file.fileno())
1085+               win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
1086+elif system_type == 'posix':
1087+    def lock(file, flags):
1088+               fcntl.flock(file.fileno(), flags)
1089+
1090+    def unlock(file):
1091+        fcntl.flock(file.fileno(), fcntl.LOCK_UN)
1092+else:
1093+    # File locking is not supported.
1094+    LOCK_EX = LOCK_SH = LOCK_NB = None
1095+       
1096+    # Dummy functions that don't do anything.
1097+    def lock(file, flags):
1098+               pass
1099+
1100+    def unlock(file):
1101+               pass
1102Index: django/core/files/uploadedfile.py
1103===================================================================
1104--- django/core/files/uploadedfile.py   (revision 0)
1105+++ django/core/files/uploadedfile.py   (revision 0)
1106@@ -0,0 +1,202 @@
1107+"""
1108+The uploaded file objects for Django.
1109+This contains the base UploadedFile and the TemporaryUploadedFile
1110+derived class.
1111+"""
1112+
1113+__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile')
1114+
1115+class UploadedFile(object):
1116+    """
1117+    The UploadedFile object behaves somewhat like a file
1118+    object and represents some data that the user submitted
1119+    and is stored in some form.
1120+    """
1121+    DEFAULT_CHUNK_SIZE = 64 * 2**10
1122+
1123+    def __init__(self):
1124+        self.file_size = None
1125+        self.file_name = None
1126+        self.content_type = None
1127+        self.charset = None
1128+        pass
1129+
1130+    def chunk(self, chunk_size=None):
1131+        """
1132+        Read the file to generate chunks of chunk_size bytes.
1133+        """
1134+        if not chunk_size:
1135+            chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
1136+
1137+        if hasattr(self, 'seek'):
1138+            self.seek(0)
1139+        # Assume the pointer is at zero...
1140+        counter = self.file_size
1141+
1142+        while counter > 0:
1143+            yield self.read(chunk_size)
1144+            counter -= chunk_size
1145+
1146+
1147+    def multiple_chunks(self, chunk_size=None):
1148+        """
1149+        Return True if you can expect multiple chunks, False otherwise.
1150+        Note: If a particular file representation is in memory, then
1151+              override this to return False.
1152+        """
1153+        if not chunk_size:
1154+            chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
1155+        return self.file_size < chunk_size
1156+       
1157+
1158+    def read(self, num_bytes=None):
1159+        """
1160+        Read from the file in whatever representation it has.
1161+        """
1162+        raise NotImplementedError()
1163+
1164+    def open(self):
1165+        """
1166+        Open the file, if one needs to.
1167+        """
1168+        pass
1169+
1170+
1171+    def close(self):
1172+        """
1173+        Close the file, if one needs to.
1174+        """
1175+        pass
1176+
1177+    def __getitem__(self, key):
1178+        """
1179+        This maintains backwards compatibility.
1180+        """
1181+        import warnings
1182+        warnings.warn("The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.", DeprecationWarning)
1183+        # Dictionary to translate labels
1184+        # for backwards compatbility.
1185+        # Should be removed at some point.
1186+        backwards_translate = {
1187+            'filename': 'file_name',
1188+            'content-type': 'content_type',
1189+            }
1190+
1191+        if key == 'content':
1192+            return self.read()
1193+        else:
1194+            return getattr(self, backwards_translate.get(key, key))
1195+
1196+    def __repr__(self):
1197+        """
1198+        This representation could be anything and can be overridden.
1199+        This is mostly done to make it look somewhat useful.
1200+        """
1201+        _dict = {
1202+            'file_name': self.file_name,
1203+            'content_type': self.content_type,
1204+            'content': '<omitted>',
1205+            }
1206+        return repr(_dict)
1207+
1208+
1209+class TemporaryUploadedFile(UploadedFile):
1210+    """
1211+    Upload a file to a temporary file.
1212+    """
1213+
1214+    def __init__(self, file, file_name, content_type, file_size, charset):
1215+        self.file = file
1216+        self.file_name = file_name
1217+        self.path = file.name
1218+        self.content_type = content_type
1219+        self.file_size = file_size
1220+        self.charset = charset
1221+        self.file.seek(0)
1222+
1223+    def temporary_file_path(self):
1224+        """
1225+        Return the full path of this file.
1226+        """
1227+        return self.path
1228+
1229+    def read(self, *args, **kwargs):
1230+        return self.file.read(*args, **kwargs)
1231+
1232+    def open(self):
1233+        """
1234+        Assume the person meant to seek.
1235+        """
1236+        self.seek(0)
1237+
1238+    def seek(self, *args, **kwargs):
1239+        self.file.seek(*args, **kwargs)
1240+
1241+
1242+class InMemoryUploadedFile(UploadedFile):
1243+    """
1244+    Upload a file into memory.
1245+    """
1246+    def __init__(self, file, field_name, file_name, content_type, charset):
1247+        self.file = file
1248+        self.field_name = field_name
1249+        self.file_name = file_name
1250+        self.content_type = content_type
1251+        self.charset = charset
1252+        self.file.seek(0)
1253+
1254+    def seek(self, *args, **kwargs):
1255+        self.file.seek(*args, **kwargs)
1256+
1257+    def open(self):
1258+        self.seek(0)
1259+
1260+    def read(self, *args, **kwargs):
1261+        return self.file.read(*args, **kwargs)
1262+
1263+    def chunk(self, chunk_size=None):
1264+        """
1265+        Return the entirety of the data regardless.
1266+        """
1267+        self.file.seek(0)
1268+        return self.read()
1269+
1270+    def multiple_chunks(self, chunk_size=None):
1271+        """
1272+        Since it's in memory, we'll never have multiple chunks.
1273+        """
1274+        return False
1275+
1276+
1277+class SimpleUploadedFile(InMemoryUploadedFile):
1278+    """
1279+    A simple representation of a file, which
1280+    just has content, size, and a name.
1281+    """
1282+    def __init__(self, name, content, content_type='text/plain'):
1283+        try:
1284+            from cStringIO import StringIO
1285+        except ImportError:
1286+            from StringIO import StringIO
1287+        self.file = StringIO(content or '')
1288+        self.file_name = name
1289+        self.field_name = None
1290+        self.file_size = len(content or '')
1291+        self.content_type = content_type
1292+        self.charset = None
1293+        self.file.seek(0)
1294+
1295+    def from_dict(cls, file_dict):
1296+        """
1297+        Creates a SimpleUploadedFile object from
1298+        a dictionary object with the following keys:
1299+           - filename
1300+           - content-type
1301+           - content
1302+        """
1303+        return cls(file_dict['filename'],
1304+                   file_dict['content'],
1305+                   file_dict.get('content-type', 'text/plain'))
1306+
1307+    from_dict = classmethod(from_dict)
1308+
1309Index: django/core/files/__init__.py
1310===================================================================
1311--- django/core/files/__init__.py       (revision 0)
1312+++ django/core/files/__init__.py       (revision 0)
1313@@ -0,0 +1 @@
1314+
1315Index: django/core/files/uploadhandler.py
1316===================================================================
1317--- django/core/files/uploadhandler.py  (revision 0)
1318+++ django/core/files/uploadhandler.py  (revision 0)
1319@@ -0,0 +1,238 @@
1320+""" A fileuploadhandler base and default subclass for handling file uploads.
1321+"""
1322+import os
1323+try:
1324+    from cStringIO import StringIO
1325+except ImportError:
1326+    from StringIO import StringIO
1327+
1328+from django.utils.encoding import force_unicode
1329+from django.utils.datastructures import MultiValueDict
1330+from django.core.exceptions import ImproperlyConfigured
1331+
1332+from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
1333+
1334+__all__ = ('UploadFileException','StopUpload', 'SkipFile',
1335+           'FileUploadHandler', 'TemporaryFileUploadHandler',
1336+           'MemoryFileUploadHandler', 'load_handler')
1337+
1338+
1339+class UploadFileException(Exception):
1340+    """ Any error having to do with Uploading Files. """
1341+    pass
1342+
1343+class StopUpload(UploadFileException):
1344+    """ This exception is raised when an upload must abort. """
1345+    def __init__(self, connection_reset=False):
1346+        """
1347+        Initializes a StopUpload Exception object.
1348+
1349+        By specifying connection_reset=True, Django knows
1350+        it is okay to halt the upload without feeding
1351+        in the rest of the upload to nowhere.
1352+        (And thus keeping the browser from the
1353+        Connection Reset error.)
1354+        """
1355+        self.connection_reset = connection_reset
1356+
1357+    def __unicode__(self):
1358+        if self.connection_reset:
1359+            return u'StopUpload: Halt current upload.'
1360+        else:
1361+            return u'StopUpload: Feed in request data.'
1362+
1363+class SkipFile(UploadFileException):
1364+    """ This exception is raised when a file needs to be skipped. """
1365+    pass
1366+
1367+class FileUploadHandler(object):
1368+    """ FileUploadHandler will take data and handle file uploads
1369+    in a streamed fashion.
1370+    """
1371+    chunk_size = 64 * 2 ** 10 #: The default chunk size is 64 KB.
1372+
1373+    def __init__(self, request=None):
1374+        " Initialize some local variables. "
1375+        self.file_name = None
1376+        self.content_type = None
1377+        self.content_length = None
1378+        self.charset = None
1379+        self.request = request
1380+
1381+    def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
1382+        """
1383+        Handle the raw input from the client.
1384+        Parameters:
1385+          *input_data* -- An object that supports reading via .read().
1386+          *content_length* -- The (integer) value of the Content-Length header from the client.
1387+          *boundary* -- The boundary from the Content-Type header. Be sure to prepend two '--'.
1388+        """
1389+        pass
1390+
1391+    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
1392+        """
1393+        Signal that a new file has been started.
1394+       
1395+        Warning: Do not trust content_length, if you get it at all.
1396+        """
1397+        self.field_name = field_name
1398+        self.file_name = file_name
1399+        self.content_type = content_type
1400+        self.content_length = content_length
1401+        self.charset = charset
1402+
1403+    def receive_data_chunk(self, raw_data, start):
1404+        """
1405+        Receive data from the streamed upload parser.
1406+        Start is the position in the file of the chunk.
1407+        """
1408+        raise NotImplementedError()
1409+
1410+    def file_complete(self, file_size):
1411+        """
1412+        Signal that a file has completed.
1413+        File size corresponds to the actual size accumulated
1414+        by all the chunks.
1415+
1416+        This should return a valid UploadedFile object.
1417+        """
1418+        raise NotImplementedError()
1419+
1420+    def upload_complete(self):
1421+        """
1422+        Signal that the upload is complete.
1423+        Do any cleanup that is necessary for this handler.
1424+        """
1425+        pass
1426+
1427+
1428+
1429+class TemporaryFileUploadHandler(FileUploadHandler):
1430+    """
1431+    Upload the streaming data into a temporary file.
1432+    """
1433+    def __init__(self, *args, **kwargs):
1434+        """ Import settings for later. """
1435+        super(TemporaryFileUploadHandler, self).__init__(*args, **kwargs)
1436+        global settings
1437+        from django.conf import settings
1438+
1439+    def new_file(self, file_name, *args, **kwargs):
1440+        """
1441+        Create the file object to append to as data is coming in.
1442+        """
1443+        super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs)
1444+        self.file = TemporaryFile(settings.FILE_UPLOAD_TEMP_DIR)
1445+        self.write = self.file.write
1446+
1447+    def receive_data_chunk(self, raw_data, start):
1448+        """
1449+        Once we get the data, we will save it to our file.
1450+        """
1451+        self.write(raw_data)
1452+
1453+    def file_complete(self, file_size):
1454+        """
1455+        Signal that a file has completed.
1456+        File size corresponds to the actual size accumulated
1457+        by all the chunks.
1458+
1459+        This should return a valid UploadedFile object.
1460+        """
1461+        self.file.seek(0)
1462+        return TemporaryUploadedFile(self.file, self.file_name,
1463+                                     self.content_type, file_size,
1464+                                     self.charset)
1465+
1466+
1467+class MemoryFileUploadHandler(FileUploadHandler):
1468+    """
1469+    The MemoryFileUploadHandler will place the data directly into memory.
1470+    """
1471+
1472+    def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
1473+        """
1474+        Use the content_length to signal whether or not this handler should be in use.
1475+        """
1476+        global settings
1477+        from django.conf import settings
1478+
1479+        # If the the post is too large, we cannot use the Memory handler.
1480+        if content_length > settings.FILE_UPLOAD_MAX_MEMORY_SIZE:
1481+            self.activated = False
1482+        else:
1483+            self.activated = True
1484+
1485+    def new_file(self, *args, **kwargs):
1486+        super(MemoryFileUploadHandler, self).new_file(*args, **kwargs)
1487+        if self.activated:
1488+            self.file = StringIO()
1489+            return "Stop"
1490+
1491+    def receive_data_chunk(self, raw_data, start):
1492+        """
1493+        Add the data to the StringIO file.
1494+        """
1495+        if self.activated:
1496+            self.file.write(raw_data)
1497+        else:
1498+            return raw_data
1499+
1500+    def file_complete(self, file_size):
1501+        """
1502+        Return a file object if we're activated.
1503+        """
1504+        if not self.activated:
1505+            return
1506+
1507+        return InMemoryUploadedFile(self.file, self.field_name, self.file_name,
1508+                                    self.content_type, self.charset)
1509+
1510+
1511+class TemporaryFile(object):
1512+    """
1513+    A temporary file that tries to delete itself when garbage collected.
1514+    """
1515+    def __init__(self, dir):
1516+        import tempfile
1517+        if not dir:
1518+            dir = tempfile.gettempdir()
1519+        try:
1520+            (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir)
1521+            self.file = os.fdopen(fd, 'w+b')
1522+        except (OSError, IOError):
1523+            raise OSError("Could not create temporary file for uploading, have you set settings.FILE_UPLOAD_TEMP_DIR correctly?")
1524+        self.name = name
1525+
1526+    def __getattr__(self, name):
1527+        a = getattr(self.__dict__['file'], name)
1528+        if type(a) != type(0):
1529+            setattr(self, name, a)
1530+        return a
1531+
1532+    def __del__(self):
1533+        try:
1534+            os.unlink(self.name)
1535+        except OSError:
1536+            pass
1537+
1538+def load_handler(path, *args, **kwargs):
1539+    """
1540+    Given a path to a handler return the instantiation of that handler.
1541+    E.g.::
1542+        load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request)
1543+    will return a TemporaryFileUploadHandler object.
1544+    """
1545+    i = path.rfind('.')
1546+    module, attr = path[:i], path[i+1:]
1547+    try:
1548+        mod = __import__(module, {}, {}, [attr])
1549+    except ImportError, e:
1550+        raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e))
1551+    except ValueError, e:
1552+        raise ImproperlyConfigured('Error importing upload handler module Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?')
1553+    try:
1554+        cls = getattr(mod, attr)
1555+    except AttributeError:
1556+        raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr))
1557+    return cls(*args, **kwargs)
1558Index: django/core/files/move.py
1559===================================================================
1560--- django/core/files/move.py   (revision 0)
1561+++ django/core/files/move.py   (revision 0)
1562@@ -0,0 +1,52 @@
1563+import os
1564+
1565+__all__ = ('file_move_safe',)
1566+
1567+try:
1568+    import shutil
1569+    file_move = shutil.move
1570+except ImportError:
1571+    file_move = os.rename
1572+
1573+def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
1574+    """
1575+    Moves a file from one location to another in the safest way possible.
1576+   
1577+    First, it tries using shutils.move, which is OS-dependent but doesn't
1578+    break with change of filesystems. Then it tries os.rename, which will
1579+    break if it encounters a change in filesystems. Lastly, it streams
1580+    it manually from one file to another in python.
1581+
1582+    Without ``allow_overwrite``, if the destination file exists, the
1583+    file will raise an IOError.
1584+    """
1585+
1586+    from django.core.files import locks
1587+
1588+    if old_file_name == new_file_name:
1589+        # No file moving takes place.
1590+        return
1591+
1592+    if not allow_overwrite and os.path.exists(new_file_name):
1593+        raise IOError("Django does not allow overwriting files.")
1594+
1595+    try:
1596+        file_move(old_file_name, new_file_name)
1597+        return
1598+    except OSError: # moving to another filesystem
1599+        pass
1600+
1601+    new_file = open(new_file_name, 'wb')
1602+    # exclusive lock
1603+    locks.lock(new_file, locks.LOCK_EX)
1604+    old_file = open(old_file_name, 'rb')
1605+    current_chunk = None
1606+
1607+    while current_chunk != '':
1608+        current_chunk = old_file.read(chunk_size)
1609+        new_file.write(current_chunk)
1610+
1611+    new_file.close()
1612+    old_file.close()
1613+
1614+    os.remove(old_file_name)
1615Index: django/newforms/fields.py
1616===================================================================
1617--- django/newforms/fields.py   (revision 7484)
1618+++ django/newforms/fields.py   (working copy)
1619@@ -4,6 +4,7 @@
1620 
1621 import copy
1622 import datetime
1623+import warnings
1624 import os
1625 import re
1626 import time
1627@@ -416,9 +417,9 @@
1628 
1629 class UploadedFile(StrAndUnicode):
1630     "A wrapper for files uploaded in a FileField"
1631-    def __init__(self, filename, content):
1632+    def __init__(self, filename, data):
1633         self.filename = filename
1634-        self.content = content
1635+        self.data = data
1636 
1637     def __unicode__(self):
1638         """
1639@@ -444,16 +445,28 @@
1640             return None
1641         elif not data and initial:
1642             return initial
1643+
1644+        if isinstance(data, dict):
1645+            # We warn once, then support both ways below.
1646+            warnings.warn("The dictionary usage for files is deprecated. Use the new object interface instead.", DeprecationWarning)
1647+
1648         try:
1649-            f = UploadedFile(data['filename'], data['content'])
1650-        except TypeError:
1651+            file_name = data.file_name
1652+            file_size = data.file_size
1653+        except AttributeError:
1654+            try:
1655+                file_name = data.get('filename')
1656+                file_size = bool(data['content'])
1657+            except (AttributeError, KeyError):
1658+                raise ValidationError(self.error_messages['invalid'])
1659+
1660+        if not file_name:
1661             raise ValidationError(self.error_messages['invalid'])
1662-        except KeyError:
1663-            raise ValidationError(self.error_messages['missing'])
1664-        if not f.content:
1665+        if not file_size:
1666             raise ValidationError(self.error_messages['empty'])
1667-        return f
1668 
1669+        return UploadedFile(file_name, data)
1670+
1671 class ImageField(FileField):
1672     default_error_messages = {
1673         'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
1674@@ -470,15 +483,36 @@
1675         elif not data and initial:
1676             return initial
1677         from PIL import Image
1678-        from cStringIO import StringIO
1679+
1680+        # We need to get the file, it either has a path
1681+        # or we have to read it all into memory...
1682+        if hasattr(data, 'temporary_file_path'):
1683+            file = data.temporary_file_path()
1684+        else:
1685+            try:
1686+                from cStringIO import StringIO
1687+            except ImportError:
1688+                from StringIO import StringIO
1689+            if hasattr(data, 'read'):
1690+                file = StringIO(data.read())
1691+            else:
1692+                file = StringIO(data['content'])
1693+
1694         try:
1695             # load() is the only method that can spot a truncated JPEG,
1696             #  but it cannot be called sanely after verify()
1697-            trial_image = Image.open(StringIO(f.content))
1698+            trial_image = Image.open(file)
1699             trial_image.load()
1700+
1701+            # Since we're about to use the file again, we have to
1702+            # reset the cursor of the file object if it has a cursor
1703+            # to reset.
1704+            if hasattr(file, 'reset'):
1705+                file.reset()
1706+
1707             # verify() is the only method that can spot a corrupt PNG,
1708             #  but it must be called immediately after the constructor
1709-            trial_image = Image.open(StringIO(f.content))
1710+            trial_image = Image.open(file)
1711             trial_image.verify()
1712         except Exception: # Python Imaging Library doesn't recognize it as an image
1713             raise ValidationError(self.error_messages['invalid_image'])
1714Index: django/utils/datastructures.py
1715===================================================================
1716--- django/utils/datastructures.py      (revision 7484)
1717+++ django/utils/datastructures.py      (working copy)
1718@@ -332,14 +332,34 @@
1719             except TypeError: # Special-case if current isn't a dict.
1720                 current = {bits[-1]: v}
1721 
1722-class FileDict(dict):
1723+class ImmutableList(tuple):
1724     """
1725-    A dictionary used to hold uploaded file contents. The only special feature
1726-    here is that repr() of this object won't dump the entire contents of the
1727-    file to the output. A handy safeguard for a large file upload.
1728+    A tuple-like object that raises useful
1729+    errors when it is asked to mutate.
1730+    Example::
1731+        a = ImmutableList(range(5), warning=AttributeError("You cannot mutate this."))
1732+        a[3] = '4'
1733+        (Raises the AttributeError)
1734     """
1735-    def __repr__(self):
1736-        if 'content' in self:
1737-            d = dict(self, content='<omitted>')
1738-            return dict.__repr__(d)
1739-        return dict.__repr__(self)
1740+
1741+    def __new__(cls, *args, **kwargs):
1742+        if 'warning' in kwargs:
1743+            warning = kwargs['warning']
1744+            del kwargs['warning']
1745+        else:
1746+            warning = 'ImmutableList object is immutable.'
1747+        self = tuple.__new__(cls, *args, **kwargs)
1748+        self.warning = warning
1749+        return self
1750+
1751+    def complain(self, *wargs, **kwargs):
1752+        if isinstance(self.warning, Exception):
1753+            raise self.warning
1754+        else:
1755+            raise AttributeError, self.warning
1756+
1757+    # All list mutation functions become complain.
1758+    __delitem__ = __delslice__ = __iadd__ = __imul__ = complain
1759+    __setitem__ = __setslice__ = complain
1760+    append = extend = insert = pop = remove = complain
1761+    sort = reverse = complain
1762Index: django/utils/text.py
1763===================================================================
1764--- django/utils/text.py        (revision 7484)
1765+++ django/utils/text.py        (working copy)
1766@@ -3,6 +3,7 @@
1767 from django.utils.encoding import force_unicode
1768 from django.utils.functional import allow_lazy
1769 from django.utils.translation import ugettext_lazy
1770+from htmlentitydefs import name2codepoint
1771 
1772 # Capitalizes the first letter of a string.
1773 capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:]
1774@@ -218,3 +219,26 @@
1775             yield bit
1776 smart_split = allow_lazy(smart_split, unicode)
1777 
1778+def _replace_entity(match):
1779+     text = match.group(1)
1780+     if text[0] == u'#':
1781+         text = text[1:]
1782+         try:
1783+             if text[0] in u'xX':
1784+                 c = int(text[1:], 16)
1785+             else:
1786+                 c = int(text)
1787+             return unichr(c)
1788+         except ValueError:
1789+             return match.group(0)
1790+     else:
1791+         try:
1792+             return unichr(name2codepoint[text])
1793+         except (ValueError, KeyError):
1794+             return match.group(0)
1795+
1796+_entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
1797+
1798+def unescape_entities(text):
1799+     return _entity_re.sub(_replace_entity, text)
1800+unescape_entities = allow_lazy(unescape_entities, unicode)
1801Index: tests/modeltests/model_forms/models.py
1802===================================================================
1803--- tests/modeltests/model_forms/models.py      (revision 7484)
1804+++ tests/modeltests/model_forms/models.py      (working copy)
1805@@ -67,7 +67,13 @@
1806 
1807 class ImageFile(models.Model):
1808     description = models.CharField(max_length=20)
1809-    image = models.FileField(upload_to=tempfile.gettempdir())
1810+    try:
1811+        # If PIL is available, try testing PIL.
1812+        # Otherwise, it's equivalent to TextFile above.
1813+        import Image
1814+        image = models.ImageField(upload_to=tempfile.gettempdir())
1815+    except ImportError:
1816+        image = models.FileField(upload_to=tempfile.gettempdir())
1817 
1818     def __unicode__(self):
1819         return self.description
1820@@ -75,6 +81,9 @@
1821 __test__ = {'API_TESTS': """
1822 >>> from django import newforms as forms
1823 >>> from django.newforms.models import ModelForm
1824+>>> from django.core.files.uploadedfile import SimpleUploadedFile
1825+>>> from warnings import filterwarnings
1826+>>> filterwarnings("ignore")
1827 
1828 The bare bones, absolutely nothing custom, basic case.
1829 
1830@@ -792,6 +801,17 @@
1831 
1832 # Upload a file and ensure it all works as expected.
1833 
1834+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')})
1835+>>> f.is_valid()
1836+True
1837+>>> type(f.cleaned_data['file'])
1838+<class 'django.newforms.fields.UploadedFile'>
1839+>>> instance = f.save()
1840+>>> instance.file
1841+u'...test1.txt'
1842+
1843+>>> os.unlink(instance.get_file_filename())
1844+
1845 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world'}})
1846 >>> f.is_valid()
1847 True
1848@@ -814,18 +834,30 @@
1849 u'...test1.txt'
1850 
1851 # Delete the current file since this is not done by Django.
1852-
1853 >>> os.unlink(instance.get_file_filename())
1854 
1855 # Override the file by uploading a new one.
1856 
1857->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}, instance=instance)
1858+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance)
1859 >>> f.is_valid()
1860 True
1861 >>> instance = f.save()
1862 >>> instance.file
1863 u'...test2.txt'
1864 
1865+# Delete the current file since this is not done by Django.
1866+>>> os.unlink(instance.get_file_filename())
1867+
1868+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}})
1869+>>> f.is_valid()
1870+True
1871+>>> instance = f.save()
1872+>>> instance.file
1873+u'...test2.txt'
1874+
1875+# Delete the current file since this is not done by Django.
1876+>>> os.unlink(instance.get_file_filename())
1877+
1878 >>> instance.delete()
1879 
1880 # Test the non-required FileField
1881@@ -838,14 +870,28 @@
1882 >>> instance.file
1883 ''
1884 
1885->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
1886+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
1887 >>> f.is_valid()
1888 True
1889 >>> instance = f.save()
1890 >>> instance.file
1891 u'...test3.txt'
1892+
1893+# Delete the current file since this is not done by Django.
1894+>>> os.unlink(instance.get_file_filename())
1895 >>> instance.delete()
1896 
1897+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}})
1898+>>> f.is_valid()
1899+True
1900+>>> instance = f.save()
1901+>>> instance.file
1902+u'...test3.txt'
1903+
1904+# Delete the current file since this is not done by Django.
1905+>>> os.unlink(instance.get_file_filename())
1906+>>> instance.delete()
1907+
1908 # ImageField ###################################################################
1909 
1910 # ImageField and FileField are nearly identical, but they differ slighty when
1911@@ -858,6 +904,18 @@
1912 
1913 >>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png")).read()
1914 
1915+>>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)})
1916+>>> f.is_valid()
1917+True
1918+>>> type(f.cleaned_data['image'])
1919+<class 'django.newforms.fields.UploadedFile'>
1920+>>> instance = f.save()
1921+>>> instance.image
1922+u'...test.png'
1923+
1924+# Delete the current file since this is not done by Django.
1925+>>> os.unlink(instance.get_image_filename())
1926+
1927 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data}})
1928 >>> f.is_valid()
1929 True
1930@@ -885,15 +943,28 @@
1931 
1932 # Override the file by uploading a new one.
1933 
1934->>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}}, instance=instance)
1935+>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)}, instance=instance)
1936 >>> f.is_valid()
1937 True
1938 >>> instance = f.save()
1939 >>> instance.image
1940 u'...test2.png'
1941 
1942+# Delete the current file since this is not done by Django.
1943+>>> os.unlink(instance.get_image_filename())
1944 >>> instance.delete()
1945 
1946+>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}})
1947+>>> f.is_valid()
1948+True
1949+>>> instance = f.save()
1950+>>> instance.image
1951+u'...test2.png'
1952+
1953+# Delete the current file since this is not done by Django.
1954+>>> os.unlink(instance.get_image_filename())
1955+>>> instance.delete()
1956+
1957 # Test the non-required ImageField
1958 
1959 >>> f = ImageFileForm(data={'description': u'Test'})
1960@@ -904,12 +975,23 @@
1961 >>> instance.image
1962 ''
1963 
1964->>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
1965+>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance)
1966 >>> f.is_valid()
1967 True
1968 >>> instance = f.save()
1969 >>> instance.image
1970 u'...test3.png'
1971+
1972+# Delete the current file since this is not done by Django.
1973+>>> os.unlink(instance.get_image_filename())
1974 >>> instance.delete()
1975 
1976+>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}})
1977+>>> f.is_valid()
1978+True
1979+>>> instance = f.save()
1980+>>> instance.image
1981+u'...test3.png'
1982+>>> instance.delete()
1983+
1984 """}
1985Index: tests/regressiontests/bug639/tests.py
1986===================================================================
1987--- tests/regressiontests/bug639/tests.py       (revision 7484)
1988+++ tests/regressiontests/bug639/tests.py       (working copy)
1989@@ -9,6 +9,7 @@
1990 from regressiontests.bug639.models import Photo
1991 from django.http import QueryDict
1992 from django.utils.datastructures import MultiValueDict
1993+from django.core.files.uploadedfile import SimpleUploadedFile
1994 
1995 class Bug639Test(unittest.TestCase):
1996         
1997@@ -21,12 +22,8 @@
1998         
1999         # Fake a request query dict with the file
2000         qd = QueryDict("title=Testing&image=", mutable=True)
2001-        qd["image_file"] = {
2002-            "filename" : "test.jpg",
2003-            "content-type" : "image/jpeg",
2004-            "content" : img
2005-        }
2006-       
2007+        qd["image_file"] = SimpleUploadedFile('test.jpg', img, 'image/jpeg')
2008+
2009         manip = Photo.AddManipulator()
2010         manip.do_html2python(qd)
2011         p = manip.save(qd)
2012@@ -39,4 +36,4 @@
2013         Make sure to delete the "uploaded" file to avoid clogging /tmp.
2014         """
2015         p = Photo.objects.get()
2016-        os.unlink(p.get_image_filename())
2017\ No newline at end of file
2018+        os.unlink(p.get_image_filename())
2019Index: tests/regressiontests/forms/error_messages.py
2020===================================================================
2021--- tests/regressiontests/forms/error_messages.py       (revision 7484)
2022+++ tests/regressiontests/forms/error_messages.py       (working copy)
2023@@ -1,6 +1,7 @@
2024 # -*- coding: utf-8 -*-
2025 tests = r"""
2026 >>> from django.newforms import *
2027+>>> from django.core.files.uploadedfile import SimpleUploadedFile
2028 
2029 # CharField ###################################################################
2030 
2031@@ -214,11 +215,11 @@
2032 Traceback (most recent call last):
2033 ...
2034 ValidationError: [u'INVALID']
2035->>> f.clean({})
2036+>>> f.clean(SimpleUploadedFile('name', None))
2037 Traceback (most recent call last):
2038 ...
2039-ValidationError: [u'MISSING']
2040->>> f.clean({'filename': 'name', 'content':''})
2041+ValidationError: [u'EMPTY FILE']
2042+>>> f.clean(SimpleUploadedFile('name', ''))
2043 Traceback (most recent call last):
2044 ...
2045 ValidationError: [u'EMPTY FILE']
2046Index: tests/regressiontests/forms/tests.py
2047===================================================================
2048--- tests/regressiontests/forms/tests.py        (revision 7484)
2049+++ tests/regressiontests/forms/tests.py        (working copy)
2050@@ -26,6 +26,8 @@
2051 from regressions import tests as regression_tests
2052 from util import tests as util_tests
2053 from widgets import tests as widgets_tests
2054+from warnings import filterwarnings
2055+filterwarnings("ignore")
2056 
2057 __test__ = {
2058     'extra_tests': extra_tests,
2059Index: tests/regressiontests/forms/fields.py
2060===================================================================
2061--- tests/regressiontests/forms/fields.py       (revision 7484)
2062+++ tests/regressiontests/forms/fields.py       (working copy)
2063@@ -2,6 +2,7 @@
2064 tests = r"""
2065 >>> from django.newforms import *
2066 >>> from django.newforms.widgets import RadioFieldRenderer
2067+>>> from django.core.files.uploadedfile import SimpleUploadedFile
2068 >>> import datetime
2069 >>> import time
2070 >>> import re
2071@@ -773,12 +774,12 @@
2072 >>> f.clean({})
2073 Traceback (most recent call last):
2074 ...
2075-ValidationError: [u'No file was submitted.']
2076+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
2077 
2078 >>> f.clean({}, '')
2079 Traceback (most recent call last):
2080 ...
2081-ValidationError: [u'No file was submitted.']
2082+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
2083 
2084 >>> f.clean({}, 'files/test3.pdf')
2085 'files/test3.pdf'
2086@@ -788,20 +789,20 @@
2087 ...
2088 ValidationError: [u'No file was submitted. Check the encoding type on the form.']
2089 
2090->>> f.clean({'filename': 'name', 'content': None})
2091+>>> f.clean(SimpleUploadedFile('name', None))
2092 Traceback (most recent call last):
2093 ...
2094 ValidationError: [u'The submitted file is empty.']
2095 
2096->>> f.clean({'filename': 'name', 'content': ''})
2097+>>> f.clean(SimpleUploadedFile('name', ''))
2098 Traceback (most recent call last):
2099 ...
2100 ValidationError: [u'The submitted file is empty.']
2101 
2102->>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}))
2103+>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
2104 <class 'django.newforms.fields.UploadedFile'>
2105 
2106->>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}, 'files/test4.pdf'))
2107+>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))
2108 <class 'django.newforms.fields.UploadedFile'>
2109 
2110 # URLField ##################################################################
2111Index: tests/regressiontests/forms/forms.py
2112===================================================================
2113--- tests/regressiontests/forms/forms.py        (revision 7484)
2114+++ tests/regressiontests/forms/forms.py        (working copy)
2115@@ -1,6 +1,7 @@
2116 # -*- coding: utf-8 -*-
2117 tests = r"""
2118 >>> from django.newforms import *
2119+>>> from django.core.files.uploadedfile import SimpleUploadedFile
2120 >>> import datetime
2121 >>> import time
2122 >>> import re
2123@@ -1465,7 +1466,7 @@
2124 >>> print f
2125 <tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
2126 
2127->>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)
2128+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False)
2129 >>> print f
2130 <tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>
2131 
2132@@ -1473,7 +1474,7 @@
2133 >>> print f
2134 <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>
2135 
2136->>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)
2137+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False)
2138 >>> print f
2139 <tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
2140 >>> f.is_valid()
2141Index: tests/regressiontests/test_client_regress/views.py
2142===================================================================
2143--- tests/regressiontests/test_client_regress/views.py  (revision 7484)
2144+++ tests/regressiontests/test_client_regress/views.py  (working copy)
2145@@ -1,5 +1,6 @@
2146 from django.contrib.auth.decorators import login_required
2147 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
2148+import sha
2149 
2150 def no_template_view(request):
2151     "A simple view that expects a GET request, and returns a rendered template"
2152@@ -10,13 +11,41 @@
2153     Check that a file upload can be updated into the POST dictionary without
2154     going pear-shaped.
2155     """
2156+    from django.core.files.uploadedfile import UploadedFile
2157     form_data = request.POST.copy()
2158     form_data.update(request.FILES)
2159-    if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
2160+    if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], unicode):
2161         return HttpResponse('')
2162     else:
2163         return HttpResponseServerError()
2164 
2165+def file_upload_view_verify(request):
2166+    """
2167+    Use the sha digest hash to verify the uploaded contents.
2168+    """
2169+    from django.core.files.uploadedfile import UploadedFile
2170+    form_data = request.POST.copy()
2171+    form_data.update(request.FILES)
2172+
2173+    # Check to see if unicode names worked out.
2174+    if not request.FILES['file_unicode'].file_name.endswith(u'test_\u4e2d\u6587_Orl\xe9ans.jpg'):
2175+        return HttpResponseServerError()
2176+
2177+    for key, value in form_data.items():
2178+        if key.endswith('_hash'):
2179+            continue
2180+        if key + '_hash' not in form_data:
2181+            continue
2182+        submitted_hash = form_data[key + '_hash']
2183+        if isinstance(value, UploadedFile):
2184+            new_hash = sha.new(value.read()).hexdigest()
2185+        else:
2186+            new_hash = sha.new(value).hexdigest()
2187+        if new_hash != submitted_hash:
2188+            return HttpResponseServerError()
2189+
2190+    return HttpResponse('')
2191+
2192 def get_view(request):
2193     "A simple login protected view"
2194     return HttpResponse("Hello world")
2195@@ -37,4 +66,4 @@
2196 def login_protected_redirect_view(request):
2197     "A view that redirects all requests to the GET view"
2198     return HttpResponseRedirect('/test_client_regress/get_view/')
2199-login_protected_redirect_view = login_required(login_protected_redirect_view)
2200\ No newline at end of file
2201+login_protected_redirect_view = login_required(login_protected_redirect_view)
2202Index: tests/regressiontests/test_client_regress/models.py
2203===================================================================
2204--- tests/regressiontests/test_client_regress/models.py (revision 7484)
2205+++ tests/regressiontests/test_client_regress/models.py (working copy)
2206@@ -5,6 +5,7 @@
2207 from django.test import Client, TestCase
2208 from django.core.urlresolvers import reverse
2209 import os
2210+import sha
2211 
2212 class AssertContainsTests(TestCase):
2213     def test_contains(self):
2214@@ -243,6 +244,51 @@
2215         response = self.client.post('/test_client_regress/file_upload/', post_data)
2216         self.assertEqual(response.status_code, 200)
2217 
2218+    def test_large_upload(self):
2219+        import tempfile
2220+        dir = tempfile.gettempdir()
2221+
2222+        (fd, name1) = tempfile.mkstemp(suffix='.file1', dir=dir)
2223+        file1 = os.fdopen(fd, 'w+b')
2224+        file1.write('a' * (2 ** 21))
2225+        file1.seek(0)
2226+
2227+        (fd, name2) = tempfile.mkstemp(suffix='.file2', dir=dir)
2228+        file2 = os.fdopen(fd, 'w+b')
2229+        file2.write('a' * (10 * 2 ** 20))
2230+        file2.seek(0)
2231+
2232+        # This file contains chinese symbols for a name.
2233+        name3 = os.path.join(dir, u'test_&#20013;&#25991;_Orl\u00e9ans.jpg')
2234+        file3 = open(name3, 'w+b')
2235+        file3.write('b' * (2 ** 10))
2236+        file3.seek(0)
2237+
2238+        post_data = {
2239+            'name': 'Ringo',
2240+            'file_field1': file1,
2241+            'file_field2': file2,
2242+            'file_unicode': file3,
2243+            }
2244+
2245+        for key in post_data.keys():
2246+            try:
2247+                post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest()
2248+                post_data[key].seek(0)
2249+            except AttributeError:
2250+                post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest()
2251+
2252+        response = self.client.post('/test_client_regress/file_upload_verify/', post_data)
2253+
2254+        for name in (name1, name2, name3):
2255+            try:
2256+                os.unlink(name)
2257+            except:
2258+                pass
2259+
2260+        self.assertEqual(response.status_code, 200)
2261+
2262+
2263 class LoginTests(TestCase):
2264     fixtures = ['testdata']
2265 
2266Index: tests/regressiontests/test_client_regress/urls.py
2267===================================================================
2268--- tests/regressiontests/test_client_regress/urls.py   (revision 7484)
2269+++ tests/regressiontests/test_client_regress/urls.py   (working copy)
2270@@ -4,6 +4,7 @@
2271 urlpatterns = patterns('',
2272     (r'^no_template_view/$', views.no_template_view),
2273     (r'^file_upload/$', views.file_upload_view),
2274+    (r'^file_upload_verify/$', views.file_upload_view_verify),
2275     (r'^get_view/$', views.get_view),
2276     url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
2277     (r'^login_protected_redirect_view/$', views.login_protected_redirect_view)
2278Index: tests/regressiontests/datastructures/tests.py
2279===================================================================
2280--- tests/regressiontests/datastructures/tests.py       (revision 7484)
2281+++ tests/regressiontests/datastructures/tests.py       (working copy)
2282@@ -117,12 +117,23 @@
2283 >>> d['person']['2']['firstname']
2284 ['Adrian']
2285 
2286-### FileDict ################################################################
2287-
2288->>> d = FileDict({'content': 'once upon a time...'})
2289+### ImmutableList ################################################################
2290+>>> d = ImmutableList(range(10))
2291+>>> d.sort()
2292+Traceback (most recent call last):
2293+  File "<stdin>", line 1, in <module>
2294+  File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain
2295+    raise AttributeError, self.warning
2296+AttributeError: ImmutableList object is immutable.
2297 >>> repr(d)
2298-"{'content': '<omitted>'}"
2299->>> d = FileDict({'other-key': 'once upon a time...'})
2300->>> repr(d)
2301-"{'other-key': 'once upon a time...'}"
2302+'(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)'
2303+>>> d = ImmutableList(range(10), warning="Object is immutable!")
2304+>>> d[1]
2305+1
2306+>>> d[1] = 'test'
2307+Traceback (most recent call last):
2308+  File "<stdin>", line 1, in <module>
2309+  File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain
2310+    raise AttributeError, self.warning
2311+AttributeError: Object is immutable!
2312 """
2313Index: AUTHORS
2314===================================================================
2315--- AUTHORS     (revision 7484)
2316+++ AUTHORS     (working copy)
2317@@ -58,7 +58,7 @@
2318     Jökull Sólberg Auðunsson <jokullsolberg@gmail.com>
2319     Arthur <avandorp@gmail.com>
2320     David Avsajanishvili <avsd05@gmail.com>
2321-    axiak@mit.edu
2322+    Mike Axiak <axiak@mit.edu>
2323     Niran Babalola <niran@niran.org>
2324     Morten Bagai <m@bagai.com>
2325     Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
2326@@ -136,6 +136,7 @@
2327     Marc Fargas <telenieko@telenieko.com>
2328     Szilveszter Farkas <szilveszter.farkas@gmail.com>
2329     favo@exoweb.net
2330+    fdr <drfarina@gmail.com>
2331     Dmitri Fedortchenko <zeraien@gmail.com>
2332     Liang Feng <hutuworm@gmail.com>
2333     Bill Fenner <fenner@gmail.com>
2334Index: docs/upload_handling.txt
2335===================================================================
2336--- docs/upload_handling.txt    (revision 0)
2337+++ docs/upload_handling.txt    (revision 0)
2338@@ -0,0 +1,311 @@
2339+============
2340+File Uploads
2341+============
2342+
2343+**New in Django development version**
2344+
2345+Most Web sites wouldn't be complete without a way to upload files. Before the
2346+file even gets into ``request.FILES`` Django has to decide where to put the
2347+all the incoming data. This document describes how Django takes the incoming
2348+request and populates ``request.FILES``.
2349+
2350+.. _Request and response objects: ../request_response/#attributes
2351+
2352+Default Behavior
2353+================
2354+
2355+After setting up a quick model that contains a `FileField`_ and enabling the
2356+`Admin interface`_ you can quickly use a form to upload files to your site.
2357+By default, if the upload is smaller than **2.5 Megabytes** in size, Django
2358+will hold the entire contents of the upload in memory, and the file will
2359+quickly be saved to its final destination (as defined by
2360+`settings.MEDIA_ROOT`_) without any intermediary.
2361+
2362+If the entire upload is larger than 2.5 Megabytes, then it will -- by
2363+default -- write the contents of the uploaded file to a temporary file
2364+in your operating system's default temporary directory. On a posix platform,
2365+this means that you could expect Django to generate a file similar in name
2366+to ``/tmp/tmpzfp6I6.upload``. During the upload, you may notice this file
2367+grow in size as Django loads the data onto disk.
2368+
2369+.. note::
2370+    You may find that the default temporary directory is not writable.
2371+    This is especially true if you are on shared hosting. If this is
2372+    the case, Django will raise an error when you try to upload. Please
2373+    read below in `Extending the Default`_ to change this directory.
2374+
2375+
2376+.. _FileField: ../model-api/#filefield
2377+.. _Admin interface: ../tutorial02/#activate-the-admin-site
2378+.. _settings.MEDIA_ROOT: ../settings/#media-root
2379+
2380+Extending the Default
2381+=====================
2382+
2383+Suppose you have quite a bit of memory. You may decide that you want Django
2384+to stream only if the entire upload exceeds 10 Megabytes. You may even
2385+decide that whatever directory is the platform default is not suitable
2386+for your project. If this is the case, Django provides these two settings:
2387+
2388+    =============================== ======================================
2389+    Setting                         Description
2390+    =============================== ======================================
2391+    ``FILE_UPLOAD_MAX_MEMORY_SIZE`` The maximum size of a request in bytes
2392+                                    for which Django will try to load the
2393+                                    entire upload contents in memory.
2394+
2395+    ``FILE_UPLOAD_TEMP_DIR``        The directory on the file system where
2396+                                    uploaded contents will be temporarily
2397+                                    stored if not completely in memory.
2398+                                    E.g.: ``"/tmp"``
2399+    =============================== ======================================
2400+
2401+There is one final setting -- ``FILE_UPLOAD_HANDLERS`` -- which allows complete
2402+customization of the upload process.
2403+
2404+Upload Handlers
2405+===============
2406+
2407+Through upload handlers Django provides the flexibility to extend the
2408+upload process beyond simple storage. You can use custom handlers to enforce
2409+user-level quotas, compress data on the fly, render progress bars, and
2410+even send data to another warehouse directly without storing it locally.
2411+
2412+There are two pieces to the Django upload handling: the upload handler and the
2413+uploaded file. These are both represented by python classes --
2414+``FileUploadHandler`` and ``UploadedFile`` respectively. Throughout the lifetime
2415+of the upload process, Django will call on the upload handler to handle the
2416+upload, while the upload handler is expected to provide Django an ``UploadedFile``
2417+object to successfully complete a file's upload.
2418+
2419+Setting Default Upload Handlers for your Project
2420+------------------------------------------------
2421+
2422+Similar to `Middleware`_, upload handlers have an order that is initially
2423+defined in ``settings.FILE_UPLOAD_HANDLERS``. The default value is::
2424+
2425+    ("django.core.files.fileuploadhandler.MemoryFileUploadHandler",
2426+     "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",)
2427+
2428+This literally means: Try putting the upload in memory first and failing
2429+that put the upload in a temporary file.
2430+
2431+This behavior, however, is completely dependent on how each of those handlers
2432+is written. For example, if someone gave you a ``Rot13UploadHandler``, you can
2433+update your ``settings`` to contain::
2434+
2435+   FILE_UPLOAD_HANDLERS = (
2436+     "app.uploadhandlers.rot13.Rot13UploadHandler",
2437+     "django.core.files.fileuploadhandler.MemoryFileUploadHandler",
2438+     "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",
2439+   )
2440+
2441+And the ``Rot13UploadHandler`` will perform the ``rot13`` operation on all
2442+data before the data get to subsequent handlers.
2443+
2444+.. _Middleware: ../middleware/
2445+
2446+Modifying your Upload Handlers Dynamically
2447+------------------------------------------
2448+
2449+During the lifetime of your project, you may realize that a particular
2450+view or views require different uploading behavior. For this reason,
2451+the ``request`` object contains a list of handlers
2452+(``request.upload_handlers``) that will be called in order. To append
2453+a handler to the list, you would append to it like any other list.
2454+For example, suppose you had an ``ProgressBarUploadHandler`` class.
2455+To append it to your upload handlers you would write::
2456+
2457+    request.upload_handlers.append(ProgressBarUploadHandler())
2458+
2459+However, since the progress bar handler would probably need to run before the
2460+other handlers get a chance, you'd probably want to insert it. That is, you'd
2461+write::
2462+
2463+   request.upload_handlers.insert(0, ProgressBarUploadHandler())
2464+
2465+If you want to replace the upload handlers completely, you can just assign a new
2466+list::
2467+
2468+   request.upload_handlers = [ProgressBarUploadHandler()]
2469+
2470+And all Django will do is keep a progress, but do nothing with the file itself!
2471+One more note: After the upload has completed, you are no longer allowed to
2472+assign or modify this list, and Django will raise an error if you try to modify
2473+this list after an upload.
2474+
2475+Writing a File Upload Handler
2476+-----------------------------
2477+
2478+All file upload handlers are subclasses of ``FileUploadHandler``, found in
2479+``django.core.files.fileuploadhandler``. To create your handler, you need to
2480+define the required methods followed by the methods that make most sense to you:
2481+
2482+chunk_size
2483+~~~~~~~~~~
2484+
2485+An integer attribute that specifies what sized chunks Django should store
2486+into memory and feed into the handler. The chunk sizes should be divisible by
2487+``4`` and should not exceed ``2 ** 31 - 1`` in size. When there are multiple
2488+chunk sizes provided by multiple handlers, Django will use the smallest chunk
2489+size.
2490+
2491+__init__
2492+~~~~~~~~
2493+
2494+The constructor, which should optionally support taking a request object as its
2495+first argument. When called via ``settings.FILE_UPLOAD_HANDLERS``, it will be
2496+passed a request object.
2497+
2498+Interface: ``__init__(self, request=None)``
2499+
2500+``request`` is the request object. Do not access ``request.POST`` or
2501+``request.FILES``.
2502+
2503+new_file
2504+~~~~~~~~
2505+
2506+``new_file`` signals that a new file is starting. You can initialize a file or
2507+whatever else is needed on a new file upload.
2508+
2509+Interface: ``new_file(self, field_name, file_name, content_type, content_length,
2510+charset)``
2511+
2512+``field_name`` is a string name of the field this was POSTed as.
2513+
2514+``file_name`` is the unicode filename that was provided by the browser.
2515+
2516+``content_type`` is the MIME type provided by the browser -- E.g.
2517+``'image/jpeg'``.
2518+
2519+``content_length`` is the length of the image given by the browser if provided,
2520+``None`` otherwise.
2521+
2522+``charset`` is the charset given by the browser if provided, ``None`` otherwise.
2523+
2524+Returns: ``None`` if you want other handlers to get ``new_file`` called.
2525+Something nonzero if you don't want subsequent handlers to get a chance.
2526+
2527+receive_data_chunk
2528+~~~~~~~~~~~~~~~~~~
2529+*required*
2530+
2531+Receives a segment of data from the file upload. For example: The
2532+``TemporaryFileUploadHandler`` takes the data and writes it to disk.
2533+
2534+Interface: ``receive_data_chunk(self, raw_data, start)``
2535+
2536+``raw_data`` is a byte string containing the uploaded data.
2537+
2538+``start`` is the position in the file where this ``raw_data`` chunk begins.
2539+
2540+Returns: ``None`` if you don't want the subsequent upload handlers to receive
2541+the data. Whatever else you return gets fed into the subsequent upload handlers'
2542+``receive_data_chunk`` method. In this way, one handler can be a "filter" for
2543+other handlers.
2544+
2545+Exceptions: If you raise a ``StopUpload`` or a ``SkipFile`` exception, the
2546+upload will abort or the file will be skipped respectively.
2547+
2548+file_complete
2549+~~~~~~~~~~~~~
2550+*required*
2551+
2552+Signals that a file has finished uploading. Expected to return an
2553+``UploadedFile`` object if the file has been packaged successfully.
2554+
2555+Interface: ``file_complete(self, file_size)``
2556+
2557+``file_size`` is the number of bytes you have received for this file.
2558+
2559+Returns: An ``UploadedFile`` object to set in the ``request.FILES`` dictionary.
2560+``None`` if you want the subsequent upload handlers to get an ``UploadedFile``
2561+object.
2562+
2563+upload_complete
2564+~~~~~~~~~~~~~~~
2565+
2566+Defines when the entire upload has completed.
2567+
2568+Interface: ``upload_complete(self)``
2569+
2570+handle_raw_input
2571+~~~~~~~~~~~~~~~~
2572+
2573+Allows the handler to completely override the parsing of the raw HTTP-layered
2574+input.
2575+
2576+Interface: ``handle_raw_input(self, input_data, META, content_length, boundary,
2577+encoding)``
2578+
2579+``input_data`` is a file-like object that supports the read operation.
2580+
2581+``META`` is the same object as ``request.META``.
2582+
2583+``content_length`` is the length of the data in ``input_data``. Don't read more
2584+than ``content_length`` bytes from ``input_data``.
2585+
2586+``boundary`` is the MIME boundary for this request.
2587+
2588+``encoding`` is the encoding of the request.
2589+
2590+Returns: ``None`` if you want to go on to the next stage, ``(POST, FILES)`` if
2591+you want to return the new data structures suitable for the request directly.
2592+
2593+Defining an Uploaded File
2594+-------------------------
2595+
2596+All file upload handlers are subclasses of ``UploadedFile``, found in
2597+``django.core.files.uploadedfile``. The uploaded file object is returned by the
2598+handler above in ``file_complete``. To create your own uploaded file class, you
2599+need to define the required methods followed by the methods that make most sense
2600+to you:
2601+
2602+read
2603+~~~~
2604+*required*
2605+
2606+Interface: ``read(self, num_bytes=None)``
2607+
2608+Returns: A byte string of length ``num_bytes`` (or the size of the file if
2609+``num_bytes`` is not supplied).
2610+
2611+chunk
2612+~~~~~
2613+
2614+A generator to yield small chunks from the file. With the ``read()`` defined,
2615+the ``UploadedFile`` class already defines a ``chunk()`` method that's probably
2616+suitable.
2617+
2618+Interface: ``chunk(self, chunk_size=None)``
2619+
2620+multiple_chunks
2621+~~~~~~~~~~~~~~~
2622+
2623+Interface: ``multiple_chunks(self, chunk_size=None)``
2624+
2625+Returns: ``True`` or ``False`` depending on whether or not the user of this
2626+file can expect more than one chunk when calling ``chunk(self, chunk_size)``.
2627+If all the data is in memory, you should return ``False``.
2628+
2629+temporary_file_path
2630+~~~~~~~~~~~~~~~~~~~
2631+
2632+If defined, this method should return the file path of the file on the
2633+operating system. This will let users move files rather than write to
2634+new files if the option is available.
2635+
2636+Interface: ``temporary_file_path(self)``
2637+
2638+Returns: A file path in a file system on the local computer.
2639+
2640+upload_errors
2641+~~~~~~~~~~~~~
2642+
2643+If defined, this method should return a string describing errors that
2644+occured during uploads.
2645+
2646+Interface: ``upload_errors(self)``
2647+
2648+Returns: A ``unicode`` string describing an error that occured with the
2649+file's upload.
2650Index: docs/request_response.txt
2651===================================================================
2652--- docs/request_response.txt   (revision 7484)
2653+++ docs/request_response.txt   (working copy)
2654@@ -80,20 +80,24 @@
2655     strings.
2656 
2657 ``FILES``
2658+   **New in Django development version**
2659     A dictionary-like object containing all uploaded files. Each key in
2660     ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each
2661-    value in ``FILES`` is a standard Python dictionary with the following three
2662-    keys:
2663+    value in ``FILES`` is an ``UploadedFile`` object containing at least the
2664+    following attributes:
2665 
2666-        * ``filename`` -- The name of the uploaded file, as a Python string.
2667-        * ``content-type`` -- The content type of the uploaded file.
2668-        * ``content`` -- The raw content of the uploaded file.
2669+        * ``read(num_bytes=None)`` -- Read a number of bytes from the file.
2670+        * ``file_name`` -- The name of the uploaded file.
2671+        * ``file_size`` -- The size, in bytes, of the uploaded file.
2672+       * ``chunk()`` -- A generator that yields sequential chunks of data.
2673 
2674-    Note that ``FILES`` will only contain data if the request method was POST
2675-    and the ``<form>`` that posted to the request had
2676-    ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank
2677-    dictionary-like object.
2678+    See `File Uploads`_ for more information. Note that ``FILES`` will only
2679+    contain data if the request method was POST and the ``<form>`` that posted
2680+    to the request had ``enctype="multipart/form-data"``. Otherwise, ``FILES``
2681+    will be a blank dictionary-like object.
2682 
2683+    .. _File Uploads: ../upload_handling/
2684+
2685 ``META``
2686     A standard Python dictionary containing all available HTTP headers.
2687     Available headers depend on the client and server, but here are some
2688Index: docs/settings.txt
2689===================================================================
2690--- docs/settings.txt   (revision 7484)
2691+++ docs/settings.txt   (working copy)
2692@@ -279,7 +279,7 @@
2693 
2694 The database backend to use. The build-in database backends are
2695 ``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
2696-``'sqlite3'`` and ``'oracle'``.
2697+``'sqlite3'``, ``'oracle'``, and ``'oracle'``.
2698 
2699 In the Django development version, you can use a database backend that doesn't
2700 ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
2701@@ -513,6 +513,36 @@
2702 The character encoding used to decode any files read from disk. This includes
2703 template files and initial SQL data files.
2704 
2705+FILE_UPLOAD_HANDLERS
2706+--------------------
2707+
2708+**New in Django development version**
2709+
2710+Default::
2711+
2712+    ("django.core.files.fileuploadhandler.MemoryFileUploadHandler",
2713+     "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",)
2714+
2715+A tuple of handlers to use for uploading.
2716+
2717+FILE_UPLOAD_MAX_MEMORY_SIZE
2718+---------------------------
2719+
2720+**New in Django development version**
2721+
2722+Default: ``2621440``
2723+
2724+The maximum size (in bytes) that an upload will be before it gets streamed to the file system.
2725+
2726+FILE_UPLOAD_TEMP_DIR
2727+--------------------
2728+
2729+**New in Django development version**
2730+
2731+Default: ``None``
2732+
2733+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.
2734+
2735 FIXTURE_DIRS
2736 -------------
2737 
2738Index: docs/newforms.txt
2739===================================================================
2740--- docs/newforms.txt   (revision 7484)
2741+++ docs/newforms.txt   (working copy)
2742@@ -805,12 +805,12 @@
2743 need to bind the file data containing the mugshot image::
2744 
2745     # Bound form with an image field
2746+    >>> from django.core.files.uploadedfile import SimpleUploadedFile
2747     >>> data = {'subject': 'hello',
2748     ...         'message': 'Hi there',
2749     ...         'sender': 'foo@example.com',
2750     ...         'cc_myself': True}
2751-    >>> file_data = {'mugshot': {'filename':'face.jpg'
2752-    ...                          'content': <file data>}}
2753+    >>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
2754     >>> f = ContactFormWithMugshot(data, file_data)
2755 
2756 In practice, you will usually specify ``request.FILES`` as the source