Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Changes in v0.11 (not released)
- Allow non-absolute paths in config (resolved relative to {player profile directory}/beefweb/)
- Fix ignoring of "clientConfigDir" setting
### DeaDBeeF
- Provide universal .deb package to match `deadbeef-static_*.deb`
- Fix deadlock with streamer
Expand Down
2 changes: 1 addition & 1 deletion cpp/server/browser_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ ResponsePtr BrowserController::getRoots()
ResponsePtr BrowserController::getEntries()
{
auto requestedPath = param<std::string>("path");
auto normalizedPath = pathFromUtf8(requestedPath).lexically_normal().make_preferred();
auto normalizedPath = pathFromUtf8(requestedPath).lexically_normal();

if (!settings_->isAllowedPath(normalizedPath))
return Response::error(HttpStatus::S_403_FORBIDDEN, "listing directory is not allowed");
Expand Down
2 changes: 1 addition & 1 deletion cpp/server/deadbeef/plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void Plugin::reconfigure()

settings->port = port_;
settings->allowRemote = allowRemote_;
settings->musicDirsStr = parseValueList<std::string>(musicDirs_, ';');
settings->musicDirsOrig = parseValueList<std::string>(musicDirs_, ';');
settings->authRequired = authRequired_;
settings->authUser = authUser_;
settings->authPassword = authPassword_;
Expand Down
25 changes: 14 additions & 11 deletions cpp/server/foobar2000/plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ Plugin::Plugin()
host_(&player_)
{
assert(!current_);
reconfigure();
current_ = this;

reconfigure();
}

Plugin::~Plugin()
Expand All @@ -33,19 +34,21 @@ Path Plugin::getProfileDir()

void Plugin::reconfigure()
{
auto settings = std::make_shared<SettingsData>();
tryCatchLog([&] {
auto settings = std::make_shared<SettingsData>();

settings->port = settings_store::port;
settings->allowRemote = settings_store::allowRemote;
settings->musicDirsStr = settings_store::getMusicDirs();
settings->authRequired = settings_store::authRequired;
settings->authUser = settings_store::authUser;
settings->authPassword = settings_store::authPassword;
settings->permissions = settings_store::getPermissions();
settings->port = settings_store::port;
settings->allowRemote = settings_store::allowRemote;
settings->musicDirsOrig = settings_store::getMusicDirs();
settings->authRequired = settings_store::authRequired;
settings->authUser = settings_store::authUser;
settings->authPassword = settings_store::authPassword;
settings->permissions = settings_store::getPermissions();

settings->initialize(getProfileDir());
settings->initialize(getProfileDir());

host_.reconfigure(std::move(settings));
host_.reconfigure(std::move(settings));
});
}

Plugin* Plugin::current_ = nullptr;
Expand Down
7 changes: 2 additions & 5 deletions cpp/server/playlists_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,12 @@ std::string PlaylistsController::validateAndNormalizeItem(const std::string& ite
else
path = pathFromUtf8(item);

path = path.lexically_normal().make_preferred();
path = path.lexically_normal();

if (settings_->isAllowedPath(path))
return pathToUtf8(path);

request()->response = Response::error(HttpStatus::S_403_FORBIDDEN, "item is not under allowed path: " + item);
request()->setProcessed();

throw InvalidRequestException();
throw OperationForbiddenException("item is not under allowed path: " + item);
}

ResponsePtr PlaylistsController::addItems()
Expand Down
74 changes: 55 additions & 19 deletions cpp/server/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ void SettingsData::initialize(const Path& profileDir)
{
assert(!profileDir.empty());

auto configDir = profileDir / MSRV_PATH_LITERAL(MSRV_PROJECT_ID);
baseDir = profileDir / MSRV_PATH_LITERAL(MSRV_PROJECT_ID);

loadFromFile(configDir / MSRV_PATH_LITERAL(MSRV_CONFIG_FILE));
loadFromFile(baseDir / MSRV_PATH_LITERAL(MSRV_CONFIG_FILE));

auto envConfigFile = getEnvAsPath(MSRV_CONFIG_FILE_ENV);
if (!envConfigFile.empty())
Expand All @@ -148,38 +148,73 @@ void SettingsData::initialize(const Path& profileDir)
logError("ignoring non-absolute config file path: %s", envConfigFile.c_str());
}

webRoot = resolvePath(pathFromUtf8(webRootOrig)).lexically_normal();

if (webRoot.empty())
{
webRoot = pathToUtf8(getDefaultWebRoot());
webRoot = getDefaultWebRoot();
}

clientConfigDir = pathFromUtf8(clientConfigDirStr).lexically_normal().make_preferred();
clientConfigDir = resolvePath(pathFromUtf8(clientConfigDirOrig)).lexically_normal();

if (!clientConfigDir.empty() && !clientConfigDir.is_absolute())
if (clientConfigDir.empty())
{
logError("ignoring non-absolute client config dir: %s", clientConfigDirStr.c_str());
clientConfigDir.clear();
clientConfigDir = baseDir / MSRV_PATH_LITERAL(MSRV_CLIENT_CONFIG_DIR);
}

if (clientConfigDir.empty())
musicDirs.clear();
musicDirs.reserve(musicDirsOrig.size());

auto index = 0;
for (const auto& dir : musicDirsOrig)
{
clientConfigDir = configDir / MSRV_PATH_LITERAL(MSRV_CLIENT_CONFIG_DIR);
if (dir.empty())
{
logError("skipping empty music directory at index %d", index);
continue;
}

auto path = resolvePath(pathFromUtf8(dir)).lexically_normal();
musicDirs.emplace_back(std::move(path));
index++;
}

musicDirs.clear();
musicDirs.reserve(musicDirsStr.size());
urlMappings.clear();
urlMappings.reserve(urlMappingsOrig.size());

for (const auto& dir : musicDirsStr)
for (const auto& kv : urlMappingsOrig)
{
auto path = pathFromUtf8(dir).lexically_normal().make_preferred();
if (kv.first.find(':') != std::string::npos)
{
logError("url mapping '%s' contains reserved character ':'", kv.first.c_str());
continue;
}

if (!path.is_absolute())
if (kv.first.empty() || kv.first == "/")
{
logError("ignoring non-absolute music dir: %s", dir.c_str());
logError("root url mapping is not allowed, use 'webRoot' instead");
continue;
}

musicDirs.emplace_back(std::move(path));
if (kv.second.empty())
{
logError("url mapping '%s' has empty target", kv.first.c_str());
continue;
}

std::string prefix(kv.first);

if (prefix.front() != '/')
prefix.insert(0, 1, '/');

if (prefix.back() != '/')
prefix.push_back('/');

auto path = resolvePath(pathFromUtf8(kv.second)).lexically_normal();

logInfo("using url mapping '%s' -> '%s'", kv.first.c_str(), kv.second.c_str());

urlMappings[std::move(prefix)] = std::move(path);
}
}

Expand All @@ -206,13 +241,14 @@ void SettingsData::loadFromJson(const Json& json)

loadValue(json, &port, "port");
loadValue(json, &allowRemote, "allowRemote");
loadValue(json, &musicDirsStr, "musicDirs");
loadValue(json, &webRoot, "webRoot");
loadValue(json, &musicDirsOrig, "musicDirs");
loadValue(json, &webRootOrig, "webRoot");
loadValue(json, &authRequired, "authRequired");
loadValue(json, &authUser, "authUser");
loadValue(json, &authPassword, "authPassword");
loadValue(json, &responseHeaders, "responseHeaders");
loadValue(json, &urlMappings, "urlMappings");
loadValue(json, &urlMappingsOrig, "urlMappings");
loadValue(json, &clientConfigDirOrig, "clientConfigDir");
loadPermissions(json);
}

Expand Down
16 changes: 12 additions & 4 deletions cpp/server/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,23 @@ class SettingsData
SettingsData();
~SettingsData();

Path baseDir;
int port = MSRV_DEFAULT_PORT;
bool allowRemote = true;
std::vector<std::string> musicDirsStr;
std::vector<std::string> musicDirsOrig;
std::vector<Path> musicDirs;
std::string webRoot;
std::string clientConfigDirStr;
Path webRoot;
std::string webRootOrig;
std::string clientConfigDirOrig;
Path clientConfigDir;
ApiPermissions permissions = ApiPermissions::ALL;

bool authRequired = false;
std::string authUser;
std::string authPassword;
std::unordered_map<std::string, std::string> responseHeaders;
std::unordered_map<std::string, std::string> urlMappings;
std::unordered_map<std::string, Path> urlMappings;
std::unordered_map<std::string, std::string> urlMappingsOrig;

static void migrate(const char* appName, const Path& profileDir);
static const Path& getDefaultWebRoot();
Expand All @@ -57,6 +60,11 @@ class SettingsData
bool isAllowedPath(const Path& path) const;
void initialize(const Path& profileDir);

Path resolvePath(const Path& path)
{
return path.empty() || path.is_absolute() ? path : baseDir / path;
}

private:
void loadFromJson(const Json& json);
void loadFromFile(const Path& path);
Expand Down
50 changes: 5 additions & 45 deletions cpp/server/static_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ ResponsePtr StaticController::getFile()
if (requestPath.empty())
return redirectToDirectory();

auto filePath = (targetDir_ / pathFromUtf8(requestPath)).lexically_normal().make_preferred();
auto filePath = (targetDir_ / pathFromUtf8(requestPath)).lexically_normal();

if (!isSubpath(targetDir_, filePath))
return Response::notFound();
Expand Down Expand Up @@ -89,19 +89,6 @@ void StaticController::defineRoutes(
{
for (auto& kv : settings->urlMappings)
{
if (kv.first.empty() || kv.first == "/")
{
logError("root url mapping is not allowed, use 'webRoot' instead");
continue;
}

if (kv.second.empty())
{
logError("url mapping '%s' has empty target", kv.first.c_str());
continue;
}

logInfo("using url mapping '%s' -> '%s'", kv.first.c_str(), kv.second.c_str());
defineRoutes(router, workQueue, kv.first, kv.second, contentTypes);
}

Expand All @@ -113,43 +100,16 @@ void StaticController::defineRoutes(
Router* router,
WorkQueue* workQueue,
const std::string& urlPrefix,
const std::string& targetDir,
const Path& targetDir,
const ContentTypeMap& contentTypes)
{
if (urlPrefix.find(':') != std::string::npos)
{
logError("url mapping '%s' contains reserved character ':'", urlPrefix.c_str());
return;
}

std::string prefix;

if (urlPrefix.empty() || urlPrefix.front() != '/')
prefix = "/" + urlPrefix;
else
prefix = urlPrefix;

if (prefix.back() != '/')
prefix.push_back('/');

auto target = pathFromUtf8(targetDir).lexically_normal().make_preferred();

if (!target.is_absolute())
{
logError("url mapping '%s' target should be absolute, got '%s'", urlPrefix.c_str(), targetDir.c_str());
return;
}

auto routes = router->defineRoutes<StaticController>();

routes.createWith([=](Request* request) {
return new StaticController(request, target, contentTypes);
});

routes.createWith([=](Request* request) { return new StaticController(request, targetDir, contentTypes); });
routes.useWorkQueue(workQueue);

routes.get(prefix, &StaticController::getFile);
routes.get(prefix + ":path*", &StaticController::getFile);
routes.get(urlPrefix, &StaticController::getFile);
routes.get(urlPrefix + ":path*", &StaticController::getFile);
}

}
2 changes: 1 addition & 1 deletion cpp/server/static_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class StaticController : public ControllerBase
Router* router,
WorkQueue* workQueue,
const std::string& urlPrefix,
const std::string& targetDir,
const Path& targetDir,
const ContentTypeMap& contentTypes);

private:
Expand Down
10 changes: 8 additions & 2 deletions docs/advanced-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ The following options are available:
}
```

### Non-absolute paths

Unless specified otherwise any path in configuration could be non-absolute.

Such paths are resolved relative to `{player_profile_dir}/beefweb` directory.

### Network settings

`port: number` - Network port to use (same as in UI)
Expand All @@ -49,7 +55,7 @@ The following options are available:

### Web server settings

`webRoot: string` - Root directory where static web content is located. This path has to be absolute.
`webRoot: string` - Root directory where static web content is located.

`urlMappings: {string: string}` - Alternative web directories defined by URL prefix

Expand Down Expand Up @@ -79,4 +85,4 @@ Please read [documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CO

### Other settings

`clientConfigDir: string` - Path to directory where client configuration is stored. Must be absolute.
`clientConfigDir: string` - Path to directory where client configuration is stored.