diff options
Diffstat (limited to 'test')
32 files changed, 2655 insertions, 472 deletions
diff --git a/test/go/ns_inspect/app.go b/test/go/ns_inspect/app.go new file mode 100644 index 00000000..ebecbb00 --- /dev/null +++ b/test/go/ns_inspect/app.go @@ -0,0 +1,79 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "nginx/unit" + "os" + "strconv" +) + +type ( + NS struct { + USER uint64 + PID uint64 + IPC uint64 + CGROUP uint64 + UTS uint64 + MNT uint64 + NET uint64 + } + + Output struct { + PID int + UID int + GID int + NS NS + } +) + +func abortonerr(err error) { + if err != nil { + panic(err) + } +} + +// returns: [nstype]:[4026531835] +func getns(nstype string) uint64 { + str, err := os.Readlink(fmt.Sprintf("/proc/self/ns/%s", nstype)) + if err != nil { + return 0 + } + + str = str[len(nstype)+2:] + str = str[:len(str)-1] + val, err := strconv.ParseUint(str, 10, 64) + abortonerr(err) + return val +} + +func handler(w http.ResponseWriter, r *http.Request) { + pid := os.Getpid() + out := &Output{ + PID: pid, + UID: os.Getuid(), + GID: os.Getgid(), + NS: NS{ + PID: getns("pid"), + USER: getns("user"), + MNT: getns("mnt"), + IPC: getns("ipc"), + UTS: getns("uts"), + NET: getns("net"), + CGROUP: getns("cgroup"), + }, + } + data, err := json.Marshal(out) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(data) +} + +func main() { + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) +} diff --git a/test/java/websockets_mirror/app.java b/test/java/websockets_mirror/app.java new file mode 100644 index 00000000..ada60231 --- /dev/null +++ b/test/java/websockets_mirror/app.java @@ -0,0 +1,57 @@ +import java.io.IOException; +import java.nio.ByteBuffer; + +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.PongMessage; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint("/") +public class app { + + @OnOpen + public void onOpen(Session session) { + session.setMaxTextMessageBufferSize(8388608); + } + + @OnMessage + public void echoTextMessage(Session session, String msg) { + try { + if (session.isOpen()) { + session.getBasicRemote().sendText(msg, true); + } + } catch (IOException e) { + try { + session.close(); + } catch (IOException e1) { + // Ignore + } + } + } + + @OnMessage + public void echoBinaryMessage(Session session, ByteBuffer bb) { + try { + if (session.isOpen()) { + session.getBasicRemote().sendBinary(bb, true); + } + } catch (IOException e) { + try { + session.close(); + } catch (IOException e1) { + // Ignore + } + } + } + + /** + * Process a received pong. This is a NO-OP. + * + * @param pm Ignored. + */ + @OnMessage + public void echoPongMessage(PongMessage pm) { + // NO-OP + } +} diff --git a/test/test_access_log.py b/test/test_access_log.py index fbcc131f..8dc87524 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -7,7 +7,7 @@ from unit.applications.lang.python import TestApplicationPython class TestAccessLog(TestApplicationPython): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} def load(self, script): super().load(script) diff --git a/test/test_configuration.py b/test/test_configuration.py index 6e59c0a7..69647858 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -3,7 +3,7 @@ from unit.control import TestControl class TestConfiguration(TestControl): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} def test_json_empty(self): self.assertIn('error', self.conf(''), 'empty') diff --git a/test/test_go_application.py b/test/test_go_application.py index 488bfdd5..42429be7 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -2,7 +2,7 @@ from unit.applications.lang.go import TestApplicationGo class TestGoApplication(TestApplicationGo): - prerequisites = ['go'] + prerequisites = {'modules': ['go']} def test_go_application_variables(self): self.load('variables') diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py new file mode 100644 index 00000000..780c2b03 --- /dev/null +++ b/test/test_go_isolation.py @@ -0,0 +1,135 @@ +import os +import json +import unittest +from unit.applications.lang.go import TestApplicationGo +from unit.feature.isolation import TestFeatureIsolation + + +class TestGoIsolation(TestApplicationGo): + prerequisites = {'modules': ['go'], 'features': ['isolation']} + + isolation = TestFeatureIsolation() + + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + TestFeatureIsolation().check(cls.available, unit.testdir) + + return unit if not complete_check else unit.complete() + + def isolation_key(self, key): + return key in self.available['features']['isolation'].keys() + + def conf_isolation(self, isolation): + self.assertIn( + 'success', + self.conf(isolation, 'applications/ns_inspect/isolation'), + 'configure isolation', + ) + + def test_isolation_values(self): + self.load('ns_inspect') + + obj = self.isolation.parsejson(self.get()['body']) + + for ns, ns_value in self.available['features']['isolation'].items(): + if ns.upper() in obj['NS']: + self.assertEqual( + obj['NS'][ns.upper()], ns_value, '%s match' % ns + ) + + def test_isolation_user(self): + if not self.isolation_key('unprivileged_userns_clone'): + print('unprivileged clone is not available') + raise unittest.SkipTest() + + self.load('ns_inspect') + obj = self.isolation.parsejson(self.get()['body']) + + self.assertTrue(obj['UID'] != 0, 'uid not zero') + self.assertTrue(obj['GID'] != 0, 'gid not zero') + self.assertEqual(obj['UID'], os.getuid(), 'uid match') + self.assertEqual(obj['GID'], os.getgid(), 'gid match') + + self.conf_isolation({"namespaces": {"credential": True}}) + + obj = self.isolation.parsejson(self.get()['body']) + + # default uid and gid maps current user to nobody + self.assertEqual(obj['UID'], 65534, 'uid nobody') + self.assertEqual(obj['GID'], 65534, 'gid nobody') + + self.conf_isolation( + { + "namespaces": {"credential": True}, + "uidmap": [ + {"container": 1000, "host": os.geteuid(), "size": 1} + ], + "gidmap": [ + {"container": 1000, "host": os.getegid(), "size": 1} + ], + } + ) + + obj = self.isolation.parsejson(self.get()['body']) + + # default uid and gid maps current user to root + self.assertEqual(obj['UID'], 1000, 'uid root') + self.assertEqual(obj['GID'], 1000, 'gid root') + + def test_isolation_mnt(self): + if not self.isolation_key('mnt'): + print('mnt namespace is not supported') + raise unittest.SkipTest() + + if not self.isolation_key('unprivileged_userns_clone'): + print('unprivileged clone is not available') + raise unittest.SkipTest() + + self.load('ns_inspect') + self.conf_isolation( + {"namespaces": {"mount": True, "credential": True}} + ) + + obj = self.isolation.parsejson(self.get()['body']) + + # all but user and mnt + allns = list(self.available['features']['isolation'].keys()) + allns.remove('user') + allns.remove('mnt') + + for ns in allns: + if ns.upper() in obj['NS']: + self.assertEqual( + obj['NS'][ns.upper()], + self.available['features']['isolation'][ns], + '%s match' % ns, + ) + + self.assertNotEqual( + obj['NS']['MNT'], self.isolation.getns('mnt'), 'mnt set' + ) + self.assertNotEqual( + obj['NS']['USER'], self.isolation.getns('user'), 'user set' + ) + + def test_isolation_pid(self): + if not self.isolation_key('pid'): + print('pid namespace is not supported') + raise unittest.SkipTest() + + if not self.isolation_key('unprivileged_userns_clone'): + print('unprivileged clone is not available') + raise unittest.SkipTest() + + self.load('ns_inspect') + self.conf_isolation({"namespaces": {"pid": True, "credential": True}}) + + obj = self.isolation.parsejson(self.get()['body']) + + self.assertEqual(obj['PID'], 1, 'pid of container is 1') + + +if __name__ == '__main__': + TestGoIsolation.main() diff --git a/test/test_http_header.py b/test/test_http_header.py index 603f6f0f..b773bd68 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -3,7 +3,7 @@ from unit.applications.lang.python import TestApplicationPython class TestHTTPHeader(TestApplicationPython): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} def test_http_header_value_leading_sp(self): self.load('custom_header') diff --git a/test/test_java_application.py b/test/test_java_application.py index 526be565..2e937718 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -4,7 +4,7 @@ from unit.applications.lang.java import TestApplicationJava class TestJavaApplication(TestApplicationJava): - prerequisites = ['java'] + prerequisites = {'modules': ['java']} def test_java_conf_error(self): self.skip_alerts.extend( diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py new file mode 100644 index 00000000..3f2c0a8a --- /dev/null +++ b/test/test_java_websockets.py @@ -0,0 +1,1469 @@ +import time +import struct +import unittest +from unit.applications.lang.java import TestApplicationJava +from unit.applications.websockets import TestApplicationWebsocket + + +class TestJavaWebsockets(TestApplicationJava): + prerequisites = {'modules': ['java']} + + ws = TestApplicationWebsocket(True) + + def setUp(self): + super().setUp() + + self.assertIn( + 'success', + self.conf( + {'http': {'websocket': {'keepalive_interval': 0}}}, 'settings' + ), + 'clear keepalive_interval', + ) + + self.skip_alerts.extend( + [r'last message send failed', r'socket close\(\d+\) failed'] + ) + + def close_connection(self, sock): + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + + self.check_close(sock) + + def check_close(self, sock, code=1000, no_close=False): + frame = self.ws.frame_read(sock) + + self.assertEqual(frame['fin'], True, 'close fin') + self.assertEqual(frame['opcode'], self.ws.OP_CLOSE, 'close opcode') + self.assertEqual(frame['code'], code, 'close code') + + if not no_close: + sock.close() + + def check_frame(self, frame, fin, opcode, payload, decode=True): + if opcode == self.ws.OP_BINARY or not decode: + data = frame['data'] + else: + data = frame['data'].decode('utf-8') + + self.assertEqual(frame['fin'], fin, 'fin') + self.assertEqual(frame['opcode'], opcode, 'opcode') + self.assertEqual(data, payload, 'payload') + + def test_java_websockets_handshake(self): + self.load('websockets_mirror') + + resp, sock, key = self.ws.upgrade() + sock.close() + + self.assertEqual(resp['status'], 101, 'status') + self.assertEqual(resp['headers']['Upgrade'], 'websocket', 'upgrade') + self.assertEqual( + resp['headers']['Connection'], 'Upgrade', 'connection' + ) + self.assertEqual( + resp['headers']['Sec-WebSocket-Accept'], self.ws.accept(key), 'key' + ) + + def test_java_websockets_mirror(self): + self.load('websockets_mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + frame = self.ws.frame_read(sock) + + self.assertEqual(message, frame['data'].decode('utf-8'), 'mirror') + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + frame = self.ws.frame_read(sock) + + self.assertEqual(message, frame['data'].decode('utf-8'), 'mirror 2') + + sock.close() + + def test_java_websockets_no_mask(self): + self.load('websockets_mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message, mask=False) + + frame = self.ws.frame_read(sock) + + self.assertEqual(frame['opcode'], self.ws.OP_CLOSE, 'no mask opcode') + self.assertEqual(frame['code'], 1002, 'no mask close code') + + sock.close() + + def test_java_websockets_fragmentation(self): + self.load('websockets_mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message, fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, ' ', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, message) + + frame = self.ws.frame_read(sock) + + self.assertEqual( + message + ' ' + message, + frame['data'].decode('utf-8'), + 'mirror framing', + ) + + sock.close() + + def test_java_websockets_frame_fragmentation_invalid(self): + self.load('websockets_mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PING, message, fin=False) + + frame = self.ws.frame_read(sock) + + frame.pop('data') + self.assertDictEqual( + frame, + { + 'fin': True, + 'rsv1': False, + 'rsv2': False, + 'rsv3': False, + 'opcode': self.ws.OP_CLOSE, + 'mask': 0, + 'code': 1002, + 'reason': 'Fragmented control frame', + }, + 'close frame', + ) + + sock.close() + + def test_java_websockets_two_clients(self): + self.load('websockets_mirror') + + message1 = 'blah1' + message2 = 'blah2' + + _, sock1, _ = self.ws.upgrade() + _, sock2, _ = self.ws.upgrade() + + self.ws.frame_write(sock1, self.ws.OP_TEXT, message1) + self.ws.frame_write(sock2, self.ws.OP_TEXT, message2) + + frame1 = self.ws.frame_read(sock1) + frame2 = self.ws.frame_read(sock2) + + self.assertEqual(message1, frame1['data'].decode('utf-8'), 'client 1') + self.assertEqual(message2, frame2['data'].decode('utf-8'), 'client 2') + + sock1.close() + sock2.close() + + @unittest.skip('not yet') + def test_java_websockets_handshake_upgrade_absent( + self + ): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 + self.load('websockets_mirror') + + self.get() + + key = self.ws.key() + resp = self.get( + headers={ + 'Host': 'localhost', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) + + self.assertEqual(resp['status'], 400, 'upgrade absent') + + def test_java_websockets_handshake_case_insensitive(self): + self.load('websockets_mirror') + + self.get() + + key = self.ws.key() + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'WEBSOCKET', + 'Connection': 'UPGRADE', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) + + self.assertEqual(resp['status'], 101, 'status') + + @unittest.skip('not yet') + def test_java_websockets_handshake_connection_absent(self): # FAIL + self.load('websockets_mirror') + + self.get() + + key = self.ws.key() + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) + + self.assertEqual(resp['status'], 400, 'status') + + def test_java_websockets_handshake_version_absent(self): + self.load('websockets_mirror') + + self.get() + + key = self.ws.key() + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + }, + read_timeout=1, + ) + + self.assertEqual(resp['status'], 426, 'status') + + @unittest.skip('not yet') + def test_java_websockets_handshake_key_invalid(self): + self.load('websockets_mirror') + + self.get() + + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': '!', + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) + + self.assertEqual(resp['status'], 400, 'key length') + + key = self.ws.key() + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': [key, key], + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) + + self.assertEqual( + resp['status'], 400, 'key double' + ) # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 + + def test_java_websockets_handshake_method_invalid(self): + self.load('websockets_mirror') + + self.get() + + key = self.ws.key() + resp = self.post( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) + + self.assertEqual(resp['status'], 400, 'status') + + def test_java_websockets_handshake_http_10(self): + self.load('websockets_mirror') + + self.get() + + key = self.ws.key() + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + http_10=True, + read_timeout=1, + ) + + self.assertEqual(resp['status'], 400, 'status') + + def test_java_websockets_handshake_uri_invalid(self): + self.load('websockets_mirror') + + self.get() + + key = self.ws.key() + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + url='!', + read_timeout=1, + ) + + self.assertEqual(resp['status'], 400, 'status') + + def test_java_websockets_protocol_absent(self): + self.load('websockets_mirror') + + self.get() + + key = self.ws.key() + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) + + self.assertEqual(resp['status'], 101, 'status') + self.assertEqual(resp['headers']['Upgrade'], 'websocket', 'upgrade') + self.assertEqual( + resp['headers']['Connection'], 'Upgrade', 'connection' + ) + self.assertEqual( + resp['headers']['Sec-WebSocket-Accept'], self.ws.accept(key), 'key' + ) + + # autobahn-testsuite + # + # Some following tests fail because of Unit does not support UTF-8 + # validation for websocket frames. It should be implemented + # by application, if necessary. + + def test_java_websockets_1_1_1__1_1_8(self): + self.load('websockets_mirror') + + opcode = self.ws.OP_TEXT + + _, sock, _ = self.ws.upgrade() + + def check_length(length, chopsize=None): + payload = '*' * length + + self.ws.frame_write(sock, opcode, payload, chopsize=chopsize) + + frame = self.ws.message_read(sock) + self.check_frame(frame, True, opcode, payload) + + check_length(0) # 1_1_1 + check_length(125) # 1_1_2 + check_length(126) # 1_1_3 + check_length(127) # 1_1_4 + check_length(128) # 1_1_5 + check_length(65535) # 1_1_6 + check_length(65536) # 1_1_7 + check_length(65536, chopsize = 997) # 1_1_8 + + self.close_connection(sock) + + def test_java_websockets_1_2_1__1_2_8(self): + self.load('websockets_mirror') + + opcode = self.ws.OP_BINARY + + _, sock, _ = self.ws.upgrade() + + def check_length(length, chopsize=None): + payload = b'\xfe' * length + + self.ws.frame_write(sock, opcode, payload, chopsize=chopsize) + + frame = self.ws.message_read(sock) + self.check_frame(frame, True, opcode, payload) + + check_length(0) # 1_2_1 + check_length(125) # 1_2_2 + check_length(126) # 1_2_3 + check_length(127) # 1_2_4 + check_length(128) # 1_2_5 + check_length(65535) # 1_2_6 + check_length(65536) # 1_2_7 + check_length(65536, chopsize = 997) # 1_2_8 + + self.close_connection(sock) + + def test_java_websockets_2_1__2_6(self): + self.load('websockets_mirror') + + op_ping = self.ws.OP_PING + op_pong = self.ws.OP_PONG + + _, sock, _ = self.ws.upgrade() + + def check_ping(payload, chopsize=None, decode=True): + self.ws.frame_write(sock, op_ping, payload, chopsize=chopsize) + frame = self.ws.frame_read(sock) + + self.check_frame(frame, True, op_pong, payload, decode=decode) + + check_ping('') # 2_1 + check_ping('Hello, world!') # 2_2 + check_ping(b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', decode=False) # 2_3 + check_ping(b'\xfe' * 125, decode=False) # 2_4 + check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6 + + self.close_connection(sock) + + # 2_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PING, b'\xfe' * 126) + self.check_close(sock, 1002) + + def test_java_websockets_2_7__2_9(self): + self.load('websockets_mirror') + + # 2_7 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PONG, '') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_7') + + # 2_8 + + self.ws.frame_write(sock, self.ws.OP_PONG, 'unsolicited pong payload') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_8') + + # 2_9 + + payload = 'ping payload' + + self.ws.frame_write(sock, self.ws.OP_PONG, 'unsolicited pong payload') + self.ws.frame_write(sock, self.ws.OP_PING, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, payload) + + self.close_connection(sock) + + def test_java_websockets_2_10__2_11(self): + self.load('websockets_mirror') + + # 2_10 + + _, sock, _ = self.ws.upgrade() + + for i in range(0, 10): + self.ws.frame_write(sock, self.ws.OP_PING, 'payload-%d' % i) + + for i in range(0, 10): + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'payload-%d' % i) + + # 2_11 + + for i in range(0, 10): + opcode = self.ws.OP_PING + self.ws.frame_write(sock, opcode, 'payload-%d' % i, chopsize=1) + + for i in range(0, 10): + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'payload-%d' % i) + + self.close_connection(sock) + + @unittest.skip('not yet') + def test_java_websockets_3_1__3_7(self): + self.load('websockets_mirror') + + payload = 'Hello, world!' + + # 3_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, rsv1=True) + self.check_close(sock, 1002) + + # 3_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, rsv2=True) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.check_close(sock, 1002, no_close=True) + + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_2') + sock.close() + + # 3_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write( + sock, self.ws.OP_TEXT, payload, rsv1=True, rsv2=True + ) + + self.check_close(sock, 1002, no_close=True) + + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_3') + sock.close() + + # 3_4 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1) + self.ws.frame_write( + sock, self.ws.OP_TEXT, payload, rsv3=True, chopsize=1 + ) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.check_close(sock, 1002, no_close=True) + + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_4') + sock.close() + + # 3_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_BINARY, + b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', + rsv1=True, + rsv3=True, + ) + + self.check_close(sock, 1002) + + # 3_6 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, self.ws.OP_PING, payload, rsv2=True, rsv3=True + ) + + self.check_close(sock, 1002) + + # 3_7 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, self.ws.OP_CLOSE, payload, rsv1=True, rsv2=True, rsv3=True + ) + + self.check_close(sock, 1002) + + def test_java_websockets_4_1_1__4_2_5(self): + self.load('websockets_mirror') + + payload = 'Hello, world!' + + # 4_1_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, 0x03, '') + self.check_close(sock, 1002) + + # 4_1_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, 0x04, 'reserved opcode payload') + self.check_close(sock, 1002) + + # 4_1_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x05, '') + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_1_4 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x06, payload) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_1_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x07, payload, chopsize=1) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_2_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, 0x0B, '') + self.check_close(sock, 1002) + + # 4_2_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, 0x0C, 'reserved opcode payload') + self.check_close(sock, 1002) + + # 4_2_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x0D, '') + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_2_4 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x0E, payload) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_2_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x0F, payload, chopsize=1) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + def test_java_websockets_5_1__5_20(self): + self.load('websockets_mirror') + + # 5_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PING, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + self.check_close(sock, 1002) + + # 5_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PONG, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + self.check_close(sock, 1002) + + # 5_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_4 + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_4') + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_5 + + self.ws.frame_write( + sock, self.ws.OP_TEXT, 'fragment1', fin=False, chopsize=1 + ) + self.ws.frame_write( + sock, self.ws.OP_CONT, 'fragment2', fin=True, chopsize=1 + ) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_6 + + ping_payload = 'ping payload' + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, ping_payload) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, ping_payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_7 + + ping_payload = 'ping payload' + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_7') + + self.ws.frame_write(sock, self.ws.OP_PING, ping_payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, ping_payload) + + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_8 + + ping_payload = 'ping payload' + + self.ws.frame_write( + sock, self.ws.OP_TEXT, 'fragment1', fin=False, chopsize=1 + ) + self.ws.frame_write(sock, self.ws.OP_PING, ping_payload, chopsize=1) + self.ws.frame_write( + sock, self.ws.OP_CONT, 'fragment2', fin=True, chopsize=1 + ) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, ping_payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_9 + + self.ws.frame_write( + sock, self.ws.OP_CONT, 'non-continuation payload', fin=True + ) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) + self.check_close(sock, 1002) + + # 5_10 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, self.ws.OP_CONT, 'non-continuation payload', fin=True + ) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) + self.check_close(sock, 1002) + + # 5_11 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'non-continuation payload', + fin=True, + chopsize=1, + ) + self.ws.frame_write( + sock, self.ws.OP_TEXT, 'Hello, world!', fin=True, chopsize=1 + ) + self.check_close(sock, 1002) + + # 5_12 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, self.ws.OP_CONT, 'non-continuation payload', fin=False + ) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) + self.check_close(sock, 1002) + + # 5_13 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, self.ws.OP_CONT, 'non-continuation payload', fin=False + ) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) + self.check_close(sock, 1002) + + # 5_14 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'non-continuation payload', + fin=False, + chopsize=1, + ) + self.ws.frame_write( + sock, self.ws.OP_TEXT, 'Hello, world!', fin=True, chopsize=1 + ) + self.check_close(sock, 1002) + + # 5_15 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment4', fin=True) + self.check_close(sock, 1002) + + # 5_16 + + _, sock, _ = self.ws.upgrade() + + for i in range(0, 2): + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=True) + self.check_close(sock, 1002) + + # 5_17 + + _, sock, _ = self.ws.upgrade() + + for i in range(0, 2): + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment1', fin=True) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=True) + self.check_close(sock, 1002) + + # 5_18 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2') + self.check_close(sock, 1002) + + # 5_19 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 1!') + + time.sleep(1) + + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment4', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 2!') + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment5') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 1!') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 2!') + + self.check_frame( + self.ws.frame_read(sock), + True, + self.ws.OP_TEXT, + 'fragment1fragment2fragment3fragment4fragment5', + ) + + # 5_20 + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 1!') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 1!') + + time.sleep(1) + + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment4', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 2!') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 2!') + + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_20') + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment5') + + self.check_frame( + self.ws.frame_read(sock), + True, + self.ws.OP_TEXT, + 'fragment1fragment2fragment3fragment4fragment5', + ) + + self.close_connection(sock) + + def test_java_websockets_6_1_1__6_4_4(self): + self.load('websockets_mirror') + + # 6_1_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, '') + frame = self.ws.frame_read(sock, read_timeout=3) + self.check_frame(frame, True, self.ws.OP_TEXT, '') + + # 6_1_2 + + self.ws.frame_write(sock, self.ws.OP_TEXT, '', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, '', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, '') + + frame = self.ws.frame_read(sock, read_timeout=3) + self.check_frame(frame, True, self.ws.OP_TEXT, '') + + # 6_1_3 + + payload = 'middle frame payload' + + self.ws.frame_write(sock, self.ws.OP_TEXT, '', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, payload, fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, '') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + # 6_2_1 + + payload = 'Hello-µ@ßöäüàá-UTF-8!!' + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + # 6_2_2 + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload[:12], fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, payload[12:]) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + # 6_2_3 + + self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + # 6_2_4 + + payload = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' + + self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.close_connection(sock) + +# Unit does not support UTF-8 validation +# +# # 6_3_1 FAIL +# +# payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' +# payload_2 = '\xed\xa0\x80' +# payload_3 = '\x65\x64\x69\x74\x65\x64' +# +# payload = payload_1 + payload_2 + payload_3 +# +# self.ws.message(sock, self.ws.OP_TEXT, payload) +# self.check_close(sock, 1007) +# +# # 6_3_2 FAIL +# +# _, sock, _ = self.ws.upgrade() +# +# self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) +# self.check_close(sock, 1007) +# +# # 6_4_1 ... 6_4_4 FAIL + + def test_java_websockets_7_1_1__7_5_1(self): + self.load('websockets_mirror') + + # 7_1_1 + + _, sock, _ = self.ws.upgrade() + + payload = "Hello World!" + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.close_connection(sock) + + # 7_1_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + + self.check_close(sock) + + # 7_1_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.check_close(sock, no_close=True) + + self.ws.frame_write(sock, self.ws.OP_PING, '') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + sock.close() + + # 7_1_4 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.check_close(sock, no_close=True) + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + sock.close() + + # 7_1_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.check_close(sock, no_close=True) + + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + sock.close() + + # 7_1_6 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'BAsd7&jh23' * 26 * 2 ** 10) + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + + self.recvall(sock, read_timeout=1) + + self.ws.frame_write(sock, self.ws.OP_PING, '') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + sock.close() + + # 7_3_1 # FAIL + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, '') + self.check_close(sock) + + # 7_3_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, 'a') + self.check_close(sock, 1002) + + # 7_3_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.check_close(sock) + + # 7_3_4 + + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(reason='Hello World!') + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock) + + # 7_3_5 + + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(reason='*' * 123) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock) + + # 7_3_6 + + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(reason='*' * 124) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, 1002) + +# # 7_5_1 FAIL Unit does not support UTF-8 validation +# +# _, sock, _ = self.ws.upgrade() +# +# payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ +# '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64') +# +# self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) +# self.check_close(sock, 1007) + + def test_java_websockets_7_7_X__7_9_X(self): + self.load('websockets_mirror') + + valid_codes = [ + 1000, + 1001, + 1002, + 1003, + 1007, + 1008, + 1009, + 1010, + 1011, + 3000, + 3999, + 4000, + 4999, + ] + + invalid_codes = [0, 999, 1004, 1005, 1006, 1016, 1100, 2000, 2999] + + for code in valid_codes: + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(code=code) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, code=code) + + for code in invalid_codes: + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(code=code) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, 1002) + + def test_java_websockets_7_13_1__7_13_2(self): + self.load('websockets_mirror') + + # 7_13_1 + + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(code=5000) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, 1002) + + # 7_13_2 + + _, sock, _ = self.ws.upgrade() + + payload = struct.pack('!I', 65536) + ''.encode('utf-8') + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, 1002) + + def test_java_websockets_9_1_1__9_6_6(self): + if not self.unsafe: + self.skipTest("unsafe, long run") + + self.load('websockets_mirror') + + self.assertIn( + 'success', + self.conf( + { + 'http': { + 'websocket': { + 'max_frame_size': 33554432, + 'keepalive_interval': 0, + } + } + }, + 'settings', + ), + 'increase max_frame_size and keepalive_interval', + ) + + _, sock, _ = self.ws.upgrade() + + op_text = self.ws.OP_TEXT + op_binary = self.ws.OP_BINARY + + def check_payload(opcode, length, chopsize=None): + if opcode == self.ws.OP_TEXT: + payload = '*' * length + else: + payload = b'*' * length + + self.ws.frame_write(sock, opcode, payload, chopsize=chopsize) + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, opcode, payload) + + def check_message(opcode, f_size): + if opcode == self.ws.OP_TEXT: + payload = '*' * 4 * 2 ** 20 + else: + payload = b'*' * 4 * 2 ** 20 + + self.ws.message(sock, opcode, payload, fragmention_size=f_size) + frame = self.ws.frame_read(sock, read_timeout=5) + self.check_frame(frame, True, opcode, payload) + + check_payload(op_text, 64 * 2 ** 10) # 9_1_1 + check_payload(op_text, 256 * 2 ** 10) # 9_1_2 + check_payload(op_text, 2 ** 20) # 9_1_3 + check_payload(op_text, 4 * 2 ** 20) # 9_1_4 + check_payload(op_text, 8 * 2 ** 20) # 9_1_5 + check_payload(op_text, 16 * 2 ** 20) # 9_1_6 + + check_payload(op_binary, 64 * 2 ** 10) # 9_2_1 + check_payload(op_binary, 256 * 2 ** 10) # 9_2_2 + check_payload(op_binary, 2 ** 20) # 9_2_3 + check_payload(op_binary, 4 * 2 ** 20) # 9_2_4 + check_payload(op_binary, 8 * 2 ** 20) # 9_2_5 + check_payload(op_binary, 16 * 2 ** 20) # 9_2_6 + + if self.system != 'Darwin' and self.system != 'FreeBSD': + check_message(op_text, 64) # 9_3_1 + check_message(op_text, 256) # 9_3_2 + check_message(op_text, 2 ** 10) # 9_3_3 + check_message(op_text, 4 * 2 ** 10) # 9_3_4 + check_message(op_text, 16 * 2 ** 10) # 9_3_5 + check_message(op_text, 64 * 2 ** 10) # 9_3_6 + check_message(op_text, 256 * 2 ** 10) # 9_3_7 + check_message(op_text, 2 ** 20) # 9_3_8 + check_message(op_text, 4 * 2 ** 20) # 9_3_9 + + check_message(op_binary, 64) # 9_4_1 + check_message(op_binary, 256) # 9_4_2 + check_message(op_binary, 2 ** 10) # 9_4_3 + check_message(op_binary, 4 * 2 ** 10) # 9_4_4 + check_message(op_binary, 16 * 2 ** 10) # 9_4_5 + check_message(op_binary, 64 * 2 ** 10) # 9_4_6 + check_message(op_binary, 256 * 2 ** 10) # 9_4_7 + check_message(op_binary, 2 ** 20) # 9_4_8 + check_message(op_binary, 4 * 2 ** 20) # 9_4_9 + + check_payload(op_text, 2 ** 20, chopsize=64) # 9_5_1 + check_payload(op_text, 2 ** 20, chopsize=128) # 9_5_2 + check_payload(op_text, 2 ** 20, chopsize=256) # 9_5_3 + check_payload(op_text, 2 ** 20, chopsize=512) # 9_5_4 + check_payload(op_text, 2 ** 20, chopsize=1024) # 9_5_5 + check_payload(op_text, 2 ** 20, chopsize=2048) # 9_5_6 + + check_payload(op_binary, 2 ** 20, chopsize=64) # 9_6_1 + check_payload(op_binary, 2 ** 20, chopsize=128) # 9_6_2 + check_payload(op_binary, 2 ** 20, chopsize=256) # 9_6_3 + check_payload(op_binary, 2 ** 20, chopsize=512) # 9_6_4 + check_payload(op_binary, 2 ** 20, chopsize=1024) # 9_6_5 + check_payload(op_binary, 2 ** 20, chopsize=2048) # 9_6_6 + + self.close_connection(sock) + + def test_java_websockets_10_1_1(self): + self.load('websockets_mirror') + + _, sock, _ = self.ws.upgrade() + + payload = '*' * 65536 + + self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1300) + + frame = self.ws.message_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.close_connection(sock) + + # settings + + def test_java_websockets_max_frame_size(self): + self.load('websockets_mirror') + + self.assertIn( + 'success', + self.conf( + {'http': {'websocket': {'max_frame_size': 100}}}, 'settings' + ), + 'configure max_frame_size', + ) + + _, sock, _ = self.ws.upgrade() + + payload = '*' * 94 + opcode = self.ws.OP_TEXT + + self.ws.frame_write(sock, opcode, payload) # frame length is 100 + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, opcode, payload) + + payload = '*' * 95 + + self.ws.frame_write(sock, opcode, payload) # frame length is 101 + self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE + + def test_java_websockets_read_timeout(self): + self.load('websockets_mirror') + + self.assertIn( + 'success', + self.conf( + {'http': {'websocket': {'read_timeout': 5}}}, 'settings' + ), + 'configure read_timeout', + ) + + _, sock, _ = self.ws.upgrade() + + frame = self.ws.frame_to_send(self.ws.OP_TEXT, 'blah') + sock.sendall(frame[:2]) + + time.sleep(2) + + self.check_close(sock, 1001) # 1001 - CLOSE_GOING_AWAY + + def test_java_websockets_keepalive_interval(self): + self.load('websockets_mirror') + + self.assertIn( + 'success', + self.conf( + {'http': {'websocket': {'keepalive_interval': 5}}}, 'settings' + ), + 'configure keepalive_interval', + ) + + _, sock, _ = self.ws.upgrade() + + frame = self.ws.frame_to_send(self.ws.OP_TEXT, 'blah') + sock.sendall(frame[:2]) + + time.sleep(2) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PING, '') # PING frame + + sock.close() + + +if __name__ == '__main__': + TestJavaWebsockets.main() diff --git a/test/test_node_application.py b/test/test_node_application.py index 0354c978..a5b4a108 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -3,7 +3,7 @@ from unit.applications.lang.node import TestApplicationNode class TestNodeApplication(TestApplicationNode): - prerequisites = ['node'] + prerequisites = {'modules': ['node']} def test_node_application_basic(self): self.load('basic') diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index 6652d8c5..b24bee75 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -4,8 +4,9 @@ import unittest from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket + class TestNodeWebsockets(TestApplicationNode): - prerequisites = ['node'] + prerequisites = {'modules': ['node']} ws = TestApplicationWebsocket() @@ -21,10 +22,7 @@ class TestNodeWebsockets(TestApplicationNode): ) self.skip_alerts.extend( - [ - r'last message send failed', - r'socket close\(\d+\) failed', - ] + [r'last message send failed', r'socket close\(\d+\) failed'] ) def close_connection(self, sock): @@ -34,7 +32,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock) - def check_close(self, sock, code = 1000, no_close = False): + def check_close(self, sock, code=1000, no_close=False): frame = self.ws.frame_read(sock) self.assertEqual(frame['fin'], True, 'close fin') @@ -61,9 +59,7 @@ class TestNodeWebsockets(TestApplicationNode): sock.close() self.assertEqual(resp['status'], 101, 'status') - self.assertEqual( - resp['headers']['Upgrade'], 'websocket', 'upgrade' - ) + self.assertEqual(resp['headers']['Upgrade'], 'websocket', 'upgrade') self.assertEqual( resp['headers']['Connection'], 'Upgrade', 'connection' ) @@ -81,16 +77,12 @@ class TestNodeWebsockets(TestApplicationNode): self.ws.frame_write(sock, self.ws.OP_TEXT, message) frame = self.ws.frame_read(sock) - self.assertEqual( - message, frame['data'].decode('utf-8'), 'mirror' - ) + self.assertEqual(message, frame['data'].decode('utf-8'), 'mirror') self.ws.frame_write(sock, self.ws.OP_TEXT, message) frame = self.ws.frame_read(sock) - self.assertEqual( - message, frame['data'].decode('utf-8'), 'mirror 2' - ) + self.assertEqual(message, frame['data'].decode('utf-8'), 'mirror 2') sock.close() @@ -160,29 +152,6 @@ class TestNodeWebsockets(TestApplicationNode): sock.close() - def test_node_websockets_partial_send(self): - self.load('websockets/mirror') - - message = 'blah' - - _, sock, _ = self.ws.upgrade() - - frame = self.ws.frame_to_send(self.ws.OP_TEXT, message) - sock.sendall(frame[:1]) - sock.sendall(frame[1:2]) - sock.sendall(frame[2:3]) - sock.sendall(frame[3:]) - - frame = self.ws.frame_read(sock) - - self.assertEqual( - message, - frame['data'].decode('utf-8'), - 'partial send', - ) - - sock.close() - def test_node_websockets_large(self): self.load('websockets/mirror_fragmentation') @@ -202,65 +171,6 @@ class TestNodeWebsockets(TestApplicationNode): sock.close() - def test_node_websockets_frame_invalid_opcode(self): - self.load('websockets/mirror') - - message = 'blah' - - _, sock, _ = self.ws.upgrade() - - self.ws.frame_write(sock, self.ws.OP_TEXT, message, fin=False) - self.ws.frame_write(sock, self.ws.OP_TEXT, message) - - frame = self.ws.frame_read(sock) - - frame.pop('data') - frame.pop('reason') - self.assertDictEqual( - frame, - { - 'fin': True, - 'rsv1': False, - 'rsv2': False, - 'rsv3': False, - 'opcode': self.ws.OP_CLOSE, - 'mask': 0, - 'code': 1002, - }, - 'close frame', - ) - - sock.close() - - def test_node_websockets_frame_invalid_opcode_2(self): - self.load('websockets/mirror') - - message = 'blah' - - _, sock, _ = self.ws.upgrade() - - self.ws.frame_write(sock, self.ws.OP_CONT, message) - - frame = self.ws.frame_read(sock) - - frame.pop('data') - self.assertDictEqual( - frame, - { - 'fin': True, - 'rsv1': False, - 'rsv2': False, - 'rsv3': False, - 'opcode': self.ws.OP_CLOSE, - 'mask': 0, - 'code': 1002, - 'reason': 'Unrecognized opcode 0', - }, - 'close frame', - ) - - sock.close() - def test_node_websockets_two_clients(self): self.load('websockets/mirror') @@ -276,28 +186,29 @@ class TestNodeWebsockets(TestApplicationNode): frame1 = self.ws.frame_read(sock1) frame2 = self.ws.frame_read(sock2) - self.assertEqual( - message1, frame1['data'].decode('utf-8'), 'client 1' - ) - self.assertEqual( - message2, frame2['data'].decode('utf-8'), 'client 2' - ) + self.assertEqual(message1, frame1['data'].decode('utf-8'), 'client 1') + self.assertEqual(message2, frame2['data'].decode('utf-8'), 'client 2') sock1.close() sock2.close() @unittest.skip('not yet') - def test_node_websockets_handshake_upgrade_absent(self): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 + def test_node_websockets_handshake_upgrade_absent( + self + ): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 self.load('websockets/mirror') key = self.ws.key() - resp = self.get(headers={ - 'Host': 'localhost', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Protocol': 'chat', - 'Sec-WebSocket-Version': 13, - }, read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) self.assertEqual(resp['status'], 400, 'upgrade absent') @@ -305,29 +216,35 @@ class TestNodeWebsockets(TestApplicationNode): self.load('websockets/mirror') key = self.ws.key() - resp = self.get(headers={ - 'Host': 'localhost', - 'Upgrade': 'WEBSOCKET', - 'Connection': 'UPGRADE', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Protocol': 'chat', - 'Sec-WebSocket-Version': 13, - }, read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'WEBSOCKET', + 'Connection': 'UPGRADE', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) self.assertEqual(resp['status'], 101, 'status') @unittest.skip('not yet') - def test_node_websockets_handshake_connection_absent(self): # FAIL + def test_node_websockets_handshake_connection_absent(self): # FAIL self.load('websockets/mirror') key = self.ws.key() - resp = self.get(headers={ - 'Host': 'localhost', - 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Protocol': 'chat', - 'Sec-WebSocket-Version': 13, - }, read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) self.assertEqual(resp['status'], 400, 'status') @@ -335,13 +252,16 @@ class TestNodeWebsockets(TestApplicationNode): self.load('websockets/mirror') key = self.ws.key() - resp = self.get(headers={ - 'Host': 'localhost', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Protocol': 'chat' - }, read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + }, + read_timeout=1, + ) self.assertEqual(resp['status'], 426, 'status') @@ -349,41 +269,52 @@ class TestNodeWebsockets(TestApplicationNode): def test_node_websockets_handshake_key_invalid(self): self.load('websockets/mirror') - resp = self.get(headers={ - 'Host': 'localhost', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': '!', - 'Sec-WebSocket-Protocol': 'chat', - 'Sec-WebSocket-Version': 13 - }, read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': '!', + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) self.assertEqual(resp['status'], 400, 'key length') key = self.ws.key() - resp = self.get(headers={ - 'Host': 'localhost', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': [key, key], - 'Sec-WebSocket-Protocol': 'chat', - 'Sec-WebSocket-Version': 13 - }, read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': [key, key], + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) - self.assertEqual(resp['status'], 400, 'key double') # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 + self.assertEqual( + resp['status'], 400, 'key double' + ) # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 def test_node_websockets_handshake_method_invalid(self): self.load('websockets/mirror') key = self.ws.key() - resp = self.post(headers={ - 'Host': 'localhost', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Protocol': 'chat', - 'Sec-WebSocket-Version': 13 - }, read_timeout=1) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) self.assertEqual(resp['status'], 400, 'status') @@ -391,14 +322,18 @@ class TestNodeWebsockets(TestApplicationNode): self.load('websockets/mirror') key = self.ws.key() - resp = self.get(headers={ - 'Host': 'localhost', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Protocol': 'chat', - 'Sec-WebSocket-Version': 13 - }, http_10=True, read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + http_10=True, + read_timeout=1, + ) self.assertEqual(resp['status'], 400, 'status') @@ -406,14 +341,18 @@ class TestNodeWebsockets(TestApplicationNode): self.load('websockets/mirror') key = self.ws.key() - resp = self.get(headers={ - 'Host': 'localhost', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Protocol': 'chat', - 'Sec-WebSocket-Version': 13 - }, url='!', read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + url='!', + read_timeout=1, + ) self.assertEqual(resp['status'], 400, 'status') @@ -421,18 +360,19 @@ class TestNodeWebsockets(TestApplicationNode): self.load('websockets/mirror') key = self.ws.key() - resp = self.get(headers={ - 'Host': 'localhost', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Version': 13 - }, read_timeout=1) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + ) self.assertEqual(resp['status'], 101, 'status') - self.assertEqual( - resp['headers']['Upgrade'], 'websocket', 'upgrade' - ) + self.assertEqual(resp['headers']['Upgrade'], 'websocket', 'upgrade') self.assertEqual( resp['headers']['Connection'], 'Upgrade', 'connection' ) @@ -441,12 +381,11 @@ class TestNodeWebsockets(TestApplicationNode): ) # autobahn-testsuite - + # # Some following tests fail because of Unit does not support UTF-8 # validation for websocket frames. It should be implemented # by application, if necessary. - @unittest.skip('not yet') def test_node_websockets_1_1_1__1_1_8(self): self.load('websockets/mirror') @@ -473,7 +412,6 @@ class TestNodeWebsockets(TestApplicationNode): self.close_connection(sock) - @unittest.skip('not yet') def test_node_websockets_1_2_1__1_2_8(self): self.load('websockets/mirror') @@ -606,7 +544,7 @@ class TestNodeWebsockets(TestApplicationNode): frame = self.ws.frame_read(sock) self.check_frame(frame, True, self.ws.OP_TEXT, payload) - self.check_close(sock, 1002, no_close = True) + self.check_close(sock, 1002, no_close=True) self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_2') sock.close() @@ -621,14 +559,10 @@ class TestNodeWebsockets(TestApplicationNode): self.check_frame(frame, True, self.ws.OP_TEXT, payload) self.ws.frame_write( - sock, - self.ws.OP_TEXT, - payload, - rsv1=True, - rsv2=True, + sock, self.ws.OP_TEXT, payload, rsv1=True, rsv2=True ) - self.check_close(sock, 1002, no_close = True) + self.check_close(sock, 1002, no_close=True) self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_3') sock.close() @@ -639,18 +573,14 @@ class TestNodeWebsockets(TestApplicationNode): self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1) self.ws.frame_write( - sock, - self.ws.OP_TEXT, - payload, - rsv3=True, - chopsize=1 + sock, self.ws.OP_TEXT, payload, rsv3=True, chopsize=1 ) self.ws.frame_write(sock, self.ws.OP_PING, '') frame = self.ws.frame_read(sock) self.check_frame(frame, True, self.ws.OP_TEXT, payload) - self.check_close(sock, 1002, no_close = True) + self.check_close(sock, 1002, no_close=True) self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_4') sock.close() @@ -674,11 +604,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() self.ws.frame_write( - sock, - self.ws.OP_PING, - payload, - rsv2=True, - rsv3=True, + sock, self.ws.OP_PING, payload, rsv2=True, rsv3=True ) self.check_close(sock, 1002) @@ -688,12 +614,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() self.ws.frame_write( - sock, - self.ws.OP_CLOSE, - payload, - rsv1=True, - rsv2=True, - rsv3=True, + sock, self.ws.OP_CLOSE, payload, rsv1=True, rsv2=True, rsv3=True ) self.check_close(sock, 1002) @@ -815,7 +736,6 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, 1002) - @unittest.skip('not yet') def test_node_websockets_5_1__5_20(self): self.load('websockets/mirror') @@ -857,18 +777,10 @@ class TestNodeWebsockets(TestApplicationNode): # 5_5 self.ws.frame_write( - sock, - self.ws.OP_TEXT, - 'fragment1', - fin=False, - chopsize=1, + sock, self.ws.OP_TEXT, 'fragment1', fin=False, chopsize=1 ) self.ws.frame_write( - sock, - self.ws.OP_CONT, - 'fragment2', - fin=True, - chopsize=1, + sock, self.ws.OP_CONT, 'fragment2', fin=True, chopsize=1 ) frame = self.ws.frame_read(sock) @@ -910,19 +822,11 @@ class TestNodeWebsockets(TestApplicationNode): ping_payload = 'ping payload' self.ws.frame_write( - sock, - self.ws.OP_TEXT, - 'fragment1', - fin=False, - chopsize=1, + sock, self.ws.OP_TEXT, 'fragment1', fin=False, chopsize=1 ) self.ws.frame_write(sock, self.ws.OP_PING, ping_payload, chopsize=1) self.ws.frame_write( - sock, - self.ws.OP_CONT, - 'fragment2', - fin=True, - chopsize=1, + sock, self.ws.OP_CONT, 'fragment2', fin=True, chopsize=1 ) frame = self.ws.frame_read(sock) @@ -934,10 +838,7 @@ class TestNodeWebsockets(TestApplicationNode): # 5_9 self.ws.frame_write( - sock, - self.ws.OP_CONT, - 'non-continuation payload', - fin=True, + sock, self.ws.OP_CONT, 'non-continuation payload', fin=True ) self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) self.check_close(sock, 1002) @@ -947,10 +848,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() self.ws.frame_write( - sock, - self.ws.OP_CONT, - 'non-continuation payload', - fin=True, + sock, self.ws.OP_CONT, 'non-continuation payload', fin=True ) self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) self.check_close(sock, 1002) @@ -967,11 +865,7 @@ class TestNodeWebsockets(TestApplicationNode): chopsize=1, ) self.ws.frame_write( - sock, - self.ws.OP_TEXT, - 'Hello, world!', - fin=True, - chopsize=1, + sock, self.ws.OP_TEXT, 'Hello, world!', fin=True, chopsize=1 ) self.check_close(sock, 1002) @@ -980,10 +874,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() self.ws.frame_write( - sock, - self.ws.OP_CONT, - 'non-continuation payload', - fin=False, + sock, self.ws.OP_CONT, 'non-continuation payload', fin=False ) self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) self.check_close(sock, 1002) @@ -993,10 +884,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() self.ws.frame_write( - sock, - self.ws.OP_CONT, - 'non-continuation payload', - fin=False, + sock, self.ws.OP_CONT, 'non-continuation payload', fin=False ) self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) self.check_close(sock, 1002) @@ -1013,11 +901,7 @@ class TestNodeWebsockets(TestApplicationNode): chopsize=1, ) self.ws.frame_write( - sock, - self.ws.OP_TEXT, - 'Hello, world!', - fin=True, - chopsize=1, + sock, self.ws.OP_TEXT, 'Hello, world!', fin=True, chopsize=1 ) self.check_close(sock, 1002) @@ -1183,8 +1067,8 @@ class TestNodeWebsockets(TestApplicationNode): self.close_connection(sock) - # Unit does not support UTF-8 validation - +# Unit does not support UTF-8 validation +# # # 6_3_1 FAIL # # payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' @@ -1235,7 +1119,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) - self.check_close(sock, no_close = True) + self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_PING, '') self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') @@ -1247,7 +1131,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) - self.check_close(sock, no_close = True) + self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_TEXT, payload) self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') @@ -1260,7 +1144,7 @@ class TestNodeWebsockets(TestApplicationNode): self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) - self.check_close(sock, no_close = True) + self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2') self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') @@ -1271,7 +1155,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() - self.ws.frame_write(sock, self.ws.OP_TEXT, 'BAsd7&jh23' * 26 * 2**10) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'BAsd7&jh23' * 26 * 2 ** 10) self.ws.frame_write(sock, self.ws.OP_TEXT, payload) self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) @@ -1307,7 +1191,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() - payload = self.ws.serialize_close(reason = 'Hello World!') + payload = self.ws.serialize_close(reason='Hello World!') self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock) @@ -1316,7 +1200,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() - payload = self.ws.serialize_close(reason = '*' * 123) + payload = self.ws.serialize_close(reason='*' * 123) self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock) @@ -1325,13 +1209,13 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() - payload = self.ws.serialize_close(reason = '*' * 124) + payload = self.ws.serialize_close(reason='*' * 124) self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock, 1002) - # 7_5_1 FAIL Unit does not support UTF-8 validation - +# # 7_5_1 FAIL Unit does not support UTF-8 validation +# # _, sock, _ = self.ws.upgrade() # # payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ @@ -1364,7 +1248,7 @@ class TestNodeWebsockets(TestApplicationNode): for code in valid_codes: _, sock, _ = self.ws.upgrade() - payload = self.ws.serialize_close(code = code) + payload = self.ws.serialize_close(code=code) self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock) @@ -1372,7 +1256,7 @@ class TestNodeWebsockets(TestApplicationNode): for code in invalid_codes: _, sock, _ = self.ws.upgrade() - payload = self.ws.serialize_close(code = code) + payload = self.ws.serialize_close(code=code) self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock, 1002) @@ -1384,7 +1268,7 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() - payload = self.ws.serialize_close(code = 5000) + payload = self.ws.serialize_close(code=5000) self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock, 1002) @@ -1437,62 +1321,62 @@ class TestNodeWebsockets(TestApplicationNode): def check_message(opcode, f_size): if opcode == self.ws.OP_TEXT: - payload = '*' * 4 * 2**20 + payload = '*' * 4 * 2 ** 20 else: - payload = b'*' * 4 * 2**20 + payload = b'*' * 4 * 2 ** 20 self.ws.message(sock, opcode, payload, fragmention_size=f_size) frame = self.ws.frame_read(sock, read_timeout=5) self.check_frame(frame, True, opcode, payload) - check_payload(op_text, 64 * 2**10) # 9_1_1 - check_payload(op_text, 256 * 2**10) # 9_1_2 - check_payload(op_text, 2**20) # 9_1_3 - check_payload(op_text, 4 * 2**20) # 9_1_4 - check_payload(op_text, 8 * 2**20) # 9_1_5 - check_payload(op_text, 16 * 2**20) # 9_1_6 + check_payload(op_text, 64 * 2 ** 10) # 9_1_1 + check_payload(op_text, 256 * 2 ** 10) # 9_1_2 + check_payload(op_text, 2 ** 20) # 9_1_3 + check_payload(op_text, 4 * 2 ** 20) # 9_1_4 + check_payload(op_text, 8 * 2 ** 20) # 9_1_5 + check_payload(op_text, 16 * 2 ** 20) # 9_1_6 - check_payload(op_binary, 64 * 2**10) # 9_2_1 - check_payload(op_binary, 256 * 2**10) # 9_2_2 - check_payload(op_binary, 2**20) # 9_2_3 - check_payload(op_binary, 4 * 2**20) # 9_2_4 - check_payload(op_binary, 8 * 2**20) # 9_2_5 - check_payload(op_binary, 16 * 2**20) # 9_2_6 + check_payload(op_binary, 64 * 2 ** 10) # 9_2_1 + check_payload(op_binary, 256 * 2 ** 10) # 9_2_2 + check_payload(op_binary, 2 ** 20) # 9_2_3 + check_payload(op_binary, 4 * 2 ** 20) # 9_2_4 + check_payload(op_binary, 8 * 2 ** 20) # 9_2_5 + check_payload(op_binary, 16 * 2 ** 20) # 9_2_6 if self.system != 'Darwin' and self.system != 'FreeBSD': - check_message(op_text, 64) # 9_3_1 - check_message(op_text, 256) # 9_3_2 - check_message(op_text, 2**10) # 9_3_3 - check_message(op_text, 4 * 2**10) # 9_3_4 - check_message(op_text, 16 * 2**10) # 9_3_5 - check_message(op_text, 64 * 2**10) # 9_3_6 - check_message(op_text, 256 * 2**10) # 9_3_7 - check_message(op_text, 2**20) # 9_3_8 - check_message(op_text, 4 * 2**20) # 9_3_9 - - check_message(op_binary, 64) # 9_4_1 - check_message(op_binary, 256) # 9_4_2 - check_message(op_binary, 2**10) # 9_4_3 - check_message(op_binary, 4 * 2**10) # 9_4_4 - check_message(op_binary, 16 * 2**10) # 9_4_5 - check_message(op_binary, 64 * 2**10) # 9_4_6 - check_message(op_binary, 256 * 2**10) # 9_4_7 - check_message(op_binary, 2**20) # 9_4_8 - check_message(op_binary, 4 * 2**20) # 9_4_9 - - check_payload(op_text, 2**20, chopsize=64) # 9_5_1 - check_payload(op_text, 2**20, chopsize=128) # 9_5_2 - check_payload(op_text, 2**20, chopsize=256) # 9_5_3 - check_payload(op_text, 2**20, chopsize=512) # 9_5_4 - check_payload(op_text, 2**20, chopsize=1024) # 9_5_5 - check_payload(op_text, 2**20, chopsize=2048) # 9_5_6 - - check_payload(op_binary, 2**20, chopsize=64) # 9_6_1 - check_payload(op_binary, 2**20, chopsize=128) # 9_6_2 - check_payload(op_binary, 2**20, chopsize=256) # 9_6_3 - check_payload(op_binary, 2**20, chopsize=512) # 9_6_4 - check_payload(op_binary, 2**20, chopsize=1024) # 9_6_5 - check_payload(op_binary, 2**20, chopsize=2048) # 9_6_6 + check_message(op_text, 64) # 9_3_1 + check_message(op_text, 256) # 9_3_2 + check_message(op_text, 2 ** 10) # 9_3_3 + check_message(op_text, 4 * 2 ** 10) # 9_3_4 + check_message(op_text, 16 * 2 ** 10) # 9_3_5 + check_message(op_text, 64 * 2 ** 10) # 9_3_6 + check_message(op_text, 256 * 2 ** 10) # 9_3_7 + check_message(op_text, 2 ** 20) # 9_3_8 + check_message(op_text, 4 * 2 ** 20) # 9_3_9 + + check_message(op_binary, 64) # 9_4_1 + check_message(op_binary, 256) # 9_4_2 + check_message(op_binary, 2 ** 10) # 9_4_3 + check_message(op_binary, 4 * 2 ** 10) # 9_4_4 + check_message(op_binary, 16 * 2 ** 10) # 9_4_5 + check_message(op_binary, 64 * 2 ** 10) # 9_4_6 + check_message(op_binary, 256 * 2 ** 10) # 9_4_7 + check_message(op_binary, 2 ** 20) # 9_4_8 + check_message(op_binary, 4 * 2 ** 20) # 9_4_9 + + check_payload(op_text, 2 ** 20, chopsize=64) # 9_5_1 + check_payload(op_text, 2 ** 20, chopsize=128) # 9_5_2 + check_payload(op_text, 2 ** 20, chopsize=256) # 9_5_3 + check_payload(op_text, 2 ** 20, chopsize=512) # 9_5_4 + check_payload(op_text, 2 ** 20, chopsize=1024) # 9_5_5 + check_payload(op_text, 2 ** 20, chopsize=2048) # 9_5_6 + + check_payload(op_binary, 2 ** 20, chopsize=64) # 9_6_1 + check_payload(op_binary, 2 ** 20, chopsize=128) # 9_6_2 + check_payload(op_binary, 2 ** 20, chopsize=256) # 9_6_3 + check_payload(op_binary, 2 ** 20, chopsize=512) # 9_6_4 + check_payload(op_binary, 2 ** 20, chopsize=1024) # 9_6_5 + check_payload(op_binary, 2 ** 20, chopsize=2048) # 9_6_6 self.close_connection(sock) @@ -1536,7 +1420,7 @@ class TestNodeWebsockets(TestApplicationNode): payload = '*' * 95 self.ws.frame_write(sock, opcode, payload) # frame length is 101 - self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE + self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE def test_node_websockets_read_timeout(self): self.load('websockets/mirror') @@ -1556,7 +1440,7 @@ class TestNodeWebsockets(TestApplicationNode): time.sleep(2) - self.check_close(sock, 1001) # 1001 - CLOSE_GOING_AWAY + self.check_close(sock, 1001) # 1001 - CLOSE_GOING_AWAY def test_node_websockets_keepalive_interval(self): self.load('websockets/mirror') @@ -1581,5 +1465,6 @@ class TestNodeWebsockets(TestApplicationNode): sock.close() + if __name__ == '__main__': TestNodeWebsockets.main() diff --git a/test/test_perl_application.py b/test/test_perl_application.py index bc26b000..bf3c65d5 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -3,7 +3,7 @@ from unit.applications.lang.perl import TestApplicationPerl class TestPerlApplication(TestApplicationPerl): - prerequisites = ['perl'] + prerequisites = {'modules': ['perl']} def test_perl_application(self): self.load('variables') diff --git a/test/test_php_application.py b/test/test_php_application.py index ee2048b5..d614885c 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -3,7 +3,7 @@ import unittest from unit.applications.lang.php import TestApplicationPHP class TestPHPApplication(TestApplicationPHP): - prerequisites = ['php'] + prerequisites = {'modules': ['php']} def before_disable_functions(self): body = self.get()['body'] diff --git a/test/test_php_basic.py b/test/test_php_basic.py index 0c84f206..7ecff1b2 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -2,7 +2,7 @@ from unit.control import TestControl class TestPHPBasic(TestControl): - prerequisites = ['php'] + prerequisites = {'modules': ['php']} conf_app = { "app": { diff --git a/test/test_python_application.py b/test/test_python_application.py index 3484b25e..5b6e2089 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -4,7 +4,7 @@ from unit.applications.lang.python import TestApplicationPython class TestPythonApplication(TestApplicationPython): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} def test_python_application_variables(self): self.load('variables') @@ -71,6 +71,37 @@ class TestPythonApplication(TestApplicationPython): 'Query-String header', ) + def test_python_application_query_string_space(self): + self.load('query_string') + + resp = self.get(url='/ ?var1=val1&var2=val2') + self.assertEqual( + resp['headers']['Query-String'], + 'var1=val1&var2=val2', + 'Query-String space', + ) + + resp = self.get(url='/ %20?var1=val1&var2=val2') + self.assertEqual( + resp['headers']['Query-String'], + 'var1=val1&var2=val2', + 'Query-String space 2', + ) + + resp = self.get(url='/ %20 ?var1=val1&var2=val2') + self.assertEqual( + resp['headers']['Query-String'], + 'var1=val1&var2=val2', + 'Query-String space 3', + ) + + resp = self.get(url='/blah %20 blah? var1= val1 & var2=val2') + self.assertEqual( + resp['headers']['Query-String'], + ' var1= val1 & var2=val2', + 'Query-String space 4', + ) + def test_python_application_query_string_empty(self): self.load('query_string') diff --git a/test/test_python_basic.py b/test/test_python_basic.py index e63158e5..67a5f548 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -2,7 +2,7 @@ from unit.control import TestControl class TestPythonBasic(TestControl): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} conf_app = { "app": { diff --git a/test/test_python_environment.py b/test/test_python_environment.py index 744f4947..fe0baa13 100644 --- a/test/test_python_environment.py +++ b/test/test_python_environment.py @@ -2,7 +2,7 @@ from unit.applications.lang.python import TestApplicationPython class TestPythonEnvironment(TestApplicationPython): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} def test_python_environment_name_null(self): self.load('environment') diff --git a/test/test_python_procman.py b/test/test_python_procman.py index b0c70e53..52d8cacb 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -6,7 +6,7 @@ from unit.applications.lang.python import TestApplicationPython class TestPythonProcman(TestApplicationPython): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} def pids_for_process(self): time.sleep(0.2) diff --git a/test/test_routing.py b/test/test_routing.py index 6073877d..20e3a1c4 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -3,7 +3,7 @@ from unit.applications.proto import TestApplicationProto class TestRouting(TestApplicationProto): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} def setUp(self): super().setUp() diff --git a/test/test_routing_tls.py b/test/test_routing_tls.py index 433a303e..3df2bc82 100644 --- a/test/test_routing_tls.py +++ b/test/test_routing_tls.py @@ -2,7 +2,7 @@ from unit.applications.tls import TestApplicationTLS class TestRoutingTLS(TestApplicationTLS): - prerequisites = ['python', 'openssl'] + prerequisites = {'modules': ['python', 'openssl']} def test_routes_match_scheme(self): self.certificate() diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 67db8a8e..6f82ae81 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -3,7 +3,7 @@ from unit.applications.lang.ruby import TestApplicationRuby class TestRubyApplication(TestApplicationRuby): - prerequisites = ['ruby'] + prerequisites = {'modules': ['ruby']} def test_ruby_application(self): self.load('variables') diff --git a/test/test_settings.py b/test/test_settings.py index 98063440..6b849558 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -5,7 +5,7 @@ from unit.applications.lang.python import TestApplicationPython class TestSettings(TestApplicationPython): - prerequisites = ['python'] + prerequisites = {'modules': ['python']} def test_settings_header_read_timeout(self): self.load('empty') diff --git a/test/test_static.py b/test/test_static.py new file mode 100644 index 00000000..573669a3 --- /dev/null +++ b/test/test_static.py @@ -0,0 +1,376 @@ +import os +import unittest +from unit.applications.proto import TestApplicationProto + + +class TestStatic(TestApplicationProto): + prerequisites = {} + + def setUp(self): + super().setUp() + + os.makedirs(self.testdir + '/assets/dir') + with open(self.testdir + '/assets/index.html', 'w') as index, \ + open(self.testdir + '/assets/README', 'w') as readme, \ + open(self.testdir + '/assets/log.log', 'w') as log, \ + open(self.testdir + '/assets/dir/file', 'w') as file: + index.write('0123456789') + readme.write('readme') + log.write('[debug]') + file.write('blah') + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": self.testdir + "/assets"}}], + "settings": { + "http": { + "static": { + "mime_types": {"text/plain": [".log", "README"]} + } + } + }, + } + ) + + def test_static_index(self): + self.assertEqual( + self.get(url='/index.html')['body'], '0123456789', 'index' + ) + self.assertEqual(self.get(url='/')['body'], '0123456789', 'index 2') + self.assertEqual( + self.get(url='/dir/')['status'], 404, 'index not found' + ) + + resp = self.get(url='/index.html/') + self.assertEqual(resp['status'], 404, 'index not found 2 status') + self.assertEqual( + resp['headers']['Content-Type'], + 'text/html', + 'index not found 2 Content-Type', + ) + + def test_static_large_file(self): + file_size = 32 * 1024 * 1024 + with open(self.testdir + '/assets/large', 'wb') as f: + f.seek(file_size - 1) + f.write(b'\0') + + self.assertEqual( + len( + self.get(url='/large', read_buffer_size=1024 * 1024)['body'] + ), + file_size, + 'large file', + ) + + def test_static_etag(self): + etag = self.get(url='/')['headers']['ETag'] + etag_2 = self.get(url='/README')['headers']['ETag'] + + self.assertNotEqual(etag, etag_2, 'different ETag') + self.assertEqual( + etag, self.get(url='/')['headers']['ETag'], 'same ETag' + ) + + with open(self.testdir + '/assets/index.html', 'w') as f: + f.write('blah') + + self.assertNotEqual( + etag, self.get(url='/')['headers']['ETag'], 'new ETag' + ) + + def test_static_redirect(self): + resp = self.get(url='/dir') + self.assertEqual(resp['status'], 301, 'redirect status') + self.assertEqual( + resp['headers']['Location'], '/dir/', 'redirect Location' + ) + self.assertNotIn( + 'Content-Type', resp['headers'], 'redirect Content-Type' + ) + + def test_static_space_in_name(self): + os.rename( + self.testdir + '/assets/dir/file', + self.testdir + '/assets/dir/fi le', + ) + self.waitforfiles(self.testdir + '/assets/dir/fi le') + self.assertEqual( + self.get(url='/dir/fi le')['body'], 'blah', 'file name' + ) + + os.rename(self.testdir + '/assets/dir', self.testdir + '/assets/di r') + self.waitforfiles(self.testdir + '/assets/di r/fi le') + self.assertEqual( + self.get(url='/di r/fi le')['body'], 'blah', 'dir name' + ) + + os.rename( + self.testdir + '/assets/di r', self.testdir + '/assets/ di r ' + ) + self.waitforfiles(self.testdir + '/assets/ di r /fi le') + self.assertEqual( + self.get(url='/ di r /fi le')['body'], 'blah', 'dir name enclosing' + ) + + self.assertEqual( + self.get(url='/%20di%20r%20/fi le')['body'], 'blah', 'dir encoded' + ) + self.assertEqual( + self.get(url='/ di r %2Ffi le')['body'], 'blah', 'slash encoded' + ) + self.assertEqual( + self.get(url='/ di r /fi%20le')['body'], 'blah', 'file encoded' + ) + self.assertEqual( + self.get(url='/%20di%20r%20%2Ffi%20le')['body'], 'blah', 'encoded' + ) + self.assertEqual( + self.get(url='/%20%64%69%20%72%20%2F%66%69%20%6C%65')['body'], + 'blah', + 'encoded 2', + ) + + os.rename( + self.testdir + '/assets/ di r /fi le', + self.testdir + '/assets/ di r / fi le ', + ) + self.waitforfiles(self.testdir + '/assets/ di r / fi le ') + self.assertEqual( + self.get(url='/%20di%20r%20/%20fi%20le%20')['body'], + 'blah', + 'file name enclosing', + ) + + try: + print('файл') + utf8 = True + + except: + utf8 = False + + if utf8: + os.rename( + self.testdir + '/assets/ di r / fi le ', + self.testdir + '/assets/ di r /фа йл', + ) + self.waitforfiles(self.testdir + '/assets/ di r /фа йл') + self.assertEqual( + self.get(url='/ di r /фа йл')['body'], 'blah', 'file name 2' + ) + + os.rename( + self.testdir + '/assets/ di r ', + self.testdir + '/assets/ди ректория', + ) + self.waitforfiles(self.testdir + '/assets/ди ректория/фа йл') + self.assertEqual( + self.get(url='/ди ректория/фа йл')['body'], 'blah', 'dir name 2' + ) + + def test_static_head(self): + resp = self.head(url='/') + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], '', 'empty body') + + def test_static_two_clients(self): + _, sock = self.get(url='/', start=True, no_recv=True) + _, sock2 = self.get(url='/', start=True, no_recv=True) + + self.assertEqual(sock.recv(1), b'H', 'client 1') + self.assertEqual(sock2.recv(1), b'H', 'client 2') + self.assertEqual(sock.recv(1), b'T', 'client 1 again') + self.assertEqual(sock2.recv(1), b'T', 'client 2 again') + + sock.close() + sock2.close() + + def test_static_mime_types(self): + self.assertIn( + 'success', + self.conf( + { + "text/x-code/x-blah/x-blah": "readme", + "text/plain": [".html", ".log", "file"], + }, + 'settings/http/static/mime_types', + ), + 'configure mime_types', + ) + + self.assertEqual( + self.get(url='/README')['headers']['Content-Type'], + 'text/x-code/x-blah/x-blah', + 'mime_types string case insensitive', + ) + self.assertEqual( + self.get(url='/index.html')['headers']['Content-Type'], + 'text/plain', + 'mime_types html', + ) + self.assertEqual( + self.get(url='/')['headers']['Content-Type'], + 'text/plain', + 'mime_types index default', + ) + self.assertEqual( + self.get(url='/dir/file')['headers']['Content-Type'], + 'text/plain', + 'mime_types file in dir', + ) + + def test_static_mime_types_partial_match(self): + self.assertIn( + 'success', + self.conf( + { + "text/x-blah": ["ile", "fil", "f", "e", ".file"], + }, + 'settings/http/static/mime_types', + ), + 'configure mime_types', + ) + self.assertNotIn( + 'Content-Type', self.get(url='/dir/file'), 'partial match' + ) + + def test_static_mime_types_reconfigure(self): + self.assertIn( + 'success', + self.conf( + { + "text/x-code": "readme", + "text/plain": [".html", ".log", "file"], + }, + 'settings/http/static/mime_types', + ), + 'configure mime_types', + ) + + self.assertEqual( + self.conf_get('settings/http/static/mime_types'), + {'text/x-code': 'readme', 'text/plain': ['.html', '.log', 'file']}, + 'mime_types get', + ) + self.assertEqual( + self.conf_get('settings/http/static/mime_types/text%2Fx-code'), + 'readme', + 'mime_types get string', + ) + self.assertEqual( + self.conf_get('settings/http/static/mime_types/text%2Fplain'), + ['.html', '.log', 'file'], + 'mime_types get array', + ) + self.assertEqual( + self.conf_get('settings/http/static/mime_types/text%2Fplain/1'), + '.log', + 'mime_types get array element', + ) + + self.assertIn( + 'success', + self.conf_delete('settings/http/static/mime_types/text%2Fplain/2'), + 'mime_types remove array element', + ) + self.assertNotIn( + 'Content-Type', + self.get(url='/dir/file')['headers'], + 'mime_types removed', + ) + + self.assertIn( + 'success', + self.conf_post( + '"file"', 'settings/http/static/mime_types/text%2Fplain' + ), + 'mime_types add array element', + ) + self.assertEqual( + self.get(url='/dir/file')['headers']['Content-Type'], + 'text/plain', + 'mime_types reverted', + ) + + self.assertIn( + 'success', + self.conf( + '"file"', 'settings/http/static/mime_types/text%2Fplain' + ), + 'configure mime_types update', + ) + self.assertEqual( + self.get(url='/dir/file')['headers']['Content-Type'], + 'text/plain', + 'mime_types updated', + ) + self.assertNotIn( + 'Content-Type', + self.get(url='/log.log')['headers'], + 'mime_types updated 2', + ) + + self.assertIn( + 'success', + self.conf( + '".log"', 'settings/http/static/mime_types/text%2Fblahblahblah' + ), + 'configure mime_types create', + ) + self.assertEqual( + self.get(url='/log.log')['headers']['Content-Type'], + 'text/blahblahblah', + 'mime_types create', + ) + + def test_static_mime_types_correct(self): + self.assertIn( + 'error', + self.conf( + {"text/x-code": "readme", "text/plain": "readme"}, + 'settings/http/static/mime_types', + ), + 'mime_types same extensions', + ) + self.assertIn( + 'error', + self.conf( + {"text/x-code": [".h", ".c"], "text/plain": ".c"}, + 'settings/http/static/mime_types', + ), + 'mime_types same extensions array', + ) + self.assertIn( + 'error', + self.conf( + { + "text/x-code": [".h", ".c", "readme"], + "text/plain": "README", + }, + 'settings/http/static/mime_types', + ), + 'mime_types same extensions case insensitive', + ) + + @unittest.skip('not yet') + def test_static_mime_types_invalid(self): + self.assertIn( + 'error', + self.http( + b"""PUT /config/settings/http/static/mime_types/%0%00% HTTP/1.1\r +Host: localhost\r +Connection: close\r +Content-Length: 6\r +\r +\"blah\"""", + raw_resp=True, + raw=True, + sock_type='unix', + addr=self.testdir + '/control.unit.sock', + ), + 'mime_types invalid', + ) + +if __name__ == '__main__': + TestStatic.main() diff --git a/test/test_tls.py b/test/test_tls.py index 076a2c38..3514bbcb 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -6,7 +6,7 @@ from unit.applications.tls import TestApplicationTLS class TestTLS(TestApplicationTLS): - prerequisites = ['python', 'openssl'] + prerequisites = {'modules': ['python', 'openssl']} def findall(self, pattern): with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index e4ab8ffa..15ac1cd9 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -4,12 +4,22 @@ from unit.applications.proto import TestApplicationProto class TestApplicationGo(TestApplicationProto): - def load(self, script, name='app'): + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) - if not os.path.isdir(self.testdir + '/go'): - os.mkdir(self.testdir + '/go') + # check go module + + go_app = TestApplicationGo() + go_app.testdir = unit.testdir + if go_app.prepare_env('empty', 'app').returncode == 0: + cls.available['modules']['go'] = [] - go_app_path = self.current_dir + '/go/' + return unit if not complete_check else unit.complete() + + def prepare_env(self, script, name): + if not os.path.exists(self.testdir + '/go'): + os.mkdir(self.testdir + '/go') env = os.environ.copy() env['GOPATH'] = self.pardir + '/go' @@ -19,12 +29,18 @@ class TestApplicationGo(TestApplicationProto): 'build', '-o', self.testdir + '/go/' + name, - go_app_path + script + '/' + name + '.go', + self.current_dir + '/go/' + script + '/' + name + '.go', ], env=env, ) + process.communicate() + return process + + def load(self, script, name='app'): + self.prepare_env(script, name) + self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + script}}, @@ -32,8 +48,10 @@ class TestApplicationGo(TestApplicationProto): script: { "type": "external", "processes": {"spare": 0}, - "working_directory": go_app_path + script, - "executable": self.testdir + '/go/' + name, + "working_directory": self.current_dir + + "/go/" + + script, + "executable": self.testdir + "/go/" + name, } }, } diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index ec1c95d9..40bf3662 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -1,4 +1,5 @@ import os +import glob import shutil from subprocess import Popen from unit.applications.proto import TestApplicationProto @@ -6,11 +7,9 @@ from unit.applications.proto import TestApplicationProto class TestApplicationJava(TestApplicationProto): def load(self, script, name='app'): - app_path = self.testdir + '/java' web_inf_path = app_path + '/WEB-INF/' classes_path = web_inf_path + 'classes/' - script_path = self.current_dir + '/java/' + script + '/' if not os.path.isdir(app_path): @@ -19,39 +18,48 @@ class TestApplicationJava(TestApplicationProto): src = [] for f in os.listdir(script_path): + file_path = script_path + f + if f.endswith('.java'): - src.append(script_path + f) + src.append(file_path) continue if f.startswith('.') or f == 'Makefile': continue - if os.path.isdir(script_path + f): + if os.path.isdir(file_path): if f == 'WEB-INF': continue - shutil.copytree(script_path + f, app_path + '/' + f) + shutil.copytree(file_path, app_path + '/' + f) continue if f == 'web.xml': if not os.path.isdir(web_inf_path): os.makedirs(web_inf_path) - shutil.copy2(script_path + f, web_inf_path) + shutil.copy2(file_path, web_inf_path) else: - shutil.copy2(script_path + f, app_path) + shutil.copy2(file_path, app_path) if src: if not os.path.isdir(classes_path): os.makedirs(classes_path) - tomcat_jar = self.pardir + '/build/tomcat-servlet-api-9.0.13.jar' + classpath = self.pardir + '/build/tomcat-servlet-api-9.0.13.jar' + + ws_jars = glob.glob( + self.pardir + '/build/websocket-api-java-*.jar' + ) + + if not ws_jars: + self.fail('websocket api jar not found.') javac = [ 'javac', '-encoding', 'utf-8', '-d', classes_path, - '-classpath', tomcat_jar, + '-classpath', classpath + ':' + ws_jars[0], ] javac.extend(src) diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index 931c6596..3cc72669 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -4,8 +4,18 @@ from unit.applications.proto import TestApplicationProto class TestApplicationNode(TestApplicationProto): - def load(self, script, name='app.js'): + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + # check node module + + if os.path.exists(unit.pardir + '/node/node_modules'): + cls.available['modules']['node'] = [] + return unit if not complete_check else unit.complete() + + def load(self, script, name='app.js'): # copy application shutil.copytree( diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 6e8deefb..1290279d 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -1,4 +1,5 @@ import os +import re import ssl import subprocess from unit.applications.proto import TestApplicationProto @@ -12,6 +13,27 @@ class TestApplicationTLS(TestApplicationProto): self.context.check_hostname = False self.context.verify_mode = ssl.CERT_NONE + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + # check tls module + + try: + subprocess.check_output(['which', 'openssl']) + + output = subprocess.check_output( + [unit.unitd, '--version'], stderr=subprocess.STDOUT + ) + + if re.search('--openssl', output.decode()): + cls.available['modules']['openssl'] = [] + + except: + pass + + return unit if not complete_check else unit.complete() + def certificate(self, name='default', load=True): self.openssl_conf() diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index 417e9504..50ff2797 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -54,24 +54,16 @@ class TestApplicationWebsocket(TestApplicationProto): def apply_mask(self, data, mask): return bytes(b ^ m for b, m in zip(data, itertools.cycle(mask))) - def serialize_close(self, code = 1000, reason = ''): + def serialize_close(self, code=1000, reason=''): return struct.pack('!H', code) + reason.encode('utf-8') - def frame_read(self, sock, read_timeout=10): + def frame_read(self, sock, read_timeout=30): def recv_bytes(sock, bytes): data = b'' while select.select([sock], [], [], read_timeout)[0]: - try: - if bytes < 65536: - data = sock.recv(bytes) - else: - data = self.recvall( - sock, - read_timeout=read_timeout, - buff_size=bytes, - ) - break - except: + data += sock.recv(bytes - len(data)) + + if len(data) == bytes: break return data @@ -99,7 +91,11 @@ class TestApplicationWebsocket(TestApplicationProto): if frame['mask']: mask_bits = recv_bytes(sock, 4) - data = recv_bytes(sock, length) + data = b'' + + if length != 0: + data = recv_bytes(sock, length) + if frame['mask']: data = self.apply_mask(data, mask_bits) @@ -175,14 +171,20 @@ class TestApplicationWebsocket(TestApplicationProto): frame = self.frame_to_send(*args, **kwargs) if chopsize is None: - sock.sendall(frame) + try: + sock.sendall(frame) + except BrokenPipeError: + pass else: pos = 0 frame_len = len(frame) - while (pos < frame_len): + while pos < frame_len: end = min(pos + chopsize, frame_len) - sock.sendall(frame[pos:end]) + try: + sock.sendall(frame[pos:end]) + except BrokenPipeError: + end = frame_len pos = end def message(self, sock, type, message, fragmention_size=None, **kwargs): @@ -197,17 +199,19 @@ class TestApplicationWebsocket(TestApplicationProto): pos = 0 op_code = type - while(pos < message_len): + while pos < message_len: end = min(pos + fragmention_size, message_len) - fin = (end == message_len) - self.frame_write(sock, op_code, message[pos:end], fin=fin, **kwargs) + fin = end == message_len + self.frame_write( + sock, op_code, message[pos:end], fin=fin, **kwargs + ) op_code = self.OP_CONT pos = end def message_read(self, sock, read_timeout=10): frame = self.frame_read(sock, read_timeout=read_timeout) - while(not frame['fin']): + while not frame['fin']: temp = self.frame_read(sock, read_timeout=read_timeout) frame['data'] += temp['data'] frame['fin'] = temp['fin'] diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py new file mode 100644 index 00000000..9b06ab3c --- /dev/null +++ b/test/unit/feature/isolation.py @@ -0,0 +1,87 @@ +import os +import json +from unit.applications.proto import TestApplicationProto +from unit.applications.lang.go import TestApplicationGo +from unit.applications.lang.java import TestApplicationJava +from unit.applications.lang.node import TestApplicationNode +from unit.applications.lang.perl import TestApplicationPerl +from unit.applications.lang.php import TestApplicationPHP +from unit.applications.lang.python import TestApplicationPython +from unit.applications.lang.ruby import TestApplicationRuby + + +class TestFeatureIsolation(TestApplicationProto): + allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net'] + + def check(self, available, testdir): + test_conf = {"namespaces": {"credential": True}} + + module = '' + app = 'empty' + if 'go' in available['modules']: + module = TestApplicationGo() + + elif 'java' in available['modules']: + module = TestApplicationJava() + + elif 'node' in available['modules']: + module = TestApplicationNode() + app = 'basic' + + elif 'perl' in available['modules']: + module = TestApplicationPerl() + app = 'body_empty' + + elif 'php' in available['modules']: + module = TestApplicationPHP() + app = 'phpinfo' + + elif 'python' in available['modules']: + module = TestApplicationPython() + + elif 'ruby' in available['modules']: + module = TestApplicationRuby() + + if not module: + return + + module.testdir = testdir + module.load(app) + + resp = module.conf(test_conf, 'applications/' + app + '/isolation') + if 'success' not in resp: + return + + userns = self.getns('user') + if not userns: + return + + available['features']['isolation'] = {'user': userns} + + unp_clone_path = '/proc/sys/kernel/unprivileged_userns_clone' + if os.path.exists(unp_clone_path): + with open(unp_clone_path, 'r') as f: + if str(f.read()).rstrip() == '1': + available['features']['isolation'][ + 'unprivileged_userns_clone' + ] = True + + for ns in self.allns: + ns_value = self.getns(ns) + if ns_value: + available['features']['isolation'][ns] = ns_value + + def getns(self, nstype): + # read namespace id from symlink file: + # it points to: '<nstype>:[<ns id>]' + # # eg.: 'pid:[4026531836]' + nspath = '/proc/self/ns/' + nstype + data = None + + if os.path.exists(nspath): + data = int(os.readlink(nspath)[len(nstype) + 2 : -1]) + + return data + + def parsejson(self, data): + return json.loads(data.split('\n')[1]) diff --git a/test/unit/http.py b/test/unit/http.py index c0af8a9e..82a6bd6a 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -12,6 +12,11 @@ class TestHTTP(TestUnit): port = 7080 if 'port' not in kwargs else kwargs['port'] url = '/' if 'url' not in kwargs else kwargs['url'] http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1' + read_buffer_size = ( + 4096 + if 'read_buffer_size' not in kwargs + else kwargs['read_buffer_size'] + ) headers = ( {'Host': 'localhost', 'Connection': 'close'} @@ -94,7 +99,9 @@ class TestHTTP(TestUnit): read_timeout = ( 30 if 'read_timeout' not in kwargs else kwargs['read_timeout'] ) - resp = self.recvall(sock, read_timeout=read_timeout).decode(enc) + resp = self.recvall( + sock, read_timeout=read_timeout, buff_size=read_buffer_size + ).decode(enc) if TestUnit.detailed: print('<<<') @@ -118,6 +125,9 @@ class TestHTTP(TestUnit): def get(self, **kwargs): return self.http('GET', **kwargs) + def head(self, **kwargs): + return self.http('HEAD', **kwargs) + def post(self, **kwargs): return self.http('POST', **kwargs) diff --git a/test/unit/main.py b/test/unit/main.py index 6a167a9e..873f1815 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -12,8 +12,6 @@ import subprocess from multiprocessing import Process -available_modules = {} - class TestUnit(unittest.TestCase): current_dir = os.path.abspath( @@ -28,6 +26,7 @@ class TestUnit(unittest.TestCase): detailed = False save_log = False + unsafe = False def __init__(self, methodName='runTest'): super().__init__(methodName) @@ -41,10 +40,12 @@ class TestUnit(unittest.TestCase): if not hasattr(self, 'application_type'): return super().run(result) + # rerun test for each available module version + type = self.application_type - for prerequisite in self.prerequisites: - if prerequisite in available_modules: - for version in available_modules[prerequisite]: + for module in self.prerequisites['modules']: + if module in self.available['modules']: + for version in self.available['modules'][module]: self.application_type = type + ' ' + version super().run(result) @@ -63,8 +64,83 @@ class TestUnit(unittest.TestCase): unittest.main() @classmethod - def setUpClass(cls): - TestUnit().check_modules(*cls.prerequisites) + def setUpClass(cls, complete_check=True): + cls.available = {'modules': {}, 'features': {}} + unit = TestUnit() + + unit._run() + + # read unit.log + + for i in range(50): + with open(unit.testdir + '/unit.log', 'r') as f: + log = f.read() + m = re.search('controller started', log) + + if m is None: + time.sleep(0.1) + else: + break + + if m is None: + unit.stop() + exit("Unit is writing log too long") + + # discover available modules from unit.log + + for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M): + if module[0] not in cls.available['modules']: + cls.available['modules'][module[0]] = [module[1]] + else: + cls.available['modules'][module[0]].append(module[1]) + + def check(available, prerequisites): + missed = [] + + # check modules + + if 'modules' in prerequisites: + available_modules = list(available['modules'].keys()) + + for module in prerequisites['modules']: + if module in available_modules: + continue + + missed.append(module) + + if missed: + print('Unit has no ' + ', '.join(missed) + ' module(s)') + raise unittest.SkipTest() + + # check features + + if 'features' in prerequisites: + available_features = list(available['features'].keys()) + + for feature in prerequisites['features']: + if feature in available_features: + continue + + missed.append(feature) + + if missed: + print(', '.join(missed) + ' feature(s) not supported') + raise unittest.SkipTest() + + def destroy(): + unit.stop() + unit._check_alerts(log) + shutil.rmtree(unit.testdir) + + def complete(): + destroy() + check(cls.available, cls.prerequisites) + + if complete_check: + complete() + else: + unit.complete = complete + return unit def setUp(self): self._run() @@ -105,92 +181,6 @@ class TestUnit(unittest.TestCase): else: self._print_path_to_log() - def check_modules(self, *modules): - self._run() - - for i in range(50): - with open(self.testdir + '/unit.log', 'r') as f: - log = f.read() - m = re.search('controller started', log) - - if m is None: - time.sleep(0.1) - else: - break - - if m is None: - self.stop() - exit("Unit is writing log too long") - - # discover all available modules - - global available_modules - available_modules = {} - for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M): - if module[0] not in available_modules: - available_modules[module[0]] = [module[1]] - else: - available_modules[module[0]].append(module[1]) - - missed_module = '' - for module in modules: - if module == 'go': - env = os.environ.copy() - env['GOPATH'] = self.pardir + '/go' - - try: - process = subprocess.Popen( - [ - 'go', - 'build', - '-o', - self.testdir + '/go/check_module', - self.current_dir + '/go/empty/app.go', - ], - env=env, - ) - process.communicate() - - m = module if process.returncode == 0 else None - - except: - m = None - - elif module == 'node': - if os.path.isdir(self.pardir + '/node/node_modules'): - m = module - else: - m = None - - elif module == 'openssl': - try: - subprocess.check_output(['which', 'openssl']) - - output = subprocess.check_output( - [self.unitd, '--version'], - stderr=subprocess.STDOUT, - ) - - m = re.search('--openssl', output.decode()) - - except: - m = None - - else: - if module not in available_modules: - m = None - - if m is None: - missed_module = module - break - - self.stop() - self._check_alerts(log) - shutil.rmtree(self.testdir) - - if missed_module: - raise unittest.SkipTest('Unit has no ' + missed_module + ' module') - def stop(self): if self._started: self._stop() @@ -350,6 +340,8 @@ class TestUnit(unittest.TestCase): TestUnit.save_log = args.save_log TestUnit.unsafe = args.unsafe + # set stdout to non-blocking + if TestUnit.detailed: fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0) |