| 1 | n/a | from test.support import check_warnings |
|---|
| 2 | n/a | import cgi |
|---|
| 3 | n/a | import os |
|---|
| 4 | n/a | import sys |
|---|
| 5 | n/a | import tempfile |
|---|
| 6 | n/a | import unittest |
|---|
| 7 | n/a | import warnings |
|---|
| 8 | n/a | from collections import namedtuple |
|---|
| 9 | n/a | from io import StringIO, BytesIO |
|---|
| 10 | n/a | from test import support |
|---|
| 11 | n/a | |
|---|
| 12 | n/a | class HackedSysModule: |
|---|
| 13 | n/a | # The regression test will have real values in sys.argv, which |
|---|
| 14 | n/a | # will completely confuse the test of the cgi module |
|---|
| 15 | n/a | argv = [] |
|---|
| 16 | n/a | stdin = sys.stdin |
|---|
| 17 | n/a | |
|---|
| 18 | n/a | cgi.sys = HackedSysModule() |
|---|
| 19 | n/a | |
|---|
| 20 | n/a | class ComparableException: |
|---|
| 21 | n/a | def __init__(self, err): |
|---|
| 22 | n/a | self.err = err |
|---|
| 23 | n/a | |
|---|
| 24 | n/a | def __str__(self): |
|---|
| 25 | n/a | return str(self.err) |
|---|
| 26 | n/a | |
|---|
| 27 | n/a | def __eq__(self, anExc): |
|---|
| 28 | n/a | if not isinstance(anExc, Exception): |
|---|
| 29 | n/a | return NotImplemented |
|---|
| 30 | n/a | return (self.err.__class__ == anExc.__class__ and |
|---|
| 31 | n/a | self.err.args == anExc.args) |
|---|
| 32 | n/a | |
|---|
| 33 | n/a | def __getattr__(self, attr): |
|---|
| 34 | n/a | return getattr(self.err, attr) |
|---|
| 35 | n/a | |
|---|
| 36 | n/a | def do_test(buf, method): |
|---|
| 37 | n/a | env = {} |
|---|
| 38 | n/a | if method == "GET": |
|---|
| 39 | n/a | fp = None |
|---|
| 40 | n/a | env['REQUEST_METHOD'] = 'GET' |
|---|
| 41 | n/a | env['QUERY_STRING'] = buf |
|---|
| 42 | n/a | elif method == "POST": |
|---|
| 43 | n/a | fp = BytesIO(buf.encode('latin-1')) # FieldStorage expects bytes |
|---|
| 44 | n/a | env['REQUEST_METHOD'] = 'POST' |
|---|
| 45 | n/a | env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' |
|---|
| 46 | n/a | env['CONTENT_LENGTH'] = str(len(buf)) |
|---|
| 47 | n/a | else: |
|---|
| 48 | n/a | raise ValueError("unknown method: %s" % method) |
|---|
| 49 | n/a | try: |
|---|
| 50 | n/a | return cgi.parse(fp, env, strict_parsing=1) |
|---|
| 51 | n/a | except Exception as err: |
|---|
| 52 | n/a | return ComparableException(err) |
|---|
| 53 | n/a | |
|---|
| 54 | n/a | parse_strict_test_cases = [ |
|---|
| 55 | n/a | ("", ValueError("bad query field: ''")), |
|---|
| 56 | n/a | ("&", ValueError("bad query field: ''")), |
|---|
| 57 | n/a | ("&&", ValueError("bad query field: ''")), |
|---|
| 58 | n/a | (";", ValueError("bad query field: ''")), |
|---|
| 59 | n/a | (";&;", ValueError("bad query field: ''")), |
|---|
| 60 | n/a | # Should the next few really be valid? |
|---|
| 61 | n/a | ("=", {}), |
|---|
| 62 | n/a | ("=&=", {}), |
|---|
| 63 | n/a | ("=;=", {}), |
|---|
| 64 | n/a | # This rest seem to make sense |
|---|
| 65 | n/a | ("=a", {'': ['a']}), |
|---|
| 66 | n/a | ("&=a", ValueError("bad query field: ''")), |
|---|
| 67 | n/a | ("=a&", ValueError("bad query field: ''")), |
|---|
| 68 | n/a | ("=&a", ValueError("bad query field: 'a'")), |
|---|
| 69 | n/a | ("b=a", {'b': ['a']}), |
|---|
| 70 | n/a | ("b+=a", {'b ': ['a']}), |
|---|
| 71 | n/a | ("a=b=a", {'a': ['b=a']}), |
|---|
| 72 | n/a | ("a=+b=a", {'a': [' b=a']}), |
|---|
| 73 | n/a | ("&b=a", ValueError("bad query field: ''")), |
|---|
| 74 | n/a | ("b&=a", ValueError("bad query field: 'b'")), |
|---|
| 75 | n/a | ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}), |
|---|
| 76 | n/a | ("a=a+b&a=b+a", {'a': ['a b', 'b a']}), |
|---|
| 77 | n/a | ("x=1&y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), |
|---|
| 78 | n/a | ("x=1;y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), |
|---|
| 79 | n/a | ("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), |
|---|
| 80 | n/a | ("Hbc5161168c542333633315dee1182227:key_store_seqid=400006&cuyer=r&view=bustomer&order_id=0bb2e248638833d48cb7fed300000f1b&expire=964546263&lobale=en-US&kid=130003.300038&ss=env", |
|---|
| 81 | n/a | {'Hbc5161168c542333633315dee1182227:key_store_seqid': ['400006'], |
|---|
| 82 | n/a | 'cuyer': ['r'], |
|---|
| 83 | n/a | 'expire': ['964546263'], |
|---|
| 84 | n/a | 'kid': ['130003.300038'], |
|---|
| 85 | n/a | 'lobale': ['en-US'], |
|---|
| 86 | n/a | 'order_id': ['0bb2e248638833d48cb7fed300000f1b'], |
|---|
| 87 | n/a | 'ss': ['env'], |
|---|
| 88 | n/a | 'view': ['bustomer'], |
|---|
| 89 | n/a | }), |
|---|
| 90 | n/a | |
|---|
| 91 | n/a | ("group_id=5470&set=custom&_assigned_to=31392&_status=1&_category=100&SUBMIT=Browse", |
|---|
| 92 | n/a | {'SUBMIT': ['Browse'], |
|---|
| 93 | n/a | '_assigned_to': ['31392'], |
|---|
| 94 | n/a | '_category': ['100'], |
|---|
| 95 | n/a | '_status': ['1'], |
|---|
| 96 | n/a | 'group_id': ['5470'], |
|---|
| 97 | n/a | 'set': ['custom'], |
|---|
| 98 | n/a | }) |
|---|
| 99 | n/a | ] |
|---|
| 100 | n/a | |
|---|
| 101 | n/a | def norm(seq): |
|---|
| 102 | n/a | return sorted(seq, key=repr) |
|---|
| 103 | n/a | |
|---|
| 104 | n/a | def first_elts(list): |
|---|
| 105 | n/a | return [p[0] for p in list] |
|---|
| 106 | n/a | |
|---|
| 107 | n/a | def first_second_elts(list): |
|---|
| 108 | n/a | return [(p[0], p[1][0]) for p in list] |
|---|
| 109 | n/a | |
|---|
| 110 | n/a | def gen_result(data, environ): |
|---|
| 111 | n/a | encoding = 'latin-1' |
|---|
| 112 | n/a | fake_stdin = BytesIO(data.encode(encoding)) |
|---|
| 113 | n/a | fake_stdin.seek(0) |
|---|
| 114 | n/a | form = cgi.FieldStorage(fp=fake_stdin, environ=environ, encoding=encoding) |
|---|
| 115 | n/a | |
|---|
| 116 | n/a | result = {} |
|---|
| 117 | n/a | for k, v in dict(form).items(): |
|---|
| 118 | n/a | result[k] = isinstance(v, list) and form.getlist(k) or v.value |
|---|
| 119 | n/a | |
|---|
| 120 | n/a | return result |
|---|
| 121 | n/a | |
|---|
| 122 | n/a | class CgiTests(unittest.TestCase): |
|---|
| 123 | n/a | |
|---|
| 124 | n/a | def test_parse_multipart(self): |
|---|
| 125 | n/a | fp = BytesIO(POSTDATA.encode('latin1')) |
|---|
| 126 | n/a | env = {'boundary': BOUNDARY.encode('latin1'), |
|---|
| 127 | n/a | 'CONTENT-LENGTH': '558'} |
|---|
| 128 | n/a | result = cgi.parse_multipart(fp, env) |
|---|
| 129 | n/a | expected = {'submit': [b' Add '], 'id': [b'1234'], |
|---|
| 130 | n/a | 'file': [b'Testing 123.\n'], 'title': [b'']} |
|---|
| 131 | n/a | self.assertEqual(result, expected) |
|---|
| 132 | n/a | |
|---|
| 133 | n/a | def test_fieldstorage_properties(self): |
|---|
| 134 | n/a | fs = cgi.FieldStorage() |
|---|
| 135 | n/a | self.assertFalse(fs) |
|---|
| 136 | n/a | self.assertIn("FieldStorage", repr(fs)) |
|---|
| 137 | n/a | self.assertEqual(list(fs), list(fs.keys())) |
|---|
| 138 | n/a | fs.list.append(namedtuple('MockFieldStorage', 'name')('fieldvalue')) |
|---|
| 139 | n/a | self.assertTrue(fs) |
|---|
| 140 | n/a | |
|---|
| 141 | n/a | def test_fieldstorage_invalid(self): |
|---|
| 142 | n/a | self.assertRaises(TypeError, cgi.FieldStorage, "not-a-file-obj", |
|---|
| 143 | n/a | environ={"REQUEST_METHOD":"PUT"}) |
|---|
| 144 | n/a | self.assertRaises(TypeError, cgi.FieldStorage, "foo", "bar") |
|---|
| 145 | n/a | fs = cgi.FieldStorage(headers={'content-type':'text/plain'}) |
|---|
| 146 | n/a | self.assertRaises(TypeError, bool, fs) |
|---|
| 147 | n/a | |
|---|
| 148 | n/a | def test_escape(self): |
|---|
| 149 | n/a | # cgi.escape() is deprecated. |
|---|
| 150 | n/a | with warnings.catch_warnings(): |
|---|
| 151 | n/a | warnings.filterwarnings('ignore', r'cgi\.escape', |
|---|
| 152 | n/a | DeprecationWarning) |
|---|
| 153 | n/a | self.assertEqual("test & string", cgi.escape("test & string")) |
|---|
| 154 | n/a | self.assertEqual("<test string>", cgi.escape("<test string>")) |
|---|
| 155 | n/a | self.assertEqual(""test string"", cgi.escape('"test string"', True)) |
|---|
| 156 | n/a | |
|---|
| 157 | n/a | def test_strict(self): |
|---|
| 158 | n/a | for orig, expect in parse_strict_test_cases: |
|---|
| 159 | n/a | # Test basic parsing |
|---|
| 160 | n/a | d = do_test(orig, "GET") |
|---|
| 161 | n/a | self.assertEqual(d, expect, "Error parsing %s method GET" % repr(orig)) |
|---|
| 162 | n/a | d = do_test(orig, "POST") |
|---|
| 163 | n/a | self.assertEqual(d, expect, "Error parsing %s method POST" % repr(orig)) |
|---|
| 164 | n/a | |
|---|
| 165 | n/a | env = {'QUERY_STRING': orig} |
|---|
| 166 | n/a | fs = cgi.FieldStorage(environ=env) |
|---|
| 167 | n/a | if isinstance(expect, dict): |
|---|
| 168 | n/a | # test dict interface |
|---|
| 169 | n/a | self.assertEqual(len(expect), len(fs)) |
|---|
| 170 | n/a | self.assertCountEqual(expect.keys(), fs.keys()) |
|---|
| 171 | n/a | ##self.assertEqual(norm(expect.values()), norm(fs.values())) |
|---|
| 172 | n/a | ##self.assertEqual(norm(expect.items()), norm(fs.items())) |
|---|
| 173 | n/a | self.assertEqual(fs.getvalue("nonexistent field", "default"), "default") |
|---|
| 174 | n/a | # test individual fields |
|---|
| 175 | n/a | for key in expect.keys(): |
|---|
| 176 | n/a | expect_val = expect[key] |
|---|
| 177 | n/a | self.assertIn(key, fs) |
|---|
| 178 | n/a | if len(expect_val) > 1: |
|---|
| 179 | n/a | self.assertEqual(fs.getvalue(key), expect_val) |
|---|
| 180 | n/a | else: |
|---|
| 181 | n/a | self.assertEqual(fs.getvalue(key), expect_val[0]) |
|---|
| 182 | n/a | |
|---|
| 183 | n/a | def test_log(self): |
|---|
| 184 | n/a | cgi.log("Testing") |
|---|
| 185 | n/a | |
|---|
| 186 | n/a | cgi.logfp = StringIO() |
|---|
| 187 | n/a | cgi.initlog("%s", "Testing initlog 1") |
|---|
| 188 | n/a | cgi.log("%s", "Testing log 2") |
|---|
| 189 | n/a | self.assertEqual(cgi.logfp.getvalue(), "Testing initlog 1\nTesting log 2\n") |
|---|
| 190 | n/a | if os.path.exists(os.devnull): |
|---|
| 191 | n/a | cgi.logfp = None |
|---|
| 192 | n/a | cgi.logfile = os.devnull |
|---|
| 193 | n/a | cgi.initlog("%s", "Testing log 3") |
|---|
| 194 | n/a | self.addCleanup(cgi.closelog) |
|---|
| 195 | n/a | cgi.log("Testing log 4") |
|---|
| 196 | n/a | |
|---|
| 197 | n/a | def test_fieldstorage_readline(self): |
|---|
| 198 | n/a | # FieldStorage uses readline, which has the capacity to read all |
|---|
| 199 | n/a | # contents of the input file into memory; we use readline's size argument |
|---|
| 200 | n/a | # to prevent that for files that do not contain any newlines in |
|---|
| 201 | n/a | # non-GET/HEAD requests |
|---|
| 202 | n/a | class TestReadlineFile: |
|---|
| 203 | n/a | def __init__(self, file): |
|---|
| 204 | n/a | self.file = file |
|---|
| 205 | n/a | self.numcalls = 0 |
|---|
| 206 | n/a | |
|---|
| 207 | n/a | def readline(self, size=None): |
|---|
| 208 | n/a | self.numcalls += 1 |
|---|
| 209 | n/a | if size: |
|---|
| 210 | n/a | return self.file.readline(size) |
|---|
| 211 | n/a | else: |
|---|
| 212 | n/a | return self.file.readline() |
|---|
| 213 | n/a | |
|---|
| 214 | n/a | def __getattr__(self, name): |
|---|
| 215 | n/a | file = self.__dict__['file'] |
|---|
| 216 | n/a | a = getattr(file, name) |
|---|
| 217 | n/a | if not isinstance(a, int): |
|---|
| 218 | n/a | setattr(self, name, a) |
|---|
| 219 | n/a | return a |
|---|
| 220 | n/a | |
|---|
| 221 | n/a | f = TestReadlineFile(tempfile.TemporaryFile("wb+")) |
|---|
| 222 | n/a | self.addCleanup(f.close) |
|---|
| 223 | n/a | f.write(b'x' * 256 * 1024) |
|---|
| 224 | n/a | f.seek(0) |
|---|
| 225 | n/a | env = {'REQUEST_METHOD':'PUT'} |
|---|
| 226 | n/a | fs = cgi.FieldStorage(fp=f, environ=env) |
|---|
| 227 | n/a | self.addCleanup(fs.file.close) |
|---|
| 228 | n/a | # if we're not chunking properly, readline is only called twice |
|---|
| 229 | n/a | # (by read_binary); if we are chunking properly, it will be called 5 times |
|---|
| 230 | n/a | # as long as the chunksize is 1 << 16. |
|---|
| 231 | n/a | self.assertGreater(f.numcalls, 2) |
|---|
| 232 | n/a | f.close() |
|---|
| 233 | n/a | |
|---|
| 234 | n/a | def test_fieldstorage_multipart(self): |
|---|
| 235 | n/a | #Test basic FieldStorage multipart parsing |
|---|
| 236 | n/a | env = { |
|---|
| 237 | n/a | 'REQUEST_METHOD': 'POST', |
|---|
| 238 | n/a | 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY), |
|---|
| 239 | n/a | 'CONTENT_LENGTH': '558'} |
|---|
| 240 | n/a | fp = BytesIO(POSTDATA.encode('latin-1')) |
|---|
| 241 | n/a | fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1") |
|---|
| 242 | n/a | self.assertEqual(len(fs.list), 4) |
|---|
| 243 | n/a | expect = [{'name':'id', 'filename':None, 'value':'1234'}, |
|---|
| 244 | n/a | {'name':'title', 'filename':None, 'value':''}, |
|---|
| 245 | n/a | {'name':'file', 'filename':'test.txt', 'value':b'Testing 123.\n'}, |
|---|
| 246 | n/a | {'name':'submit', 'filename':None, 'value':' Add '}] |
|---|
| 247 | n/a | for x in range(len(fs.list)): |
|---|
| 248 | n/a | for k, exp in expect[x].items(): |
|---|
| 249 | n/a | got = getattr(fs.list[x], k) |
|---|
| 250 | n/a | self.assertEqual(got, exp) |
|---|
| 251 | n/a | |
|---|
| 252 | n/a | def test_fieldstorage_multipart_leading_whitespace(self): |
|---|
| 253 | n/a | env = { |
|---|
| 254 | n/a | 'REQUEST_METHOD': 'POST', |
|---|
| 255 | n/a | 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY), |
|---|
| 256 | n/a | 'CONTENT_LENGTH': '560'} |
|---|
| 257 | n/a | # Add some leading whitespace to our post data that will cause the |
|---|
| 258 | n/a | # first line to not be the innerboundary. |
|---|
| 259 | n/a | fp = BytesIO(b"\r\n" + POSTDATA.encode('latin-1')) |
|---|
| 260 | n/a | fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1") |
|---|
| 261 | n/a | self.assertEqual(len(fs.list), 4) |
|---|
| 262 | n/a | expect = [{'name':'id', 'filename':None, 'value':'1234'}, |
|---|
| 263 | n/a | {'name':'title', 'filename':None, 'value':''}, |
|---|
| 264 | n/a | {'name':'file', 'filename':'test.txt', 'value':b'Testing 123.\n'}, |
|---|
| 265 | n/a | {'name':'submit', 'filename':None, 'value':' Add '}] |
|---|
| 266 | n/a | for x in range(len(fs.list)): |
|---|
| 267 | n/a | for k, exp in expect[x].items(): |
|---|
| 268 | n/a | got = getattr(fs.list[x], k) |
|---|
| 269 | n/a | self.assertEqual(got, exp) |
|---|
| 270 | n/a | |
|---|
| 271 | n/a | def test_fieldstorage_multipart_non_ascii(self): |
|---|
| 272 | n/a | #Test basic FieldStorage multipart parsing |
|---|
| 273 | n/a | env = {'REQUEST_METHOD':'POST', |
|---|
| 274 | n/a | 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY), |
|---|
| 275 | n/a | 'CONTENT_LENGTH':'558'} |
|---|
| 276 | n/a | for encoding in ['iso-8859-1','utf-8']: |
|---|
| 277 | n/a | fp = BytesIO(POSTDATA_NON_ASCII.encode(encoding)) |
|---|
| 278 | n/a | fs = cgi.FieldStorage(fp, environ=env,encoding=encoding) |
|---|
| 279 | n/a | self.assertEqual(len(fs.list), 1) |
|---|
| 280 | n/a | expect = [{'name':'id', 'filename':None, 'value':'\xe7\xf1\x80'}] |
|---|
| 281 | n/a | for x in range(len(fs.list)): |
|---|
| 282 | n/a | for k, exp in expect[x].items(): |
|---|
| 283 | n/a | got = getattr(fs.list[x], k) |
|---|
| 284 | n/a | self.assertEqual(got, exp) |
|---|
| 285 | n/a | |
|---|
| 286 | n/a | def test_fieldstorage_multipart_maxline(self): |
|---|
| 287 | n/a | # Issue #18167 |
|---|
| 288 | n/a | maxline = 1 << 16 |
|---|
| 289 | n/a | self.maxDiff = None |
|---|
| 290 | n/a | def check(content): |
|---|
| 291 | n/a | data = """---123 |
|---|
| 292 | n/a | Content-Disposition: form-data; name="upload"; filename="fake.txt" |
|---|
| 293 | n/a | Content-Type: text/plain |
|---|
| 294 | n/a | |
|---|
| 295 | n/a | %s |
|---|
| 296 | n/a | ---123-- |
|---|
| 297 | n/a | """.replace('\n', '\r\n') % content |
|---|
| 298 | n/a | environ = { |
|---|
| 299 | n/a | 'CONTENT_LENGTH': str(len(data)), |
|---|
| 300 | n/a | 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', |
|---|
| 301 | n/a | 'REQUEST_METHOD': 'POST', |
|---|
| 302 | n/a | } |
|---|
| 303 | n/a | self.assertEqual(gen_result(data, environ), |
|---|
| 304 | n/a | {'upload': content.encode('latin1')}) |
|---|
| 305 | n/a | check('x' * (maxline - 1)) |
|---|
| 306 | n/a | check('x' * (maxline - 1) + '\r') |
|---|
| 307 | n/a | check('x' * (maxline - 1) + '\r' + 'y' * (maxline - 1)) |
|---|
| 308 | n/a | |
|---|
| 309 | n/a | def test_fieldstorage_multipart_w3c(self): |
|---|
| 310 | n/a | # Test basic FieldStorage multipart parsing (W3C sample) |
|---|
| 311 | n/a | env = { |
|---|
| 312 | n/a | 'REQUEST_METHOD': 'POST', |
|---|
| 313 | n/a | 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY_W3), |
|---|
| 314 | n/a | 'CONTENT_LENGTH': str(len(POSTDATA_W3))} |
|---|
| 315 | n/a | fp = BytesIO(POSTDATA_W3.encode('latin-1')) |
|---|
| 316 | n/a | fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1") |
|---|
| 317 | n/a | self.assertEqual(len(fs.list), 2) |
|---|
| 318 | n/a | self.assertEqual(fs.list[0].name, 'submit-name') |
|---|
| 319 | n/a | self.assertEqual(fs.list[0].value, 'Larry') |
|---|
| 320 | n/a | self.assertEqual(fs.list[1].name, 'files') |
|---|
| 321 | n/a | files = fs.list[1].value |
|---|
| 322 | n/a | self.assertEqual(len(files), 2) |
|---|
| 323 | n/a | expect = [{'name': None, 'filename': 'file1.txt', 'value': b'... contents of file1.txt ...'}, |
|---|
| 324 | n/a | {'name': None, 'filename': 'file2.gif', 'value': b'...contents of file2.gif...'}] |
|---|
| 325 | n/a | for x in range(len(files)): |
|---|
| 326 | n/a | for k, exp in expect[x].items(): |
|---|
| 327 | n/a | got = getattr(files[x], k) |
|---|
| 328 | n/a | self.assertEqual(got, exp) |
|---|
| 329 | n/a | |
|---|
| 330 | n/a | def test_fieldstorage_part_content_length(self): |
|---|
| 331 | n/a | BOUNDARY = "JfISa01" |
|---|
| 332 | n/a | POSTDATA = """--JfISa01 |
|---|
| 333 | n/a | Content-Disposition: form-data; name="submit-name" |
|---|
| 334 | n/a | Content-Length: 5 |
|---|
| 335 | n/a | |
|---|
| 336 | n/a | Larry |
|---|
| 337 | n/a | --JfISa01""" |
|---|
| 338 | n/a | env = { |
|---|
| 339 | n/a | 'REQUEST_METHOD': 'POST', |
|---|
| 340 | n/a | 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY), |
|---|
| 341 | n/a | 'CONTENT_LENGTH': str(len(POSTDATA))} |
|---|
| 342 | n/a | fp = BytesIO(POSTDATA.encode('latin-1')) |
|---|
| 343 | n/a | fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1") |
|---|
| 344 | n/a | self.assertEqual(len(fs.list), 1) |
|---|
| 345 | n/a | self.assertEqual(fs.list[0].name, 'submit-name') |
|---|
| 346 | n/a | self.assertEqual(fs.list[0].value, 'Larry') |
|---|
| 347 | n/a | |
|---|
| 348 | n/a | def test_fieldstorage_as_context_manager(self): |
|---|
| 349 | n/a | fp = BytesIO(b'x' * 10) |
|---|
| 350 | n/a | env = {'REQUEST_METHOD': 'PUT'} |
|---|
| 351 | n/a | with cgi.FieldStorage(fp=fp, environ=env) as fs: |
|---|
| 352 | n/a | content = fs.file.read() |
|---|
| 353 | n/a | self.assertFalse(fs.file.closed) |
|---|
| 354 | n/a | self.assertTrue(fs.file.closed) |
|---|
| 355 | n/a | self.assertEqual(content, 'x' * 10) |
|---|
| 356 | n/a | with self.assertRaisesRegex(ValueError, 'I/O operation on closed file'): |
|---|
| 357 | n/a | fs.file.read() |
|---|
| 358 | n/a | |
|---|
| 359 | n/a | _qs_result = { |
|---|
| 360 | n/a | 'key1': 'value1', |
|---|
| 361 | n/a | 'key2': ['value2x', 'value2y'], |
|---|
| 362 | n/a | 'key3': 'value3', |
|---|
| 363 | n/a | 'key4': 'value4' |
|---|
| 364 | n/a | } |
|---|
| 365 | n/a | def testQSAndUrlEncode(self): |
|---|
| 366 | n/a | data = "key2=value2x&key3=value3&key4=value4" |
|---|
| 367 | n/a | environ = { |
|---|
| 368 | n/a | 'CONTENT_LENGTH': str(len(data)), |
|---|
| 369 | n/a | 'CONTENT_TYPE': 'application/x-www-form-urlencoded', |
|---|
| 370 | n/a | 'QUERY_STRING': 'key1=value1&key2=value2y', |
|---|
| 371 | n/a | 'REQUEST_METHOD': 'POST', |
|---|
| 372 | n/a | } |
|---|
| 373 | n/a | v = gen_result(data, environ) |
|---|
| 374 | n/a | self.assertEqual(self._qs_result, v) |
|---|
| 375 | n/a | |
|---|
| 376 | n/a | def testQSAndFormData(self): |
|---|
| 377 | n/a | data = """---123 |
|---|
| 378 | n/a | Content-Disposition: form-data; name="key2" |
|---|
| 379 | n/a | |
|---|
| 380 | n/a | value2y |
|---|
| 381 | n/a | ---123 |
|---|
| 382 | n/a | Content-Disposition: form-data; name="key3" |
|---|
| 383 | n/a | |
|---|
| 384 | n/a | value3 |
|---|
| 385 | n/a | ---123 |
|---|
| 386 | n/a | Content-Disposition: form-data; name="key4" |
|---|
| 387 | n/a | |
|---|
| 388 | n/a | value4 |
|---|
| 389 | n/a | ---123-- |
|---|
| 390 | n/a | """ |
|---|
| 391 | n/a | environ = { |
|---|
| 392 | n/a | 'CONTENT_LENGTH': str(len(data)), |
|---|
| 393 | n/a | 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', |
|---|
| 394 | n/a | 'QUERY_STRING': 'key1=value1&key2=value2x', |
|---|
| 395 | n/a | 'REQUEST_METHOD': 'POST', |
|---|
| 396 | n/a | } |
|---|
| 397 | n/a | v = gen_result(data, environ) |
|---|
| 398 | n/a | self.assertEqual(self._qs_result, v) |
|---|
| 399 | n/a | |
|---|
| 400 | n/a | def testQSAndFormDataFile(self): |
|---|
| 401 | n/a | data = """---123 |
|---|
| 402 | n/a | Content-Disposition: form-data; name="key2" |
|---|
| 403 | n/a | |
|---|
| 404 | n/a | value2y |
|---|
| 405 | n/a | ---123 |
|---|
| 406 | n/a | Content-Disposition: form-data; name="key3" |
|---|
| 407 | n/a | |
|---|
| 408 | n/a | value3 |
|---|
| 409 | n/a | ---123 |
|---|
| 410 | n/a | Content-Disposition: form-data; name="key4" |
|---|
| 411 | n/a | |
|---|
| 412 | n/a | value4 |
|---|
| 413 | n/a | ---123 |
|---|
| 414 | n/a | Content-Disposition: form-data; name="upload"; filename="fake.txt" |
|---|
| 415 | n/a | Content-Type: text/plain |
|---|
| 416 | n/a | |
|---|
| 417 | n/a | this is the content of the fake file |
|---|
| 418 | n/a | |
|---|
| 419 | n/a | ---123-- |
|---|
| 420 | n/a | """ |
|---|
| 421 | n/a | environ = { |
|---|
| 422 | n/a | 'CONTENT_LENGTH': str(len(data)), |
|---|
| 423 | n/a | 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', |
|---|
| 424 | n/a | 'QUERY_STRING': 'key1=value1&key2=value2x', |
|---|
| 425 | n/a | 'REQUEST_METHOD': 'POST', |
|---|
| 426 | n/a | } |
|---|
| 427 | n/a | result = self._qs_result.copy() |
|---|
| 428 | n/a | result.update({ |
|---|
| 429 | n/a | 'upload': b'this is the content of the fake file\n' |
|---|
| 430 | n/a | }) |
|---|
| 431 | n/a | v = gen_result(data, environ) |
|---|
| 432 | n/a | self.assertEqual(result, v) |
|---|
| 433 | n/a | |
|---|
| 434 | n/a | def test_deprecated_parse_qs(self): |
|---|
| 435 | n/a | # this func is moved to urllib.parse, this is just a sanity check |
|---|
| 436 | n/a | with check_warnings(('cgi.parse_qs is deprecated, use urllib.parse.' |
|---|
| 437 | n/a | 'parse_qs instead', DeprecationWarning)): |
|---|
| 438 | n/a | self.assertEqual({'a': ['A1'], 'B': ['B3'], 'b': ['B2']}, |
|---|
| 439 | n/a | cgi.parse_qs('a=A1&b=B2&B=B3')) |
|---|
| 440 | n/a | |
|---|
| 441 | n/a | def test_deprecated_parse_qsl(self): |
|---|
| 442 | n/a | # this func is moved to urllib.parse, this is just a sanity check |
|---|
| 443 | n/a | with check_warnings(('cgi.parse_qsl is deprecated, use urllib.parse.' |
|---|
| 444 | n/a | 'parse_qsl instead', DeprecationWarning)): |
|---|
| 445 | n/a | self.assertEqual([('a', 'A1'), ('b', 'B2'), ('B', 'B3')], |
|---|
| 446 | n/a | cgi.parse_qsl('a=A1&b=B2&B=B3')) |
|---|
| 447 | n/a | |
|---|
| 448 | n/a | def test_parse_header(self): |
|---|
| 449 | n/a | self.assertEqual( |
|---|
| 450 | n/a | cgi.parse_header("text/plain"), |
|---|
| 451 | n/a | ("text/plain", {})) |
|---|
| 452 | n/a | self.assertEqual( |
|---|
| 453 | n/a | cgi.parse_header("text/vnd.just.made.this.up ; "), |
|---|
| 454 | n/a | ("text/vnd.just.made.this.up", {})) |
|---|
| 455 | n/a | self.assertEqual( |
|---|
| 456 | n/a | cgi.parse_header("text/plain;charset=us-ascii"), |
|---|
| 457 | n/a | ("text/plain", {"charset": "us-ascii"})) |
|---|
| 458 | n/a | self.assertEqual( |
|---|
| 459 | n/a | cgi.parse_header('text/plain ; charset="us-ascii"'), |
|---|
| 460 | n/a | ("text/plain", {"charset": "us-ascii"})) |
|---|
| 461 | n/a | self.assertEqual( |
|---|
| 462 | n/a | cgi.parse_header('text/plain ; charset="us-ascii"; another=opt'), |
|---|
| 463 | n/a | ("text/plain", {"charset": "us-ascii", "another": "opt"})) |
|---|
| 464 | n/a | self.assertEqual( |
|---|
| 465 | n/a | cgi.parse_header('attachment; filename="silly.txt"'), |
|---|
| 466 | n/a | ("attachment", {"filename": "silly.txt"})) |
|---|
| 467 | n/a | self.assertEqual( |
|---|
| 468 | n/a | cgi.parse_header('attachment; filename="strange;name"'), |
|---|
| 469 | n/a | ("attachment", {"filename": "strange;name"})) |
|---|
| 470 | n/a | self.assertEqual( |
|---|
| 471 | n/a | cgi.parse_header('attachment; filename="strange;name";size=123;'), |
|---|
| 472 | n/a | ("attachment", {"filename": "strange;name", "size": "123"})) |
|---|
| 473 | n/a | self.assertEqual( |
|---|
| 474 | n/a | cgi.parse_header('form-data; name="files"; filename="fo\\"o;bar"'), |
|---|
| 475 | n/a | ("form-data", {"name": "files", "filename": 'fo"o;bar'})) |
|---|
| 476 | n/a | |
|---|
| 477 | n/a | def test_all(self): |
|---|
| 478 | n/a | blacklist = {"logfile", "logfp", "initlog", "dolog", "nolog", |
|---|
| 479 | n/a | "closelog", "log", "maxlen", "valid_boundary"} |
|---|
| 480 | n/a | support.check__all__(self, cgi, blacklist=blacklist) |
|---|
| 481 | n/a | |
|---|
| 482 | n/a | |
|---|
| 483 | n/a | BOUNDARY = "---------------------------721837373350705526688164684" |
|---|
| 484 | n/a | |
|---|
| 485 | n/a | POSTDATA = """-----------------------------721837373350705526688164684 |
|---|
| 486 | n/a | Content-Disposition: form-data; name="id" |
|---|
| 487 | n/a | |
|---|
| 488 | n/a | 1234 |
|---|
| 489 | n/a | -----------------------------721837373350705526688164684 |
|---|
| 490 | n/a | Content-Disposition: form-data; name="title" |
|---|
| 491 | n/a | |
|---|
| 492 | n/a | |
|---|
| 493 | n/a | -----------------------------721837373350705526688164684 |
|---|
| 494 | n/a | Content-Disposition: form-data; name="file"; filename="test.txt" |
|---|
| 495 | n/a | Content-Type: text/plain |
|---|
| 496 | n/a | |
|---|
| 497 | n/a | Testing 123. |
|---|
| 498 | n/a | |
|---|
| 499 | n/a | -----------------------------721837373350705526688164684 |
|---|
| 500 | n/a | Content-Disposition: form-data; name="submit" |
|---|
| 501 | n/a | |
|---|
| 502 | n/a | Add\x20 |
|---|
| 503 | n/a | -----------------------------721837373350705526688164684-- |
|---|
| 504 | n/a | """ |
|---|
| 505 | n/a | |
|---|
| 506 | n/a | POSTDATA_NON_ASCII = """-----------------------------721837373350705526688164684 |
|---|
| 507 | n/a | Content-Disposition: form-data; name="id" |
|---|
| 508 | n/a | |
|---|
| 509 | n/a | \xe7\xf1\x80 |
|---|
| 510 | n/a | -----------------------------721837373350705526688164684 |
|---|
| 511 | n/a | """ |
|---|
| 512 | n/a | |
|---|
| 513 | n/a | # http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4 |
|---|
| 514 | n/a | BOUNDARY_W3 = "AaB03x" |
|---|
| 515 | n/a | POSTDATA_W3 = """--AaB03x |
|---|
| 516 | n/a | Content-Disposition: form-data; name="submit-name" |
|---|
| 517 | n/a | |
|---|
| 518 | n/a | Larry |
|---|
| 519 | n/a | --AaB03x |
|---|
| 520 | n/a | Content-Disposition: form-data; name="files" |
|---|
| 521 | n/a | Content-Type: multipart/mixed; boundary=BbC04y |
|---|
| 522 | n/a | |
|---|
| 523 | n/a | --BbC04y |
|---|
| 524 | n/a | Content-Disposition: file; filename="file1.txt" |
|---|
| 525 | n/a | Content-Type: text/plain |
|---|
| 526 | n/a | |
|---|
| 527 | n/a | ... contents of file1.txt ... |
|---|
| 528 | n/a | --BbC04y |
|---|
| 529 | n/a | Content-Disposition: file; filename="file2.gif" |
|---|
| 530 | n/a | Content-Type: image/gif |
|---|
| 531 | n/a | Content-Transfer-Encoding: binary |
|---|
| 532 | n/a | |
|---|
| 533 | n/a | ...contents of file2.gif... |
|---|
| 534 | n/a | --BbC04y-- |
|---|
| 535 | n/a | --AaB03x-- |
|---|
| 536 | n/a | """ |
|---|
| 537 | n/a | |
|---|
| 538 | n/a | if __name__ == '__main__': |
|---|
| 539 | n/a | unittest.main() |
|---|