/* * Copyright 2017 OmniFaces * * Licensed 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.omnifaces.facesviews; import static javax.faces.application.ProjectStage.Development; import static javax.servlet.http.HttpServletResponse.SC_MOVED_PERMANENTLY; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static org.omnifaces.facesviews.FacesServletDispatchMethod.DO_FILTER; import static org.omnifaces.facesviews.FacesViews.FACES_VIEWS_ORIGINAL_PATH_INFO; import static org.omnifaces.facesviews.FacesViews.FACES_VIEWS_ORIGINAL_SERVLET_PATH; import static org.omnifaces.facesviews.FacesViews.getExtensionAction; import static org.omnifaces.facesviews.FacesViews.getExtensionlessURLWithQuery; import static org.omnifaces.facesviews.FacesViews.getFacesServletDispatchMethod; import static org.omnifaces.facesviews.FacesViews.getMappedResources; import static org.omnifaces.facesviews.FacesViews.getMultiViewsWelcomeFile; import static org.omnifaces.facesviews.FacesViews.getPathAction; import static org.omnifaces.facesviews.FacesViews.getReverseMappedResources; import static org.omnifaces.facesviews.FacesViews.isMultiViewsEnabled; import static org.omnifaces.facesviews.FacesViews.isResourceInPublicPath; import static org.omnifaces.facesviews.FacesViews.scanAndStoreViews; import static org.omnifaces.facesviews.FacesViews.stripWelcomeFilePrefix; import static org.omnifaces.util.Faces.getApplicationFromFactory; import static org.omnifaces.util.ResourcePaths.getExtension; import static org.omnifaces.util.ResourcePaths.isExtensionless; import static org.omnifaces.util.ResourcePaths.stripTrailingSlash; import static org.omnifaces.util.Servlets.getRequestRelativeURI; import java.io.IOException; import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.omnifaces.filter.HttpFilter; /** * This filter makes sure extensionless requests arrive at the FacesServlet using an extension on which that Servlet is * mapped, and that non-extensionless requests are handled according to a set preference. * <p> * A filter like this is needed for extensionless requests, since the FacesServlet does not take into account any other * mapping than prefix- and extension (suffix) mapping. * <p> * For a guide on FacesViews, please see the <a href="package-summary.html">package summary</a>. * * @author Arjan Tijms * @see FacesViews * @see ExtensionAction * @see PathAction * @see UriExtensionRequestWrapper */ public class FacesViewsForwardingFilter extends HttpFilter { private ExtensionAction extensionAction; private PathAction pathAction; private FacesServletDispatchMethod dispatchMethod; @Override public void init() throws ServletException { ServletContext servletContext = getServletContext(); try { extensionAction = getExtensionAction(servletContext); pathAction = getPathAction(servletContext); dispatchMethod = getFacesServletDispatchMethod(servletContext); } catch (IllegalStateException e) { throw new ServletException(e); } } @Override public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws ServletException, IOException { if (!(filterExtensionLess(request, response, chain) || filterExtension(request, response) || filterPublicPath(request, response))) { chain.doFilter(request, response); } } /** * A mapped resource request without extension is encountered. * The user setting "dispatchMethod" determines how we handle this. */ private boolean filterExtensionLess(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String servletPath = request.getServletPath(); if (!isExtensionless(servletPath)) { return false; } ServletContext servletContext = getServletContext(); boolean multiViews = isMultiViewsEnabled(request); Map<String, String> resources = getMappedResources(servletContext); String normalizedServletPath = stripTrailingSlash(servletPath); String resource = normalizedServletPath + (multiViews ? "/*" : ""); if (getApplicationFromFactory().getProjectStage() == Development && !resources.containsKey(resource)) { // Check if the resource was dynamically added by scanning the faces-views location(s) again. resources = scanAndStoreViews(servletContext, false); } if (multiViews && !resources.containsKey(resource)) { resource = getMultiViewsWelcomeFile(servletContext); if (resource != null) { request.setAttribute(FACES_VIEWS_ORIGINAL_PATH_INFO, servletPath); request.getRequestDispatcher(resource).forward(request, response); return true; } } return filterExtensionLess(request, response, chain, resources, resource, normalizedServletPath); } private boolean filterExtensionLess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Map<String, String> resources, String resource, String path) throws IOException, ServletException { if (resources.containsKey(resource)) { if (redirectExtensionLessWelcomeFileToFolderIfNecessary(request, response, path)) { return true; } String servletPathWithExtension = path + getExtension(resources.get(resource)); if (dispatchMethod == DO_FILTER && resources.containsKey(servletPathWithExtension)) { filterExtensionLessToExtension(request, response, chain, servletPathWithExtension); return true; } else if (forwardExtensionLessToExtensionIfNecessary(request, response, servletPathWithExtension)) { return true; } } return false; } /** * Check if a welcome file was explicitly requested and if so, redirect back to its parent folder. */ private boolean redirectExtensionLessWelcomeFileToFolderIfNecessary(HttpServletRequest request, HttpServletResponse response, String normalizedServletPath) { if ((getRequestRelativeURI(request) + "/").startsWith(normalizedServletPath + "/")) { String servletPath = request.getServletPath(); String normalizedResource = stripWelcomeFilePrefix(request.getServletContext(), servletPath); if (!servletPath.equals(normalizedResource)) { String uri = request.getContextPath() + normalizedResource; String queryString = request.getQueryString(); redirectPermanent(response, uri + ((queryString != null) ? "?" + queryString : "")); return true; } } return false; } /** * Continue the chain, but make the request appear to be to the resource with an extension. * This assumes that the FacesServlet has been mapped to something that includes the extensionless request. */ private void filterExtensionLessToExtension(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String mappedServletPath) throws IOException, ServletException { try { request.setAttribute(FACES_VIEWS_ORIGINAL_SERVLET_PATH, request.getServletPath()); String pathInfo = request.getPathInfo(); if (pathInfo != null) { request.setAttribute(FACES_VIEWS_ORIGINAL_PATH_INFO, pathInfo); } chain.doFilter(new UriExtensionRequestWrapper(request, mappedServletPath), response); } finally { request.removeAttribute(FACES_VIEWS_ORIGINAL_SERVLET_PATH); request.removeAttribute(FACES_VIEWS_ORIGINAL_PATH_INFO); } } /** * Forward the resource (view) using its original extension, on which the Facelets Servlet is mapped. * Technically it matters most that the Facelets Servlet picks up the request, * and the exact extension or even prefix is perhaps less relevant. */ private boolean forwardExtensionLessToExtensionIfNecessary(HttpServletRequest request, HttpServletResponse response, String servletPathWithExtension) throws ServletException, IOException { RequestDispatcher requestDispatcher = request.getServletContext().getRequestDispatcher(servletPathWithExtension); if (requestDispatcher != null) { requestDispatcher.forward(request, response); return true; } return false; } /** * A mapped resource request with extension is encountered. * The user setting "extensionAction" determines how we handle this. */ private boolean filterExtension(HttpServletRequest request, HttpServletResponse response) throws IOException { String resource = request.getServletPath(); Map<String, String> resources = getMappedResources(getServletContext()); if (resources.containsKey(resource)) { switch (extensionAction) { case REDIRECT_TO_EXTENSIONLESS: redirectPermanent(response, getExtensionlessURLWithQuery(request, resource)); return true; case SEND_404: response.sendError(SC_NOT_FOUND); return true; case PROCEED: break; } } return false; } /** * A direct request to one of the public paths (excluding /) from where we scanned resources is encountered. * The user setting "pathAction" determines how we handle this. */ private boolean filterPublicPath(HttpServletRequest request, HttpServletResponse response) throws IOException { String resource = request.getServletPath(); if (!isResourceInPublicPath(getServletContext(), resource)) { return false; } Map<String, String> reverseResources = getReverseMappedResources(getServletContext()); if (reverseResources.containsKey(resource)) { switch (pathAction) { case REDIRECT_TO_SCANNED_EXTENSIONLESS: redirectPermanent(response, getExtensionlessURLWithQuery(request, reverseResources.get(resource))); return true; case SEND_404: response.sendError(SC_NOT_FOUND); return true; case PROCEED: break; } } return false; } private static void redirectPermanent(HttpServletResponse response, String url) { response.setStatus(SC_MOVED_PERMANENTLY); response.setHeader("Location", url); response.setHeader("Connection", "close"); } }