| /* |
| * 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.connector; |
| |
| import java.nio.charset.Charset; |
| import java.security.cert.CertificateException; |
| import java.security.cert.X509Certificate; |
| import java.text.MessageFormat; |
| import java.util.Collection; |
| import java.util.ResourceBundle; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import jakarta.servlet.http.HttpServletRequest; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| import com.sun.appserv.ProxyHandler; |
| import java.io.CharConversionException; |
| import org.apache.catalina.Container; |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Globals; |
| import org.apache.catalina.Host; |
| import org.apache.catalina.LogFacade; |
| import org.apache.catalina.Wrapper; |
| import org.apache.catalina.core.ContainerBase; |
| import org.apache.catalina.util.ResponseUtil; |
| import org.apache.catalina.util.ServerInfo; |
| import org.apache.catalina.util.StringManager; |
| import org.glassfish.common.util.InputValidationUtil; |
| import org.glassfish.grizzly.http.Method; |
| import org.glassfish.grizzly.http.server.AfterServiceListener; |
| import org.glassfish.grizzly.http.server.HttpHandler; |
| import org.glassfish.grizzly.http.Note; |
| import org.glassfish.grizzly.http.server.util.MappingData; |
| import org.glassfish.grizzly.http.util.ByteChunk; |
| import org.glassfish.grizzly.http.util.CharChunk; |
| import org.glassfish.grizzly.http.util.DataChunk; |
| import org.glassfish.grizzly.http.util.MessageBytes; |
| import org.glassfish.hk2.api.ServiceLocator; |
| import org.glassfish.web.valve.GlassFishValve; |
| import org.glassfish.web.valve.ServletContainerInterceptor; |
| |
| /** |
| * Implementation of a request processor which delegates the processing to a |
| * Coyote processor. |
| * |
| * @author Craig R. McClanahan |
| * @author Remy Maucherat |
| * @version $Revision: 1.34 $ $Date: 2007/08/24 18:38:28 $ |
| */ |
| |
| public class CoyoteAdapter extends HttpHandler { |
| |
| private static final Logger log = LogFacade.getLogger(); |
| private static final ResourceBundle rb = log.getResourceBundle(); |
| |
| // -------------------------------------------------------------- Constants |
| private static final String POWERED_BY = "Servlet/3.1 JSP/2.3 " + |
| "(" + ServerInfo.getServerInfo() + " Java/" + |
| System.getProperty("java.vm.vendor") + "/" + |
| System.getProperty("java.specification.version") + ")"; |
| |
| |
| // protected boolean v3Enabled = |
| // Boolean.valueOf(System.getProperty("v3.grizzly.useMapper", "true")); |
| |
| |
| // public static final int ADAPTER_NOTES = 1; |
| |
| static final String JVM_ROUTE = System.getProperty("jvmRoute"); |
| |
| private Collection<ServletContainerInterceptor> interceptors = null; |
| |
| protected static final boolean ALLOW_BACKSLASH = |
| Boolean.valueOf(System.getProperty("org.glassfish.grizzly.tcp.tomcat5.CoyoteAdapter.ALLOW_BACKSLASH", "false")); |
| |
| private static final boolean COLLAPSE_ADJACENT_SLASHES = |
| Boolean.valueOf(System.getProperty( |
| "com.sun.enterprise.web.collapseAdjacentSlashes", "true")); |
| |
| /** |
| * When mod_jk is used, the adapter must be invoked the same way |
| * Tomcat does by invoking service(...) and the afterService(...). This |
| * is a hack to make it compatible with Tomcat 5|6. |
| */ |
| private boolean compatWithTomcat = false; |
| |
| private String serverName = ServerInfo.getPublicServerInfo(); |
| |
| // Make sure this value is always aligned with {@link ContainerMapper} |
| // (@see com.sun.enterprise.v3.service.impl.ContainerMapper) |
| protected final static Note<MappingData> MAPPING_DATA = |
| org.glassfish.grizzly.http.server.Request.<MappingData>createNote("MappingData"); |
| |
| static final Note<Request> CATALINA_REQUEST_NOTE = |
| org.glassfish.grizzly.http.server.Request.createNote(Request.class.getName()); |
| static final Note<Response> CATALINA_RESPONSE_NOTE = |
| org.glassfish.grizzly.http.server.Request.createNote(Response.class.getName()); |
| |
| static final CatalinaAfterServiceListener catalinaAfterServiceListener = |
| new CatalinaAfterServiceListener(); |
| |
| // Make sure this value is always aligned with {@link ContainerMapper} |
| // (@see com.sun.enterprise.v3.service.impl.ContainerMapper) |
| private final static Note<DataChunk> DATA_CHUNK = |
| org.glassfish.grizzly.http.server.Request.<DataChunk>createNote("DataChunk"); |
| |
| // ----------------------------------------------------------- Constructors |
| |
| |
| /** |
| * Construct a new CoyoteProcessor associated with the specified connector. |
| * |
| * @param connector CoyoteConnector that owns this processor |
| */ |
| public CoyoteAdapter(Connector connector) { |
| super(); |
| this.connector = connector; |
| initServletInterceptors(); |
| } |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| |
| /** |
| * The CoyoteConnector with which this processor is associated. |
| */ |
| private Connector connector = null; |
| |
| |
| // -------------------------------------------------------- Adapter Methods |
| |
| |
| /** |
| * Service method. |
| */ |
| @Override |
| public void service(org.glassfish.grizzly.http.server.Request req, |
| org.glassfish.grizzly.http.server.Response res) |
| throws Exception { |
| |
| res.getResponse().setAllowCustomReasonPhrase(Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER); |
| |
| Request request = req.getNote(CATALINA_REQUEST_NOTE); |
| Response response = req.getNote(CATALINA_RESPONSE_NOTE); |
| |
| // Grizzly already parsed, decoded, and mapped the request. |
| // Let's re-use this info here, before firing the |
| // requestStartEvent probe, so that the mapping data will be |
| // available to any probe event listener via standard |
| // ServletRequest APIs (such as getContextPath()) |
| MappingData md = req.getNote(MAPPING_DATA); |
| final boolean v3Enabled = md != null; |
| if (request == null) { |
| |
| // Create objects |
| request = (Request) connector.createRequest(); |
| response = (Response) connector.createResponse(); |
| |
| // Link objects |
| request.setResponse(response); |
| response.setRequest(request); |
| |
| // Set as notes |
| req.setNote(CATALINA_REQUEST_NOTE, request); |
| req.setNote(CATALINA_RESPONSE_NOTE, response); |
| // res.setNote(ADAPTER_NOTES, response); |
| |
| // Set query string encoding |
| req.getRequest().getRequestURIRef().setDefaultURIEncoding(Charset.forName(connector.getURIEncoding())); |
| } |
| |
| request.setCoyoteRequest(req); |
| response.setCoyoteResponse(res); |
| |
| if (v3Enabled && !compatWithTomcat) { |
| request.setMappingData(md); |
| request.updatePaths(md); |
| } |
| |
| req.addAfterServiceListener(catalinaAfterServiceListener); |
| |
| try { |
| doService(req, request, res, response, v3Enabled); |
| |
| // Request may want to initialize async processing |
| request.onExitService(); |
| } catch (Throwable t) { |
| log.log(Level.SEVERE, LogFacade.REQUEST_PROCESSING_EXCEPTION, t); |
| } |
| } |
| |
| private void enteringServletContainer(Request req, Response res) { |
| if (interceptors == null) |
| return; |
| for(ServletContainerInterceptor interceptor:interceptors) { |
| try{ |
| interceptor.preInvoke(req, res); |
| } catch (Throwable th) { |
| log.log(Level.SEVERE, LogFacade.INTERNAL_ERROR, th); |
| } |
| } |
| } |
| |
| private void leavingServletContainer(Request req, Response res) { |
| if (interceptors == null) |
| return; |
| for(ServletContainerInterceptor interceptor:interceptors) { |
| try{ |
| interceptor.postInvoke(req, res); |
| } catch (Throwable th) { |
| log.log(Level.SEVERE, LogFacade.INTERNAL_ERROR, th); |
| } |
| } |
| } |
| |
| private void initServletInterceptors() { |
| try { |
| ServiceLocator services = org.glassfish.internal.api.Globals.getDefaultHabitat(); |
| interceptors = services.getAllServices(ServletContainerInterceptor.class); |
| } catch (Throwable th) { |
| log.log(Level.SEVERE, LogFacade.FAILED_TO_INITIALIZE_THE_INTERCEPTOR, th); |
| } |
| } |
| |
| |
| private void doService(final org.glassfish.grizzly.http.server.Request req, |
| final Request request, |
| final org.glassfish.grizzly.http.server.Response res, |
| final Response response, |
| final boolean v3Enabled) |
| throws Exception { |
| |
| // START SJSAS 6331392 |
| // Check connector for disabled state |
| if (!connector.isEnabled()) { |
| String msg = MessageFormat.format(rb.getString(LogFacade.HTTP_LISTENER_DISABLED), |
| String.valueOf(connector.getPort())); |
| if (log.isLoggable(Level.FINE)) { |
| log.log(Level.FINE, msg); |
| } |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, msg); |
| return; |
| } |
| // END SJSAS 6331392 |
| |
| //// "X-Powered-By" header is set by GlassfishHttpCodecFilter |
| // if (connector.isXpoweredBy()) { |
| // response.addHeader("X-Powered-By", POWERED_BY); |
| // } |
| |
| |
| // Parse and set Catalina and configuration specific |
| // request parameters |
| if ( postParseRequest(req, request, res, response, v3Enabled) ) { |
| |
| // START S1AS 6188932 |
| boolean authPassthroughEnabled = |
| connector.getAuthPassthroughEnabled(); |
| ProxyHandler proxyHandler = connector.getProxyHandler(); |
| if (authPassthroughEnabled && proxyHandler != null) { |
| |
| // START SJSAS 6397218 |
| if (proxyHandler.getSSLKeysize( |
| (HttpServletRequest)request.getRequest()) > 0) { |
| request.setSecure(true); |
| } |
| // END SJSAS 6397218 |
| |
| X509Certificate[] certs = null; |
| try { |
| certs = proxyHandler.getSSLClientCertificateChain( |
| request.getRequest()); |
| } catch (CertificateException ce) { |
| log.log(Level.SEVERE, LogFacade.PARSING_CLIENT_CERT_EXCEPTION, |
| ce); |
| } |
| if (certs != null) { |
| request.setAttribute(Globals.CERTIFICATES_ATTR, |
| certs); |
| } |
| |
| } |
| // END S1AS 6188932 |
| |
| //// "Server" header is set by GlassfishHttpCodecFilter |
| // if (serverName != null && !serverName.isEmpty()) { |
| // response.addHeader("Server", serverName); |
| // } |
| |
| // Invoke the web container |
| connector.requestStartEvent(request.getRequest(), |
| request.getHost(), request.getContext()); |
| Container container = connector.getContainer(); |
| enteringServletContainer(request, response); |
| try { |
| request.lockSession(); |
| if (container.getPipeline().hasNonBasicValves() || |
| container.hasCustomPipeline()) { |
| container.getPipeline().invoke(request, response); |
| } else { |
| // Invoke host directly |
| Host host = request.getHost(); |
| if (host == null) { |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST); |
| |
| String msg = MessageFormat.format(rb.getString(LogFacade.NO_HOST_MATCHES_SERVER_NAME_INFO), |
| request.getRequest().getServerName()); |
| response.setDetailMessage(msg); |
| return; |
| } |
| if (host.getPipeline().hasNonBasicValves() || |
| host.hasCustomPipeline()) { |
| host.getPipeline().invoke(request, response); |
| } else { |
| GlassFishValve hostValve = host.getPipeline().getBasic(); |
| hostValve.invoke(request, response); |
| // Error handling |
| hostValve.postInvoke(request, response); |
| } |
| } |
| } finally { |
| try { |
| connector.requestEndEvent(request.getRequest(), |
| request.getHost(), request.getContext(), |
| response.getStatus()); |
| } finally { |
| leavingServletContainer(request, response); |
| } |
| } |
| } |
| |
| } |
| // ------------------------------------------------------ Protected Methods |
| |
| |
| /** |
| * Parse additional request parameters. |
| */ |
| protected boolean postParseRequest(final org.glassfish.grizzly.http.server.Request req, |
| final Request request, |
| final org.glassfish.grizzly.http.server.Response res, |
| final Response response, |
| final boolean v3Enabled) |
| throws Exception { |
| // XXX the processor may have set a correct scheme and port prior to this point, |
| // in ajp13 protocols dont make sense to get the port from the connector... |
| // otherwise, use connector configuration |
| request.setSecure(req.isSecure()); |
| |
| // URI decoding |
| DataChunk decodedURI; |
| try { |
| decodedURI = req.getRequest().getRequestURIRef().getDecodedRequestURIBC(); |
| } catch (CharConversionException cce) { |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid URI"); |
| return false; |
| } |
| |
| if (compatWithTomcat || !v3Enabled) { |
| // decodedURI.duplicate(req.requestURI()); |
| // try { |
| // req.getURLDecoder().convert(decodedURI, false); |
| // } catch (IOException ioe) { |
| // res.setStatus(400); |
| // res.setMessage("Invalid URI: " + ioe.getMessage()); |
| // return false; |
| // } |
| |
| /* GlassFish Issue 2339 |
| // Normalize decoded URI |
| if (!normalize(req.decodedURI())) { |
| res.setStatus(400); |
| res.setMessage("Invalid URI"); |
| return false; |
| } |
| */ |
| |
| // Set the remote principal |
| String principal = req.getRemoteUser(); |
| if (principal != null) { |
| request.setUserPrincipal(new CoyotePrincipal(principal)); |
| } |
| |
| // Set the authorization type |
| String authtype = req.getAuthType(); |
| if (authtype != null) { |
| request.setAuthType(authtype); |
| } |
| |
| /* CR 6309511 |
| // URI character decoding |
| convertURI(decodedURI, request); |
| |
| // Parse session Id |
| parseSessionId(req, request); |
| */ |
| // START CR 6309511 |
| // URI character decoding |
| // request.convertURI(decodedURI); |
| |
| // START GlassFish Issue 2339 |
| // Normalize decoded URI |
| // if (!normalize(decodedURI)) { |
| // res.setStatus(400); |
| // res.setMessage("Invalid URI"); |
| // return false; |
| // } |
| // END GlassFish Issue 2339 |
| } |
| // END CR 6309511 |
| |
| /* |
| * Remove any parameters from the URI, so they won't be considered |
| * by the mapping algorithm, and save them in a temporary CharChunk, |
| * so that any session id param may be parsed once the target |
| * context, which may use a custom session parameter name, has been |
| * identified |
| */ |
| final CharChunk uriParamsCC = request.getURIParams(); |
| final CharChunk uriCC = decodedURI.getCharChunk(); |
| final int semicolon = uriCC.indexOf(';'); |
| if (semicolon > 0) { |
| final int absSemicolon = uriCC.getStart() + semicolon; |
| uriParamsCC.setChars(uriCC.getBuffer(), absSemicolon, |
| uriCC.getEnd() - absSemicolon); |
| decodedURI.setChars(uriCC.getBuffer(), uriCC.getStart(), |
| absSemicolon - uriCC.getStart()); |
| } |
| |
| if (compatWithTomcat || !v3Enabled) { |
| /*mod_jk*/ |
| DataChunk localDecodedURI = decodedURI; |
| if (semicolon > 0) { |
| localDecodedURI = req.getNote(DATA_CHUNK); |
| if (localDecodedURI == null) { |
| localDecodedURI = DataChunk.newInstance(); |
| req.setNote(DATA_CHUNK, localDecodedURI); |
| } |
| localDecodedURI.duplicate(decodedURI); |
| } |
| connector.getMapper().map(req.getRequest().serverName(), localDecodedURI, |
| request.getMappingData()); |
| MappingData md = request.getMappingData(); |
| req.setNote(MAPPING_DATA, md); |
| request.updatePaths(md); |
| } |
| |
| // FIXME: the code below doesnt belongs to here, |
| // this is only have sense |
| // in Http11, not in ajp13.. |
| // At this point the Host header has been processed. |
| // Override if the proxyPort/proxyHost are set |
| String proxyName = connector.getProxyName(); |
| int proxyPort = connector.getProxyPort(); |
| if (proxyPort != 0) { |
| req.setServerPort(proxyPort); |
| } |
| if (proxyName != null) { |
| req.setServerName(proxyName); |
| } |
| |
| Context ctx = (Context) request.getMappingData().context; |
| |
| // Parse session id |
| if (ctx != null) { |
| if (req.isRequestedSessionIdFromURL() && |
| Globals.SESSION_PARAMETER_NAME.equals(ctx.getSessionParameterName())) { |
| request.obtainSessionId(); |
| } else if (!uriParamsCC.isNull()) { |
| // String sessionParam = ";" + ctx.getSessionParameterName() + "="; |
| request.parseSessionId(ctx.getSessionParameterName(), uriParamsCC); |
| } |
| } |
| |
| // START GlassFish 1024 |
| request.setDefaultContext(request.getMappingData().isDefaultContext); |
| // END GlassFish 1024 |
| |
| // START SJSAS 6253524 |
| // request.setContext((Context) request.getMappingData().context); |
| // END SJSAS 6253524 |
| // START SJSAS 6253524 |
| request.setContext(ctx); |
| // END SJSAS 6253524 |
| |
| if (ctx != null && !uriParamsCC.isNull()) { |
| request.parseSessionVersion(uriParamsCC); |
| } |
| |
| if (!uriParamsCC.isNull()) { |
| request.parseJReplica(uriParamsCC); |
| } |
| |
| request.setWrapper((Wrapper) request.getMappingData().wrapper); |
| |
| // Filter trace method |
| if (!connector.getAllowTrace() && Method.TRACE.equals(req.getMethod())) { |
| Wrapper wrapper = request.getWrapper(); |
| String header = null; |
| if (wrapper != null) { |
| String[] methods = wrapper.getServletMethods(); |
| if (methods != null) { |
| for (String method : methods) { |
| // Exclude TRACE from methods returned in Allow header |
| if ("TRACE".equals(method)) { |
| continue; |
| } |
| if (header == null) { |
| header = method; |
| } else { |
| header += ", " + method; |
| } |
| } |
| } |
| } |
| res.setStatus(405, "TRACE method is not allowed"); |
| res.addHeader("Allow", header); |
| return false; |
| } |
| |
| // Possible redirect |
| DataChunk redirectPathMB = request.getMappingData().redirectPath; |
| // START SJSAS 6253524 |
| // if (!redirectPathMB.isNull()) { |
| // END SJSAS 6253524 |
| // START SJSAS 6253524 |
| if (!redirectPathMB.isNull() |
| && (!ctx.hasAdHocPaths() |
| || (ctx.getAdHocServletName(((HttpServletRequest) |
| request.getRequest()).getServletPath()) == null))) { |
| // END SJSAS 6253524 |
| String redirectPath = redirectPathMB.toString(); |
| String query = request.getQueryString(); |
| if (request.isRequestedSessionIdFromURL()) { |
| // This is not optimal, but as this is not very common, it |
| // shouldn't matter |
| redirectPath = redirectPath + ";" + ctx.getSessionParameterName() + "=" |
| + request.getRequestedSessionId(); |
| } |
| // START GlassFish 936 |
| redirectPath = response.encode(redirectPath); |
| // END GlassFish 936 |
| if (query != null) { |
| // This is not optimal, but as this is not very common, it |
| // shouldn't matter |
| redirectPath = redirectPath + "?" + query; |
| } |
| |
| // START CR 6590921 |
| boolean authPassthroughEnabled = |
| connector.getAuthPassthroughEnabled(); |
| ProxyHandler proxyHandler = connector.getProxyHandler(); |
| if (authPassthroughEnabled && proxyHandler != null) { |
| |
| if (proxyHandler.getSSLKeysize( |
| (HttpServletRequest)request.getRequest()) > 0) { |
| request.setSecure(true); |
| } |
| } |
| // END CR 6590921 |
| // Issue a permanent redirect |
| // Validating the redirectPath for header injection |
| if (InputValidationUtil.validateStringforCRLF(redirectPath)) { |
| response.sendError(403, "Forbidden"); |
| } else { |
| response.sendRedirect(InputValidationUtil.removeLinearWhiteSpaces(redirectPath), |
| false); |
| } |
| |
| return false; |
| } |
| |
| // Parse session Id |
| /* CR 6309511 |
| parseSessionCookiesId(req, request); |
| */ |
| // START CR 6309511 |
| request.parseSessionCookiesId(); |
| // END CR 6309511 |
| |
| // START SJSAS 6346226 |
| request.parseJrouteCookie(); |
| // END SJSAS 6346226 |
| |
| return true; |
| } |
| |
| |
| /** |
| * Normalize URI. |
| * <p> |
| * This method normalizes "\", "//", "/./" and "/../". This method will |
| * return false when trying to go above the root, or if the URI contains |
| * a null byte. |
| * |
| * @param uriMB URI to be normalized |
| */ |
| public static boolean normalize(MessageBytes uriMB) { |
| |
| int type = uriMB.getType(); |
| if (type == MessageBytes.T_CHARS) { |
| return normalizeChars(uriMB); |
| } else { |
| return normalizeBytes(uriMB); |
| } |
| } |
| |
| |
| private static boolean normalizeBytes(MessageBytes uriMB) { |
| |
| ByteChunk uriBC = uriMB.getByteChunk(); |
| byte[] b = uriBC.getBytes(); |
| int start = uriBC.getStart(); |
| int end = uriBC.getEnd(); |
| |
| // An empty URL is not acceptable |
| if (start == end) |
| return false; |
| |
| // URL * is acceptable |
| if ((end - start == 1) && b[start] == (byte) '*') |
| return true; |
| |
| int pos = 0; |
| int index = 0; |
| |
| // Replace '\' with '/' |
| // Check for null byte |
| for (pos = start; pos < end; pos++) { |
| if (b[pos] == (byte) '\\') { |
| if (ALLOW_BACKSLASH) { |
| b[pos] = (byte) '/'; |
| } else { |
| return false; |
| } |
| } |
| if (b[pos] == (byte) 0) { |
| return false; |
| } |
| } |
| |
| // The URL must start with '/' |
| if (b[start] != (byte) '/') { |
| return false; |
| } |
| |
| // Replace "//" with "/" |
| if (COLLAPSE_ADJACENT_SLASHES) { |
| for (pos = start; pos < (end - 1); pos++) { |
| if (b[pos] == (byte) '/') { |
| while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) { |
| copyBytes(b, pos, pos + 1, end - pos - 1); |
| end--; |
| } |
| } |
| } |
| } |
| |
| // If the URI ends with "/." or "/..", then we append an extra "/" |
| // Note: It is possible to extend the URI by 1 without any side effect |
| // as the next character is a non-significant WS. |
| if (((end - start) > 2) && (b[end - 1] == (byte) '.')) { |
| if ((b[end - 2] == (byte) '/') |
| || ((b[end - 2] == (byte) '.') |
| && (b[end - 3] == (byte) '/'))) { |
| b[end] = (byte) '/'; |
| end++; |
| } |
| } |
| |
| uriBC.setEnd(end); |
| |
| index = 0; |
| |
| // Resolve occurrences of "/./" in the normalized path |
| while (true) { |
| index = uriBC.indexOf("/./", 0, 3, index); |
| if (index < 0) |
| break; |
| copyBytes(b, start + index, start + index + 2, |
| end - start - index - 2); |
| end = end - 2; |
| uriBC.setEnd(end); |
| } |
| |
| index = 0; |
| |
| // Resolve occurrences of "/../" in the normalized path |
| while (true) { |
| index = uriBC.indexOf("/../", 0, 4, index); |
| if (index < 0) |
| break; |
| // Prevent from going outside our context |
| if (index == 0) |
| return false; |
| int index2 = -1; |
| for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) { |
| if (b[pos] == (byte) '/') { |
| index2 = pos; |
| } |
| } |
| copyBytes(b, start + index2, start + index + 3, |
| end - start - index - 3); |
| end = end + index2 - index - 3; |
| uriBC.setEnd(end); |
| index = index2; |
| } |
| |
| uriBC.setBytes(b, start, end); |
| |
| return true; |
| |
| } |
| |
| |
| private static boolean normalizeChars(MessageBytes uriMB) { |
| |
| CharChunk uriCC = uriMB.getCharChunk(); |
| char[] c = uriCC.getChars(); |
| int start = uriCC.getStart(); |
| int end = uriCC.getEnd(); |
| |
| // URL * is acceptable |
| if ((end - start == 1) && c[start] == (char) '*') |
| return true; |
| |
| int pos = 0; |
| int index = 0; |
| |
| // Replace '\' with '/' |
| // Check for null char |
| for (pos = start; pos < end; pos++) { |
| if (c[pos] == (char) '\\') { |
| if (ALLOW_BACKSLASH) { |
| c[pos] = (char) '/'; |
| } else { |
| return false; |
| } |
| } |
| if (c[pos] == (char) 0) { |
| return false; |
| } |
| } |
| |
| // The URL must start with '/' |
| if (c[start] != (char) '/') { |
| return false; |
| } |
| |
| // Replace "//" with "/" |
| if (COLLAPSE_ADJACENT_SLASHES) { |
| for (pos = start; pos < (end - 1); pos++) { |
| if (c[pos] == (char) '/') { |
| while ((pos + 1 < end) && (c[pos + 1] == (char) '/')) { |
| copyChars(c, pos, pos + 1, end - pos - 1); |
| end--; |
| } |
| } |
| } |
| } |
| |
| // If the URI ends with "/." or "/..", then we append an extra "/" |
| // Note: It is possible to extend the URI by 1 without any side effect |
| // as the next character is a non-significant WS. |
| if (((end - start) > 2) && (c[end - 1] == (char) '.')) { |
| if ((c[end - 2] == (char) '/') |
| || ((c[end - 2] == (char) '.') |
| && (c[end - 3] == (char) '/'))) { |
| c[end] = (char) '/'; |
| end++; |
| } |
| } |
| |
| uriCC.setEnd(end); |
| |
| index = 0; |
| |
| // Resolve occurrences of "/./" in the normalized path |
| while (true) { |
| index = uriCC.indexOf("/./", 0, 3, index); |
| if (index < 0) |
| break; |
| copyChars(c, start + index, start + index + 2, |
| end - start - index - 2); |
| end = end - 2; |
| uriCC.setEnd(end); |
| } |
| |
| index = 0; |
| |
| // Resolve occurrences of "/../" in the normalized path |
| while (true) { |
| index = uriCC.indexOf("/../", 0, 4, index); |
| if (index < 0) |
| break; |
| // Prevent from going outside our context |
| if (index == 0) |
| return false; |
| int index2 = -1; |
| for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) { |
| if (c[pos] == (char) '/') { |
| index2 = pos; |
| } |
| } |
| copyChars(c, start + index2, start + index + 3, |
| end - start - index - 3); |
| end = end + index2 - index - 3; |
| uriCC.setEnd(end); |
| index = index2; |
| } |
| |
| uriCC.setChars(c, start, end); |
| |
| return true; |
| |
| } |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| |
| /** |
| * Copy an array of bytes to a different position. Used during |
| * normalization. |
| */ |
| protected static void copyBytes(byte[] b, int dest, int src, int len) { |
| for (int pos = 0; pos < len; pos++) { |
| b[pos + dest] = b[pos + src]; |
| } |
| } |
| |
| |
| /** |
| * Copy an array of chars to a different position. Used during |
| * normalization. |
| */ |
| private static void copyChars(char[] c, int dest, int src, int len) { |
| for (int pos = 0; pos < len; pos++) { |
| c[pos + dest] = c[pos + src]; |
| } |
| } |
| |
| |
| /** |
| * Log a message on the Logger associated with our Container (if any) |
| * |
| * @param message Message to be logged |
| */ |
| protected void log(String message) { |
| log.log(Level.INFO, message); |
| } |
| |
| |
| /** |
| * Log a message on the Logger associated with our Container (if any) |
| * |
| * @param message Message to be logged |
| * @param throwable Associated exception |
| */ |
| protected void log(String message, Throwable throwable) { |
| log.log(Level.SEVERE, message, throwable); |
| } |
| |
| |
| /** |
| * Character conversion of the a US-ASCII MessageBytes. |
| */ |
| /* CR 6309511 |
| protected void convertMB(MessageBytes mb) { |
| |
| // This is of course only meaningful for bytes |
| if (mb.getType() != MessageBytes.T_BYTES) |
| return; |
| |
| ByteChunk bc = mb.getByteChunk(); |
| CharChunk cc = mb.getCharChunk(); |
| cc.allocate(bc.getLength(), -1); |
| |
| // Default encoding: fast conversion |
| byte[] bbuf = bc.getBuffer(); |
| char[] cbuf = cc.getBuffer(); |
| int start = bc.getStart(); |
| for (int i = 0; i < bc.getLength(); i++) { |
| cbuf[i] = (char) (bbuf[i + start] & 0xff); |
| } |
| mb.setChars(cbuf, 0, bc.getLength()); |
| |
| } |
| */ |
| |
| |
| // START SJSAS 6349248 |
| /** |
| * Notify all container event listeners that a particular event has |
| * occurred for this Adapter. The default implementation performs |
| * this notification synchronously using the calling thread. |
| * |
| * @param type Event type |
| * @param data Event data |
| */ |
| public void fireAdapterEvent(String type, Object data) { |
| if ( connector != null && connector.getContainer() != null) { |
| try{ |
| ((ContainerBase)connector.getContainer()) |
| .fireContainerEvent(type,data); |
| } catch (Throwable t){ |
| log.log(Level.SEVERE, LogFacade.REQUEST_PROCESSING_EXCEPTION, t); |
| } |
| } |
| } |
| // END SJSAS 6349248 |
| |
| |
| /** |
| * Return true when an instance is executed the same way it does in Tomcat. |
| */ |
| public boolean isCompatWithTomcat() { |
| return compatWithTomcat; |
| } |
| |
| |
| /** |
| * <tt>true</tt> if this class needs to be compatible with Tomcat |
| * Adapter class. Since Tomcat Adapter implementation doesn't support |
| * the afterService method, the afterService method must be invoked |
| * inside the service method. |
| */ |
| public void setCompatWithTomcat(boolean compatWithTomcat) { |
| this.compatWithTomcat = compatWithTomcat; |
| |
| // Add server header |
| if (compatWithTomcat){ |
| serverName = "Apache/" + serverName; |
| } else { |
| // Recalculate. |
| serverName = ServerInfo.getPublicServerInfo(); |
| } |
| } |
| |
| |
| /** |
| * Gets the port of this CoyoteAdapter. |
| * |
| * @return the port of this CoyoteAdapter |
| */ |
| public int getPort() { |
| return connector.getPort(); |
| } |
| |
| /** |
| * AfterServiceListener, which is responsible for recycle catalina request and response |
| * objects. |
| */ |
| static final class CatalinaAfterServiceListener implements AfterServiceListener { |
| |
| @Override |
| public void onAfterService(final org.glassfish.grizzly.http.server.Request request) { |
| final Request servletRequest = request.getNote(CATALINA_REQUEST_NOTE); |
| final Response servletResponse = request.getNote(CATALINA_RESPONSE_NOTE); |
| |
| if (servletRequest != null) { |
| try { |
| if (!servletRequest.isUpgrade()) { |
| servletResponse.finishResponse(); |
| } else { |
| servletResponse.setUpgrade(servletRequest.isUpgrade()); |
| } |
| } catch (Exception e) { |
| log.log(Level.SEVERE, LogFacade.REQUEST_PROCESSING_EXCEPTION, e); |
| } finally { |
| try { |
| servletRequest.unlockSession(); |
| } finally { |
| servletRequest.recycle(); |
| servletResponse.recycle(); |
| } |
| } |
| } |
| } |
| } |
| } |