summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlton Gibson <carlton.gibson@noumenal.es>2022-05-10 12:12:17 +0200
committerCarlton Gibson <carlton@noumenal.es>2022-05-11 14:06:31 +0200
commit34e2148fc725e7200050f74130d7523e3cd8507a (patch)
treeb4bc74515b1d4911c6c07d5cd7697c78eff91407
parent02dbf1667c6da61ea9346f7c9f174a158b896811 (diff)
Refs #33173 -- Removed use of deprecated cgi module.
https://peps.python.org/pep-0594/#cgi
-rw-r--r--django/core/management/templates.py4
-rw-r--r--django/http/multipartparser.py6
-rw-r--r--django/http/request.py5
-rw-r--r--django/utils/http.py33
-rw-r--r--tests/utils_tests/test_http.py37
5 files changed, 78 insertions, 7 deletions
diff --git a/django/core/management/templates.py b/django/core/management/templates.py
index 72db9651b0..e06e6a6307 100644
--- a/django/core/management/templates.py
+++ b/django/core/management/templates.py
@@ -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
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
index f52ff7619e..308fbfa385 100644
--- a/django/http/multipartparser.py
+++ b/django/http/multipartparser.py
@@ -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)
)
diff --git a/django/http/request.py b/django/http/request.py
index c25fa4379a..4b160bc5f4 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -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:
diff --git a/django/utils/http.py b/django/utils/http.py
index 0292713235..6e2091bf52 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -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
diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py
index 67f3361068..2ba617dfc9 100644
--- a/tests/utils_tests/test_http.py
+++ b/tests/utils_tests/test_http.py
@@ -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)