From b8a975dd854d0d9a0706f94e1f858a42d2ecfa5e Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Thu, 23 Jul 2026 12:34:18 +0200 Subject: [PATCH] ESB-1204 Fixed the Jetty 12 redirect action problem when running locally --- ...ServletActionRedirectResultWithAnchor.java | 25 ++++++++ ...letActionRedirectResultWithAnchorTest.java | 62 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 admin-console/src/test/java/com/agiletec/apsadmin/system/dispatcher/ServletActionRedirectResultWithAnchorTest.java diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/system/dispatcher/ServletActionRedirectResultWithAnchor.java b/admin-console/src/main/java/com/agiletec/apsadmin/system/dispatcher/ServletActionRedirectResultWithAnchor.java index 60f003b22..cf03fb719 100644 --- a/admin-console/src/main/java/com/agiletec/apsadmin/system/dispatcher/ServletActionRedirectResultWithAnchor.java +++ b/admin-console/src/main/java/com/agiletec/apsadmin/system/dispatcher/ServletActionRedirectResultWithAnchor.java @@ -19,6 +19,8 @@ import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.result.ServletRedirectResult; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -129,6 +131,29 @@ public void execute(ActionInvocation invocation) throws Exception { setLocation(tmpLocation.toString()); super.execute(invocation); } + + /** + * Jetty 12 (ee10) {@link HttpServletResponse#sendRedirect(String)} throws + * {@code IllegalArgumentException: Fragment} for any redirect location containing a URL fragment + * ({@code #anchor}). A fragment in the {@code Location} header is valid per RFC 7231 ยง7.1.2 and is + * honoured by browsers, so when the target carries an anchor we set the status + {@code Location} + * header directly โ€” mirroring the superclass's own non-302 branch โ€” instead of calling + * {@code sendRedirect}. Fragment-free locations keep the standard behaviour. + */ + @Override + protected void sendRedirect(HttpServletResponse response, String finalLocation) throws IOException { + if (null != finalLocation && finalLocation.indexOf('#') >= 0) { + response.setStatus(statusCode); + response.setHeader("Location", finalLocation); + try { + response.getWriter().write(finalLocation); + } finally { + response.getWriter().close(); + } + return; + } + super.sendRedirect(response, finalLocation); + } /** * Sets the action name diff --git a/admin-console/src/test/java/com/agiletec/apsadmin/system/dispatcher/ServletActionRedirectResultWithAnchorTest.java b/admin-console/src/test/java/com/agiletec/apsadmin/system/dispatcher/ServletActionRedirectResultWithAnchorTest.java new file mode 100644 index 000000000..9abdc9bbe --- /dev/null +++ b/admin-console/src/test/java/com/agiletec/apsadmin/system/dispatcher/ServletActionRedirectResultWithAnchorTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package com.agiletec.apsadmin.system.dispatcher; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import jakarta.servlet.http.HttpServletResponse; +import java.io.PrintWriter; +import org.junit.jupiter.api.Test; + +/** + * Guards the Jetty 12 fix: a redirect location carrying a URL fragment must be emitted via the + * {@code Location} header (not {@code HttpServletResponse.sendRedirect}, which Jetty 12 rejects with + * {@code IllegalArgumentException: Fragment}), while fragment-free locations keep standard behaviour. + */ +class ServletActionRedirectResultWithAnchorTest { + + private static final String ANCHOR_URL = + "/entando-de-app/do/Entity/CompositeAttribute/entryCompositeAttribute.action#fagiano_compositeTypesList"; + private static final String PLAIN_URL = + "/entando-de-app/do/Entity/entryEntityType.action"; + + @Test + void locationWithAnchorIsSentViaHeaderNotSendRedirect() throws Exception { + ServletActionRedirectResultWithAnchor result = new ServletActionRedirectResultWithAnchor(); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(mock(PrintWriter.class)); + + result.sendRedirect(response, ANCHOR_URL); + + // 302 + Location header directly โ€” never sendRedirect (which Jetty 12 would reject on the fragment) + verify(response).setStatus(HttpServletResponse.SC_FOUND); + verify(response).setHeader("Location", ANCHOR_URL); + verify(response, never()).sendRedirect(anyString()); + } + + @Test + void locationWithoutAnchorDelegatesToSendRedirect() throws Exception { + ServletActionRedirectResultWithAnchor result = new ServletActionRedirectResultWithAnchor(); + HttpServletResponse response = mock(HttpServletResponse.class); + + result.sendRedirect(response, PLAIN_URL); + + verify(response).sendRedirect(PLAIN_URL); + verify(response, never()).setHeader(anyString(), anyString()); + } +}