test_cgi.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. import os
  2. import sys
  3. import tempfile
  4. import unittest
  5. from collections import namedtuple
  6. from io import StringIO, BytesIO
  7. from test import support
  8. from test.support import warnings_helper
  9. cgi = warnings_helper.import_deprecated("cgi")
  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. cgi.sys = HackedSysModule()
  16. class ComparableException:
  17. def __init__(self, err):
  18. self.err = err
  19. def __str__(self):
  20. return str(self.err)
  21. def __eq__(self, anExc):
  22. if not isinstance(anExc, Exception):
  23. return NotImplemented
  24. return (self.err.__class__ == anExc.__class__ and
  25. self.err.args == anExc.args)
  26. def __getattr__(self, attr):
  27. return getattr(self.err, attr)
  28. def do_test(buf, method):
  29. env = {}
  30. if method == "GET":
  31. fp = None
  32. env['REQUEST_METHOD'] = 'GET'
  33. env['QUERY_STRING'] = buf
  34. elif method == "POST":
  35. fp = BytesIO(buf.encode('latin-1')) # FieldStorage expects bytes
  36. env['REQUEST_METHOD'] = 'POST'
  37. env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
  38. env['CONTENT_LENGTH'] = str(len(buf))
  39. else:
  40. raise ValueError("unknown method: %s" % method)
  41. try:
  42. return cgi.parse(fp, env, strict_parsing=1)
  43. except Exception as err:
  44. return ComparableException(err)
  45. parse_strict_test_cases = [
  46. ("", {}),
  47. ("&", ValueError("bad query field: ''")),
  48. ("&&", ValueError("bad query field: ''")),
  49. # Should the next few really be valid?
  50. ("=", {}),
  51. ("=&=", {}),
  52. # This rest seem to make sense
  53. ("=a", {'': ['a']}),
  54. ("&=a", ValueError("bad query field: ''")),
  55. ("=a&", ValueError("bad query field: ''")),
  56. ("=&a", ValueError("bad query field: 'a'")),
  57. ("b=a", {'b': ['a']}),
  58. ("b+=a", {'b ': ['a']}),
  59. ("a=b=a", {'a': ['b=a']}),
  60. ("a=+b=a", {'a': [' b=a']}),
  61. ("&b=a", ValueError("bad query field: ''")),
  62. ("b&=a", ValueError("bad query field: 'b'")),
  63. ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}),
  64. ("a=a+b&a=b+a", {'a': ['a b', 'b a']}),
  65. ("x=1&y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
  66. ("Hbc5161168c542333633315dee1182227:key_store_seqid=400006&cuyer=r&view=bustomer&order_id=0bb2e248638833d48cb7fed300000f1b&expire=964546263&lobale=en-US&kid=130003.300038&ss=env",
  67. {'Hbc5161168c542333633315dee1182227:key_store_seqid': ['400006'],
  68. 'cuyer': ['r'],
  69. 'expire': ['964546263'],
  70. 'kid': ['130003.300038'],
  71. 'lobale': ['en-US'],
  72. 'order_id': ['0bb2e248638833d48cb7fed300000f1b'],
  73. 'ss': ['env'],
  74. 'view': ['bustomer'],
  75. }),
  76. ("group_id=5470&set=custom&_assigned_to=31392&_status=1&_category=100&SUBMIT=Browse",
  77. {'SUBMIT': ['Browse'],
  78. '_assigned_to': ['31392'],
  79. '_category': ['100'],
  80. '_status': ['1'],
  81. 'group_id': ['5470'],
  82. 'set': ['custom'],
  83. })
  84. ]
  85. def norm(seq):
  86. return sorted(seq, key=repr)
  87. def first_elts(list):
  88. return [p[0] for p in list]
  89. def first_second_elts(list):
  90. return [(p[0], p[1][0]) for p in list]
  91. def gen_result(data, environ):
  92. encoding = 'latin-1'
  93. fake_stdin = BytesIO(data.encode(encoding))
  94. fake_stdin.seek(0)
  95. form = cgi.FieldStorage(fp=fake_stdin, environ=environ, encoding=encoding)
  96. result = {}
  97. for k, v in dict(form).items():
  98. result[k] = isinstance(v, list) and form.getlist(k) or v.value
  99. return result
  100. class CgiTests(unittest.TestCase):
  101. def test_parse_multipart(self):
  102. fp = BytesIO(POSTDATA.encode('latin1'))
  103. env = {'boundary': BOUNDARY.encode('latin1'),
  104. 'CONTENT-LENGTH': '558'}
  105. result = cgi.parse_multipart(fp, env)
  106. expected = {'submit': [' Add '], 'id': ['1234'],
  107. 'file': [b'Testing 123.\n'], 'title': ['']}
  108. self.assertEqual(result, expected)
  109. def test_parse_multipart_without_content_length(self):
  110. POSTDATA = '''--JfISa01
  111. Content-Disposition: form-data; name="submit-name"
  112. just a string
  113. --JfISa01--
  114. '''
  115. fp = BytesIO(POSTDATA.encode('latin1'))
  116. env = {'boundary': 'JfISa01'.encode('latin1')}
  117. result = cgi.parse_multipart(fp, env)
  118. expected = {'submit-name': ['just a string\n']}
  119. self.assertEqual(result, expected)
  120. def test_parse_multipart_invalid_encoding(self):
  121. BOUNDARY = "JfISa01"
  122. POSTDATA = """--JfISa01
  123. Content-Disposition: form-data; name="submit-name"
  124. Content-Length: 3
  125. \u2603
  126. --JfISa01"""
  127. fp = BytesIO(POSTDATA.encode('utf8'))
  128. env = {'boundary': BOUNDARY.encode('latin1'),
  129. 'CONTENT-LENGTH': str(len(POSTDATA.encode('utf8')))}
  130. result = cgi.parse_multipart(fp, env, encoding="ascii",
  131. errors="surrogateescape")
  132. expected = {'submit-name': ["\udce2\udc98\udc83"]}
  133. self.assertEqual(result, expected)
  134. self.assertEqual("\u2603".encode('utf8'),
  135. result["submit-name"][0].encode('utf8', 'surrogateescape'))
  136. def test_fieldstorage_properties(self):
  137. fs = cgi.FieldStorage()
  138. self.assertFalse(fs)
  139. self.assertIn("FieldStorage", repr(fs))
  140. self.assertEqual(list(fs), list(fs.keys()))
  141. fs.list.append(namedtuple('MockFieldStorage', 'name')('fieldvalue'))
  142. self.assertTrue(fs)
  143. def test_fieldstorage_invalid(self):
  144. self.assertRaises(TypeError, cgi.FieldStorage, "not-a-file-obj",
  145. environ={"REQUEST_METHOD":"PUT"})
  146. self.assertRaises(TypeError, cgi.FieldStorage, "foo", "bar")
  147. fs = cgi.FieldStorage(headers={'content-type':'text/plain'})
  148. self.assertRaises(TypeError, bool, fs)
  149. def test_strict(self):
  150. for orig, expect in parse_strict_test_cases:
  151. # Test basic parsing
  152. d = do_test(orig, "GET")
  153. self.assertEqual(d, expect, "Error parsing %s method GET" % repr(orig))
  154. d = do_test(orig, "POST")
  155. self.assertEqual(d, expect, "Error parsing %s method POST" % repr(orig))
  156. env = {'QUERY_STRING': orig}
  157. fs = cgi.FieldStorage(environ=env)
  158. if isinstance(expect, dict):
  159. # test dict interface
  160. self.assertEqual(len(expect), len(fs))
  161. self.assertCountEqual(expect.keys(), fs.keys())
  162. ##self.assertEqual(norm(expect.values()), norm(fs.values()))
  163. ##self.assertEqual(norm(expect.items()), norm(fs.items()))
  164. self.assertEqual(fs.getvalue("nonexistent field", "default"), "default")
  165. # test individual fields
  166. for key in expect.keys():
  167. expect_val = expect[key]
  168. self.assertIn(key, fs)
  169. if len(expect_val) > 1:
  170. self.assertEqual(fs.getvalue(key), expect_val)
  171. else:
  172. self.assertEqual(fs.getvalue(key), expect_val[0])
  173. def test_separator(self):
  174. parse_semicolon = [
  175. ("x=1;y=2.0", {'x': ['1'], 'y': ['2.0']}),
  176. ("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
  177. (";", ValueError("bad query field: ''")),
  178. (";;", ValueError("bad query field: ''")),
  179. ("=;a", ValueError("bad query field: 'a'")),
  180. (";b=a", ValueError("bad query field: ''")),
  181. ("b;=a", ValueError("bad query field: 'b'")),
  182. ("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
  183. ("a=a+b;a=b+a", {'a': ['a b', 'b a']}),
  184. ]
  185. for orig, expect in parse_semicolon:
  186. env = {'QUERY_STRING': orig}
  187. fs = cgi.FieldStorage(separator=';', environ=env)
  188. if isinstance(expect, dict):
  189. for key in expect.keys():
  190. expect_val = expect[key]
  191. self.assertIn(key, fs)
  192. if len(expect_val) > 1:
  193. self.assertEqual(fs.getvalue(key), expect_val)
  194. else:
  195. self.assertEqual(fs.getvalue(key), expect_val[0])
  196. @warnings_helper.ignore_warnings(category=DeprecationWarning)
  197. def test_log(self):
  198. cgi.log("Testing")
  199. cgi.logfp = StringIO()
  200. cgi.initlog("%s", "Testing initlog 1")
  201. cgi.log("%s", "Testing log 2")
  202. self.assertEqual(cgi.logfp.getvalue(), "Testing initlog 1\nTesting log 2\n")
  203. if os.path.exists(os.devnull):
  204. cgi.logfp = None
  205. cgi.logfile = os.devnull
  206. cgi.initlog("%s", "Testing log 3")
  207. self.addCleanup(cgi.closelog)
  208. cgi.log("Testing log 4")
  209. def test_fieldstorage_readline(self):
  210. # FieldStorage uses readline, which has the capacity to read all
  211. # contents of the input file into memory; we use readline's size argument
  212. # to prevent that for files that do not contain any newlines in
  213. # non-GET/HEAD requests
  214. class TestReadlineFile:
  215. def __init__(self, file):
  216. self.file = file
  217. self.numcalls = 0
  218. def readline(self, size=None):
  219. self.numcalls += 1
  220. if size:
  221. return self.file.readline(size)
  222. else:
  223. return self.file.readline()
  224. def __getattr__(self, name):
  225. file = self.__dict__['file']
  226. a = getattr(file, name)
  227. if not isinstance(a, int):
  228. setattr(self, name, a)
  229. return a
  230. f = TestReadlineFile(tempfile.TemporaryFile("wb+"))
  231. self.addCleanup(f.close)
  232. f.write(b'x' * 256 * 1024)
  233. f.seek(0)
  234. env = {'REQUEST_METHOD':'PUT'}
  235. fs = cgi.FieldStorage(fp=f, environ=env)
  236. self.addCleanup(fs.file.close)
  237. # if we're not chunking properly, readline is only called twice
  238. # (by read_binary); if we are chunking properly, it will be called 5 times
  239. # as long as the chunksize is 1 << 16.
  240. self.assertGreater(f.numcalls, 2)
  241. f.close()
  242. def test_fieldstorage_multipart(self):
  243. #Test basic FieldStorage multipart parsing
  244. env = {
  245. 'REQUEST_METHOD': 'POST',
  246. 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY),
  247. 'CONTENT_LENGTH': '558'}
  248. fp = BytesIO(POSTDATA.encode('latin-1'))
  249. fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1")
  250. self.assertEqual(len(fs.list), 4)
  251. expect = [{'name':'id', 'filename':None, 'value':'1234'},
  252. {'name':'title', 'filename':None, 'value':''},
  253. {'name':'file', 'filename':'test.txt', 'value':b'Testing 123.\n'},
  254. {'name':'submit', 'filename':None, 'value':' Add '}]
  255. for x in range(len(fs.list)):
  256. for k, exp in expect[x].items():
  257. got = getattr(fs.list[x], k)
  258. self.assertEqual(got, exp)
  259. def test_fieldstorage_multipart_leading_whitespace(self):
  260. env = {
  261. 'REQUEST_METHOD': 'POST',
  262. 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY),
  263. 'CONTENT_LENGTH': '560'}
  264. # Add some leading whitespace to our post data that will cause the
  265. # first line to not be the innerboundary.
  266. fp = BytesIO(b"\r\n" + POSTDATA.encode('latin-1'))
  267. fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1")
  268. self.assertEqual(len(fs.list), 4)
  269. expect = [{'name':'id', 'filename':None, 'value':'1234'},
  270. {'name':'title', 'filename':None, 'value':''},
  271. {'name':'file', 'filename':'test.txt', 'value':b'Testing 123.\n'},
  272. {'name':'submit', 'filename':None, 'value':' Add '}]
  273. for x in range(len(fs.list)):
  274. for k, exp in expect[x].items():
  275. got = getattr(fs.list[x], k)
  276. self.assertEqual(got, exp)
  277. def test_fieldstorage_multipart_non_ascii(self):
  278. #Test basic FieldStorage multipart parsing
  279. env = {'REQUEST_METHOD':'POST',
  280. 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY),
  281. 'CONTENT_LENGTH':'558'}
  282. for encoding in ['iso-8859-1','utf-8']:
  283. fp = BytesIO(POSTDATA_NON_ASCII.encode(encoding))
  284. fs = cgi.FieldStorage(fp, environ=env,encoding=encoding)
  285. self.assertEqual(len(fs.list), 1)
  286. expect = [{'name':'id', 'filename':None, 'value':'\xe7\xf1\x80'}]
  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.assertEqual(got, exp)
  291. def test_fieldstorage_multipart_maxline(self):
  292. # Issue #18167
  293. maxline = 1 << 16
  294. self.maxDiff = None
  295. def check(content):
  296. data = """---123
  297. Content-Disposition: form-data; name="upload"; filename="fake.txt"
  298. Content-Type: text/plain
  299. %s
  300. ---123--
  301. """.replace('\n', '\r\n') % content
  302. environ = {
  303. 'CONTENT_LENGTH': str(len(data)),
  304. 'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
  305. 'REQUEST_METHOD': 'POST',
  306. }
  307. self.assertEqual(gen_result(data, environ),
  308. {'upload': content.encode('latin1')})
  309. check('x' * (maxline - 1))
  310. check('x' * (maxline - 1) + '\r')
  311. check('x' * (maxline - 1) + '\r' + 'y' * (maxline - 1))
  312. def test_fieldstorage_multipart_w3c(self):
  313. # Test basic FieldStorage multipart parsing (W3C sample)
  314. env = {
  315. 'REQUEST_METHOD': 'POST',
  316. 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY_W3),
  317. 'CONTENT_LENGTH': str(len(POSTDATA_W3))}
  318. fp = BytesIO(POSTDATA_W3.encode('latin-1'))
  319. fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1")
  320. self.assertEqual(len(fs.list), 2)
  321. self.assertEqual(fs.list[0].name, 'submit-name')
  322. self.assertEqual(fs.list[0].value, 'Larry')
  323. self.assertEqual(fs.list[1].name, 'files')
  324. files = fs.list[1].value
  325. self.assertEqual(len(files), 2)
  326. expect = [{'name': None, 'filename': 'file1.txt', 'value': b'... contents of file1.txt ...'},
  327. {'name': None, 'filename': 'file2.gif', 'value': b'...contents of file2.gif...'}]
  328. for x in range(len(files)):
  329. for k, exp in expect[x].items():
  330. got = getattr(files[x], k)
  331. self.assertEqual(got, exp)
  332. def test_fieldstorage_part_content_length(self):
  333. BOUNDARY = "JfISa01"
  334. POSTDATA = """--JfISa01
  335. Content-Disposition: form-data; name="submit-name"
  336. Content-Length: 5
  337. Larry
  338. --JfISa01"""
  339. env = {
  340. 'REQUEST_METHOD': 'POST',
  341. 'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY),
  342. 'CONTENT_LENGTH': str(len(POSTDATA))}
  343. fp = BytesIO(POSTDATA.encode('latin-1'))
  344. fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1")
  345. self.assertEqual(len(fs.list), 1)
  346. self.assertEqual(fs.list[0].name, 'submit-name')
  347. self.assertEqual(fs.list[0].value, 'Larry')
  348. def test_field_storage_multipart_no_content_length(self):
  349. fp = BytesIO(b"""--MyBoundary
  350. Content-Disposition: form-data; name="my-arg"; filename="foo"
  351. Test
  352. --MyBoundary--
  353. """)
  354. env = {
  355. "REQUEST_METHOD": "POST",
  356. "CONTENT_TYPE": "multipart/form-data; boundary=MyBoundary",
  357. "wsgi.input": fp,
  358. }
  359. fields = cgi.FieldStorage(fp, environ=env)
  360. self.assertEqual(len(fields["my-arg"].file.read()), 5)
  361. def test_fieldstorage_as_context_manager(self):
  362. fp = BytesIO(b'x' * 10)
  363. env = {'REQUEST_METHOD': 'PUT'}
  364. with cgi.FieldStorage(fp=fp, environ=env) as fs:
  365. content = fs.file.read()
  366. self.assertFalse(fs.file.closed)
  367. self.assertTrue(fs.file.closed)
  368. self.assertEqual(content, 'x' * 10)
  369. with self.assertRaisesRegex(ValueError, 'I/O operation on closed file'):
  370. fs.file.read()
  371. _qs_result = {
  372. 'key1': 'value1',
  373. 'key2': ['value2x', 'value2y'],
  374. 'key3': 'value3',
  375. 'key4': 'value4'
  376. }
  377. def testQSAndUrlEncode(self):
  378. data = "key2=value2x&key3=value3&key4=value4"
  379. environ = {
  380. 'CONTENT_LENGTH': str(len(data)),
  381. 'CONTENT_TYPE': 'application/x-www-form-urlencoded',
  382. 'QUERY_STRING': 'key1=value1&key2=value2y',
  383. 'REQUEST_METHOD': 'POST',
  384. }
  385. v = gen_result(data, environ)
  386. self.assertEqual(self._qs_result, v)
  387. def test_max_num_fields(self):
  388. # For application/x-www-form-urlencoded
  389. data = '&'.join(['a=a']*11)
  390. environ = {
  391. 'CONTENT_LENGTH': str(len(data)),
  392. 'CONTENT_TYPE': 'application/x-www-form-urlencoded',
  393. 'REQUEST_METHOD': 'POST',
  394. }
  395. with self.assertRaises(ValueError):
  396. cgi.FieldStorage(
  397. fp=BytesIO(data.encode()),
  398. environ=environ,
  399. max_num_fields=10,
  400. )
  401. # For multipart/form-data
  402. data = """---123
  403. Content-Disposition: form-data; name="a"
  404. 3
  405. ---123
  406. Content-Type: application/x-www-form-urlencoded
  407. a=4
  408. ---123
  409. Content-Type: application/x-www-form-urlencoded
  410. a=5
  411. ---123--
  412. """
  413. environ = {
  414. 'CONTENT_LENGTH': str(len(data)),
  415. 'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
  416. 'QUERY_STRING': 'a=1&a=2',
  417. 'REQUEST_METHOD': 'POST',
  418. }
  419. # 2 GET entities
  420. # 1 top level POST entities
  421. # 1 entity within the second POST entity
  422. # 1 entity within the third POST entity
  423. with self.assertRaises(ValueError):
  424. cgi.FieldStorage(
  425. fp=BytesIO(data.encode()),
  426. environ=environ,
  427. max_num_fields=4,
  428. )
  429. cgi.FieldStorage(
  430. fp=BytesIO(data.encode()),
  431. environ=environ,
  432. max_num_fields=5,
  433. )
  434. def testQSAndFormData(self):
  435. data = """---123
  436. Content-Disposition: form-data; name="key2"
  437. value2y
  438. ---123
  439. Content-Disposition: form-data; name="key3"
  440. value3
  441. ---123
  442. Content-Disposition: form-data; name="key4"
  443. value4
  444. ---123--
  445. """
  446. environ = {
  447. 'CONTENT_LENGTH': str(len(data)),
  448. 'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
  449. 'QUERY_STRING': 'key1=value1&key2=value2x',
  450. 'REQUEST_METHOD': 'POST',
  451. }
  452. v = gen_result(data, environ)
  453. self.assertEqual(self._qs_result, v)
  454. def testQSAndFormDataFile(self):
  455. data = """---123
  456. Content-Disposition: form-data; name="key2"
  457. value2y
  458. ---123
  459. Content-Disposition: form-data; name="key3"
  460. value3
  461. ---123
  462. Content-Disposition: form-data; name="key4"
  463. value4
  464. ---123
  465. Content-Disposition: form-data; name="upload"; filename="fake.txt"
  466. Content-Type: text/plain
  467. this is the content of the fake file
  468. ---123--
  469. """
  470. environ = {
  471. 'CONTENT_LENGTH': str(len(data)),
  472. 'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
  473. 'QUERY_STRING': 'key1=value1&key2=value2x',
  474. 'REQUEST_METHOD': 'POST',
  475. }
  476. result = self._qs_result.copy()
  477. result.update({
  478. 'upload': b'this is the content of the fake file\n'
  479. })
  480. v = gen_result(data, environ)
  481. self.assertEqual(result, v)
  482. def test_parse_header(self):
  483. self.assertEqual(
  484. cgi.parse_header("text/plain"),
  485. ("text/plain", {}))
  486. self.assertEqual(
  487. cgi.parse_header("text/vnd.just.made.this.up ; "),
  488. ("text/vnd.just.made.this.up", {}))
  489. self.assertEqual(
  490. cgi.parse_header("text/plain;charset=us-ascii"),
  491. ("text/plain", {"charset": "us-ascii"}))
  492. self.assertEqual(
  493. cgi.parse_header('text/plain ; charset="us-ascii"'),
  494. ("text/plain", {"charset": "us-ascii"}))
  495. self.assertEqual(
  496. cgi.parse_header('text/plain ; charset="us-ascii"; another=opt'),
  497. ("text/plain", {"charset": "us-ascii", "another": "opt"}))
  498. self.assertEqual(
  499. cgi.parse_header('attachment; filename="silly.txt"'),
  500. ("attachment", {"filename": "silly.txt"}))
  501. self.assertEqual(
  502. cgi.parse_header('attachment; filename="strange;name"'),
  503. ("attachment", {"filename": "strange;name"}))
  504. self.assertEqual(
  505. cgi.parse_header('attachment; filename="strange;name";size=123;'),
  506. ("attachment", {"filename": "strange;name", "size": "123"}))
  507. self.assertEqual(
  508. cgi.parse_header('form-data; name="files"; filename="fo\\"o;bar"'),
  509. ("form-data", {"name": "files", "filename": 'fo"o;bar'}))
  510. def test_all(self):
  511. not_exported = {
  512. "logfile", "logfp", "initlog", "dolog", "nolog", "closelog", "log",
  513. "maxlen", "valid_boundary"}
  514. support.check__all__(self, cgi, not_exported=not_exported)
  515. BOUNDARY = "---------------------------721837373350705526688164684"
  516. POSTDATA = """-----------------------------721837373350705526688164684
  517. Content-Disposition: form-data; name="id"
  518. 1234
  519. -----------------------------721837373350705526688164684
  520. Content-Disposition: form-data; name="title"
  521. -----------------------------721837373350705526688164684
  522. Content-Disposition: form-data; name="file"; filename="test.txt"
  523. Content-Type: text/plain
  524. Testing 123.
  525. -----------------------------721837373350705526688164684
  526. Content-Disposition: form-data; name="submit"
  527. Add\x20
  528. -----------------------------721837373350705526688164684--
  529. """
  530. POSTDATA_NON_ASCII = """-----------------------------721837373350705526688164684
  531. Content-Disposition: form-data; name="id"
  532. \xe7\xf1\x80
  533. -----------------------------721837373350705526688164684
  534. """
  535. # http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
  536. BOUNDARY_W3 = "AaB03x"
  537. POSTDATA_W3 = """--AaB03x
  538. Content-Disposition: form-data; name="submit-name"
  539. Larry
  540. --AaB03x
  541. Content-Disposition: form-data; name="files"
  542. Content-Type: multipart/mixed; boundary=BbC04y
  543. --BbC04y
  544. Content-Disposition: file; filename="file1.txt"
  545. Content-Type: text/plain
  546. ... contents of file1.txt ...
  547. --BbC04y
  548. Content-Disposition: file; filename="file2.gif"
  549. Content-Type: image/gif
  550. Content-Transfer-Encoding: binary
  551. ...contents of file2.gif...
  552. --BbC04y--
  553. --AaB03x--
  554. """
  555. if __name__ == '__main__':
  556. unittest.main()