Home » apache-tomcat-6.0.26-src » org.apache » catalina » connector » [javadoc | source]

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    * 
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    * 
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   
   18   
   19   package org.apache.catalina.connector;
   20   
   21   import java.io.IOException;
   22   import java.io.UnsupportedEncodingException;
   23   
   24   import org.apache.catalina.CometEvent;
   25   import org.apache.catalina.Context;
   26   import org.apache.catalina.Globals;
   27   import org.apache.catalina.Wrapper;
   28   import org.apache.catalina.util.StringManager;
   29   import org.apache.catalina.util.ServerInfo;
   30   import org.apache.catalina.util.URLEncoder;
   31   import org.apache.coyote.ActionCode;
   32   import org.apache.coyote.Adapter;
   33   import org.apache.juli.logging.Log;
   34   import org.apache.juli.logging.LogFactory;
   35   import org.apache.tomcat.util.buf.B2CConverter;
   36   import org.apache.tomcat.util.buf.ByteChunk;
   37   import org.apache.tomcat.util.buf.CharChunk;
   38   import org.apache.tomcat.util.buf.MessageBytes;
   39   import org.apache.tomcat.util.http.Cookies;
   40   import org.apache.tomcat.util.http.ServerCookie;
   41   import org.apache.tomcat.util.net.SocketStatus;
   42   
   43   
   44   /**
   45    * Implementation of a request processor which delegates the processing to a
   46    * Coyote processor.
   47    *
   48    * @author Craig R. McClanahan
   49    * @author Remy Maucherat
   50    * @version $Revision: 896389 $ $Date: 2010-01-06 12:09:55 +0100 (Wed, 06 Jan 2010) $
   51    */
   52   
   53   public class CoyoteAdapter implements Adapter {
   54       
   55       private static Log log = LogFactory.getLog(CoyoteAdapter.class);
   56   
   57       // -------------------------------------------------------------- Constants
   58   
   59       private static final String POWERED_BY = "Servlet/2.5 JSP/2.1 " +
   60               "(" + ServerInfo.getServerInfo() + " Java/" +
   61               System.getProperty("java.vm.vendor") + "/" +
   62               System.getProperty("java.runtime.version") + ")";
   63   
   64       public static final int ADAPTER_NOTES = 1;
   65   
   66   
   67       protected static final boolean ALLOW_BACKSLASH = 
   68           Boolean.valueOf(System.getProperty("org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH", "false")).booleanValue();
   69   
   70   
   71       // ----------------------------------------------------------- Constructors
   72   
   73   
   74       /**
   75        * Construct a new CoyoteProcessor associated with the specified connector.
   76        *
   77        * @param connector CoyoteConnector that owns this processor
   78        */
   79       public CoyoteAdapter(Connector connector) {
   80   
   81           super();
   82           this.connector = connector;
   83   
   84       }
   85   
   86   
   87       // ----------------------------------------------------- Instance Variables
   88   
   89   
   90       /**
   91        * The CoyoteConnector with which this processor is associated.
   92        */
   93       private Connector connector = null;
   94   
   95   
   96       /**
   97        * The match string for identifying a session ID parameter.
   98        */
   99       private static final String match =
  100           ";" + Globals.SESSION_PARAMETER_NAME + "=";
  101   
  102   
  103       /**
  104        * The string manager for this package.
  105        */
  106       protected StringManager sm =
  107           StringManager.getManager(Constants.Package);
  108   
  109   
  110       /**
  111        * Encoder for the Location URL in HTTP redirects.
  112        */
  113       protected static URLEncoder urlEncoder;
  114   
  115   
  116       // ----------------------------------------------------- Static Initializer
  117   
  118   
  119       /**
  120        * The safe character set.
  121        */
  122       static {
  123           urlEncoder = new URLEncoder();
  124           urlEncoder.addSafeCharacter('-');
  125           urlEncoder.addSafeCharacter('_');
  126           urlEncoder.addSafeCharacter('.');
  127           urlEncoder.addSafeCharacter('*');
  128           urlEncoder.addSafeCharacter('/');
  129       }
  130   
  131   
  132       // -------------------------------------------------------- Adapter Methods
  133   
  134       
  135       /**
  136        * Event method.
  137        * 
  138        * @return false to indicate an error, expected or not
  139        */
  140       public boolean event(org.apache.coyote.Request req, 
  141               org.apache.coyote.Response res, SocketStatus status) {
  142   
  143           Request request = (Request) req.getNote(ADAPTER_NOTES);
  144           Response response = (Response) res.getNote(ADAPTER_NOTES);
  145   
  146           if (request.getWrapper() != null) {
  147               
  148               boolean error = false;
  149               boolean read = false;
  150               try {
  151                   if (status == SocketStatus.OPEN) {
  152                       if (response.isClosed()) {
  153                           // The event has been closed asynchronously, so call end instead of
  154                           // read to cleanup the pipeline
  155                           request.getEvent().setEventType(CometEvent.EventType.END);
  156                           request.getEvent().setEventSubType(null);
  157                       } else {
  158                           try {
  159                               // Fill the read buffer of the servlet layer
  160                               if (request.read()) {
  161                                   read = true;
  162                               }
  163                           } catch (IOException e) {
  164                               error = true;
  165                           }
  166                           if (read) {
  167                               request.getEvent().setEventType(CometEvent.EventType.READ);
  168                               request.getEvent().setEventSubType(null);
  169                           } else if (error) {
  170                               request.getEvent().setEventType(CometEvent.EventType.ERROR);
  171                               request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
  172                           } else {
  173                               request.getEvent().setEventType(CometEvent.EventType.END);
  174                               request.getEvent().setEventSubType(null);
  175                           }
  176                       }
  177                   } else if (status == SocketStatus.DISCONNECT) {
  178                       request.getEvent().setEventType(CometEvent.EventType.ERROR);
  179                       request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
  180                       error = true;
  181                   } else if (status == SocketStatus.ERROR) {
  182                       request.getEvent().setEventType(CometEvent.EventType.ERROR);
  183                       request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
  184                       error = true;
  185                   } else if (status == SocketStatus.STOP) {
  186                       request.getEvent().setEventType(CometEvent.EventType.END);
  187                       request.getEvent().setEventSubType(CometEvent.EventSubType.SERVER_SHUTDOWN);
  188                   } else if (status == SocketStatus.TIMEOUT) {
  189                       if (response.isClosed()) {
  190                           // The event has been closed asynchronously, so call end instead of
  191                           // read to cleanup the pipeline
  192                           request.getEvent().setEventType(CometEvent.EventType.END);
  193                           request.getEvent().setEventSubType(null);
  194                       } else {
  195                           request.getEvent().setEventType(CometEvent.EventType.ERROR);
  196                           request.getEvent().setEventSubType(CometEvent.EventSubType.TIMEOUT);
  197                       }
  198                   }
  199   
  200                   req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
  201                   
  202                   // Calling the container
  203                   connector.getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
  204   
  205                   if (!error && !response.isClosed() && (request.getAttribute(Globals.EXCEPTION_ATTR) != null)) {
  206                       // An unexpected exception occurred while processing the event, so
  207                       // error should be called
  208                       request.getEvent().setEventType(CometEvent.EventType.ERROR);
  209                       request.getEvent().setEventSubType(null);
  210                       error = true;
  211                       connector.getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
  212                   }
  213                   if (response.isClosed() || !request.isComet()) {
  214                       if (status==SocketStatus.OPEN) {
  215                           //CometEvent.close was called during an event.
  216                           request.getEvent().setEventType(CometEvent.EventType.END);
  217                           request.getEvent().setEventSubType(null);
  218                           error = true;
  219                           connector.getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
  220                       }
  221                       res.action(ActionCode.ACTION_COMET_END, null);
  222                   } else if (!error && read && request.getAvailable()) {
  223                       // If this was a read and not all bytes have been read, or if no data
  224                       // was read from the connector, then it is an error
  225                       request.getEvent().setEventType(CometEvent.EventType.ERROR);
  226                       request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
  227                       error = true;
  228                       connector.getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
  229                   }
  230                   return (!error);
  231               } catch (Throwable t) {
  232                   if (!(t instanceof IOException)) {
  233                       log.error(sm.getString("coyoteAdapter.service"), t);
  234                   }
  235                   error = true;
  236                   return false;
  237               } finally {
  238                   req.getRequestProcessor().setWorkerThreadName(null);
  239                   // Recycle the wrapper request and response
  240                   if (error || response.isClosed() || !request.isComet()) {
  241                       request.recycle();
  242                       request.setFilterChain(null);
  243                       response.recycle();
  244                   }
  245               }
  246               
  247           } else {
  248               return false;
  249           }
  250       }
  251       
  252   
  253       /**
  254        * Service method.
  255        */
  256       public void service(org.apache.coyote.Request req, 
  257       	                org.apache.coyote.Response res)
  258           throws Exception {
  259   
  260           Request request = (Request) req.getNote(ADAPTER_NOTES);
  261           Response response = (Response) res.getNote(ADAPTER_NOTES);
  262   
  263           if (request == null) {
  264   
  265               // Create objects
  266               request = (Request) connector.createRequest();
  267               request.setCoyoteRequest(req);
  268               response = (Response) connector.createResponse();
  269               response.setCoyoteResponse(res);
  270   
  271               // Link objects
  272               request.setResponse(response);
  273               response.setRequest(request);
  274   
  275               // Set as notes
  276               req.setNote(ADAPTER_NOTES, request);
  277               res.setNote(ADAPTER_NOTES, response);
  278   
  279               // Set query string encoding
  280               req.getParameters().setQueryStringEncoding
  281                   (connector.getURIEncoding());
  282   
  283           }
  284   
  285           if (connector.getXpoweredBy()) {
  286               response.addHeader("X-Powered-By", POWERED_BY);
  287           }
  288   
  289           boolean comet = false;
  290           
  291           try {
  292   
  293               // Parse and set Catalina and configuration specific 
  294               // request parameters
  295               req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
  296               if (postParseRequest(req, request, res, response)) {
  297                   // Calling the container
  298                   connector.getContainer().getPipeline().getFirst().invoke(request, response);
  299   
  300                   if (request.isComet()) {
  301                       if (!response.isClosed() && !response.isError()) {
  302                           if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
  303                               // Invoke a read event right away if there are available bytes
  304                               if (event(req, res, SocketStatus.OPEN)) {
  305                                   comet = true;
  306                                   res.action(ActionCode.ACTION_COMET_BEGIN, null);
  307                               }
  308                           } else {
  309                               comet = true;
  310                               res.action(ActionCode.ACTION_COMET_BEGIN, null);
  311                           }
  312                       } else {
  313                           // Clear the filter chain, as otherwise it will not be reset elsewhere
  314                           // since this is a Comet request
  315                           request.setFilterChain(null);
  316                       }
  317                   }
  318   
  319               }
  320   
  321               if (!comet) {
  322                   response.finishResponse();
  323                   req.action(ActionCode.ACTION_POST_REQUEST , null);
  324               }
  325   
  326           } catch (IOException e) {
  327               ;
  328           } catch (Throwable t) {
  329               log.error(sm.getString("coyoteAdapter.service"), t);
  330           } finally {
  331               req.getRequestProcessor().setWorkerThreadName(null);
  332               // Recycle the wrapper request and response
  333               if (!comet) {
  334                   request.recycle();
  335                   response.recycle();
  336               } else {
  337                   // Clear converters so that the minimum amount of memory 
  338                   // is used by this processor
  339                   request.clearEncoders();
  340                   response.clearEncoders();
  341               }
  342           }
  343   
  344       }
  345   
  346   
  347       // ------------------------------------------------------ Protected Methods
  348   
  349   
  350       /**
  351        * Parse additional request parameters.
  352        */
  353       protected boolean postParseRequest(org.apache.coyote.Request req, 
  354                                          Request request,
  355       		                       org.apache.coyote.Response res, 
  356                                          Response response)
  357               throws Exception {
  358   
  359           // XXX the processor needs to set a correct scheme and port prior to this point, 
  360           // in ajp13 protocols dont make sense to get the port from the connector..
  361           // XXX the processor may have set a correct scheme and port prior to this point, 
  362           // in ajp13 protocols dont make sense to get the port from the connector...
  363           // otherwise, use connector configuration
  364           if (! req.scheme().isNull()) {
  365               // use processor specified scheme to determine secure state
  366               request.setSecure(req.scheme().equals("https"));
  367           } else {
  368               // use connector scheme and secure configuration, (defaults to
  369               // "http" and false respectively)
  370               req.scheme().setString(connector.getScheme());
  371               request.setSecure(connector.getSecure());
  372           }
  373   
  374           // FIXME: the code below doesnt belongs to here, 
  375           // this is only have sense 
  376           // in Http11, not in ajp13..
  377           // At this point the Host header has been processed.
  378           // Override if the proxyPort/proxyHost are set 
  379           String proxyName = connector.getProxyName();
  380           int proxyPort = connector.getProxyPort();
  381           if (proxyPort != 0) {
  382               req.setServerPort(proxyPort);
  383           }
  384           if (proxyName != null) {
  385               req.serverName().setString(proxyName);
  386           }
  387   
  388           // Parse session Id
  389           parseSessionId(req, request);
  390   
  391           // URI decoding
  392           MessageBytes decodedURI = req.decodedURI();
  393           decodedURI.duplicate(req.requestURI());
  394   
  395           if (decodedURI.getType() == MessageBytes.T_BYTES) {
  396               // Remove any path parameters
  397               ByteChunk uriBB = decodedURI.getByteChunk();
  398               int semicolon = uriBB.indexOf(';', 0);
  399               if (semicolon > 0) {
  400                   decodedURI.setBytes
  401                       (uriBB.getBuffer(), uriBB.getStart(), semicolon);
  402               }
  403               // %xx decoding of the URL
  404               try {
  405                   req.getURLDecoder().convert(decodedURI, false);
  406               } catch (IOException ioe) {
  407                   res.setStatus(400);
  408                   res.setMessage("Invalid URI: " + ioe.getMessage());
  409                   return false;
  410               }
  411               // Normalization
  412               if (!normalize(req.decodedURI())) {
  413                   res.setStatus(400);
  414                   res.setMessage("Invalid URI");
  415                   return false;
  416               }
  417               // Character decoding
  418               convertURI(decodedURI, request);
  419               // Check that the URI is still normalized
  420               if (!checkNormalize(req.decodedURI())) {
  421                   res.setStatus(400);
  422                   res.setMessage("Invalid URI character encoding");
  423                   return false;
  424               }
  425           } else {
  426               // The URL is chars or String, and has been sent using an in-memory
  427               // protocol handler, we have to assume the URL has been properly
  428               // decoded already
  429               decodedURI.toChars();
  430               // Remove any path parameters
  431               CharChunk uriCC = decodedURI.getCharChunk();
  432               int semicolon = uriCC.indexOf(';');
  433               if (semicolon > 0) {
  434                   decodedURI.setChars
  435                       (uriCC.getBuffer(), uriCC.getStart(), semicolon);
  436               }
  437           }
  438   
  439           // Set the remote principal
  440           String principal = req.getRemoteUser().toString();
  441           if (principal != null) {
  442               request.setUserPrincipal(new CoyotePrincipal(principal));
  443           }
  444   
  445           // Set the authorization type
  446           String authtype = req.getAuthType().toString();
  447           if (authtype != null) {
  448               request.setAuthType(authtype);
  449           }
  450   
  451           // Request mapping.
  452           MessageBytes serverName;
  453           if (connector.getUseIPVHosts()) {
  454               serverName = req.localName();
  455               if (serverName.isNull()) {
  456                   // well, they did ask for it
  457                   res.action(ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE, null);
  458               }
  459           } else {
  460               serverName = req.serverName();
  461           }
  462           connector.getMapper().map(serverName, decodedURI, 
  463                                     request.getMappingData());
  464           request.setContext((Context) request.getMappingData().context);
  465           request.setWrapper((Wrapper) request.getMappingData().wrapper);
  466   
  467           // Filter trace method
  468           if (!connector.getAllowTrace() 
  469                   && req.method().equalsIgnoreCase("TRACE")) {
  470               Wrapper wrapper = request.getWrapper();
  471               String header = null;
  472               if (wrapper != null) {
  473                   String[] methods = wrapper.getServletMethods();
  474                   if (methods != null) {
  475                       for (int i=0; i<methods.length; i++) {
  476                           if ("TRACE".equals(methods[i])) {
  477                               continue;
  478                           }
  479                           if (header == null) {
  480                               header = methods[i];
  481                           } else {
  482                               header += ", " + methods[i];
  483                           }
  484                       }
  485                   }
  486               }                               
  487               res.setStatus(405);
  488               res.addHeader("Allow", header);
  489               res.setMessage("TRACE method is not allowed");
  490               return false;
  491           }
  492   
  493           // Possible redirect
  494           MessageBytes redirectPathMB = request.getMappingData().redirectPath;
  495           if (!redirectPathMB.isNull()) {
  496               String redirectPath = urlEncoder.encode(redirectPathMB.toString());
  497               String query = request.getQueryString();
  498               if (request.isRequestedSessionIdFromURL()) {
  499                   // This is not optimal, but as this is not very common, it
  500                   // shouldn't matter
  501                   redirectPath = redirectPath + ";" + Globals.SESSION_PARAMETER_NAME + "=" 
  502                       + request.getRequestedSessionId();
  503               }
  504               if (query != null) {
  505                   // This is not optimal, but as this is not very common, it
  506                   // shouldn't matter
  507                   redirectPath = redirectPath + "?" + query;
  508               }
  509               response.sendRedirect(redirectPath);
  510               return false;
  511           }
  512   
  513           // Parse session Id
  514           parseSessionCookiesId(req, request);
  515   
  516           return true;
  517       }
  518   
  519   
  520       /**
  521        * Parse session id in URL.
  522        */
  523       protected void parseSessionId(org.apache.coyote.Request req, Request request) {
  524   
  525           ByteChunk uriBC = req.requestURI().getByteChunk();
  526           int semicolon = uriBC.indexOf(match, 0, match.length(), 0);
  527   
  528           if (semicolon > 0) {
  529               // What encoding to use? Some platforms, eg z/os, use a default
  530               // encoding that doesn't give the expected result so be explicit 
  531               String enc = connector.getURIEncoding();
  532               if (enc == null) {
  533                   enc = "ISO-8859-1";
  534               }
  535   
  536               // Parse session ID, and extract it from the decoded request URI
  537               int start = uriBC.getStart();
  538               int end = uriBC.getEnd();
  539   
  540               int sessionIdStart = semicolon + match.length();
  541               int semicolon2 = uriBC.indexOf(';', sessionIdStart);
  542               try {
  543                   if (semicolon2 >= 0) {
  544                       request.setRequestedSessionId
  545                           (new String(uriBC.getBuffer(), start + sessionIdStart,
  546                                   semicolon2 - sessionIdStart, enc));
  547                       // Extract session ID from request URI
  548                       byte[] buf = uriBC.getBuffer();
  549                       for (int i = 0; i < end - start - semicolon2; i++) {
  550                           buf[start + semicolon + i] 
  551                               = buf[start + i + semicolon2];
  552                       }
  553                       uriBC.setBytes(buf, start,
  554                               end - start - semicolon2 + semicolon);
  555                   } else {
  556                       request.setRequestedSessionId
  557                           (new String(uriBC.getBuffer(), start + sessionIdStart, 
  558                                   (end - start) - sessionIdStart, enc));
  559                       uriBC.setEnd(start + semicolon);
  560                   }
  561                   request.setRequestedSessionURL(true);
  562               } catch (UnsupportedEncodingException uee) {
  563                   // Make sure no session ID is returned
  564                   request.setRequestedSessionId(null);
  565                   request.setRequestedSessionURL(false);
  566                   log.warn(sm.getString("coyoteAdapter.parseSession", enc), uee);
  567               }
  568           } else {
  569               request.setRequestedSessionId(null);
  570               request.setRequestedSessionURL(false);
  571           }
  572   
  573       }
  574   
  575   
  576       /**
  577        * Parse session id in URL.
  578        */
  579       protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {
  580   
  581           // If session tracking via cookies has been disabled for the current
  582           // context, don't go looking for a session ID in a cookie as a cookie
  583           // from a parent context with a session ID may be present which would
  584           // overwrite the valid session ID encoded in the URL
  585           Context context = (Context) request.getMappingData().context;
  586           if (context != null && !context.getCookies())
  587               return;
  588   
  589           // Parse session id from cookies
  590           Cookies serverCookies = req.getCookies();
  591           int count = serverCookies.getCookieCount();
  592           if (count <= 0)
  593               return;
  594   
  595           for (int i = 0; i < count; i++) {
  596               ServerCookie scookie = serverCookies.getCookie(i);
  597               if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {
  598                   // Override anything requested in the URL
  599                   if (!request.isRequestedSessionIdFromCookie()) {
  600                       // Accept only the first session id cookie
  601                       convertMB(scookie.getValue());
  602                       request.setRequestedSessionId
  603                           (scookie.getValue().toString());
  604                       request.setRequestedSessionCookie(true);
  605                       request.setRequestedSessionURL(false);
  606                       if (log.isDebugEnabled())
  607                           log.debug(" Requested cookie session id is " +
  608                               request.getRequestedSessionId());
  609                   } else {
  610                       if (!request.isRequestedSessionIdValid()) {
  611                           // Replace the session id until one is valid
  612                           convertMB(scookie.getValue());
  613                           request.setRequestedSessionId
  614                               (scookie.getValue().toString());
  615                       }
  616                   }
  617               }
  618           }
  619   
  620       }
  621   
  622   
  623       /**
  624        * Character conversion of the URI.
  625        */
  626       protected void convertURI(MessageBytes uri, Request request) 
  627           throws Exception {
  628   
  629           ByteChunk bc = uri.getByteChunk();
  630           int length = bc.getLength();
  631           CharChunk cc = uri.getCharChunk();
  632           cc.allocate(length, -1);
  633   
  634           String enc = connector.getURIEncoding();
  635           if (enc != null) {
  636               B2CConverter conv = request.getURIConverter();
  637               try {
  638                   if (conv == null) {
  639                       conv = new B2CConverter(enc);
  640                       request.setURIConverter(conv);
  641                   }
  642               } catch (IOException e) {
  643                   // Ignore
  644                   log.error("Invalid URI encoding; using HTTP default");
  645                   connector.setURIEncoding(null);
  646               }
  647               if (conv != null) {
  648                   try {
  649                       conv.convert(bc, cc);
  650                       uri.setChars(cc.getBuffer(), cc.getStart(), 
  651                                    cc.getLength());
  652                       return;
  653                   } catch (IOException e) {
  654                       log.error("Invalid URI character encoding; trying ascii");
  655                       cc.recycle();
  656                   }
  657               }
  658           }
  659   
  660           // Default encoding: fast conversion
  661           byte[] bbuf = bc.getBuffer();
  662           char[] cbuf = cc.getBuffer();
  663           int start = bc.getStart();
  664           for (int i = 0; i < length; i++) {
  665               cbuf[i] = (char) (bbuf[i + start] & 0xff);
  666           }
  667           uri.setChars(cbuf, 0, length);
  668   
  669       }
  670   
  671   
  672       /**
  673        * Character conversion of the a US-ASCII MessageBytes.
  674        */
  675       protected void convertMB(MessageBytes mb) {
  676   
  677           // This is of course only meaningful for bytes
  678           if (mb.getType() != MessageBytes.T_BYTES)
  679               return;
  680           
  681           ByteChunk bc = mb.getByteChunk();
  682           CharChunk cc = mb.getCharChunk();
  683           int length = bc.getLength();
  684           cc.allocate(length, -1);
  685   
  686           // Default encoding: fast conversion
  687           byte[] bbuf = bc.getBuffer();
  688           char[] cbuf = cc.getBuffer();
  689           int start = bc.getStart();
  690           for (int i = 0; i < length; i++) {
  691               cbuf[i] = (char) (bbuf[i + start] & 0xff);
  692           }
  693           mb.setChars(cbuf, 0, length);
  694   
  695       }
  696   
  697   
  698       /**
  699        * Normalize URI.
  700        * <p>
  701        * This method normalizes "\", "//", "/./" and "/../". This method will
  702        * return false when trying to go above the root, or if the URI contains
  703        * a null byte.
  704        * 
  705        * @param uriMB URI to be normalized
  706        */
  707       public static boolean normalize(MessageBytes uriMB) {
  708   
  709           ByteChunk uriBC = uriMB.getByteChunk();
  710           final byte[] b = uriBC.getBytes();
  711           final int start = uriBC.getStart();
  712           int end = uriBC.getEnd();
  713   
  714           // An empty URL is not acceptable
  715           if (start == end)
  716               return false;
  717   
  718           // URL * is acceptable
  719           if ((end - start == 1) && b[start] == (byte) '*')
  720             return true;
  721   
  722           int pos = 0;
  723           int index = 0;
  724   
  725           // Replace '\' with '/'
  726           // Check for null byte
  727           for (pos = start; pos < end; pos++) {
  728               if (b[pos] == (byte) '\\') {
  729                   if (ALLOW_BACKSLASH) {
  730                       b[pos] = (byte) '/';
  731                   } else {
  732                       return false;
  733                   }
  734               }
  735               if (b[pos] == (byte) 0) {
  736                   return false;
  737               }
  738           }
  739   
  740           // The URL must start with '/'
  741           if (b[start] != (byte) '/') {
  742               return false;
  743           }
  744   
  745           // Replace "//" with "/"
  746           for (pos = start; pos < (end - 1); pos++) {
  747               if (b[pos] == (byte) '/') {
  748                   while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
  749                       copyBytes(b, pos, pos + 1, end - pos - 1);
  750                       end--;
  751                   }
  752               }
  753           }
  754   
  755           // If the URI ends with "/." or "/..", then we append an extra "/"
  756           // Note: It is possible to extend the URI by 1 without any side effect
  757           // as the next character is a non-significant WS.
  758           if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) {
  759               if ((b[end - 2] == (byte) '/') 
  760                   || ((b[end - 2] == (byte) '.') 
  761                       && (b[end - 3] == (byte) '/'))) {
  762                   b[end] = (byte) '/';
  763                   end++;
  764               }
  765           }
  766   
  767           uriBC.setEnd(end);
  768   
  769           index = 0;
  770   
  771           // Resolve occurrences of "/./" in the normalized path
  772           while (true) {
  773               index = uriBC.indexOf("/./", 0, 3, index);
  774               if (index < 0)
  775                   break;
  776               copyBytes(b, start + index, start + index + 2, 
  777                         end - start - index - 2);
  778               end = end - 2;
  779               uriBC.setEnd(end);
  780           }
  781   
  782           index = 0;
  783   
  784           // Resolve occurrences of "/../" in the normalized path
  785           while (true) {
  786               index = uriBC.indexOf("/../", 0, 4, index);
  787               if (index < 0)
  788                   break;
  789               // Prevent from going outside our context
  790               if (index == 0)
  791                   return false;
  792               int index2 = -1;
  793               for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
  794                   if (b[pos] == (byte) '/') {
  795                       index2 = pos;
  796                   }
  797               }
  798               copyBytes(b, start + index2, start + index + 3,
  799                         end - start - index - 3);
  800               end = end + index2 - index - 3;
  801               uriBC.setEnd(end);
  802               index = index2;
  803           }
  804   
  805           return true;
  806   
  807       }
  808   
  809   
  810       /**
  811        * Check that the URI is normalized following character decoding.
  812        * <p>
  813        * This method checks for "\", 0, "//", "/./" and "/../". This method will
  814        * return false if sequences that are supposed to be normalized are still 
  815        * present in the URI.
  816        * 
  817        * @param uriMB URI to be checked (should be chars)
  818        */
  819       public static boolean checkNormalize(MessageBytes uriMB) {
  820   
  821           CharChunk uriCC = uriMB.getCharChunk();
  822           char[] c = uriCC.getChars();
  823           int start = uriCC.getStart();
  824           int end = uriCC.getEnd();
  825   
  826           int pos = 0;
  827   
  828           // Check for '\' and 0
  829           for (pos = start; pos < end; pos++) {
  830               if (c[pos] == '\\') {
  831                   return false;
  832               }
  833               if (c[pos] == 0) {
  834                   return false;
  835               }
  836           }
  837   
  838           // Check for "//"
  839           for (pos = start; pos < (end - 1); pos++) {
  840               if (c[pos] == '/') {
  841                   if (c[pos + 1] == '/') {
  842                       return false;
  843                   }
  844               }
  845           }
  846   
  847           // Check for ending with "/." or "/.."
  848           if (((end - start) >= 2) && (c[end - 1] == '.')) {
  849               if ((c[end - 2] == '/') 
  850                       || ((c[end - 2] == '.') 
  851                       && (c[end - 3] == '/'))) {
  852                   return false;
  853               }
  854           }
  855   
  856           // Check for "/./"
  857           if (uriCC.indexOf("/./", 0, 3, 0) >= 0) {
  858               return false;
  859           }
  860   
  861           // Check for "/../"
  862           if (uriCC.indexOf("/../", 0, 4, 0) >= 0) {
  863               return false;
  864           }
  865   
  866           return true;
  867   
  868       }
  869   
  870   
  871       // ------------------------------------------------------ Protected Methods
  872   
  873   
  874       /**
  875        * Copy an array of bytes to a different position. Used during 
  876        * normalization.
  877        */
  878       protected static void copyBytes(byte[] b, int dest, int src, int len) {
  879           for (int pos = 0; pos < len; pos++) {
  880               b[pos + dest] = b[pos + src];
  881           }
  882       }
  883   
  884   
  885   }

Home » apache-tomcat-6.0.26-src » org.apache » catalina » connector » [javadoc | source]