Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for external URL to be used in web page. #2836

Merged
merged 2 commits into from
Jun 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions config/config2.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<xs:element ref="model" minOccurs="0"/>
<xs:element ref="manufacturerURL" minOccurs="0"/>
<xs:element ref="virtualURL" minOccurs="0"/>
<xs:element ref="externalURL" minOccurs="0"/>
<xs:element ref="presentationURL" minOccurs="0"/>
<xs:element ref="upnp-string-limit" minOccurs="0"/>
<xs:element ref="alive" minOccurs="0"/>
Expand Down
17 changes: 15 additions & 2 deletions doc/config-server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,21 @@ to enable special features that otherwise are only active with the vendor implem
* Optional
* Default: unset

This tag sets the virtual URL of Gerbera web UI, a custom setting may be necessary if you want to access the web page via a reverse proxy.
The value defaults to `http://<ip>:port`.
This tag sets the virtual URL of Gerbera content which is part of the browse response.
The value defaults to `http://<ip>:<port>`.

``externalURL``
~~~~~~~~~~~~~~

.. code-block:: xml

<externalURL>http://gerbera.io/</externalURL>

* Optional
* Default: unset

This tag sets the external URL of Gerbera web UI, a custom setting may be necessary if you want to access the web page via a reverse proxy.
The value defaults to virtualURL or `http://<ip>:<port>` if virtualURL is not set.

``modelName``
~~~~~~~~~~~~~
Expand Down
1 change: 1 addition & 0 deletions src/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ enum config_option_t {
CFG_TRANSCODING_MIMETYPE_PROF_MAP_ALLOW_UNUSED,
CFG_TRANSCODING_PROFILES_PROFILE_ALLOW_UNUSED,
CFG_VIRTUAL_URL,
CFG_EXTERNAL_URL,
CFG_IMPORT_SYSTEM_DIRECTORIES,
CFG_IMPORT_VISIBLE_DIRECTORIES,
CFG_UPNP_MULTI_VALUES_ENABLED,
Expand Down
3 changes: 3 additions & 0 deletions src/config/config_definition.cc
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,9 @@ const std::vector<std::shared_ptr<ConfigSetup>> ConfigDefinition::complexOptions
std::make_shared<ConfigStringSetup>(CFG_VIRTUAL_URL,
"/server/virtualURL", "config-server.html#virtualURL",
""),
std::make_shared<ConfigStringSetup>(CFG_EXTERNAL_URL,
"/server/externalURL", "config-server.html#externalURL",
""),
std::make_shared<ConfigStringSetup>(CFG_SERVER_MODEL_NAME,
"/server/modelName", "config-server.html#modelname",
DESC_MODEL_NAME),
Expand Down
83 changes: 58 additions & 25 deletions src/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,11 @@
std::string presentationURL = getPresentationUrl();

log_debug("Creating UpnpXMLBuilder");
xmlBuilder = std::make_shared<UpnpXMLBuilder>(context, getVirtualUrl(), presentationURL);
upnpXmlBuilder = std::make_shared<UpnpXMLBuilder>(context, getVirtualUrl(), presentationURL);
webXmlBuilder = std::make_shared<UpnpXMLBuilder>(context, getExternalUrl(), presentationURL);

// register root device with the library
auto desc = xmlBuilder->renderDeviceDescription();
auto desc = upnpXmlBuilder->renderDeviceDescription();
std::ostringstream buf;
desc->print(buf, "", 0);
std::string deviceDescription = buf.str();
Expand Down Expand Up @@ -213,48 +214,60 @@
}

log_debug("Creating ContentDirectoryService");
cds = std::make_unique<ContentDirectoryService>(context, xmlBuilder, rootDeviceHandle,
cds = std::make_unique<ContentDirectoryService>(context, upnpXmlBuilder, rootDeviceHandle,
config->getIntOption(CFG_SERVER_UPNP_TITLE_AND_DESC_STRING_LIMIT));

log_debug("Creating ConnectionManagerService");
cmgr = std::make_unique<ConnectionManagerService>(context, xmlBuilder, rootDeviceHandle);
cmgr = std::make_unique<ConnectionManagerService>(context, upnpXmlBuilder, rootDeviceHandle);

log_debug("Creating MRRegistrarService");
mrreg = std::make_unique<MRRegistrarService>(context, xmlBuilder, rootDeviceHandle);
mrreg = std::make_unique<MRRegistrarService>(context, upnpXmlBuilder, rootDeviceHandle);

// The advertisement will be sent by LibUPnP every (A/2)-30 seconds, and will have a cache-control max-age of A where A is
// the value configured here. Ex: A value of 62 will result in an SSDP advertisement being sent every second.
log_info("Will send UPnP Alive advertisements every {} seconds", (aliveAdvertisementInterval / 2) - 30);
log_debug("Will send UPnP Alive advertisements every {} seconds", (aliveAdvertisementInterval / 2) - 30);
ret = UpnpSendAdvertisement(rootDeviceHandle, aliveAdvertisementInterval);
if (ret != UPNP_E_SUCCESS) {
throw UpnpException(ret, fmt::format("run: UpnpSendAdvertisement {} failed", aliveAdvertisementInterval));
}

// UpnpSetAllowLiteralHostRedirection(1);
Fixed Show fixed Hide fixed

UpnpSetHostValidateCallback(
[](auto host, auto cookie) -> int {
auto hostStr = std::string(host);
auto ip = std::string(UpnpGetServerIpAddress());
auto ip6 = std::string(UpnpGetServerIp6Address());
auto ip6gla = std::string(UpnpGetServerUlaGuaIp6Address());

if (hostStr.find(ip) != std::string::npos || hostStr.find(ip6) != std::string::npos || hostStr.find(ip6gla) != std::string::npos) {
return UPNP_E_SUCCESS;
for (auto&& name : static_cast<Server*>(cookie)->validHosts) {
if (hostStr.find(name) != std::string::npos) {
return UPNP_E_SUCCESS;
} else if (name.find(hostStr) != std::string::npos) {
return UPNP_E_SUCCESS;
}
}

auto virtualURL = static_cast<Server*>(cookie)->getVirtualUrl();
if (!virtualURL.empty() && virtualURL.find(host) != std::string::npos) {
return UPNP_E_SUCCESS;
}
log_warning("Rejected attempt to load host '{}' as it does not match configured virtualURL: '{}'. "
log_warning("Rejected attempt to load host '{}' as it does not match configured virtualURL/externalURL: '{}'/'{}'. "
"See https://docs.gerbera.io/en/stable/config-server.html#virtualurl",
host, static_cast<Server*>(cookie)->config->getOption(CFG_VIRTUAL_URL));
host, static_cast<Server*>(cookie)->config->getOption(CFG_VIRTUAL_URL),
static_cast<Server*>(cookie)->config->getOption(CFG_EXTERNAL_URL));
return UPNP_E_BAD_HTTPMSG;
},
this);

std::string url = getVirtualUrl();
writeBookmark(url);
log_info("The Web UI can be reached by following this link: {}", url);

validHosts = std::vector<std::string> {
std::string(UpnpGetServerIpAddress()),
std::string(UpnpGetServerIp6Address()),
std::string(UpnpGetServerUlaGuaIp6Address()),
};
if (!url.empty()) {
validHosts.push_back(url);
}
url = getExternalUrl();
if (!url.empty()) {
validHosts.push_back(url);
}
}

int Server::startupInterface(const std::string& iface, in_port_t inPort)
Expand Down Expand Up @@ -296,15 +309,18 @@
{
std::string presentationURL = config->getOption(CFG_SERVER_PRESENTATION_URL);
if (presentationURL.empty()) {
presentationURL = fmt::format("http://{}:{}/", ip, port);
presentationURL = fmt::format("http://{}/", GrbNet::renderWebUri(ip, port));
} else {
auto appendto = EnumOption<UrlAppendMode>::getEnumOption(config, CFG_SERVER_APPEND_PRESENTATION_URL_TO);
if (appendto == UrlAppendMode::ip) {
presentationURL = fmt::format("http://{}:{}", ip, presentationURL);
presentationURL = fmt::format("{}/{}", GrbNet::renderWebUri(ip, 0), presentationURL);
} else if (appendto == UrlAppendMode::port) {
presentationURL = fmt::format("http://{}:{}/{}", ip, port, presentationURL);
presentationURL = fmt::format("{}/{}", GrbNet::renderWebUri(ip, port), presentationURL);
} // else appendto is none and we take the URL as it entered by user
}
if (!startswith(presentationURL, "http")) { // url does not start with http
presentationURL = fmt::format("http://{}", presentationURL);
}
return presentationURL;
}

Expand Down Expand Up @@ -342,6 +358,23 @@
return virtUrl;
}

std::string Server::getExternalUrl() const
{
auto virtUrl = config->getOption(CFG_EXTERNAL_URL);
if (virtUrl.empty()) {
virtUrl = config->getOption(CFG_VIRTUAL_URL);
if (virtUrl.empty()) {
virtUrl = GrbNet::renderWebUri(ip, port);
}
}
if (!startswith(virtUrl, "http")) { // url does not start with http
virtUrl = fmt::format("http://{}", virtUrl);
}
if (virtUrl.back() == '/')
virtUrl.pop_back();
return virtUrl;
}

bool Server::getShutdownStatus() const
{
return server_shutdown_flag;
Expand Down Expand Up @@ -415,7 +448,7 @@
case UPNP_CONTROL_ACTION_REQUEST:
log_debug("UPNP_CONTROL_ACTION_REQUEST");
try {
auto request = ActionRequest(xmlBuilder, clientManager, static_cast<UpnpActionRequest*>(const_cast<void*>(event)));
auto request = ActionRequest(upnpXmlBuilder, clientManager, static_cast<UpnpActionRequest*>(const_cast<void*>(event)));
routeActionRequest(request);
request.update();
} catch (const UpnpException& upnpE) {
Expand Down Expand Up @@ -547,7 +580,7 @@
log_debug("Filename: {}", filename);

if (startswith(link, fmt::format("/{}/{}", SERVER_VIRTUAL_DIR, CONTENT_MEDIA_HANDLER))) {
return std::make_unique<FileRequestHandler>(content, xmlBuilder);
return std::make_unique<FileRequestHandler>(content, upnpXmlBuilder);
}

if (startswith(link, fmt::format("/{}/{}", SERVER_VIRTUAL_DIR, CONTENT_UI_HANDLER))) {
Expand All @@ -558,11 +591,11 @@
auto it = params.find(URL_REQUEST_TYPE);
std::string rType = it != params.end() && !it->second.empty() ? it->second : "index";

return Web::createWebRequestHandler(context, content, xmlBuilder, rType);
return Web::createWebRequestHandler(context, content, webXmlBuilder, rType);
}

if (startswith(link, fmt::format("/{}/{}", SERVER_VIRTUAL_DIR, DEVICE_DESCRIPTION_PATH))) {
return std::make_unique<DeviceDescriptionHandler>(content, xmlBuilder);
return std::make_unique<DeviceDescriptionHandler>(content, upnpXmlBuilder);
}

#if defined(HAVE_CURL)
Expand Down
7 changes: 5 additions & 2 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class Server : public std::enable_shared_from_this<Server> {

std::string ip;
in_port_t port {};
std::vector<std::string> validHosts {};

/// \brief This flag is set to true by the upnp_cleanup() function.
bool server_shutdown_flag {};
Expand Down Expand Up @@ -127,7 +128,8 @@ class Server : public std::enable_shared_from_this<Server> {
/// The value is read from the configuration.
int aliveAdvertisementInterval {};

std::shared_ptr<UpnpXMLBuilder> xmlBuilder;
std::shared_ptr<UpnpXMLBuilder> upnpXmlBuilder;
std::shared_ptr<UpnpXMLBuilder> webXmlBuilder;

/// \brief ContentDirectoryService instance.
///
Expand Down Expand Up @@ -211,7 +213,6 @@ class Server : public std::enable_shared_from_this<Server> {
void writeBookmark(const std::string& addr);
void emptyBookmark();

std::string getPresentationUrl() const;
int startupInterface(const std::string& iface, in_port_t inPort);

/// \brief Returns the content url of the server.
Expand All @@ -221,6 +222,8 @@ class Server : public std::enable_shared_from_this<Server> {
/// that we actually get that port after startup. This function
/// contains the port on which the server is actually running.
std::string getVirtualUrl() const;
std::string getExternalUrl() const;
std::string getPresentationUrl() const;
};

#endif // __SERVER_H__
8 changes: 7 additions & 1 deletion src/util/grb_net.cc
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,13 @@ std::string GrbNet::ipToInterface(std::string_view ip)

std::string GrbNet::renderWebUri(std::string_view ip, in_port_t port)
{
if (ip.find(':') != std::string_view::npos)
if (port == 0) {
if (ip.find(':') != std::string_view::npos) {
return fmt::format("[{}]", ip);
}
return fmt::format("{}", ip);
} else if (ip.find(':') != std::string_view::npos) {
return fmt::format("[{}]:{}", ip, port);
}
return fmt::format("{}:{}", ip, port);
}