Skip to content

Commit c74a756

Browse files
authored
allow relative paths in config (#312)
1 parent f4cd6e0 commit c74a756

File tree

10 files changed

+101
-89
lines changed

10 files changed

+101
-89
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Changes in v0.11 (not released)
2+
- Allow non-absolute paths in config (resolved relative to {player profile directory}/beefweb/)
3+
- Fix ignoring of "clientConfigDir" setting
24
### DeaDBeeF
35
- Provide universal .deb package to match `deadbeef-static_*.deb`
46
- Fix deadlock with streamer

cpp/server/browser_controller.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ ResponsePtr BrowserController::getRoots()
8383
ResponsePtr BrowserController::getEntries()
8484
{
8585
auto requestedPath = param<std::string>("path");
86-
auto normalizedPath = pathFromUtf8(requestedPath).lexically_normal().make_preferred();
86+
auto normalizedPath = pathFromUtf8(requestedPath).lexically_normal();
8787

8888
if (!settings_->isAllowedPath(normalizedPath))
8989
return Response::error(HttpStatus::S_403_FORBIDDEN, "listing directory is not allowed");

cpp/server/deadbeef/plugin.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ void Plugin::reconfigure()
5959

6060
settings->port = port_;
6161
settings->allowRemote = allowRemote_;
62-
settings->musicDirsStr = parseValueList<std::string>(musicDirs_, ';');
62+
settings->musicDirsOrig = parseValueList<std::string>(musicDirs_, ';');
6363
settings->authRequired = authRequired_;
6464
settings->authUser = authUser_;
6565
settings->authPassword = authPassword_;

cpp/server/foobar2000/plugin.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ Plugin::Plugin()
1212
host_(&player_)
1313
{
1414
assert(!current_);
15-
reconfigure();
1615
current_ = this;
16+
17+
reconfigure();
1718
}
1819

1920
Plugin::~Plugin()
@@ -33,19 +34,21 @@ Path Plugin::getProfileDir()
3334

3435
void Plugin::reconfigure()
3536
{
36-
auto settings = std::make_shared<SettingsData>();
37+
tryCatchLog([&] {
38+
auto settings = std::make_shared<SettingsData>();
3739

38-
settings->port = settings_store::port;
39-
settings->allowRemote = settings_store::allowRemote;
40-
settings->musicDirsStr = settings_store::getMusicDirs();
41-
settings->authRequired = settings_store::authRequired;
42-
settings->authUser = settings_store::authUser;
43-
settings->authPassword = settings_store::authPassword;
44-
settings->permissions = settings_store::getPermissions();
40+
settings->port = settings_store::port;
41+
settings->allowRemote = settings_store::allowRemote;
42+
settings->musicDirsOrig = settings_store::getMusicDirs();
43+
settings->authRequired = settings_store::authRequired;
44+
settings->authUser = settings_store::authUser;
45+
settings->authPassword = settings_store::authPassword;
46+
settings->permissions = settings_store::getPermissions();
4547

46-
settings->initialize(getProfileDir());
48+
settings->initialize(getProfileDir());
4749

48-
host_.reconfigure(std::move(settings));
50+
host_.reconfigure(std::move(settings));
51+
});
4952
}
5053

5154
Plugin* Plugin::current_ = nullptr;

cpp/server/playlists_controller.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,12 @@ std::string PlaylistsController::validateAndNormalizeItem(const std::string& ite
128128
else
129129
path = pathFromUtf8(item);
130130

131-
path = path.lexically_normal().make_preferred();
131+
path = path.lexically_normal();
132132

133133
if (settings_->isAllowedPath(path))
134134
return pathToUtf8(path);
135135

136-
request()->response = Response::error(HttpStatus::S_403_FORBIDDEN, "item is not under allowed path: " + item);
137-
request()->setProcessed();
138-
139-
throw InvalidRequestException();
136+
throw OperationForbiddenException("item is not under allowed path: " + item);
140137
}
141138

142139
ResponsePtr PlaylistsController::addItems()

cpp/server/settings.cpp

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,9 @@ void SettingsData::initialize(const Path& profileDir)
135135
{
136136
assert(!profileDir.empty());
137137

138-
auto configDir = profileDir / MSRV_PATH_LITERAL(MSRV_PROJECT_ID);
138+
baseDir = profileDir / MSRV_PATH_LITERAL(MSRV_PROJECT_ID);
139139

140-
loadFromFile(configDir / MSRV_PATH_LITERAL(MSRV_CONFIG_FILE));
140+
loadFromFile(baseDir / MSRV_PATH_LITERAL(MSRV_CONFIG_FILE));
141141

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

151+
webRoot = resolvePath(pathFromUtf8(webRootOrig)).lexically_normal();
152+
151153
if (webRoot.empty())
152154
{
153-
webRoot = pathToUtf8(getDefaultWebRoot());
155+
webRoot = getDefaultWebRoot();
154156
}
155157

156-
clientConfigDir = pathFromUtf8(clientConfigDirStr).lexically_normal().make_preferred();
158+
clientConfigDir = resolvePath(pathFromUtf8(clientConfigDirOrig)).lexically_normal();
157159

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

164-
if (clientConfigDir.empty())
165+
musicDirs.clear();
166+
musicDirs.reserve(musicDirsOrig.size());
167+
168+
auto index = 0;
169+
for (const auto& dir : musicDirsOrig)
165170
{
166-
clientConfigDir = configDir / MSRV_PATH_LITERAL(MSRV_CLIENT_CONFIG_DIR);
171+
if (dir.empty())
172+
{
173+
logError("skipping empty music directory at index %d", index);
174+
continue;
175+
}
176+
177+
auto path = resolvePath(pathFromUtf8(dir)).lexically_normal();
178+
musicDirs.emplace_back(std::move(path));
179+
index++;
167180
}
168181

169-
musicDirs.clear();
170-
musicDirs.reserve(musicDirsStr.size());
182+
urlMappings.clear();
183+
urlMappings.reserve(urlMappingsOrig.size());
171184

172-
for (const auto& dir : musicDirsStr)
185+
for (const auto& kv : urlMappingsOrig)
173186
{
174-
auto path = pathFromUtf8(dir).lexically_normal().make_preferred();
187+
if (kv.first.find(':') != std::string::npos)
188+
{
189+
logError("url mapping '%s' contains reserved character ':'", kv.first.c_str());
190+
continue;
191+
}
175192

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

182-
musicDirs.emplace_back(std::move(path));
199+
if (kv.second.empty())
200+
{
201+
logError("url mapping '%s' has empty target", kv.first.c_str());
202+
continue;
203+
}
204+
205+
std::string prefix(kv.first);
206+
207+
if (prefix.front() != '/')
208+
prefix.insert(0, 1, '/');
209+
210+
if (prefix.back() != '/')
211+
prefix.push_back('/');
212+
213+
auto path = resolvePath(pathFromUtf8(kv.second)).lexically_normal();
214+
215+
logInfo("using url mapping '%s' -> '%s'", kv.first.c_str(), kv.second.c_str());
216+
217+
urlMappings[std::move(prefix)] = std::move(path);
183218
}
184219
}
185220

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

207242
loadValue(json, &port, "port");
208243
loadValue(json, &allowRemote, "allowRemote");
209-
loadValue(json, &musicDirsStr, "musicDirs");
210-
loadValue(json, &webRoot, "webRoot");
244+
loadValue(json, &musicDirsOrig, "musicDirs");
245+
loadValue(json, &webRootOrig, "webRoot");
211246
loadValue(json, &authRequired, "authRequired");
212247
loadValue(json, &authUser, "authUser");
213248
loadValue(json, &authPassword, "authPassword");
214249
loadValue(json, &responseHeaders, "responseHeaders");
215-
loadValue(json, &urlMappings, "urlMappings");
250+
loadValue(json, &urlMappingsOrig, "urlMappings");
251+
loadValue(json, &clientConfigDirOrig, "clientConfigDir");
216252
loadPermissions(json);
217253
}
218254

cpp/server/settings.hpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,23 @@ class SettingsData
3030
SettingsData();
3131
~SettingsData();
3232

33+
Path baseDir;
3334
int port = MSRV_DEFAULT_PORT;
3435
bool allowRemote = true;
35-
std::vector<std::string> musicDirsStr;
36+
std::vector<std::string> musicDirsOrig;
3637
std::vector<Path> musicDirs;
37-
std::string webRoot;
38-
std::string clientConfigDirStr;
38+
Path webRoot;
39+
std::string webRootOrig;
40+
std::string clientConfigDirOrig;
3941
Path clientConfigDir;
4042
ApiPermissions permissions = ApiPermissions::ALL;
4143

4244
bool authRequired = false;
4345
std::string authUser;
4446
std::string authPassword;
4547
std::unordered_map<std::string, std::string> responseHeaders;
46-
std::unordered_map<std::string, std::string> urlMappings;
48+
std::unordered_map<std::string, Path> urlMappings;
49+
std::unordered_map<std::string, std::string> urlMappingsOrig;
4750

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

63+
Path resolvePath(const Path& path)
64+
{
65+
return path.empty() || path.is_absolute() ? path : baseDir / path;
66+
}
67+
6068
private:
6169
void loadFromJson(const Json& json);
6270
void loadFromFile(const Path& path);

cpp/server/static_controller.cpp

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ ResponsePtr StaticController::getFile()
5252
if (requestPath.empty())
5353
return redirectToDirectory();
5454

55-
auto filePath = (targetDir_ / pathFromUtf8(requestPath)).lexically_normal().make_preferred();
55+
auto filePath = (targetDir_ / pathFromUtf8(requestPath)).lexically_normal();
5656

5757
if (!isSubpath(targetDir_, filePath))
5858
return Response::notFound();
@@ -89,19 +89,6 @@ void StaticController::defineRoutes(
8989
{
9090
for (auto& kv : settings->urlMappings)
9191
{
92-
if (kv.first.empty() || kv.first == "/")
93-
{
94-
logError("root url mapping is not allowed, use 'webRoot' instead");
95-
continue;
96-
}
97-
98-
if (kv.second.empty())
99-
{
100-
logError("url mapping '%s' has empty target", kv.first.c_str());
101-
continue;
102-
}
103-
104-
logInfo("using url mapping '%s' -> '%s'", kv.first.c_str(), kv.second.c_str());
10592
defineRoutes(router, workQueue, kv.first, kv.second, contentTypes);
10693
}
10794

@@ -113,43 +100,16 @@ void StaticController::defineRoutes(
113100
Router* router,
114101
WorkQueue* workQueue,
115102
const std::string& urlPrefix,
116-
const std::string& targetDir,
103+
const Path& targetDir,
117104
const ContentTypeMap& contentTypes)
118105
{
119-
if (urlPrefix.find(':') != std::string::npos)
120-
{
121-
logError("url mapping '%s' contains reserved character ':'", urlPrefix.c_str());
122-
return;
123-
}
124-
125-
std::string prefix;
126-
127-
if (urlPrefix.empty() || urlPrefix.front() != '/')
128-
prefix = "/" + urlPrefix;
129-
else
130-
prefix = urlPrefix;
131-
132-
if (prefix.back() != '/')
133-
prefix.push_back('/');
134-
135-
auto target = pathFromUtf8(targetDir).lexically_normal().make_preferred();
136-
137-
if (!target.is_absolute())
138-
{
139-
logError("url mapping '%s' target should be absolute, got '%s'", urlPrefix.c_str(), targetDir.c_str());
140-
return;
141-
}
142-
143106
auto routes = router->defineRoutes<StaticController>();
144107

145-
routes.createWith([=](Request* request) {
146-
return new StaticController(request, target, contentTypes);
147-
});
148-
108+
routes.createWith([=](Request* request) { return new StaticController(request, targetDir, contentTypes); });
149109
routes.useWorkQueue(workQueue);
150110

151-
routes.get(prefix, &StaticController::getFile);
152-
routes.get(prefix + ":path*", &StaticController::getFile);
111+
routes.get(urlPrefix, &StaticController::getFile);
112+
routes.get(urlPrefix + ":path*", &StaticController::getFile);
153113
}
154114

155115
}

cpp/server/static_controller.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class StaticController : public ControllerBase
3232
Router* router,
3333
WorkQueue* workQueue,
3434
const std::string& urlPrefix,
35-
const std::string& targetDir,
35+
const Path& targetDir,
3636
const ContentTypeMap& contentTypes);
3737

3838
private:

docs/advanced-config.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ The following options are available:
2929
}
3030
```
3131

32+
### Non-absolute paths
33+
34+
Unless specified otherwise any path in configuration could be non-absolute.
35+
36+
Such paths are resolved relative to `{player_profile_dir}/beefweb` directory.
37+
3238
### Network settings
3339

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

5056
### Web server settings
5157

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

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

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

8086
### Other settings
8187

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

0 commit comments

Comments
 (0)