diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/parsers.py | 83 | ||||
| -rw-r--r-- | rest_framework/tests/parsers.py | 33 | 
2 files changed, 114 insertions, 2 deletions
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 491acd68..27a0db65 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -6,9 +6,10 @@ on the request, such as form content or json encoded data.  """  from __future__ import unicode_literals  from django.conf import settings +from django.core.files.uploadhandler import StopFutureHandlers  from django.http import QueryDict  from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser -from django.http.multipartparser import MultiPartParserError +from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter  from rest_framework.compat import yaml, etree  from rest_framework.exceptions import ParseError  from rest_framework.compat import six @@ -205,3 +206,83 @@ class XMLParser(BaseParser):              pass          return value + + +class FileUploadParser(BaseParser): +    """ +    Parser for file upload data. +    """ +    media_type = '*/*' + +    def parse(self, stream, media_type=None, parser_context=None): +        """ +        Returns a DataAndFiles object. + +        `.data` will be None (we expect request body to be a file content). +        `.files` will be a `QueryDict` containing one 'file' elemnt - a parsed file. +        """ + +        parser_context = parser_context or {} +        request = parser_context['request'] +        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) +        meta = request.META +        upload_handlers = request.upload_handlers +        filename = self.get_filename(stream, media_type, parser_context) + +        content_type = meta.get('HTTP_CONTENT_TYPE', meta.get('CONTENT_TYPE', '')) +        try: +            content_length = int(meta.get('HTTP_CONTENT_LENGTH', meta.get('CONTENT_LENGTH', 0))) +        except (ValueError, TypeError): +            content_length = None + +        # See if the handler will want to take care of the parsing. +        for handler in upload_handlers: +            result = handler.handle_raw_input(None, +                                              meta, +                                              content_length, +                                              None, +                                              encoding) +            if result is not None: +                return DataAndFiles(None, {'file': result[1]}) + +        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] +        chunk_size = min([2**31-4] + possible_sizes) +        chunks = ChunkIter(stream, chunk_size) +        counters = [0] * len(upload_handlers) + +        for handler in upload_handlers: +            try: +                handler.new_file(None, filename, content_type, content_length, encoding) +            except StopFutureHandlers: +                break + +        for chunk in chunks: +            for i, handler in enumerate(upload_handlers): +                chunk_length = len(chunk) +                chunk = handler.receive_data_chunk(chunk, counters[i]) +                counters[i] += chunk_length +                if chunk is None: +                    # If the chunk received by the handler is None, then don't continue. +                    break + +        for i, handler in enumerate(upload_handlers): +            file_obj = handler.file_complete(counters[i]) +            if file_obj: +                return DataAndFiles(None, {'file': file_obj}) +        raise ParseError("FileUpload parse error - none of upload handlers can handle the stream") + +    def get_filename(self, stream, media_type, parser_context): +        """ +        Detects the uploaded file name. First searches a 'filename' url kwarg. +        Then tries to parse Content-Disposition header. +        """ +        try: +            return parser_context['kwargs']['filename'] +        except KeyError: +            pass +        try: +            meta = parser_context['request'].META +            disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION']) +            return disposition[1]['filename'] +        except (AttributeError, KeyError): +            pass diff --git a/rest_framework/tests/parsers.py b/rest_framework/tests/parsers.py index 539c5b44..7699e10c 100644 --- a/rest_framework/tests/parsers.py +++ b/rest_framework/tests/parsers.py @@ -1,10 +1,11 @@  from __future__ import unicode_literals  from rest_framework.compat import StringIO  from django import forms +from django.core.files.uploadhandler import MemoryFileUploadHandler  from django.test import TestCase  from django.utils import unittest  from rest_framework.compat import etree -from rest_framework.parsers import FormParser +from rest_framework.parsers import FormParser, FileUploadParser  from rest_framework.parsers import XMLParser  import datetime @@ -82,3 +83,33 @@ class TestXMLParser(TestCase):          parser = XMLParser()          data = parser.parse(self._complex_data_input)          self.assertEqual(data, self._complex_data) + + +class TestFileUploadParser(TestCase): +    def setUp(self): +        class MockRequest(object): +            pass +        from io import BytesIO +        self.stream = BytesIO( +            "Test text file".encode('utf-8') +        ) +        request = MockRequest() +        request.upload_handlers = (MemoryFileUploadHandler(),) +        request.META = { +            'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt'.encode('utf-8'), +            'HTTP_CONTENT_LENGTH': 14, +        } +        self.parser_context = {'request': request, 'kwargs': {}} + +    def test_parse(self): +        """ Make sure the `QueryDict` works OK """ +        parser = FileUploadParser() +        self.stream.seek(0) +        data_and_files = parser.parse(self.stream, None, self.parser_context) +        file_obj = data_and_files.files['file'] +        self.assertEqual(file_obj._size, 14) + +    def test_get_filename(self): +        parser = FileUploadParser() +        filename = parser.get_filename(self.stream, None, self.parser_context) +        self.assertEqual(filename, 'file.txt'.encode('utf-8'))  | 
