| /* |
| * Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved. |
| * 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 static jakarta.servlet.DispatcherType.ERROR; |
| import static jakarta.servlet.RequestDispatcher.ERROR_MESSAGE; |
| import static jakarta.servlet.RequestDispatcher.ERROR_REQUEST_URI; |
| import static jakarta.servlet.RequestDispatcher.ERROR_SERVLET_NAME; |
| import static jakarta.servlet.RequestDispatcher.ERROR_STATUS_CODE; |
| import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; |
| import static java.util.Locale.ENGLISH; |
| import static java.util.logging.Level.FINE; |
| import static java.util.logging.Level.INFO; |
| import static java.util.logging.Level.WARNING; |
| import static org.apache.catalina.Globals.DISPATCHER_REQUEST_PATH_ATTR; |
| import static org.apache.catalina.LogFacade.NO_CONTEXT_TO_PROCESS; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.ResourceBundle; |
| import java.util.logging.Logger; |
| |
| import org.apache.catalina.Context; |
| import org.apache.catalina.HttpRequest; |
| import org.apache.catalina.HttpResponse; |
| import org.apache.catalina.LogFacade; |
| import org.apache.catalina.Request; |
| import org.apache.catalina.Response; |
| import org.apache.catalina.Wrapper; |
| import org.apache.catalina.connector.ClientAbortException; |
| import org.apache.catalina.deploy.ErrorPage; |
| import org.apache.catalina.util.ResponseUtil; |
| import org.apache.catalina.valves.ValveBase; |
| import org.glassfish.web.valve.GlassFishValve; |
| |
| import jakarta.servlet.RequestDispatcher; |
| import jakarta.servlet.ServletContext; |
| import jakarta.servlet.ServletException; |
| import jakarta.servlet.ServletOutputStream; |
| import jakarta.servlet.ServletRequest; |
| import jakarta.servlet.ServletResponse; |
| import jakarta.servlet.http.HttpServletRequest; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| /** |
| * Valve that implements the default basic behavior for the <code>StandardHost</code> container implementation. |
| * <p> |
| * <b>USAGE CONSTRAINT</b>: This implementation is likely to be useful only when processing HTTP requests. |
| * |
| * @author Craig R. McClanahan |
| * @author Remy Maucherat |
| * @version $Revision: 1.21 $ $Date: 2007/04/10 17:12:22 $ |
| */ |
| |
| final class StandardHostValve extends ValveBase { |
| |
| private static final Logger log = LogFacade.getLogger(); |
| private static final ResourceBundle rb = log.getResourceBundle(); |
| |
| private static final ClassLoader standardHostValveClassLoader = StandardHostValve.class.getClassLoader(); |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| /** |
| * The descriptive information related to this implementation. |
| */ |
| private static final String info = "org.apache.catalina.core.StandardHostValve/1.0"; |
| |
| private GlassFishValve errorReportValve; |
| |
| // ------------------------------------------------------------- Properties |
| |
| /** |
| * Return descriptive information about this Valve implementation. |
| */ |
| @Override |
| public String getInfo() { |
| return info; |
| } |
| |
| // --------------------------------------------------------- Public Methods |
| |
| /** |
| * Select the appropriate child Context to process this request, based on the specified request URI. If no matching |
| * Context can be found, return an appropriate HTTP error. |
| * |
| * @param request Request to be processed |
| * @param response Response to be produced |
| * |
| * @exception IOException if an input/output error occurred |
| * @exception ServletException if a servlet error occurred |
| */ |
| @Override |
| public int invoke(Request request, Response response) throws IOException, ServletException { |
| Context context = preInvoke(request, response); |
| if (context == null) { |
| return END_PIPELINE; |
| } |
| |
| // Ask this Context to process this request |
| if (context.getPipeline().hasNonBasicValves() || context.hasCustomPipeline()) { |
| context.getPipeline().invoke(request, response); |
| } else { |
| context.getPipeline().getBasic().invoke(request, response); |
| } |
| |
| return END_PIPELINE; |
| } |
| |
| /** |
| * Tomcat style invocation. |
| */ |
| @Override |
| public void invoke(org.apache.catalina.connector.Request request, org.apache.catalina.connector.Response response) throws IOException, ServletException { |
| Context context = preInvoke(request, response); |
| if (context == null) { |
| return; |
| } |
| |
| // Ask this Context to process this request |
| if (context.getPipeline().hasNonBasicValves() || context.hasCustomPipeline()) { |
| context.getPipeline().invoke(request, response); |
| } else { |
| context.getPipeline().getBasic().invoke(request, response); |
| } |
| |
| postInvoke(request, response); |
| } |
| |
| @Override |
| public void postInvoke(Request request, Response response) throws IOException, ServletException { |
| try { |
| |
| HttpServletRequest hreq = (HttpServletRequest) request.getRequest(); |
| |
| // Error page processing |
| response.setSuspended(false); |
| |
| Throwable t = (Throwable) hreq.getAttribute(RequestDispatcher.ERROR_EXCEPTION); |
| |
| if (t != null) { |
| throwable(request, response, t); |
| } else { |
| status(request, response); |
| } |
| |
| boolean isDefaultErrorPageEnabled = true; |
| Wrapper wrapper = request.getWrapper(); |
| if (wrapper != null) { |
| String initParam = wrapper.findInitParameter(Constants.IS_DEFAULT_ERROR_PAGE_ENABLED_INIT_PARAM); |
| if (initParam != null) { |
| isDefaultErrorPageEnabled = Boolean.parseBoolean(initParam); |
| } |
| } |
| |
| if (errorReportValve != null && response.isError() && isDefaultErrorPageEnabled) { |
| errorReportValve.postInvoke(request, response); |
| } |
| |
| Context context = request.getContext(); |
| if (context != null) { |
| context.fireRequestDestroyedEvent(hreq); |
| } |
| } finally { |
| Thread.currentThread().setContextClassLoader(standardHostValveClassLoader); |
| } |
| } |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| /** |
| * Handle the specified Throwable encountered while processing the specified Request to produce the specified Response. |
| * Any exceptions that occur during generation of the exception report are logged and swallowed. |
| * |
| * @param request The request being processed |
| * @param response The response being generated |
| * @param throwable The throwable that occurred (which possibly wraps a root cause exception |
| */ |
| protected void throwable(Request request, Response response, Throwable throwable) { |
| Context context = request.getContext(); |
| if (context == null) { |
| return; |
| } |
| |
| Throwable realError = throwable; |
| if (realError instanceof ServletException) { |
| realError = ((ServletException) realError).getRootCause(); |
| if (realError == null) { |
| realError = throwable; |
| } |
| } |
| |
| // If this is an aborted request from a client just log it and return |
| if (realError instanceof ClientAbortException) { |
| if (log.isLoggable(FINE)) { |
| log.log(FINE, LogFacade.REMOTE_CLIENT_ABORTED_EXCEPTION, realError.getCause().getMessage()); |
| } |
| return; |
| } |
| |
| ErrorPage errorPage = findErrorPage(context, throwable); |
| if ((errorPage == null) && (realError != throwable)) { |
| errorPage = findErrorPage(context, realError); |
| } |
| |
| if (errorPage != null) { |
| dispatchToErrorPage(request, response, errorPage, throwable, realError, 0); |
| } else if (context.getDefaultErrorPage() != null) { |
| dispatchToErrorPage(request, response, context.getDefaultErrorPage(), throwable, realError, 0); |
| } else { |
| // A custom error-page has not been defined for the exception |
| // that was thrown during request processing. Check if an |
| // error-page for error code 500 was specified and if so, |
| // send that page back as the response. |
| ServletResponse servletResponse = (ServletResponse) response; |
| |
| ((HttpServletResponse) servletResponse).setStatus(SC_INTERNAL_SERVER_ERROR); |
| |
| // The response is an error |
| response.setError(); |
| |
| status(request, response); |
| } |
| |
| } |
| |
| /** |
| * Handle the HTTP status code (and corresponding message) generated while processing the specified Request to produce |
| * the specified Response. Any exceptions that occur during generation of the error report are logged and swallowed. |
| * |
| * @param request The request being processed |
| * @param response The response being generated |
| */ |
| protected void status(Request request, Response response) { |
| // Handle a custom error page for this status code |
| Context context = request.getContext(); |
| if (context == null) { |
| return; |
| } |
| |
| /* |
| * Only look for error pages when isError() is set. isError() is set when response.sendError() is invoked. |
| */ |
| if (!response.isError()) { |
| return; |
| } |
| |
| int statusCode = ((HttpResponse) response).getStatus(); |
| ErrorPage errorPage = context.findErrorPage(statusCode); |
| if (errorPage != null) { |
| if (errorPage.getLocation() != null) { |
| File file = new File(context.getDocBase(), errorPage.getLocation()); |
| if (!file.exists()) { |
| File file2 = new File(errorPage.getLocation()); |
| if (!file2.exists()) { |
| log.log(WARNING, LogFacade.ERROR_PAGE_NOT_EXIST, |
| new Object[] { file.getAbsolutePath(), file2.getAbsolutePath() }); |
| } |
| } |
| } |
| setErrorPageContentType(response, errorPage.getLocation(), context); |
| dispatchToErrorPage(request, response, errorPage, null, null, statusCode); |
| } else if (statusCode >= 400 && statusCode < 600 && context.getDefaultErrorPage() != null) { |
| dispatchToErrorPage(request, response, context.getDefaultErrorPage(), null, null, statusCode); |
| } |
| // START SJSAS 6324911 |
| else { |
| errorPage = ((StandardHost) getContainer()).findErrorPage(statusCode); |
| if (errorPage != null) { |
| if (errorPage.getLocation() != null) { |
| File file = new File(context.getDocBase(), errorPage.getLocation()); |
| if (!file.exists()) { |
| File file2 = new File(errorPage.getLocation()); |
| if (!file2.exists()) { |
| log.log(WARNING, LogFacade.ERROR_PAGE_NOT_EXIST, |
| new Object[] { file.getAbsolutePath(), file2.getAbsolutePath() }); |
| } |
| } |
| } |
| try { |
| setErrorPageContentType(response, errorPage.getLocation(), context); |
| handleHostErrorPage(response, errorPage, statusCode); |
| } catch (Exception e) { |
| log("Exception processing " + errorPage, e); |
| } |
| } |
| } |
| // END SJSAS 6324911 |
| } |
| |
| /** |
| * Find and return the ErrorPage instance for the specified exception's class, or an ErrorPage instance for the closest |
| * superclass for which there is such a definition. If no associated ErrorPage instance is found, return |
| * <code>null</code>. |
| * |
| * @param context The Context in which to search |
| * @param exception The exception for which to find an ErrorPage |
| */ |
| protected static ErrorPage findErrorPage(Context context, Throwable exception) { |
| if (exception == null) { |
| return null; |
| } |
| |
| Class<?> clazz = exception.getClass(); |
| String name = clazz.getName(); |
| while (!Object.class.equals(clazz)) { |
| ErrorPage errorPage = context.findErrorPage(name); |
| if (errorPage != null) { |
| return errorPage; |
| } |
| |
| clazz = clazz.getSuperclass(); |
| if (clazz == null) { |
| break; |
| } |
| name = clazz.getName(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Handle an HTTP status code or Java exception by forwarding control to the location included in the specified |
| * errorPage object. It is assumed that the caller has already recorded any request attributes that are to be forwarded |
| * to this page. Return <code>true</code> if we successfully utilized the specified error page location, or |
| * <code>false</code> if the default error report should be rendered. |
| * |
| * @param request The request being processed |
| * @param response The response being generated |
| * @param errorPage The errorPage directive we are obeying |
| */ |
| protected boolean custom(Request request, Response response, ErrorPage errorPage) { |
| if (debug >= 1) { |
| log("Processing " + errorPage); |
| } |
| |
| HttpResponse httpResponse = (HttpResponse) response; |
| HttpRequest httpRequest = (HttpRequest) request; |
| |
| HttpServletRequest httpServletRequest = (HttpServletRequest) request.getRequest(); |
| HttpServletResponse httpServletResponse = (HttpServletResponse) response.getResponse(); |
| |
| httpRequest.setPathInfo(errorPage.getLocation()); |
| |
| try { |
| httpResponse.setError( |
| (Integer) httpServletRequest.getAttribute(ERROR_STATUS_CODE), |
| (String) httpServletRequest.getAttribute(ERROR_MESSAGE)); |
| |
| // Forward control to the specified location |
| ServletContext servletContext = request.getContext().getServletContext(); |
| ApplicationDispatcher dispatcher = (ApplicationDispatcher) servletContext.getRequestDispatcher(errorPage.getLocation()); |
| |
| if (httpServletResponse.isCommitted()) { |
| // Response is committed - including the error page is the best we can do |
| dispatcher.include(httpServletRequest, httpServletResponse); |
| } else { |
| // Reset the response (keeping the real error code and message) |
| response.resetBuffer(true); |
| |
| dispatcher.dispatch(httpServletRequest, httpServletResponse, ERROR); |
| |
| // If we forward, the response is suspended again |
| response.setSuspended(false); |
| } |
| |
| // Indicate that we have successfully processed this custom page |
| return true; |
| |
| } catch (Throwable t) { |
| // Report our failure to process this custom page |
| log("Exception Processing " + errorPage, t); |
| return false; |
| } |
| |
| } |
| |
| /** |
| * Log a message on the Logger associated with our Container (if any). |
| * |
| * @param message Message to be logged |
| */ |
| protected void log(String message) { |
| org.apache.catalina.Logger logger = container.getLogger(); |
| if (logger != null) { |
| logger.log(this.toString() + ": " + message); |
| } else { |
| if (log.isLoggable(INFO)) { |
| log.log(INFO, this.toString() + ": " + message); |
| } |
| } |
| } |
| |
| /** |
| * Log a message on the Logger associated with our Container (if any). |
| * |
| * @param message Message to be logged |
| * @param t Associated exception |
| */ |
| protected void log(String message, Throwable t) { |
| org.apache.catalina.Logger logger = container.getLogger(); |
| if (logger != null) { |
| logger.log(this.toString() + ": " + message, t, org.apache.catalina.Logger.WARNING); |
| } else { |
| log.log(WARNING, this.toString() + ": " + message, t); |
| } |
| } |
| |
| /** |
| * Copies the contents of the given error page to the response, and updates the status message with the reason string of |
| * the error page. |
| * |
| * @param response The response object |
| * @param errorPage The error page whose contents are to be copied |
| * @param statusCode The status code |
| */ |
| private void handleHostErrorPage(Response response, ErrorPage errorPage, int statusCode) throws Exception { |
| ServletOutputStream outputStream = null; |
| PrintWriter writer = null; |
| FileReader reader = null; |
| BufferedInputStream istream = null; |
| IOException ioException = null; |
| |
| if (!response.getResponse().isCommitted()) { |
| response.resetBuffer(true); |
| } |
| |
| String message = errorPage.getReason(); |
| if (message != null) { |
| ((HttpResponse) response).reset(statusCode, message); |
| } |
| |
| try { |
| outputStream = response.getResponse().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.getResponse().getWriter(); |
| } |
| |
| if (writer != null) { |
| reader = new FileReader(errorPage.getLocation()); |
| ioException = ResponseUtil.copy(reader, writer); |
| try { |
| reader.close(); |
| } catch (Throwable t) { |
| |
| } |
| } else { |
| istream = new BufferedInputStream(new FileInputStream(errorPage.getLocation())); |
| ioException = ResponseUtil.copy(istream, outputStream); |
| try { |
| istream.close(); |
| } catch (Throwable t) { |
| |
| } |
| } |
| |
| // Rethrow any exception that may have occurred |
| if (ioException != null) { |
| throw ioException; |
| } |
| } |
| |
| void setErrorReportValve(GlassFishValve errorReportValve) { |
| this.errorReportValve = errorReportValve; |
| } |
| |
| private Context preInvoke(Request request, Response response) throws IOException, ServletException { |
| // Select the Context to be used for this Request |
| Context context = request.getContext(); |
| if (context == null) { |
| ((HttpServletResponse) response.getResponse()).sendError(SC_INTERNAL_SERVER_ERROR); |
| response.setDetailMessage(rb.getString(NO_CONTEXT_TO_PROCESS)); |
| return null; |
| } |
| |
| // Bind the context CL to the current thread |
| if (context.getLoader() != null) { |
| // Not started - it should check for availability first |
| // This should eventually move to Engine, it's generic. |
| Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader()); |
| } |
| |
| // Update the session last access time for our session (if any) |
| HttpServletRequest httpServletRequest = (HttpServletRequest) request.getRequest(); |
| httpServletRequest.getSession(false); |
| |
| context.fireRequestInitializedEvent(httpServletRequest); |
| |
| return context; |
| } |
| |
| private void dispatchToErrorPage(Request request, Response response, ErrorPage errorPage, Throwable throwable, Throwable realError, int statusCode) { |
| response.setAppCommitted(false); |
| |
| ServletRequest servletRequest = request.getRequest(); |
| ServletResponse servletResponse = response.getResponse(); |
| |
| servletRequest.setAttribute(DISPATCHER_REQUEST_PATH_ATTR, errorPage.getLocation()); |
| servletRequest.setAttribute(ERROR_REQUEST_URI, ((HttpServletRequest) servletRequest).getRequestURI()); |
| Wrapper wrapper = request.getWrapper(); |
| if (wrapper != null) { |
| servletRequest.setAttribute(ERROR_SERVLET_NAME, wrapper.getName()); |
| } |
| |
| if (throwable != null) { |
| servletRequest.setAttribute(ERROR_STATUS_CODE, Integer.valueOf(SC_INTERNAL_SERVER_ERROR)); |
| servletRequest.setAttribute(ERROR_MESSAGE, throwable.getMessage()); |
| servletRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, realError); |
| servletRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, realError.getClass()); |
| } else { |
| servletRequest.setAttribute(ERROR_STATUS_CODE, Integer.valueOf(statusCode)); |
| String message = ((HttpResponse) response).getMessage(); |
| if (message == null) { |
| message = ""; |
| } |
| servletRequest.setAttribute(ERROR_MESSAGE, message); |
| } |
| |
| if (custom(request, response, errorPage)) { |
| try { |
| servletResponse.flushBuffer(); |
| } catch (IOException e) { |
| log("Exception processing " + errorPage, e); |
| } |
| } |
| } |
| |
| private void setErrorPageContentType(Response response, String location, Context context) { |
| if (response.getContentType() == null && location != null) { |
| String contentType = location.substring(location.lastIndexOf('.') + 1); |
| contentType = context.findMimeMapping(contentType.toLowerCase(ENGLISH)); |
| if (contentType != null) { |
| ((ServletResponse) response).setContentType(contentType); |
| } |
| } |
| } |
| |
| } |