/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. * * * This file incorporates work covered by the following copyright and * permission notice: * * Copyright 2004 The Apache Software Foundation * * 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.apache.catalina.core; import org.apache.catalina.Context; import org.apache.catalina.Globals; import org.apache.catalina.Wrapper; import org.apache.catalina.deploy.ErrorPage; import org.apache.catalina.util.ResponseUtil; import org.apache.catalina.util.StringManager; import org.apache.catalina.valves.ErrorReportValve; import org.glassfish.logging.annotation.LogMessageInfo; import org.glassfish.web.util.HtmlEntityEncoder; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.text.MessageFormat; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; /** * Class responsible for processing the result of a RD.forward() invocation * before committing the response. * * If sendError() was called during RD.forward(), an attempt is made to match * the status code against the error pages of the RD's associated context, or * those of the host on which the context has been deployed. * * The response contents are then committed, to comply with SRV.8.4 * ("The Forward Method"): * * Before the forward method of the <code>RequestDispatcher</code> * interface returns without exception, the response content must be * sent and committed, and closed by the servlet container. * * If an error occurs in the target of the * <code>RequestDispatcher.forward()</code> the exception may be * propogated back through all the calling filters and servlets and * eventually back to the container. */ class ApplicationDispatcherForward { private static final Logger log = StandardServer.log; private static final ResourceBundle rb = log.getResourceBundle(); @LogMessageInfo( message = "Exception processing {0}", level = "WARNING") public static final String EXCEPTION_PROCESSING = "AS-WEB-CORE-00095"; @LogMessageInfo( message = "Exception sending default error page", level = "WARNING") public static final String EXCEPTION_SENDING_DEFAULT_ERROR_PAGE = "AS-WEB-CORE-00096"; private static final StringManager sm = StringManager.getManager(org.apache.catalina.valves.Constants.Package); static void commit(ServletRequest request, ServletResponse response, Context context, Wrapper wrapper) throws IOException, ServletException { if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { closeResponse(response); return; } HttpServletRequest hreq = (HttpServletRequest) request; HttpServletResponse hres = (HttpServletResponse) response; int statusCode = hres.getStatus(); Object exception = hreq.getAttribute( RequestDispatcher.ERROR_EXCEPTION); String errorReportValveClass = ((StandardHost)(context.getParent())).getErrorReportValveClass(); boolean hasErrorReportValve = (errorReportValveClass != null && errorReportValveClass.length() > 0); RequestFacadeHelper reqFacHelper = RequestFacadeHelper.getInstance(request); if (hasErrorReportValve && reqFacHelper != null && reqFacHelper.isResponseError() && statusCode >= 400 && exception == null) { boolean matchFound = status(hreq, hres, context, wrapper, statusCode); if (!matchFound) { boolean isDefaultErrorPageEnabled = true; if (wrapper != null) { String initParam = wrapper.findInitParameter(Constants.IS_DEFAULT_ERROR_PAGE_ENABLED_INIT_PARAM); if (initParam != null) { isDefaultErrorPageEnabled = Boolean.parseBoolean(initParam); } } if (isDefaultErrorPageEnabled) { serveDefaultErrorPage(hreq, hres, statusCode); } } } /* * Commit the response only if no exception */ if (statusCode < 400 || (exception == null && hasErrorReportValve)) { closeResponse(response); } } /* * Searches and processes a custom error page for the given status code. * * This method attempts to map the given status code to an error page, * using the mappings of the given context or those of the host on which * the given context has been deployed. * * If a match is found using the context mappings, the request is forwarded * to the error page. Otherwise, if a match is found using the host * mappings, the contents of the error page are returned. If no match is * found, no action is taken. * * @return true if a matching error page was found, false otherwise */ private static boolean status(HttpServletRequest request, HttpServletResponse response, Context context, Wrapper wrapper, int statusCode) { RequestFacadeHelper reqFacHelper = RequestFacadeHelper.getInstance(request); /* * Attempt error-page mapping only if response.sendError(), as * opposed to response.setStatus(), was called. */ if (reqFacHelper == null || !reqFacHelper.isResponseError()) { return false; } boolean matchFound = false; ErrorPage errorPage = context.findErrorPage(statusCode); if (errorPage != null) { matchFound = true; // Prevent infinite loop String requestPath = (String) request.getAttribute( Globals.DISPATCHER_REQUEST_PATH_ATTR); if (requestPath == null || !requestPath.equals(errorPage.getLocation())) { String message = reqFacHelper.getResponseMessage(); if (message == null) { message = ""; } else { message = HtmlEntityEncoder.encodeXSS(message); } prepareRequestForDispatch(request, wrapper, errorPage.getLocation(), statusCode, message); custom(request, response, errorPage, context); } } else { errorPage = ((StandardHost) context.getParent()).findErrorPage( statusCode); if (errorPage != null) { matchFound = true; try { serveErrorPage(response, errorPage, statusCode); } catch (Exception e) { String msg = MessageFormat.format(rb.getString(EXCEPTION_PROCESSING), errorPage); log.log(Level.WARNING, msg, e); } } } return matchFound; } /** * Handles an HTTP status code or exception by forwarding control * to the location included in the specified errorPage object. */ private static void custom(HttpServletRequest request, HttpServletResponse response, ErrorPage errorPage, Context context) { try { // Forward control to the specified error page if (response.isCommitted()) { /* * If the target of the RD.forward() has called * response.sendError(), the response will have been committed, * and any attempt to RD.forward() the response to the error * page will cause an IllegalStateException. * Uncommit the response. */ RequestFacadeHelper reqFacHelper = RequestFacadeHelper.getInstance(request); if (reqFacHelper != null) { reqFacHelper.resetResponse(); } } ServletContext servletContext = context.getServletContext(); ApplicationDispatcher dispatcher = (ApplicationDispatcher) servletContext.getRequestDispatcher(errorPage.getLocation()); dispatcher.dispatch(request, response, DispatcherType.ERROR); } catch (IllegalStateException ise) { String msg = MessageFormat.format(rb.getString(EXCEPTION_PROCESSING), errorPage); log.log(Level.WARNING, msg, ise); } catch (Throwable t) { String msg = MessageFormat.format(rb.getString(EXCEPTION_PROCESSING), errorPage); log.log(Level.WARNING, msg, t); } } /** * Adds request attributes in preparation for RD.forward(). */ private static void prepareRequestForDispatch(HttpServletRequest request, Wrapper errorServlet, String errorPageLocation, int errorCode, String errorMessage) { request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI()); if (errorServlet != null) { // Save the logical name of the servlet in which the error occurred request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, errorServlet.getName()); } request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, errorPageLocation); request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, Integer.valueOf(errorCode)); request.setAttribute(RequestDispatcher.ERROR_MESSAGE, errorMessage); } /** * Copies the contents of the given error page to the response. */ private static void serveErrorPage(HttpServletResponse response, ErrorPage errorPage, int statusCode) throws Exception { ServletOutputStream ostream = null; PrintWriter writer = null; FileReader reader = null; BufferedInputStream istream = null; IOException ioe = null; String message = errorPage.getReason(); if (message != null && !response.isCommitted()) { response.reset(); response.setStatus(statusCode, message); } try { ostream = response.getOutputStream(); } catch (IllegalStateException e) { // If it fails, we try to get a Writer instead if we're // trying to serve a text file writer = response.getWriter(); } if (writer != null) { reader = new FileReader(errorPage.getLocation()); ioe = ResponseUtil.copy(reader, writer); try { reader.close(); } catch (Throwable t) { ; } } else { istream = new BufferedInputStream( new FileInputStream(errorPage.getLocation())); ioe = ResponseUtil.copy(istream, ostream); try { istream.close(); } catch (Throwable t) { ; } } // Rethrow any exception that may have occurred if (ioe != null) { throw ioe; } } /** * Renders the default error page. */ private static void serveDefaultErrorPage(HttpServletRequest request, HttpServletResponse response, int statusCode) throws IOException, ServletException { RequestFacadeHelper reqFacHelper = RequestFacadeHelper.getInstance(request); // Do nothing on a 1xx, 2xx and 3xx status if (response.isCommitted() || statusCode < 400 || (reqFacHelper != null && reqFacHelper.getResponseContentCount() > 0) || Boolean.TRUE.equals(request.getAttribute("org.glassfish.jsp.error_handled"))) { return; } String message = null; if (reqFacHelper != null) { message = reqFacHelper.getResponseMessage(); } if (message == null) { message = ""; } else { message = HtmlEntityEncoder.encodeXSS(message); } // Do nothing if there is no report for the specified status code String report = null; try { report = sm.getString("http." + statusCode, message); } catch (Throwable t) { ; } if (report == null) { return; } String responseContents = ErrorReportValve.makeErrorPage(statusCode, message, null, null, report, response); // START SJSAS 6412710 response.setLocale(sm.getResourceBundleLocale(response.getLocale())); // END SJSAS 6412710 try { response.setContentType("text/html"); response.getWriter().write(responseContents); } catch (Throwable t) { log.log(Level.WARNING, EXCEPTION_SENDING_DEFAULT_ERROR_PAGE, t); } } /* private static ResponseFacade getResponseFacade(ServletResponse response) { while (response instanceof ServletResponseWrapper) { response = ((ServletResponseWrapper) response).getResponse(); } return ((ResponseFacade) response); } */ private static void closeResponse(ServletResponse response) { try { PrintWriter writer = response.getWriter(); writer.flush(); writer.close(); } catch (IllegalStateException e) { try { ServletOutputStream stream = response.getOutputStream(); stream.flush(); stream.close(); } catch (IllegalStateException f) { ; } catch (IOException f) { ; } } catch (IOException e) { ; } } }