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