diff options
author | Juan J. Martinez <jjm@usebox.net> | 2021-07-03 14:14:31 +0100 |
---|---|---|
committer | Juan J. Martinez <jjm@usebox.net> | 2021-07-22 19:49:53 +0100 |
commit | c760d7c9bd14d7e56ae1553a0b3c4a1258448686 (patch) | |
tree | e60a35310a2a9f120a5c3e769ea862e950d322f5 /server/test/src/ServerSpec.scala | |
parent | ab4a5268bd2971a364bf026aede2cb50c885a03d (diff) | |
download | spacebeans-c760d7c9bd14d7e56ae1553a0b3c4a1258448686.tar.gz spacebeans-c760d7c9bd14d7e56ae1553a0b3c4a1258448686.zip |
CGI support
Diffstat (limited to 'server/test/src/ServerSpec.scala')
-rw-r--r-- | server/test/src/ServerSpec.scala | 425 |
1 files changed, 315 insertions, 110 deletions
diff --git a/server/test/src/ServerSpec.scala b/server/test/src/ServerSpec.scala index 05e1580..7a2b501 100644 --- a/server/test/src/ServerSpec.scala +++ b/server/test/src/ServerSpec.scala @@ -12,12 +12,6 @@ class ServerSpec extends AnyFlatSpec with Matchers { def getPath(value: String) = FileSystems.getDefault().getPath(value) - def getPath(root: String, dir: String) = - FileSystems - .getDefault() - .getPath(root, dir) - .normalize() - behavior of "validPath" it should "return true for the emtpy path" in { @@ -135,107 +129,110 @@ class ServerSpec extends AnyFlatSpec with Matchers { behavior of "handleReq" it should "return bad request on URLs with no scheme" in { - Server(TestData.conf).handleReq("//localhost/") should matchPattern { - case _: BadRequest => - } + Server(TestData.conf) + .handleReq("//localhost/", "127.0.0.1") should be(a[BadRequest]) } it should "return proxy request refused on port mismatch" in { Server(TestData.conf) - .handleReq("gemini://localhost:8080/") should matchPattern { - case _: ProxyRequestRefused => - } + .handleReq("gemini://localhost:8080/", "127.0.0.1") should be( + a[ProxyRequestRefused] + ) } it should "return proxy request refused when port not provided and configured port is not default" in { Server(TestData.conf.copy(port = 8080)) - .handleReq("gemini://localhost/") should matchPattern { - case _: ProxyRequestRefused => - } + .handleReq("gemini://localhost/", "127.0.0.1") should be( + a[ProxyRequestRefused] + ) } it should "return success when port is provided and matches configured port (not default)" in { Server(TestData.conf.copy(port = 8080)) - .handleReq("gemini://localhost:8080/") should matchPattern { - case _: Success => - } + .handleReq("gemini://localhost:8080/", "127.0.0.1") should be(a[Success]) } it should "return proxy request refused when the vhost is not found" in { Server(TestData.conf) - .handleReq("gemini://otherhost/") should matchPattern { - case _: ProxyRequestRefused => - } + .handleReq("gemini://otherhost/", "127.0.0.1") should be( + a[ProxyRequestRefused] + ) } it should "return bad request when user info is present" in { Server(TestData.conf) - .handleReq("gemini://user@localhost/") should matchPattern { - case _: BadRequest => - } + .handleReq("gemini://user@localhost/", "127.0.0.1") should be( + a[BadRequest] + ) } it should "return bad request when the path is out of root dir" in { Server(TestData.conf) - .handleReq("gemini://localhost/../../") should matchPattern { - case _: BadRequest => - } + .handleReq("gemini://localhost/../../", "127.0.0.1") should be( + a[BadRequest] + ) } it should "return bad request for invalid URLs" in { Server(TestData.conf) - .handleReq("gemini://localhost/ invalid") should matchPattern { - case _: BadRequest => - } + .handleReq( + "gemini://localhost/ invalid", + "127.0.0.1" + ) should be(a[BadRequest]) } it should "redirect to normalize the URL" in { Server(TestData.conf) - .handleReq("gemini://localhost/./") should matchPattern { - case _: PermanentRedirect => - } + .handleReq("gemini://localhost/./", "127.0.0.1") should be( + a[PermanentRedirect] + ) } it should "return not found if the path doesn't exist" in { Server(TestData.conf) - .handleReq("gemini://localhost/doesnotexist") should matchPattern { - case _: NotFound => - } + .handleReq( + "gemini://localhost/doesnotexist", + "127.0.0.1" + ) should be(a[NotFound]) } it should "return not found if a dot file" in { Server(TestData.conf) - .handleReq("gemini://localhost/.dotfile") should matchPattern { - case _: NotFound => - } + .handleReq( + "gemini://localhost/.dotfile", + "127.0.0.1" + ) should be(a[NotFound]) } it should "return success on reading file" in { Server(TestData.conf) - .handleReq("gemini://localhost/index.gmi") should matchPattern { + .handleReq( + "gemini://localhost/index.gmi", + "127.0.0.1" + ) should matchPattern { case Success(_, "text/gemini", Some(_), 25L) => } } it should "redirect and normalize request on a directory" in { Server(TestData.conf) - .handleReq("gemini://localhost/dir") should matchPattern { - case _: PermanentRedirect => - } + .handleReq("gemini://localhost/dir", "127.0.0.1") should be( + a[PermanentRedirect] + ) } it should "return an existing index file when requesting a directory" in { Server(TestData.conf) - .handleReq("gemini://localhost/") should matchPattern { + .handleReq("gemini://localhost/", "127.0.0.1") should matchPattern { case Success(_, "text/gemini", Some(_), 25L) => } } it should "return proxy request refused for non gemini schemes" in { Server(TestData.conf) - .handleReq("https://localhost/") should matchPattern { - case _: ProxyRequestRefused => - } + .handleReq("https://localhost/", "127.0.0.1") should be( + a[ProxyRequestRefused] + ) } it should "include gemini params for gemini MIME type" in { @@ -243,7 +240,10 @@ class ServerSpec extends AnyFlatSpec with Matchers { TestData.conf.copy(virtualHosts = List(TestData.conf.virtualHosts(0).copy(geminiParams = Some("test"))) ) - ).handleReq("gemini://localhost/index.gmi") should matchPattern { + ).handleReq( + "gemini://localhost/index.gmi", + "127.0.0.1" + ) should matchPattern { case Success(_, "text/gemini; test", Some(_), 25L) => } } @@ -252,55 +252,80 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "return a directory listing if is enabled and no index" in { Server(TestData.conf) - .handleReq("gemini://localhost/dir/") should matchPattern { - case _: DirListing => - } + .handleReq("gemini://localhost/dir/", "127.0.0.1") should be( + a[DirListing] + ) } it should "return a directory listing, directory listing flags: vhost flag false, directories flag true" in { Server( - TestData.conf.copy(virtualHosts = - List( - TestData.conf - .virtualHosts(0) - .copy( - directoryListing = false, - directories = List( - Directory( - getPath(getClass.getResource("/").getPath(), "dir/") - .toString(), - directoryListing = Some(true) + ServiceConf.initConf( + TestData.conf.copy(virtualHosts = + List( + TestData.conf + .virtualHosts(0) + .copy( + directoryListing = false, + directories = List( + Directory( + "dir/", + directoryListing = Some(true), + allowCgi = None + ) ) ) - ) + ) ) ) - ).handleReq("gemini://localhost/dir/") should matchPattern { - case _: DirListing => - } + ).handleReq("gemini://localhost/dir/", "127.0.0.1") should be(a[DirListing]) } it should "return not found with no index, directory listing flags: vhost flag true, directories flag false" in { Server( - TestData.conf.copy(virtualHosts = - List( - TestData.conf - .virtualHosts(0) - .copy( - directoryListing = true, - directories = List( - Directory( - getPath(getClass.getResource("/").getPath(), "dir/") - .toString(), - directoryListing = Some(false) + ServiceConf.initConf( + TestData.conf.copy(virtualHosts = + List( + TestData.conf + .virtualHosts(0) + .copy( + directoryListing = true, + directories = List( + Directory( + "dir/", + directoryListing = Some(false), + allowCgi = None + ) ) ) - ) + ) ) ) - ).handleReq("gemini://localhost/dir/") should matchPattern { - case _: NotFound => - } + ).handleReq("gemini://localhost/dir/", "127.0.0.1") should be(a[NotFound]) + } + + it should "not apply directory listing override to subdirectories" in { + Server( + ServiceConf.initConf( + TestData.conf.copy(virtualHosts = + List( + TestData.conf + .virtualHosts(0) + .copy( + directoryListing = false, + directories = List( + Directory( + "dir/", + directoryListing = Some(true), + allowCgi = None + ) + ) + ) + ) + ) + ) + ).handleReq("gemini://localhost/dir/sub/", "127.0.0.1") should be( + a[NotFound] + ) } it should "return not found if directory listing is not enabled and no index" in { @@ -308,16 +333,15 @@ class ServerSpec extends AnyFlatSpec with Matchers { TestData.conf.copy(virtualHosts = List(TestData.conf.virtualHosts(0).copy(directoryListing = false)) ) - ).handleReq("gemini://localhost/dir/") should matchPattern { - case _: NotFound => - } + ).handleReq("gemini://localhost/dir/", "127.0.0.1") should be(a[NotFound]) } behavior of "handleReq, user directories" it should "return success on reading file" in { Server(TestData.confUserDir).handleReq( - "gemini://localhost/~username/index.gmi" + "gemini://localhost/~username/index.gmi", + "127.0.0.1" ) should matchPattern { case Success(_, "text/gemini", Some(_), 38L) => } @@ -325,15 +349,15 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "return redirect accessing the user directory without ending slash" in { Server(TestData.confUserDir).handleReq( - "gemini://localhost/~username" - ) should matchPattern { - case _: PermanentRedirect => - } + "gemini://localhost/~username", + "127.0.0.1" + ) should be(a[PermanentRedirect]) } it should "return success accessing the user directory index" in { Server(TestData.confUserDir).handleReq( - "gemini://localhost/~username/" + "gemini://localhost/~username/", + "127.0.0.1" ) should matchPattern { case Success(_, "text/gemini", Some(_), 38L) => } @@ -341,32 +365,28 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "return bad request trying to exit the root directory" in { Server(TestData.confUserDir).handleReq( - "gemini://localhost/~username/../../" - ) should matchPattern { - case _: BadRequest => - } + "gemini://localhost/~username/../../", + "127.0.0.1" + ) should be(a[BadRequest]) } it should "return redirect to the virtual host root when leaving the user dir" in { Server(TestData.confUserDir).handleReq( - "gemini://localhost/~username/../" - ) should matchPattern { - case _: PermanentRedirect => - } + "gemini://localhost/~username/../", + "127.0.0.1" + ) should be(a[PermanentRedirect]) } it should "not translate root if used an invalid user pattern" in { Server(TestData.confUserDir).handleReq( - "gemini://localhost/~username../" - ) should matchPattern { - case _: NotFound => - } + "gemini://localhost/~username../", + "127.0.0.1" + ) should be(a[NotFound]) Server(TestData.confUserDir).handleReq( - "gemini://localhost/~0invalid/" - ) should matchPattern { - case _: NotFound => - } + "gemini://localhost/~0invalid/", + "127.0.0.1" + ) should be(a[NotFound]) } it should "not translate root if no user directory path was provided" in { @@ -379,21 +399,187 @@ class ServerSpec extends AnyFlatSpec with Matchers { ) ) ).handleReq( - "gemini://localhost/~username/" + "gemini://localhost/~username/", + "127.0.0.1" + ) should be(a[NotFound]) + } + + it should "not execute a CGI if the target resource is not executable" in { + Server(TestData.cgiConf).handleReq( + "gemini://localhost/dir/file.txt", + "127.0.0.1" + ) should matchPattern { + case Success(_, "text/plain", Some(_), 5L) => + } + } + + it should "not execute a CGI if the target resource is a directory" in { + Server(TestData.cgiConf).handleReq( + "gemini://localhost/dir/sub/", + "127.0.0.1" + ) should be(a[DirListing]) + } + + it should "not apply allow CGI to subdirectories" in { + Server(TestData.cgiConf).handleReq( + "gemini://localhost/dir/sub/cgi", + "127.0.0.1" + ) should matchPattern { + case Success(_, "text/plain", Some(_), 72) => + } + } + + it should "execute a CGI" in { + val cgi = Server(TestData.cgiConf) + .handleReq( + "gemini://localhost/dir/cgi", + "127.0.0.1" + ) + .asInstanceOf[Cgi] + + cgi.status should be(20) + cgi.meta should be("text/gemini") + cgi.body should include("GATEWAY_INTERFACE=CGI/1.1") + } + + it should "execute a CGI: empty parameters, host and port" in { + Server(TestData.cgiConf).handleReq( + "gemini://localhost/dir/cgi", + "127.0.0.1" + ) should matchPattern { + case Cgi( + _, + _, + "", + "", + "cgi", + TestData.host, + TestData.portStr, + _ + ) => + } + } + + it should "execute a CGI: query string" in { + Server(TestData.cgiConf).handleReq( + "gemini://localhost/dir/cgi?query&string", + "127.0.0.1" + ) should matchPattern { + case Cgi( + _, + _, + "query&string", + "", + "cgi", + TestData.host, + TestData.portStr, + _ + ) => + } + } + + it should "execute a CGI: path info" in { + Server(TestData.cgiConf).handleReq( + "gemini://localhost/dir/cgi/path/info", + "127.0.0.1" + ) should matchPattern { + case Cgi( + _, + _, + "", + "/path/info", + "cgi", + TestData.host, + TestData.portStr, + _ + ) => + } + } + + it should "execute a CGI: query string and path info" in { + Server(TestData.cgiConf).handleReq( + "gemini://localhost/dir/cgi/path/info?query=string", + "127.0.0.1" ) should matchPattern { - case _: NotFound => + case Cgi( + _, + _, + "query=string", + "/path/info", + "cgi", + TestData.host, + TestData.portStr, + _ + ) => } } + it should "not execute an executable if allow CGI is off" in { + Server(TestData.conf) + .handleReq( + "gemini://localhost/dir/cgi", + "127.0.0.1" + ) should matchPattern { + case Success(_, "text/plain", Some(_), _) => + } + } + + it should "response with an error if the CGI exits with non 0" in { + val bad = Server(TestData.cgiConf) + .handleReq( + "gemini://localhost/dir/bad-cgi", + "127.0.0.1" + ) + .asInstanceOf[Cgi] + + val meta = "Error executing CGI" + bad.status should be(50) + bad.meta should be(meta) + bad.body should include(meta) + } + + it should "return a response with an error if the CGI exits with non 0" in { + val bad = Server(TestData.cgiConf) + .handleReq( + "gemini://localhost/dir/bad-cgi", + "127.0.0.1" + ) + .asInstanceOf[Cgi] + + val meta = "Error executing CGI" + bad.status should be(50) + bad.meta should be(meta) + bad.body should include(meta) + } + + it should "return a response with an error if the CGI response is invalid" in { + val bad = Server(TestData.cgiConf) + .handleReq( + "gemini://localhost/dir/bad-response", + "127.0.0.1" + ) + .asInstanceOf[Cgi] + + val meta = "Invalid response from CGI" + bad.status should be(40) + bad.meta should be(meta) + bad.body should include(meta) + } + object TestData { + + val host = "localhost" + val port = 1965 + val portStr = port.toString() + val conf = ServiceConf( address = "127.0.0.1", - port = 1965, + port = port, defaultMimeType = "text/plain", idleTimeout = 10.seconds, virtualHosts = List( VirtualHost( - host = "localhost", + host = host, root = getClass.getResource("/").getPath() ) ), @@ -402,6 +588,25 @@ class ServerSpec extends AnyFlatSpec with Matchers { enabledCipherSuites = Nil ) + val cgiConf = ServiceConf.initConf( + conf.copy(virtualHosts = + List( + conf + .virtualHosts(0) + .copy( + directoryListing = true, + directories = List( + Directory( + "dir/", + directoryListing = Some(false), + allowCgi = Some(true) + ) + ) + ) + ) + ) + ) + val confUserDir = conf.copy(virtualHosts = List( conf |