From 8a9f34b838adc285f2d877846fda19349e28ab30 Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 14:18:10 +0400 Subject: [PATCH 1/8] Added get filename as encoded This filename described in RFC 6266 --- rest_framework/parsers.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index aa4fd3f11..f285ac975 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -11,7 +11,7 @@ from django.http import QueryDict from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter from django.utils import six -from rest_framework.compat import etree, yaml, force_text +from rest_framework.compat import etree, yaml, force_text, urlparse from rest_framework.exceptions import ParseError from rest_framework import renderers import json @@ -290,6 +290,17 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) - return force_text(disposition[1]['filename']) + + filename = force_text(disposition[1]['filename']) + + if 'filename*' in disposition[1]: + filename_encoded = force_text(disposition[1]['filename*']) + try: + charset, filename_encoded = filename_encoded.split('\'\'', 1) + filename = urlparse.unquote(filename_encoded, charset) + except (ValueError, LookupError): + pass + + return filename except (AttributeError, KeyError): pass From 6e90dad8e3155deb8bc27c4b9e764b90fde24e4a Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 14:25:30 +0400 Subject: [PATCH 2/8] Added test for encoded filename --- tests/test_parsers.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_parsers.py b/tests/test_parsers.py index 8af906772..aafb431a2 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -113,3 +113,27 @@ class TestFileUploadParser(TestCase): parser = FileUploadParser() filename = parser.get_filename(self.stream, None, self.parser_context) self.assertEqual(filename, 'file.txt') + + def test_get_encoded_filename(self): + parser = FileUploadParser() + + self.__replace_content_disposition('inline; filename*=utf-8\'\'ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'ÀĥƦ.txt') + + self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8\'\'ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'ÀĥƦ.txt') + + self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8--ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'fallback.txt') + + self.__replace_content_disposition('inline; filename=fallback.txt; filename*=WRONG\'\'ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'fallback.txt') + + def __replace_content_disposition(self, disposition): + self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition + + From 644f9b54219c884b59793eafbbdafa0d1175f1ab Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 15:02:52 +0400 Subject: [PATCH 3/8] Removed assert for wrong charset --- tests/test_parsers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_parsers.py b/tests/test_parsers.py index aafb431a2..d19b46519 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -1,3 +1,5 @@ +#-*- coding: utf-8 -*- + from __future__ import unicode_literals from rest_framework.compat import StringIO from django import forms @@ -129,10 +131,6 @@ class TestFileUploadParser(TestCase): filename = parser.get_filename(self.stream, None, self.parser_context) self.assertEqual(filename, 'fallback.txt') - self.__replace_content_disposition('inline; filename=fallback.txt; filename*=WRONG\'\'ÀĥƦ.txt') - filename = parser.get_filename(self.stream, None, self.parser_context) - self.assertEqual(filename, 'fallback.txt') - def __replace_content_disposition(self, disposition): self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition From bcca9ed0fd0af251e5a64be0ab7865b396d6b4b8 Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 15:03:50 +0400 Subject: [PATCH 4/8] Removed use encoding available only in py3 --- rest_framework/parsers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index f285ac975..c67f0088c 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -291,15 +291,15 @@ class FileUploadParser(BaseParser): meta = parser_context['request'].META disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) - filename = force_text(disposition[1]['filename']) - if 'filename*' in disposition[1]: filename_encoded = force_text(disposition[1]['filename*']) try: charset, filename_encoded = filename_encoded.split('\'\'', 1) - filename = urlparse.unquote(filename_encoded, charset) + filename = urlparse.unquote(filename_encoded) except (ValueError, LookupError): - pass + filename = force_text(disposition[1]['filename']) + else: + filename = force_text(disposition[1]['filename']) return filename except (AttributeError, KeyError): From bcd8a24db1d7022cac3d11641c563f0bfdbb8e97 Mon Sep 17 00:00:00 2001 From: Jason Bittel Date: Fri, 5 Sep 2014 14:56:54 -0700 Subject: [PATCH 5/8] Refactor encoded filename feature per #1531 --- rest_framework/parsers.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index c67f0088c..82caa6f6d 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -289,18 +289,22 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META - disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) - - if 'filename*' in disposition[1]: - filename_encoded = force_text(disposition[1]['filename*']) - try: - charset, filename_encoded = filename_encoded.split('\'\'', 1) - filename = urlparse.unquote(filename_encoded) - except (ValueError, LookupError): - filename = force_text(disposition[1]['filename']) - else: - filename = force_text(disposition[1]['filename']) - - return filename + disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8'))[1] + if 'filename*' in disposition: + return self.get_encoded_filename(disposition) + return force_text(disposition['filename']) except (AttributeError, KeyError): pass + + def get_encoded_filename(self, disposition): + """ + Handle encoded filenames per RFC6266. See also: + http://tools.ietf.org/html/rfc2231#section-4 + """ + encoded_filename = force_text(disposition['filename*']) + try: + charset, filename = encoded_filename.split('\'\'', 1) + filename = urlparse.unquote(filename) + except (ValueError, LookupError): + filename = force_text(disposition['filename']) + return filename From c8e475023c527f3ad22258c4ae0e0b5d992cb98b Mon Sep 17 00:00:00 2001 From: Jason Bittel Date: Fri, 5 Sep 2014 15:22:43 -0700 Subject: [PATCH 6/8] Fix encoded filename parsing to allow for lang --- rest_framework/parsers.py | 2 +- tests/test_parsers.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 82caa6f6d..161bc8090 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -303,7 +303,7 @@ class FileUploadParser(BaseParser): """ encoded_filename = force_text(disposition['filename*']) try: - charset, filename = encoded_filename.split('\'\'', 1) + charset, lang, filename = encoded_filename.split('\'', 2) filename = urlparse.unquote(filename) except (ValueError, LookupError): filename = force_text(disposition['filename']) diff --git a/tests/test_parsers.py b/tests/test_parsers.py index d19b46519..bc84f1c71 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -127,6 +127,10 @@ class TestFileUploadParser(TestCase): filename = parser.get_filename(self.stream, None, self.parser_context) self.assertEqual(filename, 'ÀĥƦ.txt') + self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8\'en-us\'ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'ÀĥƦ.txt') + self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8--ÀĥƦ.txt') filename = parser.get_filename(self.stream, None, self.parser_context) self.assertEqual(filename, 'fallback.txt') From 959e23426088661e12f5c8c3fafc64da42d1fc24 Mon Sep 17 00:00:00 2001 From: Jason Bittel Date: Fri, 5 Sep 2014 15:33:47 -0700 Subject: [PATCH 7/8] Move parser tests to correct directory --- tests/test_parsers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_parsers.py b/tests/test_parsers.py index bc84f1c71..3f2672df0 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- from __future__ import unicode_literals from rest_framework.compat import StringIO @@ -137,5 +137,3 @@ class TestFileUploadParser(TestCase): def __replace_content_disposition(self, disposition): self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition - - From 3f7fad2e5a1dd2cccaff97d662745ac24cc18efa Mon Sep 17 00:00:00 2001 From: Jason Bittel Date: Fri, 5 Sep 2014 16:27:55 -0700 Subject: [PATCH 8/8] Refactor disposition unpacking for clarity --- rest_framework/parsers.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 161bc8090..c287908dc 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -289,22 +289,23 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META - disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8'))[1] - if 'filename*' in disposition: - return self.get_encoded_filename(disposition) - return force_text(disposition['filename']) + disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) + filename_parm = disposition[1] + if 'filename*' in filename_parm: + return self.get_encoded_filename(filename_parm) + return force_text(filename_parm['filename']) except (AttributeError, KeyError): pass - def get_encoded_filename(self, disposition): + def get_encoded_filename(self, filename_parm): """ Handle encoded filenames per RFC6266. See also: http://tools.ietf.org/html/rfc2231#section-4 """ - encoded_filename = force_text(disposition['filename*']) + encoded_filename = force_text(filename_parm['filename*']) try: charset, lang, filename = encoded_filename.split('\'', 2) filename = urlparse.unquote(filename) except (ValueError, LookupError): - filename = force_text(disposition['filename']) + filename = force_text(filename_parm['filename']) return filename