1from test.test_support import run_unittest, check_warnings 2import cgi 3import os 4import sys 5import tempfile 6import unittest 7 8from collections import namedtuple 9 10class HackedSysModule: 11 # The regression test will have real values in sys.argv, which 12 # will completely confuse the test of the cgi module 13 argv = [] 14 stdin = sys.stdin 15 16cgi.sys = HackedSysModule() 17 18try: 19 from cStringIO import StringIO 20except ImportError: 21 from StringIO import StringIO 22 23class ComparableException: 24 def __init__(self, err): 25 self.err = err 26 27 def __str__(self): 28 return str(self.err) 29 30 def __cmp__(self, anExc): 31 if not isinstance(anExc, Exception): 32 return -1 33 x = cmp(self.err.__class__, anExc.__class__) 34 if x != 0: 35 return x 36 return cmp(self.err.args, anExc.args) 37 38 def __getattr__(self, attr): 39 return getattr(self.err, attr) 40 41def do_test(buf, method): 42 env = {} 43 if method == "GET": 44 fp = None 45 env['REQUEST_METHOD'] = 'GET' 46 env['QUERY_STRING'] = buf 47 elif method == "POST": 48 fp = StringIO(buf) 49 env['REQUEST_METHOD'] = 'POST' 50 env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' 51 env['CONTENT_LENGTH'] = str(len(buf)) 52 else: 53 raise ValueError, "unknown method: %s" % method 54 try: 55 return cgi.parse(fp, env, strict_parsing=1) 56 except StandardError, err: 57 return ComparableException(err) 58 59parse_strict_test_cases = [ 60 ("", ValueError("bad query field: ''")), 61 ("&", ValueError("bad query field: ''")), 62 ("&&", ValueError("bad query field: ''")), 63 (";", ValueError("bad query field: ''")), 64 (";&;", ValueError("bad query field: ''")), 65 # Should the next few really be valid? 66 ("=", {}), 67 ("=&=", {}), 68 ("=;=", {}), 69 # This rest seem to make sense 70 ("=a", {'': ['a']}), 71 ("&=a", ValueError("bad query field: ''")), 72 ("=a&", ValueError("bad query field: ''")), 73 ("=&a", ValueError("bad query field: 'a'")), 74 ("b=a", {'b': ['a']}), 75 ("b+=a", {'b ': ['a']}), 76 ("a=b=a", {'a': ['b=a']}), 77 ("a=+b=a", {'a': [' b=a']}), 78 ("&b=a", ValueError("bad query field: ''")), 79 ("b&=a", ValueError("bad query field: 'b'")), 80 ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}), 81 ("a=a+b&a=b+a", {'a': ['a b', 'b a']}), 82 ("x=1&y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), 83 ("x=1;y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), 84 ("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), 85 ("Hbc5161168c542333633315dee1182227:key_store_seqid=400006&cuyer=r&view=bustomer&order_id=0bb2e248638833d48cb7fed300000f1b&expire=964546263&lobale=en-US&kid=130003.300038&ss=env", 86 {'Hbc5161168c542333633315dee1182227:key_store_seqid': ['400006'], 87 'cuyer': ['r'], 88 'expire': ['964546263'], 89 'kid': ['130003.300038'], 90 'lobale': ['en-US'], 91 'order_id': ['0bb2e248638833d48cb7fed300000f1b'], 92 'ss': ['env'], 93 'view': ['bustomer'], 94 }), 95 96 ("group_id=5470&set=custom&_assigned_to=31392&_status=1&_category=100&SUBMIT=Browse", 97 {'SUBMIT': ['Browse'], 98 '_assigned_to': ['31392'], 99 '_category': ['100'], 100 '_status': ['1'], 101 'group_id': ['5470'], 102 'set': ['custom'], 103 }) 104 ] 105 106def first_elts(list): 107 return map(lambda x:x[0], list) 108 109def first_second_elts(list): 110 return map(lambda p:(p[0], p[1][0]), list) 111 112def gen_result(data, environ): 113 fake_stdin = StringIO(data) 114 fake_stdin.seek(0) 115 form = cgi.FieldStorage(fp=fake_stdin, environ=environ) 116 117 result = {} 118 for k, v in dict(form).items(): 119 result[k] = isinstance(v, list) and form.getlist(k) or v.value 120 121 return result 122 123class CgiTests(unittest.TestCase): 124 125 def test_escape(self): 126 self.assertEqual("test & string", cgi.escape("test & string")) 127 self.assertEqual("<test string>", cgi.escape("<test string>")) 128 self.assertEqual(""test string"", cgi.escape('"test string"', True)) 129 130 def test_strict(self): 131 for orig, expect in parse_strict_test_cases: 132 # Test basic parsing 133 d = do_test(orig, "GET") 134 self.assertEqual(d, expect, "Error parsing %s" % repr(orig)) 135 d = do_test(orig, "POST") 136 self.assertEqual(d, expect, "Error parsing %s" % repr(orig)) 137 138 env = {'QUERY_STRING': orig} 139 fcd = cgi.FormContentDict(env) 140 sd = cgi.SvFormContentDict(env) 141 fs = cgi.FieldStorage(environ=env) 142 if isinstance(expect, dict): 143 # test dict interface 144 self.assertEqual(len(expect), len(fcd)) 145 self.assertItemsEqual(expect.keys(), fcd.keys()) 146 self.assertItemsEqual(expect.values(), fcd.values()) 147 self.assertItemsEqual(expect.items(), fcd.items()) 148 self.assertEqual(fcd.get("nonexistent field", "default"), "default") 149 self.assertEqual(len(sd), len(fs)) 150 self.assertItemsEqual(sd.keys(), fs.keys()) 151 self.assertEqual(fs.getvalue("nonexistent field", "default"), "default") 152 # test individual fields 153 for key in expect.keys(): 154 expect_val = expect[key] 155 self.assertTrue(fcd.has_key(key)) 156 self.assertItemsEqual(fcd[key], expect[key]) 157 self.assertEqual(fcd.get(key, "default"), fcd[key]) 158 self.assertTrue(fs.has_key(key)) 159 if len(expect_val) > 1: 160 single_value = 0 161 else: 162 single_value = 1 163 try: 164 val = sd[key] 165 except IndexError: 166 self.assertFalse(single_value) 167 self.assertEqual(fs.getvalue(key), expect_val) 168 else: 169 self.assertTrue(single_value) 170 self.assertEqual(val, expect_val[0]) 171 self.assertEqual(fs.getvalue(key), expect_val[0]) 172 self.assertItemsEqual(sd.getlist(key), expect_val) 173 if single_value: 174 self.assertItemsEqual(sd.values(), 175 first_elts(expect.values())) 176 self.assertItemsEqual(sd.items(), 177 first_second_elts(expect.items())) 178 179 def test_weird_formcontentdict(self): 180 # Test the weird FormContentDict classes 181 env = {'QUERY_STRING': "x=1&y=2.0&z=2-3.%2b0&1=1abc"} 182 expect = {'x': 1, 'y': 2.0, 'z': '2-3.+0', '1': '1abc'} 183 d = cgi.InterpFormContentDict(env) 184 for k, v in expect.items(): 185 self.assertEqual(d[k], v) 186 for k, v in d.items(): 187 self.assertEqual(expect[k], v) 188 self.assertItemsEqual(expect.values(), d.values()) 189 190 def test_log(self): 191 cgi.log("Testing") 192 193 cgi.logfp = StringIO() 194 cgi.initlog("%s", "Testing initlog 1") 195 cgi.log("%s", "Testing log 2") 196 self.assertEqual(cgi.logfp.getvalue(), "Testing initlog 1\nTesting log 2\n") 197 if os.path.exists("/dev/null"): 198 cgi.logfp = None 199 cgi.logfile = "/dev/null" 200 cgi.initlog("%s", "Testing log 3") 201 cgi.log("Testing log 4") 202 203 def test_fieldstorage_readline(self): 204 # FieldStorage uses readline, which has the capacity to read all 205 # contents of the input file into memory; we use readline's size argument 206 # to prevent that for files that do not contain any newlines in 207 # non-GET/HEAD requests 208 class TestReadlineFile: 209 def __init__(self, file): 210 self.file = file 211 self.numcalls = 0 212 213 def readline(self, size=None): 214 self.numcalls += 1 215 if size: 216 return self.file.readline(size) 217 else: 218 return self.file.readline() 219 220 def __getattr__(self, name): 221 file = self.__dict__['file'] 222 a = getattr(file, name) 223 if not isinstance(a, int): 224 setattr(self, name, a) 225 return a 226 227 f = TestReadlineFile(tempfile.TemporaryFile()) 228 f.write('x' * 256 * 1024) 229 f.seek(0) 230 env = {'REQUEST_METHOD':'PUT'} 231 fs = cgi.FieldStorage(fp=f, environ=env) 232 # if we're not chunking properly, readline is only called twice 233 # (by read_binary); if we are chunking properly, it will be called 5 times 234 # as long as the chunksize is 1 << 16. 235 self.assertGreater(f.numcalls, 2) 236 237 def test_fieldstorage_invalid(self): 238 fs = cgi.FieldStorage() 239 self.assertFalse(fs) 240 self.assertRaises(TypeError, bool(fs)) 241 self.assertEqual(list(fs), list(fs.keys())) 242 fs.list.append(namedtuple('MockFieldStorage', 'name')('fieldvalue')) 243 self.assertTrue(fs) 244 245 def test_fieldstorage_multipart(self): 246 #Test basic FieldStorage multipart parsing 247 env = {'REQUEST_METHOD':'POST', 'CONTENT_TYPE':'multipart/form-data; boundary=---------------------------721837373350705526688164684', 'CONTENT_LENGTH':'558'} 248 postdata = """-----------------------------721837373350705526688164684 249Content-Disposition: form-data; name="id" 250 2511234 252-----------------------------721837373350705526688164684 253Content-Disposition: form-data; name="title" 254 255 256-----------------------------721837373350705526688164684 257Content-Disposition: form-data; name="file"; filename="test.txt" 258Content-Type: text/plain 259 260Testing 123. 261 262-----------------------------721837373350705526688164684 263Content-Disposition: form-data; name="submit" 264 265 Add\x20 266-----------------------------721837373350705526688164684-- 267""" 268 fs = cgi.FieldStorage(fp=StringIO(postdata), environ=env) 269 self.assertEqual(len(fs.list), 4) 270 expect = [{'name':'id', 'filename':None, 'value':'1234'}, 271 {'name':'title', 'filename':None, 'value':''}, 272 {'name':'file', 'filename':'test.txt','value':'Testing 123.\n'}, 273 {'name':'submit', 'filename':None, 'value':' Add '}] 274 for x in range(len(fs.list)): 275 for k, exp in expect[x].items(): 276 got = getattr(fs.list[x], k) 277 self.assertEqual(got, exp) 278 279 def test_fieldstorage_multipart_maxline(self): 280 # Issue #18167 281 maxline = 1 << 16 282 self.maxDiff = None 283 def check(content): 284 data = """ 285---123 286Content-Disposition: form-data; name="upload"; filename="fake.txt" 287Content-Type: text/plain 288 289%s 290---123-- 291""".replace('\n', '\r\n') % content 292 environ = { 293 'CONTENT_LENGTH': str(len(data)), 294 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', 295 'REQUEST_METHOD': 'POST', 296 } 297 self.assertEqual(gen_result(data, environ), {'upload': content}) 298 check('x' * (maxline - 1)) 299 check('x' * (maxline - 1) + '\r') 300 check('x' * (maxline - 1) + '\r' + 'y' * (maxline - 1)) 301 302 _qs_result = { 303 'key1': 'value1', 304 'key2': ['value2x', 'value2y'], 305 'key3': 'value3', 306 'key4': 'value4' 307 } 308 def testQSAndUrlEncode(self): 309 data = "key2=value2x&key3=value3&key4=value4" 310 environ = { 311 'CONTENT_LENGTH': str(len(data)), 312 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 313 'QUERY_STRING': 'key1=value1&key2=value2y', 314 'REQUEST_METHOD': 'POST', 315 } 316 v = gen_result(data, environ) 317 self.assertEqual(self._qs_result, v) 318 319 def testQSAndFormData(self): 320 data = """ 321---123 322Content-Disposition: form-data; name="key2" 323 324value2y 325---123 326Content-Disposition: form-data; name="key3" 327 328value3 329---123 330Content-Disposition: form-data; name="key4" 331 332value4 333---123-- 334""" 335 environ = { 336 'CONTENT_LENGTH': str(len(data)), 337 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', 338 'QUERY_STRING': 'key1=value1&key2=value2x', 339 'REQUEST_METHOD': 'POST', 340 } 341 v = gen_result(data, environ) 342 self.assertEqual(self._qs_result, v) 343 344 def testQSAndFormDataFile(self): 345 data = """ 346---123 347Content-Disposition: form-data; name="key2" 348 349value2y 350---123 351Content-Disposition: form-data; name="key3" 352 353value3 354---123 355Content-Disposition: form-data; name="key4" 356 357value4 358---123 359Content-Disposition: form-data; name="upload"; filename="fake.txt" 360Content-Type: text/plain 361 362this is the content of the fake file 363 364---123-- 365""" 366 environ = { 367 'CONTENT_LENGTH': str(len(data)), 368 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', 369 'QUERY_STRING': 'key1=value1&key2=value2x', 370 'REQUEST_METHOD': 'POST', 371 } 372 result = self._qs_result.copy() 373 result.update({ 374 'upload': 'this is the content of the fake file\n' 375 }) 376 v = gen_result(data, environ) 377 self.assertEqual(result, v) 378 379 def test_deprecated_parse_qs(self): 380 # this func is moved to urlparse, this is just a sanity check 381 with check_warnings(('cgi.parse_qs is deprecated, use urlparse.' 382 'parse_qs instead', PendingDeprecationWarning)): 383 self.assertEqual({'a': ['A1'], 'B': ['B3'], 'b': ['B2']}, 384 cgi.parse_qs('a=A1&b=B2&B=B3')) 385 386 def test_deprecated_parse_qsl(self): 387 # this func is moved to urlparse, this is just a sanity check 388 with check_warnings(('cgi.parse_qsl is deprecated, use urlparse.' 389 'parse_qsl instead', PendingDeprecationWarning)): 390 self.assertEqual([('a', 'A1'), ('b', 'B2'), ('B', 'B3')], 391 cgi.parse_qsl('a=A1&b=B2&B=B3')) 392 393 def test_parse_header(self): 394 self.assertEqual( 395 cgi.parse_header("text/plain"), 396 ("text/plain", {})) 397 self.assertEqual( 398 cgi.parse_header("text/vnd.just.made.this.up ; "), 399 ("text/vnd.just.made.this.up", {})) 400 self.assertEqual( 401 cgi.parse_header("text/plain;charset=us-ascii"), 402 ("text/plain", {"charset": "us-ascii"})) 403 self.assertEqual( 404 cgi.parse_header('text/plain ; charset="us-ascii"'), 405 ("text/plain", {"charset": "us-ascii"})) 406 self.assertEqual( 407 cgi.parse_header('text/plain ; charset="us-ascii"; another=opt'), 408 ("text/plain", {"charset": "us-ascii", "another": "opt"})) 409 self.assertEqual( 410 cgi.parse_header('attachment; filename="silly.txt"'), 411 ("attachment", {"filename": "silly.txt"})) 412 self.assertEqual( 413 cgi.parse_header('attachment; filename="strange;name"'), 414 ("attachment", {"filename": "strange;name"})) 415 self.assertEqual( 416 cgi.parse_header('attachment; filename="strange;name";size=123;'), 417 ("attachment", {"filename": "strange;name", "size": "123"})) 418 self.assertEqual( 419 cgi.parse_header('form-data; name="files"; filename="fo\\"o;bar"'), 420 ("form-data", {"name": "files", "filename": 'fo"o;bar'})) 421 422 423def test_main(): 424 run_unittest(CgiTests) 425 426if __name__ == '__main__': 427 test_main() 428