Tests: parsing of "Transfer-Encoding: chunked" responses.

This commit is contained in:
Tiago Natel
2019-11-26 15:58:25 +00:00
parent 4eecf1cb6a
commit 01103c5005
6 changed files with 71 additions and 23 deletions

View File

@@ -141,7 +141,7 @@ class TestNodeApplication(TestApplicationNode):
self.load('write_buffer') self.load('write_buffer')
self.assertEqual( self.assertEqual(
self.get()['body'], '6\r\nbuffer\r\n0\r\n\r\n', 'write buffer' self.get()['body'], 'buffer', 'write buffer'
) )
def test_node_application_write_callback(self): def test_node_application_write_callback(self):
@@ -149,7 +149,7 @@ class TestNodeApplication(TestApplicationNode):
self.assertEqual( self.assertEqual(
self.get()['body'], self.get()['body'],
'5\r\nhello\r\n5\r\nworld\r\n0\r\n\r\n', 'helloworld',
'write callback order', 'write callback order',
) )
self.assertTrue( self.assertTrue(
@@ -173,7 +173,7 @@ class TestNodeApplication(TestApplicationNode):
self.assertEqual( self.assertEqual(
self.get()['body'], self.get()['body'],
'4\r\nbody\r\n4\r\ntrue\r\n0\r\n\r\n', 'bodytrue',
'write return', 'write return',
) )

View File

@@ -157,7 +157,7 @@ class TestPerlApplication(TestApplicationPerl):
def test_perl_application_body_empty(self): def test_perl_application_body_empty(self):
self.load('body_empty') self.load('body_empty')
self.assertEqual(self.get()['body'], '0\r\n\r\n', 'body empty') self.assertEqual(self.get()['body'], '', 'body empty')
def test_perl_application_body_array(self): def test_perl_application_body_array(self):
self.load('body_array') self.load('body_array')

View File

@@ -540,7 +540,7 @@ Connection: close
} }
) )
self.assertEqual(resp['status'], 200, 'status') self.assertEqual(resp['status'], 200, 'status')
self.assertEqual(resp['body'][-5:], '0\r\n\r\n', 'body') self.assertEqual(resp['body'], 'XXXXXXX', 'body')
# Exception before start_response(). # Exception before start_response().
@@ -607,12 +607,11 @@ Connection: close
'X-Skip': '2', 'X-Skip': '2',
'X-Chunked': '1', 'X-Chunked': '1',
'Connection': 'close', 'Connection': 'close',
} },
) raw_resp=True
if 'body' in resp:
self.assertNotEqual(
resp['body'][-5:], '0\r\n\r\n', 'incomplete body'
) )
if resp:
self.assertNotEqual(resp[-5:], '0\r\n\r\n', 'incomplete body')
self.assertEqual( self.assertEqual(
len(self.findall(r'Traceback')), 4, 'traceback count 4' len(self.findall(r'Traceback')), 4, 'traceback count 4'
) )
@@ -646,12 +645,11 @@ Connection: close
'X-Skip': '3', 'X-Skip': '3',
'X-Chunked': '1', 'X-Chunked': '1',
'Connection': 'close', 'Connection': 'close',
} },
) raw_resp=True
if 'body' in resp:
self.assertNotEqual(
resp['body'][-5:], '0\r\n\r\n', 'incomplete body 2'
) )
if resp:
self.assertNotEqual(resp[-5:], '0\r\n\r\n', 'incomplete body 2')
self.assertEqual( self.assertEqual(
len(self.findall(r'Traceback')), 6, 'traceback count 6' len(self.findall(r'Traceback')), 6, 'traceback count 6'
) )

View File

@@ -285,7 +285,7 @@ class TestRubyApplication(TestApplicationRuby):
def test_ruby_application_body_empty(self): def test_ruby_application_body_empty(self):
self.load('body_empty') self.load('body_empty')
self.assertEqual(self.get()['body'], '0\r\n\r\n', 'body empty') self.assertEqual(self.get()['body'], '', 'body empty')
def test_ruby_application_body_array(self): def test_ruby_application_body_array(self):
self.load('body_array') self.load('body_array')

View File

@@ -84,4 +84,4 @@ class TestFeatureIsolation(TestApplicationProto):
return data return data
def parsejson(self, data): def parsejson(self, data):
return json.loads(data.split('\n')[1]) return json.loads(data)

View File

@@ -1,5 +1,6 @@
import re import re
import time import time
import json
import socket import socket
import select import select
from unit.main import TestUnit from unit.main import TestUnit
@@ -86,23 +87,24 @@ class TestHTTP(TestUnit):
sock.sendall(req) sock.sendall(req)
encoding = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding']
if TestUnit.detailed: if TestUnit.detailed:
print('>>>') print('>>>')
try: try:
print(req.decode('utf-8', 'ignore')) print(req.decode(encoding, 'ignore'))
except UnicodeEncodeError: except UnicodeEncodeError:
print(req) print(req)
resp = '' resp = ''
if 'no_recv' not in kwargs: if 'no_recv' not in kwargs:
enc = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding']
read_timeout = ( read_timeout = (
30 if 'read_timeout' not in kwargs else kwargs['read_timeout'] 30 if 'read_timeout' not in kwargs else kwargs['read_timeout']
) )
resp = self.recvall( resp = self.recvall(
sock, read_timeout=read_timeout, buff_size=read_buffer_size sock, read_timeout=read_timeout, buff_size=read_buffer_size
).decode(enc) ).decode(encoding)
if TestUnit.detailed: if TestUnit.detailed:
print('<<<') print('<<<')
@@ -114,6 +116,12 @@ class TestHTTP(TestUnit):
if 'raw_resp' not in kwargs: if 'raw_resp' not in kwargs:
resp = self._resp_to_dict(resp) resp = self._resp_to_dict(resp)
headers = resp.get('headers')
if headers and headers.get('Transfer-Encoding') == 'chunked':
resp['body'] = self._parse_chunked_body(resp['body']).decode(
encoding
)
if 'start' not in kwargs: if 'start' not in kwargs:
sock.close() sock.close()
return resp return resp
@@ -151,7 +159,7 @@ class TestHTTP(TestUnit):
return data return data
def _resp_to_dict(self, resp): def _resp_to_dict(self, resp):
m = re.search('(.*?\x0d\x0a?)\x0d\x0a?(.*)', resp, re.M | re.S) m = re.search(r'(.*?\x0d\x0a?)\x0d\x0a?(.*)', resp, re.M | re.S)
if not m: if not m:
return {} return {}
@@ -162,12 +170,12 @@ class TestHTTP(TestUnit):
headers_lines = p.findall(headers_text) headers_lines = p.findall(headers_text)
status = re.search( status = re.search(
'^HTTP\/\d\.\d\s(\d+)|$', headers_lines.pop(0) r'^HTTP\/\d\.\d\s(\d+)|$', headers_lines.pop(0)
).group(1) ).group(1)
headers = {} headers = {}
for line in headers_lines: for line in headers_lines:
m = re.search('(.*)\:\s(.*)', line) m = re.search(r'(.*)\:\s(.*)', line)
if m.group(1) not in headers: if m.group(1) not in headers:
headers[m.group(1)] = m.group(2) headers[m.group(1)] = m.group(2)
@@ -180,6 +188,48 @@ class TestHTTP(TestUnit):
return {'status': int(status), 'headers': headers, 'body': body} return {'status': int(status), 'headers': headers, 'body': body}
def _parse_chunked_body(self, raw_body):
if isinstance(raw_body, str):
raw_body = bytes(raw_body.encode())
crlf = b'\r\n'
chunks = raw_body.split(crlf)
if len(chunks) < 3:
self.fail('Invalid chunked body')
if chunks.pop() != b'':
self.fail('No CRLF at the end of the body')
try:
last_size = int(chunks[-2], 16)
except:
self.fail('Invalid zero size chunk')
if last_size != 0 or chunks[-1] != b'':
self.fail('Incomplete body')
body = b''
while len(chunks) >= 2:
try:
size = int(chunks.pop(0), 16)
except:
self.fail('Invalid chunk size %s' % str(size))
if size == 0:
self.assertEqual(len(chunks), 1, 'last zero size')
break
temp_body = crlf.join(chunks)
body += temp_body[:size]
temp_body = temp_body[size + len(crlf) :]
chunks = temp_body.split(crlf)
return body
def waitforsocket(self, port): def waitforsocket(self, port):
ret = False ret = False