Changes
diff --git a/src/config.cpp b/src/config.cpp
index c8ed81c..9142eee 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -28,6 +28,7 @@ const std::set<std::string> CONFIG_KEYS = {
"email",
"listen_address",
"listen_port",
+ "listen_socket_permission",
"maximum_accumulated_pulls",
"static_root",
"thumbnail_long_side",
@@ -237,6 +238,24 @@ mw::E<Config> loadConfig(const std::filesystem::path& config_path)
base_url->path(base_path.c_str());
std::optional<mw::HTTPServer::ListenAddress> listen_address;
+ std::optional<unsigned int> listen_socket_permission;
+ if(table.contains("listen_socket_permission"))
+ {
+ auto permission = table["listen_socket_permission"].value<
+ std::int64_t>();
+ if(!permission)
+ {
+ return std::unexpected(mw::runtimeError(
+ "Configuration key 'listen_socket_permission' must be an "
+ "integer"));
+ }
+ if(*permission < 0 || *permission > 0777)
+ {
+ return std::unexpected(mw::runtimeError(
+ "listen_socket_permission must be from 0 through 0o777"));
+ }
+ listen_socket_permission = static_cast<unsigned int>(*permission);
+ }
if(listen_address_text->starts_with("unix:"))
{
const std::string socket_path = listen_address_text->substr(5);
@@ -245,7 +264,9 @@ mw::E<Config> loadConfig(const std::filesystem::path& config_path)
return std::unexpected(mw::runtimeError(
"A Unix listen address requires a socket path"));
}
- listen_address.emplace(mw::SocketFileInfo(socket_path));
+ mw::SocketFileInfo socket(socket_path);
+ socket.permission = listen_socket_permission;
+ listen_address.emplace(std::move(socket));
}
else
{
diff --git a/tests/config_test.cpp b/tests/config_test.cpp
index 2f90762..d68bfe9 100644
--- a/tests/config_test.cpp
+++ b/tests/config_test.cpp
@@ -111,9 +111,44 @@ TEST(ConfigTest, LoadsUnixSocketConfiguration)
ASSERT_TRUE(config) << config.error().msg();
ASSERT_TRUE(std::holds_alternative<mw::SocketFileInfo>(
config->listen_address));
- EXPECT_EQ(
- std::get<mw::SocketFileInfo>(config->listen_address).filename,
- "/tmp/card-collection.sock");
+ const mw::SocketFileInfo& socket =
+ std::get<mw::SocketFileInfo>(config->listen_address);
+ EXPECT_EQ(socket.filename, "/tmp/card-collection.sock");
+ EXPECT_FALSE(socket.permission.has_value());
+}
+
+/// Verify Unix socket permissions are passed to the HTTP server.
+TEST(ConfigTest, LoadsUnixSocketPermission)
+{
+ TemporaryConfigRoot temporary;
+ const std::filesystem::path config_path = temporary.path() / "app.toml";
+ writeConfig(
+ config_path,
+ "listen_address = \"unix:/tmp/card-collection.sock\"\n"
+ "listen_socket_permission = 0o660\n");
+
+ auto config = loadConfig(config_path);
+
+ ASSERT_TRUE(config) << config.error().msg();
+ ASSERT_TRUE(std::holds_alternative<mw::SocketFileInfo>(
+ config->listen_address));
+ const mw::SocketFileInfo& socket =
+ std::get<mw::SocketFileInfo>(config->listen_address);
+ ASSERT_TRUE(socket.permission.has_value());
+ EXPECT_EQ(*socket.permission, 0660);
+}
+
+/// Reject socket permissions outside the regular permission-bit range.
+TEST(ConfigTest, RejectsInvalidUnixSocketPermission)
+{
+ TemporaryConfigRoot temporary;
+ const std::filesystem::path config_path = temporary.path() / "app.toml";
+ writeConfig(
+ config_path,
+ "listen_address = \"unix:/tmp/card-collection.sock\"\n"
+ "listen_socket_permission = 0o1000\n");
+
+ EXPECT_FALSE(loadConfig(config_path));
}
/// Verify unknown configuration keys are rejected.