/** * Copyright (C) 2015 BonitaSoft S.A. * BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2.0 of the License, or * (at your option) any later version. * This program 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.bonitasoft.console.common.server.page; import java.io.File; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.bonitasoft.console.common.server.page.extension.PageResourceProviderImpl; import org.bonitasoft.console.common.server.utils.BonitaHomeFolderAccessor; import org.bonitasoft.console.common.server.utils.SessionUtil; import org.bonitasoft.engine.exception.BonitaException; import org.bonitasoft.engine.exception.NotFoundException; import org.bonitasoft.engine.exception.UnauthorizedAccessException; import org.bonitasoft.engine.page.PageNotFoundException; import org.bonitasoft.engine.session.APISession; /** * Servlet allowing to display a page or the resource of a page * (it can be a custom page or an external page) * * @author Anthony Birembaut */ public class PageServlet extends HttpServlet { /** * UID */ private static final long serialVersionUID = 2789496969243916444L; /** * Logger */ private static Logger LOGGER = Logger.getLogger(PageServlet.class.getName()); public static final String RESOURCE_PATH_SEPARATOR = "/content"; public static final String API_PATH_SEPARATOR = "/API"; public static final String THEME_PATH_SEPARATOR = "/theme"; protected CustomPageRequestModifier customPageRequestModifier = new CustomPageRequestModifier(); protected PageMappingService pageMappingService = new PageMappingService(); protected BonitaHomeFolderAccessor bonitaHomeFolderAccessor = new BonitaHomeFolderAccessor(); protected ResourceRenderer resourceRenderer = new ResourceRenderer(); protected PageRenderer pageRenderer = new PageRenderer(resourceRenderer); @Override protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { final String pathInfo = request.getPathInfo(); if (!pathInfo.contains(RESOURCE_PATH_SEPARATOR + "/") && pathInfo.indexOf(API_PATH_SEPARATOR + "/") > 0) { //Support relative calls to the REST API from the forms using ../API/ final String apiPath = pathInfo.substring(pathInfo.indexOf(API_PATH_SEPARATOR + "/")); request.getRequestDispatcher(apiPath).forward(request, response); } else if (!pathInfo.contains(RESOURCE_PATH_SEPARATOR + "/") && pathInfo.indexOf(THEME_PATH_SEPARATOR + "/") > 0) { //BS-14188 : as theme are only currently managed in living app, we cannot currently serve // theme request from Forms. But, it shouldn't produce any 400 request error either // thus, we return 200 return ; } else { super.service(request, response); } } @Override protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { final HttpSession session = request.getSession(); final APISession apiSession = (APISession) session.getAttribute(SessionUtil.API_SESSION_PARAM_KEY); final String pathInfo = request.getPathInfo(); // Check if requested URL is missing final slash (necessary in order to be able to use relative URLs for resources) if (pathInfo.endsWith(RESOURCE_PATH_SEPARATOR)) { customPageRequestModifier.redirectToValidPageUrl(request, response); } else if (pathInfo.indexOf(RESOURCE_PATH_SEPARATOR + "/") > 0) { final String[] pathInfoSegments = pathInfo.split(RESOURCE_PATH_SEPARATOR + "/", 2); final String mappingKey = pathInfoSegments[0].substring(1); String resourcePath = null; if (pathInfoSegments.length > 1 && !pathInfoSegments[1].isEmpty()) { resourcePath = pathInfoSegments[1]; } try { resolveAndDisplayPage(request, response, apiSession, mappingKey, resourcePath); } catch (final Exception e) { handleException(response, mappingKey, e); } } else { final String message = "/content is expected in the URL after the page mapping key"; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Bad request: " + message); } response.sendError(HttpServletResponse.SC_BAD_REQUEST, message); } } protected void resolveAndDisplayPage(final HttpServletRequest request, final HttpServletResponse response, final APISession apiSession, final String mappingKey, final String resourcePath) throws BonitaException, IOException, InstantiationException, IllegalAccessException { try { final PageReference pageReference = pageMappingService.getPage(request, apiSession, mappingKey, pageRenderer.getCurrentLocale(request), isNotResourcePath(resourcePath)); if (pageReference.getURL() != null) { displayExternalPage(request, response, pageReference.getURL()); } else if (pageReference.getPageId() != null) { displayPageOrResource(request, response, apiSession, pageReference.getPageId(), resourcePath); } else { if (LOGGER.isLoggable(Level.FINE)) { final String message = "Both URL and pageId are not set in the page mapping for " + mappingKey; LOGGER.log(Level.FINE, message); } return; } } catch (final UnauthorizedAccessException e) { final String message = "User not Authorized"; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Forbidden: " + message, e); } response.sendError(HttpServletResponse.SC_FORBIDDEN, message); } catch (final NotFoundException e) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Not found: Cannot find the form mapping for key " + mappingKey, e); } response.sendError(HttpServletResponse.SC_NOT_FOUND, "Form mapping not found"); } } protected void displayPageOrResource(final HttpServletRequest request, final HttpServletResponse response, final APISession apiSession, final Long pageId, final String resourcePath) throws InstantiationException, IllegalAccessException, IOException, BonitaException { try { if (isNotResourcePath(resourcePath)) { pageRenderer.displayCustomPage(request, response, apiSession, pageId); } else { resourceRenderer.renderFile(request, response, getResourceFile(response, apiSession, pageId, resourcePath), apiSession); } } catch (final PageNotFoundException e) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "Cannot find the page with ID " + pageId); } response.sendError(HttpServletResponse.SC_NOT_FOUND, "Page not found"); } } private boolean isNotResourcePath(final String resourcePath) { return resourcePath == null || CustomPageService.PAGE_INDEX_FILENAME.equals(resourcePath) || CustomPageService.PAGE_CONTROLLER_FILENAME.equals(resourcePath) || CustomPageService.PAGE_INDEX_NAME.equals(resourcePath); } protected File getResourceFile(final HttpServletResponse response, final APISession apiSession, final Long pageId, final String resourcePath) throws IOException, BonitaException { final PageResourceProviderImpl pageResourceProvider = pageRenderer.getPageResourceProvider(pageId, apiSession); final File resourceFile = pageResourceProvider.getResourceAsFile(CustomPageService.RESOURCES_PROPERTY + File.separator + resourcePath); if (!bonitaHomeFolderAccessor.isInFolder(resourceFile, pageResourceProvider.getPageDirectory())) { final String message = "For security reasons, access to this file path is forbidden : " + resourcePath; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Forbidden: " + message); } response.sendError(HttpServletResponse.SC_FORBIDDEN); } pageRenderer.ensurePageFolderIsPresent(apiSession, pageResourceProvider); return resourceFile; } protected void displayExternalPage(final HttpServletRequest request, final HttpServletResponse response, final String url) throws IOException { response.sendRedirect(response.encodeRedirectURL(url)); } protected void handleException(final HttpServletResponse response, final String mappingKey, final Exception e) throws ServletException { try { if (e instanceof IllegalArgumentException) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "The parameters passed to the servlet are invalid.", e); } response.sendError(HttpServletResponse.SC_BAD_REQUEST); } else { if (LOGGER.isLoggable(Level.WARNING)) { final String message = "Error while trying to display a page for key " + mappingKey; LOGGER.log(Level.WARNING, message, e); } response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } } catch (final IOException ioe) { throw new ServletException(ioe); } } }