From de599c91d3519d216c4e048fc207717881a6144c Mon Sep 17 00:00:00 2001 From: Arun Date: Fri, 17 Jul 2026 15:48:08 +0530 Subject: [PATCH] WW-5646 Modernize path normalization in Include component (#1780) * Modernize path normalization in Include component and handle edge cases * Add tests for edge case dot-dot handling in Include path normalization * Fix reversed path segment order in getContextRelativePath() The for-each loop iterated the ArrayDeque head-to-tail (most recently pushed first), which is the reverse of the old Stack's insertion-order iteration. This caused rebuilt paths like "car/view.jsp" to come out as "view.jsp/car". Use descendingIterator() to restore the original oldest-first ordering when rebuilding the flat path string. --- .../apache/struts2/components/Include.java | 19 ++++++++++++------- .../struts2/views/jsp/IncludeTagTest.java | 12 ++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/components/Include.java b/core/src/main/java/org/apache/struts2/components/Include.java index f8decac25..55f0baaf8 100644 --- a/core/src/main/java/org/apache/struts2/components/Include.java +++ b/core/src/main/java/org/apache/struts2/components/Include.java @@ -47,8 +47,10 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Stack; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.StringTokenizer; +import java.util.Iterator; /** * @@ -203,7 +205,7 @@ public class Include extends Component { // .. is illegal in an absolute path according to the Servlet Spec and will cause // known problems on Orion application servers. if (returnValue.contains("..")) { - Stack stack = new Stack<>(); + Deque segments = new ArrayDeque<>(); StringTokenizer pathParts = new StringTokenizer(returnValue.replace('\\', '/'), "/"); while (pathParts.hasMoreTokens()) { @@ -211,20 +213,23 @@ public class Include extends Component { if (!part.equals(".")) { if (part.equals("..")) { - stack.pop(); + if (!segments.isEmpty()) { + segments.pop(); + } } else { - stack.push(part); + segments.push(part); } } } StringBuilder flatPathBuffer = new StringBuilder(); - for (int i = 0; i < stack.size(); i++) { - flatPathBuffer.append("/").append(stack.elementAt(i)); + Iterator it = segments.descendingIterator(); + while (it.hasNext()) { + flatPathBuffer.append("/").append(it.next()); } - returnValue = flatPathBuffer.toString(); + returnValue = flatPathBuffer.length() > 0 ? flatPathBuffer.toString() : "/"; } return returnValue; diff --git a/core/src/test/java/org/apache/struts2/views/jsp/IncludeTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/IncludeTagTest.java index 7ca1daf12..9ca0587e3 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/IncludeTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/IncludeTagTest.java @@ -220,6 +220,18 @@ public class IncludeTagTest extends AbstractTagTest { strutsBodyTagsAreReflectionEqual(tag, freshTag)); } + public void testGetContextRelativePathExcessDotDot() { + // Excess ".." segments beyond root should be silently clamped, not throw EmptyStackException + String result = Include.getContextRelativePath(request, "/../../../other/resource.jsp"); + assertEquals("/other/resource.jsp", result); + } + + public void testGetContextRelativePathAllDotDot() { + // All segments are ".." - should resolve to root + String result = Include.getContextRelativePath(request, "/a/../../.."); + assertEquals("/", result); + } + public void testIncludeSetUseResponseEncodingTrue() throws Exception { // TODO: If possible in future mock-test an actual content-includes with various encodings // while setting the response encoding to match. Doesn't appear to be possible