]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java
Merge "Update notes about password security"
[gigi.git] / lib / jetty / org / eclipse / jetty / server / handler / ResourceHandler.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4 //  ------------------------------------------------------------------------
5 //  All rights reserved. This program and the accompanying materials
6 //  are made available under the terms of the Eclipse Public License v1.0
7 //  and Apache License v2.0 which accompanies this distribution.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
16 //  ========================================================================
17 //
18
19 package org.eclipse.jetty.server.handler;
20
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.net.MalformedURLException;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.ReadableByteChannel;
26
27 import javax.servlet.AsyncContext;
28 import javax.servlet.RequestDispatcher;
29 import javax.servlet.ServletException;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.eclipse.jetty.http.HttpFields;
34 import org.eclipse.jetty.http.HttpHeader;
35 import org.eclipse.jetty.http.HttpMethod;
36 import org.eclipse.jetty.http.HttpStatus;
37 import org.eclipse.jetty.http.MimeTypes;
38 import org.eclipse.jetty.io.WriterOutputStream;
39 import org.eclipse.jetty.server.HttpOutput;
40 import org.eclipse.jetty.server.Request;
41 import org.eclipse.jetty.server.Response;
42 import org.eclipse.jetty.server.handler.ContextHandler.Context;
43 import org.eclipse.jetty.util.BufferUtil;
44 import org.eclipse.jetty.util.Callback;
45 import org.eclipse.jetty.util.URIUtil;
46 import org.eclipse.jetty.util.log.Log;
47 import org.eclipse.jetty.util.log.Logger;
48 import org.eclipse.jetty.util.resource.FileResource;
49 import org.eclipse.jetty.util.resource.Resource;
50
51
52 /* ------------------------------------------------------------ */
53 /** Resource Handler.
54  *
55  * This handle will serve static content and handle If-Modified-Since headers.
56  * No caching is done.
57  * Requests for resources that do not exist are let pass (Eg no 404's).
58  *
59  *
60  * @org.apache.xbean.XBean
61  */
62 public class ResourceHandler extends HandlerWrapper
63 {
64     private static final Logger LOG = Log.getLogger(ResourceHandler.class);
65
66     ContextHandler _context;
67     Resource _baseResource;
68     Resource _defaultStylesheet;
69     Resource _stylesheet;
70     String[] _welcomeFiles={"index.html"};
71     MimeTypes _mimeTypes = new MimeTypes();
72     String _cacheControl;
73     boolean _directory;
74     boolean _etags;
75     int _minMemoryMappedContentLength=1024;
76     int _minAsyncContentLength=0;
77
78     /* ------------------------------------------------------------ */
79     public ResourceHandler()
80     {
81
82     }
83
84     /* ------------------------------------------------------------ */
85     public MimeTypes getMimeTypes()
86     {
87         return _mimeTypes;
88     }
89
90     /* ------------------------------------------------------------ */
91     public void setMimeTypes(MimeTypes mimeTypes)
92     {
93         _mimeTypes = mimeTypes;
94     }
95
96     /* ------------------------------------------------------------ */
97     /** Get the directory option.
98      * @return true if directories are listed.
99      */
100     public boolean isDirectoriesListed()
101     {
102         return _directory;
103     }
104
105     /* ------------------------------------------------------------ */
106     /** Set the directory.
107      * @param directory true if directories are listed.
108      */
109     public void setDirectoriesListed(boolean directory)
110     {
111         _directory = directory;
112     }
113
114     /* ------------------------------------------------------------ */
115     /** Get minimum memory mapped file content length.
116      * @return the minimum size in bytes of a file resource that will
117      * be served using a memory mapped buffer, or -1 (default) for no memory mapped
118      * buffers.
119      */
120     public int getMinMemoryMappedContentLength()
121     {
122         return _minMemoryMappedContentLength;
123     }
124
125     /* ------------------------------------------------------------ */
126     /** Set minimum memory mapped file content length.
127      * @param minMemoryMappedFileSize the minimum size in bytes of a file resource that will
128      * be served using a memory mapped buffer, or -1 for no memory mapped
129      * buffers.
130      */
131     public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
132     {
133         _minMemoryMappedContentLength = minMemoryMappedFileSize;
134     }
135
136     /* ------------------------------------------------------------ */
137     /** Get the minimum content length for async handling.
138      * @return The minimum size in bytes of the content before asynchronous 
139      * handling is used, or -1 for no async handling or 0 (default) for using
140      * {@link HttpServletResponse#getBufferSize()} as the minimum length.
141      */
142     public int getMinAsyncContentLength()
143     {
144         return _minAsyncContentLength;
145     }
146
147     /* ------------------------------------------------------------ */
148     /** Set the minimum content length for async handling.
149      * @param minAsyncContentLength The minimum size in bytes of the content before asynchronous 
150      * handling is used, or -1 for no async handling or 0 for using
151      * {@link HttpServletResponse#getBufferSize()} as the minimum length.
152      */
153     public void setMinAsyncContentLength(int minAsyncContentLength)
154     {
155         _minAsyncContentLength = minAsyncContentLength;
156     }
157
158     /* ------------------------------------------------------------ */
159     /**
160      * @return True if ETag processing is done
161      */
162     public boolean isEtags()
163     {
164         return _etags;
165     }
166
167     /* ------------------------------------------------------------ */
168     /**
169      * @param etags True if ETag processing is done
170      */
171     public void setEtags(boolean etags)
172     {
173         _etags = etags;
174     }
175
176     /* ------------------------------------------------------------ */
177     @Override
178     public void doStart()
179     throws Exception
180     {
181         Context scontext = ContextHandler.getCurrentContext();
182         _context = (scontext==null?null:scontext.getContextHandler());
183
184         super.doStart();
185     }
186
187     /* ------------------------------------------------------------ */
188     /**
189      * @return Returns the resourceBase.
190      */
191     public Resource getBaseResource()
192     {
193         if (_baseResource==null)
194             return null;
195         return _baseResource;
196     }
197
198     /* ------------------------------------------------------------ */
199     /**
200      * @return Returns the base resource as a string.
201      */
202     public String getResourceBase()
203     {
204         if (_baseResource==null)
205             return null;
206         return _baseResource.toString();
207     }
208
209
210     /* ------------------------------------------------------------ */
211     /**
212      * @param base The resourceBase to set.
213      */
214     public void setBaseResource(Resource base)
215     {
216         _baseResource=base;
217     }
218
219     /* ------------------------------------------------------------ */
220     /**
221      * @param resourceBase The base resource as a string.
222      */
223     public void setResourceBase(String resourceBase)
224     {
225         try
226         {
227             setBaseResource(Resource.newResource(resourceBase));
228         }
229         catch (Exception e)
230         {
231             LOG.warn(e.toString());
232             LOG.debug(e);
233             throw new IllegalArgumentException(resourceBase);
234         }
235     }
236
237     /* ------------------------------------------------------------ */
238     /**
239      * @return Returns the stylesheet as a Resource.
240      */
241     public Resource getStylesheet()
242     {
243         if(_stylesheet != null)
244         {
245             return _stylesheet;
246         }
247         else
248         {
249             if(_defaultStylesheet == null)
250             {
251                 _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
252             }
253             return _defaultStylesheet;
254         }
255     }
256
257     /* ------------------------------------------------------------ */
258     /**
259      * @param stylesheet The location of the stylesheet to be used as a String.
260      */
261     public void setStylesheet(String stylesheet)
262     {
263         try
264         {
265             _stylesheet = Resource.newResource(stylesheet);
266             if(!_stylesheet.exists())
267             {
268                 LOG.warn("unable to find custom stylesheet: " + stylesheet);
269                 _stylesheet = null;
270             }
271         }
272         catch(Exception e)
273         {
274             LOG.warn(e.toString());
275             LOG.debug(e);
276             throw new IllegalArgumentException(stylesheet);
277         }
278     }
279
280     /* ------------------------------------------------------------ */
281     /**
282      * @return the cacheControl header to set on all static content.
283      */
284     public String getCacheControl()
285     {
286         return _cacheControl;
287     }
288
289     /* ------------------------------------------------------------ */
290     /**
291      * @param cacheControl the cacheControl header to set on all static content.
292      */
293     public void setCacheControl(String cacheControl)
294     {
295         _cacheControl=cacheControl;
296     }
297
298     /* ------------------------------------------------------------ */
299     /*
300      */
301     public Resource getResource(String path) throws MalformedURLException
302     {
303         if (path==null || !path.startsWith("/"))
304             throw new MalformedURLException(path);
305
306         if (LOG.isDebugEnabled())
307             LOG.debug("{} getResource({})",_context==null?_baseResource:_context,_baseResource,path);
308         
309         Resource base = _baseResource;
310         if (base==null)
311         {
312             if (_context==null)
313                 return null;
314             return _context.getResource(path);
315         }
316
317         try
318         {
319             path=URIUtil.canonicalPath(path);
320             Resource r = base.addPath(path);
321             
322             if (r!=null && r.getAlias()!=null && (_context==null || !_context.checkAlias(path, r)))
323             {
324                 if (LOG.isDebugEnabled())
325                     LOG.debug("resource={} alias={}",r,r.getAlias());
326                 return null;
327             }
328             return r;
329         }
330         catch(Exception e)
331         {
332             LOG.ignore(e);
333         }
334
335         return null;
336     }
337
338     /* ------------------------------------------------------------ */
339     protected Resource getResource(HttpServletRequest request) throws MalformedURLException
340     {
341         String servletPath;
342         String pathInfo;
343         Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
344         if (included != null && included.booleanValue())
345         {
346             servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
347             pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
348
349             if (servletPath == null && pathInfo == null)
350             {
351                 servletPath = request.getServletPath();
352                 pathInfo = request.getPathInfo();
353             }
354         }
355         else
356         {
357             servletPath = request.getServletPath();
358             pathInfo = request.getPathInfo();
359         }
360
361         String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
362         return getResource(pathInContext);
363     }
364
365
366     /* ------------------------------------------------------------ */
367     public String[] getWelcomeFiles()
368     {
369         return _welcomeFiles;
370     }
371
372     /* ------------------------------------------------------------ */
373     public void setWelcomeFiles(String[] welcomeFiles)
374     {
375         _welcomeFiles=welcomeFiles;
376     }
377
378     /* ------------------------------------------------------------ */
379     protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
380     {
381         for (int i=0;i<_welcomeFiles.length;i++)
382         {
383             Resource welcome=directory.addPath(_welcomeFiles[i]);
384             if (welcome.exists() && !welcome.isDirectory())
385                 return welcome;
386         }
387
388         return null;
389     }
390
391     /* ------------------------------------------------------------ */
392     /*
393      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
394      */
395     @Override
396     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
397     {
398         if (baseRequest.isHandled())
399             return;
400
401         boolean skipContentBody = false;
402
403         if(!HttpMethod.GET.is(request.getMethod()))
404         {
405             if(!HttpMethod.HEAD.is(request.getMethod()))
406             {
407                 //try another handler
408                 super.handle(target, baseRequest, request, response);
409                 return;
410             }
411             skipContentBody = true;
412         }
413
414         Resource resource = getResource(request);
415         
416         if (LOG.isDebugEnabled())
417         { 
418             if (resource==null)
419                 LOG.debug("resource=null");
420             else
421                 LOG.debug("resource={} alias={} exists={}",resource,resource.getAlias(),resource.exists());
422         }
423         
424         
425         // If resource is not found
426         if (resource==null || !resource.exists())
427         {
428             // inject the jetty-dir.css file if it matches
429             if (target.endsWith("/jetty-dir.css"))
430             {
431                 resource = getStylesheet();
432                 if (resource==null)
433                     return;
434                 response.setContentType("text/css");
435             }
436             else
437             {
438                 //no resource - try other handlers
439                 super.handle(target, baseRequest, request, response);
440                 return;
441             }
442         }
443
444         // We are going to serve something
445         baseRequest.setHandled(true);
446
447         // handle directories
448         if (resource.isDirectory())
449         {
450             String pathInfo = request.getPathInfo();
451             boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
452             if (!endsWithSlash)
453             {
454                 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
455                 return;
456             }
457
458             Resource welcome=getWelcome(resource);
459             if (welcome!=null && welcome.exists())
460                 resource=welcome;
461             else
462             {
463                 doDirectory(request,response,resource);
464                 baseRequest.setHandled(true);
465                 return;
466             }
467         }
468
469         // Handle ETAGS
470         long last_modified=resource.lastModified();
471         String etag=null;
472         if (_etags)
473         {
474             // simple handling of only a single etag
475             String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
476             etag=resource.getWeakETag();
477             if (ifnm!=null && resource!=null && ifnm.equals(etag))
478             {
479                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
480                 baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
481                 return;
482             }
483         }
484         
485         // Handle if modified since 
486         if (last_modified>0)
487         {
488             long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
489             if (if_modified>0 && last_modified/1000<=if_modified/1000)
490             {
491                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
492                 return;
493             }
494         }
495
496         // set the headers
497         String mime=_mimeTypes.getMimeByExtension(resource.toString());
498         if (mime==null)
499             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
500         doResponseHeaders(response,resource,mime);
501         if (_etags)
502             baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
503         
504         if(skipContentBody)
505             return;
506         
507         
508         // Send the content
509         OutputStream out =null;
510         try {out = response.getOutputStream();}
511         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
512
513         // Has the output been wrapped
514         if (!(out instanceof HttpOutput))
515             // Write content via wrapped output
516             resource.writeTo(out,0,resource.length());
517         else
518         {
519             // select async by size
520             int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
521             
522             if (request.isAsyncSupported() && 
523                 min_async_size>0 &&
524                 resource.length()>=min_async_size)
525             {
526                 final AsyncContext async = request.startAsync();
527                 async.setTimeout(0);
528                 Callback callback = new Callback()
529                 {
530                     @Override
531                     public void succeeded()
532                     {
533                         async.complete();
534                     }
535
536                     @Override
537                     public void failed(Throwable x)
538                     {
539                         LOG.warn(x.toString());
540                         LOG.debug(x);
541                         async.complete();
542                     }   
543                 };
544
545                 // Can we use a memory mapped file?
546                 if (_minMemoryMappedContentLength>0 && 
547                     resource.length()>_minMemoryMappedContentLength &&
548                     resource.length()<Integer.MAX_VALUE &&
549                     resource instanceof FileResource)
550                 {
551                     ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
552                     ((HttpOutput)out).sendContent(buffer,callback);
553                 }
554                 else  // Do a blocking write of a channel (if available) or input stream
555                 {
556                     // Close of the channel/inputstream is done by the async sendContent
557                     ReadableByteChannel channel= resource.getReadableByteChannel();
558                     if (channel!=null)
559                         ((HttpOutput)out).sendContent(channel,callback);
560                     else
561                         ((HttpOutput)out).sendContent(resource.getInputStream(),callback);
562                 }
563             }
564             else
565             {
566                 // Can we use a memory mapped file?
567                 if (_minMemoryMappedContentLength>0 && 
568                     resource.length()>_minMemoryMappedContentLength &&
569                     resource instanceof FileResource)
570                 {
571                     ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
572                     ((HttpOutput)out).sendContent(buffer);
573                 }
574                 else  // Do a blocking write of a channel (if available) or input stream
575                 {
576                     ReadableByteChannel channel= resource.getReadableByteChannel();
577                     if (channel!=null)
578                         ((HttpOutput)out).sendContent(channel);
579                     else
580                         ((HttpOutput)out).sendContent(resource.getInputStream());
581                 }
582             }
583         }
584     }
585
586     /* ------------------------------------------------------------ */
587     protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
588         throws IOException
589     {
590         if (_directory)
591         {
592             String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
593             response.setContentType("text/html; charset=UTF-8");
594             response.getWriter().println(listing);
595         }
596         else
597             response.sendError(HttpStatus.FORBIDDEN_403);
598     }
599
600     /* ------------------------------------------------------------ */
601     /** Set the response headers.
602      * This method is called to set the response headers such as content type and content length.
603      * May be extended to add additional headers.
604      * @param response
605      * @param resource
606      * @param mimeType
607      */
608     protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
609     {
610         if (mimeType!=null)
611             response.setContentType(mimeType);
612
613         long length=resource.length();
614
615         if (response instanceof Response)
616         {
617             HttpFields fields = ((Response)response).getHttpFields();
618
619             if (length>0)
620                 ((Response)response).setLongContentLength(length);
621
622             if (_cacheControl!=null)
623                 fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
624         }
625         else
626         {
627             if (length>Integer.MAX_VALUE)
628                 response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
629             else if (length>0)
630                 response.setContentLength((int)length);
631
632             if (_cacheControl!=null)
633                 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
634         }
635     }
636 }