Home | History | Annotate | Download | only in test
      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("&lt;test string&gt;", cgi.escape("<test string>"))
    128         self.assertEqual("&quot;test string&quot;", 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