1 from test.test_support import run_unittest, check_warnings 2 import cgi 3 import os 4 import sys 5 import tempfile 6 import unittest 7 8 from collections import namedtuple 9 10 class 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 16 cgi.sys = HackedSysModule() 17 18 try: 19 from cStringIO import StringIO 20 except ImportError: 21 from StringIO import StringIO 22 23 class 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 41 def 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 59 parse_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 106 def first_elts(list): 107 return map(lambda x:x[0], list) 108 109 def first_second_elts(list): 110 return map(lambda p:(p[0], p[1][0]), list) 111 112 def 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 123 class 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 249 Content-Disposition: form-data; name="id" 250 251 1234 252 -----------------------------721837373350705526688164684 253 Content-Disposition: form-data; name="title" 254 255 256 -----------------------------721837373350705526688164684 257 Content-Disposition: form-data; name="file"; filename="test.txt" 258 Content-Type: text/plain 259 260 Testing 123. 261 262 -----------------------------721837373350705526688164684 263 Content-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 286 Content-Disposition: form-data; name="upload"; filename="fake.txt" 287 Content-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 322 Content-Disposition: form-data; name="key2" 323 324 value2y 325 ---123 326 Content-Disposition: form-data; name="key3" 327 328 value3 329 ---123 330 Content-Disposition: form-data; name="key4" 331 332 value4 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 347 Content-Disposition: form-data; name="key2" 348 349 value2y 350 ---123 351 Content-Disposition: form-data; name="key3" 352 353 value3 354 ---123 355 Content-Disposition: form-data; name="key4" 356 357 value4 358 ---123 359 Content-Disposition: form-data; name="upload"; filename="fake.txt" 360 Content-Type: text/plain 361 362 this 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 423 def test_main(): 424 run_unittest(CgiTests) 425 426 if __name__ == '__main__': 427 test_main() 428