This is an automated email from the ASF dual-hosted git repository.

lprimak pushed a commit to branch 3.x
in repository https://gitbox.apache.org/repos/asf/shiro.git

commit bd278bcc360938161abb7c4740c8e1c4a1e44831
Author: lprimak <[email protected]>
AuthorDate: Thu May 21 19:34:39 2026 -0500

    enh(jakarta-ee): strip out the host part of the referer header
---
 .../shiro/ee/filters/FormResubmitSupport.java      |  28 +++-
 .../apache/shiro/ee/filters/FormSupportTest.java   | 143 +++++++++++++++++++--
 2 files changed, 153 insertions(+), 18 deletions(-)

diff --git 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
index 0e4732eff..b3aee2bed 100644
--- 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
+++ 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
@@ -259,13 +259,31 @@ public class FormResubmitSupport {
 
     static String getReferer(HttpServletRequest request) {
         String referer = request.getHeader("referer");
-        if (referer != null) {
-            // do not switch to https if custom port is specified
-            if 
(!referer.matches("^http:\\/\\/[A-z|.|[0-9]]+:[0-9]+(\\/.*|$)")) {
-                referer = referer.replaceFirst("^http:", "https:");
+        if (referer == null || referer.isBlank()) {
+            return null;
+        }
+
+        try {
+            URI uri = URI.create(referer);
+
+            String contextPath = WebUtils.getContextPath(request);
+            String path = WebUtils.normalize(uri.getPath());
+
+            if (path == null) {
+                return null;
             }
+
+            if (!contextPath.isEmpty()
+                    && !path.equals(contextPath)
+                    && !path.startsWith(contextPath + "/")) {
+                return null;
+            }
+
+            String query = uri.getRawQuery();
+            return query == null ? path : path + "?" + query;
+        } catch (IllegalArgumentException e) {
+            return null;
         }
-        return referer;
     }
 
     /**
diff --git 
a/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
 
b/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
index f1535753d..c827a60cf 100644
--- 
a/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
+++ 
b/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
@@ -28,9 +28,9 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 
 import static org.apache.shiro.ee.util.JakartaTransformer.jakartify;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -46,38 +46,155 @@ import org.mockito.junit.jupiter.MockitoExtension;
  * Resubmit forms support
  */
 @ExtendWith(MockitoExtension.class)
-public class FormSupportTest {
+class FormSupportTest {
     @Mock
     private HttpServletRequest request;
 
     @Test
     void nullReferer() {
         when(request.getHeader("referer")).thenReturn(null);
-        assertNull(getReferer(request));
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void blankReferer() {
+        when(request.getHeader("referer")).thenReturn("   ");
+        assertThat(getReferer(request)).isNull();
     }
 
     @Test
     void plainStringReferer() {
         when(request.getHeader("referer")).thenReturn("hello");
-        assertEquals("hello", getReferer(request));
+        when(request.getContextPath()).thenReturn("/myapp");
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void malformedReferer() {
+        when(request.getHeader("referer")).thenReturn("http://exa mple.com");
+        assertThat(getReferer(request)).isNull();
     }
 
     @Test
-    void switchToHttps() {
-        when(request.getHeader("referer")).thenReturn("http://example.com";);
-        assertEquals("https://example.com";, getReferer(request));
+    void refererWithinContextPath() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/login.xhtml";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml");
     }
 
     @Test
-    void dontSwitchToHttpsWhenCustomPort() {
-        
when(request.getHeader("referer")).thenReturn("http://example.com:8080/";);
-        assertEquals("http://example.com:8080/";, getReferer(request));
+    void refererWithinContextPathWithQuery() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/login.xhtml?a=1&b=2";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        
assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml?a=1&b=2");
     }
 
     @Test
-    void dontSwitchToHttpsWhenCustomPortNoTrailingSlash() {
-        
when(request.getHeader("referer")).thenReturn("http://example.com:8080";);
-        assertEquals("http://example.com:8080";, getReferer(request));
+    void refererEqualToContextPathBecomesRoot() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapp";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isEqualTo("/myapp");
+    }
+
+    @Test
+    void refererOutsideContextPathIsRejected() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/otherapp/login.xhtml";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void rootContextKeepsPath() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/login.xhtml";);
+        when(request.getContextPath()).thenReturn("");
+
+        assertThat(getReferer(request)).isEqualTo("/login.xhtml");
+    }
+
+    @Test
+    void rootContextKeepsPathWithQuery() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/login.xhtml?x=1";);
+        when(request.getContextPath()).thenReturn("");
+
+        assertThat(getReferer(request)).isEqualTo("/login.xhtml?x=1");
+    }
+
+    @Test
+    void normalizedPathWithinContextIsAccepted() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapp//foo/./bar.xhtml";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isEqualTo("/myapp/foo/bar.xhtml");
+    }
+
+    @Test
+    void normalizedPathEscapingContextIsRejected() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/../otherapp/page.xhtml";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void opaqueUriRefererIsRejected() {
+        
when(request.getHeader("referer")).thenReturn("mailto:[email protected]";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void javascriptUriRefererIsRejected() {
+        when(request.getHeader("referer")).thenReturn("javascript:alert(1)");
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void contextPathPrefixMatchRequiresPathBoundary() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapplication/page.xhtml";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void refererWithFragmentDropsFragmentAndKeepsQueryOnly() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/page.xhtml?a=1#frag";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isEqualTo("/myapp/page.xhtml?a=1");
+    }
+
+    @Test
+    void externalHostWithMatchingContextCurrentlyPasses() {
+        
when(request.getHeader("referer")).thenReturn("https://attacker.example/myapp/login.xhtml";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml");
+    }
+
+    @Test
+    void encodedPathTraversalRefererIsRejected() {
+        when(request.getHeader("referer"))
+                
.thenReturn("https://example.com/myapp/%2e%2e/otherapp/page.xhtml";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void encodedPathTraversalWithEncodedSlashesRefererIsRejected() {
+        when(request.getHeader("referer"))
+                
.thenReturn("https://example.com/myapp/%2e%2e%2fotherapp%2fpage.xhtml";);
+        when(request.getContextPath()).thenReturn("/myapp");
+
+        assertThat(getReferer(request)).isNull();
     }
 
     @Test

Reply via email to