Skip to content

Commit ad282da

Browse files
carltongibsonJaap Roes
andauthored
Replaced parse_header with parse_header_parameters. (#8556)
Add a backwards compatibility shim for Django versions that have no (or an incompatible) django.utils.http.parse_header_parameters implementation. Thanks to Shai Berger for review. Co-authored-by: Jaap Roes <[email protected]>
1 parent 101aff6 commit ad282da

File tree

6 files changed

+46
-36
lines changed

6 files changed

+46
-36
lines changed

rest_framework/compat.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
The `compat` module provides support for backwards compatibility with older
33
versions of Django/Python, and compatibility wrappers around optional packages.
44
"""
5+
import django
56
from django.conf import settings
67
from django.views.generic import View
78

@@ -152,6 +153,30 @@ def md_filter_add_syntax_highlight(md):
152153
return False
153154

154155

156+
if django.VERSION >= (4, 2):
157+
# Django 4.2+: use the stock parse_header_parameters function
158+
# Note: Django 4.1 also has an implementation of parse_header_parameters
159+
# which is slightly different from the one in 4.2, it needs
160+
# the compatibility shim as well.
161+
from django.utils.http import parse_header_parameters
162+
else:
163+
# Django <= 4.1: create a compatibility shim for parse_header_parameters
164+
from django.http.multipartparser import parse_header
165+
166+
def parse_header_parameters(line):
167+
# parse_header works with bytes, but parse_header_parameters
168+
# works with strings. Call encode to convert the line to bytes.
169+
main_value_pair, params = parse_header(line.encode())
170+
return main_value_pair, {
171+
# parse_header will convert *some* values to string.
172+
# parse_header_parameters converts *all* values to string.
173+
# Make sure all values are converted by calling decode on
174+
# any remaining non-string values.
175+
k: v if isinstance(v, str) else v.decode()
176+
for k, v in params.items()
177+
}
178+
179+
155180
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
156181
# See: https://bugs.python.org/issue22767
157182
SHORT_SEPARATORS = (',', ':')

rest_framework/negotiation.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"""
55
from django.http import Http404
66

7-
from rest_framework import HTTP_HEADER_ENCODING, exceptions
7+
from rest_framework import exceptions
88
from rest_framework.settings import api_settings
99
from rest_framework.utils.mediatypes import (
1010
_MediaType, media_type_matches, order_by_precedence
@@ -64,9 +64,11 @@ def select_renderer(self, request, renderers, format_suffix=None):
6464
# Accepted media type is 'application/json'
6565
full_media_type = ';'.join(
6666
(renderer.media_type,) +
67-
tuple('{}={}'.format(
68-
key, value.decode(HTTP_HEADER_ENCODING))
69-
for key, value in media_type_wrapper.params.items()))
67+
tuple(
68+
'{}={}'.format(key, value)
69+
for key, value in media_type_wrapper.params.items()
70+
)
71+
)
7072
return renderer, full_media_type
7173
else:
7274
# Eg client requests 'application/json; indent=8'

rest_framework/parsers.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@
55
on the request, such as form content or json encoded data.
66
"""
77
import codecs
8-
from urllib import parse
98

109
from django.conf import settings
1110
from django.core.files.uploadhandler import StopFutureHandlers
1211
from django.http import QueryDict
1312
from django.http.multipartparser import ChunkIter
1413
from django.http.multipartparser import \
1514
MultiPartParser as DjangoMultiPartParser
16-
from django.http.multipartparser import MultiPartParserError, parse_header
17-
from django.utils.encoding import force_str
15+
from django.http.multipartparser import MultiPartParserError
1816

1917
from rest_framework import renderers
18+
from rest_framework.compat import parse_header_parameters
2019
from rest_framework.exceptions import ParseError
2120
from rest_framework.settings import api_settings
2221
from rest_framework.utils import json
@@ -201,23 +200,10 @@ def get_filename(self, stream, media_type, parser_context):
201200

202201
try:
203202
meta = parser_context['request'].META
204-
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode())
205-
filename_parm = disposition[1]
206-
if 'filename*' in filename_parm:
207-
return self.get_encoded_filename(filename_parm)
208-
return force_str(filename_parm['filename'])
203+
disposition, params = parse_header_parameters(meta['HTTP_CONTENT_DISPOSITION'])
204+
if 'filename*' in params:
205+
return params['filename*']
206+
else:
207+
return params['filename']
209208
except (AttributeError, KeyError, ValueError):
210209
pass
211-
212-
def get_encoded_filename(self, filename_parm):
213-
"""
214-
Handle encoded filenames per RFC6266. See also:
215-
https://tools.ietf.org/html/rfc2231#section-4
216-
"""
217-
encoded_filename = force_str(filename_parm['filename*'])
218-
try:
219-
charset, lang, filename = encoded_filename.split('\'', 2)
220-
filename = parse.unquote(filename)
221-
except (ValueError, LookupError):
222-
filename = force_str(filename_parm['filename'])
223-
return filename

rest_framework/renderers.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@
1414
from django.conf import settings
1515
from django.core.exceptions import ImproperlyConfigured
1616
from django.core.paginator import Page
17-
from django.http.multipartparser import parse_header
1817
from django.template import engines, loader
1918
from django.urls import NoReverseMatch
2019
from django.utils.html import mark_safe
2120

2221
from rest_framework import VERSION, exceptions, serializers, status
2322
from rest_framework.compat import (
2423
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
25-
pygments_css, yaml
24+
parse_header_parameters, pygments_css, yaml
2625
)
2726
from rest_framework.exceptions import ParseError
2827
from rest_framework.request import is_form_media_type, override_method
@@ -72,7 +71,7 @@ def get_indent(self, accepted_media_type, renderer_context):
7271
# If the media type looks like 'application/json; indent=4',
7372
# then pretty print the result.
7473
# Note that we coerce `indent=0` into `indent=None`.
75-
base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
74+
base_media_type, params = parse_header_parameters(accepted_media_type)
7675
try:
7776
return zero_as_none(max(min(int(params['indent']), 8), 0))
7877
except (KeyError, ValueError, TypeError):

rest_framework/request.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@
1414

1515
from django.conf import settings
1616
from django.http import HttpRequest, QueryDict
17-
from django.http.multipartparser import parse_header
1817
from django.http.request import RawPostDataException
1918
from django.utils.datastructures import MultiValueDict
2019

21-
from rest_framework import HTTP_HEADER_ENCODING, exceptions
20+
from rest_framework import exceptions
21+
from rest_framework.compat import parse_header_parameters
2222
from rest_framework.settings import api_settings
2323

2424

2525
def is_form_media_type(media_type):
2626
"""
2727
Return True if the media type is a valid form media type.
2828
"""
29-
base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING))
29+
base_media_type, params = parse_header_parameters(media_type)
3030
return (base_media_type == 'application/x-www-form-urlencoded' or
3131
base_media_type == 'multipart/form-data')
3232

rest_framework/utils/mediatypes.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
44
See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
55
"""
6-
from django.http.multipartparser import parse_header
7-
8-
from rest_framework import HTTP_HEADER_ENCODING
6+
from rest_framework.compat import parse_header_parameters
97

108

119
def media_type_matches(lhs, rhs):
@@ -46,7 +44,7 @@ def order_by_precedence(media_type_lst):
4644
class _MediaType:
4745
def __init__(self, media_type_str):
4846
self.orig = '' if (media_type_str is None) else media_type_str
49-
self.full_type, self.params = parse_header(self.orig.encode(HTTP_HEADER_ENCODING))
47+
self.full_type, self.params = parse_header_parameters(self.orig)
5048
self.main_type, sep, self.sub_type = self.full_type.partition('/')
5149

5250
def match(self, other):
@@ -79,5 +77,5 @@ def precedence(self):
7977
def __str__(self):
8078
ret = "%s/%s" % (self.main_type, self.sub_type)
8179
for key, val in self.params.items():
82-
ret += "; %s=%s" % (key, val.decode('ascii'))
80+
ret += "; %s=%s" % (key, val)
8381
return ret

0 commit comments

Comments
 (0)