Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

1307 рядки
38 KiB

  1. # -*- coding: utf-8 -*-
  2. import os
  3. import sys
  4. import glob
  5. import yaml
  6. import base64
  7. import random
  8. import tempfile
  9. from .compat import (
  10. parametrize,
  11. parametrize_class,
  12. slow_test,
  13. unittest,
  14. )
  15. from io import BytesIO
  16. from six import binary_type, text_type
  17. from mock import MagicMock, Mock, patch
  18. from ..multipart import *
  19. # Get the current directory for our later test cases.
  20. curr_dir = os.path.abspath(os.path.dirname(__file__))
  21. def force_bytes(val):
  22. if isinstance(val, text_type):
  23. val = val.encode(sys.getfilesystemencoding())
  24. return val
  25. class TestField(unittest.TestCase):
  26. def setUp(self):
  27. self.f = Field('foo')
  28. def test_name(self):
  29. self.assertEqual(self.f.field_name, 'foo')
  30. def test_data(self):
  31. self.f.write(b'test123')
  32. self.assertEqual(self.f.value, b'test123')
  33. def test_cache_expiration(self):
  34. self.f.write(b'test')
  35. self.assertEqual(self.f.value, b'test')
  36. self.f.write(b'123')
  37. self.assertEqual(self.f.value, b'test123')
  38. def test_finalize(self):
  39. self.f.write(b'test123')
  40. self.f.finalize()
  41. self.assertEqual(self.f.value, b'test123')
  42. def test_close(self):
  43. self.f.write(b'test123')
  44. self.f.close()
  45. self.assertEqual(self.f.value, b'test123')
  46. def test_from_value(self):
  47. f = Field.from_value(b'name', b'value')
  48. self.assertEqual(f.field_name, b'name')
  49. self.assertEqual(f.value, b'value')
  50. f2 = Field.from_value(b'name', None)
  51. self.assertEqual(f2.value, None)
  52. def test_equality(self):
  53. f1 = Field.from_value(b'name', b'value')
  54. f2 = Field.from_value(b'name', b'value')
  55. self.assertEqual(f1, f2)
  56. def test_equality_with_other(self):
  57. f = Field.from_value(b'foo', b'bar')
  58. self.assertFalse(f == b'foo')
  59. self.assertFalse(b'foo' == f)
  60. def test_set_none(self):
  61. f = Field(b'foo')
  62. self.assertEqual(f.value, b'')
  63. f.set_none()
  64. self.assertEqual(f.value, None)
  65. class TestFile(unittest.TestCase):
  66. def setUp(self):
  67. self.c = {}
  68. self.d = force_bytes(tempfile.mkdtemp())
  69. self.f = File(b'foo.txt', config=self.c)
  70. def assert_data(self, data):
  71. f = self.f.file_object
  72. f.seek(0)
  73. self.assertEqual(f.read(), data)
  74. f.seek(0)
  75. f.truncate()
  76. def assert_exists(self):
  77. full_path = os.path.join(self.d, self.f.actual_file_name)
  78. self.assertTrue(os.path.exists(full_path))
  79. def test_simple(self):
  80. self.f.write(b'foobar')
  81. self.assert_data(b'foobar')
  82. def test_invalid_write(self):
  83. m = Mock()
  84. m.write.return_value = 5
  85. self.f._fileobj = m
  86. v = self.f.write(b'foobar')
  87. self.assertEqual(v, 5)
  88. def test_file_fallback(self):
  89. self.c['MAX_MEMORY_FILE_SIZE'] = 1
  90. self.f.write(b'1')
  91. self.assertTrue(self.f.in_memory)
  92. self.assert_data(b'1')
  93. self.f.write(b'123')
  94. self.assertFalse(self.f.in_memory)
  95. self.assert_data(b'123')
  96. # Test flushing too.
  97. old_obj = self.f.file_object
  98. self.f.flush_to_disk()
  99. self.assertFalse(self.f.in_memory)
  100. self.assertIs(self.f.file_object, old_obj)
  101. def test_file_fallback_with_data(self):
  102. self.c['MAX_MEMORY_FILE_SIZE'] = 10
  103. self.f.write(b'1' * 10)
  104. self.assertTrue(self.f.in_memory)
  105. self.f.write(b'2' * 10)
  106. self.assertFalse(self.f.in_memory)
  107. self.assert_data(b'11111111112222222222')
  108. def test_file_name(self):
  109. # Write to this dir.
  110. self.c['UPLOAD_DIR'] = self.d
  111. self.c['MAX_MEMORY_FILE_SIZE'] = 10
  112. # Write.
  113. self.f.write(b'12345678901')
  114. self.assertFalse(self.f.in_memory)
  115. # Assert that the file exists
  116. self.assertIsNotNone(self.f.actual_file_name)
  117. self.assert_exists()
  118. def test_file_full_name(self):
  119. # Write to this dir.
  120. self.c['UPLOAD_DIR'] = self.d
  121. self.c['UPLOAD_KEEP_FILENAME'] = True
  122. self.c['MAX_MEMORY_FILE_SIZE'] = 10
  123. # Write.
  124. self.f.write(b'12345678901')
  125. self.assertFalse(self.f.in_memory)
  126. # Assert that the file exists
  127. self.assertEqual(self.f.actual_file_name, b'foo')
  128. self.assert_exists()
  129. def test_file_full_name_with_ext(self):
  130. self.c['UPLOAD_DIR'] = self.d
  131. self.c['UPLOAD_KEEP_FILENAME'] = True
  132. self.c['UPLOAD_KEEP_EXTENSIONS'] = True
  133. self.c['MAX_MEMORY_FILE_SIZE'] = 10
  134. # Write.
  135. self.f.write(b'12345678901')
  136. self.assertFalse(self.f.in_memory)
  137. # Assert that the file exists
  138. self.assertEqual(self.f.actual_file_name, b'foo.txt')
  139. self.assert_exists()
  140. def test_file_full_name_with_ext(self):
  141. self.c['UPLOAD_DIR'] = self.d
  142. self.c['UPLOAD_KEEP_FILENAME'] = True
  143. self.c['UPLOAD_KEEP_EXTENSIONS'] = True
  144. self.c['MAX_MEMORY_FILE_SIZE'] = 10
  145. # Write.
  146. self.f.write(b'12345678901')
  147. self.assertFalse(self.f.in_memory)
  148. # Assert that the file exists
  149. self.assertEqual(self.f.actual_file_name, b'foo.txt')
  150. self.assert_exists()
  151. def test_no_dir_with_extension(self):
  152. self.c['UPLOAD_KEEP_EXTENSIONS'] = True
  153. self.c['MAX_MEMORY_FILE_SIZE'] = 10
  154. # Write.
  155. self.f.write(b'12345678901')
  156. self.assertFalse(self.f.in_memory)
  157. # Assert that the file exists
  158. ext = os.path.splitext(self.f.actual_file_name)[1]
  159. self.assertEqual(ext, b'.txt')
  160. self.assert_exists()
  161. def test_invalid_dir_with_name(self):
  162. # Write to this dir.
  163. self.c['UPLOAD_DIR'] = force_bytes(os.path.join('/', 'tmp', 'notexisting'))
  164. self.c['UPLOAD_KEEP_FILENAME'] = True
  165. self.c['MAX_MEMORY_FILE_SIZE'] = 5
  166. # Write.
  167. with self.assertRaises(FileError):
  168. self.f.write(b'1234567890')
  169. def test_invalid_dir_no_name(self):
  170. # Write to this dir.
  171. self.c['UPLOAD_DIR'] = force_bytes(os.path.join('/', 'tmp', 'notexisting'))
  172. self.c['UPLOAD_KEEP_FILENAME'] = False
  173. self.c['MAX_MEMORY_FILE_SIZE'] = 5
  174. # Write.
  175. with self.assertRaises(FileError):
  176. self.f.write(b'1234567890')
  177. # TODO: test uploading two files with the same name.
  178. class TestParseOptionsHeader(unittest.TestCase):
  179. def test_simple(self):
  180. t, p = parse_options_header('application/json')
  181. self.assertEqual(t, b'application/json')
  182. self.assertEqual(p, {})
  183. def test_blank(self):
  184. t, p = parse_options_header('')
  185. self.assertEqual(t, b'')
  186. self.assertEqual(p, {})
  187. def test_single_param(self):
  188. t, p = parse_options_header('application/json;par=val')
  189. self.assertEqual(t, b'application/json')
  190. self.assertEqual(p, {b'par': b'val'})
  191. def test_single_param_with_spaces(self):
  192. t, p = parse_options_header(b'application/json; par=val')
  193. self.assertEqual(t, b'application/json')
  194. self.assertEqual(p, {b'par': b'val'})
  195. def test_multiple_params(self):
  196. t, p = parse_options_header(b'application/json;par=val;asdf=foo')
  197. self.assertEqual(t, b'application/json')
  198. self.assertEqual(p, {b'par': b'val', b'asdf': b'foo'})
  199. def test_quoted_param(self):
  200. t, p = parse_options_header(b'application/json;param="quoted"')
  201. self.assertEqual(t, b'application/json')
  202. self.assertEqual(p, {b'param': b'quoted'})
  203. def test_quoted_param_with_semicolon(self):
  204. t, p = parse_options_header(b'application/json;param="quoted;with;semicolons"')
  205. self.assertEqual(p[b'param'], b'quoted;with;semicolons')
  206. def test_quoted_param_with_escapes(self):
  207. t, p = parse_options_header(b'application/json;param="This \\" is \\" a \\" quote"')
  208. self.assertEqual(p[b'param'], b'This " is " a " quote')
  209. def test_handles_ie6_bug(self):
  210. t, p = parse_options_header(b'text/plain; filename="C:\\this\\is\\a\\path\\file.txt"')
  211. self.assertEqual(p[b'filename'], b'file.txt')
  212. class TestBaseParser(unittest.TestCase):
  213. def setUp(self):
  214. self.b = BaseParser()
  215. self.b.callbacks = {}
  216. def test_callbacks(self):
  217. # The stupid list-ness is to get around lack of nonlocal on py2
  218. l = [0]
  219. def on_foo():
  220. l[0] += 1
  221. self.b.set_callback('foo', on_foo)
  222. self.b.callback('foo')
  223. self.assertEqual(l[0], 1)
  224. self.b.set_callback('foo', None)
  225. self.b.callback('foo')
  226. self.assertEqual(l[0], 1)
  227. class TestQuerystringParser(unittest.TestCase):
  228. def assert_fields(self, *args, **kwargs):
  229. if kwargs.pop('finalize', True):
  230. self.p.finalize()
  231. self.assertEqual(self.f, list(args))
  232. if kwargs.get('reset', True):
  233. self.f = []
  234. def setUp(self):
  235. self.reset()
  236. def reset(self):
  237. self.f = []
  238. name_buffer = []
  239. data_buffer = []
  240. def on_field_name(data, start, end):
  241. name_buffer.append(data[start:end])
  242. def on_field_data(data, start, end):
  243. data_buffer.append(data[start:end])
  244. def on_field_end():
  245. self.f.append((
  246. b''.join(name_buffer),
  247. b''.join(data_buffer)
  248. ))
  249. del name_buffer[:]
  250. del data_buffer[:]
  251. callbacks = {
  252. 'on_field_name': on_field_name,
  253. 'on_field_data': on_field_data,
  254. 'on_field_end': on_field_end
  255. }
  256. self.p = QuerystringParser(callbacks)
  257. def test_simple_querystring(self):
  258. self.p.write(b'foo=bar')
  259. self.assert_fields((b'foo', b'bar'))
  260. def test_querystring_blank_beginning(self):
  261. self.p.write(b'&foo=bar')
  262. self.assert_fields((b'foo', b'bar'))
  263. def test_querystring_blank_end(self):
  264. self.p.write(b'foo=bar&')
  265. self.assert_fields((b'foo', b'bar'))
  266. def test_multiple_querystring(self):
  267. self.p.write(b'foo=bar&asdf=baz')
  268. self.assert_fields(
  269. (b'foo', b'bar'),
  270. (b'asdf', b'baz')
  271. )
  272. def test_streaming_simple(self):
  273. self.p.write(b'foo=bar&')
  274. self.assert_fields(
  275. (b'foo', b'bar'),
  276. finalize=False
  277. )
  278. self.p.write(b'asdf=baz')
  279. self.assert_fields(
  280. (b'asdf', b'baz')
  281. )
  282. def test_streaming_break(self):
  283. self.p.write(b'foo=one')
  284. self.assert_fields(finalize=False)
  285. self.p.write(b'two')
  286. self.assert_fields(finalize=False)
  287. self.p.write(b'three')
  288. self.assert_fields(finalize=False)
  289. self.p.write(b'&asd')
  290. self.assert_fields(
  291. (b'foo', b'onetwothree'),
  292. finalize=False
  293. )
  294. self.p.write(b'f=baz')
  295. self.assert_fields(
  296. (b'asdf', b'baz')
  297. )
  298. def test_semicolon_seperator(self):
  299. self.p.write(b'foo=bar;asdf=baz')
  300. self.assert_fields(
  301. (b'foo', b'bar'),
  302. (b'asdf', b'baz')
  303. )
  304. def test_too_large_field(self):
  305. self.p.max_size = 15
  306. # Note: len = 8
  307. self.p.write(b"foo=bar&")
  308. self.assert_fields((b'foo', b'bar'), finalize=False)
  309. # Note: len = 8, only 7 bytes processed
  310. self.p.write(b'a=123456')
  311. self.assert_fields((b'a', b'12345'))
  312. def test_invalid_max_size(self):
  313. with self.assertRaises(ValueError):
  314. p = QuerystringParser(max_size=-100)
  315. def test_strict_parsing_pass(self):
  316. data = b'foo=bar&another=asdf'
  317. for first, last in split_all(data):
  318. self.reset()
  319. self.p.strict_parsing = True
  320. print("%r / %r" % (first, last))
  321. self.p.write(first)
  322. self.p.write(last)
  323. self.assert_fields((b'foo', b'bar'), (b'another', b'asdf'))
  324. def test_strict_parsing_fail_double_sep(self):
  325. data = b'foo=bar&&another=asdf'
  326. for first, last in split_all(data):
  327. self.reset()
  328. self.p.strict_parsing = True
  329. cnt = 0
  330. with self.assertRaises(QuerystringParseError) as cm:
  331. cnt += self.p.write(first)
  332. cnt += self.p.write(last)
  333. self.p.finalize()
  334. # The offset should occur at 8 bytes into the data (as a whole),
  335. # so we calculate the offset into the chunk.
  336. if cm is not None:
  337. self.assertEqual(cm.exception.offset, 8 - cnt)
  338. def test_double_sep(self):
  339. data = b'foo=bar&&another=asdf'
  340. for first, last in split_all(data):
  341. print(" %r / %r " % (first, last))
  342. self.reset()
  343. cnt = 0
  344. cnt += self.p.write(first)
  345. cnt += self.p.write(last)
  346. self.assert_fields((b'foo', b'bar'), (b'another', b'asdf'))
  347. def test_strict_parsing_fail_no_value(self):
  348. self.p.strict_parsing = True
  349. with self.assertRaises(QuerystringParseError) as cm:
  350. self.p.write(b'foo=bar&blank&another=asdf')
  351. if cm is not None:
  352. self.assertEqual(cm.exception.offset, 8)
  353. def test_success_no_value(self):
  354. self.p.write(b'foo=bar&blank&another=asdf')
  355. self.assert_fields(
  356. (b'foo', b'bar'),
  357. (b'blank', b''),
  358. (b'another', b'asdf')
  359. )
  360. class TestOctetStreamParser(unittest.TestCase):
  361. def setUp(self):
  362. self.d = []
  363. self.started = 0
  364. self.finished = 0
  365. def on_start():
  366. self.started += 1
  367. def on_data(data, start, end):
  368. self.d.append(data[start:end])
  369. def on_end():
  370. self.finished += 1
  371. callbacks = {
  372. 'on_start': on_start,
  373. 'on_data': on_data,
  374. 'on_end': on_end
  375. }
  376. self.p = OctetStreamParser(callbacks)
  377. def assert_data(self, data, finalize=True):
  378. self.assertEqual(b''.join(self.d), data)
  379. self.d = []
  380. def assert_started(self, val=True):
  381. if val:
  382. self.assertEqual(self.started, 1)
  383. else:
  384. self.assertEqual(self.started, 0)
  385. def assert_finished(self, val=True):
  386. if val:
  387. self.assertEqual(self.finished, 1)
  388. else:
  389. self.assertEqual(self.finished, 0)
  390. def test_simple(self):
  391. # Assert is not started
  392. self.assert_started(False)
  393. # Write something, it should then be started + have data
  394. self.p.write(b'foobar')
  395. self.assert_started()
  396. self.assert_data(b'foobar')
  397. # Finalize, and check
  398. self.assert_finished(False)
  399. self.p.finalize()
  400. self.assert_finished()
  401. def test_multiple_chunks(self):
  402. self.p.write(b'foo')
  403. self.p.write(b'bar')
  404. self.p.write(b'baz')
  405. self.p.finalize()
  406. self.assert_data(b'foobarbaz')
  407. self.assert_finished()
  408. def test_max_size(self):
  409. self.p.max_size = 5
  410. self.p.write(b'0123456789')
  411. self.p.finalize()
  412. self.assert_data(b'01234')
  413. self.assert_finished()
  414. def test_invalid_max_size(self):
  415. with self.assertRaises(ValueError):
  416. q = OctetStreamParser(max_size='foo')
  417. class TestBase64Decoder(unittest.TestCase):
  418. # Note: base64('foobar') == 'Zm9vYmFy'
  419. def setUp(self):
  420. self.f = BytesIO()
  421. self.d = Base64Decoder(self.f)
  422. def assert_data(self, data, finalize=True):
  423. if finalize:
  424. self.d.finalize()
  425. self.f.seek(0)
  426. self.assertEqual(self.f.read(), data)
  427. self.f.seek(0)
  428. self.f.truncate()
  429. def test_simple(self):
  430. self.d.write(b'Zm9vYmFy')
  431. self.assert_data(b'foobar')
  432. def test_bad(self):
  433. with self.assertRaises(DecodeError):
  434. self.d.write(b'Zm9v!mFy')
  435. def test_split_properly(self):
  436. self.d.write(b'Zm9v')
  437. self.d.write(b'YmFy')
  438. self.assert_data(b'foobar')
  439. def test_bad_split(self):
  440. buff = b'Zm9v'
  441. for i in range(1, 4):
  442. first, second = buff[:i], buff[i:]
  443. self.setUp()
  444. self.d.write(first)
  445. self.d.write(second)
  446. self.assert_data(b'foo')
  447. def test_long_bad_split(self):
  448. buff = b'Zm9vYmFy'
  449. for i in range(5, 8):
  450. first, second = buff[:i], buff[i:]
  451. self.setUp()
  452. self.d.write(first)
  453. self.d.write(second)
  454. self.assert_data(b'foobar')
  455. def test_close_and_finalize(self):
  456. parser = Mock()
  457. f = Base64Decoder(parser)
  458. f.finalize()
  459. parser.finalize.assert_called_once_with()
  460. f.close()
  461. parser.close.assert_called_once_with()
  462. def test_bad_length(self):
  463. self.d.write(b'Zm9vYmF') # missing ending 'y'
  464. with self.assertRaises(DecodeError):
  465. self.d.finalize()
  466. class TestQuotedPrintableDecoder(unittest.TestCase):
  467. def setUp(self):
  468. self.f = BytesIO()
  469. self.d = QuotedPrintableDecoder(self.f)
  470. def assert_data(self, data, finalize=True):
  471. if finalize:
  472. self.d.finalize()
  473. self.f.seek(0)
  474. self.assertEqual(self.f.read(), data)
  475. self.f.seek(0)
  476. self.f.truncate()
  477. def test_simple(self):
  478. self.d.write(b'foobar')
  479. self.assert_data(b'foobar')
  480. def test_with_escape(self):
  481. self.d.write(b'foo=3Dbar')
  482. self.assert_data(b'foo=bar')
  483. def test_with_newline_escape(self):
  484. self.d.write(b'foo=\r\nbar')
  485. self.assert_data(b'foobar')
  486. def test_with_only_newline_escape(self):
  487. self.d.write(b'foo=\nbar')
  488. self.assert_data(b'foobar')
  489. def test_with_split_escape(self):
  490. self.d.write(b'foo=3')
  491. self.d.write(b'Dbar')
  492. self.assert_data(b'foo=bar')
  493. def test_with_split_newline_escape_1(self):
  494. self.d.write(b'foo=\r')
  495. self.d.write(b'\nbar')
  496. self.assert_data(b'foobar')
  497. def test_with_split_newline_escape_2(self):
  498. self.d.write(b'foo=')
  499. self.d.write(b'\r\nbar')
  500. self.assert_data(b'foobar')
  501. def test_close_and_finalize(self):
  502. parser = Mock()
  503. f = QuotedPrintableDecoder(parser)
  504. f.finalize()
  505. parser.finalize.assert_called_once_with()
  506. f.close()
  507. parser.close.assert_called_once_with()
  508. def test_not_aligned(self):
  509. """
  510. https://github.com/andrew-d/python-multipart/issues/6
  511. """
  512. self.d.write(b'=3AX')
  513. self.assert_data(b':X')
  514. # Additional offset tests
  515. self.d.write(b'=3')
  516. self.d.write(b'AX')
  517. self.assert_data(b':X')
  518. self.d.write(b'q=3AX')
  519. self.assert_data(b'q:X')
  520. # Load our list of HTTP test cases.
  521. http_tests_dir = os.path.join(curr_dir, 'test_data', 'http')
  522. # Read in all test cases and load them.
  523. NON_PARAMETRIZED_TESTS = set(['single_field_blocks'])
  524. http_tests = []
  525. for f in os.listdir(http_tests_dir):
  526. # Only load the HTTP test cases.
  527. fname, ext = os.path.splitext(f)
  528. if fname in NON_PARAMETRIZED_TESTS:
  529. continue
  530. if ext == '.http':
  531. # Get the YAML file and load it too.
  532. yaml_file = os.path.join(http_tests_dir, fname + '.yaml')
  533. # Load both.
  534. with open(os.path.join(http_tests_dir, f), 'rb') as f:
  535. test_data = f.read()
  536. with open(yaml_file, 'rb') as f:
  537. yaml_data = yaml.load(f)
  538. http_tests.append({
  539. 'name': fname,
  540. 'test': test_data,
  541. 'result': yaml_data
  542. })
  543. def split_all(val):
  544. """
  545. This function will split an array all possible ways. For example:
  546. split_all([1,2,3,4])
  547. will give:
  548. ([1], [2,3,4]), ([1,2], [3,4]), ([1,2,3], [4])
  549. """
  550. for i in range(1, len(val) - 1):
  551. yield (val[:i], val[i:])
  552. @parametrize_class
  553. class TestFormParser(unittest.TestCase):
  554. def make(self, boundary, config={}):
  555. self.ended = False
  556. self.files = []
  557. self.fields = []
  558. def on_field(f):
  559. self.fields.append(f)
  560. def on_file(f):
  561. self.files.append(f)
  562. def on_end():
  563. self.ended = True
  564. # Get a form-parser instance.
  565. self.f = FormParser('multipart/form-data', on_field, on_file, on_end,
  566. boundary=boundary, config=config)
  567. def assert_file_data(self, f, data):
  568. o = f.file_object
  569. o.seek(0)
  570. file_data = o.read()
  571. self.assertEqual(file_data, data)
  572. def assert_file(self, field_name, file_name, data):
  573. # Find this file.
  574. found = None
  575. for f in self.files:
  576. if f.field_name == field_name:
  577. found = f
  578. break
  579. # Assert that we found it.
  580. self.assertIsNotNone(found)
  581. try:
  582. # Assert about this file.
  583. self.assert_file_data(found, data)
  584. self.assertEqual(found.file_name, file_name)
  585. # Remove it from our list.
  586. self.files.remove(found)
  587. finally:
  588. # Close our file
  589. found.close()
  590. def assert_field(self, name, value):
  591. # Find this field in our fields list.
  592. found = None
  593. for f in self.fields:
  594. if f.field_name == name:
  595. found = f
  596. break
  597. # Assert that it exists and matches.
  598. self.assertIsNotNone(found)
  599. self.assertEqual(value, found.value)
  600. # Remove it for future iterations.
  601. self.fields.remove(found)
  602. @parametrize('param', http_tests)
  603. def test_http(self, param):
  604. # Firstly, create our parser with the given boundary.
  605. boundary = param['result']['boundary']
  606. if isinstance(boundary, text_type):
  607. boundary = boundary.encode('latin-1')
  608. self.make(boundary)
  609. # Now, we feed the parser with data.
  610. exc = None
  611. try:
  612. processed = self.f.write(param['test'])
  613. self.f.finalize()
  614. except MultipartParseError as e:
  615. processed = 0
  616. exc = e
  617. # print(repr(param))
  618. # print("")
  619. # print(repr(self.fields))
  620. # print(repr(self.files))
  621. # Do we expect an error?
  622. if 'error' in param['result']['expected']:
  623. self.assertIsNotNone(exc)
  624. self.assertEqual(param['result']['expected']['error'], exc.offset)
  625. return
  626. # No error!
  627. self.assertEqual(processed, len(param['test']))
  628. # Assert that the parser gave us the appropriate fields/files.
  629. for e in param['result']['expected']:
  630. # Get our type and name.
  631. type = e['type']
  632. name = e['name'].encode('latin-1')
  633. if type == 'field':
  634. self.assert_field(name, e['data'])
  635. elif type == 'file':
  636. self.assert_file(
  637. name,
  638. e['file_name'].encode('latin-1'),
  639. e['data']
  640. )
  641. else:
  642. assert False
  643. def test_random_splitting(self):
  644. """
  645. This test runs a simple multipart body with one field and one file
  646. through every possible split.
  647. """
  648. # Load test data.
  649. test_file = 'single_field_single_file.http'
  650. with open(os.path.join(http_tests_dir, test_file), 'rb') as f:
  651. test_data = f.read()
  652. # We split the file through all cases.
  653. for first, last in split_all(test_data):
  654. # Create form parser.
  655. self.make('boundary')
  656. # Feed with data in 2 chunks.
  657. i = 0
  658. i += self.f.write(first)
  659. i += self.f.write(last)
  660. self.f.finalize()
  661. # Assert we processed everything.
  662. self.assertEqual(i, len(test_data))
  663. # Assert that our file and field are here.
  664. self.assert_field(b'field', b'test1')
  665. self.assert_file(b'file', b'file.txt', b'test2')
  666. def test_feed_single_bytes(self):
  667. """
  668. This test parses a simple multipart body 1 byte at a time.
  669. """
  670. # Load test data.
  671. test_file = 'single_field_single_file.http'
  672. with open(os.path.join(http_tests_dir, test_file), 'rb') as f:
  673. test_data = f.read()
  674. # Create form parser.
  675. self.make('boundary')
  676. # Write all bytes.
  677. # NOTE: Can't simply do `for b in test_data`, since that gives
  678. # an integer when iterating over a bytes object on Python 3.
  679. i = 0
  680. for x in range(len(test_data)):
  681. b = test_data[x:x + 1]
  682. i += self.f.write(b)
  683. self.f.finalize()
  684. # Assert we processed everything.
  685. self.assertEqual(i, len(test_data))
  686. # Assert that our file and field are here.
  687. self.assert_field(b'field', b'test1')
  688. self.assert_file(b'file', b'file.txt', b'test2')
  689. def test_feed_blocks(self):
  690. """
  691. This test parses a simple multipart body 1 byte at a time.
  692. """
  693. # Load test data.
  694. test_file = 'single_field_blocks.http'
  695. with open(os.path.join(http_tests_dir, test_file), 'rb') as f:
  696. test_data = f.read()
  697. for c in range(1, len(test_data) + 1):
  698. # Skip first `d` bytes - not interesting
  699. for d in range(c):
  700. # Create form parser.
  701. self.make('boundary')
  702. # Skip
  703. i = 0
  704. self.f.write(test_data[:d])
  705. i += d
  706. for x in range(d, len(test_data), c):
  707. # Write a chunk to achieve condition
  708. # `i == data_length - 1`
  709. # in boundary search loop (multipatr.py:1302)
  710. b = test_data[x:x + c]
  711. i += self.f.write(b)
  712. self.f.finalize()
  713. # Assert we processed everything.
  714. self.assertEqual(i, len(test_data))
  715. # Assert that our field is here.
  716. self.assert_field(b'field',
  717. b'0123456789ABCDEFGHIJ0123456789ABCDEFGHIJ')
  718. @slow_test
  719. def test_request_body_fuzz(self):
  720. """
  721. This test randomly fuzzes the request body to ensure that no strange
  722. exceptions are raised and we don't end up in a strange state. The
  723. fuzzing consists of randomly doing one of the following:
  724. - Adding a random byte at a random offset
  725. - Randomly deleting a single byte
  726. - Randomly swapping two bytes
  727. """
  728. # Load test data.
  729. test_file = 'single_field_single_file.http'
  730. with open(os.path.join(http_tests_dir, test_file), 'rb') as f:
  731. test_data = f.read()
  732. iterations = 1000
  733. successes = 0
  734. failures = 0
  735. exceptions = 0
  736. print("Running %d iterations of fuzz testing:" % (iterations,))
  737. for i in range(iterations):
  738. # Create a bytearray to mutate.
  739. fuzz_data = bytearray(test_data)
  740. # Pick what we're supposed to do.
  741. choice = random.choice([1, 2, 3])
  742. if choice == 1:
  743. # Add a random byte.
  744. i = random.randrange(len(test_data))
  745. b = random.randrange(256)
  746. fuzz_data.insert(i, b)
  747. msg = "Inserting byte %r at offset %d" % (b, i)
  748. elif choice == 2:
  749. # Remove a random byte.
  750. i = random.randrange(len(test_data))
  751. del fuzz_data[i]
  752. msg = "Deleting byte at offset %d" % (i,)
  753. elif choice == 3:
  754. # Swap two bytes.
  755. i = random.randrange(len(test_data) - 1)
  756. fuzz_data[i], fuzz_data[i + 1] = fuzz_data[i + 1], fuzz_data[i]
  757. msg = "Swapping bytes %d and %d" % (i, i + 1)
  758. # Print message, so if this crashes, we can inspect the output.
  759. print(" " + msg)
  760. # Create form parser.
  761. self.make('boundary')
  762. # Feed with data, and ignore form parser exceptions.
  763. i = 0
  764. try:
  765. i = self.f.write(bytes(fuzz_data))
  766. self.f.finalize()
  767. except FormParserError:
  768. exceptions += 1
  769. else:
  770. if i == len(fuzz_data):
  771. successes += 1
  772. else:
  773. failures += 1
  774. print("--------------------------------------------------")
  775. print("Successes: %d" % (successes,))
  776. print("Failures: %d" % (failures,))
  777. print("Exceptions: %d" % (exceptions,))
  778. @slow_test
  779. def test_request_body_fuzz_random_data(self):
  780. """
  781. This test will fuzz the multipart parser with some number of iterations
  782. of randomly-generated data.
  783. """
  784. iterations = 1000
  785. successes = 0
  786. failures = 0
  787. exceptions = 0
  788. print("Running %d iterations of fuzz testing:" % (iterations,))
  789. for i in range(iterations):
  790. data_size = random.randrange(100, 4096)
  791. data = os.urandom(data_size)
  792. print(" Testing with %d random bytes..." % (data_size,))
  793. # Create form parser.
  794. self.make('boundary')
  795. # Feed with data, and ignore form parser exceptions.
  796. i = 0
  797. try:
  798. i = self.f.write(bytes(data))
  799. self.f.finalize()
  800. except FormParserError:
  801. exceptions += 1
  802. else:
  803. if i == len(data):
  804. successes += 1
  805. else:
  806. failures += 1
  807. print("--------------------------------------------------")
  808. print("Successes: %d" % (successes,))
  809. print("Failures: %d" % (failures,))
  810. print("Exceptions: %d" % (exceptions,))
  811. def test_bad_start_boundary(self):
  812. self.make('boundary')
  813. data = b'--boundary\rfoobar'
  814. with self.assertRaises(MultipartParseError):
  815. self.f.write(data)
  816. self.make('boundary')
  817. data = b'--boundaryfoobar'
  818. with self.assertRaises(MultipartParseError):
  819. i = self.f.write(data)
  820. def test_octet_stream(self):
  821. files = []
  822. def on_file(f):
  823. files.append(f)
  824. on_field = Mock()
  825. on_end = Mock()
  826. f = FormParser('application/octet-stream', on_field, on_file, on_end=on_end, file_name=b'foo.txt')
  827. self.assertTrue(isinstance(f.parser, OctetStreamParser))
  828. f.write(b'test')
  829. f.write(b'1234')
  830. f.finalize()
  831. # Assert that we only recieved a single file, with the right data, and that we're done.
  832. self.assertFalse(on_field.called)
  833. self.assertEqual(len(files), 1)
  834. self.assert_file_data(files[0], b'test1234')
  835. self.assertTrue(on_end.called)
  836. def test_querystring(self):
  837. fields = []
  838. def on_field(f):
  839. fields.append(f)
  840. on_file = Mock()
  841. on_end = Mock()
  842. def simple_test(f):
  843. # Reset tracking.
  844. del fields[:]
  845. on_file.reset_mock()
  846. on_end.reset_mock()
  847. # Write test data.
  848. f.write(b'foo=bar')
  849. f.write(b'&test=asdf')
  850. f.finalize()
  851. # Assert we only recieved 2 fields...
  852. self.assertFalse(on_file.called)
  853. self.assertEqual(len(fields), 2)
  854. # ...assert that we have the correct data...
  855. self.assertEqual(fields[0].field_name, b'foo')
  856. self.assertEqual(fields[0].value, b'bar')
  857. self.assertEqual(fields[1].field_name, b'test')
  858. self.assertEqual(fields[1].value, b'asdf')
  859. # ... and assert that we've finished.
  860. self.assertTrue(on_end.called)
  861. f = FormParser('application/x-www-form-urlencoded', on_field, on_file, on_end=on_end)
  862. self.assertTrue(isinstance(f.parser, QuerystringParser))
  863. simple_test(f)
  864. f = FormParser('application/x-url-encoded', on_field, on_file, on_end=on_end)
  865. self.assertTrue(isinstance(f.parser, QuerystringParser))
  866. simple_test(f)
  867. def test_close_methods(self):
  868. parser = Mock()
  869. f = FormParser('application/x-url-encoded', None, None)
  870. f.parser = parser
  871. f.finalize()
  872. parser.finalize.assert_called_once_with()
  873. f.close()
  874. parser.close.assert_called_once_with()
  875. def test_bad_content_type(self):
  876. # We should raise a ValueError for a bad Content-Type
  877. with self.assertRaises(ValueError):
  878. f = FormParser('application/bad', None, None)
  879. def test_no_boundary_given(self):
  880. # We should raise a FormParserError when parsing a multipart message
  881. # without a boundary.
  882. with self.assertRaises(FormParserError):
  883. f = FormParser('multipart/form-data', None, None)
  884. def test_bad_content_transfer_encoding(self):
  885. data = b'----boundary\r\nContent-Disposition: form-data; name="file"; filename="test.txt"\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: badstuff\r\n\r\nTest\r\n----boundary--\r\n'
  886. files = []
  887. def on_file(f):
  888. files.append(f)
  889. on_field = Mock()
  890. on_end = Mock()
  891. # Test with erroring.
  892. config = {'UPLOAD_ERROR_ON_BAD_CTE': True}
  893. f = FormParser('multipart/form-data', on_field, on_file,
  894. on_end=on_end, boundary='--boundary', config=config)
  895. with self.assertRaises(FormParserError):
  896. f.write(data)
  897. f.finalize()
  898. # Test without erroring.
  899. config = {'UPLOAD_ERROR_ON_BAD_CTE': False}
  900. f = FormParser('multipart/form-data', on_field, on_file,
  901. on_end=on_end, boundary='--boundary', config=config)
  902. f.write(data)
  903. f.finalize()
  904. self.assert_file_data(files[0], b'Test')
  905. def test_handles_None_fields(self):
  906. fields = []
  907. def on_field(f):
  908. fields.append(f)
  909. on_file = Mock()
  910. on_end = Mock()
  911. f = FormParser('application/x-www-form-urlencoded', on_field, on_file, on_end=on_end)
  912. f.write(b'foo=bar&another&baz=asdf')
  913. f.finalize()
  914. self.assertEqual(fields[0].field_name, b'foo')
  915. self.assertEqual(fields[0].value, b'bar')
  916. self.assertEqual(fields[1].field_name, b'another')
  917. self.assertEqual(fields[1].value, None)
  918. self.assertEqual(fields[2].field_name, b'baz')
  919. self.assertEqual(fields[2].value, b'asdf')
  920. def test_max_size_multipart(self):
  921. # Load test data.
  922. test_file = 'single_field_single_file.http'
  923. with open(os.path.join(http_tests_dir, test_file), 'rb') as f:
  924. test_data = f.read()
  925. # Create form parser.
  926. self.make('boundary')
  927. # Set the maximum length that we can process to be halfway through the
  928. # given data.
  929. self.f.parser.max_size = len(test_data) / 2
  930. i = self.f.write(test_data)
  931. self.f.finalize()
  932. # Assert we processed the correct amount.
  933. self.assertEqual(i, len(test_data) / 2)
  934. def test_max_size_form_parser(self):
  935. # Load test data.
  936. test_file = 'single_field_single_file.http'
  937. with open(os.path.join(http_tests_dir, test_file), 'rb') as f:
  938. test_data = f.read()
  939. # Create form parser setting the maximum length that we can process to
  940. # be halfway through the given data.
  941. size = len(test_data) / 2
  942. self.make('boundary', config={'MAX_BODY_SIZE': size})
  943. i = self.f.write(test_data)
  944. self.f.finalize()
  945. # Assert we processed the correct amount.
  946. self.assertEqual(i, len(test_data) / 2)
  947. def test_octet_stream_max_size(self):
  948. files = []
  949. def on_file(f):
  950. files.append(f)
  951. on_field = Mock()
  952. on_end = Mock()
  953. f = FormParser('application/octet-stream', on_field, on_file,
  954. on_end=on_end, file_name=b'foo.txt',
  955. config={'MAX_BODY_SIZE': 10})
  956. f.write(b'0123456789012345689')
  957. f.finalize()
  958. self.assert_file_data(files[0], b'0123456789')
  959. def test_invalid_max_size_multipart(self):
  960. with self.assertRaises(ValueError):
  961. q = MultipartParser(b'bound', max_size='foo')
  962. class TestHelperFunctions(unittest.TestCase):
  963. def test_create_form_parser(self):
  964. r = create_form_parser({'Content-Type': 'application/octet-stream'},
  965. None, None)
  966. self.assertTrue(isinstance(r, FormParser))
  967. def test_create_form_parser_error(self):
  968. headers = {}
  969. with self.assertRaises(ValueError):
  970. create_form_parser(headers, None, None)
  971. def test_parse_form(self):
  972. on_field = Mock()
  973. on_file = Mock()
  974. parse_form(
  975. {'Content-Type': 'application/octet-stream',
  976. },
  977. BytesIO(b'123456789012345'),
  978. on_field,
  979. on_file
  980. )
  981. on_file.assert_called_once()
  982. # Assert that the first argument of the call (a File object) has size
  983. # 15 - i.e. all data is written.
  984. self.assertEqual(on_file.call_args[0][0].size, 15)
  985. def test_parse_form_content_length(self):
  986. files = []
  987. def on_file(file):
  988. files.append(file)
  989. parse_form(
  990. {'Content-Type': 'application/octet-stream',
  991. 'Content-Length': '10'
  992. },
  993. BytesIO(b'123456789012345'),
  994. None,
  995. on_file
  996. )
  997. self.assertEqual(len(files), 1)
  998. self.assertEqual(files[0].size, 10)
  999. def suite():
  1000. suite = unittest.TestSuite()
  1001. suite.addTest(unittest.makeSuite(TestFile))
  1002. suite.addTest(unittest.makeSuite(TestParseOptionsHeader))
  1003. suite.addTest(unittest.makeSuite(TestBaseParser))
  1004. suite.addTest(unittest.makeSuite(TestQuerystringParser))
  1005. suite.addTest(unittest.makeSuite(TestOctetStreamParser))
  1006. suite.addTest(unittest.makeSuite(TestBase64Decoder))
  1007. suite.addTest(unittest.makeSuite(TestQuotedPrintableDecoder))
  1008. suite.addTest(unittest.makeSuite(TestFormParser))
  1009. suite.addTest(unittest.makeSuite(TestHelperFunctions))
  1010. return suite