Skip to content

Commit 3150b4d

Browse files
committed
Update cgi.py from Python 3.11
1 parent 1be262d commit 3150b4d

File tree

2 files changed

+44
-21
lines changed

2 files changed

+44
-21
lines changed

Lib/cgi.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
1414
This module defines a number of utilities for use by CGI scripts
1515
written in Python.
16+
17+
The global variable maxlen can be set to an integer indicating the maximum size
18+
of a POST request. POST requests larger than this size will result in a
19+
ValueError being raised during parsing. The default value of this variable is 0,
20+
meaning the request size is unlimited.
1621
"""
1722

1823
# History
@@ -41,12 +46,16 @@
4146
import html
4247
import locale
4348
import tempfile
49+
import warnings
4450

4551
__all__ = ["MiniFieldStorage", "FieldStorage", "parse", "parse_multipart",
4652
"parse_header", "test", "print_exception", "print_environ",
4753
"print_form", "print_directory", "print_arguments",
4854
"print_environ_usage"]
4955

56+
57+
warnings._deprecated(__name__, remove=(3,13))
58+
5059
# Logging support
5160
# ===============
5261

@@ -77,9 +86,11 @@ def initlog(*allargs):
7786
7887
"""
7988
global log, logfile, logfp
89+
warnings.warn("cgi.log() is deprecated as of 3.10. Use logging instead",
90+
DeprecationWarning, stacklevel=2)
8091
if logfile and not logfp:
8192
try:
82-
logfp = open(logfile, "a")
93+
logfp = open(logfile, "a", encoding="locale")
8394
except OSError:
8495
pass
8596
if not logfp:
@@ -115,7 +126,8 @@ def closelog():
115126
# 0 ==> unlimited input
116127
maxlen = 0
117128

118-
def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
129+
def parse(fp=None, environ=os.environ, keep_blank_values=0,
130+
strict_parsing=0, separator='&'):
119131
"""Parse a query in the environment or from a file (default stdin)
120132
121133
Arguments, all optional:
@@ -134,6 +146,9 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
134146
strict_parsing: flag indicating what to do with parsing errors.
135147
If false (the default), errors are silently ignored.
136148
If true, errors raise a ValueError exception.
149+
150+
separator: str. The symbol to use for separating the query arguments.
151+
Defaults to &.
137152
"""
138153
if fp is None:
139154
fp = sys.stdin
@@ -154,7 +169,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
154169
if environ['REQUEST_METHOD'] == 'POST':
155170
ctype, pdict = parse_header(environ['CONTENT_TYPE'])
156171
if ctype == 'multipart/form-data':
157-
return parse_multipart(fp, pdict)
172+
return parse_multipart(fp, pdict, separator=separator)
158173
elif ctype == 'application/x-www-form-urlencoded':
159174
clength = int(environ['CONTENT_LENGTH'])
160175
if maxlen and clength > maxlen:
@@ -178,10 +193,10 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
178193
qs = ""
179194
environ['QUERY_STRING'] = qs # XXX Shouldn't, really
180195
return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing,
181-
encoding=encoding)
196+
encoding=encoding, separator=separator)
182197

183198

184-
def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"):
199+
def parse_multipart(fp, pdict, encoding="utf-8", errors="replace", separator='&'):
185200
"""Parse multipart input.
186201
187202
Arguments:
@@ -194,15 +209,18 @@ def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"):
194209
value is a list of values for that field. For non-file fields, the value
195210
is a list of strings.
196211
"""
197-
# RFC 2026, Section 5.1 : The "multipart" boundary delimiters are always
212+
# RFC 2046, Section 5.1 : The "multipart" boundary delimiters are always
198213
# represented as 7bit US-ASCII.
199214
boundary = pdict['boundary'].decode('ascii')
200215
ctype = "multipart/form-data; boundary={}".format(boundary)
201216
headers = Message()
202217
headers.set_type(ctype)
203-
headers['Content-Length'] = pdict['CONTENT-LENGTH']
218+
try:
219+
headers['Content-Length'] = pdict['CONTENT-LENGTH']
220+
except KeyError:
221+
pass
204222
fs = FieldStorage(fp, headers=headers, encoding=encoding, errors=errors,
205-
environ={'REQUEST_METHOD': 'POST'})
223+
environ={'REQUEST_METHOD': 'POST'}, separator=separator)
206224
return {k: fs.getlist(k) for k in fs}
207225

208226
def _parseparam(s):
@@ -312,7 +330,7 @@ class FieldStorage:
312330
def __init__(self, fp=None, headers=None, outerboundary=b'',
313331
environ=os.environ, keep_blank_values=0, strict_parsing=0,
314332
limit=None, encoding='utf-8', errors='replace',
315-
max_num_fields=None):
333+
max_num_fields=None, separator='&'):
316334
"""Constructor. Read multipart/* until last part.
317335
318336
Arguments, all optional:
@@ -360,6 +378,7 @@ def __init__(self, fp=None, headers=None, outerboundary=b'',
360378
self.keep_blank_values = keep_blank_values
361379
self.strict_parsing = strict_parsing
362380
self.max_num_fields = max_num_fields
381+
self.separator = separator
363382
if 'REQUEST_METHOD' in environ:
364383
method = environ['REQUEST_METHOD'].upper()
365384
self.qs_on_post = None
@@ -586,7 +605,7 @@ def read_urlencoded(self):
586605
query = urllib.parse.parse_qsl(
587606
qs, self.keep_blank_values, self.strict_parsing,
588607
encoding=self.encoding, errors=self.errors,
589-
max_num_fields=self.max_num_fields)
608+
max_num_fields=self.max_num_fields, separator=self.separator)
590609
self.list = [MiniFieldStorage(key, value) for key, value in query]
591610
self.skip_lines()
592611

@@ -602,7 +621,7 @@ def read_multi(self, environ, keep_blank_values, strict_parsing):
602621
query = urllib.parse.parse_qsl(
603622
self.qs_on_post, self.keep_blank_values, self.strict_parsing,
604623
encoding=self.encoding, errors=self.errors,
605-
max_num_fields=self.max_num_fields)
624+
max_num_fields=self.max_num_fields, separator=self.separator)
606625
self.list.extend(MiniFieldStorage(key, value) for key, value in query)
607626

608627
klass = self.FieldStorageClass or self.__class__
@@ -646,7 +665,7 @@ def read_multi(self, environ, keep_blank_values, strict_parsing):
646665
else self.limit - self.bytes_read
647666
part = klass(self.fp, headers, ib, environ, keep_blank_values,
648667
strict_parsing, limit,
649-
self.encoding, self.errors, max_num_fields)
668+
self.encoding, self.errors, max_num_fields, self.separator)
650669

651670
if max_num_fields is not None:
652671
max_num_fields -= 1
@@ -736,7 +755,8 @@ def read_lines_to_outerboundary(self):
736755
last_line_lfend = True
737756
_read = 0
738757
while 1:
739-
if self.limit is not None and _read >= self.limit:
758+
759+
if self.limit is not None and 0 <= self.limit <= _read:
740760
break
741761
line = self.fp.readline(1<<16) # bytes
742762
self.bytes_read += len(line)

Lib/test/test_cgi.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import cgi
21
import os
32
import sys
43
import tempfile
54
import unittest
65
from collections import namedtuple
76
from io import StringIO, BytesIO
87
from test import support
8+
from test.support import warnings_helper
9+
10+
cgi = warnings_helper.import_deprecated("cgi")
11+
912

1013
class HackedSysModule:
1114
# The regression test will have real values in sys.argv, which
@@ -50,7 +53,7 @@ def do_test(buf, method):
5053
return ComparableException(err)
5154

5255
parse_strict_test_cases = [
53-
("", ValueError("bad query field: ''")),
56+
("", {}),
5457
("&", ValueError("bad query field: ''")),
5558
("&&", ValueError("bad query field: ''")),
5659
# Should the next few really be valid?
@@ -123,8 +126,6 @@ def test_parse_multipart(self):
123126
'file': [b'Testing 123.\n'], 'title': ['']}
124127
self.assertEqual(result, expected)
125128

126-
# TODO: RUSTPYTHON
127-
@unittest.expectedFailure
128129
def test_parse_multipart_without_content_length(self):
129130
POSTDATA = '''--JfISa01
130131
Content-Disposition: form-data; name="submit-name"
@@ -174,6 +175,8 @@ def test_fieldstorage_invalid(self):
174175
fs = cgi.FieldStorage(headers={'content-type':'text/plain'})
175176
self.assertRaises(TypeError, bool, fs)
176177

178+
# TODO: RUSTPYTHON
179+
@unittest.expectedFailure
177180
def test_strict(self):
178181
for orig, expect in parse_strict_test_cases:
179182
# Test basic parsing
@@ -200,8 +203,6 @@ def test_strict(self):
200203
else:
201204
self.assertEqual(fs.getvalue(key), expect_val[0])
202205

203-
# TODO: RUSTPYTHON
204-
@unittest.expectedFailure
205206
def test_separator(self):
206207
parse_semicolon = [
207208
("x=1;y=2.0", {'x': ['1'], 'y': ['2.0']}),
@@ -226,6 +227,7 @@ def test_separator(self):
226227
else:
227228
self.assertEqual(fs.getvalue(key), expect_val[0])
228229

230+
@warnings_helper.ignore_warnings(category=DeprecationWarning)
229231
def test_log(self):
230232
cgi.log("Testing")
231233

@@ -578,8 +580,9 @@ def test_parse_header(self):
578580
("form-data", {"name": "files", "filename": 'fo"o;bar'}))
579581

580582
def test_all(self):
581-
not_exported = {"logfile", "logfp", "initlog", "dolog", "nolog",
582-
"closelog", "log", "maxlen", "valid_boundary"}
583+
not_exported = {
584+
"logfile", "logfp", "initlog", "dolog", "nolog", "closelog", "log",
585+
"maxlen", "valid_boundary"}
583586
support.check__all__(self, cgi, not_exported=not_exported)
584587

585588

0 commit comments

Comments
 (0)