Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 64445) +++ Misc/NEWS (working copy) @@ -108,6 +108,8 @@ Library ------- +- Issue #1817: cgi now correctly handles the querystring on POST requests + - Issue #3136: fileConfig()'s disabling of old loggers is now conditional via an optional disable_existing_loggers parameter, but the default value is such that the old behaviour is preserved. Thanks to Leandro Lucarella for Index: Doc/library/cgi.rst =================================================================== --- Doc/library/cgi.rst (revision 64445) +++ Doc/library/cgi.rst (working copy) @@ -165,6 +165,8 @@ actually be instances of the class :class:`MiniFieldStorage`. In this case, the :attr:`list`, :attr:`file`, and :attr:`filename` attributes are always ``None``. +A form submitted via POST that also has a query string will contain both +:class:`FieldStorage` and :class:`MiniFieldStorage` items. Higher Level Interface ---------------------- Index: Lib/cgi.py =================================================================== --- Lib/cgi.py (revision 64445) +++ 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.py =================================================================== --- Lib/test/test_cgi.py (revision 64445) +++ Lib/test/test_cgi.py (working copy) @@ -130,6 +130,17 @@ def first_second_elts(list): return map(lambda p:(p[0], p[1][0]), list) +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 CgiTests(unittest.TestCase): def test_qsl(self): @@ -277,7 +288,84 @@ for k, exp in expect[x].items(): got = getattr(fs.list[x], k) self.assertEquals(got, exp) + + _qs_result = { + 'key1': 'value1', + 'key2': ['value2x', 'value2y'], + 'key3': 'value3', + 'key4': 'value4' + } + def testQSAndUrlEncode(self): + data = "key2=value2x&key3=value3&key4=value4" + environ = { + 'CONTENT_LENGTH': str(len(data)), + 'CONTENT_TYPE': 'application/x-www-form-urlencoded', + 'QUERY_STRING': 'key1=value1&key2=value2y', + 'REQUEST_METHOD': 'POST', + } + v = gen_result(data, environ) + self.assertEqual(self._qs_result, v) + def testQSAndFormData(self): + 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-- +""" + environ = { + 'CONTENT_LENGTH': str(len(data)), + 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', + 'QUERY_STRING': 'key1=value1&key2=value2x', + 'REQUEST_METHOD': 'POST', + } + v = gen_result(data, environ) + self.assertEqual(self._qs_result, v) + + def testQSAndFormDataFile(self): + 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-- +""" + environ = { + 'CONTENT_LENGTH': str(len(data)), + 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', + 'QUERY_STRING': 'key1=value1&key2=value2x', + 'REQUEST_METHOD': 'POST', + } + result = self._qs_result.copy() + result.update({ + 'upload': 'this is the content of the fake file\n' + }) + v = gen_result(data, environ) + self.assertEqual(result, v) + def test_main(): run_unittest(CgiTests)