Skip to content

Commit

Permalink
Add option for external URL to be used in web page.
Browse files Browse the repository at this point in the history
  • Loading branch information
KarlStraussberger committed Jun 25, 2023
1 parent d4415ab commit b3c98ff
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 31 deletions.
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
85 changes: 59 additions & 26 deletions src/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,11 @@ void Server::run()
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 @@ void Server::run()
}

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);

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 Server::getPresentationUrl() const
{
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 @@ std::string Server::getVirtualUrl() const
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 @@ int Server::handleUpnpRootDeviceEvent(Upnp_EventType eventType, const void* even
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 @@ std::unique_ptr<RequestHandler> Server::createRequestHandler(const char* filenam
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,16 +591,16 @@ std::unique_ptr<RequestHandler> Server::createRequestHandler(const char* filenam
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)
if (startswith(link, fmt::format("/{}/{}", SERVER_VIRTUAL_DIR, CONTENT_ONLINE_HANDLER))) {
return std::make_unique<URLRequestHandler>(content);
return std::make_unique<URLRequestHandler>(content, upnpXmlBuilder);
}
#endif

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);
}

0 comments on commit b3c98ff

Please sign in to comment.