diff options
Diffstat (limited to 'test')
44 files changed, 2946 insertions, 798 deletions
diff --git a/test/go/404/app.go b/test/go/404/app.go index abb33066..08fe56c9 100644 --- a/test/go/404/app.go +++ b/test/go/404/app.go @@ -1,22 +1,22 @@ package main import ( - "io" - "io/ioutil" - "net/http" - "nginx/unit" + "io" + "io/ioutil" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - b, e := ioutil.ReadFile("404.html") + b, e := ioutil.ReadFile("404.html") - if e == nil { - w.WriteHeader(http.StatusNotFound) - io.WriteString(w, string(b)) - } + if e == nil { + w.WriteHeader(http.StatusNotFound) + io.WriteString(w, string(b)) + } } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/command_line_arguments/app.go b/test/go/command_line_arguments/app.go index 228e07c0..234e565e 100644 --- a/test/go/command_line_arguments/app.go +++ b/test/go/command_line_arguments/app.go @@ -1,23 +1,23 @@ package main import ( - "io" - "os" - "fmt" - "strings" - "net/http" - "nginx/unit" + "fmt" + "io" + "net/http" + "nginx/unit" + "os" + "strings" ) func handler(w http.ResponseWriter, r *http.Request) { - args := strings.Join(os.Args[1:], ",") + args := strings.Join(os.Args[1:], ",") - w.Header().Add("X-Arg-0", fmt.Sprintf("%v", os.Args[0])) - w.Header().Add("Content-Length", fmt.Sprintf("%v", len(args))) - io.WriteString(w, args) + w.Header().Add("X-Arg-0", fmt.Sprintf("%v", os.Args[0])) + w.Header().Add("Content-Length", fmt.Sprintf("%v", len(args))) + io.WriteString(w, args) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/cookies/app.go b/test/go/cookies/app.go index 6fb9def0..e6647ea8 100644 --- a/test/go/cookies/app.go +++ b/test/go/cookies/app.go @@ -1,19 +1,19 @@ package main import ( - "net/http" - "nginx/unit" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - cookie1, _ := r.Cookie("var1") - cookie2, _ := r.Cookie("var2") + cookie1, _ := r.Cookie("var1") + cookie2, _ := r.Cookie("var2") - w.Header().Set("X-Cookie-1", cookie1.Value) - w.Header().Set("X-Cookie-2", cookie2.Value) + w.Header().Set("X-Cookie-1", cookie1.Value) + w.Header().Set("X-Cookie-2", cookie2.Value) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/empty/app.go b/test/go/empty/app.go index 2e07405f..6e0fce1b 100644 --- a/test/go/empty/app.go +++ b/test/go/empty/app.go @@ -1,13 +1,13 @@ package main import ( - "net/http" - "nginx/unit" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) {} func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/get_variables/app.go b/test/go/get_variables/app.go index 563febc8..4dcc0e7b 100644 --- a/test/go/get_variables/app.go +++ b/test/go/get_variables/app.go @@ -1,17 +1,17 @@ package main import ( - "net/http" - "nginx/unit" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Var-1", r.URL.Query().Get("var1")) - w.Header().Set("X-Var-2", r.URL.Query().Get("var2")) - w.Header().Set("X-Var-3", r.URL.Query().Get("var3")) + w.Header().Set("X-Var-1", r.URL.Query().Get("var1")) + w.Header().Set("X-Var-2", r.URL.Query().Get("var2")) + w.Header().Set("X-Var-3", r.URL.Query().Get("var3")) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/mirror/app.go b/test/go/mirror/app.go index 82b1c92d..748aa7ee 100644 --- a/test/go/mirror/app.go +++ b/test/go/mirror/app.go @@ -1,21 +1,21 @@ package main import ( - "io" - "fmt" - "net/http" - "nginx/unit" + "fmt" + "io" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - var buf [32768]byte; - len, _ := r.Body.Read(buf[:]) + var buf [32768]byte + len, _ := r.Body.Read(buf[:]) - w.Header().Add("Content-Length", fmt.Sprintf("%v", len)) - io.WriteString(w, string(buf[:len])) + w.Header().Add("Content-Length", fmt.Sprintf("%v", len)) + io.WriteString(w, string(buf[:len])) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/post_variables/app.go b/test/go/post_variables/app.go index 433afc62..947976d2 100644 --- a/test/go/post_variables/app.go +++ b/test/go/post_variables/app.go @@ -1,19 +1,19 @@ package main import ( - "net/http" - "nginx/unit" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - r.ParseForm() + r.ParseForm() - w.Header().Set("X-Var-1", r.Form.Get("var1")) - w.Header().Set("X-Var-2", r.Form.Get("var2")) - w.Header().Set("X-Var-3", r.Form.Get("var3")) + w.Header().Set("X-Var-1", r.Form.Get("var1")) + w.Header().Set("X-Var-2", r.Form.Get("var2")) + w.Header().Set("X-Var-3", r.Form.Get("var3")) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/variables/app.go b/test/go/variables/app.go index 5db4ac67..fdcbf7e8 100644 --- a/test/go/variables/app.go +++ b/test/go/variables/app.go @@ -1,30 +1,30 @@ package main import ( - "io" - "fmt" - "net/http" - "nginx/unit" + "fmt" + "io" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - var buf [4096]byte; - len, _ := r.Body.Read(buf[:]) + var buf [4096]byte + len, _ := r.Body.Read(buf[:]) - w.Header().Set("Request-Method", r.Method) - w.Header().Set("Request-Uri", r.RequestURI) - w.Header().Set("Server-Protocol", r.Proto) - w.Header().Set("Server-Protocol-Major", fmt.Sprintf("%v", r.ProtoMajor)) - w.Header().Set("Server-Protocol-Minor", fmt.Sprintf("%v", r.ProtoMinor)) - w.Header().Set("Content-Length", fmt.Sprintf("%v", len)) - w.Header().Set("Content-Type", r.Header.Get("Content-Type")) - w.Header().Set("Custom-Header", r.Header.Get("Custom-Header")) - w.Header().Set("Http-Host", r.Header.Get("Host")) + w.Header().Set("Request-Method", r.Method) + w.Header().Set("Request-Uri", r.RequestURI) + w.Header().Set("Server-Protocol", r.Proto) + w.Header().Set("Server-Protocol-Major", fmt.Sprintf("%v", r.ProtoMajor)) + w.Header().Set("Server-Protocol-Minor", fmt.Sprintf("%v", r.ProtoMinor)) + w.Header().Set("Content-Length", fmt.Sprintf("%v", len)) + w.Header().Set("Content-Type", r.Header.Get("Content-Type")) + w.Header().Set("Custom-Header", r.Header.Get("Custom-Header")) + w.Header().Set("Http-Host", r.Header.Get("Host")) - io.WriteString(w, string(buf[:len])) + io.WriteString(w, string(buf[:len])) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/java/empty_war/empty.war b/test/java/empty_war/empty.war Binary files differnew file mode 100644 index 00000000..4985e804 --- /dev/null +++ b/test/java/empty_war/empty.war diff --git a/test/java/multipart/app.java b/test/java/multipart/app.java new file mode 100644 index 00000000..c4c89ffb --- /dev/null +++ b/test/java/multipart/app.java @@ -0,0 +1,93 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import java.util.Map; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.annotation.MultipartConfig; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import javax.servlet.http.Part; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +@MultipartConfig( + fileSizeThreshold = 1024 * 1024 * 1, // 1 MB + maxFileSize = 1024 * 1024 * 10, // 10 MB + maxRequestSize = 1024 * 1024 * 15 // 15 MB +) +public class app extends HttpServlet +{ + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.setContentType("text/html;charset=UTF-8"); + + // Create path components to save the file + final String path = request.getParameter("destination"); + final Part filePart = request.getPart("file"); + final String fileName = getFileName(filePart); + + OutputStream out = null; + InputStream filecontent = null; + final PrintWriter writer = response.getWriter(); + + try { + out = new FileOutputStream(new File(path + File.separator + + fileName)); + filecontent = filePart.getInputStream(); + + int read = 0; + final byte[] bytes = new byte[1024]; + + while ((read = filecontent.read(bytes)) != -1) { + out.write(bytes, 0, read); + } + writer.println(fileName + " created at " + path); + + } catch (FileNotFoundException fne) { + writer.println("You either did not specify a file to upload or are " + + "trying to upload a file to a protected or nonexistent " + + "location."); + writer.println("<br/> ERROR: " + fne.getMessage()); + + } finally { + if (out != null) { + out.close(); + } + if (filecontent != null) { + filecontent.close(); + } + if (writer != null) { + writer.close(); + } + } + + return; + } + + private String getFileName(final Part part) { + final String partHeader = part.getHeader("content-disposition"); + + for (String content : part.getHeader("content-disposition").split(";")) + { + if (content.trim().startsWith("filename")) { + return content.substring( + content.indexOf("=") + 1).trim().replace("\"", ""); + } + } + return null; + } +} diff --git a/test/java/session_inactive/app.java b/test/java/session_inactive/app.java index f338fc89..618e4d67 100644 --- a/test/java/session_inactive/app.java +++ b/test/java/session_inactive/app.java @@ -17,7 +17,13 @@ public class app extends HttpServlet HttpSession s = request.getSession(); if (s.isNew()) { - s.setMaxInactiveInterval(2); + String interval = request.getHeader("X-Interval"); + + if (interval == null) { + s.setMaxInactiveInterval(0); + } else { + s.setMaxInactiveInterval(Integer.parseInt(interval)); + } } response.addHeader("X-Session-Id", s.getId()); diff --git a/test/node/404/app.js b/test/node/404/app.js index 9600d486..587c432d 100755 --- a/test/node/404/app.js +++ b/test/node/404/app.js @@ -3,6 +3,5 @@ var fs = require('fs'); require('unit-http').createServer(function (req, res) { - res.writeHead(404, {}); - res.end(fs.readFileSync('404.html')); + res.writeHead(404, {}).end(fs.readFileSync('404.html')); }).listen(7080); diff --git a/test/node/basic/app.js b/test/node/basic/app.js index bc8d570a..7820c474 100755 --- a/test/node/basic/app.js +++ b/test/node/basic/app.js @@ -1,6 +1,6 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}); - res.end('Hello World\n'); + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); }).listen(7080); diff --git a/test/node/double_end/app.js b/test/node/double_end/app.js index d8280917..63912097 100755 --- a/test/node/double_end/app.js +++ b/test/node/double_end/app.js @@ -1,6 +1,5 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.end(); - res.end(); + res.end().end(); }).listen(7080); diff --git a/test/node/mirror/app.js b/test/node/mirror/app.js index abcb87cb..1488917e 100755 --- a/test/node/mirror/app.js +++ b/test/node/mirror/app.js @@ -6,7 +6,7 @@ require('unit-http').createServer(function (req, res) { body += chunk.toString(); }); req.on('end', () => { - res.writeHead(200, {'Content-Length': Buffer.byteLength(body)}); - res.end(body); + res.writeHead(200, {'Content-Length': Buffer.byteLength(body)}) + .end(body); }); }).listen(7080); diff --git a/test/node/promise_handler/app.js b/test/node/promise_handler/app.js index 60b0c3bb..51c3666b 100755 --- a/test/node/promise_handler/app.js +++ b/test/node/promise_handler/app.js @@ -6,8 +6,7 @@ require('unit-http').createServer(function (req, res) { res.end(); if (req.headers['x-write-call']) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.write('blah'); + res.writeHead(200, {'Content-Type': 'text/plain'}).write('blah'); } Promise.resolve().then(() => { diff --git a/test/node/status_message/app.js b/test/node/status_message/app.js index 4f3b064a..e8a798dd 100755 --- a/test/node/status_message/app.js +++ b/test/node/status_message/app.js @@ -1,6 +1,5 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.writeHead(200, 'blah', {'Content-Type': 'text/plain'}); - res.end(); + res.writeHead(200, 'blah', {'Content-Type': 'text/plain'}).end(); }).listen(7080); diff --git a/test/node/variables/app.js b/test/node/variables/app.js index 4ed94d09..d8cdc20c 100755 --- a/test/node/variables/app.js +++ b/test/node/variables/app.js @@ -14,7 +14,6 @@ require('unit-http').createServer(function (req, res) { res.setHeader('Content-Type', req.headers['content-type']); res.setHeader('Custom-Header', req.headers['custom-header']); res.setHeader('Http-Host', req.headers['host']); - res.writeHead(200, {}); - res.end(body); + res.writeHead(200, {}).end(body); }); }).listen(7080); diff --git a/test/node/websockets/mirror/app.js b/test/node/websockets/mirror/app.js new file mode 100755 index 00000000..23746465 --- /dev/null +++ b/test/node/websockets/mirror/app.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +server = require('unit-http').createServer(function() {}); +webSocketServer = require('unit-http/websocket').server; +//server = require('http').createServer(function() {}); +//webSocketServer = require('websocket').server; + +server.listen(7080, function() {}); + +var wsServer = new webSocketServer({ + maxReceivedMessageSize: 0x1000000000, + maxReceivedFrameSize: 0x1000000000, + fragmentOutgoingMessages: false, + fragmentationThreshold: 0x1000000000, + httpServer: server, +}); + +wsServer.on('request', function(request) { + var connection = request.accept(null); + + connection.on('message', function(message) { + if (message.type === 'utf8') { + connection.send(message.utf8Data); + } else if (message.type === 'binary') { + connection.send(message.binaryData); + } + + }); + + connection.on('close', function(r) {}); +}); diff --git a/test/node/websockets/mirror_fragmentation/app.js b/test/node/websockets/mirror_fragmentation/app.js new file mode 100755 index 00000000..7024252a --- /dev/null +++ b/test/node/websockets/mirror_fragmentation/app.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +server = require('unit-http').createServer(function() {}); +webSocketServer = require('unit-http/websocket').server; +//server = require('http').createServer(function() {}); +//webSocketServer = require('websocket').server; + +server.listen(7080, function() {}); + +var wsServer = new webSocketServer({ + httpServer: server +}); + +wsServer.on('request', function(request) { + //console.log('request'); + var connection = request.accept(null); + + connection.on('message', function(message) { + //console.log('message'); + connection.send(message.utf8Data); + }); + + connection.on('close', function(r) { + //console.log('close'); + }); +}); diff --git a/test/node/write_before_write_head/app.js b/test/node/write_before_write_head/app.js index 6e3fb9a9..724b0efb 100755 --- a/test/node/write_before_write_head/app.js +++ b/test/node/write_before_write_head/app.js @@ -2,6 +2,5 @@ require('unit-http').createServer(function (req, res) { res.write('blah'); - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end(); + res.writeHead(200, {'Content-Type': 'text/plain'}).end(); }).listen(7080); diff --git a/test/node/write_buffer/app.js b/test/node/write_buffer/app.js index f41de2a1..a7623523 100755 --- a/test/node/write_buffer/app.js +++ b/test/node/write_buffer/app.js @@ -1,6 +1,6 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end(new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])); + res.writeHead(200, {'Content-Type': 'text/plain'}) + .end(new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])); }).listen(7080); diff --git a/test/node/write_return/app.js b/test/node/write_return/app.js index 3ae967c6..82dfbc6e 100755 --- a/test/node/write_return/app.js +++ b/test/node/write_return/app.js @@ -1,6 +1,6 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end(res.write('body').toString()); + res.writeHead(200, {'Content-Type': 'text/plain'}) + .end(res.write('body').toString()); }).listen(7080); diff --git a/test/php/header/index.php b/test/php/header/index.php new file mode 100644 index 00000000..1aa5ca04 --- /dev/null +++ b/test/php/header/index.php @@ -0,0 +1,4 @@ +<?php +header($_SERVER['HTTP_X_HEADER']); +header('Content-Length: 0'); +?> diff --git a/test/php/script/phpinfo.php b/test/php/script/phpinfo.php new file mode 100644 index 00000000..cf608608 --- /dev/null +++ b/test/php/script/phpinfo.php @@ -0,0 +1,3 @@ +<?php +phpinfo(); +?> diff --git a/test/php/variables/index.php b/test/php/variables/index.php index 8f2e3bfc..279efc79 100644 --- a/test/php/variables/index.php +++ b/test/php/variables/index.php @@ -4,6 +4,7 @@ $body = file_get_contents('php://input'); header('Content-Length: ' . strlen($body)); header('Request-Method: ' . $_SERVER['REQUEST_METHOD']); header('Request-Uri: ' . $_SERVER['REQUEST_URI']); +header('Path-Info: ' . $_SERVER['PATH_INFO']); header('Http-Host: ' . $_SERVER['HTTP_HOST']); header('Server-Protocol: ' . $_SERVER['SERVER_PROTOCOL']); header('Server-Software: ' . $_SERVER['SERVER_SOFTWARE']); diff --git a/test/test_access_log.py b/test/test_access_log.py index 49497ad2..fbcc131f 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -180,7 +180,9 @@ Connection: close self.assertEqual(self.post()['status'], 200, 'init') - resp = self.http(b"""GE""", raw=True, read_timeout=5) + resp = self.http(b"""GE""", raw=True, read_timeout=1) + + time.sleep(1) self.stop() @@ -206,7 +208,9 @@ Connection: close self.assertEqual(self.post()['status'], 200, 'init') - resp = self.http(b"""GET / HTTP/1.1""", raw=True, read_timeout=5) + resp = self.http(b"""GET / HTTP/1.1""", raw=True, read_timeout=1) + + time.sleep(1) self.stop() @@ -219,7 +223,9 @@ Connection: close self.assertEqual(self.post()['status'], 200, 'init') - resp = self.http(b"""GET / HTTP/1.1\n""", raw=True, read_timeout=5) + resp = self.http(b"""GET / HTTP/1.1\n""", raw=True, read_timeout=1) + + time.sleep(1) self.stop() diff --git a/test/test_java_application.py b/test/test_java_application.py index 5d0350fa..526be565 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -1,10 +1,52 @@ import time +import unittest from unit.applications.lang.java import TestApplicationJava class TestJavaApplication(TestApplicationJava): prerequisites = ['java'] + def test_java_conf_error(self): + self.skip_alerts.extend( + [ + r'realpath.*failed', + r'failed to apply new conf', + ] + ) + self.assertIn( + 'error', + self.conf( + { + "listeners": {"*:7080": {"pass": "applications/app"}}, + "applications": { + "app": { + "type": "java", + "processes": 1, + "working_directory": self.current_dir + + "/java/empty", + "webapp": self.testdir + "/java", + "unit_jars": self.testdir + "/no_such_dir", + } + }, + } + ), + 'conf error', + ) + + def test_java_war(self): + self.load('empty_war') + + self.assertIn( + 'success', + self.conf( + '"' + self.testdir + '/java/empty.war"', + '/config/applications/empty_war/webapp', + ), + 'configure war', + ) + + self.assertEqual(self.get()['status'], 200, 'war') + def test_java_application_cookies(self): self.load('cookies') @@ -99,12 +141,16 @@ class TestJavaApplication(TestApplicationJava): def test_java_application_session_active(self): self.load('session_inactive') - resp = self.get() + resp = self.get(headers={ + 'X-Interval': '4', + 'Host': 'localhost', + 'Connection': 'close', + }) session_id = resp['headers']['X-Session-Id'] self.assertEqual(resp['status'], 200, 'session init') self.assertEqual( - resp['headers']['X-Session-Interval'], '2', 'session interval' + resp['headers']['X-Session-Interval'], '4', 'session interval' ) self.assertLess( abs( @@ -147,7 +193,7 @@ class TestJavaApplication(TestApplicationJava): resp['headers']['X-Session-Id'], session_id, 'session active 2' ) - time.sleep(1) + time.sleep(2) resp = self.get( headers={ @@ -164,7 +210,11 @@ class TestJavaApplication(TestApplicationJava): def test_java_application_session_inactive(self): self.load('session_inactive') - resp = self.get() + resp = self.get(headers={ + 'X-Interval': '1', + 'Host': 'localhost', + 'Connection': 'close', + }) session_id = resp['headers']['X-Session-Id'] time.sleep(3) @@ -1164,6 +1214,43 @@ class TestJavaApplication(TestApplicationJava): ) self.assertEqual(headers['X-Get-Date'], date, 'get date header') + def test_java_application_multipart(self): + self.load('multipart') + + body = """Preamble. Should be ignored.\r +\r +--12345\r +Content-Disposition: form-data; name="file"; filename="sample.txt"\r +Content-Type: text/plain\r +\r +Data from sample file\r +--12345\r +Content-Disposition: form-data; name="destination"\r +\r +%s\r +--12345\r +Content-Disposition: form-data; name="upload"\r +\r +Upload\r +--12345--\r +\r +Epilogue. Should be ignored.""" % self.testdir + + resp = self.post( + headers={ + 'Content-Type': 'multipart/form-data; boundary=12345', + 'Host': 'localhost', + 'Connection': 'close', + }, + body=body, + ) + + self.assertEqual(resp['status'], 200, 'multipart status') + self.assertRegex(resp['body'], r'sample\.txt created', 'multipart body') + self.assertIsNotNone( + self.search_in_log(r'^Data from sample file$', name='sample.txt'), + 'file created', + ) if __name__ == '__main__': TestJavaApplication.main() diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py new file mode 100644 index 00000000..6652d8c5 --- /dev/null +++ b/test/test_node_websockets.py @@ -0,0 +1,1585 @@ +import time +import struct +import unittest +from unit.applications.lang.node import TestApplicationNode +from unit.applications.websockets import TestApplicationWebsocket + +class TestNodeWebsockets(TestApplicationNode): + prerequisites = ['node'] + + ws = TestApplicationWebsocket() + + 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_node_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_node_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_node_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_node_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_node_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_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') + + message = '0123456789' * 3000 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + + frame = self.ws.frame_read(sock) + data = frame['data'].decode('utf-8') + + frame = self.ws.frame_read(sock) + data += frame['data'].decode('utf-8') + + self.assertEqual(message, data, 'large') + + 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') + + 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_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) + + self.assertEqual(resp['status'], 400, 'upgrade absent') + + def test_node_websockets_handshake_case_insensitive(self): + 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) + + self.assertEqual(resp['status'], 101, 'status') + + @unittest.skip('not yet') + 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) + + self.assertEqual(resp['status'], 400, 'status') + + def test_node_websockets_handshake_version_absent(self): + 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) + + self.assertEqual(resp['status'], 426, 'status') + + @unittest.skip('not yet') + 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) + + 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_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) + + self.assertEqual(resp['status'], 400, 'status') + + def test_node_websockets_handshake_http_10(self): + 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) + + self.assertEqual(resp['status'], 400, 'status') + + def test_node_websockets_handshake_uri_invalid(self): + 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) + + self.assertEqual(resp['status'], 400, 'status') + + def test_node_websockets_protocol_absent(self): + 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) + + 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. + + @unittest.skip('not yet') + def test_node_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.frame_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) + + @unittest.skip('not yet') + def test_node_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.frame_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_node_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_node_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_node_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_node_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_node_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) + + @unittest.skip('not yet') + def test_node_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_node_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) + 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) + 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_node_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_node_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) + + 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_node_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_node_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, read_timeout=5) + 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_node_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.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.close_connection(sock) + + # settings + + def test_node_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_node_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_node_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__': + TestNodeWebsockets.main() diff --git a/test/test_php_application.py b/test/test_php_application.py index 8032e96e..ee2048b5 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -24,6 +24,7 @@ class TestPHPApplication(TestApplicationPHP): 'Connection': 'close', }, body=body, + url='/index.php/blah?var=val' ) self.assertEqual(resp['status'], 200, 'status') @@ -54,7 +55,8 @@ class TestPHPApplication(TestApplicationPHP): 'Connection': 'close', 'Content-Length': str(len(body)), 'Request-Method': 'POST', - 'Request-Uri': '/', + 'Path-Info': '/blah', + 'Request-Uri': '/index.php/blah?var=val', 'Http-Host': 'localhost', 'Server-Protocol': 'HTTP/1.1', 'Custom-Header': 'blah', @@ -102,6 +104,46 @@ class TestPHPApplication(TestApplicationPHP): self.assertEqual(resp['status'], 200, 'status') self.assertNotEqual(resp['body'], '', 'body not empty') + def test_php_application_header_status(self): + self.load('header') + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'X-Header': 'HTTP/1.1 404 Not Found', + } + )['status'], + 404, + 'status', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'X-Header': 'http/1.1 404 Not Found', + } + )['status'], + 404, + 'status case insensitive', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'X-Header': 'HTTP/ 404 Not Found', + } + )['status'], + 404, + 'status version empty', + ) + + def test_php_application_404(self): self.load('404') @@ -420,6 +462,45 @@ class TestPHPApplication(TestApplicationPHP): self.get()['body'], r'012345', 'disable_classes before' ) + def test_php_application_script(self): + self.assertIn( + 'success', self.conf( + { + "listeners": {"*:7080": {"pass": "applications/script"}}, + "applications": { + "script": { + "type": "php", + "processes": {"spare": 0}, + "root": self.current_dir + "/php/script", + "script": "phpinfo.php", + } + }, + } + ), 'configure script' + ) + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'status') + self.assertNotEqual(resp['body'], '', 'body not empty') + + def test_php_application_index_default(self): + self.assertIn( + 'success', self.conf( + { + "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, + "applications": { + "phpinfo": { + "type": "php", + "processes": {"spare": 0}, + "root": self.current_dir + "/php/phpinfo", + } + }, + } + ), 'configure index default' + ) + + self.assertEqual(self.get()['status'], 200, 'status') if __name__ == '__main__': TestPHPApplication.main() diff --git a/test/test_php_basic.py b/test/test_php_basic.py index 02ff81de..0c84f206 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -164,6 +164,32 @@ class TestPHPBasic(TestControl): 'error', self.conf_delete('applications/app'), 'delete app again' ) + def test_php_delete_blocks(self): + self.conf(self.conf_basic) + + self.assertIn( + 'success', + self.conf_delete('listeners'), + 'listeners delete', + ) + + self.assertIn( + 'success', + self.conf_delete('applications'), + 'applications delete', + ) + + self.assertIn( + 'success', + self.conf(self.conf_app, 'applications'), + 'listeners restore', + ) + + self.assertIn( + 'success', + self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners'), + 'applications restore', + ) if __name__ == '__main__': TestPHPBasic.main() diff --git a/test/test_python_basic.py b/test/test_python_basic.py index 9987e886..e63158e5 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -177,6 +177,33 @@ class TestPythonBasic(TestControl): 'error', self.conf_delete('applications/app'), 'delete app again' ) + def test_python_delete_blocks(self): + self.conf(self.conf_basic) + + self.assertIn( + 'success', + self.conf_delete('listeners'), + 'listeners delete', + ) + + self.assertIn( + 'success', + self.conf_delete('applications'), + 'applications delete', + ) + + self.assertIn( + 'success', + self.conf(self.conf_app, 'applications'), + 'listeners restore', + ) + + self.assertIn( + 'success', + self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners'), + 'applications restore', + ) + if __name__ == '__main__': TestPythonBasic.main() diff --git a/test/test_routing.py b/test/test_routing.py index ac2e0de8..6073877d 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -38,6 +38,9 @@ class TestRouting(TestApplicationProto): } ) + def route(self, route): + return self.conf([route], 'routes') + def test_routes_match_method_positive(self): self.assertEqual(self.get()['status'], 200, 'method positive GET') self.assertEqual(self.post()['status'], 404, 'method positive POST') @@ -45,14 +48,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_positive_many(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": ["GET", "POST"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": ["GET", "POST"]}, + "action": {"pass": "applications/empty"}, + } ), 'method positive many configure', ) @@ -68,14 +68,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_negative(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "!GET"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "!GET"}, + "action": {"pass": "applications/empty"}, + } ), 'method negative configure', ) @@ -86,14 +83,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_negative_many(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": ["!GET", "!POST"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": ["!GET", "!POST"]}, + "action": {"pass": "applications/empty"}, + } ), 'method negative many configure', ) @@ -109,14 +103,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_wildcard_left(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*ET"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*ET"}, + "action": {"pass": "applications/empty"}, + } ), 'method wildcard left configure', ) @@ -129,14 +120,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_wildcard_right(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "GE*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "GE*"}, + "action": {"pass": "applications/empty"}, + } ), 'method wildcard right configure', ) @@ -151,14 +139,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_wildcard_left_right(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*GET*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*GET*"}, + "action": {"pass": "applications/empty"}, + } ), 'method wildcard left right configure', ) @@ -173,14 +158,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_wildcard(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*"}, + "action": {"pass": "applications/empty"}, + } ), 'method wildcard configure', ) @@ -190,70 +172,55 @@ class TestRouting(TestApplicationProto): def test_routes_match_invalid(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"method": "**"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "**"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"method": "blah**"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "blah**"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid 2', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"host": "*blah*blah"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "*blah*blah"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid 3', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"host": "blah*blah*blah"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "blah*blah*blah"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid 4', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"host": "blah*blah*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "blah*blah*"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid 5', ) @@ -261,14 +228,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_middle(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "ex*le"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "ex*le"}, + "action": {"pass": "applications/empty"}, + } ), 'host wildcard middle configure', ) @@ -308,14 +272,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "get"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "get"}, + "action": {"pass": "applications/empty"}, + } ), 'method case insensitive configure', ) @@ -325,14 +286,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_left_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*et"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*et"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard case insensitive configure', ) @@ -344,14 +302,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_middle_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "g*t"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "g*t"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard case insensitive configure', ) @@ -363,14 +318,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_right_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "get*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "get*"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard case insensitive configure', ) @@ -382,14 +334,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_substring_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*et*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*et*"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard substring case insensitive configure', ) @@ -403,14 +352,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_left_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "*blah"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "*blah"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard left case sensitive configure', ) @@ -430,14 +376,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_middle_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/b*h"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/b*h"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard middle case sensitive configure', ) @@ -457,14 +400,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_right_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/bla*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/bla*"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard right case sensitive configure', ) @@ -484,14 +424,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_substring_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "*bla*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "*bla*"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard substring case sensitive configure', ) @@ -677,14 +614,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_positive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "localhost"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "localhost"}, + "action": {"pass": "applications/empty"}, + } ), 'match host positive configure', ) @@ -729,14 +663,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_absent(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "localhost"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "localhost"}, + "action": {"pass": "applications/empty"}, + } ), 'match host absent configure', ) @@ -750,14 +681,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_ipv4(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "127.0.0.1"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "127.0.0.1"}, + "action": {"pass": "applications/empty"}, + } ), 'match host ipv4 configure', ) @@ -773,14 +701,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_ipv6(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "[::1]"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "[::1]"}, + "action": {"pass": "applications/empty"}, + } ), 'match host ipv6 configure', ) @@ -804,14 +729,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_positive_many(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": ["localhost", "example.com"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": ["localhost", "example.com"]}, + "action": {"pass": "applications/empty"}, + } ), 'match host positive many configure', ) @@ -831,16 +753,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_positive_and_negative(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "host": ["*example.com", "!www.example.com"] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": ["*example.com", "!www.example.com"]}, + "action": {"pass": "applications/empty"}, + } ), 'match host positive and negative configure', ) @@ -878,14 +795,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_positive_and_negative_wildcard(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": ["*example*", "!www.example*"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": ["*example*", "!www.example*"]}, + "action": {"pass": "applications/empty"}, + } ), 'match host positive and negative wildcard configure', ) @@ -909,14 +823,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "Example.com"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "Example.com"}, + "action": {"pass": "applications/empty"}, + } ), 'host case insensitive configure', ) @@ -940,14 +851,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_port(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "example.com"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "example.com"}, + "action": {"pass": "applications/empty"}, + } ), 'match host port configure', ) @@ -963,14 +871,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": ""}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": ""}, + "action": {"pass": "applications/empty"}, + } ), 'match host empty configure', ) @@ -990,14 +895,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_uri_positive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/"}, + "action": {"pass": "applications/empty"}, + } ), 'match uri positive configure', ) @@ -1025,14 +927,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_uri_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/BLAH"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/BLAH"}, + "action": {"pass": "applications/empty"}, + } ), 'match uri case sensitive configure', ) @@ -1056,14 +955,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_uri_normalize(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/blah"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/blah"}, + "action": {"pass": "applications/empty"}, + } ), 'match uri normalize configure', ) @@ -1075,14 +971,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_empty_array(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": []}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": []}, + "action": {"pass": "applications/empty"}, + } ), 'match empty array configure', ) @@ -1180,14 +1073,11 @@ class TestRouting(TestApplicationProto): def test_routes_edit(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "GET"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "GET"}, + "action": {"pass": "applications/empty"}, + } ), 'routes edit configure', ) @@ -1324,14 +1214,11 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": ["GET", "POST"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": ["GET", "POST"]}, + "action": {"pass": "applications/empty"}, + } ), 'match edit configure', ) @@ -1457,18 +1344,15 @@ class TestRouting(TestApplicationProto): def test_routes_match_rules(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "method": "GET", - "host": "localhost", - "uri": "/", - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "method": "GET", + "host": "localhost", + "uri": "/", + }, + "action": {"pass": "applications/empty"}, + } ), 'routes match rules configure', ) @@ -1478,10 +1362,7 @@ class TestRouting(TestApplicationProto): def test_routes_loop(self): self.assertIn( 'success', - self.conf( - [{"match": {"uri": "/"}, "action": {"pass": "routes"}}], - 'routes', - ), + self.route({"match": {"uri": "/"}, "action": {"pass": "routes"}}), 'routes loop configure', ) @@ -1490,14 +1371,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"host": "localhost"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"host": "localhost"}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers configure', ) @@ -1547,16 +1425,13 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_multiple(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "headers": {"host": "localhost", "x-blah": "test"} - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "headers": {"host": "localhost", "x-blah": "test"} + }, + "action": {"pass": "applications/empty"}, + } ), 'match headers multiple configure', ) @@ -1590,14 +1465,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_multiple_values(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"x-blah": "test"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"x-blah": "test"}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers multiple values configure', ) @@ -1639,14 +1511,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_multiple_rules(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"x-blah": ["test", "blah"]}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"x-blah": ["test", "blah"]}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers multiple rules configure', ) @@ -1706,14 +1575,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"X-BLAH": "TEST"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"X-BLAH": "TEST"}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers case insensitive configure', ) @@ -1733,28 +1599,22 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_invalid(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"headers": ["blah"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": ["blah"]}, + "action": {"pass": "applications/empty"}, + } ), 'match headers invalid', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"headers": {"foo": ["bar", {}]}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"foo": ["bar", {}]}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers invalid 2', ) @@ -1762,14 +1622,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_empty_rule(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"host": ""}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"host": ""}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers empty rule configure', ) @@ -1785,14 +1642,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_rule_field_empty(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"headers": {"": "blah"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"": "blah"}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers rule field empty configure', ) @@ -1800,14 +1654,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers empty configure', ) @@ -1816,14 +1667,11 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": []}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": []}, + "action": {"pass": "applications/empty"}, + } ), 'match headers array empty configure 2', ) @@ -1835,14 +1683,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_rule_array_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"blah": []}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"blah": []}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers rule array empty configure', ) @@ -1863,21 +1708,18 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_array(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "headers": [ - {"x-header1": "foo*"}, - {"x-header2": "bar"}, - {"x-header3": ["foo", "bar"]}, - {"x-header1": "bar", "x-header4": "foo"}, - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "headers": [ + {"x-header1": "foo*"}, + {"x-header2": "bar"}, + {"x-header3": ["foo", "bar"]}, + {"x-header1": "bar", "x-header4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } ), 'match headers array configure', ) @@ -1972,14 +1814,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"arguments": {"foo": "bar"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": "bar"}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments configure', ) @@ -1993,12 +1832,12 @@ class TestRouting(TestApplicationProto): self.get(url='/?Foo=bar')['status'], 404, 'match arguments case sensitive', - ) # FAIL + ) self.assertEqual( self.get(url='/?foo=Bar')['status'], 404, 'match arguments case sensitive 2', - ) # FAIL + ) self.assertEqual( self.get(url='/?foo=bar1')['status'], 404, @@ -2013,14 +1852,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"arguments": {}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments empty configure', ) @@ -2029,14 +1865,11 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf( - [ - { - "match": {"arguments": []}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": []}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments empty configure 2', ) @@ -2046,46 +1879,33 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_invalid(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"arguments": ["var"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": ["var"]}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments invalid', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"arguments": [{"var1": {}}]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": [{"var1": {}}]}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments invalid 2', ) self.assertIn( 'error', - self.conf( - [ - { - "match": { - "arguments": { - "": "bar" - } - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"": "bar"}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments invalid 3', ) @@ -2094,18 +1914,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_space(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": { - "foo": "bar " - } - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": "bar "}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments space configure', ) @@ -2130,18 +1943,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_plus(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": [ - {"foo": "bar+"} - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": [{"foo": "bar+"}]}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments plus configure', ) @@ -2161,18 +1967,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_hex(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": [ - {"foo": "bar"} - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": [{"foo": "bar"}]}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments hex configure', ) @@ -2186,18 +1985,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_chars(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": { - "foo": "-._()[],;" - } - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": "-._()[],;"}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments chars configure', ) @@ -2211,18 +2003,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_complex(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": { - "foo": "" - } - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": ""}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments complex configure', ) @@ -2266,16 +2051,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_multiple(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": {"foo": "bar", "blah": "test"} - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": "bar", "blah": "test"}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments multiple configure', ) @@ -2297,14 +2077,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_multiple_rules(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"arguments": {"foo": ["bar", "blah"]}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": ["bar", "blah"]}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments multiple rules configure', ) @@ -2340,21 +2117,18 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_array(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": [ - {"var1": "val1*"}, - {"var2": "val2"}, - {"var3": ["foo", "bar"]}, - {"var1": "bar", "var4": "foo"}, - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "arguments": [ + {"var1": "val1*"}, + {"var2": "val2"}, + {"var3": ["foo", "bar"]}, + {"var1": "bar", "var4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } ), 'match arguments array configure', ) @@ -2406,14 +2180,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": {"foO": "bar"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {"foO": "bar"}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookie configure', ) @@ -2423,7 +2194,7 @@ class TestRouting(TestApplicationProto): self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=bar', + 'Cookie': 'foO=bar', 'Connection': 'close', }, )['status'], @@ -2434,7 +2205,7 @@ class TestRouting(TestApplicationProto): self.get( headers={ 'Host': 'localhost', - 'Cookie': ['foo=bar', 'blah=blah'], + 'Cookie': ['foO=bar', 'blah=blah'], 'Connection': 'close', }, )['status'], @@ -2445,7 +2216,7 @@ class TestRouting(TestApplicationProto): self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=bar; blah=blah', + 'Cookie': 'foO=bar; blah=blah', 'Connection': 'close', }, )['status'], @@ -2461,25 +2232,25 @@ class TestRouting(TestApplicationProto): 'Connection': 'close', }, )['status'], - 200, - 'match cookies case insensitive', + 404, + 'match cookies case sensitive', ) self.assertEqual( self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=Bar', + 'Cookie': 'foO=Bar', 'Connection': 'close', }, )['status'], - 200, - 'match cookies case insensitive 2', + 404, + 'match cookies case sensitive 2', ) self.assertEqual( self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=bar1', + 'Cookie': 'foO=bar1', 'Connection': 'close', }, )['status'], @@ -2490,25 +2261,33 @@ class TestRouting(TestApplicationProto): self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=bar;', + 'Cookie': '1foO=bar;', 'Connection': 'close', }, )['status'], - 200, + 404, 'match cookies exact 2', ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'foO=bar;1', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies exact 3', + ) def test_routes_match_cookies_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": {}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies empty configure', ) @@ -2517,14 +2296,11 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": []}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": []}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies empty configure 2', ) @@ -2534,28 +2310,22 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_invalid(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"cookies": ["var"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": ["var"]}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies invalid', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"cookies": [{"foo": {}}]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": [{"foo": {}}]}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies invalid 2', ) @@ -2563,16 +2333,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_multiple(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "cookies": {"foo": "bar", "blah": "blah"} - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {"foo": "bar", "blah": "blah"}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies multiple configure', ) @@ -2630,14 +2395,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_multiple_values(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": {"blah": "blah"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {"blah": "blah"}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies multiple values configure', ) @@ -2679,14 +2441,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_multiple_rules(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": {"blah": ["test", "blah"]}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {"blah": ["test", "blah"]}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies multiple rules configure', ) @@ -2758,21 +2517,18 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_array(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "cookies": [ - {"var1": "val1*"}, - {"var2": "val2"}, - {"var3": ["foo", "bar"]}, - {"var1": "bar", "var4": "foo"}, - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "cookies": [ + {"var1": "val1*"}, + {"var2": "val2"}, + {"var3": ["foo", "bar"]}, + {"var1": "bar", "var4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } ), 'match cookies array configure', ) @@ -2885,5 +2641,99 @@ class TestRouting(TestApplicationProto): 'match cookies array 10', ) + def test_routes_match_scheme(self): + self.assertIn( + 'success', + self.route( + { + "match": {"scheme": "http"}, + "action": {"pass": "applications/empty"}, + } + ), + 'match scheme http configure', + ) + self.assertIn( + 'success', + self.route( + { + "match": {"scheme": "https"}, + "action": {"pass": "applications/empty"}, + } + ), + 'match scheme https configure', + ) + self.assertIn( + 'success', + self.route( + { + "match": {"scheme": "HtTp"}, + "action": {"pass": "applications/empty"}, + } + ), + 'match scheme http case insensitive configure', + ) + self.assertIn( + 'success', + self.route( + { + "match": {"scheme": "HtTpS"}, + "action": {"pass": "applications/empty"}, + } + ), + 'match scheme https case insensitive configure', + ) + + def test_routes_match_scheme_invalid(self): + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": ["http"]}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid type no arrays allowed', + ) + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": "ftp"}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid protocol 1', + ) + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": "ws"}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid protocol 2', + ) + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": "*"}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid no wildcard allowed', + ) + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": ""}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid empty', + ) + if __name__ == '__main__': TestRouting.main() diff --git a/test/test_routing_tls.py b/test/test_routing_tls.py new file mode 100644 index 00000000..433a303e --- /dev/null +++ b/test/test_routing_tls.py @@ -0,0 +1,58 @@ +from unit.applications.tls import TestApplicationTLS + + +class TestRoutingTLS(TestApplicationTLS): + prerequisites = ['python', 'openssl'] + + def test_routes_match_scheme(self): + self.certificate() + + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": { + "pass": "routes", + "tls": {"certificate": 'default'}, + }, + }, + "routes": [ + { + "match": {"scheme": "http"}, + "action": {"pass": "applications/empty"}, + }, + { + "match": {"scheme": "https"}, + "action": {"pass": "applications/204_no_content"}, + }, + ], + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/empty", + "module": "wsgi", + }, + "204_no_content": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + + "/python/204_no_content", + "module": "wsgi", + }, + }, + } + ), + 'scheme configure', + ) + + self.assertEqual(self.get()['status'], 200, 'scheme http') + self.assertEqual( + self.get_ssl(port=7081)['status'], 204, 'scheme https' + ) + + +if __name__ == '__main__': + TestRoutingTLS.main() diff --git a/test/test_tls.py b/test/test_tls.py index 14efb3a7..076a2c38 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -1,6 +1,5 @@ import re import ssl -import time import subprocess import unittest from unit.applications.tls import TestApplicationTLS @@ -146,6 +145,8 @@ class TestTLS(TestApplicationTLS): def test_tls_certificate_key_ec(self): self.load('empty') + self.openssl_conf() + subprocess.call( [ 'openssl', @@ -515,8 +516,6 @@ basicConstraints = critical,CA:TRUE""" self.skip_alerts.append(r'process \d+ exited on signal 9') self.load('mirror') - self.assertEqual(self.get()['status'], 200, 'init') - self.certificate() self.conf('1', 'applications/mirror/processes') diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index c4390f15..ec1c95d9 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -64,7 +64,7 @@ class TestApplicationJava(TestApplicationProto): "applications": { script: { "unit_jars": self.pardir + '/build', - "type": "java", + "type": 'java', "processes": {"spare": 0}, "working_directory": script_path, "webapp": app_path, diff --git a/test/unit/applications/lang/perl.py b/test/unit/applications/lang/perl.py index 8aaf33a4..79df2cfa 100644 --- a/test/unit/applications/lang/perl.py +++ b/test/unit/applications/lang/perl.py @@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationPerl(TestApplicationProto): + application_type = "perl" + def load(self, script, name='psgi.pl'): script_path = self.current_dir + '/perl/' + script @@ -10,7 +12,7 @@ class TestApplicationPerl(TestApplicationProto): "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": "perl", + "type": self.application_type, "processes": {"spare": 0}, "working_directory": script_path, "script": script_path + '/' + name, diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py index 99d84164..9c54368d 100644 --- a/test/unit/applications/lang/php.py +++ b/test/unit/applications/lang/php.py @@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationPHP(TestApplicationProto): + application_type = "php" + def load(self, script, name='index.php'): script_path = self.current_dir + '/php/' + script @@ -10,7 +12,7 @@ class TestApplicationPHP(TestApplicationProto): "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": "php", + "type": self.application_type, "processes": {"spare": 0}, "root": script_path, "working_directory": script_path, diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index d1b5b839..ded76cb6 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationPython(TestApplicationProto): + application_type = "python" + def load(self, script, name=None): if name is None: name = script @@ -13,7 +15,7 @@ class TestApplicationPython(TestApplicationProto): "listeners": {"*:7080": {"pass": "applications/" + name}}, "applications": { name: { - "type": "python", + "type": self.application_type, "processes": {"spare": 0}, "path": script_path, "working_directory": script_path, diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py index c2d8633e..d30735ad 100644 --- a/test/unit/applications/lang/ruby.py +++ b/test/unit/applications/lang/ruby.py @@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationRuby(TestApplicationProto): + application_type = "ruby" + def load(self, script, name='config.ru'): script_path = self.current_dir + '/ruby/' + script @@ -10,7 +12,7 @@ class TestApplicationRuby(TestApplicationProto): "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": "ruby", + "type": self.application_type, "processes": {"spare": 0}, "working_directory": script_path, "script": script_path + '/' + name, diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index c8287ac5..6e8deefb 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -1,3 +1,4 @@ +import os import ssl import subprocess from unit.applications.proto import TestApplicationProto @@ -12,6 +13,8 @@ class TestApplicationTLS(TestApplicationProto): self.context.verify_mode = ssl.CERT_NONE def certificate(self, name='default', load=True): + self.openssl_conf() + subprocess.call( [ 'openssl', @@ -59,13 +62,13 @@ class TestApplicationTLS(TestApplicationProto): return ssl.get_server_certificate(addr, ssl_version=ssl_version) - def load(self, script, name=None): - if name is None: - name = script + def openssl_conf(self): + conf_path = self.testdir + '/openssl.conf' - # create default openssl configuration + if os.path.exists(conf_path): + return - with open(self.testdir + '/openssl.conf', 'w') as f: + with open(conf_path, 'w') as f: f.write( """[ req ] default_bits = 2048 @@ -74,9 +77,13 @@ distinguished_name = req_distinguished_name [ req_distinguished_name ]""" ) + def load(self, script, name=None): + if name is None: + name = script + script_path = self.current_dir + '/python/' + script - self.conf( + self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + name}}, "applications": { diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py new file mode 100644 index 00000000..417e9504 --- /dev/null +++ b/test/unit/applications/websockets.py @@ -0,0 +1,215 @@ +import random +import base64 +import struct +import select +import hashlib +import itertools +from unit.applications.proto import TestApplicationProto + +GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + +class TestApplicationWebsocket(TestApplicationProto): + + OP_CONT = 0x00 + OP_TEXT = 0x01 + OP_BINARY = 0x02 + OP_CLOSE = 0x08 + OP_PING = 0x09 + OP_PONG = 0x0A + CLOSE_CODES = [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011] + + def __init__(self, preinit=False): + self.preinit = preinit + + def key(self): + raw_key = bytes(random.getrandbits(8) for _ in range(16)) + return base64.b64encode(raw_key).decode() + + def accept(self, key): + sha1 = hashlib.sha1((key + GUID).encode()).digest() + return base64.b64encode(sha1).decode() + + def upgrade(self): + key = self.key() + + if self.preinit: + self.get() + + resp, sock = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + start=True, + ) + + return (resp, sock, key) + + 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 = ''): + return struct.pack('!H', code) + reason.encode('utf-8') + + def frame_read(self, sock, read_timeout=10): + 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: + break + + return data + + frame = {} + + head1, = struct.unpack('!B', recv_bytes(sock, 1)) + head2, = struct.unpack('!B', recv_bytes(sock, 1)) + + frame['fin'] = bool(head1 & 0b10000000) + frame['rsv1'] = bool(head1 & 0b01000000) + frame['rsv2'] = bool(head1 & 0b00100000) + frame['rsv3'] = bool(head1 & 0b00010000) + frame['opcode'] = head1 & 0b00001111 + frame['mask'] = head2 & 0b10000000 + + length = head2 & 0b01111111 + if length == 126: + data = recv_bytes(sock, 2) + length, = struct.unpack('!H', data) + elif length == 127: + data = recv_bytes(sock, 8) + length, = struct.unpack('!Q', data) + + if frame['mask']: + mask_bits = recv_bytes(sock, 4) + + data = recv_bytes(sock, length) + if frame['mask']: + data = self.apply_mask(data, mask_bits) + + if frame['opcode'] == self.OP_CLOSE: + if length >= 2: + code, = struct.unpack('!H', data[:2]) + reason = data[2:].decode('utf-8') + if not (code in self.CLOSE_CODES or 3000 <= code < 5000): + self.fail('Invalid status code') + frame['code'] = code + frame['reason'] = reason + elif length == 0: + frame['code'] = 1005 + frame['reason'] = '' + else: + self.fail('Close frame too short') + + frame['data'] = data + + if frame['mask']: + self.fail('Received frame with mask') + + return frame + + def frame_to_send( + self, + opcode, + data, + fin=True, + length=None, + rsv1=False, + rsv2=False, + rsv3=False, + mask=True, + ): + frame = b'' + + if isinstance(data, str): + data = data.encode('utf-8') + + head1 = ( + (0b10000000 if fin else 0) + | (0b01000000 if rsv1 else 0) + | (0b00100000 if rsv2 else 0) + | (0b00010000 if rsv3 else 0) + | opcode + ) + + head2 = 0b10000000 if mask else 0 + + data_length = len(data) if length is None else length + if data_length < 126: + frame += struct.pack('!BB', head1, head2 | data_length) + elif data_length < 65536: + frame += struct.pack('!BBH', head1, head2 | 126, data_length) + else: + frame += struct.pack('!BBQ', head1, head2 | 127, data_length) + + if mask: + mask_bits = struct.pack('!I', random.getrandbits(32)) + frame += mask_bits + + if mask: + frame += self.apply_mask(data, mask_bits) + else: + frame += data + + return frame + + def frame_write(self, sock, *args, **kwargs): + chopsize = kwargs.pop('chopsize') if 'chopsize' in kwargs else None + + frame = self.frame_to_send(*args, **kwargs) + + if chopsize is None: + sock.sendall(frame) + + else: + pos = 0 + frame_len = len(frame) + while (pos < frame_len): + end = min(pos + chopsize, frame_len) + sock.sendall(frame[pos:end]) + pos = end + + def message(self, sock, type, message, fragmention_size=None, **kwargs): + message_len = len(message) + + if fragmention_size is None: + fragmention_size = message_len + + if message_len <= fragmention_size: + self.frame_write(sock, type, message, **kwargs) + return + + pos = 0 + op_code = type + 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) + 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']): + temp = self.frame_read(sock, read_timeout=read_timeout) + frame['data'] += temp['data'] + frame['fin'] = temp['fin'] + + return frame diff --git a/test/unit/http.py b/test/unit/http.py index 1ce86e5a..c0af8a9e 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -81,7 +81,11 @@ class TestHTTP(TestUnit): sock.sendall(req) if TestUnit.detailed: - print('>>>', req, sep='\n') + print('>>>') + try: + print(req.decode('utf-8', 'ignore')) + except UnicodeEncodeError: + print(req) resp = '' @@ -93,7 +97,11 @@ class TestHTTP(TestUnit): resp = self.recvall(sock, read_timeout=read_timeout).decode(enc) if TestUnit.detailed: - print('<<<', resp.encode('utf-8'), sep='\n') + print('<<<') + try: + print(resp) + except UnicodeEncodeError: + print(resp.encode()) if 'raw_resp' not in kwargs: resp = self._resp_to_dict(resp) diff --git a/test/unit/main.py b/test/unit/main.py index 49806fe7..6a167a9e 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -12,6 +12,8 @@ import subprocess from multiprocessing import Process +available_modules = {} + class TestUnit(unittest.TestCase): current_dir = os.path.abspath( @@ -21,6 +23,7 @@ class TestUnit(unittest.TestCase): os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) ) architecture = platform.architecture()[0] + system = platform.system() maxDiff = None detailed = False @@ -34,6 +37,17 @@ class TestUnit(unittest.TestCase): TestUnit._set_args(args) + def run(self, result=None): + if not hasattr(self, 'application_type'): + return super().run(result) + + type = self.application_type + for prerequisite in self.prerequisites: + if prerequisite in available_modules: + for version in available_modules[prerequisite]: + self.application_type = type + ' ' + version + super().run(result) + @classmethod def main(cls): args, rest = TestUnit._parse_args() @@ -108,6 +122,16 @@ class TestUnit(unittest.TestCase): 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': @@ -153,7 +177,8 @@ class TestUnit(unittest.TestCase): m = None else: - m = re.search('module: ' + module, log) + if module not in available_modules: + m = None if m is None: missed_module = module @@ -309,6 +334,13 @@ class TestUnit(unittest.TestCase): action='store_true', help='Save unit.log after the test execution', ) + parser.add_argument( + '-u', + '--unsafe', + dest='unsafe', + action='store_true', + help='Run unsafe tests', + ) return parser.parse_known_args() @@ -316,6 +348,7 @@ class TestUnit(unittest.TestCase): def _set_args(args): TestUnit.detailed = args.detailed TestUnit.save_log = args.save_log + TestUnit.unsafe = args.unsafe if TestUnit.detailed: fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0) |