mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +03:00
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 <jroes@leukeleu.nl>
This commit is contained in:
parent
101aff6c43
commit
ad282da97c
|
@ -2,6 +2,7 @@
|
||||||
The `compat` module provides support for backwards compatibility with older
|
The `compat` module provides support for backwards compatibility with older
|
||||||
versions of Django/Python, and compatibility wrappers around optional packages.
|
versions of Django/Python, and compatibility wrappers around optional packages.
|
||||||
"""
|
"""
|
||||||
|
import django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
|
@ -152,6 +153,30 @@ else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if django.VERSION >= (4, 2):
|
||||||
|
# Django 4.2+: use the stock parse_header_parameters function
|
||||||
|
# Note: Django 4.1 also has an implementation of parse_header_parameters
|
||||||
|
# which is slightly different from the one in 4.2, it needs
|
||||||
|
# the compatibility shim as well.
|
||||||
|
from django.utils.http import parse_header_parameters
|
||||||
|
else:
|
||||||
|
# Django <= 4.1: create a compatibility shim for parse_header_parameters
|
||||||
|
from django.http.multipartparser import parse_header
|
||||||
|
|
||||||
|
def parse_header_parameters(line):
|
||||||
|
# parse_header works with bytes, but parse_header_parameters
|
||||||
|
# works with strings. Call encode to convert the line to bytes.
|
||||||
|
main_value_pair, params = parse_header(line.encode())
|
||||||
|
return main_value_pair, {
|
||||||
|
# parse_header will convert *some* values to string.
|
||||||
|
# parse_header_parameters converts *all* values to string.
|
||||||
|
# Make sure all values are converted by calling decode on
|
||||||
|
# any remaining non-string values.
|
||||||
|
k: v if isinstance(v, str) else v.decode()
|
||||||
|
for k, v in params.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
|
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
|
||||||
# See: https://bugs.python.org/issue22767
|
# See: https://bugs.python.org/issue22767
|
||||||
SHORT_SEPARATORS = (',', ':')
|
SHORT_SEPARATORS = (',', ':')
|
||||||
|
|
|
@ -4,7 +4,7 @@ incoming request. Typically this will be based on the request's Accept header.
|
||||||
"""
|
"""
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
|
||||||
from rest_framework import HTTP_HEADER_ENCODING, exceptions
|
from rest_framework import exceptions
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.utils.mediatypes import (
|
from rest_framework.utils.mediatypes import (
|
||||||
_MediaType, media_type_matches, order_by_precedence
|
_MediaType, media_type_matches, order_by_precedence
|
||||||
|
@ -64,9 +64,11 @@ class DefaultContentNegotiation(BaseContentNegotiation):
|
||||||
# Accepted media type is 'application/json'
|
# Accepted media type is 'application/json'
|
||||||
full_media_type = ';'.join(
|
full_media_type = ';'.join(
|
||||||
(renderer.media_type,) +
|
(renderer.media_type,) +
|
||||||
tuple('{}={}'.format(
|
tuple(
|
||||||
key, value.decode(HTTP_HEADER_ENCODING))
|
'{}={}'.format(key, value)
|
||||||
for key, value in media_type_wrapper.params.items()))
|
for key, value in media_type_wrapper.params.items()
|
||||||
|
)
|
||||||
|
)
|
||||||
return renderer, full_media_type
|
return renderer, full_media_type
|
||||||
else:
|
else:
|
||||||
# Eg client requests 'application/json; indent=8'
|
# Eg client requests 'application/json; indent=8'
|
||||||
|
|
|
@ -5,7 +5,6 @@ They give us a generic way of being able to handle various media types
|
||||||
on the request, such as form content or json encoded data.
|
on the request, such as form content or json encoded data.
|
||||||
"""
|
"""
|
||||||
import codecs
|
import codecs
|
||||||
from urllib import parse
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files.uploadhandler import StopFutureHandlers
|
from django.core.files.uploadhandler import StopFutureHandlers
|
||||||
|
@ -13,10 +12,10 @@ from django.http import QueryDict
|
||||||
from django.http.multipartparser import ChunkIter
|
from django.http.multipartparser import ChunkIter
|
||||||
from django.http.multipartparser import \
|
from django.http.multipartparser import \
|
||||||
MultiPartParser as DjangoMultiPartParser
|
MultiPartParser as DjangoMultiPartParser
|
||||||
from django.http.multipartparser import MultiPartParserError, parse_header
|
from django.http.multipartparser import MultiPartParserError
|
||||||
from django.utils.encoding import force_str
|
|
||||||
|
|
||||||
from rest_framework import renderers
|
from rest_framework import renderers
|
||||||
|
from rest_framework.compat import parse_header_parameters
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.utils import json
|
from rest_framework.utils import json
|
||||||
|
@ -201,23 +200,10 @@ class FileUploadParser(BaseParser):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
meta = parser_context['request'].META
|
meta = parser_context['request'].META
|
||||||
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode())
|
disposition, params = parse_header_parameters(meta['HTTP_CONTENT_DISPOSITION'])
|
||||||
filename_parm = disposition[1]
|
if 'filename*' in params:
|
||||||
if 'filename*' in filename_parm:
|
return params['filename*']
|
||||||
return self.get_encoded_filename(filename_parm)
|
else:
|
||||||
return force_str(filename_parm['filename'])
|
return params['filename']
|
||||||
except (AttributeError, KeyError, ValueError):
|
except (AttributeError, KeyError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_encoded_filename(self, filename_parm):
|
|
||||||
"""
|
|
||||||
Handle encoded filenames per RFC6266. See also:
|
|
||||||
https://tools.ietf.org/html/rfc2231#section-4
|
|
||||||
"""
|
|
||||||
encoded_filename = force_str(filename_parm['filename*'])
|
|
||||||
try:
|
|
||||||
charset, lang, filename = encoded_filename.split('\'', 2)
|
|
||||||
filename = parse.unquote(filename)
|
|
||||||
except (ValueError, LookupError):
|
|
||||||
filename = force_str(filename_parm['filename'])
|
|
||||||
return filename
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.paginator import Page
|
from django.core.paginator import Page
|
||||||
from django.http.multipartparser import parse_header
|
|
||||||
from django.template import engines, loader
|
from django.template import engines, loader
|
||||||
from django.urls import NoReverseMatch
|
from django.urls import NoReverseMatch
|
||||||
from django.utils.html import mark_safe
|
from django.utils.html import mark_safe
|
||||||
|
@ -22,7 +21,7 @@ from django.utils.html import mark_safe
|
||||||
from rest_framework import VERSION, exceptions, serializers, status
|
from rest_framework import VERSION, exceptions, serializers, status
|
||||||
from rest_framework.compat import (
|
from rest_framework.compat import (
|
||||||
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
|
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
|
||||||
pygments_css, yaml
|
parse_header_parameters, pygments_css, yaml
|
||||||
)
|
)
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from rest_framework.request import is_form_media_type, override_method
|
from rest_framework.request import is_form_media_type, override_method
|
||||||
|
@ -72,7 +71,7 @@ class JSONRenderer(BaseRenderer):
|
||||||
# If the media type looks like 'application/json; indent=4',
|
# If the media type looks like 'application/json; indent=4',
|
||||||
# then pretty print the result.
|
# then pretty print the result.
|
||||||
# Note that we coerce `indent=0` into `indent=None`.
|
# Note that we coerce `indent=0` into `indent=None`.
|
||||||
base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
|
base_media_type, params = parse_header_parameters(accepted_media_type)
|
||||||
try:
|
try:
|
||||||
return zero_as_none(max(min(int(params['indent']), 8), 0))
|
return zero_as_none(max(min(int(params['indent']), 8), 0))
|
||||||
except (KeyError, ValueError, TypeError):
|
except (KeyError, ValueError, TypeError):
|
||||||
|
|
|
@ -14,11 +14,11 @@ from contextlib import contextmanager
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpRequest, QueryDict
|
from django.http import HttpRequest, QueryDict
|
||||||
from django.http.multipartparser import parse_header
|
|
||||||
from django.http.request import RawPostDataException
|
from django.http.request import RawPostDataException
|
||||||
from django.utils.datastructures import MultiValueDict
|
from django.utils.datastructures import MultiValueDict
|
||||||
|
|
||||||
from rest_framework import HTTP_HEADER_ENCODING, exceptions
|
from rest_framework import exceptions
|
||||||
|
from rest_framework.compat import parse_header_parameters
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ def is_form_media_type(media_type):
|
||||||
"""
|
"""
|
||||||
Return True if the media type is a valid form media type.
|
Return True if the media type is a valid form media type.
|
||||||
"""
|
"""
|
||||||
base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING))
|
base_media_type, params = parse_header_parameters(media_type)
|
||||||
return (base_media_type == 'application/x-www-form-urlencoded' or
|
return (base_media_type == 'application/x-www-form-urlencoded' or
|
||||||
base_media_type == 'multipart/form-data')
|
base_media_type == 'multipart/form-data')
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,7 @@ Handling of media types, as found in HTTP Content-Type and Accept headers.
|
||||||
|
|
||||||
See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
||||||
"""
|
"""
|
||||||
from django.http.multipartparser import parse_header
|
from rest_framework.compat import parse_header_parameters
|
||||||
|
|
||||||
from rest_framework import HTTP_HEADER_ENCODING
|
|
||||||
|
|
||||||
|
|
||||||
def media_type_matches(lhs, rhs):
|
def media_type_matches(lhs, rhs):
|
||||||
|
@ -46,7 +44,7 @@ def order_by_precedence(media_type_lst):
|
||||||
class _MediaType:
|
class _MediaType:
|
||||||
def __init__(self, media_type_str):
|
def __init__(self, media_type_str):
|
||||||
self.orig = '' if (media_type_str is None) else media_type_str
|
self.orig = '' if (media_type_str is None) else media_type_str
|
||||||
self.full_type, self.params = parse_header(self.orig.encode(HTTP_HEADER_ENCODING))
|
self.full_type, self.params = parse_header_parameters(self.orig)
|
||||||
self.main_type, sep, self.sub_type = self.full_type.partition('/')
|
self.main_type, sep, self.sub_type = self.full_type.partition('/')
|
||||||
|
|
||||||
def match(self, other):
|
def match(self, other):
|
||||||
|
@ -79,5 +77,5 @@ class _MediaType:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ret = "%s/%s" % (self.main_type, self.sub_type)
|
ret = "%s/%s" % (self.main_type, self.sub_type)
|
||||||
for key, val in self.params.items():
|
for key, val in self.params.items():
|
||||||
ret += "; %s=%s" % (key, val.decode('ascii'))
|
ret += "; %s=%s" % (key, val)
|
||||||
return ret
|
return ret
|
||||||
|
|
Loading…
Reference in New Issue
Block a user