Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.ui;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.ui.deployment.beans.CarbonUIDefinitions;
import org.wso2.carbon.ui.deployment.beans.Context;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static org.wso2.carbon.ui.CarbonUIUtil.getDefaultManagementUIPath;

/**
* A servlet that handles requests to the root path "/" and redirects to the
* configured default context (e.g., /carbon/admin/index.jsp).
* This servlet is necessary because when using OSGi HTTP Whiteboard,
* the ServletContextHelper (CarbonSecuredHttpContext) registered at path "/"
* doesn't receive requests for "/" directly unless there's a servlet
* registered to handle that path. Without this servlet, requests to "/"
* would result in a 404 error being forwarded to the error page.
*/
public class RootRedirectServlet extends HttpServlet {

private static final long serialVersionUID = 6080777136479623373L;
private static final Log log = LogFactory.getLog(RootRedirectServlet.class);

private final CarbonUIDefinitions carbonUIDefinitions;

/**
* Constructor with CarbonUIDefinitions for getting the default context.
*
* @param carbonUIDefinitions the UI definitions containing context configuration
*/
public RootRedirectServlet(CarbonUIDefinitions carbonUIDefinitions) {
Comment on lines +53 to +55
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log Improvement Suggestion No: 1

Suggested change
* @param carbonUIDefinitions the UI definitions containing context configuration
*/
public RootRedirectServlet(CarbonUIDefinitions carbonUIDefinitions) {
public RootRedirectServlet(CarbonUIDefinitions carbonUIDefinitions) {
this.carbonUIDefinitions = carbonUIDefinitions;
log.info("RootRedirectServlet initialized with CarbonUIDefinitions");
}


this.carbonUIDefinitions = carbonUIDefinitions;
}

/**
* Default constructor when no CarbonUIDefinitions is available.
*/
public RootRedirectServlet() {

this.carbonUIDefinitions = null;
Comment on lines +63 to +65
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public RootRedirectServlet() {
this.carbonUIDefinitions = null;
public RootRedirectServlet() {
this.carbonUIDefinitions = null;

}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

handleRedirect(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

handleRedirect(request, response);
}

/**
* Handles the redirect from root path to the configured default context.
*/
private void handleRedirect(HttpServletRequest request, HttpServletResponse response)
throws IOException {

String redirectUrl = getRedirectUrl(request);

if (log.isDebugEnabled()) {
log.debug("Redirecting root request to: " + redirectUrl);
}

response.sendRedirect(redirectUrl);
}

/**
* Determines the redirect URL based on configuration.
*
* @param request the HTTP request
* @return the URL to redirect to
*/
private String getRedirectUrl(HttpServletRequest request) {

// Check for configured default context
Context defaultContext = null;
if (carbonUIDefinitions != null &&
carbonUIDefinitions.getContexts().containsKey("default-context")) {
defaultContext = carbonUIDefinitions.getContexts().get("default-context");
}

if (defaultContext != null && !"".equals(defaultContext.getContextName())
&& !"null".equals(defaultContext.getContextName())) {
String adminConsoleURL = CarbonUIUtil.getAdminConsoleURL(request);
if (adminConsoleURL != null) {
int index = adminConsoleURL.lastIndexOf("carbon");
if (index >= 0) {
return adminConsoleURL.substring(0, index) + defaultContext.getContextName() + "/";
}
}
}

// Fall back to default management UI path
String defaultManagementUIPath = getDefaultManagementUIPath();
if (StringUtils.isBlank(defaultManagementUIPath)) {
defaultManagementUIPath = "/carbon/admin/index.jsp";
}

return defaultManagementUIPath;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.wso2.carbon.ui.CarbonUIAuthenticator;
import org.wso2.carbon.ui.CarbonUIUtil;
import org.wso2.carbon.ui.DefaultCarbonAuthenticator;
import org.wso2.carbon.ui.RootRedirectServlet;
import org.wso2.carbon.ui.TenantAwareCsrfJsFilter;
import org.wso2.carbon.ui.TextJavascriptHandler;
import org.wso2.carbon.ui.TilesJspServlet;
Expand Down Expand Up @@ -84,6 +85,7 @@
import javax.servlet.Servlet;
import javax.servlet.ServletContextListener;
import javax.servlet.Filter;

@Component(name = "core.ui.dscomponent", immediate = true)
public class CarbonUIServiceComponent {

Expand Down Expand Up @@ -252,6 +254,17 @@ public void start(BundleContext context) throws Exception {
contextProps.put("osgi.http.whiteboard.context.path", "/");
context.registerService(ServletContextHelper.class, (ServletContextHelper) commonContext, contextProps);

// Register RootRedirectServlet to handle requests to "/" path
// This is necessary because without a servlet registered for "/", requests to the root path
// would result in a 404 error being forwarded to the error page instead of being handled
// by handleSecurity in CarbonSecuredHttpContext
Comment on lines +257 to +260
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we use javadoc multi line commenting style.

Servlet rootRedirectServlet = new RootRedirectServlet(carbonUIDefinitions);
Comment on lines +257 to +261
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log Improvement Suggestion No: 3

Suggested change
// Register RootRedirectServlet to handle requests to "/" path
// This is necessary because without a servlet registered for "/", requests to the root path
// would result in a 404 error being forwarded to the error page instead of being handled
// by handleSecurity in CarbonSecuredHttpContext
Servlet rootRedirectServlet = new RootRedirectServlet(carbonUIDefinitions);
// Register RootRedirectServlet to handle requests to "/" path
// This is necessary because without a servlet registered for "/", requests to the root path
// would result in a 404 error being forwarded to the error page instead of being handled
// by handleSecurity in CarbonSecuredHttpContext
log.info("Registering RootRedirectServlet for root path handling");
Servlet rootRedirectServlet = new RootRedirectServlet(carbonUIDefinitions);

Dictionary<String, String> rootRedirectServletProps = new Hashtable<>();
rootRedirectServletProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/");
rootRedirectServletProps.put("osgi.http.whiteboard.context.select",
"(osgi.http.whiteboard.context.name=carbonContext)");
context.registerService(Servlet.class, rootRedirectServlet, rootRedirectServletProps);
Comment on lines +257 to +266
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -type f -name "RootRedirectServlet.java"

Repository: wso2/carbon-kernel

Length of output: 232


🏁 Script executed:

fd -type f -name "CarbonUIServiceComponent.java"

Repository: wso2/carbon-kernel

Length of output: 232


🏁 Script executed:

git ls-files | grep -i "root" | head -20

Repository: wso2/carbon-kernel

Length of output: 358


🏁 Script executed:

cat -n "core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/RootRedirectServlet.java"

Repository: wso2/carbon-kernel

Length of output: 5713


🏁 Script executed:

cat -n "core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/internal/CarbonUIServiceComponent.java" | sed -n '250,275p'

Repository: wso2/carbon-kernel

Length of output: 2246


🌐 Web query:

OSGi HTTP Whiteboard servlet pattern "/" exact root match vs default mapping unmatched paths

💡 Result:

In OSGi HTTP Whiteboard, osgi.http.whiteboard.servlet.pattern uses the Servlet URL-pattern rules (Servlet spec §12.2), and the key distinction is:

  • "/" is not an “exact root-only” match. A pattern consisting of only "/" registers the servlet as the default servlet for that ServletContextHelper (context path). It will be selected for any request path that is not matched by a more specific exact/path/extension mapping. (docs.osgi.org)
    Example (context path /app): if no other servlet matches, your "/" servlet can receive /app/foo, /app/foo/bar, etc.

  • "" (empty string) is the exact context root match. The empty-string pattern maps only to the application/context root URL of the form http://host:port/<context-root>/. (docs.osgi.org)
    Example (context path /app): matches /app/ (root), but not /app/foo.

So:

  • If you want a catch-all fallback for otherwise-unmatched paths: use "/".
  • If you want only the context root (/.../): use "".

Citations:


🌐 Web query:

Servlet URL pattern "/" specification exact match or default handler

💡 Result:

In the Servlet spec, a url-pattern of "/" is not an exact match. It designates the application’s default servlet mapping. For this mapping, the container sets:

  • servletPath = requestURI - contextPath
  • pathInfo = null (jakarta.ee)

If you want an exact match for the context root (i.e., only http://host:port/<context-root>/), the spec defines a special pattern: the empty string "". In that case:

  • contextPath = ""
  • servletPath = ""
  • pathInfo = "/" (jakarta.ee)

Related: a pattern ending in "/*" is a path mapping (prefix match), while patterns like "/catalog" are exact matches. (jakarta.ee)

Citations:


"/" servlet pattern registers as default handler for all unmatched paths; add root-only validation.

Pattern "/" in OSGi HTTP Whiteboard is a default servlet mapping—it captures any request not matched by more specific patterns, not just the root URL. Combined with the lack of path validation in RootRedirectServlet.handleRedirect(), unmatched paths will be redirected to the console instead of returning 404.

Either change the pattern to "" (exact context root match per Servlet spec) or add a root-only guard in the servlet. The current implementation doesn't match the stated PR objective to handle root requests only.

Recommended fix: add root path validation
--- a/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/RootRedirectServlet.java
+++ b/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/RootRedirectServlet.java
@@ -81,6 +81,12 @@ public class RootRedirectServlet extends HttpServlet {
     private void handleRedirect(HttpServletRequest request, HttpServletResponse response) 
             throws IOException {
+        // Only redirect if this is the root path, not other unmatched paths
+        String requestPath = request.getRequestURI().substring(request.getContextPath().length());
+        if (!"/".equals(requestPath)) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
         String redirectUrl = getRedirectUrl(request);
         
         if (log.isDebugEnabled()) {

Alternatively, change the servlet pattern from "/" to "" for exact root-only matching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/internal/CarbonUIServiceComponent.java`
around lines 257 - 266, The RootRedirectServlet is registered with the pattern
"/" which acts as a default handler for unmatched paths; update the registration
in CarbonUIServiceComponent or the servlet to ensure only the exact root is
handled: either change the servlet registration pattern passed to
context.registerService(Servlet.class, rootRedirectServlet,
rootRedirectServletProps) from "/" to an empty pattern "" for exact context-root
matching, or add a root-only guard inside RootRedirectServlet (e.g., in
handleRedirect() / doGet()) that checks the request path
(request.getRequestURI() and/or request.getContextPath()) and only performs the
redirect when the path is exactly the context root ("/" or empty), otherwise
respond with 404/chain to next handler.


// Register file download servlet using HTTP Whiteboard pattern
Servlet fileDownloadServlet = new FileDownloadServlet(context, getConfigurationContextService());
Dictionary<String, String> fileDownloadServletProperties = new Hashtable<>();
Expand Down
Loading