From d4a7eee241d1494757f409c4769c2293f573b235 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 04:13:44 +0000
Subject: [PATCH 1/5] Initial plan
From ab0e924b3af3ff469574af50f5cbd7f0d91b3e56 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 04:19:46 +0000
Subject: [PATCH 2/5] Add browser-like headers to TempFileAPI URL requests to
fix remote image downloads
Co-authored-by: fmontes <751424+fmontes@users.noreply.github.com>
---
.../dotcms/rest/api/v1/temp/TempFileAPI.java | 26 ++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
index 15ff0f3cdad0..55e3cd93651a 100644
--- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
@@ -23,6 +23,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.liferay.portal.model.User;
import com.liferay.portal.util.PortalUtil;
@@ -43,6 +44,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -72,6 +74,26 @@ public class TempFileAPI {
private static final String WHO_CAN_USE_TEMP_FILE = "whoCanUse.tmp";
private static final String TEMP_RESOURCE_BY_URL_ADMIN_ONLY="TEMP_RESOURCE_BY_URL_ADMIN_ONLY";
private static final Lazy allowAccessToPrivateSubnets = Lazy.of(()->Config.getBooleanProperty("ALLOW_ACCESS_TO_PRIVATE_SUBNETS", false));
+
+ /**
+ * Browser-like HTTP request headers used when downloading remote files via URL.
+ * Many image servers (CDNs, Unsplash, Pexels, Cloudflare-protected sites) reject
+ * requests that lack standard browser headers, returning 403 or 406 responses.
+ */
+ static final Map BROWSER_HEADERS = ImmutableMap.builder()
+ .put("User-Agent", Config.getStringProperty("TEMP_FILE_URL_USER_AGENT",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"))
+ .put("Accept", Config.getStringProperty("TEMP_FILE_URL_ACCEPT",
+ "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"))
+ .put("Accept-Language", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_LANGUAGE",
+ "en-US,en;q=0.9"))
+ .put("Accept-Encoding", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_ENCODING",
+ "gzip, deflate, br"))
+ .put("Connection", "keep-alive")
+ .put("Sec-Fetch-Dest", "image")
+ .put("Sec-Fetch-Mode", "no-cors")
+ .put("Sec-Fetch-Site", "cross-site")
+ .build();
/**
@@ -242,6 +264,7 @@ public DotTempFile createTempFileFromUrl(final String incomingFileName,
Files.newOutputStream(tempFile.toPath()))){
final CircuitBreakerUrl urlGetter =
CircuitBreakerUrl.builder().setMethod(Method.GET).setUrl(finalUrl)
+ .setHeaders(BROWSER_HEADERS)
.setTimeout(timeoutSeconds * 1000L).build();
urlGetter.doOut(out);
}
@@ -270,7 +293,8 @@ public boolean validUrl(final String url) {
String done;
try {
final CircuitBreakerUrl urlGetter =
- CircuitBreakerUrl.builder().setMethod(Method.GET).setUrl(url).build();
+ CircuitBreakerUrl.builder().setMethod(Method.GET).setUrl(url)
+ .setHeaders(BROWSER_HEADERS).build();
done = urlGetter.doString();
} catch (IOException | BadRequestException e) {//If response is not 200, CircuitBreakerUrl throws BadRequestException
return false;
From f35c3221c8983adf87fa27a249650e64673012d1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 20:52:16 +0000
Subject: [PATCH 3/5] Add tests for BROWSER_HEADERS constant in TempFileAPITest
Co-authored-by: fmontes <751424+fmontes@users.noreply.github.com>
---
.../rest/api/v1/temp/TempFileAPITest.java | 58 +++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
index a68cb204898d..f60106c1a5e2 100644
--- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
+++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
@@ -3,6 +3,7 @@
import static com.dotcms.datagen.TestDataUtils.getFileAssetContent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -18,6 +19,8 @@
import com.liferay.portal.util.WebKeys;
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.jetbrains.annotations.NotNull;
@@ -108,5 +111,60 @@ public static HttpServletRequest mockHttpServletRequest() {
return request;
}
+ /**
+ * Method to test: {@link TempFileAPI#BROWSER_HEADERS}
+ * Test scenario: Verify that all required browser-like header keys are present
+ * Expected: The map contains all 8 required header keys
+ */
+ @Test
+ public void testBrowserHeaders_containsAllRequiredKeys() {
+ final List requiredHeaders = Arrays.asList(
+ "User-Agent",
+ "Accept",
+ "Accept-Language",
+ "Accept-Encoding",
+ "Connection",
+ "Sec-Fetch-Dest",
+ "Sec-Fetch-Mode",
+ "Sec-Fetch-Site"
+ );
+
+ assertNotNull("BROWSER_HEADERS must not be null", TempFileAPI.BROWSER_HEADERS);
+ for (final String header : requiredHeaders) {
+ assertTrue(
+ "BROWSER_HEADERS must contain header: " + header,
+ TempFileAPI.BROWSER_HEADERS.containsKey(header));
+ assertNotNull(
+ "Value for header '" + header + "' must not be null",
+ TempFileAPI.BROWSER_HEADERS.get(header));
+ assertFalse(
+ "Value for header '" + header + "' must not be empty",
+ TempFileAPI.BROWSER_HEADERS.get(header).isEmpty());
+ }
+ }
+
+ /**
+ * Method to test: {@link TempFileAPI#BROWSER_HEADERS}
+ * Test scenario: Verify the default values of the browser-like headers
+ * Expected: Static/non-configurable headers have their expected default values;
+ * configurable headers have valid non-empty values
+ */
+ @Test
+ public void testBrowserHeaders_defaultValues() {
+ assertEquals("keep-alive", TempFileAPI.BROWSER_HEADERS.get("Connection"));
+ assertEquals("image", TempFileAPI.BROWSER_HEADERS.get("Sec-Fetch-Dest"));
+ assertEquals("no-cors", TempFileAPI.BROWSER_HEADERS.get("Sec-Fetch-Mode"));
+ assertEquals("cross-site", TempFileAPI.BROWSER_HEADERS.get("Sec-Fetch-Site"));
+
+ // Configurable headers must be present and non-empty (may be overridden via Config)
+ assertTrue("User-Agent must not be empty",
+ !TempFileAPI.BROWSER_HEADERS.get("User-Agent").isEmpty());
+ assertTrue("Accept must not be empty",
+ !TempFileAPI.BROWSER_HEADERS.get("Accept").isEmpty());
+ assertTrue("Accept-Language must not be empty",
+ !TempFileAPI.BROWSER_HEADERS.get("Accept-Language").isEmpty());
+ assertTrue("Accept-Encoding must not be empty",
+ !TempFileAPI.BROWSER_HEADERS.get("Accept-Encoding").isEmpty());
+ }
}
From 880ef976d1b2f451bf43e4e1f4bfc3195eb1ba4f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 21:17:56 +0000
Subject: [PATCH 4/5] Address code review: getBrowserHeaders() method, drop
Accept-Encoding br, add wire-level header test
Co-authored-by: fmontes <751424+fmontes@users.noreply.github.com>
---
.../dotcms/rest/api/v1/temp/TempFileAPI.java | 38 ++++---
.../rest/api/v1/temp/TempFileAPITest.java | 101 +++++++++++++++---
2 files changed, 105 insertions(+), 34 deletions(-)
diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
index 55e3cd93651a..9f0d6b3e2ec9 100644
--- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
@@ -76,24 +76,28 @@ public class TempFileAPI {
private static final Lazy allowAccessToPrivateSubnets = Lazy.of(()->Config.getBooleanProperty("ALLOW_ACCESS_TO_PRIVATE_SUBNETS", false));
/**
- * Browser-like HTTP request headers used when downloading remote files via URL.
+ * Builds a map of browser-like HTTP request headers for remote URL downloads.
* Many image servers (CDNs, Unsplash, Pexels, Cloudflare-protected sites) reject
* requests that lack standard browser headers, returning 403 or 406 responses.
+ * The four content-negotiation headers are re-read from {@link Config} on every call
+ * so that runtime changes (system table updates / hot-reload) take effect without a restart.
*/
- static final Map BROWSER_HEADERS = ImmutableMap.builder()
- .put("User-Agent", Config.getStringProperty("TEMP_FILE_URL_USER_AGENT",
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"))
- .put("Accept", Config.getStringProperty("TEMP_FILE_URL_ACCEPT",
- "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"))
- .put("Accept-Language", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_LANGUAGE",
- "en-US,en;q=0.9"))
- .put("Accept-Encoding", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_ENCODING",
- "gzip, deflate, br"))
- .put("Connection", "keep-alive")
- .put("Sec-Fetch-Dest", "image")
- .put("Sec-Fetch-Mode", "no-cors")
- .put("Sec-Fetch-Site", "cross-site")
- .build();
+ static Map getBrowserHeaders() {
+ return ImmutableMap.builder()
+ .put("User-Agent", Config.getStringProperty("TEMP_FILE_URL_USER_AGENT",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"))
+ .put("Accept", Config.getStringProperty("TEMP_FILE_URL_ACCEPT",
+ "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"))
+ .put("Accept-Language", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_LANGUAGE",
+ "en-US,en;q=0.9"))
+ .put("Accept-Encoding", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_ENCODING",
+ "gzip, deflate"))
+ .put("Connection", "keep-alive")
+ .put("Sec-Fetch-Dest", "image")
+ .put("Sec-Fetch-Mode", "no-cors")
+ .put("Sec-Fetch-Site", "cross-site")
+ .build();
+ }
/**
@@ -264,7 +268,7 @@ public DotTempFile createTempFileFromUrl(final String incomingFileName,
Files.newOutputStream(tempFile.toPath()))){
final CircuitBreakerUrl urlGetter =
CircuitBreakerUrl.builder().setMethod(Method.GET).setUrl(finalUrl)
- .setHeaders(BROWSER_HEADERS)
+ .setHeaders(getBrowserHeaders())
.setTimeout(timeoutSeconds * 1000L).build();
urlGetter.doOut(out);
}
@@ -294,7 +298,7 @@ public boolean validUrl(final String url) {
try {
final CircuitBreakerUrl urlGetter =
CircuitBreakerUrl.builder().setMethod(Method.GET).setUrl(url)
- .setHeaders(BROWSER_HEADERS).build();
+ .setHeaders(getBrowserHeaders()).build();
done = urlGetter.doString();
} catch (IOException | BadRequestException e) {//If response is not 200, CircuitBreakerUrl throws BadRequestException
return false;
diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
index f60106c1a5e2..d559bb6f1208 100644
--- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
+++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
@@ -10,8 +10,11 @@
import com.dotcms.datagen.TestDataUtils;
import com.dotcms.datagen.TestDataUtils.TestFile;
+import com.dotcms.http.server.mock.MockHttpServer;
+import com.dotcms.http.server.mock.MockHttpServerContext;
import com.dotcms.mock.request.MockSession;
import com.dotcms.util.IntegrationTestInitService;
+import com.dotcms.util.network.IPUtils;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.util.FileUtil;
@@ -19,9 +22,12 @@
import com.liferay.portal.util.WebKeys;
import java.io.File;
import java.io.IOException;
+import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServletRequest;
import org.jetbrains.annotations.NotNull;
import org.junit.BeforeClass;
@@ -112,7 +118,7 @@ public static HttpServletRequest mockHttpServletRequest() {
}
/**
- * Method to test: {@link TempFileAPI#BROWSER_HEADERS}
+ * Method to test: {@link TempFileAPI#getBrowserHeaders()}
* Test scenario: Verify that all required browser-like header keys are present
* Expected: The map contains all 8 required header keys
*/
@@ -129,42 +135,103 @@ public void testBrowserHeaders_containsAllRequiredKeys() {
"Sec-Fetch-Site"
);
- assertNotNull("BROWSER_HEADERS must not be null", TempFileAPI.BROWSER_HEADERS);
+ final Map headers = TempFileAPI.getBrowserHeaders();
+ assertNotNull("getBrowserHeaders() must not return null", headers);
for (final String header : requiredHeaders) {
assertTrue(
- "BROWSER_HEADERS must contain header: " + header,
- TempFileAPI.BROWSER_HEADERS.containsKey(header));
+ "getBrowserHeaders() must contain header: " + header,
+ headers.containsKey(header));
assertNotNull(
"Value for header '" + header + "' must not be null",
- TempFileAPI.BROWSER_HEADERS.get(header));
+ headers.get(header));
assertFalse(
"Value for header '" + header + "' must not be empty",
- TempFileAPI.BROWSER_HEADERS.get(header).isEmpty());
+ headers.get(header).isEmpty());
}
}
/**
- * Method to test: {@link TempFileAPI#BROWSER_HEADERS}
+ * Method to test: {@link TempFileAPI#getBrowserHeaders()}
* Test scenario: Verify the default values of the browser-like headers
* Expected: Static/non-configurable headers have their expected default values;
- * configurable headers have valid non-empty values
+ * configurable headers have valid non-empty values;
+ * Accept-Encoding does not advertise Brotli (br) since Apache HttpClient lacks a Brotli decoder
*/
@Test
public void testBrowserHeaders_defaultValues() {
- assertEquals("keep-alive", TempFileAPI.BROWSER_HEADERS.get("Connection"));
- assertEquals("image", TempFileAPI.BROWSER_HEADERS.get("Sec-Fetch-Dest"));
- assertEquals("no-cors", TempFileAPI.BROWSER_HEADERS.get("Sec-Fetch-Mode"));
- assertEquals("cross-site", TempFileAPI.BROWSER_HEADERS.get("Sec-Fetch-Site"));
+ final Map headers = TempFileAPI.getBrowserHeaders();
+ assertEquals("keep-alive", headers.get("Connection"));
+ assertEquals("image", headers.get("Sec-Fetch-Dest"));
+ assertEquals("no-cors", headers.get("Sec-Fetch-Mode"));
+ assertEquals("cross-site", headers.get("Sec-Fetch-Site"));
// Configurable headers must be present and non-empty (may be overridden via Config)
assertTrue("User-Agent must not be empty",
- !TempFileAPI.BROWSER_HEADERS.get("User-Agent").isEmpty());
+ !headers.get("User-Agent").isEmpty());
assertTrue("Accept must not be empty",
- !TempFileAPI.BROWSER_HEADERS.get("Accept").isEmpty());
+ !headers.get("Accept").isEmpty());
assertTrue("Accept-Language must not be empty",
- !TempFileAPI.BROWSER_HEADERS.get("Accept-Language").isEmpty());
- assertTrue("Accept-Encoding must not be empty",
- !TempFileAPI.BROWSER_HEADERS.get("Accept-Encoding").isEmpty());
+ !headers.get("Accept-Language").isEmpty());
+
+ // Accept-Encoding must not advertise brotli; Apache HttpClient has no brotli decoder
+ final String acceptEncoding = headers.get("Accept-Encoding");
+ assertTrue("Accept-Encoding must not be empty", !acceptEncoding.isEmpty());
+ assertFalse("Accept-Encoding must not advertise brotli (br)", acceptEncoding.contains("br"));
+ }
+
+ /**
+ * Method to test: {@link TempFileAPI#validUrl(String)}
+ * Test scenario: Verify that browser-like headers are actually sent in the outbound HTTP request
+ * Expected: All headers returned by getBrowserHeaders() arrive at the mock server
+ */
+ @Test
+ public void testValidUrl_sendsBrowserHeaders() {
+ final String mockIp = "127.0.0.1";
+ final int mockPort = 50881;
+ final String path = "/image.png";
+
+ // Capture the incoming request headers via an AtomicReference
+ final AtomicReference capturedHeaders =
+ new AtomicReference<>();
+
+ final MockHttpServerContext context = new MockHttpServerContext.Builder()
+ .uri(path)
+ .responseStatus(HttpURLConnection.HTTP_OK)
+ .responseBody("OK")
+ .requestCondition(
+ "Capture request headers",
+ requestCtx -> {
+ capturedHeaders.set(requestCtx.getHeaders());
+ return true;
+ })
+ .mustBeCalled()
+ .build();
+
+ final MockHttpServer mockHttpServer = new MockHttpServer(mockIp, mockPort);
+ mockHttpServer.addContext(context);
+ mockHttpServer.start();
+ IPUtils.disabledIpPrivateSubnet(true);
+
+ try {
+ final boolean valid = APILocator.getTempFileAPI()
+ .validUrl("http://" + mockIp + ":" + mockPort + path);
+
+ assertTrue("validUrl should return true for a 200 response", valid);
+ mockHttpServer.validate();
+
+ final com.sun.net.httpserver.Headers received = capturedHeaders.get();
+ assertNotNull("Request headers must have been captured", received);
+
+ // Every header in getBrowserHeaders() must have been sent to the server
+ for (final String expectedHeader : TempFileAPI.getBrowserHeaders().keySet()) {
+ assertTrue(
+ "Outbound request must include header: " + expectedHeader,
+ received.containsKey(expectedHeader));
+ }
+ } finally {
+ mockHttpServer.stop();
+ IPUtils.disabledIpPrivateSubnet(false);
+ }
}
}
From 9cca63bbd9f1966fab5c44d194d0f0f33672a4d5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 22:19:29 +0000
Subject: [PATCH 5/5] Remove Sec-Fetch headers, generalize Accept to */*, make
all headers configurable and disableable
Co-authored-by: fmontes <751424+fmontes@users.noreply.github.com>
---
.../dotcms/rest/api/v1/temp/TempFileAPI.java | 57 ++++++++++++-------
.../rest/api/v1/temp/TempFileAPITest.java | 36 +++++++-----
2 files changed, 59 insertions(+), 34 deletions(-)
diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
index 9f0d6b3e2ec9..fb7366070670 100644
--- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
@@ -43,6 +43,7 @@
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -76,27 +77,45 @@ public class TempFileAPI {
private static final Lazy allowAccessToPrivateSubnets = Lazy.of(()->Config.getBooleanProperty("ALLOW_ACCESS_TO_PRIVATE_SUBNETS", false));
/**
- * Builds a map of browser-like HTTP request headers for remote URL downloads.
- * Many image servers (CDNs, Unsplash, Pexels, Cloudflare-protected sites) reject
- * requests that lack standard browser headers, returning 403 or 406 responses.
- * The four content-negotiation headers are re-read from {@link Config} on every call
- * so that runtime changes (system table updates / hot-reload) take effect without a restart.
+ * Builds a map of browser-compatible HTTP request headers for remote URL downloads.
+ * Many servers (CDNs, Cloudflare-protected sites, etc.) reject requests that lack
+ * standard browser headers, returning 403 or 406 responses.
+ *
+ * All headers are read from {@link Config} on every call so that runtime changes
+ * (system table updates / hot-reload) take effect without a restart.
+ * Setting a config key to a blank value disables that header entirely, giving operators
+ * full control over which headers are sent.
+ *
+ * Config keys and their defaults:
+ *
+ * - {@code TEMP_FILE_URL_USER_AGENT} – Chrome-compatible User-Agent string
+ * - {@code TEMP_FILE_URL_ACCEPT} – {@code *}{@code /*} (accepts any content type)
+ * - {@code TEMP_FILE_URL_ACCEPT_LANGUAGE} – {@code en-US,en;q=0.9}
+ * - {@code TEMP_FILE_URL_ACCEPT_ENCODING} – {@code gzip, deflate}
+ * - {@code TEMP_FILE_URL_CONNECTION} – {@code keep-alive}
+ *
+ *
*/
static Map getBrowserHeaders() {
- return ImmutableMap.builder()
- .put("User-Agent", Config.getStringProperty("TEMP_FILE_URL_USER_AGENT",
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"))
- .put("Accept", Config.getStringProperty("TEMP_FILE_URL_ACCEPT",
- "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"))
- .put("Accept-Language", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_LANGUAGE",
- "en-US,en;q=0.9"))
- .put("Accept-Encoding", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_ENCODING",
- "gzip, deflate"))
- .put("Connection", "keep-alive")
- .put("Sec-Fetch-Dest", "image")
- .put("Sec-Fetch-Mode", "no-cors")
- .put("Sec-Fetch-Site", "cross-site")
- .build();
+ final Map headers = new LinkedHashMap<>();
+ final String[][] defs = {
+ {"User-Agent", Config.getStringProperty("TEMP_FILE_URL_USER_AGENT",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")},
+ {"Accept", Config.getStringProperty("TEMP_FILE_URL_ACCEPT",
+ "*/*")},
+ {"Accept-Language", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_LANGUAGE",
+ "en-US,en;q=0.9")},
+ {"Accept-Encoding", Config.getStringProperty("TEMP_FILE_URL_ACCEPT_ENCODING",
+ "gzip, deflate")},
+ {"Connection", Config.getStringProperty("TEMP_FILE_URL_CONNECTION",
+ "keep-alive")},
+ };
+ for (final String[] entry : defs) {
+ if (UtilMethods.isSet(entry[1])) {
+ headers.put(entry[0], entry[1]);
+ }
+ }
+ return ImmutableMap.copyOf(headers);
}
diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
index d559bb6f1208..8e2828f33190 100644
--- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
+++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/temp/TempFileAPITest.java
@@ -119,8 +119,8 @@ public static HttpServletRequest mockHttpServletRequest() {
/**
* Method to test: {@link TempFileAPI#getBrowserHeaders()}
- * Test scenario: Verify that all required browser-like header keys are present
- * Expected: The map contains all 8 required header keys
+ * Test scenario: Verify that all required browser-compatible header keys are present
+ * Expected: The map contains the 5 required header keys (no Sec-Fetch-* headers)
*/
@Test
public void testBrowserHeaders_containsAllRequiredKeys() {
@@ -129,10 +129,7 @@ public void testBrowserHeaders_containsAllRequiredKeys() {
"Accept",
"Accept-Language",
"Accept-Encoding",
- "Connection",
- "Sec-Fetch-Dest",
- "Sec-Fetch-Mode",
- "Sec-Fetch-Site"
+ "Connection"
);
final Map headers = TempFileAPI.getBrowserHeaders();
@@ -152,24 +149,26 @@ public void testBrowserHeaders_containsAllRequiredKeys() {
/**
* Method to test: {@link TempFileAPI#getBrowserHeaders()}
- * Test scenario: Verify the default values of the browser-like headers
- * Expected: Static/non-configurable headers have their expected default values;
- * configurable headers have valid non-empty values;
- * Accept-Encoding does not advertise Brotli (br) since Apache HttpClient lacks a Brotli decoder
+ * Test scenario: Verify the default values of the browser-compatible headers
+ * Expected:
+ * - Accept defaults to {@code *}{@code /*} (generic, not image-specific)
+ * - Connection defaults to {@code keep-alive} and is configurable
+ * - Accept-Encoding does not advertise Brotli (br) — Apache HttpClient has no brotli decoder
+ * - No Sec-Fetch-* headers are present (they are browser-generated metadata, not for server use)
*/
@Test
public void testBrowserHeaders_defaultValues() {
final Map headers = TempFileAPI.getBrowserHeaders();
+
+ // Connection defaults to keep-alive (configurable via TEMP_FILE_URL_CONNECTION)
assertEquals("keep-alive", headers.get("Connection"));
- assertEquals("image", headers.get("Sec-Fetch-Dest"));
- assertEquals("no-cors", headers.get("Sec-Fetch-Mode"));
- assertEquals("cross-site", headers.get("Sec-Fetch-Site"));
+
+ // Accept must default to */* — the endpoint downloads any file type, not just images
+ assertEquals("*/*", headers.get("Accept"));
// Configurable headers must be present and non-empty (may be overridden via Config)
assertTrue("User-Agent must not be empty",
!headers.get("User-Agent").isEmpty());
- assertTrue("Accept must not be empty",
- !headers.get("Accept").isEmpty());
assertTrue("Accept-Language must not be empty",
!headers.get("Accept-Language").isEmpty());
@@ -177,6 +176,13 @@ public void testBrowserHeaders_defaultValues() {
final String acceptEncoding = headers.get("Accept-Encoding");
assertTrue("Accept-Encoding must not be empty", !acceptEncoding.isEmpty());
assertFalse("Accept-Encoding must not advertise brotli (br)", acceptEncoding.contains("br"));
+
+ // Sec-Fetch-* headers must NOT be present — they are browser-generated Fetch Metadata
+ // headers whose purpose is to let servers verify a request came from a real browser context.
+ // Sending them from a server-side HTTP client is misleading and may cause rejections.
+ assertFalse("Sec-Fetch-Dest must not be present", headers.containsKey("Sec-Fetch-Dest"));
+ assertFalse("Sec-Fetch-Mode must not be present", headers.containsKey("Sec-Fetch-Mode"));
+ assertFalse("Sec-Fetch-Site must not be present", headers.containsKey("Sec-Fetch-Site"));
}
/**