//
//  ========================================================================
//  Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.server;

import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

public class Dispatcher implements RequestDispatcher
{
    private static final Logger LOG = Log.getLogger(Dispatcher.class);

    public static final String __ERROR_DISPATCH = "org.eclipse.jetty.server.Dispatcher.ERROR";

    
Dispatch include attribute names
/** * Dispatch include attribute names */
public static final String __INCLUDE_PREFIX = "javax.servlet.include.";
Dispatch include attribute names
/** * Dispatch include attribute names */
public static final String __FORWARD_PREFIX = "javax.servlet.forward."; private final ContextHandler _contextHandler; private final HttpURI _uri; private final String _pathInContext; private final String _named; public Dispatcher(ContextHandler contextHandler, HttpURI uri, String pathInContext) { _contextHandler = contextHandler; _uri = uri; _pathInContext = pathInContext; _named = null; } public Dispatcher(ContextHandler contextHandler, String name) throws IllegalStateException { _contextHandler = contextHandler; _uri = null; _pathInContext = null; _named = name; } public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException { try { request.setAttribute(__ERROR_DISPATCH, Boolean.TRUE); forward(request, response, DispatcherType.ERROR); } finally { request.setAttribute(__ERROR_DISPATCH, null); } } @Override public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException { Request baseRequest = Request.getBaseRequest(request); if (!(request instanceof HttpServletRequest)) request = new ServletRequestHttpWrapper(request); if (!(response instanceof HttpServletResponse)) response = new ServletResponseHttpWrapper(response); final DispatcherType old_type = baseRequest.getDispatcherType(); final Attributes old_attr = baseRequest.getAttributes(); final MultiMap<String> old_query_params = baseRequest.getQueryParameters(); try { baseRequest.setDispatcherType(DispatcherType.INCLUDE); baseRequest.getResponse().include(); if (_named != null) { _contextHandler.handle(_named, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); } else { IncludeAttributes attr = new IncludeAttributes(old_attr); attr._requestURI = _uri.getPath(); attr._contextPath = _contextHandler.getContextPath(); attr._servletPath = null; // set by ServletHandler attr._pathInfo = _pathInContext; attr._query = _uri.getQuery(); if (attr._query != null) baseRequest.mergeQueryParameters(baseRequest.getQueryString(), attr._query, false); baseRequest.setAttributes(attr); _contextHandler.handle(_pathInContext, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); } } finally { baseRequest.setAttributes(old_attr); baseRequest.getResponse().included(); baseRequest.setQueryParameters(old_query_params); baseRequest.resetParameters(); baseRequest.setDispatcherType(old_type); } } @Override public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException { forward(request, response, DispatcherType.FORWARD); } protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException { Request baseRequest = Request.getBaseRequest(request); Response baseResponse = baseRequest.getResponse(); baseResponse.resetForForward(); if (!(request instanceof HttpServletRequest)) request = new ServletRequestHttpWrapper(request); if (!(response instanceof HttpServletResponse)) response = new ServletResponseHttpWrapper(response); final HttpURI old_uri = baseRequest.getHttpURI(); final String old_context_path = baseRequest.getContextPath(); final String old_servlet_path = baseRequest.getServletPath(); final String old_path_info = baseRequest.getPathInfo(); final MultiMap<String> old_query_params = baseRequest.getQueryParameters(); final Attributes old_attr = baseRequest.getAttributes(); final DispatcherType old_type = baseRequest.getDispatcherType(); try { baseRequest.setDispatcherType(dispatch); if (_named != null) { _contextHandler.handle(_named, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); } else { ForwardAttributes attr = new ForwardAttributes(old_attr); //If we have already been forwarded previously, then keep using the established //original value. Otherwise, this is the first forward and we need to establish the values. //Note: the established value on the original request for pathInfo and //for queryString is allowed to be null, but cannot be null for the other values. if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null) { attr._pathInfo = (String)old_attr.getAttribute(FORWARD_PATH_INFO); attr._query = (String)old_attr.getAttribute(FORWARD_QUERY_STRING); attr._requestURI = (String)old_attr.getAttribute(FORWARD_REQUEST_URI); attr._contextPath = (String)old_attr.getAttribute(FORWARD_CONTEXT_PATH); attr._servletPath = (String)old_attr.getAttribute(FORWARD_SERVLET_PATH); } else { attr._pathInfo = old_path_info; attr._query = old_uri.getQuery(); attr._requestURI = old_uri.getPath(); attr._contextPath = old_context_path; attr._servletPath = old_servlet_path; } HttpURI uri = new HttpURI(old_uri.getScheme(), old_uri.getHost(), old_uri.getPort(), _uri.getPath(), _uri.getParam(), _uri.getQuery(), _uri.getFragment()); baseRequest.setHttpURI(uri); baseRequest.setContextPath(_contextHandler.getContextPath()); baseRequest.setServletPath(null); baseRequest.setPathInfo(_pathInContext); if (_uri.getQuery() != null || old_uri.getQuery() != null) { try { baseRequest.mergeQueryParameters(old_uri.getQuery(), _uri.getQuery(), true); } catch (BadMessageException e) { // Only throw BME if not in Error Dispatch Mode // This allows application ErrorPageErrorHandler to handle BME messages if (dispatch != DispatcherType.ERROR) { throw e; } else { LOG.warn("Ignoring Original Bad Request Query String: " + old_uri, e); } } } baseRequest.setAttributes(attr); _contextHandler.handle(_pathInContext, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); if (!baseRequest.getHttpChannelState().isAsync()) commitResponse(response, baseRequest); } } finally { baseRequest.setHttpURI(old_uri); baseRequest.setContextPath(old_context_path); baseRequest.setServletPath(old_servlet_path); baseRequest.setPathInfo(old_path_info); baseRequest.setQueryParameters(old_query_params); baseRequest.resetParameters(); baseRequest.setAttributes(old_attr); baseRequest.setDispatcherType(old_type); } } @Override public String toString() { return String.format("Dispatcher@0x%x{%s,%s}", hashCode(), _named, _uri); } @SuppressWarnings("Duplicates") private void commitResponse(ServletResponse response, Request baseRequest) throws IOException, ServletException { if (baseRequest.getResponse().isWriting()) { try { // Try closing Writer first (based on knowledge in Response obj) response.getWriter().close(); } catch (IllegalStateException ex1) { try { // Try closing OutputStream as alternate route // This path is possible due to badly behaving Response wrappers response.getOutputStream().close(); } catch (IllegalStateException ex2) { ServletException servletException = new ServletException("Unable to commit the response", ex2); servletException.addSuppressed(ex1); throw servletException; } } } else { try { // Try closing OutputStream first (based on knowledge in Response obj) response.getOutputStream().close(); } catch (IllegalStateException ex1) { try { // Try closing Writer as alternate route // This path is possible due to badly behaving Response wrappers response.getWriter().close(); } catch (IllegalStateException ex2) { ServletException servletException = new ServletException("Unable to commit the response", ex2); servletException.addSuppressed(ex1); throw servletException; } } } } private class ForwardAttributes implements Attributes { final Attributes _attr; String _requestURI; String _contextPath; String _servletPath; String _pathInfo; String _query; ForwardAttributes(Attributes attributes) { _attr = attributes; } @Override public Object getAttribute(String key) { if (Dispatcher.this._named == null) { if (key.equals(FORWARD_PATH_INFO)) return _pathInfo; if (key.equals(FORWARD_REQUEST_URI)) return _requestURI; if (key.equals(FORWARD_SERVLET_PATH)) return _servletPath; if (key.equals(FORWARD_CONTEXT_PATH)) return _contextPath; if (key.equals(FORWARD_QUERY_STRING)) return _query; } if (key.startsWith(__INCLUDE_PREFIX)) return null; return _attr.getAttribute(key); } @Override public Enumeration<String> getAttributeNames() { HashSet<String> set = new HashSet<>(); Enumeration<String> e = _attr.getAttributeNames(); while (e.hasMoreElements()) { String name = e.nextElement(); if (!name.startsWith(__INCLUDE_PREFIX) && !name.startsWith(__FORWARD_PREFIX)) set.add(name); } if (_named == null) { if (_pathInfo != null) set.add(FORWARD_PATH_INFO); else set.remove(FORWARD_PATH_INFO); set.add(FORWARD_REQUEST_URI); set.add(FORWARD_SERVLET_PATH); set.add(FORWARD_CONTEXT_PATH); if (_query != null) set.add(FORWARD_QUERY_STRING); else set.remove(FORWARD_QUERY_STRING); } return Collections.enumeration(set); } @Override public void setAttribute(String key, Object value) { if (_named == null && key.startsWith("javax.servlet.")) { if (key.equals(FORWARD_PATH_INFO)) _pathInfo = (String)value; else if (key.equals(FORWARD_REQUEST_URI)) _requestURI = (String)value; else if (key.equals(FORWARD_SERVLET_PATH)) _servletPath = (String)value; else if (key.equals(FORWARD_CONTEXT_PATH)) _contextPath = (String)value; else if (key.equals(FORWARD_QUERY_STRING)) _query = (String)value; else if (value == null) _attr.removeAttribute(key); else _attr.setAttribute(key, value); } else if (value == null) _attr.removeAttribute(key); else _attr.setAttribute(key, value); } @Override public String toString() { return "FORWARD+" + _attr.toString(); } @Override public void clearAttributes() { throw new IllegalStateException(); } @Override public void removeAttribute(String name) { setAttribute(name, null); } } private class IncludeAttributes implements Attributes { final Attributes _attr; String _requestURI; String _contextPath; String _servletPath; String _pathInfo; String _query; IncludeAttributes(Attributes attributes) { _attr = attributes; } @Override public Object getAttribute(String key) { if (Dispatcher.this._named == null) { if (key.equals(INCLUDE_PATH_INFO)) return _pathInfo; if (key.equals(INCLUDE_SERVLET_PATH)) return _servletPath; if (key.equals(INCLUDE_CONTEXT_PATH)) return _contextPath; if (key.equals(INCLUDE_QUERY_STRING)) return _query; if (key.equals(INCLUDE_REQUEST_URI)) return _requestURI; } else if (key.startsWith(__INCLUDE_PREFIX)) return null; return _attr.getAttribute(key); } @Override public Enumeration<String> getAttributeNames() { HashSet<String> set = new HashSet<>(); Enumeration<String> e = _attr.getAttributeNames(); while (e.hasMoreElements()) { String name = e.nextElement(); if (!name.startsWith(__INCLUDE_PREFIX)) set.add(name); } if (_named == null) { if (_pathInfo != null) set.add(INCLUDE_PATH_INFO); else set.remove(INCLUDE_PATH_INFO); set.add(INCLUDE_REQUEST_URI); set.add(INCLUDE_SERVLET_PATH); set.add(INCLUDE_CONTEXT_PATH); if (_query != null) set.add(INCLUDE_QUERY_STRING); else set.remove(INCLUDE_QUERY_STRING); } return Collections.enumeration(set); } @Override public void setAttribute(String key, Object value) { if (_named == null && key.startsWith("javax.servlet.")) { if (key.equals(INCLUDE_PATH_INFO)) _pathInfo = (String)value; else if (key.equals(INCLUDE_REQUEST_URI)) _requestURI = (String)value; else if (key.equals(INCLUDE_SERVLET_PATH)) _servletPath = (String)value; else if (key.equals(INCLUDE_CONTEXT_PATH)) _contextPath = (String)value; else if (key.equals(INCLUDE_QUERY_STRING)) _query = (String)value; else if (value == null) _attr.removeAttribute(key); else _attr.setAttribute(key, value); } else if (value == null) _attr.removeAttribute(key); else _attr.setAttribute(key, value); } @Override public String toString() { return "INCLUDE+" + _attr.toString(); } @Override public void clearAttributes() { throw new IllegalStateException(); } @Override public void removeAttribute(String name) { setAttribute(name, null); } } }