diff --git a/documentation/PROXY.md b/documentation/PROXY.md index 09ec7c6a..beba787b 100644 --- a/documentation/PROXY.md +++ b/documentation/PROXY.md @@ -49,6 +49,23 @@ libresonic.war, you do not need to change anything. ## Reverse proxy configuration +#### How it works +Libresonic expects proxies to provide information about their incoming URL so that Libresonic can craft it when needed. +To do so, Libresonic looks for the following HTTP headers: + + - `X-Forwarded-Host` + - Provides server name and optionally port in the case that the proxy is on a non-standard port + - `X-Forwarded-Proto` + - Tells Libresonic whether to craft an HTTP or HTTPS url + - `X-Forwarded-Server` + - This is only a fallback in the case that `X-Forwarded-Host` is not available + +Currently this is used wherever, `NetworkService#getBaseUrl` is called. A couple notable places include: + +- Stream urls +- Share urls +- Coverart urls + ### Nginx The following configuration works for Nginx (HTTPS with HTTP redirection): @@ -74,6 +91,7 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $http_host; proxy_set_header Host $http_host; proxy_max_temp_file_size 0; proxy_pass http://127.0.0.1:4040; @@ -98,6 +116,8 @@ The following configuration works for Apache (without HTTPS): ### HAProxy +**OUTDATED since `X-Forwarded-Host`/`X-FORWARDED-PROTO` change** + The following configuration works for HAProxy (HTTP and HTTPS): ```haproxy diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java b/libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java index c3c1d663..f470171c 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java @@ -19,6 +19,8 @@ */ package org.libresonic.player.service; +import org.apache.commons.lang3.StringUtils; +import org.libresonic.player.Logger; import org.springframework.web.util.UrlPathHelper; import javax.servlet.http.HttpServletRequest; @@ -28,22 +30,62 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; + public class NetworkService { + private static UrlPathHelper urlPathHelper = new UrlPathHelper(); + private static final String X_FORWARDED_SERVER = "X-Forwarded-Server"; + private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto"; + private static final String X_FORWARDED_HOST = "X-Forwarded-Host"; + + private final static Logger LOG = Logger.getLogger(NetworkService.class); + public static String getBaseUrl(HttpServletRequest request) { try { - UrlPathHelper urlPathHelper = new UrlPathHelper(); - URL url = new URL(request.getRequestURL().toString()); - String host = url.getHost(); - String userInfo = url.getUserInfo(); - String scheme = url.getProtocol(); - int port = url.getPort(); - - URI uri = new URI(scheme, userInfo, host, port, urlPathHelper.getContextPath(request), null, null); - return uri.toString() + "/"; + URI uri; + try { + uri = calculateProxyUri(request); + } catch (Exception e) { + LOG.debug("Could not calculate proxy uri", e); + uri = calculateNonProxyUri(request); + } + + String baseUrl = uri.toString() + "/"; + LOG.debug("Calculated base url to " + baseUrl); + return baseUrl; } catch (MalformedURLException | URISyntaxException e) { throw new RuntimeException("Could not calculate base url", e); } } + private static URI calculateProxyUri(HttpServletRequest request) throws URISyntaxException { + String xForardedHost = request.getHeader(X_FORWARDED_HOST); + if(!isValidXForwardedHost(xForardedHost)) { + xForardedHost = request.getHeader(X_FORWARDED_SERVER); + if(!isValidXForwardedHost(xForardedHost)) { + throw new RuntimeException("Cannot calculate proxy uri without HTTP header " + X_FORWARDED_HOST); + } + } + + URI proxyHost = new URI("ignored://" + xForardedHost); + String host = proxyHost.getHost(); + int port = proxyHost.getPort(); + String scheme = request.getHeader(X_FORWARDED_PROTO); + + return new URI(scheme, null, host, port, urlPathHelper.getContextPath(request), null, null); + } + + private static boolean isValidXForwardedHost(String xForardedHost) { + return StringUtils.isNotBlank(xForardedHost) && !StringUtils.equals("null", xForardedHost); + } + + private static URI calculateNonProxyUri(HttpServletRequest request) throws MalformedURLException, URISyntaxException { + URL url = new URL(request.getRequestURL().toString()); + String host = url.getHost(); + String scheme = url.getProtocol(); + int port = url.getPort(); + String userInfo = url.getUserInfo(); + return new URI(scheme, userInfo, host, port, urlPathHelper.getContextPath(request), null, null); + } + }