Index: Lib/cgi.py =================================================================== --- Lib/cgi.py (revision 64442) +++ Lib/cgi.py (working copy) @@ -456,6 +456,7 @@ self.strict_parsing = strict_parsing if 'REQUEST_METHOD' in environ: method = environ['REQUEST_METHOD'].upper() + self.qs_on_post = None if method == 'GET' or method == 'HEAD': if 'QUERY_STRING' in environ: qs = environ['QUERY_STRING'] @@ -474,6 +475,8 @@ headers['content-type'] = "application/x-www-form-urlencoded" if 'CONTENT_TYPE' in environ: headers['content-type'] = environ['CONTENT_TYPE'] + if 'QUERY_STRING' in environ: + self.qs_on_post = environ['QUERY_STRING'] if 'CONTENT_LENGTH' in environ: headers['content-length'] = environ['CONTENT_LENGTH'] self.fp = fp or sys.stdin @@ -631,6 +634,8 @@ def read_urlencoded(self): """Internal: read data in query string format.""" qs = self.fp.read(self.length) + if self.qs_on_post: + qs += '&' + self.qs_on_post self.list = list = [] for key, value in parse_qsl(qs, self.keep_blank_values, self.strict_parsing): @@ -645,6 +650,12 @@ if not valid_boundary(ib): raise ValueError, 'Invalid boundary in multipart form: %r' % (ib,) self.list = [] + if self.qs_on_post: + for key, value in parse_qsl(self.qs_on_post, self.keep_blank_values, + self.strict_parsing): + self.list.append(MiniFieldStorage(key, value)) + FieldStorageClass = None + klass = self.FieldStorageClass or self.__class__ part = klass(self.fp, {}, ib, environ, keep_blank_values, strict_parsing) Index: Lib/test/test_cgi_post_qs.py =================================================================== --- Lib/test/test_cgi_post_qs.py (revision 0) +++ Lib/test/test_cgi_post_qs.py (revision 0) @@ -0,0 +1,183 @@ +#!/usr/bin/env python +""" +This suite is going to realize three tests, simulating +three different HTML form to the cgi module. + +1st Form: +
+ + + +
+ +As the original cgi module cannot handle QUERY_STRING on POST, +key1 will never be present on the result of cgi.FieldStorage(). +Also, key2 is present in QUERY_STRING and on POST data, so, it +should generate a list accessed by form.getlist('key2'). +In this case, cgi.FieldStorage() is going to return a list of +MiniFieldStorage objects. + + +2nd Form: +
+ + + +
+ +When the form is encoded, cgi.FieldStorage() return a list of +FieldStorage objects instead of MiniFieldStorage. The original +cgi module completely ignores QUERY_STRING and key1 will never +be present, and, again, key2 won't be a list, but a FieldStorage +object. With the patch, that result will be a mix of FieldStorage +for values on the form and MiniFieldStorage for values on +QUERY_STRING, like this: + +original cgi module produces: +FieldStorage(None, None, [ + FieldStorage('key2', None, 'value2y'), + FieldStorage('key3', None, 'value3'), + FieldStorage('key4', None, 'value4') +]) + +patched cgi module produces: +FieldStorage(None, None, [ + MiniFieldStorage('key1', 'value1'), + MiniFieldStorage('key2', 'value2x'), + FieldStorage('key2', None, 'value2y'), + FieldStorage('key3', None, 'value3'), + FieldStorage('key4', None, 'value4') +]) + + +3rd Form: +This is exactly like the 2nd one, except that +it adds a field of type "file", for file upload. + +original cgi module produces: +FieldStorage(None, None, [ + FieldStorage('key2', None, 'value2y'), + FieldStorage('key3', None, 'value3'), + FieldStorage('key4', None, 'value4') + FieldStorage('upload', 'fake.txt', 'this is the content of the fake file\n') +]) + +patched cgi module produces: +FieldStorage(None, None, [ + MiniFieldStorage('key1', 'value1'), + MiniFieldStorage('key2', 'value2x'), + FieldStorage('key2', None, 'value2y'), + FieldStorage('key3', None, 'value3'), + FieldStorage('key4', None, 'value4'), + FieldStorage('upload', 'fake.txt', 'this is the content of the fake file\n') +]) +""" + +from StringIO import StringIO +import cgi, unittest + + +# test1: regular POST with QS +urlencode_data = "key2=value2x&key3=value3&key4=value4" +urlencode_environ = { + 'CONTENT_LENGTH': str(len(urlencode_data)), + 'CONTENT_TYPE': 'application/x-www-form-urlencoded', + 'QUERY_STRING': 'key1=value1&key2=value2y', + 'REQUEST_METHOD': 'POST', +} + + +# test2: encoded POST with QS +formdata_data = """ +---123 +Content-Disposition: form-data; name="key2" + +value2y +---123 +Content-Disposition: form-data; name="key3" + +value3 +---123 +Content-Disposition: form-data; name="key4" + +value4 +---123-- +""" +formdata_environ = { + 'CONTENT_LENGTH': str(len(formdata_data)), + 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', + 'QUERY_STRING': 'key1=value1&key2=value2x', + 'REQUEST_METHOD': 'POST', +} + + +# test3: encoded POST with file upload and QS +formdata_file_data = """ +---123 +Content-Disposition: form-data; name="key2" + +value2y +---123 +Content-Disposition: form-data; name="key3" + +value3 +---123 +Content-Disposition: form-data; name="key4" + +value4 +---123 +Content-Disposition: form-data; name="upload"; filename="fake.txt" +Content-Type: text/plain + +this is the content of the fake file + +---123-- +""" +formdata_file_environ = { + 'CONTENT_LENGTH': str(len(formdata_file_data)), + 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', + 'QUERY_STRING': 'key1=value1&key2=value2x', + 'REQUEST_METHOD': 'POST', +} + + +def gen_result(data, environ): + fake_stdin = StringIO(data) + fake_stdin.seek(0) + form = cgi.FieldStorage(fp=fake_stdin, environ=environ) + + result = {} + for k, v in dict(form).items(): + result[k] = type(v) is list and form.getlist(k) or v.value + + return result + + +class TestCGI(unittest.TestCase): + result = { + 'key1': 'value1', + 'key2': ['value2x', 'value2y'], + 'key3': 'value3', + 'key4': 'value4' + } + + def testUrlEncode(self): + v = gen_result(urlencode_data, urlencode_environ) + self.assertEqual(self.result, v) + + def testFormData(self): + v = gen_result(formdata_data, formdata_environ) + self.assertEqual(self.result, v) + + def testFormDataFile(self): + result = self.result.copy() + result.update({ + 'upload': 'this is the content of the fake file\n' + }) + v = gen_result(formdata_file_data, formdata_file_environ) + self.assertEqual(result, v) + + +if __name__ == '__main__': + unittest.main()