View Javadoc

    1   // ========================================================================
    2   // Copyright 1999-2005 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.handler;
    16
    17  import java.io.IOException;
    18  import java.io.OutputStream;
    19  import java.net.MalformedURLException;
    20
    21  import javax.servlet.ServletException;
    22  import javax.servlet.http.HttpServletRequest;
    23  import javax.servlet.http.HttpServletResponse;
    24
    25  import org.mortbay.io.Buffer;
    26  import org.mortbay.io.ByteArrayBuffer;
    27  import org.mortbay.io.WriterOutputStream;
    28  import org.mortbay.jetty.HttpConnection;
    29  import org.mortbay.jetty.HttpFields;
    30  import org.mortbay.jetty.HttpHeaders;
    31  import org.mortbay.jetty.HttpMethods;
    32  import org.mortbay.jetty.MimeTypes;
    33  import org.mortbay.jetty.Request;
    34  import org.mortbay.jetty.Response;
    35  import org.mortbay.jetty.handler.ContextHandler.SContext;
    36  import org.mortbay.log.Log;
    37  import org.mortbay.resource.FileResource;
    38  import org.mortbay.resource.Resource;
    39  import org.mortbay.util.StringUtil;
    40  import org.mortbay.util.TypeUtil;
    41  import org.mortbay.util.URIUtil;
    42
    43
    44  /* ------------------------------------------------------------ */
    45  /** Resource Handler.
    46   *
    47   * This handle will serve static content and handle If-Modified-Since headers.
    48   * No caching is done.
    49   * Requests that cannot be handled are let pass (Eg no 404's)
    50   *
    51   * @author Greg Wilkins (gregw)
    52   * @org.apache.xbean.XBean
    53   */
    54  public class ResourceHandler extends AbstractHandler
    55  {
    56      ContextHandler _context;
    57      Resource _baseResource;
    58      String[] _welcomeFiles={"index.html"};
    59      MimeTypes _mimeTypes = new MimeTypes();
    60      ByteArrayBuffer _cacheControl;
    61      boolean _aliases;
    62
    63      /* ------------------------------------------------------------ */
    64      public ResourceHandler()
    65      {
    66      }
    67
    68      /* ------------------------------------------------------------ */
    69      public MimeTypes getMimeTypes()
    70      {
    71          return _mimeTypes;
    72      }
    73
    74      /* ------------------------------------------------------------ */
    75      public void setMimeTypes(MimeTypes mimeTypes)
    76      {
    77          _mimeTypes = mimeTypes;
    78      }
    79
    80      /* ------------------------------------------------------------ */
    81      /**
    82       * @return True if resource aliases are allowed.
    83       */
    84      public boolean isAliases()
    85      {
    86          return _aliases;
    87      }
    88
    89      /* ------------------------------------------------------------ */
    90      /**
    91       * Set if resource aliases (eg symlink, 8.3 names, case insensitivity) are allowed.
    92       * Allowing aliases can significantly increase security vulnerabilities.
    93       * @param aliases True if aliases are supported.
    94       */
    95      public void setAliases(boolean aliases)
    96      {
    97          _aliases = aliases;
    98      }
    99
    100     /* ------------------------------------------------------------ */
    101     public void doStart()
    102     throws Exception
    103     {
    104         SContext scontext = ContextHandler.getCurrentContext();
    105         _context = (scontext==null?null:scontext.getContextHandler());
    106
    107         if (!_aliases && !FileResource.getCheckAliases())
    108             throw new IllegalStateException("Alias checking disabled");
    109
    110         super.doStart();
    111     }
    112
    113     /* ------------------------------------------------------------ */
    114     /**
    115      * @return Returns the resourceBase.
    116      */
    117     public Resource getBaseResource()
    118     {
    119         if (_baseResource==null)
    120             return null;
    121         return _baseResource;
    122     }
    123
    124     /* ------------------------------------------------------------ */
    125     /**
    126      * @return Returns the base resource as a string.
    127      */
    128     public String getResourceBase()
    129     {
    130         if (_baseResource==null)
    131             return null;
    132         return _baseResource.toString();
    133     }
    134
    135
    136     /* ------------------------------------------------------------ */
    137     /**
    138      * @param base The resourceBase to set.
    139      */
    140     public void setBaseResource(Resource base)
    141     {
    142         _baseResource=base;
    143     }
    144
    145     /* ------------------------------------------------------------ */
    146     /**
    147      * @param resourceBase The base resource as a string.
    148      */
    149     public void setResourceBase(String resourceBase)
    150     {
    151         try
    152         {
    153             setBaseResource(Resource.newResource(resourceBase));
    154         }
    155         catch (Exception e)
    156         {
    157             Log.warn(e.toString());
    158             Log.debug(e);
    159             throw new IllegalArgumentException(resourceBase);
    160         }
    161     }
    162
    163     /* ------------------------------------------------------------ */
    164     /**
    165      * @return the cacheControl header to set on all static content.
    166      */
    167     public String getCacheControl()
    168     {
    169         return _cacheControl.toString();
    170     }
    171
    172     /* ------------------------------------------------------------ */
    173     /**
    174      * @param cacheControl the cacheControl header to set on all static content.
    175      */
    176     public void setCacheControl(String cacheControl)
    177     {
    178         _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
    179     }
    180
    181     /* ------------------------------------------------------------ */
    182     /*
    183      */
    184     public Resource getResource(String path) throws MalformedURLException
    185     {
    186         if (path==null || !path.startsWith("/"))
    187             throw new MalformedURLException(path);
    188
    189         Resource base = _baseResource;
    190         if (base==null)
    191         {
    192             if (_context==null)
    193                 return null;
    194             base=_context.getBaseResource();
    195             if (base==null)
    196                 return null;
    197         }
    198
    199         try
    200         {
    201             path=URIUtil.canonicalPath(path);
    202             Resource resource=base.addPath(path);
    203             return resource;
    204         }
    205         catch(Exception e)
    206         {
    207             Log.ignore(e);
    208         }
    209
    210         return null;
    211     }
    212
    213     /* ------------------------------------------------------------ */
    214     protected Resource getResource(HttpServletRequest request) throws MalformedURLException
    215     {
    216         String path_info=request.getPathInfo();
    217         if (path_info==null)
    218             return null;
    219         return getResource(path_info);
    220     }
    221
    222
    223     /* ------------------------------------------------------------ */
    224     public String[] getWelcomeFiles()
    225     {
    226         return _welcomeFiles;
    227     }
    228
    229     /* ------------------------------------------------------------ */
    230     public void setWelcomeFiles(String[] welcomeFiles)
    231     {
    232         _welcomeFiles=welcomeFiles;
    233     }
    234
    235     /* ------------------------------------------------------------ */
    236     protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
    237     {
    238         for (int i=0;i<_welcomeFiles.length;i++)
    239         {
    240             Resource welcome=directory.addPath(_welcomeFiles[i]);
    241             if (welcome.exists() && !welcome.isDirectory())
    242                 return welcome;
    243         }
    244
    245         return null;
    246     }
    247
    248     /* ------------------------------------------------------------ */
    249     /*
    250      * @see org.mortbay.jetty.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
    251      */
    252     public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
    253     {
    254         Request base_request = request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest();
    255         if (base_request.isHandled())
    256             return;
    257
    258         boolean skipContentBody = false;
    259         if(!HttpMethods.GET.equals(request.getMethod()))
    260         {
    261             if(!HttpMethods.HEAD.equals(request.getMethod()))
    262                 return;
    263             skipContentBody = true;
    264         }
    265
    266         Resource resource=getResource(request);
    267
    268         if (resource==null || !resource.exists())
    269             return;
    270         if (!_aliases && resource.getAlias()!=null)
    271         {
    272             Log.info(resource+" aliased to "+resource.getAlias());
    273             return;
    274         }
    275
    276         // We are going to server something
    277         base_request.setHandled(true);
    278
    279         if (resource.isDirectory())
    280         {
    281             if (!request.getPathInfo().endsWith(URIUtil.SLASH))
    282             {
    283                 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
    284                 return;
    285             }
    286             resource=getWelcome(resource);
    287
    288             if (resource==null || !resource.exists() || resource.isDirectory())
    289             {
    290                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
    291                 return;
    292             }
    293         }
    294
    295         // set some headers
    296         long last_modified=resource.lastModified();
    297         if (last_modified>0)
    298         {
    299             long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
    300             if (if_modified>0 && last_modified/1000<=if_modified/1000)
    301             {
    302                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
    303                 return;
    304             }
    305         }
    306
    307         Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
    308         if (mime==null)
    309             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
    310
    311         // set the headers
    312         doResponseHeaders(response,resource,mime!=null?mime.toString():null);
    313         response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
    314         if(skipContentBody)
    315             return;
    316         // Send the content
    317         OutputStream out =null;
    318         try {out = response.getOutputStream();}
    319         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
    320
    321         // See if a short direct method can be used?
    322         if (out instanceof HttpConnection.Output)
    323         {
    324             // TODO file mapped buffers
    325             ((HttpConnection.Output)out).sendContent(resource.getInputStream());
    326         }
    327         else
    328         {
    329             // Write content normally
    330             resource.writeTo(out,0,resource.length());
    331         }
    332     }
    333
    334     /* ------------------------------------------------------------ */
    335     /** Set the response headers.
    336      * This method is called to set the response headers such as content type and content length.
    337      * May be extended to add additional headers.
    338      * @param response
    339      * @param resource
    340      * @param mimeType
    341      */
    342     protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
    343     {
    344         if (mimeType!=null)
    345             response.setContentType(mimeType);
    346
    347         long length=resource.length();
    348
    349         if (response instanceof Response)
    350         {
    351             HttpFields fields = ((Response)response).getHttpFields();
    352
    353             if (length>0)
    354                 fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);
    355
    356             if (_cacheControl!=null)
    357                 fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
    358         }
    359         else
    360         {
    361             if (length>0)
    362                 response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(length));
    363
    364             if (_cacheControl!=null)
    365                 response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
    366         }
    367
    368     }
    369 }