summaryrefslogtreecommitdiff
path: root/server/test/src
diff options
context:
space:
mode:
authorJuan J. Martinez <jjm@usebox.net>2021-07-03 14:14:31 +0100
committerJuan J. Martinez <jjm@usebox.net>2021-07-22 19:49:53 +0100
commitc760d7c9bd14d7e56ae1553a0b3c4a1258448686 (patch)
treee60a35310a2a9f120a5c3e769ea862e950d322f5 /server/test/src
parentab4a5268bd2971a364bf026aede2cb50c885a03d (diff)
downloadspacebeans-c760d7c9bd14d7e56ae1553a0b3c4a1258448686.tar.gz
spacebeans-c760d7c9bd14d7e56ae1553a0b3c4a1258448686.zip
CGI support
Diffstat (limited to 'server/test/src')
-rw-r--r--server/test/src/ServerSpec.scala425
-rw-r--r--server/test/src/ServiceConfSpec.scala135
2 files changed, 450 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
diff --git a/server/test/src/ServiceConfSpec.scala b/server/test/src/ServiceConfSpec.scala
new file mode 100644
index 0000000..d886b4f
--- /dev/null
+++ b/server/test/src/ServiceConfSpec.scala
@@ -0,0 +1,135 @@
+package net.usebox.gemini.server
+
+import java.nio.file.FileSystems
+
+import scala.concurrent.duration._
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class ServiceConfSpec extends AnyFlatSpec with Matchers {
+
+ def getPath(value: String) =
+ FileSystems.getDefault().getPath(getClass.getResource("/").getPath(), value)
+
+ behavior of "getDirectoryListing"
+
+ it should "resolve directory listing using vhost conf if no directory override" in {
+ val vh = TestData.conf.virtualHosts.head
+ vh.getDirectoryListing(getPath("/dir")) shouldBe vh.directoryListing
+ }
+
+ it should "resolve directory listing using directory override" in {
+ val vh = TestData.conf.virtualHosts.head.copy(
+ directoryListing = false,
+ directories = List(
+ Directory(
+ getPath("dir").toString(),
+ directoryListing = Some(true),
+ None
+ )
+ )
+ )
+ vh.getDirectoryListing(getPath("dir")) shouldBe true
+ }
+
+ it should "ignore non matching directories resolving directory listing" in {
+ val vh = TestData.conf.virtualHosts.head.copy(
+ directoryListing = false,
+ directories = List(
+ Directory(
+ getPath("no-match").toString(),
+ directoryListing = Some(true),
+ allowCgi = None
+ )
+ )
+ )
+ vh.getDirectoryListing(getPath("dir")) shouldBe false
+ }
+
+ behavior of "getCgi"
+
+ it should "return None as allow CGI is off by default" in {
+ val vh = TestData.conf.virtualHosts.head
+ vh.getCgi(getPath("dir/cgi")) shouldBe None
+ }
+
+ it should "set allow CGI via directory override" in {
+ List(true, false).foreach { value =>
+ val vh = TestData.conf.virtualHosts.head.copy(
+ directories = List(
+ Directory(
+ getPath("dir").toString(),
+ directoryListing = None,
+ allowCgi = Some(value)
+ )
+ )
+ )
+ vh.getCgi(getPath("dir/cgi")) should matchPattern {
+ case Some(_) if value =>
+ case None if !value =>
+ }
+ }
+ }
+
+ it should "return the CGI path minus path info" in {
+ val vh = TestData.conf.virtualHosts.head.copy(
+ directories = List(
+ Directory(
+ getPath("dir").toString(),
+ directoryListing = None,
+ allowCgi = Some(true)
+ )
+ )
+ )
+ vh.getCgi(getPath("dir/cgi/path/info")) shouldBe Some(
+ getPath("dir/cgi")
+ )
+ }
+
+ it should "not return the CGI path if is exactly the CGI dir" in {
+ val vh = TestData.conf.virtualHosts.head.copy(
+ directories = List(
+ Directory(
+ getPath("dir").toString(),
+ directoryListing = None,
+ allowCgi = Some(true)
+ )
+ )
+ )
+ vh.getCgi(getPath("dir")) shouldBe None
+ vh.getCgi(getPath("dir/")) shouldBe None
+ }
+
+ it should "not return the CGI path if allow CGI is false" in {
+ val vh = TestData.conf.virtualHosts.head.copy(
+ directories = List(
+ Directory(
+ getPath("dir").toString(),
+ directoryListing = None,
+ allowCgi = Some(false)
+ )
+ )
+ )
+ vh.getCgi(getPath("dir/cgi")) shouldBe None
+ vh.getCgi(getPath("dir/cgit/with/path")) shouldBe None
+ }
+
+ object TestData {
+ val conf = ServiceConf(
+ address = "127.0.0.1",
+ port = 1965,
+ defaultMimeType = "text/plain",
+ idleTimeout = 10.seconds,
+ virtualHosts = List(
+ VirtualHost(
+ host = "localhost",
+ root = getClass.getResource("/").getPath()
+ )
+ ),
+ genCertValidFor = 1.day,
+ enabledProtocols = Nil,
+ enabledCipherSuites = Nil
+ )
+ }
+}