Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace parse_header with parse_header_parameters #8554

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
The `compat` module provides support for backwards compatibility with older
versions of Django/Python, and compatibility wrappers around optional packages.
"""
import django
from django.conf import settings
from django.views.generic import View

Expand Down Expand Up @@ -152,6 +153,32 @@ def md_filter_add_syntax_highlight(md):
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
parse_header_parameters = parse_header_parameters
Copy link
Collaborator

@carltongibson carltongibson Jul 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line isn't needed I think. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flake8 yells at me if I don't do this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. OK. Let me have a look... 🤔

Thanks!


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
# See: https://bugs.python.org/issue22767
SHORT_SEPARATORS = (',', ':')
Expand Down
10 changes: 6 additions & 4 deletions rest_framework/negotiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
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.utils.mediatypes import (
_MediaType, media_type_matches, order_by_precedence
Expand Down Expand Up @@ -64,9 +64,11 @@ def select_renderer(self, request, renderers, format_suffix=None):
# Accepted media type is 'application/json'
full_media_type = ';'.join(
(renderer.media_type,) +
tuple('{}={}'.format(
key, value.decode(HTTP_HEADER_ENCODING))
for key, value in media_type_wrapper.params.items()))
tuple(
'{}={}'.format(key, value)
for key, value in media_type_wrapper.params.items()
)
)
return renderer, full_media_type
else:
# Eg client requests 'application/json; indent=8'
Expand Down
28 changes: 7 additions & 21 deletions rest_framework/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@
on the request, such as form content or json encoded data.
"""
import codecs
from urllib import parse

from django.conf import settings
from django.core.files.uploadhandler import StopFutureHandlers
from django.http import QueryDict
from django.http.multipartparser import ChunkIter
from django.http.multipartparser import \
MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header
from django.utils.encoding import force_str
from django.http.multipartparser import MultiPartParserError

from rest_framework import renderers
from rest_framework.compat import parse_header_parameters
from rest_framework.exceptions import ParseError
from rest_framework.settings import api_settings
from rest_framework.utils import json
Expand Down Expand Up @@ -201,23 +200,10 @@ def get_filename(self, stream, media_type, parser_context):

try:
meta = parser_context['request'].META
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode())
filename_parm = disposition[1]
if 'filename*' in filename_parm:
return self.get_encoded_filename(filename_parm)
return force_str(filename_parm['filename'])
disposition, params = parse_header_parameters(meta['HTTP_CONTENT_DISPOSITION'])
if 'filename*' in params:
return params['filename*']
else:
return params['filename']
except (AttributeError, KeyError, ValueError):
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
5 changes: 2 additions & 3 deletions rest_framework/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import Page
from django.http.multipartparser import parse_header
from django.template import engines, loader
from django.urls import NoReverseMatch
from django.utils.html import mark_safe

from rest_framework import VERSION, exceptions, serializers, status
from rest_framework.compat import (
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.request import is_form_media_type, override_method
Expand Down Expand Up @@ -72,7 +71,7 @@ def get_indent(self, accepted_media_type, renderer_context):
# If the media type looks like 'application/json; indent=4',
# then pretty print the result.
# 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:
return zero_as_none(max(min(int(params['indent']), 8), 0))
except (KeyError, ValueError, TypeError):
Expand Down
6 changes: 3 additions & 3 deletions rest_framework/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@

from django.conf import settings
from django.http import HttpRequest, QueryDict
from django.http.multipartparser import parse_header
from django.http.request import RawPostDataException
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


def is_form_media_type(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
base_media_type == 'multipart/form-data')

Expand Down
8 changes: 3 additions & 5 deletions rest_framework/utils/mediatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
"""
from django.http.multipartparser import parse_header

from rest_framework import HTTP_HEADER_ENCODING
from rest_framework.compat import parse_header_parameters


def media_type_matches(lhs, rhs):
Expand Down Expand Up @@ -46,7 +44,7 @@ def order_by_precedence(media_type_lst):
class _MediaType:
def __init__(self, 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('/')

def match(self, other):
Expand Down Expand Up @@ -79,5 +77,5 @@ def precedence(self):
def __str__(self):
ret = "%s/%s" % (self.main_type, self.sub_type)
for key, val in self.params.items():
ret += "; %s=%s" % (key, val.decode('ascii'))
ret += "; %s=%s" % (key, val)
return ret