~ubuntu-branches/ubuntu/saucy/python-django/saucy-updates

« back to all changes in this revision

Viewing changes to django/http/multipartparser.py

  • Committer: Package Import Robot
  • Author(s): Luke Faraone, Jakub Wilk, Luke Faraone
  • Date: 2013-05-09 15:10:47 UTC
  • mfrom: (1.1.21) (4.4.27 sid)
  • Revision ID: package-import@ubuntu.com-20130509151047-aqv8d71oj9wvcv8c
Tags: 1.5.1-2
[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

[ Luke Faraone ]
* Upload to unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
Exposes one class, ``MultiPartParser``, which feeds chunks of uploaded data to
5
5
file upload handlers for processing.
6
6
"""
 
7
from __future__ import unicode_literals
7
8
 
 
9
import base64
8
10
import cgi
 
11
 
9
12
from django.conf import settings
10
13
from django.core.exceptions import SuspiciousOperation
11
14
from django.utils.datastructures import MultiValueDict
12
 
from django.utils.encoding import force_unicode
 
15
from django.utils.encoding import force_text
 
16
from django.utils import six
13
17
from django.utils.text import unescape_entities
14
18
from django.core.files.uploadhandler import StopUpload, SkipFile, StopFutureHandlers
15
19
 
59
63
            raise MultiPartParserError('Invalid Content-Type: %s' % content_type)
60
64
 
61
65
        # Parse the header to get the boundary to split the parts.
62
 
        ctypes, opts = parse_header(content_type)
 
66
        ctypes, opts = parse_header(content_type.encode('ascii'))
63
67
        boundary = opts.get('boundary')
64
68
        if not boundary or not cgi.valid_boundary(boundary):
65
69
            raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
66
70
 
67
 
 
68
71
        # Content-Length should contain the length of the body we are about
69
72
        # to receive.
70
73
        try:
71
 
            content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0)))
 
74
            content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH', 0)))
72
75
        except (ValueError, TypeError):
73
76
            content_length = 0
74
77
 
76
79
            # This means we shouldn't continue...raise an error.
77
80
            raise MultiPartParserError("Invalid content length: %r" % content_length)
78
81
 
 
82
        if isinstance(boundary, six.text_type):
 
83
            boundary = boundary.encode('ascii')
79
84
        self._boundary = boundary
80
85
        self._input_data = input_data
81
86
 
105
110
        # HTTP spec says that Content-Length >= 0 is valid
106
111
        # handling content-length == 0 before continuing
107
112
        if self._content_length == 0:
108
 
            return QueryDict(MultiValueDict(), encoding=self._encoding), MultiValueDict()
 
113
            return QueryDict('', encoding=self._encoding), MultiValueDict()
109
114
 
110
115
        # See if the handler will want to take care of the parsing.
111
116
        # This allows overriding everything if somebody wants it.
147
152
                transfer_encoding = meta_data.get('content-transfer-encoding')
148
153
                if transfer_encoding is not None:
149
154
                    transfer_encoding = transfer_encoding[0].strip()
150
 
                field_name = force_unicode(field_name, encoding, errors='replace')
 
155
                field_name = force_text(field_name, encoding, errors='replace')
151
156
 
152
157
                if item_type == FIELD:
153
158
                    # This is a post field, we can just set it in the post
161
166
                        data = field_stream.read()
162
167
 
163
168
                    self._post.appendlist(field_name,
164
 
                                          force_unicode(data, encoding, errors='replace'))
 
169
                                          force_text(data, encoding, errors='replace'))
165
170
                elif item_type == FILE:
166
171
                    # This is a file, use the handler...
167
172
                    file_name = disposition.get('filename')
168
173
                    if not file_name:
169
174
                        continue
170
 
                    file_name = force_unicode(file_name, encoding, errors='replace')
 
175
                    file_name = force_text(file_name, encoding, errors='replace')
171
176
                    file_name = self.IE_sanitize(unescape_entities(file_name))
172
177
 
173
178
                    content_type = meta_data.get('content-type', ('',))[0].strip()
174
179
                    try:
175
 
                        charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
 
180
                        charset = meta_data.get('content-type', (0, {}))[1].get('charset', None)
176
181
                    except:
177
182
                        charset = None
178
183
 
194
199
                        for chunk in field_stream:
195
200
                            if transfer_encoding == 'base64':
196
201
                                # We only special-case base64 transfer encoding
 
202
                                # We should always read base64 streams by multiple of 4
 
203
                                over_bytes = len(chunk) % 4
 
204
                                if over_bytes:
 
205
                                    over_chunk = field_stream.read(4 - over_bytes)
 
206
                                    chunk += over_chunk
 
207
 
197
208
                                try:
198
 
                                    chunk = str(chunk).decode('base64')
199
 
                                except Exception, e:
 
209
                                    chunk = base64.b64decode(chunk)
 
210
                                except Exception as e:
200
211
                                    # Since this is only a chunk, any error is an unfixable error.
201
212
                                    raise MultiPartParserError("Could not decode base64 data: %r" % e)
202
213
 
209
220
                                    # If the chunk received by the handler is None, then don't continue.
210
221
                                    break
211
222
 
212
 
                    except SkipFile, e:
 
223
                    except SkipFile:
213
224
                        # Just use up the rest of this file...
214
225
                        exhaust(field_stream)
215
226
                    else:
218
229
                else:
219
230
                    # If this is neither a FIELD or a FILE, just exhaust the stream.
220
231
                    exhaust(stream)
221
 
        except StopUpload, e:
 
232
        except StopUpload as e:
222
233
            if not e.connection_reset:
223
234
                exhaust(self._input_data)
224
235
        else:
241
252
            file_obj = handler.file_complete(counters[i])
242
253
            if file_obj:
243
254
                # If it returns a file object, then set the files dict.
244
 
                self._files.appendlist(force_unicode(old_field_name,
 
255
                self._files.appendlist(force_text(old_field_name,
245
256
                                                     self._encoding,
246
257
                                                     errors='replace'),
247
258
                                       file_obj)
251
262
        """Cleanup filename from Internet Explorer full paths."""
252
263
        return filename and filename[filename.rfind("\\")+1:].strip()
253
264
 
254
 
class LazyStream(object):
 
265
class LazyStream(six.Iterator):
255
266
    """
256
267
    The LazyStream wrapper allows one to get and "unget" bytes from a stream.
257
268
 
268
279
        """
269
280
        self._producer = producer
270
281
        self._empty = False
271
 
        self._leftover = ''
 
282
        self._leftover = b''
272
283
        self.length = length
273
284
        self.position = 0
274
285
        self._remaining = length
282
293
            remaining = (size is not None and [size] or [self._remaining])[0]
283
294
            # do the whole thing in one shot if no limit was provided.
284
295
            if remaining is None:
285
 
                yield ''.join(self)
 
296
                yield b''.join(self)
286
297
                return
287
298
 
288
299
            # otherwise do some bookkeeping to return exactly enough
291
302
            while remaining != 0:
292
303
                assert remaining > 0, 'remaining bytes to read should never go negative'
293
304
 
294
 
                chunk = self.next()
 
305
                chunk = next(self)
295
306
 
296
307
                emitting = chunk[:remaining]
297
308
                self.unget(chunk[remaining:])
298
309
                remaining -= len(emitting)
299
310
                yield emitting
300
311
 
301
 
        out = ''.join(parts())
 
312
        out = b''.join(parts())
302
313
        return out
303
314
 
304
 
    def next(self):
 
315
    def __next__(self):
305
316
        """
306
317
        Used when the exact number of bytes to read is unimportant.
307
318
 
311
322
        """
312
323
        if self._leftover:
313
324
            output = self._leftover
314
 
            self._leftover = ''
 
325
            self._leftover = b''
315
326
        else:
316
 
            output = self._producer.next()
 
327
            output = next(self._producer)
317
328
            self._unget_history = []
318
329
        self.position += len(output)
319
330
        return output
341
352
            return
342
353
        self._update_unget_history(len(bytes))
343
354
        self.position -= len(bytes)
344
 
        self._leftover = ''.join([bytes, self._leftover])
 
355
        self._leftover = b''.join([bytes, self._leftover])
345
356
 
346
357
    def _update_unget_history(self, num_bytes):
347
358
        """
362
373
                " if there is none, report this to the Django developers."
363
374
            )
364
375
 
365
 
class ChunkIter(object):
 
376
class ChunkIter(six.Iterator):
366
377
    """
367
378
    An iterable that will yield chunks of data. Given a file-like object as the
368
379
    constructor, this object will yield chunks of read operations from that
372
383
        self.flo = flo
373
384
        self.chunk_size = chunk_size
374
385
 
375
 
    def next(self):
 
386
    def __next__(self):
376
387
        try:
377
388
            data = self.flo.read(self.chunk_size)
378
389
        except InputStreamExhausted:
385
396
    def __iter__(self):
386
397
        return self
387
398
 
388
 
class InterBoundaryIter(object):
 
399
class InterBoundaryIter(six.Iterator):
389
400
    """
390
401
    A Producer that will iterate over boundaries.
391
402
    """
396
407
    def __iter__(self):
397
408
        return self
398
409
 
399
 
    def next(self):
 
410
    def __next__(self):
400
411
        try:
401
412
            return LazyStream(BoundaryIter(self._stream, self._boundary))
402
413
        except InputStreamExhausted:
403
414
            raise StopIteration()
404
415
 
405
 
class BoundaryIter(object):
 
416
class BoundaryIter(six.Iterator):
406
417
    """
407
418
    A Producer that is sensitive to boundaries.
408
419
 
410
421
    before the boundary, throw away the boundary bytes themselves, and push the
411
422
    post-boundary bytes back on the stream.
412
423
 
413
 
    The future calls to .next() after locating the boundary will raise a
 
424
    The future calls to next() after locating the boundary will raise a
414
425
    StopIteration exception.
415
426
    """
416
427
 
437
448
    def __iter__(self):
438
449
        return self
439
450
 
440
 
    def next(self):
 
451
    def __next__(self):
441
452
        if self._done:
442
453
            raise StopIteration()
443
454
 
459
470
        if not chunks:
460
471
            raise StopIteration()
461
472
 
462
 
        chunk = ''.join(chunks)
 
473
        chunk = b''.join(chunks)
463
474
        boundary = self._find_boundary(chunk, len(chunk) < self._rollback)
464
475
 
465
476
        if boundary:
495
506
            end = index
496
507
            next = index + len(self._boundary)
497
508
            # backup over CRLF
498
 
            if data[max(0,end-1)] == '\n':
 
509
            last = max(0, end-1)
 
510
            if data[last:last+1] == b'\n':
499
511
                end -= 1
500
 
            if data[max(0,end-1)] == '\r':
 
512
            last = max(0, end-1)
 
513
            if data[last:last+1] == b'\r':
501
514
                end -= 1
502
515
            return end, next
503
516
 
531
544
    # 'find' returns the top of these four bytes, so we'll
532
545
    # need to munch them later to prevent them from polluting
533
546
    # the payload.
534
 
    header_end = chunk.find('\r\n\r\n')
 
547
    header_end = chunk.find(b'\r\n\r\n')
535
548
 
536
549
    def _parse_header(line):
537
550
        main_value_pair, params = parse_header(line)
557
570
    outdict = {}
558
571
 
559
572
    # Eliminate blank lines
560
 
    for line in header.split('\r\n'):
 
573
    for line in header.split(b'\r\n'):
561
574
        # This terminology ("main value" and "dictionary of
562
575
        # parameters") is from the Python docs.
563
576
        try:
580
593
class Parser(object):
581
594
    def __init__(self, stream, boundary):
582
595
        self._stream = stream
583
 
        self._separator = '--' + boundary
 
596
        self._separator = b'--' + boundary
584
597
 
585
598
    def __iter__(self):
586
599
        boundarystream = InterBoundaryIter(self._stream, self._separator)
589
602
            yield parse_boundary_stream(sub_stream, 1024)
590
603
 
591
604
def parse_header(line):
592
 
    """ Parse the header into a key-value. """
593
 
    plist = _parse_header_params(';' + line)
594
 
    key = plist.pop(0).lower()
 
605
    """ Parse the header into a key-value.
 
606
        Input (line): bytes, output: unicode for key/name, bytes for value which
 
607
        will be decoded later
 
608
    """
 
609
    plist = _parse_header_params(b';' + line)
 
610
    key = plist.pop(0).lower().decode('ascii')
595
611
    pdict = {}
596
612
    for p in plist:
597
 
        i = p.find('=')
 
613
        i = p.find(b'=')
598
614
        if i >= 0:
599
 
            name = p[:i].strip().lower()
 
615
            name = p[:i].strip().lower().decode('ascii')
600
616
            value = p[i+1:].strip()
601
 
            if len(value) >= 2 and value[0] == value[-1] == '"':
 
617
            if len(value) >= 2 and value[:1] == value[-1:] == b'"':
602
618
                value = value[1:-1]
603
 
                value = value.replace('\\\\', '\\').replace('\\"', '"')
 
619
                value = value.replace(b'\\\\', b'\\').replace(b'\\"', b'"')
604
620
            pdict[name] = value
605
621
    return key, pdict
606
622
 
607
623
def _parse_header_params(s):
608
624
    plist = []
609
 
    while s[:1] == ';':
 
625
    while s[:1] == b';':
610
626
        s = s[1:]
611
 
        end = s.find(';')
612
 
        while end > 0 and s.count('"', 0, end) % 2:
613
 
            end = s.find(';', end + 1)
 
627
        end = s.find(b';')
 
628
        while end > 0 and s.count(b'"', 0, end) % 2:
 
629
            end = s.find(b';', end + 1)
614
630
        if end < 0:
615
631
            end = len(s)
616
632
        f = s[:end]