zip and response issues (#5786)

This commit is contained in:
Anthony Stirling
2026-02-24 20:08:18 +00:00
committed by GitHub
parent 1f9b90ad57
commit abbd332909
4 changed files with 21 additions and 17 deletions
@@ -100,6 +100,11 @@ public class FileToPdf {
while (entry != null) { while (entry != null) {
Path filePath = Path filePath =
tempUnzippedDir.getPath().resolve(sanitizeZipFilename(entry.getName())); tempUnzippedDir.getPath().resolve(sanitizeZipFilename(entry.getName()));
Path normalizedTargetDir = tempUnzippedDir.getPath().toAbsolutePath().normalize();
Path normalizedFilePath = filePath.toAbsolutePath().normalize();
if (!normalizedFilePath.startsWith(normalizedTargetDir)) {
throw new IOException("Zip entry path escapes target directory: " + entry.getName());
}
if (!entry.isDirectory()) { if (!entry.isDirectory()) {
Files.createDirectories(filePath.getParent()); Files.createDirectories(filePath.getParent());
if (entry.getName().toLowerCase(Locale.ROOT).endsWith(".html") if (entry.getName().toLowerCase(Locale.ROOT).endsWith(".html")
@@ -166,7 +166,7 @@ public class ConvertWebsiteToPDF {
private String fetchRemoteHtml(String url) throws IOException, InterruptedException { private String fetchRemoteHtml(String url) throws IOException, InterruptedException {
HttpClient client = HttpClient client =
HttpClient.newBuilder() HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL) .followRedirects(HttpClient.Redirect.NEVER)
.connectTimeout(Duration.ofSeconds(10)) .connectTimeout(Duration.ofSeconds(10))
.build(); .build();
@@ -180,7 +180,7 @@ public class ConvertWebsiteToPDF {
HttpResponse<String> response = HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (response.statusCode() >= 400 || response.body() == null) { if (response.statusCode() >= 300 || response.body() == null) {
throw ExceptionUtils.createIOException( throw ExceptionUtils.createIOException(
"error.httpRequestFailed", "error.httpRequestFailed",
"Failed to retrieve remote HTML. Status: {0}", "Failed to retrieve remote HTML. Status: {0}",
@@ -29,6 +29,8 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.databind.ObjectMapper;
import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.ExceptionUtils.*; import stirling.software.common.util.ExceptionUtils.*;
import stirling.software.common.util.RegexPatternUtils; import stirling.software.common.util.RegexPatternUtils;
@@ -866,21 +868,18 @@ public class GlobalExceptionHandler {
response.setContentType("application/problem+json"); response.setContentType("application/problem+json");
response.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");
String errorJson = // Use ObjectMapper to properly escape JSON values and prevent XSS
String.format( ObjectMapper mapper = new ObjectMapper();
""" java.util.Map<String, Object> errorMap = new java.util.LinkedHashMap<>();
{ errorMap.put("type", "about:blank");
"type": "about:blank", errorMap.put("title", "Not Acceptable");
"title": "Not Acceptable", errorMap.put("status", 406);
"status": 406, errorMap.put("detail", "The requested resource could not be returned in an acceptable format. Error responses are returned as JSON.");
"detail": "The requested resource could not be returned in an acceptable format. Error responses are returned as JSON.", errorMap.put("instance", request.getRequestURI());
"instance": "%s", errorMap.put("timestamp", Instant.now().toString());
"timestamp": "%s", errorMap.put("hints", java.util.Arrays.asList("Error responses are always returned as application/json or application/problem+json", "Set Accept header to include application/json for proper error handling"));
"hints": ["Error responses are always returned as application/json or application/problem+json", "Set Accept header to include application/json for proper error handling"]
}
""",
request.getRequestURI(), Instant.now().toString());
String errorJson = mapper.writeValueAsString(errorMap);
response.getWriter().write(errorJson); response.getWriter().write(errorJson);
response.getWriter().flush(); response.getWriter().flush();
} }
@@ -298,7 +298,7 @@ public class ConvertWebsiteToPdfTest {
HttpResponse<String> response = Mockito.mock(); HttpResponse<String> response = Mockito.mock();
httpClientStatic.when(HttpClient::newBuilder).thenReturn(builder); httpClientStatic.when(HttpClient::newBuilder).thenReturn(builder);
when(builder.followRedirects(HttpClient.Redirect.NORMAL)).thenReturn(builder); when(builder.followRedirects(HttpClient.Redirect.NEVER)).thenReturn(builder);
when(builder.connectTimeout(any(Duration.class))).thenReturn(builder); when(builder.connectTimeout(any(Duration.class))).thenReturn(builder);
when(builder.build()).thenReturn(client); when(builder.build()).thenReturn(client);