From fc60ea8c4d52c5015edce1e4c6086d80e7a7d830 Mon Sep 17 00:00:00 2001 From: "Juan J. Martinez" Date: Sun, 28 Feb 2021 08:15:55 +0000 Subject: Flags per directory, override vhost settings --- server/src/net/usebox/gemini/server/Server.scala | 2 +- .../src/net/usebox/gemini/server/ServiceConf.scala | 46 ++++++++++++- server/test/src/ServerSpec.scala | 79 ++++++++++++++++++---- spacebeans.conf.example | 8 +++ 4 files changed, 119 insertions(+), 16 deletions(-) diff --git a/server/src/net/usebox/gemini/server/Server.scala b/server/src/net/usebox/gemini/server/Server.scala index ed9252d..2e9bb6b 100644 --- a/server/src/net/usebox/gemini/server/Server.scala +++ b/server/src/net/usebox/gemini/server/Server.scala @@ -142,7 +142,7 @@ case class Server(conf: ServiceConf) { bodySize = dirFile.length(), bodyPath = Some(dirFilePath) ) - } else if (vhost.directoryListing) { + } else if (vhost.getDirectoryListing(resource)) { logger.debug("directory listing") DirListing( req, diff --git a/server/src/net/usebox/gemini/server/ServiceConf.scala b/server/src/net/usebox/gemini/server/ServiceConf.scala index 9c0f8e8..f0751d2 100644 --- a/server/src/net/usebox/gemini/server/ServiceConf.scala +++ b/server/src/net/usebox/gemini/server/ServiceConf.scala @@ -1,21 +1,39 @@ package net.usebox.gemini.server +import java.nio.file.{Path, FileSystems} + +import scala.concurrent.duration.FiniteDuration + import pureconfig._ import pureconfig.generic.semiauto._ -import scala.concurrent.duration.FiniteDuration +import org.log4s._ case class KeyStore(path: String, alias: String, password: String) +case class Directory(path: String, directoryListing: Option[Boolean]) + case class VirtualHost( host: String, root: String, keyStore: Option[KeyStore] = None, indexFile: String = "index.gmi", directoryListing: Boolean = true, - geminiParams: Option[String] = None + geminiParams: Option[String] = None, + directories: List[Directory] ) +object VirtualHost { + implicit class VirtualHostOps(vhost: VirtualHost) { + def getDirectoryListing(path: Path): Boolean = + vhost.directories + .find(_.path == path.toString()) + .fold(vhost.directoryListing)(loc => + loc.directoryListing.getOrElse(vhost.directoryListing) + ) + } +} + case class ServiceConf( address: String, port: Int, @@ -30,9 +48,31 @@ case class ServiceConf( object ServiceConf { + private[this] val logger = getLogger + implicit val keyStoreReader = deriveReader[KeyStore] + implicit val directoryHostReader = deriveReader[Directory] implicit val virtualHostReader = deriveReader[VirtualHost] implicit val serviceConfReader = deriveReader[ServiceConf] - def load(confFile: String) = ConfigSource.file(confFile).load[ServiceConf] + def load(confFile: String) = + ConfigSource.file(confFile).load[ServiceConf].map { conf => + conf.copy(virtualHosts = conf.virtualHosts.map { vhost => + vhost.copy(directories = vhost.directories.map { dir => + val path = + FileSystems + .getDefault() + .getPath(vhost.root, dir.path) + .normalize() + + if (!path.toFile().isDirectory()) + logger.warn( + s"In virtual host '${vhost.host}': directory entry '${dir.path}' is not a directory" + ) + + dir + .copy(path = path.toString()) + }) + }) + } } diff --git a/server/test/src/ServerSpec.scala b/server/test/src/ServerSpec.scala index d086d2d..c81afa3 100644 --- a/server/test/src/ServerSpec.scala +++ b/server/test/src/ServerSpec.scala @@ -10,6 +10,14 @@ import akka.util.ByteString 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 { @@ -34,7 +42,7 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "resolve a known MIME type" in { Server(TestData.conf) .guessMimeType( - FileSystems.getDefault().getPath("file.html"), + getPath("file.html"), None ) shouldBe "text/html" } @@ -42,7 +50,7 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "resolve de default MIME type for unknown types" in { Server(TestData.conf) .guessMimeType( - FileSystems.getDefault().getPath("unknow"), + getPath("unknow"), None ) shouldBe TestData.conf.defaultMimeType } @@ -50,12 +58,12 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "resolve gemini MIME type" in { Server(TestData.conf) .guessMimeType( - FileSystems.getDefault().getPath("file.gmi"), + getPath("file.gmi"), None ) shouldBe "text/gemini" Server(TestData.conf) .guessMimeType( - FileSystems.getDefault().getPath("file.gemini"), + getPath("file.gemini"), None ) shouldBe "text/gemini" } @@ -63,12 +71,12 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "resolve gemini MIME type, including parameters" in { Server(TestData.conf) .guessMimeType( - FileSystems.getDefault().getPath("file.gmi"), + getPath("file.gmi"), Some("param") ) shouldBe "text/gemini; param" Server(TestData.conf) .guessMimeType( - FileSystems.getDefault().getPath("file.gemini"), + getPath("file.gemini"), Some("param") ) shouldBe "text/gemini; param" } @@ -76,7 +84,7 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "gemini MIME type parameters are sanitized" in { Server(TestData.conf) .guessMimeType( - FileSystems.getDefault().getPath("file.gmi"), + getPath("file.gmi"), Some(" ; param") ) shouldBe "text/gemini; param" } @@ -86,7 +94,7 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "resolve a known MIME type" in { Server(TestData.conf.copy(mimeTypes = TestData.mimeTypes)) .guessMimeType( - FileSystems.getDefault().getPath("file.gmi"), + getPath("file.gmi"), None ) shouldBe "config" } @@ -95,7 +103,7 @@ class ServerSpec extends AnyFlatSpec with Matchers { Server( TestData.conf.copy(mimeTypes = Some(Map("text/gemini" -> List(".gmi")))) ).guessMimeType( - FileSystems.getDefault().getPath("file.gmi"), + getPath("file.gmi"), Some("param") ) shouldBe "text/gemini; param" } @@ -103,7 +111,7 @@ class ServerSpec extends AnyFlatSpec with Matchers { it should "resolve de default MIME type for unknown types" in { Server(TestData.conf.copy(mimeTypes = TestData.mimeTypes)) .guessMimeType( - FileSystems.getDefault().getPath("unknow"), + getPath("unknow"), None ) shouldBe TestData.conf.defaultMimeType } @@ -230,7 +238,53 @@ class ServerSpec extends AnyFlatSpec with Matchers { } } - it should "return not found if directory listing is nt enabled and no index" in { + 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) + ) + ) + ) + ) + ) + ).handleReq("gemini://localhost/dir/") should matchPattern { + case _: 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) + ) + ) + ) + ) + ) + ).handleReq("gemini://localhost/dir/") should matchPattern { + case _: NotFound => + } + } + + it should "return not found if directory listing is not enabled and no index" in { Server( TestData.conf.copy(virtualHosts = List(TestData.conf.virtualHosts(0).copy(directoryListing = false)) @@ -266,7 +320,8 @@ class ServerSpec extends AnyFlatSpec with Matchers { virtualHosts = List( VirtualHost( host = "localhost", - root = getClass.getResource("/").getPath() + root = getClass.getResource("/").getPath(), + directories = Nil ) ), genCertValidFor = 1.day, diff --git a/spacebeans.conf.example b/spacebeans.conf.example index e2d586b..8bd5949 100644 --- a/spacebeans.conf.example +++ b/spacebeans.conf.example @@ -23,11 +23,19 @@ virtual-hosts = [ root = "/var/gemini/localhost/" index-file = "index.gmi" + // default for the virtual host directory-listing = true // optional parameters for text/gemini // gemini-params = "charset=utf-8; lang=en" + // override defaults, set properties per directory + // important: directory's path is relative to the root + // + // directories = [ + // { path = "relative/path/", directory-listing = true } + // ] + // comment out to use an auto-generated self-signed certificate key-store { path = "/path/to/keystore.jks" -- cgit v1.2.3