View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.Enumeration;
31  import java.util.EventListener;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.concurrent.CopyOnWriteArrayList;
40  
41  import javax.servlet.RequestDispatcher;
42  import javax.servlet.Servlet;
43  import javax.servlet.ServletContext;
44  import javax.servlet.ServletContextAttributeEvent;
45  import javax.servlet.ServletContextAttributeListener;
46  import javax.servlet.ServletContextEvent;
47  import javax.servlet.ServletContextListener;
48  import javax.servlet.ServletException;
49  import javax.servlet.ServletRequestAttributeListener;
50  import javax.servlet.ServletRequestEvent;
51  import javax.servlet.ServletRequestListener;
52  import javax.servlet.http.HttpServletRequest;
53  import javax.servlet.http.HttpServletResponse;
54  
55  import org.eclipse.jetty.http.HttpException;
56  import org.eclipse.jetty.http.MimeTypes;
57  import org.eclipse.jetty.io.Buffer;
58  import org.eclipse.jetty.server.AbstractHttpConnection;
59  import org.eclipse.jetty.server.Dispatcher;
60  import org.eclipse.jetty.server.DispatcherType;
61  import org.eclipse.jetty.server.Handler;
62  import org.eclipse.jetty.server.HandlerContainer;
63  import org.eclipse.jetty.server.Request;
64  import org.eclipse.jetty.server.Server;
65  import org.eclipse.jetty.util.Attributes;
66  import org.eclipse.jetty.util.AttributesMap;
67  import org.eclipse.jetty.util.LazyList;
68  import org.eclipse.jetty.util.Loader;
69  import org.eclipse.jetty.util.StringUtil;
70  import org.eclipse.jetty.util.TypeUtil;
71  import org.eclipse.jetty.util.URIUtil;
72  import org.eclipse.jetty.util.component.AggregateLifeCycle;
73  import org.eclipse.jetty.util.component.Dumpable;
74  import org.eclipse.jetty.util.log.Log;
75  import org.eclipse.jetty.util.log.Logger;
76  import org.eclipse.jetty.util.resource.Resource;
77  
78  /* ------------------------------------------------------------ */
79  /**
80   * ContextHandler.
81   *
82   * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
83   *
84   * <p>
85   * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
86   * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
87   * <p>
88   * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
89   * and org.eclipse.jetty.server.Request.maxFormContentSize.  These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
90   * 
91   * @org.apache.xbean.XBean description="Creates a basic HTTP context"
92   */
93  public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful
94  {
95      private static final Logger LOG = Log.getLogger(ContextHandler.class);
96  
97      private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
98  
99      /**
100      * If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
101      * with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
102      * for the attribute value.
103      */
104     public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
105 
106     /* ------------------------------------------------------------ */
107     /**
108      * Get the current ServletContext implementation.
109      *
110      * @return ServletContext implementation
111      */
112     public static Context getCurrentContext()
113     {
114         return __context.get();
115     }
116 
117     protected Context _scontext;
118 
119     private final AttributesMap _attributes;
120     private final AttributesMap _contextAttributes;
121     private final Map<String, String> _initParams;
122     private ClassLoader _classLoader;
123     private String _contextPath = "/";
124     private String _displayName;
125     private Resource _baseResource;
126     private MimeTypes _mimeTypes;
127     private Map<String, String> _localeEncodingMap;
128     private String[] _welcomeFiles;
129     private ErrorHandler _errorHandler;
130     private String[] _vhosts;
131     private Set<String> _connectors;
132     private EventListener[] _eventListeners;
133     private Logger _logger;
134     private boolean _allowNullPathInfo;
135     private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
136     private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
137     private boolean _compactPath = false;
138     private boolean _aliases = false;
139 
140     private Object _contextListeners;
141     private Object _contextAttributeListeners;
142     private Object _requestListeners;
143     private Object _requestAttributeListeners;
144     private Map<String, Object> _managedAttributes;
145     private String[] _protectedTargets;
146     private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
147 
148     private boolean _shutdown = false;
149     private boolean _available = true;
150     private volatile int _availability; // 0=STOPPED, 1=AVAILABLE, 2=SHUTDOWN, 3=UNAVAILABLE
151 
152     private final static int __STOPPED = 0, __AVAILABLE = 1, __SHUTDOWN = 2, __UNAVAILABLE = 3;
153 
154     /* ------------------------------------------------------------ */
155     /**
156      *
157      */
158     public ContextHandler()
159     {
160         super();
161         _scontext = new Context();
162         _attributes = new AttributesMap();
163         _contextAttributes = new AttributesMap();
164         _initParams = new HashMap<String, String>();
165         addAliasCheck(new ApproveNonExistentDirectoryAliases());
166     }
167 
168     /* ------------------------------------------------------------ */
169     /**
170      *
171      */
172     protected ContextHandler(Context context)
173     {
174         super();
175         _scontext = context;
176         _attributes = new AttributesMap();
177         _contextAttributes = new AttributesMap();
178         _initParams = new HashMap<String, String>();
179         addAliasCheck(new ApproveNonExistentDirectoryAliases());
180     }
181 
182     /* ------------------------------------------------------------ */
183     /**
184      *
185      */
186     public ContextHandler(String contextPath)
187     {
188         this();
189         setContextPath(contextPath);
190     }
191 
192     /* ------------------------------------------------------------ */
193     /**
194      *
195      */
196     public ContextHandler(HandlerContainer parent, String contextPath)
197     {
198         this();
199         setContextPath(contextPath);
200         if (parent instanceof HandlerWrapper)
201             ((HandlerWrapper)parent).setHandler(this);
202         else if (parent instanceof HandlerCollection)
203             ((HandlerCollection)parent).addHandler(this);
204     }
205 
206     /* ------------------------------------------------------------ */
207     @Override
208     public void dump(Appendable out, String indent) throws IOException
209     {
210         dumpThis(out);
211         dump(out,indent,Collections.singletonList(new CLDump(getClassLoader())),TypeUtil.asList(getHandlers()),getBeans(),_initParams.entrySet(),
212                 _attributes.getAttributeEntrySet(),_contextAttributes.getAttributeEntrySet());
213     }
214 
215     /* ------------------------------------------------------------ */
216     public Context getServletContext()
217     {
218         return _scontext;
219     }
220 
221     /* ------------------------------------------------------------ */
222     /**
223      * @return the allowNullPathInfo true if /context is not redirected to /context/
224      */
225     public boolean getAllowNullPathInfo()
226     {
227         return _allowNullPathInfo;
228     }
229 
230     /* ------------------------------------------------------------ */
231     /**
232      * @param allowNullPathInfo
233      *            true if /context is not redirected to /context/
234      */
235     public void setAllowNullPathInfo(boolean allowNullPathInfo)
236     {
237         _allowNullPathInfo = allowNullPathInfo;
238     }
239 
240     /* ------------------------------------------------------------ */
241     @Override
242     public void setServer(Server server)
243     {
244         if (_errorHandler != null)
245         {
246             Server old_server = getServer();
247             if (old_server != null && old_server != server)
248                 old_server.getContainer().update(this,_errorHandler,null,"error",true);
249             super.setServer(server);
250             if (server != null && server != old_server)
251                 server.getContainer().update(this,null,_errorHandler,"error",true);
252             _errorHandler.setServer(server);
253         }
254         else
255             super.setServer(server);
256     }
257 
258     /* ------------------------------------------------------------ */
259     /**
260      * Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
261      * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
262      * matching virtual host name.
263      *
264      * @param vhosts
265      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
266      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
267      */
268     public void setVirtualHosts(String[] vhosts)
269     {
270         if (vhosts == null)
271         {
272             _vhosts = vhosts;
273         }
274         else
275         {
276             _vhosts = new String[vhosts.length];
277             for (int i = 0; i < vhosts.length; i++)
278                 _vhosts[i] = normalizeHostname(vhosts[i]);
279         }
280     }
281 
282     /* ------------------------------------------------------------ */
283     /** Either set virtual hosts or add to an existing set of virtual hosts.
284      *
285      * @param virtualHosts
286      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
287      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
288      */
289     public void addVirtualHosts(String[] virtualHosts)
290     {
291         if (virtualHosts == null)  // since this is add, we don't null the old ones
292         {
293             return;
294         }
295         else
296         {
297             List<String> currentVirtualHosts = null;
298             if (_vhosts != null)
299             {
300                 currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
301             }
302             else
303             {
304                 currentVirtualHosts = new ArrayList<String>();
305             }
306 
307             for (int i = 0; i < virtualHosts.length; i++)
308             {
309                 String normVhost = normalizeHostname(virtualHosts[i]);
310                 if (!currentVirtualHosts.contains(normVhost))
311                 {
312                     currentVirtualHosts.add(normVhost);
313                 }
314             }
315             _vhosts = currentVirtualHosts.toArray(new String[0]);
316         }
317     }
318 
319     /* ------------------------------------------------------------ */
320     /**
321      * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
322      *
323      *  @param virtualHosts
324      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
325      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
326      */
327     public void removeVirtualHosts(String[] virtualHosts)
328     {
329         if (virtualHosts == null)
330         {
331             return; // do nothing
332         }
333         else if ( _vhosts == null || _vhosts.length == 0)
334         {
335             return; // do nothing
336         }
337         else
338         {
339             List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
340 
341             for (int i = 0; i < virtualHosts.length; i++)
342             {
343                 String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
344                 if (existingVirtualHosts.contains(toRemoveVirtualHost))
345                 {
346                     existingVirtualHosts.remove(toRemoveVirtualHost);
347                 }
348             }
349 
350             if (existingVirtualHosts.isEmpty())
351             {
352                 _vhosts = null; // if we ended up removing them all, just null out _vhosts
353             }
354             else
355             {
356                 _vhosts = existingVirtualHosts.toArray(new String[0]);
357             }
358         }
359     }
360 
361     /* ------------------------------------------------------------ */
362     /**
363      * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
364      * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
365      * matching virtual host name.
366      *
367      * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String
368      *         representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
369      */
370     public String[] getVirtualHosts()
371     {
372         return _vhosts;
373     }
374 
375     /* ------------------------------------------------------------ */
376     /**
377      * @return an array of connector names that this context will accept a request from.
378      */
379     public String[] getConnectorNames()
380     {
381         if (_connectors == null || _connectors.size() == 0)
382             return null;
383 
384         return _connectors.toArray(new String[_connectors.size()]);
385     }
386 
387     /* ------------------------------------------------------------ */
388     /**
389      * Set the names of accepted connectors.
390      *
391      * Names are either "host:port" or a specific configured name for a connector.
392      *
393      * @param connectors
394      *            If non null, an array of connector names that this context will accept a request from.
395      */
396     public void setConnectorNames(String[] connectors)
397     {
398         if (connectors == null || connectors.length == 0)
399             _connectors = null;
400         else
401             _connectors = new HashSet<String>(Arrays.asList(connectors));
402     }
403 
404     /* ------------------------------------------------------------ */
405     /*
406      * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
407      */
408     public Object getAttribute(String name)
409     {
410         return _attributes.getAttribute(name);
411     }
412 
413     /* ------------------------------------------------------------ */
414     /*
415      * @see javax.servlet.ServletContext#getAttributeNames()
416      */
417     @SuppressWarnings("unchecked")
418     public Enumeration getAttributeNames()
419     {
420         return AttributesMap.getAttributeNamesCopy(_attributes);
421     }
422 
423     /* ------------------------------------------------------------ */
424     /**
425      * @return Returns the attributes.
426      */
427     public Attributes getAttributes()
428     {
429         return _attributes;
430     }
431 
432     /* ------------------------------------------------------------ */
433     /**
434      * @return Returns the classLoader.
435      */
436     public ClassLoader getClassLoader()
437     {
438         return _classLoader;
439     }
440 
441     /* ------------------------------------------------------------ */
442     /**
443      * Make best effort to extract a file classpath from the context classloader
444      *
445      * @return Returns the classLoader.
446      */
447     public String getClassPath()
448     {
449         if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
450             return null;
451         URLClassLoader loader = (URLClassLoader)_classLoader;
452         URL[] urls = loader.getURLs();
453         StringBuilder classpath = new StringBuilder();
454         for (int i = 0; i < urls.length; i++)
455         {
456             try
457             {
458                 Resource resource = newResource(urls[i]);
459                 File file = resource.getFile();
460                 if (file != null && file.exists())
461                 {
462                     if (classpath.length() > 0)
463                         classpath.append(File.pathSeparatorChar);
464                     classpath.append(file.getAbsolutePath());
465                 }
466             }
467             catch (IOException e)
468             {
469                 LOG.debug(e);
470             }
471         }
472         if (classpath.length() == 0)
473             return null;
474         return classpath.toString();
475     }
476 
477     /* ------------------------------------------------------------ */
478     /**
479      * @return Returns the _contextPath.
480      */
481     public String getContextPath()
482     {
483         return _contextPath;
484     }
485 
486     /* ------------------------------------------------------------ */
487     /*
488      * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
489      */
490     public String getInitParameter(String name)
491     {
492         return _initParams.get(name);
493     }
494 
495     /* ------------------------------------------------------------ */
496     /*
497      */
498     public String setInitParameter(String name, String value)
499     {
500         return _initParams.put(name,value);
501     }
502 
503     /* ------------------------------------------------------------ */
504     /*
505      * @see javax.servlet.ServletContext#getInitParameterNames()
506      */
507     @SuppressWarnings("rawtypes")
508     public Enumeration getInitParameterNames()
509     {
510         return Collections.enumeration(_initParams.keySet());
511     }
512 
513     /* ------------------------------------------------------------ */
514     /**
515      * @return Returns the initParams.
516      */
517     public Map<String, String> getInitParams()
518     {
519         return _initParams;
520     }
521 
522     /* ------------------------------------------------------------ */
523     /*
524      * @see javax.servlet.ServletContext#getServletContextName()
525      */
526     public String getDisplayName()
527     {
528         return _displayName;
529     }
530 
531     /* ------------------------------------------------------------ */
532     public EventListener[] getEventListeners()
533     {
534         return _eventListeners;
535     }
536 
537     /* ------------------------------------------------------------ */
538     /**
539      * Set the context event listeners.
540      *
541      * @param eventListeners
542      *            the event listeners
543      * @see ServletContextListener
544      * @see ServletContextAttributeListener
545      * @see ServletRequestListener
546      * @see ServletRequestAttributeListener
547      */
548     public void setEventListeners(EventListener[] eventListeners)
549     {
550         _contextListeners = null;
551         _contextAttributeListeners = null;
552         _requestListeners = null;
553         _requestAttributeListeners = null;
554 
555         _eventListeners = eventListeners;
556 
557         for (int i = 0; eventListeners != null && i < eventListeners.length; i++)
558         {
559             EventListener listener = _eventListeners[i];
560 
561             if (listener instanceof ServletContextListener)
562                 _contextListeners = LazyList.add(_contextListeners,listener);
563 
564             if (listener instanceof ServletContextAttributeListener)
565                 _contextAttributeListeners = LazyList.add(_contextAttributeListeners,listener);
566 
567             if (listener instanceof ServletRequestListener)
568                 _requestListeners = LazyList.add(_requestListeners,listener);
569 
570             if (listener instanceof ServletRequestAttributeListener)
571                 _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
572         }
573     }
574 
575     /* ------------------------------------------------------------ */
576     /**
577      * Add a context event listeners.
578      *
579      * @see ServletContextListener
580      * @see ServletContextAttributeListener
581      * @see ServletRequestListener
582      * @see ServletRequestAttributeListener
583      */
584     public void addEventListener(EventListener listener)
585     {
586         setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(),listener,EventListener.class));
587     }
588 
589     /* ------------------------------------------------------------ */
590     /**
591      * @return true if this context is accepting new requests
592      */
593     public boolean isShutdown()
594     {
595         synchronized (this)
596         {
597             return !_shutdown;
598         }
599     }
600 
601     /* ------------------------------------------------------------ */
602     /**
603      * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
604      * requests can complete, but no new requests are accepted.
605      *
606      * @param shutdown
607      *            true if this context is (not?) accepting new requests
608      */
609     public void setShutdown(boolean shutdown)
610     {
611         synchronized (this)
612         {
613             _shutdown = shutdown;
614             _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
615         }
616     }
617 
618     /* ------------------------------------------------------------ */
619     /**
620      * @return false if this context is unavailable (sends 503)
621      */
622     public boolean isAvailable()
623     {
624         synchronized (this)
625         {
626             return _available;
627         }
628     }
629 
630     /* ------------------------------------------------------------ */
631     /**
632      * Set Available status.
633      */
634     public void setAvailable(boolean available)
635     {
636         synchronized (this)
637         {
638             _available = available;
639             _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
640         }
641     }
642 
643     /* ------------------------------------------------------------ */
644     public Logger getLogger()
645     {
646         return _logger;
647     }
648 
649     /* ------------------------------------------------------------ */
650     public void setLogger(Logger logger)
651     {
652         _logger = logger;
653     }
654 
655     /* ------------------------------------------------------------ */
656     /*
657      * @see org.eclipse.thread.AbstractLifeCycle#doStart()
658      */
659     @Override
660     protected void doStart() throws Exception
661     {
662         _availability = __STOPPED;
663 
664         if (_contextPath == null)
665             throw new IllegalStateException("Null contextPath");
666 
667         _logger = Log.getLogger(getDisplayName() == null?getContextPath():getDisplayName());
668         ClassLoader old_classloader = null;
669         Thread current_thread = null;
670         Context old_context = null;
671 
672         try
673         {
674             // Set the classloader
675             if (_classLoader != null)
676             {
677                 current_thread = Thread.currentThread();
678                 old_classloader = current_thread.getContextClassLoader();
679                 current_thread.setContextClassLoader(_classLoader);
680             }
681 
682             if (_mimeTypes == null)
683                 _mimeTypes = new MimeTypes();
684 
685             old_context = __context.get();
686             __context.set(_scontext);
687 
688             // defers the calling of super.doStart()
689             startContext();
690 
691             synchronized(this)
692             {
693                 _availability = _shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE;
694             }
695         }
696         finally
697         {
698             __context.set(old_context);
699 
700             // reset the classloader
701             if (_classLoader != null)
702             {
703                 current_thread.setContextClassLoader(old_classloader);
704             }
705 
706         }
707     }
708 
709     /* ------------------------------------------------------------ */
710     /**
711      * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
712      * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
713      *
714      * @see org.eclipse.jetty.server.handler.ContextHandler.Context
715      */
716     protected void startContext() throws Exception
717     {
718         String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
719         if (managedAttributes != null)
720         {
721             _managedAttributes = new HashMap<String, Object>();
722             String[] attributes = managedAttributes.split(",");
723             for (String attribute : attributes)
724                 _managedAttributes.put(attribute,null);
725 
726             Enumeration e = _scontext.getAttributeNames();
727             while (e.hasMoreElements())
728             {
729                 String name = (String)e.nextElement();
730                 Object value = _scontext.getAttribute(name);
731                 checkManagedAttribute(name,value);
732             }
733         }
734 
735         super.doStart();
736 
737         if (_errorHandler != null)
738             _errorHandler.start();
739 
740         // Context listeners
741         if (_contextListeners != null)
742         {
743             ServletContextEvent event = new ServletContextEvent(_scontext);
744             for (int i = 0; i < LazyList.size(_contextListeners); i++)
745             {
746                 ((ServletContextListener)LazyList.get(_contextListeners,i)).contextInitialized(event);
747             }
748         }
749 
750         LOG.info("started {}",this);
751     }
752 
753     /* ------------------------------------------------------------ */
754     /*
755      * @see org.eclipse.thread.AbstractLifeCycle#doStop()
756      */
757     @Override
758     protected void doStop() throws Exception
759     {
760         _availability = __STOPPED;
761 
762         ClassLoader old_classloader = null;
763         Thread current_thread = null;
764 
765         Context old_context = __context.get();
766         __context.set(_scontext);
767         try
768         {
769             // Set the classloader
770             if (_classLoader != null)
771             {
772                 current_thread = Thread.currentThread();
773                 old_classloader = current_thread.getContextClassLoader();
774                 current_thread.setContextClassLoader(_classLoader);
775             }
776 
777             super.doStop();
778 
779             // Context listeners
780             if (_contextListeners != null)
781             {
782                 ServletContextEvent event = new ServletContextEvent(_scontext);
783                 for (int i = LazyList.size(_contextListeners); i-- > 0;)
784                 {
785                     ((ServletContextListener)LazyList.get(_contextListeners,i)).contextDestroyed(event);
786                 }
787             }
788 
789             if (_errorHandler != null)
790                 _errorHandler.stop();
791 
792             Enumeration e = _scontext.getAttributeNames();
793             while (e.hasMoreElements())
794             {
795                 String name = (String)e.nextElement();
796                 checkManagedAttribute(name,null);
797             }
798         }
799         finally
800         {
801             LOG.info("stopped {}",this);
802             __context.set(old_context);
803             // reset the classloader
804             if (_classLoader != null)
805                 current_thread.setContextClassLoader(old_classloader);
806         }
807 
808         _contextAttributes.clearAttributes();
809     }
810 
811     /* ------------------------------------------------------------ */
812     /*
813      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
814      */
815     public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException, ServletException
816     {
817         DispatcherType dispatch = baseRequest.getDispatcherType();
818 
819         switch (_availability)
820         {
821             case __STOPPED:
822             case __SHUTDOWN:
823                 return false;
824             case __UNAVAILABLE:
825                 baseRequest.setHandled(true);
826                 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
827                 return false;
828             default:
829                 if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
830                     return false;
831         }
832 
833         // Check the vhosts
834         if (_vhosts != null && _vhosts.length > 0)
835         {
836             String vhost = normalizeHostname(baseRequest.getServerName());
837 
838             boolean match = false;
839 
840             // TODO non-linear lookup
841             for (int i = 0; !match && i < _vhosts.length; i++)
842             {
843                 String contextVhost = _vhosts[i];
844                 if (contextVhost == null)
845                     continue;
846                 if (contextVhost.startsWith("*."))
847                 {
848                     // wildcard only at the beginning, and only for one additional subdomain level
849                     match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
850                 }
851                 else
852                     match = contextVhost.equalsIgnoreCase(vhost);
853             }
854             if (!match)
855                 return false;
856         }
857 
858         // Check the connector
859         if (_connectors != null && _connectors.size() > 0)
860         {
861             String connector = AbstractHttpConnection.getCurrentConnection().getConnector().getName();
862             if (connector == null || !_connectors.contains(connector))
863                 return false;
864         }
865 
866         // Are we not the root context?
867         if (_contextPath.length() > 1)
868         {
869             // reject requests that are not for us
870             if (!target.startsWith(_contextPath))
871                 return false;
872             if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
873                 return false;
874 
875             // redirect null path infos
876             if (!_allowNullPathInfo && _contextPath.length() == target.length())
877             {
878                 // context request must end with /
879                 baseRequest.setHandled(true);
880                 if (baseRequest.getQueryString() != null)
881                     response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
882                 else
883                     response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
884                 return false;
885             }
886         }
887 
888         return true;
889     }
890 
891     /* ------------------------------------------------------------ */
892     /**
893      * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
894      *      javax.servlet.http.HttpServletResponse)
895      */
896     @Override
897     public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
898     {
899         if (LOG.isDebugEnabled())
900             LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);
901 
902         Context old_context = null;
903         String old_context_path = null;
904         String old_servlet_path = null;
905         String old_path_info = null;
906         ClassLoader old_classloader = null;
907         Thread current_thread = null;
908         String pathInfo = target;
909 
910         DispatcherType dispatch = baseRequest.getDispatcherType();
911 
912         old_context = baseRequest.getContext();
913 
914         // Are we already in this context?
915         if (old_context != _scontext)
916         {
917             // check the target.
918             if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch))
919             {
920                 if (_compactPath)
921                     target = URIUtil.compactPath(target);
922                 if (!checkContext(target,baseRequest,response))
923                     return;
924 
925                 if (target.length() > _contextPath.length())
926                 {
927                     if (_contextPath.length() > 1)
928                         target = target.substring(_contextPath.length());
929                     pathInfo = target;
930                 }
931                 else if (_contextPath.length() == 1)
932                 {
933                     target = URIUtil.SLASH;
934                     pathInfo = URIUtil.SLASH;
935                 }
936                 else
937                 {
938                     target = URIUtil.SLASH;
939                     pathInfo = null;
940                 }
941             }
942 
943             // Set the classloader
944             if (_classLoader != null)
945             {
946                 current_thread = Thread.currentThread();
947                 old_classloader = current_thread.getContextClassLoader();
948                 current_thread.setContextClassLoader(_classLoader);
949             }
950         }
951 
952         try
953         {
954             old_context_path = baseRequest.getContextPath();
955             old_servlet_path = baseRequest.getServletPath();
956             old_path_info = baseRequest.getPathInfo();
957 
958             // Update the paths
959             baseRequest.setContext(_scontext);
960             __context.set(_scontext);
961             if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
962             {
963                 if (_contextPath.length() == 1)
964                     baseRequest.setContextPath("");
965                 else
966                     baseRequest.setContextPath(_contextPath);
967                 baseRequest.setServletPath(null);
968                 baseRequest.setPathInfo(pathInfo);
969             }
970 
971             if (LOG.isDebugEnabled())
972                 LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
973 
974             // start manual inline of nextScope(target,baseRequest,request,response);
975             if (never())
976                 nextScope(target,baseRequest,request,response);
977             else if (_nextScope != null)
978                 _nextScope.doScope(target,baseRequest,request,response);
979             else if (_outerScope != null)
980                 _outerScope.doHandle(target,baseRequest,request,response);
981             else
982                 doHandle(target,baseRequest,request,response);
983             // end manual inline (pathentic attempt to reduce stack depth)
984         }
985         finally
986         {
987             if (old_context != _scontext)
988             {
989                 // reset the classloader
990                 if (_classLoader != null)
991                 {
992                     current_thread.setContextClassLoader(old_classloader);
993                 }
994 
995                 // reset the context and servlet path.
996                 baseRequest.setContext(old_context);
997                 __context.set(old_context);
998                 baseRequest.setContextPath(old_context_path);
999                 baseRequest.setServletPath(old_servlet_path);
1000                 baseRequest.setPathInfo(old_path_info);
1001             }
1002         }
1003     }
1004 
1005     /* ------------------------------------------------------------ */
1006     /**
1007      * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
1008      *      javax.servlet.http.HttpServletResponse)
1009      */
1010     @Override
1011     public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
1012     {
1013         final DispatcherType dispatch = baseRequest.getDispatcherType();
1014         final boolean new_context = baseRequest.takeNewContext();
1015         try
1016         {
1017             if (new_context)
1018             {
1019                 // Handle the REALLY SILLY request events!
1020                 if (_requestAttributeListeners != null)
1021                 {
1022                     final int s = LazyList.size(_requestAttributeListeners);
1023                     for (int i = 0; i < s; i++)
1024                         baseRequest.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
1025                 }
1026 
1027                 if (_requestListeners != null)
1028                 {
1029                     final int s = LazyList.size(_requestListeners);
1030                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
1031                     for (int i = 0; i < s; i++)
1032                         ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestInitialized(sre);
1033                 }
1034             }
1035 
1036             if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
1037                 throw new HttpException(HttpServletResponse.SC_NOT_FOUND);
1038 
1039             // start manual inline of nextHandle(target,baseRequest,request,response);
1040             // noinspection ConstantIfStatement
1041             if (never())
1042                 nextHandle(target,baseRequest,request,response);
1043             else if (_nextScope != null && _nextScope == _handler)
1044                 _nextScope.doHandle(target,baseRequest,request,response);
1045             else if (_handler != null)
1046                 _handler.handle(target,baseRequest,request,response);
1047             // end manual inline
1048         }
1049         catch (HttpException e)
1050         {
1051             LOG.debug(e);
1052             baseRequest.setHandled(true);
1053             response.sendError(e.getStatus(),e.getReason());
1054         }
1055         finally
1056         {
1057             // Handle more REALLY SILLY request events!
1058             if (new_context)
1059             {
1060                 if (_requestListeners != null)
1061                 {
1062                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
1063                     for (int i = LazyList.size(_requestListeners); i-- > 0;)
1064                         ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestDestroyed(sre);
1065                 }
1066 
1067                 if (_requestAttributeListeners != null)
1068                 {
1069                     for (int i = LazyList.size(_requestAttributeListeners); i-- > 0;)
1070                         baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
1071                 }
1072             }
1073         }
1074     }
1075 
1076     /* ------------------------------------------------------------ */
1077     /*
1078      * Handle a runnable in this context
1079      */
1080     public void handle(Runnable runnable)
1081     {
1082         ClassLoader old_classloader = null;
1083         Thread current_thread = null;
1084         Context old_context = null;
1085         try
1086         {
1087             old_context = __context.get();
1088             __context.set(_scontext);
1089 
1090             // Set the classloader
1091             if (_classLoader != null)
1092             {
1093                 current_thread = Thread.currentThread();
1094                 old_classloader = current_thread.getContextClassLoader();
1095                 current_thread.setContextClassLoader(_classLoader);
1096             }
1097 
1098             runnable.run();
1099         }
1100         finally
1101         {
1102             __context.set(old_context);
1103             if (old_classloader != null)
1104             {
1105                 current_thread.setContextClassLoader(old_classloader);
1106             }
1107         }
1108     }
1109 
1110     /* ------------------------------------------------------------ */
1111     /**
1112      * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
1113      * the target is protected, 404 is returned. 
1114      */
1115     /* ------------------------------------------------------------ */
1116     public boolean isProtectedTarget(String target)
1117     {
1118         if (target == null || _protectedTargets == null)
1119             return false;
1120         
1121         while (target.startsWith("//"))
1122             target=URIUtil.compactPath(target);
1123         
1124         boolean isProtected = false;
1125         int i=0;
1126         while (!isProtected && i<_protectedTargets.length)
1127         {
1128             isProtected = StringUtil.startsWithIgnoreCase(target, _protectedTargets[i++]);
1129         }
1130         return isProtected;
1131     }
1132     
1133     
1134     public void setProtectedTargets (String[] targets)
1135     {
1136         if (targets == null)
1137         {
1138             _protectedTargets = null;
1139             return;
1140         }
1141         
1142         _protectedTargets = new String[targets.length];
1143         System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
1144     }
1145     
1146     public String[] getProtectedTargets ()
1147     {
1148         if (_protectedTargets == null)
1149             return null;
1150         
1151         String[] tmp = new String[_protectedTargets.length];
1152         System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
1153         return tmp;
1154     }
1155     
1156 
1157     /* ------------------------------------------------------------ */
1158     /*
1159      * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
1160      */
1161     public void removeAttribute(String name)
1162     {
1163         checkManagedAttribute(name,null);
1164         _attributes.removeAttribute(name);
1165     }
1166 
1167     /* ------------------------------------------------------------ */
1168     /*
1169      * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
1170      * a context. No attribute listener events are triggered by this API.
1171      *
1172      * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
1173      */
1174     public void setAttribute(String name, Object value)
1175     {
1176         checkManagedAttribute(name,value);
1177         _attributes.setAttribute(name,value);
1178     }
1179 
1180     /* ------------------------------------------------------------ */
1181     /**
1182      * @param attributes
1183      *            The attributes to set.
1184      */
1185     public void setAttributes(Attributes attributes)
1186     {
1187         _attributes.clearAttributes();
1188         _attributes.addAll(attributes);
1189         Enumeration e = _attributes.getAttributeNames();
1190         while (e.hasMoreElements())
1191         {
1192             String name = (String)e.nextElement();
1193             checkManagedAttribute(name,attributes.getAttribute(name));
1194         }
1195     }
1196 
1197     /* ------------------------------------------------------------ */
1198     public void clearAttributes()
1199     {
1200         Enumeration e = _attributes.getAttributeNames();
1201         while (e.hasMoreElements())
1202         {
1203             String name = (String)e.nextElement();
1204             checkManagedAttribute(name,null);
1205         }
1206         _attributes.clearAttributes();
1207     }
1208 
1209     /* ------------------------------------------------------------ */
1210     public void checkManagedAttribute(String name, Object value)
1211     {
1212         if (_managedAttributes != null && _managedAttributes.containsKey(name))
1213         {
1214             setManagedAttribute(name,value);
1215         }
1216     }
1217 
1218     /* ------------------------------------------------------------ */
1219     public void setManagedAttribute(String name, Object value)
1220     {
1221         Object old = _managedAttributes.put(name,value);
1222         getServer().getContainer().update(this,old,value,name,true);
1223     }
1224 
1225     /* ------------------------------------------------------------ */
1226     /**
1227      * @param classLoader
1228      *            The classLoader to set.
1229      */
1230     public void setClassLoader(ClassLoader classLoader)
1231     {
1232         _classLoader = classLoader;
1233     }
1234 
1235     /* ------------------------------------------------------------ */
1236     /**
1237      * @param contextPath
1238      *            The _contextPath to set.
1239      */
1240     public void setContextPath(String contextPath)
1241     {
1242         if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/"))
1243             throw new IllegalArgumentException("ends with /");
1244         _contextPath = contextPath;
1245 
1246         if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
1247         {
1248             Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
1249             for (int h = 0; contextCollections != null && h < contextCollections.length; h++)
1250                 ((ContextHandlerCollection)contextCollections[h]).mapContexts();
1251         }
1252     }
1253 
1254     /* ------------------------------------------------------------ */
1255     /**
1256      * @param servletContextName
1257      *            The servletContextName to set.
1258      */
1259     public void setDisplayName(String servletContextName)
1260     {
1261         _displayName = servletContextName;
1262     }
1263 
1264     /* ------------------------------------------------------------ */
1265     /**
1266      * @return Returns the resourceBase.
1267      */
1268     public Resource getBaseResource()
1269     {
1270         if (_baseResource == null)
1271             return null;
1272         return _baseResource;
1273     }
1274 
1275     /* ------------------------------------------------------------ */
1276     /**
1277      * @return Returns the base resource as a string.
1278      */
1279     public String getResourceBase()
1280     {
1281         if (_baseResource == null)
1282             return null;
1283         return _baseResource.toString();
1284     }
1285 
1286     /* ------------------------------------------------------------ */
1287     /**
1288      * @param base
1289      *            The resourceBase to set.
1290      */
1291     public void setBaseResource(Resource base)
1292     {
1293         _baseResource = base;
1294     }
1295 
1296     /* ------------------------------------------------------------ */
1297     /**
1298      * @param resourceBase
1299      *            The base resource as a string.
1300      */
1301     public void setResourceBase(String resourceBase)
1302     {
1303         try
1304         {
1305             setBaseResource(newResource(resourceBase));
1306         }
1307         catch (Exception e)
1308         {
1309             LOG.warn(e.toString());
1310             LOG.debug(e);
1311             throw new IllegalArgumentException(resourceBase);
1312         }
1313     }
1314 
1315     /* ------------------------------------------------------------ */
1316     /**
1317      * @return True if aliases are allowed
1318      */
1319     public boolean isAliases()
1320     {
1321         return _aliases;
1322     }
1323 
1324     /* ------------------------------------------------------------ */
1325     /**
1326      * @param aliases
1327      *            aliases are allowed
1328      */
1329     public void setAliases(boolean aliases)
1330     {
1331         _aliases = aliases;
1332     }
1333 
1334     /* ------------------------------------------------------------ */
1335     /**
1336      * @return Returns the mimeTypes.
1337      */
1338     public MimeTypes getMimeTypes()
1339     {
1340         if (_mimeTypes == null)
1341             _mimeTypes = new MimeTypes();
1342         return _mimeTypes;
1343     }
1344 
1345     /* ------------------------------------------------------------ */
1346     /**
1347      * @param mimeTypes
1348      *            The mimeTypes to set.
1349      */
1350     public void setMimeTypes(MimeTypes mimeTypes)
1351     {
1352         _mimeTypes = mimeTypes;
1353     }
1354 
1355     /* ------------------------------------------------------------ */
1356     /**
1357      */
1358     public void setWelcomeFiles(String[] files)
1359     {
1360         _welcomeFiles = files;
1361     }
1362 
1363     /* ------------------------------------------------------------ */
1364     /**
1365      * @return The names of the files which the server should consider to be welcome files in this context.
1366      * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
1367      * @see #setWelcomeFiles
1368      */
1369     public String[] getWelcomeFiles()
1370     {
1371         return _welcomeFiles;
1372     }
1373 
1374     /* ------------------------------------------------------------ */
1375     /**
1376      * @return Returns the errorHandler.
1377      */
1378     public ErrorHandler getErrorHandler()
1379     {
1380         return _errorHandler;
1381     }
1382 
1383     /* ------------------------------------------------------------ */
1384     /**
1385      * @param errorHandler
1386      *            The errorHandler to set.
1387      */
1388     public void setErrorHandler(ErrorHandler errorHandler)
1389     {
1390         if (errorHandler != null)
1391             errorHandler.setServer(getServer());
1392         if (getServer() != null)
1393             getServer().getContainer().update(this,_errorHandler,errorHandler,"errorHandler",true);
1394         _errorHandler = errorHandler;
1395     }
1396 
1397     /* ------------------------------------------------------------ */
1398     public int getMaxFormContentSize()
1399     {
1400         return _maxFormContentSize;
1401     }
1402 
1403     /* ------------------------------------------------------------ */
1404     /**
1405      * Set the maximum size of a form post, to protect against DOS attacks from large forms.
1406      * @param maxSize
1407      */
1408     public void setMaxFormContentSize(int maxSize)
1409     {
1410         _maxFormContentSize = maxSize;
1411     }
1412 
1413     /* ------------------------------------------------------------ */
1414     public int getMaxFormKeys()
1415     {
1416         return _maxFormKeys;
1417     }
1418 
1419     /* ------------------------------------------------------------ */
1420     /**
1421      * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
1422      * @param max
1423      */
1424     public void setMaxFormKeys(int max)
1425     {
1426         _maxFormKeys = max;
1427     }
1428 
1429     /* ------------------------------------------------------------ */
1430     /**
1431      * @return True if URLs are compacted to replace multiple '/'s with a single '/'
1432      */
1433     public boolean isCompactPath()
1434     {
1435         return _compactPath;
1436     }
1437 
1438     /* ------------------------------------------------------------ */
1439     /**
1440      * @param compactPath
1441      *            True if URLs are compacted to replace multiple '/'s with a single '/'
1442      */
1443     public void setCompactPath(boolean compactPath)
1444     {
1445         _compactPath = compactPath;
1446     }
1447 
1448     /* ------------------------------------------------------------ */
1449     @Override
1450     public String toString()
1451     {
1452         String[] vhosts = getVirtualHosts();
1453 
1454         StringBuilder b = new StringBuilder();
1455 
1456         Package pkg = getClass().getPackage();
1457         if (pkg != null)
1458         {
1459             String p = pkg.getName();
1460             if (p != null && p.length() > 0)
1461             {
1462                 String[] ss = p.split("\\.");
1463                 for (String s : ss)
1464                     b.append(s.charAt(0)).append('.');
1465             }
1466         }
1467         b.append(getClass().getSimpleName());
1468         b.append('{').append(getContextPath()).append(',').append(getBaseResource());
1469 
1470         if (vhosts != null && vhosts.length > 0)
1471             b.append(',').append(vhosts[0]);
1472         b.append('}');
1473 
1474         return b.toString();
1475     }
1476 
1477     /* ------------------------------------------------------------ */
1478     public synchronized Class<?> loadClass(String className) throws ClassNotFoundException
1479     {
1480         if (className == null)
1481             return null;
1482 
1483         if (_classLoader == null)
1484             return Loader.loadClass(this.getClass(),className);
1485 
1486         return _classLoader.loadClass(className);
1487     }
1488 
1489     /* ------------------------------------------------------------ */
1490     public void addLocaleEncoding(String locale, String encoding)
1491     {
1492         if (_localeEncodingMap == null)
1493             _localeEncodingMap = new HashMap<String, String>();
1494         _localeEncodingMap.put(locale,encoding);
1495     }
1496 
1497     public String getLocaleEncoding(String locale)
1498     {
1499         if (_localeEncodingMap == null)
1500             return null;
1501         String encoding = _localeEncodingMap.get(locale);
1502         return encoding;
1503     }
1504 
1505     /* ------------------------------------------------------------ */
1506     /**
1507      * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale
1508      * language is looked up.
1509      *
1510      * @param locale
1511      *            a <code>Locale</code> value
1512      * @return a <code>String</code> representing the character encoding for the locale or null if none found.
1513      */
1514     public String getLocaleEncoding(Locale locale)
1515     {
1516         if (_localeEncodingMap == null)
1517             return null;
1518         String encoding = _localeEncodingMap.get(locale.toString());
1519         if (encoding == null)
1520             encoding = _localeEncodingMap.get(locale.getLanguage());
1521         return encoding;
1522     }
1523 
1524     /* ------------------------------------------------------------ */
1525     /*
1526      */
1527     public Resource getResource(String path) throws MalformedURLException
1528     {
1529         if (path == null || !path.startsWith(URIUtil.SLASH))
1530             throw new MalformedURLException(path);
1531 
1532         if (_baseResource == null)
1533             return null;
1534 
1535         try
1536         {
1537             path = URIUtil.canonicalPath(path);
1538             Resource resource = _baseResource.addPath(path);
1539 
1540             // Is the resource aliased?
1541             if (!_aliases && resource.getAlias() != null)
1542             {
1543                 if (LOG.isDebugEnabled())
1544                     LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
1545 
1546                 // alias checks
1547                 for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
1548                 {
1549                     AliasCheck check = i.next();
1550                     if (check.check(path,resource))
1551                     {
1552                         if (LOG.isDebugEnabled())
1553                             LOG.debug("Aliased resource: " + resource + " approved by " + check);
1554                         return resource;
1555                     }
1556                 }
1557                 return null;
1558             }
1559 
1560             return resource;
1561         }
1562         catch (Exception e)
1563         {
1564             LOG.ignore(e);
1565         }
1566 
1567         return null;
1568     }
1569 
1570     /* ------------------------------------------------------------ */
1571     /**
1572      * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
1573      */
1574     public Resource newResource(URL url) throws IOException
1575     {
1576         return Resource.newResource(url);
1577     }
1578 
1579     /* ------------------------------------------------------------ */
1580     /**
1581      * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
1582      *
1583      * @param urlOrPath
1584      *            The URL or path to convert
1585      * @return The Resource for the URL/path
1586      * @throws IOException
1587      *             The Resource could not be created.
1588      */
1589     public Resource newResource(String urlOrPath) throws IOException
1590     {
1591         return Resource.newResource(urlOrPath);
1592     }
1593 
1594     /* ------------------------------------------------------------ */
1595     /*
1596      */
1597     public Set<String> getResourcePaths(String path)
1598     {
1599         try
1600         {
1601             path = URIUtil.canonicalPath(path);
1602             Resource resource = getResource(path);
1603 
1604             if (resource != null && resource.exists())
1605             {
1606                 if (!path.endsWith(URIUtil.SLASH))
1607                     path = path + URIUtil.SLASH;
1608 
1609                 String[] l = resource.list();
1610                 if (l != null)
1611                 {
1612                     HashSet<String> set = new HashSet<String>();
1613                     for (int i = 0; i < l.length; i++)
1614                         set.add(path + l[i]);
1615                     return set;
1616                 }
1617             }
1618         }
1619         catch (Exception e)
1620         {
1621             LOG.ignore(e);
1622         }
1623         return Collections.emptySet();
1624     }
1625 
1626     /* ------------------------------------------------------------ */
1627     private String normalizeHostname(String host)
1628     {
1629         if (host == null)
1630             return null;
1631 
1632         if (host.endsWith("."))
1633             return host.substring(0,host.length() - 1);
1634 
1635         return host;
1636     }
1637     
1638     /* ------------------------------------------------------------ */
1639     /**
1640      * Add an AliasCheck instance to possibly permit aliased resources
1641      * @param check The alias checker
1642      */
1643     public void addAliasCheck(AliasCheck check)
1644     {
1645         _aliasChecks.add(check);
1646     }
1647     
1648     /* ------------------------------------------------------------ */
1649     /**
1650      * @return Mutable list of Alias checks
1651      */
1652     public List<AliasCheck> getAliasChecks()
1653     {
1654         return _aliasChecks;
1655     }
1656 
1657     /* ------------------------------------------------------------ */
1658     /**
1659      * Context.
1660      * <p>
1661      * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
1662      * </p>
1663      *
1664      *
1665      */
1666     public class Context implements ServletContext
1667     {
1668         /* ------------------------------------------------------------ */
1669         protected Context()
1670         {
1671         }
1672 
1673         /* ------------------------------------------------------------ */
1674         public ContextHandler getContextHandler()
1675         {
1676             // TODO reduce visibility of this method
1677             return ContextHandler.this;
1678         }
1679 
1680         /* ------------------------------------------------------------ */
1681         /*
1682          * @see javax.servlet.ServletContext#getContext(java.lang.String)
1683          */
1684         public ServletContext getContext(String uripath)
1685         {
1686             List<ContextHandler> contexts = new ArrayList<ContextHandler>();
1687             Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
1688             String matched_path = null;
1689 
1690             for (Handler handler : handlers)
1691             {
1692                 if (handler == null)
1693                     continue;
1694                 ContextHandler ch = (ContextHandler)handler;
1695                 String context_path = ch.getContextPath();
1696 
1697                 if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
1698                         || "/".equals(context_path))
1699                 {
1700                     // look first for vhost matching context only
1701                     if (getVirtualHosts() != null && getVirtualHosts().length > 0)
1702                     {
1703                         if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0)
1704                         {
1705                             for (String h1 : getVirtualHosts())
1706                                 for (String h2 : ch.getVirtualHosts())
1707                                     if (h1.equals(h2))
1708                                     {
1709                                         if (matched_path == null || context_path.length() > matched_path.length())
1710                                         {
1711                                             contexts.clear();
1712                                             matched_path = context_path;
1713                                         }
1714 
1715                                         if (matched_path.equals(context_path))
1716                                             contexts.add(ch);
1717                                     }
1718                         }
1719                     }
1720                     else
1721                     {
1722                         if (matched_path == null || context_path.length() > matched_path.length())
1723                         {
1724                             contexts.clear();
1725                             matched_path = context_path;
1726                         }
1727 
1728                         if (matched_path.equals(context_path))
1729                             contexts.add(ch);
1730                     }
1731                 }
1732             }
1733 
1734             if (contexts.size() > 0)
1735                 return contexts.get(0)._scontext;
1736 
1737             // try again ignoring virtual hosts
1738             matched_path = null;
1739             for (Handler handler : handlers)
1740             {
1741                 if (handler == null)
1742                     continue;
1743                 ContextHandler ch = (ContextHandler)handler;
1744                 String context_path = ch.getContextPath();
1745 
1746                 if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
1747                         || "/".equals(context_path))
1748                 {
1749                     if (matched_path == null || context_path.length() > matched_path.length())
1750                     {
1751                         contexts.clear();
1752                         matched_path = context_path;
1753                     }
1754 
1755                     if (matched_path.equals(context_path))
1756                         contexts.add(ch);
1757                 }
1758             }
1759 
1760             if (contexts.size() > 0)
1761                 return contexts.get(0)._scontext;
1762             return null;
1763         }
1764 
1765         /* ------------------------------------------------------------ */
1766         /*
1767          * @see javax.servlet.ServletContext#getMajorVersion()
1768          */
1769         public int getMajorVersion()
1770         {
1771             return 2;
1772         }
1773 
1774         /* ------------------------------------------------------------ */
1775         /*
1776          * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
1777          */
1778         public String getMimeType(String file)
1779         {
1780             if (_mimeTypes == null)
1781                 return null;
1782             Buffer mime = _mimeTypes.getMimeByExtension(file);
1783             if (mime != null)
1784                 return mime.toString();
1785             return null;
1786         }
1787 
1788         /* ------------------------------------------------------------ */
1789         /*
1790          * @see javax.servlet.ServletContext#getMinorVersion()
1791          */
1792         public int getMinorVersion()
1793         {
1794             return 5;
1795         }
1796 
1797         /* ------------------------------------------------------------ */
1798         /*
1799          * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
1800          */
1801         public RequestDispatcher getNamedDispatcher(String name)
1802         {
1803             return null;
1804         }
1805 
1806         /* ------------------------------------------------------------ */
1807         /*
1808          * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
1809          */
1810         public RequestDispatcher getRequestDispatcher(String uriInContext)
1811         {
1812             if (uriInContext == null)
1813                 return null;
1814 
1815             if (!uriInContext.startsWith("/"))
1816                 return null;
1817 
1818             try
1819             {
1820                 String query = null;
1821                 int q = 0;
1822                 if ((q = uriInContext.indexOf('?')) > 0)
1823                 {
1824                     query = uriInContext.substring(q + 1);
1825                     uriInContext = uriInContext.substring(0,q);
1826                 }
1827                 // if ((q = uriInContext.indexOf(';')) > 0)
1828                 //     uriInContext = uriInContext.substring(0,q);
1829 
1830                 String pathInContext = URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
1831                 String uri = URIUtil.addPaths(getContextPath(),uriInContext);
1832                 ContextHandler context = ContextHandler.this;
1833                 return new Dispatcher(context,uri,pathInContext,query);
1834             }
1835             catch (Exception e)
1836             {
1837                 LOG.ignore(e);
1838             }
1839             return null;
1840         }
1841 
1842         /* ------------------------------------------------------------ */
1843         /*
1844          * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
1845          */
1846         public String getRealPath(String path)
1847         {
1848             if (path == null)
1849                 return null;
1850             if (path.length() == 0)
1851                 path = URIUtil.SLASH;
1852             else if (path.charAt(0) != '/')
1853                 path = URIUtil.SLASH + path;
1854 
1855             try
1856             {
1857                 Resource resource = ContextHandler.this.getResource(path);
1858                 if (resource != null)
1859                 {
1860                     File file = resource.getFile();
1861                     if (file != null)
1862                         return file.getCanonicalPath();
1863                 }
1864             }
1865             catch (Exception e)
1866             {
1867                 LOG.ignore(e);
1868             }
1869 
1870             return null;
1871         }
1872 
1873         /* ------------------------------------------------------------ */
1874         public URL getResource(String path) throws MalformedURLException
1875         {
1876             Resource resource = ContextHandler.this.getResource(path);
1877             if (resource != null && resource.exists())
1878                 return resource.getURL();
1879             return null;
1880         }
1881 
1882         /* ------------------------------------------------------------ */
1883         /*
1884          * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
1885          */
1886         public InputStream getResourceAsStream(String path)
1887         {
1888             try
1889             {
1890                 URL url = getResource(path);
1891                 if (url == null)
1892                     return null;
1893                 Resource r = Resource.newResource(url);
1894                 return r.getInputStream();
1895             }
1896             catch (Exception e)
1897             {
1898                 LOG.ignore(e);
1899                 return null;
1900             }
1901         }
1902 
1903         /* ------------------------------------------------------------ */
1904         /*
1905          * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
1906          */
1907         public Set getResourcePaths(String path)
1908         {
1909             return ContextHandler.this.getResourcePaths(path);
1910         }
1911 
1912         /* ------------------------------------------------------------ */
1913         /*
1914          * @see javax.servlet.ServletContext#getServerInfo()
1915          */
1916         public String getServerInfo()
1917         {
1918             return "jetty/" + Server.getVersion();
1919         }
1920 
1921         /* ------------------------------------------------------------ */
1922         /*
1923          * @see javax.servlet.ServletContext#getServlet(java.lang.String)
1924          */
1925         public Servlet getServlet(String name) throws ServletException
1926         {
1927             return null;
1928         }
1929 
1930         /* ------------------------------------------------------------ */
1931         /*
1932          * @see javax.servlet.ServletContext#getServletNames()
1933          */
1934         @SuppressWarnings("unchecked")
1935         public Enumeration getServletNames()
1936         {
1937             return Collections.enumeration(Collections.EMPTY_LIST);
1938         }
1939 
1940         /* ------------------------------------------------------------ */
1941         /*
1942          * @see javax.servlet.ServletContext#getServlets()
1943          */
1944         @SuppressWarnings("unchecked")
1945         public Enumeration getServlets()
1946         {
1947             return Collections.enumeration(Collections.EMPTY_LIST);
1948         }
1949 
1950         /* ------------------------------------------------------------ */
1951         /*
1952          * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
1953          */
1954         public void log(Exception exception, String msg)
1955         {
1956             _logger.warn(msg,exception);
1957         }
1958 
1959         /* ------------------------------------------------------------ */
1960         /*
1961          * @see javax.servlet.ServletContext#log(java.lang.String)
1962          */
1963         public void log(String msg)
1964         {
1965             _logger.info(msg);
1966         }
1967 
1968         /* ------------------------------------------------------------ */
1969         /*
1970          * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
1971          */
1972         public void log(String message, Throwable throwable)
1973         {
1974             _logger.warn(message,throwable);
1975         }
1976 
1977         /* ------------------------------------------------------------ */
1978         /*
1979          * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
1980          */
1981         public String getInitParameter(String name)
1982         {
1983             return ContextHandler.this.getInitParameter(name);
1984         }
1985 
1986         /* ------------------------------------------------------------ */
1987         /*
1988          * @see javax.servlet.ServletContext#getInitParameterNames()
1989          */
1990         @SuppressWarnings("unchecked")
1991         public Enumeration getInitParameterNames()
1992         {
1993             return ContextHandler.this.getInitParameterNames();
1994         }
1995 
1996         /* ------------------------------------------------------------ */
1997         /*
1998          * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
1999          */
2000         public synchronized Object getAttribute(String name)
2001         {
2002             Object o = ContextHandler.this.getAttribute(name);
2003             if (o == null && _contextAttributes != null)
2004                 o = _contextAttributes.getAttribute(name);
2005             return o;
2006         }
2007 
2008         /* ------------------------------------------------------------ */
2009         /*
2010          * @see javax.servlet.ServletContext#getAttributeNames()
2011          */
2012         @SuppressWarnings("unchecked")
2013         public synchronized Enumeration getAttributeNames()
2014         {
2015             HashSet<String> set = new HashSet<String>();
2016             if (_contextAttributes != null)
2017             {
2018                 Enumeration<String> e = _contextAttributes.getAttributeNames();
2019                 while (e.hasMoreElements())
2020                     set.add(e.nextElement());
2021             }
2022             Enumeration<String> e = _attributes.getAttributeNames();
2023             while (e.hasMoreElements())
2024                 set.add(e.nextElement());
2025 
2026             return Collections.enumeration(set);
2027         }
2028 
2029         /* ------------------------------------------------------------ */
2030         /*
2031          * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
2032          */
2033         public synchronized void setAttribute(String name, Object value)
2034         {
2035             checkManagedAttribute(name,value);
2036             Object old_value = _contextAttributes.getAttribute(name);
2037 
2038             if (value == null)
2039                 _contextAttributes.removeAttribute(name);
2040             else
2041                 _contextAttributes.setAttribute(name,value);
2042 
2043             if (_contextAttributeListeners != null)
2044             {
2045                 ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);
2046 
2047                 for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
2048                 {
2049                     ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i);
2050 
2051                     if (old_value == null)
2052                         l.attributeAdded(event);
2053                     else if (value == null)
2054                         l.attributeRemoved(event);
2055                     else
2056                         l.attributeReplaced(event);
2057                 }
2058             }
2059         }
2060 
2061         /* ------------------------------------------------------------ */
2062         /*
2063          * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
2064          */
2065         public synchronized void removeAttribute(String name)
2066         {
2067             checkManagedAttribute(name,null);
2068 
2069             if (_contextAttributes == null)
2070             {
2071                 // Set it on the handler
2072                 _attributes.removeAttribute(name);
2073                 return;
2074             }
2075 
2076             Object old_value = _contextAttributes.getAttribute(name);
2077             _contextAttributes.removeAttribute(name);
2078             if (old_value != null)
2079             {
2080                 if (_contextAttributeListeners != null)
2081                 {
2082                     ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
2083 
2084                     for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
2085                         ((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event);
2086                 }
2087             }
2088         }
2089 
2090         /* ------------------------------------------------------------ */
2091         /*
2092          * @see javax.servlet.ServletContext#getServletContextName()
2093          */
2094         public String getServletContextName()
2095         {
2096             String name = ContextHandler.this.getDisplayName();
2097             if (name == null)
2098                 name = ContextHandler.this.getContextPath();
2099             return name;
2100         }
2101 
2102         /* ------------------------------------------------------------ */
2103         public String getContextPath()
2104         {
2105             if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
2106                 return "";
2107 
2108             return _contextPath;
2109         }
2110 
2111         /* ------------------------------------------------------------ */
2112         @Override
2113         public String toString()
2114         {
2115             return "ServletContext@" + ContextHandler.this.toString();
2116         }
2117 
2118         /* ------------------------------------------------------------ */
2119         public boolean setInitParameter(String name, String value)
2120         {
2121             if (ContextHandler.this.getInitParameter(name) != null)
2122                 return false;
2123             ContextHandler.this.getInitParams().put(name,value);
2124             return true;
2125         }
2126 
2127     }
2128 
2129     private static class CLDump implements Dumpable
2130     {
2131         final ClassLoader _loader;
2132 
2133         CLDump(ClassLoader loader)
2134         {
2135             _loader = loader;
2136         }
2137 
2138         public String dump()
2139         {
2140             return AggregateLifeCycle.dump(this);
2141         }
2142 
2143         public void dump(Appendable out, String indent) throws IOException
2144         {
2145             out.append(String.valueOf(_loader)).append("\n");
2146 
2147             if (_loader != null)
2148             {
2149                 Object parent = _loader.getParent();
2150                 if (parent != null)
2151                 {
2152                     if (!(parent instanceof Dumpable))
2153                         parent = new CLDump((ClassLoader)parent);
2154 
2155                     if (_loader instanceof URLClassLoader)
2156                         AggregateLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
2157                     else
2158                         AggregateLifeCycle.dump(out,indent,Collections.singleton(parent));
2159                 }
2160             }
2161         }
2162 
2163     }
2164     
2165     
2166     /* ------------------------------------------------------------ */
2167     /** Interface to check aliases
2168      */
2169     public interface AliasCheck
2170     {
2171         /* ------------------------------------------------------------ */
2172         /** Check an alias
2173          * @param path The path the aliased resource was created for
2174          * @param resource The aliased resourced
2175          * @return True if the resource is OK to be served.
2176          */
2177         boolean check(String path, Resource resource);
2178     }
2179     
2180     
2181     /* ------------------------------------------------------------ */
2182     /** Approve Aliases with same suffix.
2183      * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
2184      * approved because both the resource and alias end with ".html".
2185      */
2186     public static class ApproveSameSuffixAliases implements AliasCheck
2187     {
2188         public boolean check(String path, Resource resource)
2189         {
2190             int dot = path.lastIndexOf('.');
2191             if (dot<0)
2192                 return false;
2193             String suffix=path.substring(dot);
2194             return resource.toString().endsWith(suffix);
2195         }
2196     }
2197     
2198     
2199     /* ------------------------------------------------------------ */
2200     /** Approve Aliases with a path prefix.
2201      * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
2202      * approved because both the resource and alias end with "/foobar.html".
2203      */
2204     public static class ApprovePathPrefixAliases implements AliasCheck
2205     {
2206         public boolean check(String path, Resource resource)
2207         {
2208             int slash = path.lastIndexOf('/');
2209             if (slash<0 || slash==path.length()-1)
2210                 return false;
2211             String suffix=path.substring(slash);
2212             return resource.toString().endsWith(suffix);
2213         }
2214     }
2215     /* ------------------------------------------------------------ */
2216     /** Approve Aliases of a non existent directory.
2217      * If a directory "/foobar/" does not exist, then the resource is 
2218      * aliased to "/foobar".  Accept such aliases.
2219      */
2220     public static class ApproveNonExistentDirectoryAliases implements AliasCheck
2221     {
2222         public boolean check(String path, Resource resource)
2223         {
2224             int slash = path.lastIndexOf('/');
2225             if (slash<0 || resource.exists())
2226                 return false;
2227             String suffix=path.substring(slash);
2228             return resource.getAlias().toString().endsWith(suffix);
2229         }
2230     }
2231 }