1 from test
.support
import run_unittest
7 from io
import StringIO
8 from warnings
import catch_warnings
, filterwarnings
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
16 cgi
.sys
= HackedSysModule()
19 from io
import StringIO
21 from io
import StringIO
23 class ComparableException
:
24 def __init__(self
, err
):
30 def __eq__(self
, anExc
):
31 if not isinstance(anExc
, Exception):
33 return (self
.err
.__class
__ == anExc
.__class
__ and
34 self
.err
.args
== anExc
.args
)
36 def __getattr__(self
, attr
):
37 return getattr(self
.err
, attr
)
39 def do_test(buf
, method
):
43 env
['REQUEST_METHOD'] = 'GET'
44 env
['QUERY_STRING'] = buf
45 elif method
== "POST":
47 env
['REQUEST_METHOD'] = 'POST'
48 env
['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
49 env
['CONTENT_LENGTH'] = str(len(buf
))
51 raise ValueError("unknown method: %s" % method
)
53 return cgi
.parse(fp
, env
, strict_parsing
=1)
54 except Exception as err
:
55 return ComparableException(err
)
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?
67 # This rest seem to make sense
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'],
86 'expire': ['964546263'],
87 'kid': ['130003.300038'],
89 'order_id': ['0bb2e248638833d48cb7fed300000f1b'],
94 ("group_id=5470&set=custom&_assigned_to=31392&_status=1&_category=100&SUBMIT=Browse",
95 {'SUBMIT': ['Browse'],
96 '_assigned_to': ['31392'],
105 return sorted(seq
, key
=repr)
107 def first_elts(list):
108 return [p
[0] for p
in list]
110 def first_second_elts(list):
111 return [(p
[0], p
[1][0]) for p
in list]
113 def gen_result(data
, environ
):
114 fake_stdin
= StringIO(data
)
116 form
= cgi
.FieldStorage(fp
=fake_stdin
, environ
=environ
)
119 for k
, v
in dict(form
).items():
120 result
[k
] = type(v
) is list and form
.getlist(k
) or v
.value
124 class CgiTests(unittest
.TestCase
):
126 def test_strict(self
):
127 for orig
, expect
in parse_strict_test_cases
:
129 d
= do_test(orig
, "GET")
130 self
.assertEqual(d
, expect
, "Error parsing %s" % repr(orig
))
131 d
= do_test(orig
, "POST")
132 self
.assertEqual(d
, expect
, "Error parsing %s" % repr(orig
))
134 env
= {'QUERY_STRING': orig
}
135 fs
= cgi
.FieldStorage(environ
=env
)
136 if type(expect
) == type({}):
137 # test dict interface
138 self
.assertEqual(len(expect
), len(fs
))
139 self
.assertEqual(norm(expect
.keys()), norm(fs
.keys()))
140 ##self.assertEqual(norm(expect.values()), norm(fs.values()))
141 ##self.assertEqual(norm(expect.items()), norm(fs.items()))
142 self
.assertEqual(fs
.getvalue("nonexistent field", "default"), "default")
143 # test individual fields
144 for key
in expect
.keys():
145 expect_val
= expect
[key
]
146 self
.assert_(key
in fs
)
147 if len(expect_val
) > 1:
148 self
.assertEqual(fs
.getvalue(key
), expect_val
)
150 self
.assertEqual(fs
.getvalue(key
), expect_val
[0])
155 cgi
.logfp
= StringIO()
156 cgi
.initlog("%s", "Testing initlog 1")
157 cgi
.log("%s", "Testing log 2")
158 self
.assertEqual(cgi
.logfp
.getvalue(), "Testing initlog 1\nTesting log 2\n")
159 if os
.path
.exists("/dev/null"):
161 cgi
.logfile
= "/dev/null"
162 cgi
.initlog("%s", "Testing log 3")
163 cgi
.log("Testing log 4")
165 def test_fieldstorage_readline(self
):
166 # FieldStorage uses readline, which has the capacity to read all
167 # contents of the input file into memory; we use readline's size argument
168 # to prevent that for files that do not contain any newlines in
169 # non-GET/HEAD requests
170 class TestReadlineFile
:
171 def __init__(self
, file):
175 def readline(self
, size
=None):
178 return self
.file.readline(size
)
180 return self
.file.readline()
182 def __getattr__(self
, name
):
183 file = self
.__dict
__['file']
184 a
= getattr(file, name
)
185 if not isinstance(a
, int):
186 setattr(self
, name
, a
)
189 f
= TestReadlineFile(tempfile
.TemporaryFile("w+"))
190 f
.write('x' * 256 * 1024)
192 env
= {'REQUEST_METHOD':'PUT'}
193 fs
= cgi
.FieldStorage(fp
=f
, environ
=env
)
194 # if we're not chunking properly, readline is only called twice
195 # (by read_binary); if we are chunking properly, it will be called 5 times
196 # as long as the chunksize is 1 << 16.
197 self
.assert_(f
.numcalls
> 2)
199 def test_fieldstorage_multipart(self
):
200 #Test basic FieldStorage multipart parsing
201 env
= {'REQUEST_METHOD':'POST', 'CONTENT_TYPE':'multipart/form-data; boundary=---------------------------721837373350705526688164684', 'CONTENT_LENGTH':'558'}
202 postdata
= """-----------------------------721837373350705526688164684
203 Content-Disposition: form-data; name="id"
206 -----------------------------721837373350705526688164684
207 Content-Disposition: form-data; name="title"
210 -----------------------------721837373350705526688164684
211 Content-Disposition: form-data; name="file"; filename="test.txt"
212 Content-Type: text/plain
216 -----------------------------721837373350705526688164684
217 Content-Disposition: form-data; name="submit"
220 -----------------------------721837373350705526688164684--
222 fs
= cgi
.FieldStorage(fp
=StringIO(postdata
), environ
=env
)
223 self
.assertEquals(len(fs
.list), 4)
224 expect
= [{'name':'id', 'filename':None, 'value':'1234'},
225 {'name':'title', 'filename':None, 'value':''},
226 {'name':'file', 'filename':'test.txt', 'value':'Testing 123.'},
227 {'name':'submit', 'filename':None, 'value':' Add '}]
228 for x
in range(len(fs
.list)):
229 for k
, exp
in expect
[x
].items():
230 got
= getattr(fs
.list[x
], k
)
231 self
.assertEquals(got
, exp
)
235 'key2': ['value2x', 'value2y'],
239 def testQSAndUrlEncode(self
):
240 data
= "key2=value2x&key3=value3&key4=value4"
242 'CONTENT_LENGTH': str(len(data
)),
243 'CONTENT_TYPE': 'application/x-www-form-urlencoded',
244 'QUERY_STRING': 'key1=value1&key2=value2y',
245 'REQUEST_METHOD': 'POST',
247 v
= gen_result(data
, environ
)
248 self
.assertEqual(self
._qs
_result
, v
)
250 def testQSAndFormData(self
):
253 Content-Disposition: form-data; name="key2"
257 Content-Disposition: form-data; name="key3"
261 Content-Disposition: form-data; name="key4"
267 'CONTENT_LENGTH': str(len(data
)),
268 'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
269 'QUERY_STRING': 'key1=value1&key2=value2x',
270 'REQUEST_METHOD': 'POST',
272 v
= gen_result(data
, environ
)
273 self
.assertEqual(self
._qs
_result
, v
)
275 def testQSAndFormDataFile(self
):
278 Content-Disposition: form-data; name="key2"
282 Content-Disposition: form-data; name="key3"
286 Content-Disposition: form-data; name="key4"
290 Content-Disposition: form-data; name="upload"; filename="fake.txt"
291 Content-Type: text/plain
293 this is the content of the fake file
298 'CONTENT_LENGTH': str(len(data
)),
299 'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
300 'QUERY_STRING': 'key1=value1&key2=value2x',
301 'REQUEST_METHOD': 'POST',
303 result
= self
._qs
_result
.copy()
305 'upload': 'this is the content of the fake file'
307 v
= gen_result(data
, environ
)
308 self
.assertEqual(result
, v
)
310 def test_deprecated_parse_qs(self
):
311 # this func is moved to urlparse, this is just a sanity check
312 with
catch_warnings():
313 filterwarnings('ignore',
314 'cgi.parse_qs is deprecated, use urllib.parse.parse_qs instead',
316 self
.assertEqual({'a': ['A1'], 'B': ['B3'], 'b': ['B2']},
317 cgi
.parse_qs('a=A1&b=B2&B=B3'))
319 def test_deprecated_parse_qsl(self
):
320 # this func is moved to urlparse, this is just a sanity check
321 with
catch_warnings():
322 filterwarnings('ignore',
323 'cgi.parse_qsl is deprecated, use urllib.parse.parse_qsl instead',
325 self
.assertEqual([('a', 'A1'), ('b', 'B2'), ('B', 'B3')],
326 cgi
.parse_qsl('a=A1&b=B2&B=B3'))
328 def test_parse_header(self
):
330 cgi
.parse_header("text/plain"),
333 cgi
.parse_header("text/vnd.just.made.this.up ; "),
334 ("text/vnd.just.made.this.up", {}))
336 cgi
.parse_header("text/plain;charset=us-ascii"),
337 ("text/plain", {"charset": "us-ascii"}))
339 cgi
.parse_header('text/plain ; charset="us-ascii"'),
340 ("text/plain", {"charset": "us-ascii"}))
342 cgi
.parse_header('text/plain ; charset="us-ascii"; another=opt'),
343 ("text/plain", {"charset": "us-ascii", "another": "opt"}))
345 cgi
.parse_header('attachment; filename="silly.txt"'),
346 ("attachment", {"filename": "silly.txt"}))
348 cgi
.parse_header('attachment; filename="strange;name"'),
349 ("attachment", {"filename": "strange;name"}))
351 cgi
.parse_header('attachment; filename="strange;name";size=123;'),
352 ("attachment", {"filename": "strange;name", "size": "123"}))
356 run_unittest(CgiTests
)
358 if __name__
== '__main__':