diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/browser/AbstractBrowserPDFPrinter.java b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/browser/AbstractBrowserPDFPrinter.java index b5a9d0928651..3343c0c4175e 100644 --- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/browser/AbstractBrowserPDFPrinter.java +++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/browser/AbstractBrowserPDFPrinter.java @@ -26,8 +26,10 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.Enumeration; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -54,6 +56,10 @@ */ public abstract class AbstractBrowserPDFPrinter implements PDFPrinter { + private static final String HTTP_HEADER_FORWARDED = "Forwarded"; + + private static final String HTTP_HEADER_FORWARDED_FOR = "X-Forwarded-For"; + @Inject protected Logger logger; @@ -75,6 +81,7 @@ public InputStream print(URL printPreviewURL) throws IOException CookieFilterContext cookieFilterContext = findCookieFilterContext(printPreviewURL, browserTab); Cookie[] cookies = getCookies(cookieFilterContext); try { + browserTab.setExtraHTTPHeaders(getExtraHTTPHeaders(cookieFilterContext)); if (!browserTab.navigate(cookieFilterContext.getTargetURL(), cookies, true, this.configuration.getPageReadyTimeout())) { throw new IOException("Failed to load the print preview URL: " + cookieFilterContext.getTargetURL()); @@ -180,7 +187,7 @@ private Optional getCookieFilterContext(URL targetURL, bool BrowserTab browserTab) { Optional browserIPAddress = isFilterRequired ? getBrowserIPAddress(targetURL, browserTab) - : Optional.of(StringUtils.defaultString(getRequest().getHeader("X-Forwarded-For"))); + : Optional.of(StringUtils.defaultString(getRequest().getHeader(HTTP_HEADER_FORWARDED_FOR))); return browserIPAddress.map(ip -> new CookieFilterContext() { @Override @@ -215,6 +222,38 @@ private Optional getBrowserIPAddress(URL targetURL, BrowserTab browserTa return Optional.empty(); } + private Map> getExtraHTTPHeaders(CookieFilterContext cookieFilterContext) + { + HttpServletRequest request = getRequest(); + + List forwarded = new LinkedList<>(); + Enumeration forwardedValues = request.getHeaders(HTTP_HEADER_FORWARDED); + if (forwardedValues != null) { + forwardedValues.asIterator().forEachRemaining(forwarded::add); + } + + String forwardedFor = request.getHeader(HTTP_HEADER_FORWARDED_FOR); + if (StringUtils.isBlank(forwardedFor)) { + forwardedFor = request.getRemoteAddr(); + } + + String host = request.getHeader("X-Forwarded-Host"); + if (StringUtils.isBlank(host)) { + host = request.getHeader("Host"); + } + + String protocol = request.getHeader("X-Forwarded-Proto"); + if (StringUtils.isBlank(protocol)) { + protocol = request.getScheme(); + } + + String lastForwarded = String.format("by=%s;for=%s;host=%s;proto=%s", cookieFilterContext.getBrowserIPAddress(), + forwardedFor, host, protocol); + forwarded.add(lastForwarded); + + return Map.of(HTTP_HEADER_FORWARDED, forwarded); + } + @Override public boolean isAvailable() { diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/browser/BrowserTab.java b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/browser/BrowserTab.java index 434a0cd54f54..f482c1ea1949 100644 --- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/browser/BrowserTab.java +++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/browser/BrowserTab.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.List; +import java.util.Map; import javax.servlet.http.Cookie; @@ -33,6 +35,20 @@ */ public interface BrowserTab extends AutoCloseable { + /** + * Sets the extra HTTP headers to use when requesting web pages in this browser tab. + * + * @param headers the extra HTTP headers to use when requesting web pages in this browser tab + * @since 15.10.16 + * @since 16.4.6 + * @since 16.10.2 + * @since 17.0.0RC1 + */ + default void setExtraHTTPHeaders(Map> headers) + { + // Do nothing by default. + } + /** * Navigates to the specified web page, optionally waiting for it to be ready (fully loaded). * diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/test/java/org/xwiki/export/pdf/browser/BrowserPDFPrinterTest.java b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/test/java/org/xwiki/export/pdf/browser/BrowserPDFPrinterTest.java index 342560b4aeb5..9023af677b09 100644 --- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/test/java/org/xwiki/export/pdf/browser/BrowserPDFPrinterTest.java +++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/test/java/org/xwiki/export/pdf/browser/BrowserPDFPrinterTest.java @@ -37,6 +37,7 @@ import java.net.URL; import java.util.Arrays; import java.util.List; +import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -127,6 +128,10 @@ void print() throws Exception Cookie[] cookies = new Cookie[] {mock(Cookie.class)}; when(this.request.getCookies()).thenReturn(cookies); + when(this.request.getRemoteAddr()).thenReturn("192.168.0.118"); + when(this.request.getHeader("Host")).thenReturn("external:9293"); + when(this.request.getScheme()).thenReturn("https"); + when(this.browserManager.createIncognitoTab()).thenReturn(this.browserTab); when(this.browserTab.navigate(new URL("http://xwiki-host:9293/xwiki/rest/client?media=json"))).thenReturn(true); when(this.browserTab.getSource()).thenReturn("{\"ip\":\"172.12.0.3\"}"); @@ -152,6 +157,9 @@ public InputStream answer(InvocationOnMock invocation) throws Throwable assertEquals("172.12.0.3", this.cookieFilterContextCaptor.getValue().getBrowserIPAddress()); assertEquals(browserPrintPreviewURL, this.cookieFilterContextCaptor.getValue().getTargetURL()); + verify(this.browserTab).setExtraHTTPHeaders( + Map.of("Forwarded", List.of("by=172.12.0.3;for=192.168.0.118;host=external:9293;proto=https"))); + // Only the configured XWiki URI should be used to get the browser IP address. verify(this.browserTab, never()).navigate(new URL("http://external:9293/xwiki/rest/client?media=json")); verify(this.browserTab).close(); @@ -213,6 +221,27 @@ void printWithXWikiSchemeAndPortSpecified() throws Exception verify(this.browserTab).navigate(new URL("ftp://xwiki-host:8080/xwiki/rest/client?media=json")); } + @Test + void printWithReverseProxy() throws Exception + { + URL printPreviewURL = new URL("http://external:9293/xwiki/bin/export/Some/Page?x=y#z"); + URL browserPrintPreviewURL = new URL("http://xwiki-host:9293/xwiki/bin/export/Some/Page?x=y#z"); + + when(this.cookieFilter.isFilterRequired()).thenReturn(false); + + when(this.request.getHeader("X-Forwarded-For")).thenReturn("192.168.0.117"); + when(this.request.getHeader("Host")).thenReturn("external:9293"); + when(this.request.getHeader("X-Forwarded-Proto")).thenReturn("ftp"); + + when(this.browserManager.createIncognitoTab()).thenReturn(this.browserTab); + when(this.browserTab.navigate(browserPrintPreviewURL, null, true, 30)).thenReturn(true); + + this.printer.print(printPreviewURL); + + verify(this.browserTab).setExtraHTTPHeaders( + Map.of("Forwarded", List.of("by=192.168.0.117;for=192.168.0.117;host=external:9293;proto=ftp"))); + } + @Test void isAvailable() { diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-default/src/main/java/org/xwiki/export/pdf/internal/chrome/ChromeTab.java b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-default/src/main/java/org/xwiki/export/pdf/internal/chrome/ChromeTab.java index 671fdcbfb94c..02fe18da12d4 100644 --- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-default/src/main/java/org/xwiki/export/pdf/internal/chrome/ChromeTab.java +++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-default/src/main/java/org/xwiki/export/pdf/internal/chrome/ChromeTab.java @@ -25,6 +25,7 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -32,6 +33,7 @@ import javax.servlet.http.Cookie; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xwiki.export.pdf.PDFExportConfiguration; @@ -256,4 +258,18 @@ private void setCookies(Cookie[] servletCookies, URL targetURL) network.clearBrowserCookies(); network.setCookies(browserCookies); } + + @Override + public void setExtraHTTPHeaders(Map> headers) + { + LOGGER.debug("Setting extra HTTP headers [{}].", headers); + Network network = this.tabDevToolsService.getNetwork(); + network.enable(); + // The documentation of setExtraHTTPHeaders is not clear about the type of value we can pass for a header (key). + // We tried passing a List and a String[] but in both cases we got an exception: "Invalid header value, + // string expected". So we concatenate the values of a header with a comma. + Map extraHeaders = headers.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> StringUtils.join(entry.getValue(), ","))); + network.setExtraHTTPHeaders(extraHeaders); + } } diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-test/xwiki-platform-export-pdf-test-docker/src/test/it/org/xwiki/export/pdf/test/ui/PDFExportIT.java b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-test/xwiki-platform-export-pdf-test-docker/src/test/it/org/xwiki/export/pdf/test/ui/PDFExportIT.java index 754883c0e6f4..2f7efe4a2647 100644 --- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-test/xwiki-platform-export-pdf-test-docker/src/test/it/org/xwiki/export/pdf/test/ui/PDFExportIT.java +++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-test/xwiki-platform-export-pdf-test-docker/src/test/it/org/xwiki/export/pdf/test/ui/PDFExportIT.java @@ -19,6 +19,10 @@ */ package org.xwiki.export.pdf.test.ui; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.net.URL; import java.util.Arrays; import java.util.Collections; @@ -54,12 +58,9 @@ import org.xwiki.test.docker.junit5.UITest; import org.xwiki.test.ui.TestUtils; import org.xwiki.test.ui.po.LiveTableElement; +import org.xwiki.test.ui.po.SuggestInputElement; import org.xwiki.test.ui.po.ViewPage; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * Tests for PDF export. * @@ -296,7 +297,8 @@ void exportWithCustomPDFTemplate(TestUtils setup, TestReference testReference, T setup.createPage(testReference, "", "").createPage().createPageFromTemplate("My cool template", null, null, "XWiki.PDFExport.TemplateProvider"); PDFTemplateEditPage templateEditPage = new PDFTemplateEditPage(); - templateEditPage.setCover(templateEditPage.getCover().replace("

", "

Book: ")); + templateEditPage.setCover(templateEditPage.getCover().replace("

", "

Book: ").replace("

", + "

\n

$escapetool.xml($tdoc.externalURL)

")); templateEditPage .setTableOfContents(templateEditPage.getTableOfContents().replace("core.pdf.tableOfContents", "Chapters")); templateEditPage.setHeader(templateEditPage.getHeader().replaceFirst("