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("