Skip to content
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
32 changes: 19 additions & 13 deletions Lib/cgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,39 +667,39 @@ def read_multi(self, environ, keep_blank_values, strict_parsing):
def read_single(self):
"""Internal: read an atomic part."""
if self.length >= 0:
self.read_binary()
self.read_content()
self.skip_lines()
else:
self.read_lines()
self.file.seek(0)

bufsize = 8*1024 # I/O buffering size for copy to file

def read_binary(self):
"""Internal: read binary data."""
self.file = self.make_file()
def read_content(self):
"""Internal: read data up until content-length len."""
if self.length > 1000:
self.file = self.make_file()
else:
self.file = self.io_object()
self.__file = self.file
todo = self.length
if todo >= 0:
while todo > 0:
data = self.fp.read(min(todo, self.bufsize)) # bytes
if not isinstance(data, bytes):
raise ValueError("%s should return bytes, got %s"
% (self.fp, type(data).__name__))
self.bytes_read += len(data)
if not data:
self.done = -1
break
self.file.write(data)
self.__write(data)
todo = todo - len(data)

def read_lines(self):
"""Internal: read lines until EOF or outerboundary."""
if self._binary_file:
self.file = self.__file = BytesIO() # store data as bytes for files
else:
self.file = self.__file = StringIO() # as strings for other fields
self.file = self.__file = self.io_object()
if self.outerboundary:
self.read_lines_to_outerboundary()
elif self.length >= 0:
self.read_content()
else:
self.read_lines_to_eof()

Expand All @@ -721,7 +721,7 @@ def __write(self, line):
def read_lines_to_eof(self):
"""Internal: read lines until EOF."""
while 1:
line = self.fp.readline(1<<16) # bytes
line = self.fp.read(1<<16) # bytes
self.bytes_read += len(line)
if not line:
self.done = -1
Expand Down Expand Up @@ -830,6 +830,12 @@ def make_file(self):
return tempfile.TemporaryFile("w+",
encoding=self.encoding, newline = '\n')

def io_object(self):
if self._binary_file:
return BytesIO() # store data as bytes for files
else:
return StringIO() # as strings for other fields


# Test/debug code
# ===============
Expand Down
32 changes: 22 additions & 10 deletions Lib/test/test_cgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,22 +215,22 @@ def test_log(self):
self.addCleanup(cgi.closelog)
cgi.log("Testing log 4")

def test_fieldstorage_readline(self):
# FieldStorage uses readline, which has the capacity to read all
# contents of the input file into memory; we use readline's size argument
# to prevent that for files that do not contain any newlines in
def test_fieldstorage_read(self):
# FieldStorage uses read, which has the capacity to read all contents
# of the input file into memory; we use read's size argument to
# prevent that for files that do not contain any newlines in
# non-GET/HEAD requests
class TestReadlineFile:
class TestReadFile:
def __init__(self, file):
self.file = file
self.numcalls = 0

def readline(self, size=None):
def read(self, size=None):
self.numcalls += 1
if size:
return self.file.readline(size)
return self.file.read(size)
else:
return self.file.readline()
return self.file.read()

def __getattr__(self, name):
file = self.__dict__['file']
Expand All @@ -239,14 +239,14 @@ def __getattr__(self, name):
setattr(self, name, a)
return a

f = TestReadlineFile(tempfile.TemporaryFile("wb+"))
f = TestReadFile(tempfile.TemporaryFile("wb+"))
self.addCleanup(f.close)
f.write(b'x' * 256 * 1024)
f.seek(0)
env = {'REQUEST_METHOD':'PUT'}
fs = cgi.FieldStorage(fp=f, environ=env)
self.addCleanup(fs.file.close)
# if we're not chunking properly, readline is only called twice
# if we're not chunking properly, read is only called twice
# (by read_binary); if we are chunking properly, it will be called 5 times
# as long as the chunksize is 1 << 16.
self.assertGreater(f.numcalls, 2)
Expand Down Expand Up @@ -366,6 +366,18 @@ def test_fieldstorage_part_content_length(self):
self.assertEqual(fs.list[0].name, 'submit-name')
self.assertEqual(fs.list[0].value, 'Larry')

def test_content_length_no_content_disposition(self):
body = b'{"test":123}'
env = {
'CONTENT_LENGTH': len(body),
'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'application/json',
'wsgi.input': BytesIO(body),
}

form = cgi.FieldStorage(fp=env['wsgi.input'], environ=env)
self.assertEqual(form.file.read(), body.decode(form.encoding))

def test_field_storage_multipart_no_content_length(self):
fp = BytesIO(b"""--MyBoundary
Content-Disposition: form-data; name="my-arg"; filename="foo"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixes a bug in :mod:`cgi` :class:`cgi.FieldStorage` when a request has a "Content-Length" header but
without "Content-Disposition" headers.