FileDocCategorySizeDatePackage
CoyoteAdapter.javaAPI DocGlassfish v2 API34202Tue Aug 14 16:08:06 BST 2007org.apache.coyote.tomcat5

CoyoteAdapter

public class CoyoteAdapter extends Object implements org.apache.coyote.Adapter
Implementation of a request processor which delegates the processing to a Coyote processor.
author
Craig R. McClanahan
author
Remy Maucherat
version
$Revision: 1.32.2.1 $ $Date: 2007/08/14 23:08:06 $

Fields Summary
private static com.sun.org.apache.commons.logging.Log
log
public static final int
ADAPTER_NOTES
protected static final boolean
ALLOW_BACKSLASH
private static final boolean
COLLAPSE_ADJACENT_SLASHES
private boolean
compatWithTomcat
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 String
serverName
private CoyoteConnector
connector
The CoyoteConnector with which this processor is associated.
private int
debug
The debugging detail level for this component.
private org.apache.tomcat.util.buf.UEncoder
urlEncoder
protected org.apache.catalina.util.StringManager
sm
The string manager for this package.
Constructors Summary
public CoyoteAdapter(CoyoteConnector connector)
Construct a new CoyoteProcessor associated with the specified connector.

param
connector CoyoteConnector that owns this processor
param
id Identifier of this CoyoteProcessor (unique per connector)

    
    // ----------------------------------------------------------- Constructors


                                  
       

        super();
        this.connector = connector;
        this.debug = connector.getDebug();
        // START GlassFish 936
        urlEncoder.addSafeCharacter('/");
        // END GlassFish 936
    
Methods Summary
public voidafterService(org.apache.coyote.Request req, org.apache.coyote.Response res)
Finish the response and close the connection based on the connection header.

        CoyoteRequest request = (CoyoteRequest) req.getNote(ADAPTER_NOTES);
        CoyoteResponse response = (CoyoteResponse) res.getNote(ADAPTER_NOTES);
        
        if ( request == null || response == null) return;
        
        try{
            response.finishResponse();
            req.action( ActionCode.ACTION_POST_REQUEST , null);
        }catch (Throwable t) {
            log.error(sm.getString("coyoteAdapter.service"), t);
        } finally {
            // Recycle the wrapper request and response
            request.recycle();
            response.recycle();
        }
    
protected static voidcopyBytes(byte[] b, int dest, int src, int len)
Copy an array of bytes to a different position. Used during normalization.

        for (int pos = 0; pos < len; pos++) {
            b[pos + dest] = b[pos + src];
        }
    
private static voidcopyChars(char[] c, int dest, int src, int len)
Copy an array of chars to a different position. Used during normalization.

        for (int pos = 0; pos < len; pos++) {
            c[pos + dest] = c[pos + src];
        }
    
public voidfireAdapterEvent(java.lang.String type, java.lang.Object data)
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

        if ( connector != null && connector.getContainer() != null) {
            try{
                ((ContainerBase)connector.getContainer())
                    .fireContainerEvent(type,data);
            } catch (Throwable t){
                log.error(sm.getString("coyoteAdapter.service"), t);
            }
        }
    
public booleanisCompatWithTomcat()
Return true when an instance is executed the same way it does in Tomcat.

        return compatWithTomcat;
    
protected voidlog(java.lang.String message)
Log a message on the Logger associated with our Container (if any)

param
message Message to be logged

        log.info( message );
    
protected voidlog(java.lang.String message, java.lang.Throwable throwable)
Log a message on the Logger associated with our Container (if any)

param
message Message to be logged
param
throwable Associated exception

        log.error( message, throwable);
    
public static booleannormalize(org.apache.tomcat.util.buf.MessageBytes uriMB)
Normalize URI.

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


        int type = uriMB.getType();
        if (type == MessageBytes.T_CHARS) {
            return normalizeChars(uriMB);
        } else {
            return normalizeBytes(uriMB);
        }
    
private static booleannormalizeBytes(org.apache.tomcat.util.buf.MessageBytes uriMB)


        ByteChunk uriBC = uriMB.getByteChunk();
        byte[] b = uriBC.getBytes();
        int start = uriBC.getStart();
        int end = uriBC.getEnd();

        // 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 booleannormalizeChars(org.apache.tomcat.util.buf.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 booleanpostParseRequest(org.apache.coyote.Request req, CoyoteRequest request, org.apache.coyote.Response res, CoyoteResponse response)
Parse additional request parameters.

        // XXX the processor needs to set a correct scheme and port prior to this point, 
        // in ajp13 protocols dont make sense to get the port from the connector..
        // 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
        if (! req.scheme().isNull()) {
            // use processor specified scheme to determine secure state
            request.setSecure(req.scheme().equals("https"));
        } else {
            // use connector scheme and secure configuration, (defaults to
            // "http" and false respectively)
            req.scheme().setString(connector.getScheme());
            request.setSecure(connector.getSecure());
        }

        // 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.serverName().setString(proxyName);
        }

        // URI decoding
        MessageBytes decodedURI = req.decodedURI();
        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().toString();
        if (principal != null) {
            request.setUserPrincipal(new CoyotePrincipal(principal));
        }

        // Set the authorization type
        String authtype = req.getAuthType().toString();
        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

        // Parse session Id
        request.parseSessionId();
        // END CR 6309511

        // Remove any remaining parameters (other than session id, which has
        // already been removed in parseSessionId()) from the URI, so they
        // won't be considered by the mapping algorithm.
        CharChunk uriCC = decodedURI.getCharChunk();
        int semicolon = uriCC.indexOf(';");
        String sessionVersionString = null;
        if (semicolon > 0) {
            sessionVersionString = request.parseSessionVersion();
            decodedURI.setChars
                (uriCC.getBuffer(), uriCC.getStart(), semicolon);
        }

        // Request mapping.
        connector.getMapper().map(req.serverName(), decodedURI, 
                                  request.getMappingData());
        // 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
        Context ctx = (Context) request.getMappingData().context;
        request.setContext(ctx);
        // END SJSAS 6253524

        request.setWrapper((Wrapper) request.getMappingData().wrapper);

        // Filter trace method
        if (!connector.getAllowTrace() 
                && req.method().equalsIgnoreCase("TRACE")) {
            Wrapper wrapper = request.getWrapper();
            String header = null;
            if (wrapper != null) {
                String[] methods = wrapper.getServletMethods();
                if (methods != null) {
                    for (int i=0; i<methods.length; i++) {
                        // Exclude TRACE from methods returned in Allow header
                        if ("TRACE".equals(methods[i])) {
                            continue;
                        }
                        if (header == null) {
                            header = methods[i];
                        } else {
                            header += ", " + methods[i];
                        }
                    }
                }
            }                               
            res.setStatus(405);
            res.addHeader("Allow", header);
            res.setMessage("TRACE method is not allowed");
            return false;
        }

        // Possible redirect
        MessageBytes 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 + ";jsessionid=" 
                    + request.getRequestedSessionId();
            }            
            // START GlassFish 936
            redirectPath = urlEncoder.encodeURL(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;
            }
            response.sendRedirect(redirectPath);
            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

        if (sessionVersionString != null) {
            request.parseSessionVersionString(sessionVersionString);
        }

        return true;
    
public voidservice(org.apache.coyote.Request req, org.apache.coyote.Response res)
Service method.



    // -------------------------------------------------------- Adapter Methods


           
         
          

        CoyoteRequest request = (CoyoteRequest) req.getNote(ADAPTER_NOTES);
        CoyoteResponse response = (CoyoteResponse) res.getNote(ADAPTER_NOTES);

        if (request == null) {

            // Create objects
            request = (CoyoteRequest) connector.createRequest();
            request.setCoyoteRequest(req);
            response = (CoyoteResponse) connector.createResponse();
            response.setCoyoteResponse(res);

            // Link objects
            request.setResponse(response);
            response.setRequest(request);

            // Set as notes
            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);

            // Set query string encoding
            req.getParameters().setQueryStringEncoding
                (connector.getURIEncoding());
        }

        // START SJSAS 6331392
        // Check connector for disabled state
        if (!connector.isEnabled()) {
            String msg = sm.getString("coyoteAdapter.listenerOff",
                                      String.valueOf(connector.getPort()));
            if (log.isDebugEnabled()) {
                log.debug(msg);            
            }
            response.sendError(HttpServletResponse.SC_NOT_FOUND, msg);
            return;
        }
        // END SJSAS 6331392

        if (connector.isXpoweredBy()) {
            response.addHeader("X-Powered-By", "Servlet/2.5");
        }

        try {

            // Parse and set Catalina and configuration specific 
            // request parameters
            if ( postParseRequest(req, request, res, response) ) {

                // 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.error(sm.getString(
                            "coyoteAdapter.proxyAuthCertError"),
                            ce);
                    }
                    if (certs != null) {
                        request.setAttribute(Globals.CERTIFICATES_ATTR,
                                             certs);
                    }
                    
                }
                // END S1AS 6188932
                
                response.addHeader("Server",serverName);
                
                // Calling the container
                connector.getContainer().invoke(request, response);
            }
         /* GlassFish Issue 79    
            response.finishResponse();
            req.action( ActionCode.ACTION_POST_REQUEST , null);

         } catch (IOException e) {
            ;
         } catch (Throwable t) {
             log.error(sm.getString("coyoteAdapter.service"), t);
         } finally {
             // Recycle the wrapper request and response
             request.recycle();
             response.recycle();
         }*/
        // START GlassFish Issue 798
        } catch (IOException e) {
            // Recycle the wrapper request and response
            request.recycle();
            response.recycle();
        } catch (Throwable t) {
            log.error(sm.getString("coyoteAdapter.service"), t);
                        // Recycle the wrapper request and response
            request.recycle();
            response.recycle();
        } 
        // END GlassFish Issue 798
        
        if ( compatWithTomcat ) {
            afterService(req,res);
        }
        
    
public voidsetCompatWithTomcat(boolean compatWithTomcat)
true 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.

        this.compatWithTomcat = compatWithTomcat;

        // Add server header
        if (compatWithTomcat){
            serverName = "Apache/" + serverName;
        } else {
            // Recalculate.
            serverName = System.getProperty("product.name");
        }