Refs #33173 -- Removed use of deprecated cgi module.

https://peps.python.org/pep-0594/#cgi
This commit is contained in:
Carlton Gibson 2022-05-10 12:12:17 +02:00 committed by Carlton Gibson
parent 02dbf1667c
commit 34e2148fc7
5 changed files with 78 additions and 7 deletions

View File

@ -1,5 +1,4 @@
import argparse import argparse
import cgi
import mimetypes import mimetypes
import os import os
import posixpath import posixpath
@ -15,6 +14,7 @@ from django.core.management.base import BaseCommand, CommandError
from django.core.management.utils import handle_extensions, run_formatters from django.core.management.utils import handle_extensions, run_formatters
from django.template import Context, Engine from django.template import Context, Engine
from django.utils import archive from django.utils import archive
from django.utils.http import parse_header_parameters
from django.utils.version import get_docs_version from django.utils.version import get_docs_version
@ -327,7 +327,7 @@ class TemplateCommand(BaseCommand):
# Trying to get better name from response headers # Trying to get better name from response headers
content_disposition = headers["content-disposition"] content_disposition = headers["content-disposition"]
if content_disposition: if content_disposition:
_, params = cgi.parse_header(content_disposition) _, params = parse_header_parameters(content_disposition)
guessed_filename = params.get("filename") or used_name guessed_filename = params.get("filename") or used_name
else: else:
guessed_filename = used_name guessed_filename = used_name

View File

@ -6,7 +6,6 @@ file upload handlers for processing.
""" """
import base64 import base64
import binascii import binascii
import cgi
import collections import collections
import html import html
from urllib.parse import unquote from urllib.parse import unquote
@ -20,6 +19,7 @@ from django.core.exceptions import (
from django.core.files.uploadhandler import SkipFile, StopFutureHandlers, StopUpload from django.core.files.uploadhandler import SkipFile, StopFutureHandlers, StopUpload
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.regex_helper import _lazy_re_compile
__all__ = ("MultiPartParser", "MultiPartParserError", "InputStreamExhausted") __all__ = ("MultiPartParser", "MultiPartParserError", "InputStreamExhausted")
@ -49,6 +49,8 @@ class MultiPartParser:
and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``.
""" """
boundary_re = _lazy_re_compile(rb"[ -~]{0,200}[!-~]")
def __init__(self, META, input_data, upload_handlers, encoding=None): def __init__(self, META, input_data, upload_handlers, encoding=None):
""" """
Initialize the MultiPartParser object. Initialize the MultiPartParser object.
@ -77,7 +79,7 @@ class MultiPartParser:
% force_str(content_type) % force_str(content_type)
) )
boundary = opts.get("boundary") boundary = opts.get("boundary")
if not boundary or not cgi.valid_boundary(boundary): if not boundary or not self.boundary_re.fullmatch(boundary):
raise MultiPartParserError( raise MultiPartParserError(
"Invalid boundary in multipart: %s" % force_str(boundary) "Invalid boundary in multipart: %s" % force_str(boundary)
) )

View File

@ -1,4 +1,3 @@
import cgi
import codecs import codecs
import copy import copy
from io import BytesIO from io import BytesIO
@ -22,7 +21,7 @@ from django.utils.datastructures import (
) )
from django.utils.encoding import escape_uri_path, iri_to_uri from django.utils.encoding import escape_uri_path, iri_to_uri
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.http import is_same_domain from django.utils.http import is_same_domain, parse_header_parameters
from django.utils.regex_helper import _lazy_re_compile from django.utils.regex_helper import _lazy_re_compile
from .multipartparser import parse_header from .multipartparser import parse_header
@ -97,7 +96,7 @@ class HttpRequest:
def _set_content_type_params(self, meta): def _set_content_type_params(self, meta):
"""Set content_type, content_params, and encoding.""" """Set content_type, content_params, and encoding."""
self.content_type, self.content_params = cgi.parse_header( self.content_type, self.content_params = parse_header_parameters(
meta.get("CONTENT_TYPE", "") meta.get("CONTENT_TYPE", "")
) )
if "charset" in self.content_params: if "charset" in self.content_params:

View File

@ -366,3 +366,36 @@ def escape_leading_slashes(url):
if url.startswith("//"): if url.startswith("//"):
url = "/%2F{}".format(url[2:]) url = "/%2F{}".format(url[2:])
return url return url
def _parseparam(s):
while s[:1] == ";":
s = s[1:]
end = s.find(";")
while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
end = s.find(";", end + 1)
if end < 0:
end = len(s)
f = s[:end]
yield f.strip()
s = s[end:]
def parse_header_parameters(line):
"""
Parse a Content-type like header.
Return the main content-type and a dictionary of options.
"""
parts = _parseparam(";" + line)
key = parts.__next__()
pdict = {}
for p in parts:
i = p.find("=")
if i >= 0:
name = p[:i].strip().lower()
value = p[i + 1 :].strip()
if len(value) >= 2 and value[0] == value[-1] == '"':
value = value[1:-1]
value = value.replace("\\\\", "\\").replace('\\"', '"')
pdict[name] = value
return key, pdict

View File

@ -12,6 +12,7 @@ from django.utils.http import (
int_to_base36, int_to_base36,
is_same_domain, is_same_domain,
parse_etags, parse_etags,
parse_header_parameters,
parse_http_date, parse_http_date,
quote_etag, quote_etag,
url_has_allowed_host_and_scheme, url_has_allowed_host_and_scheme,
@ -435,3 +436,39 @@ class EscapeLeadingSlashesTests(unittest.TestCase):
for url, expected in tests: for url, expected in tests:
with self.subTest(url=url): with self.subTest(url=url):
self.assertEqual(escape_leading_slashes(url), expected) self.assertEqual(escape_leading_slashes(url), expected)
class ParseHeaderParameterTests(unittest.TestCase):
def test_basic(self):
tests = [
("text/plain", ("text/plain", {})),
("text/vnd.just.made.this.up ; ", ("text/vnd.just.made.this.up", {})),
("text/plain;charset=us-ascii", ("text/plain", {"charset": "us-ascii"})),
(
'text/plain ; charset="us-ascii"',
("text/plain", {"charset": "us-ascii"}),
),
(
'text/plain ; charset="us-ascii"; another=opt',
("text/plain", {"charset": "us-ascii", "another": "opt"}),
),
(
'attachment; filename="silly.txt"',
("attachment", {"filename": "silly.txt"}),
),
(
'attachment; filename="strange;name"',
("attachment", {"filename": "strange;name"}),
),
(
'attachment; filename="strange;name";size=123;',
("attachment", {"filename": "strange;name", "size": "123"}),
),
(
'form-data; name="files"; filename="fo\\"o;bar"',
("form-data", {"name": "files", "filename": 'fo"o;bar'}),
),
]
for header, expected in tests:
with self.subTest(header=header):
self.assertEqual(parse_header_parameters(header), expected)