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 cgi
import mimetypes
import os
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.template import Context, Engine
from django.utils import archive
from django.utils.http import parse_header_parameters
from django.utils.version import get_docs_version
@ -327,7 +327,7 @@ class TemplateCommand(BaseCommand):
# Trying to get better name from response headers
content_disposition = headers["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
else:
guessed_filename = used_name

View File

@ -6,7 +6,6 @@ file upload handlers for processing.
"""
import base64
import binascii
import cgi
import collections
import html
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.utils.datastructures import MultiValueDict
from django.utils.encoding import force_str
from django.utils.regex_helper import _lazy_re_compile
__all__ = ("MultiPartParser", "MultiPartParserError", "InputStreamExhausted")
@ -49,6 +49,8 @@ class MultiPartParser:
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):
"""
Initialize the MultiPartParser object.
@ -77,7 +79,7 @@ class MultiPartParser:
% force_str(content_type)
)
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(
"Invalid boundary in multipart: %s" % force_str(boundary)
)

View File

@ -1,4 +1,3 @@
import cgi
import codecs
import copy
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.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 .multipartparser import parse_header
@ -97,7 +96,7 @@ class HttpRequest:
def _set_content_type_params(self, meta):
"""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", "")
)
if "charset" in self.content_params:

View File

@ -366,3 +366,36 @@ def escape_leading_slashes(url):
if url.startswith("//"):
url = "/%2F{}".format(url[2:])
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,
is_same_domain,
parse_etags,
parse_header_parameters,
parse_http_date,
quote_etag,
url_has_allowed_host_and_scheme,
@ -435,3 +436,39 @@ class EscapeLeadingSlashesTests(unittest.TestCase):
for url, expected in tests:
with self.subTest(url=url):
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)