blob: 042e50713a3a0ff1e849cc0fc7e9f23a3936b3e7 [file] [log] [blame]
Georg Brandle1844332006-10-29 20:09:12 +00001from test.test_support import run_unittest
Jeremy Hyltond9827c42000-08-03 22:11:43 +00002import cgi
3import os
4import sys
Guido van Rossum9568b732006-08-10 17:41:07 +00005import tempfile
Georg Brandle1844332006-10-29 20:09:12 +00006import unittest
Guido van Rossum9568b732006-08-10 17:41:07 +00007from StringIO import StringIO
Jeremy Hyltond9827c42000-08-03 22:11:43 +00008
9class HackedSysModule:
10 # The regression test will have real values in sys.argv, which
Fred Drake004d5e62000-10-23 17:22:08 +000011 # will completely confuse the test of the cgi module
Jeremy Hyltond9827c42000-08-03 22:11:43 +000012 argv = []
13 stdin = sys.stdin
14
15cgi.sys = HackedSysModule()
16
17try:
18 from cStringIO import StringIO
19except ImportError:
20 from StringIO import StringIO
21
22class ComparableException:
23 def __init__(self, err):
24 self.err = err
25
26 def __str__(self):
27 return str(self.err)
28
29 def __cmp__(self, anExc):
30 if not isinstance(anExc, Exception):
31 return -1
32 x = cmp(self.err.__class__, anExc.__class__)
33 if x != 0:
34 return x
35 return cmp(self.err.args, anExc.args)
36
37 def __getattr__(self, attr):
Guido van Rossum846d6db2001-01-17 15:08:37 +000038 return getattr(self.err, attr)
Jeremy Hyltond9827c42000-08-03 22:11:43 +000039
40def do_test(buf, method):
41 env = {}
42 if method == "GET":
43 fp = None
44 env['REQUEST_METHOD'] = 'GET'
45 env['QUERY_STRING'] = buf
46 elif method == "POST":
47 fp = StringIO(buf)
48 env['REQUEST_METHOD'] = 'POST'
49 env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
50 env['CONTENT_LENGTH'] = str(len(buf))
51 else:
52 raise ValueError, "unknown method: %s" % method
53 try:
54 return cgi.parse(fp, env, strict_parsing=1)
55 except StandardError, err:
56 return ComparableException(err)
57
58# A list of test cases. Each test case is a a two-tuple that contains
59# a string with the query and a dictionary with the expected result.
Fred Drake004d5e62000-10-23 17:22:08 +000060
Neil Schemenauer66edb622004-07-19 15:38:11 +000061parse_qsl_test_cases = [
62 ("", []),
63 ("&", []),
64 ("&&", []),
65 ("=", [('', '')]),
66 ("=a", [('', 'a')]),
67 ("a", [('a', '')]),
68 ("a=", [('a', '')]),
69 ("a=", [('a', '')]),
70 ("&a=b", [('a', 'b')]),
71 ("a=a+b&b=b+c", [('a', 'a b'), ('b', 'b c')]),
72 ("a=1&a=2", [('a', '1'), ('a', '2')]),
73]
74
75parse_strict_test_cases = [
Jeremy Hyltond9827c42000-08-03 22:11:43 +000076 ("", ValueError("bad query field: ''")),
77 ("&", ValueError("bad query field: ''")),
78 ("&&", ValueError("bad query field: ''")),
Jeremy Hyltonafde7e22000-09-15 20:06:57 +000079 (";", ValueError("bad query field: ''")),
80 (";&;", ValueError("bad query field: ''")),
Jeremy Hyltond9827c42000-08-03 22:11:43 +000081 # Should the next few really be valid?
82 ("=", {}),
83 ("=&=", {}),
Jeremy Hyltonafde7e22000-09-15 20:06:57 +000084 ("=;=", {}),
Jeremy Hyltond9827c42000-08-03 22:11:43 +000085 # This rest seem to make sense
86 ("=a", {'': ['a']}),
87 ("&=a", ValueError("bad query field: ''")),
88 ("=a&", ValueError("bad query field: ''")),
89 ("=&a", ValueError("bad query field: 'a'")),
90 ("b=a", {'b': ['a']}),
91 ("b+=a", {'b ': ['a']}),
92 ("a=b=a", {'a': ['b=a']}),
93 ("a=+b=a", {'a': [' b=a']}),
94 ("&b=a", ValueError("bad query field: ''")),
95 ("b&=a", ValueError("bad query field: 'b'")),
96 ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}),
97 ("a=a+b&a=b+a", {'a': ['a b', 'b a']}),
98 ("x=1&y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
Jeremy Hyltonafde7e22000-09-15 20:06:57 +000099 ("x=1;y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
100 ("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
Jeremy Hyltond9827c42000-08-03 22:11:43 +0000101 ("Hbc5161168c542333633315dee1182227:key_store_seqid=400006&cuyer=r&view=bustomer&order_id=0bb2e248638833d48cb7fed300000f1b&expire=964546263&lobale=en-US&kid=130003.300038&ss=env",
102 {'Hbc5161168c542333633315dee1182227:key_store_seqid': ['400006'],
103 'cuyer': ['r'],
104 'expire': ['964546263'],
105 'kid': ['130003.300038'],
106 'lobale': ['en-US'],
107 'order_id': ['0bb2e248638833d48cb7fed300000f1b'],
108 'ss': ['env'],
109 'view': ['bustomer'],
110 }),
Fred Drake004d5e62000-10-23 17:22:08 +0000111
Jeremy Hyltond9827c42000-08-03 22:11:43 +0000112 ("group_id=5470&set=custom&_assigned_to=31392&_status=1&_category=100&SUBMIT=Browse",
113 {'SUBMIT': ['Browse'],
114 '_assigned_to': ['31392'],
115 '_category': ['100'],
116 '_status': ['1'],
117 'group_id': ['5470'],
118 'set': ['custom'],
119 })
120 ]
121
122def norm(list):
123 if type(list) == type([]):
124 list.sort()
125 return list
126
127def first_elts(list):
128 return map(lambda x:x[0], list)
129
130def first_second_elts(list):
131 return map(lambda p:(p[0], p[1][0]), list)
132
Facundo Batistaa6a4d502008-06-21 18:58:04 +0000133def gen_result(data, environ):
134 fake_stdin = StringIO(data)
135 fake_stdin.seek(0)
136 form = cgi.FieldStorage(fp=fake_stdin, environ=environ)
137
138 result = {}
139 for k, v in dict(form).items():
140 result[k] = type(v) is list and form.getlist(k) or v.value
141
142 return result
143
Georg Brandle1844332006-10-29 20:09:12 +0000144class CgiTests(unittest.TestCase):
Neil Schemenauer66edb622004-07-19 15:38:11 +0000145
Georg Brandle1844332006-10-29 20:09:12 +0000146 def test_qsl(self):
147 for orig, expect in parse_qsl_test_cases:
148 result = cgi.parse_qsl(orig, keep_blank_values=True)
149 self.assertEqual(result, expect, "Error parsing %s" % repr(orig))
Jeremy Hyltond9827c42000-08-03 22:11:43 +0000150
Georg Brandle1844332006-10-29 20:09:12 +0000151 def test_strict(self):
152 for orig, expect in parse_strict_test_cases:
153 # Test basic parsing
154 d = do_test(orig, "GET")
155 self.assertEqual(d, expect, "Error parsing %s" % repr(orig))
156 d = do_test(orig, "POST")
157 self.assertEqual(d, expect, "Error parsing %s" % repr(orig))
158
159 env = {'QUERY_STRING': orig}
160 fcd = cgi.FormContentDict(env)
161 sd = cgi.SvFormContentDict(env)
162 fs = cgi.FieldStorage(environ=env)
163 if type(expect) == type({}):
164 # test dict interface
165 self.assertEqual(len(expect), len(fcd))
166 self.assertEqual(norm(expect.keys()), norm(fcd.keys()))
167 self.assertEqual(norm(expect.values()), norm(fcd.values()))
168 self.assertEqual(norm(expect.items()), norm(fcd.items()))
169 self.assertEqual(fcd.get("nonexistent field", "default"), "default")
170 self.assertEqual(len(sd), len(fs))
171 self.assertEqual(norm(sd.keys()), norm(fs.keys()))
172 self.assertEqual(fs.getvalue("nonexistent field", "default"), "default")
173 # test individual fields
174 for key in expect.keys():
175 expect_val = expect[key]
176 self.assert_(fcd.has_key(key))
177 self.assertEqual(norm(fcd[key]), norm(expect[key]))
178 self.assertEqual(fcd.get(key, "default"), fcd[key])
179 self.assert_(fs.has_key(key))
180 if len(expect_val) > 1:
181 single_value = 0
182 else:
183 single_value = 1
184 try:
185 val = sd[key]
186 except IndexError:
187 self.failIf(single_value)
188 self.assertEqual(fs.getvalue(key), expect_val)
189 else:
190 self.assert_(single_value)
191 self.assertEqual(val, expect_val[0])
192 self.assertEqual(fs.getvalue(key), expect_val[0])
193 self.assertEqual(norm(sd.getlist(key)), norm(expect_val))
194 if single_value:
195 self.assertEqual(norm(sd.values()),
196 first_elts(norm(expect.values())))
197 self.assertEqual(norm(sd.items()),
198 first_second_elts(norm(expect.items())))
199
200 def test_weird_formcontentdict(self):
201 # Test the weird FormContentDict classes
202 env = {'QUERY_STRING': "x=1&y=2.0&z=2-3.%2b0&1=1abc"}
203 expect = {'x': 1, 'y': 2.0, 'z': '2-3.+0', '1': '1abc'}
204 d = cgi.InterpFormContentDict(env)
205 for k, v in expect.items():
206 self.assertEqual(d[k], v)
207 for k, v in d.items():
208 self.assertEqual(expect[k], v)
209 self.assertEqual(norm(expect.values()), norm(d.values()))
210
211 def test_log(self):
212 cgi.log("Testing")
213
214 cgi.logfp = StringIO()
215 cgi.initlog("%s", "Testing initlog 1")
216 cgi.log("%s", "Testing log 2")
217 self.assertEqual(cgi.logfp.getvalue(), "Testing initlog 1\nTesting log 2\n")
218 if os.path.exists("/dev/null"):
219 cgi.logfp = None
220 cgi.logfile = "/dev/null"
221 cgi.initlog("%s", "Testing log 3")
222 cgi.log("Testing log 4")
223
224 def test_fieldstorage_readline(self):
225 # FieldStorage uses readline, which has the capacity to read all
226 # contents of the input file into memory; we use readline's size argument
227 # to prevent that for files that do not contain any newlines in
228 # non-GET/HEAD requests
229 class TestReadlineFile:
230 def __init__(self, file):
231 self.file = file
232 self.numcalls = 0
233
234 def readline(self, size=None):
235 self.numcalls += 1
236 if size:
237 return self.file.readline(size)
Jeremy Hyltond9827c42000-08-03 22:11:43 +0000238 else:
Georg Brandle1844332006-10-29 20:09:12 +0000239 return self.file.readline()
Jeremy Hyltond9827c42000-08-03 22:11:43 +0000240
Georg Brandle1844332006-10-29 20:09:12 +0000241 def __getattr__(self, name):
242 file = self.__dict__['file']
243 a = getattr(file, name)
244 if not isinstance(a, int):
245 setattr(self, name, a)
246 return a
Jeremy Hyltond9827c42000-08-03 22:11:43 +0000247
Georg Brandle1844332006-10-29 20:09:12 +0000248 f = TestReadlineFile(tempfile.TemporaryFile())
249 f.write('x' * 256 * 1024)
250 f.seek(0)
251 env = {'REQUEST_METHOD':'PUT'}
252 fs = cgi.FieldStorage(fp=f, environ=env)
253 # if we're not chunking properly, readline is only called twice
254 # (by read_binary); if we are chunking properly, it will be called 5 times
255 # as long as the chunksize is 1 << 16.
256 self.assert_(f.numcalls > 2)
Jeremy Hyltond9827c42000-08-03 22:11:43 +0000257
Georg Brandle1844332006-10-29 20:09:12 +0000258 def test_fieldstorage_multipart(self):
259 #Test basic FieldStorage multipart parsing
260 env = {'REQUEST_METHOD':'POST', 'CONTENT_TYPE':'multipart/form-data; boundary=---------------------------721837373350705526688164684', 'CONTENT_LENGTH':'558'}
261 postdata = """-----------------------------721837373350705526688164684
Guido van Rossum9568b732006-08-10 17:41:07 +0000262Content-Disposition: form-data; name="id"
263
2641234
265-----------------------------721837373350705526688164684
266Content-Disposition: form-data; name="title"
267
268
269-----------------------------721837373350705526688164684
270Content-Disposition: form-data; name="file"; filename="test.txt"
271Content-Type: text/plain
272
273Testing 123.
274
275-----------------------------721837373350705526688164684
276Content-Disposition: form-data; name="submit"
277
Tim Petersb7ad1eb2006-08-10 23:22:13 +0000278 Add\x20
Guido van Rossum9568b732006-08-10 17:41:07 +0000279-----------------------------721837373350705526688164684--
280"""
Georg Brandle1844332006-10-29 20:09:12 +0000281 fs = cgi.FieldStorage(fp=StringIO(postdata), environ=env)
282 self.assertEquals(len(fs.list), 4)
283 expect = [{'name':'id', 'filename':None, 'value':'1234'},
284 {'name':'title', 'filename':None, 'value':''},
285 {'name':'file', 'filename':'test.txt','value':'Testing 123.\n'},
286 {'name':'submit', 'filename':None, 'value':' Add '}]
287 for x in range(len(fs.list)):
288 for k, exp in expect[x].items():
289 got = getattr(fs.list[x], k)
290 self.assertEquals(got, exp)
Guido van Rossum9568b732006-08-10 17:41:07 +0000291
Facundo Batistaa6a4d502008-06-21 18:58:04 +0000292 _qs_result = {
293 'key1': 'value1',
294 'key2': ['value2x', 'value2y'],
295 'key3': 'value3',
296 'key4': 'value4'
297 }
298 def testQSAndUrlEncode(self):
299 data = "key2=value2x&key3=value3&key4=value4"
300 environ = {
301 'CONTENT_LENGTH': str(len(data)),
302 'CONTENT_TYPE': 'application/x-www-form-urlencoded',
303 'QUERY_STRING': 'key1=value1&key2=value2y',
304 'REQUEST_METHOD': 'POST',
305 }
306 v = gen_result(data, environ)
307 self.assertEqual(self._qs_result, v)
308
309 def testQSAndFormData(self):
310 data = """
311---123
312Content-Disposition: form-data; name="key2"
313
314value2y
315---123
316Content-Disposition: form-data; name="key3"
317
318value3
319---123
320Content-Disposition: form-data; name="key4"
321
322value4
323---123--
324"""
325 environ = {
326 'CONTENT_LENGTH': str(len(data)),
327 'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
328 'QUERY_STRING': 'key1=value1&key2=value2x',
329 'REQUEST_METHOD': 'POST',
330 }
331 v = gen_result(data, environ)
332 self.assertEqual(self._qs_result, v)
333
334 def testQSAndFormDataFile(self):
335 data = """
336---123
337Content-Disposition: form-data; name="key2"
338
339value2y
340---123
341Content-Disposition: form-data; name="key3"
342
343value3
344---123
345Content-Disposition: form-data; name="key4"
346
347value4
348---123
349Content-Disposition: form-data; name="upload"; filename="fake.txt"
350Content-Type: text/plain
351
352this is the content of the fake file
353
354---123--
355"""
356 environ = {
357 'CONTENT_LENGTH': str(len(data)),
358 'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
359 'QUERY_STRING': 'key1=value1&key2=value2x',
360 'REQUEST_METHOD': 'POST',
361 }
362 result = self._qs_result.copy()
363 result.update({
364 'upload': 'this is the content of the fake file\n'
365 })
366 v = gen_result(data, environ)
367 self.assertEqual(result, v)
368
Georg Brandle1844332006-10-29 20:09:12 +0000369def test_main():
370 run_unittest(CgiTests)
371
372if __name__ == '__main__':
373 test_main()